How to book a table at a restaurant using uAgents
Introduction
In this guide, we want to show how to set up the code to create a restaurant booking service with two uAgents: a restaurant with tables available, and a user requesting table availability.
We also want to define 2 specific protocols, one for table querying (i.e., Table querying protocol
) and one for table booking (i.e., Table booking protocol
). Then we will need to define two uAgents scripts, restaurant and user, which will make use of the protocols to query and book a table.
Walk-through
- First of all, we need to navigate towards the directory you created for your project and create a folder for this task:
mkdir booking_demo
. - Inside this folder we will create another folder for our
protocols
:mkdir protocols
. - After having defined our protocols, we will create 2 scripts, one for our
restaurant
and the other one foruser
uAgents. These uAgents will make use of the protocols to query, check, confirm and book an available table at the restaurant.
We can start by writing the code for our 2 protocols.
Protocols
Table querying protocol
Let's start by defining the protocol for querying availability of tables at the restaurant:
-
First of all, we need to navigate towards the protocols folder:
cd protocols
-
Let's then create a Python script named, and name it:
touch query.py
-
In the text editor application, we proceed and define the
table querying protocol
. -
We now need to import necessary classes and define the message data models. Then, create an instance of the
Protocol
class and name itquery_proto
:from typing import List from uagents import Context, Model, Protocol class TableStatus(Model): seats: int time_start: int time_end: int class QueryTableRequest(Model): guests: int time_start: int duration: int class QueryTableResponse(Model): tables: List[int] class GetTotalQueries(Model): pass class TotalQueries(Model): total_queries: int query_proto = Protocol()
TableStatus
: this represents the status of a table and includes the attributes number of seats, start time, and end time.QueryTableRequest
: this is used for querying table availability. It includes information about the number of guests, start time, and duration of the table request.QueryTableResponse
: this contains the response to the query table availability. It includes a list of table numbers that are available based on query parameters.GetTotalQueries
: this is used to request the total number of queries made to the system.TotalQueries
: this contains the response to the total queries request, including the count of total queries made to the system.
-
Let's then define the message handlers for the
query_proto
protocol:@query_proto.on_message(model=QueryTableRequest, replies=QueryTableResponse) async def handle_query_request(ctx: Context, sender: str, msg: QueryTableRequest): tables = { int(num): TableStatus(**status) for ( num, status, ) in ctx.storage._data.items() # pylint: disable=protected-access if isinstance(num, int) } available_tables = [] for number, status in tables.items(): if ( status.seats >= msg.guests and status.time_start <= msg.time_start and status.time_end >= msg.time_start + msg.duration ): available_tables.append(int(number)) ctx.logger.info(f"Query: {msg}. Available tables: {available_tables}.") await ctx.send(sender, QueryTableResponse(tables=available_tables)) total_queries = int(ctx.storage.get("total_queries") or 0) ctx.storage.set("total_queries", total_queries + 1) @query_proto.on_query(model=GetTotalQueries, replies=TotalQueries) async def handle_get_total_queries(ctx: Context, sender: str, _msg: GetTotalQueries): total_queries = int(ctx.storage.get("total_queries") or 0) await ctx.send(sender, TotalQueries(total_queries=total_queries))
-
handle_query_request()
: this message handler function is defined using the.on_message()
decorator. It handles theQueryTableRequest
messages and replies with aQueryTableResponse
message. The handler processes the table availability query based on the provided parameters, checks the table statuses stored in the agent's storage, and sends the available table numbers as a response to the querying agent. Additionally, the handler tracks the total number of queries made and increments the count in storage. -
handle_get_total_queries()
: this message handler function is defined using the.on_query
decorator. It handles theGetTotalQueries
query and replies with aTotalQueries
message containing the total number of queries made to the system. The handler retrieves the total query count from the agent's storage and responds with the count.
-
-
Save the script.
The overall script should look as follows:
from typing import List
from uagents import Context, Model, Protocol
class TableStatus(Model):
seats: int
time_start: int
time_end: int
class QueryTableRequest(Model):
guests: int
time_start: int
duration: int
class QueryTableResponse(Model):
tables: List[int]
class GetTotalQueries(Model):
pass
class TotalQueries(Model):
total_queries: int
query_proto = Protocol()
@query_proto.on_message(model=QueryTableRequest, replies=QueryTableResponse)
async def handle_query_request(ctx: Context, sender: str, msg: QueryTableRequest):
tables = {
int(num): TableStatus(**status)
for (
num,
status,
) in ctx.storage._data.items() # pylint: disable=protected-access
if isinstance(num, int)
}
available_tables = []
for number, status in tables.items():
if (
status.seats >= msg.guests
and status.time_start <= msg.time_start
and status.time_end >= msg.time_start + msg.duration
):
available_tables.append(int(number))
ctx.logger.info(f"Query: {msg}. Available tables: {available_tables}.")
await ctx.send(sender, QueryTableResponse(tables=available_tables))
total_queries = int(ctx.storage.get("total_queries") or 0)
ctx.storage.set("total_queries", total_queries + 1)
@query_proto.on_query(model=GetTotalQueries, replies=TotalQueries)
async def handle_get_total_queries(ctx: Context, sender: str, _msg: GetTotalQueries):
total_queries = int(ctx.storage.get("total_queries") or 0)
await ctx.send(sender, TotalQueries(total_queries=total_queries))
Table booking protocol
We can now proceed by writing the booking protocol script for booking the table at the restaurant.
-
First of all, navigate towards the protocols folder:
cd protocols
-
In here, let's create a Python script and name it:
touch book.py
-
In the text editor application, we can proceed to define the table booking protocol. We need to import the necessary classes and define the message data models. In this case, the booking protocol consists of two message models:
BookTableRequest
andBookTableResponse
. Then create an instance of theProtocol
class and name itbook_proto
:from uagents import Context, Model, Protocol from .query import TableStatus class BookTableRequest(Model): table_number: int time_start: int duration: int class BookTableResponse(Model): success: bool book_proto = Protocol()
BookTableRequest
: this represents the request to book a table. It includes attributes:table_number
to be booked,time_start
of the booking, and theduration
of the booking.BookTableResponse
: this contains the response to the table booking request. It includes a boolean attributesuccess
, indicating whether the booking was successful or not.
-
Let's now define the message handler function:
@book_proto.on_message(model=BookTableRequest, replies=BookTableResponse) async def handle_book_request(ctx: Context, sender: str, msg: BookTableRequest): tables = { int(num): TableStatus(**status) for ( num, status, ) in ctx.storage._data.items() # pylint: disable=protected-access if isinstance(num, int) } table = tables[msg.table_number] if ( table.time_start <= msg.time_start and table.time_end >= msg.time_start + msg.duration ): success = True table.time_start = msg.time_start + msg.duration ctx.storage.set(msg.table_number, table.dict()) else: success = False # send the response await ctx.send(sender, BookTableResponse(success=success))
The
handle_book_request()
handler first retrieves table statuses from the agent's storage and converts them into a dictionary with integer keys (table numbers) andTableStatus
values. TheTableStatus
class is imported from thequery
module. Next, the handler gets the table associated with the requestedtable_number
from thetables
dictionary. The handler checks if the requestedtime_start
falls within the availability period of the table. If the table is available for the requested booking duration, the handler setssuccess
toTrue
, updates the table'stime_start
to reflect the end of the booking, and saves the updated table information in the agent's storage usingctx.storage.set()
. If the table is not available for the requested booking, the handler setssuccess
toFalse
.The handler sends a
BookTableResponse
message back to the sender with thesuccess
status of the booking using awaitctx.send()
method. -
Save the script.
The overall script should look as follows:
from uagents import Context, Model, Protocol
from .query import TableStatus
class BookTableRequest(Model):
table_number: int
time_start: int
duration: int
class BookTableResponse(Model):
success: bool
book_proto = Protocol()
@book_proto.on_message(model=BookTableRequest, replies=BookTableResponse)
async def handle_book_request(ctx: Context, sender: str, msg: BookTableRequest):
tables = {
int(num): TableStatus(**status)
for (
num,
status,
) in ctx.storage._data.items() # pylint: disable=protected-access
if isinstance(num, int)
}
table = tables[msg.table_number]
if (
table.time_start <= msg.time_start
and table.time_end >= msg.time_start + msg.duration
):
success = True
table.time_start = msg.time_start + msg.duration
ctx.storage.set(msg.table_number, table.dict())
else:
success = False
# send the response
await ctx.send(sender, BookTableResponse(success=success))
Restaurant uAgent
We are now ready to define our restaurant uAgent.
-
Let's now create a Python script in
booking_demo
folder, and name it:touch restaurant.py
-
We then need to import the necessary classes from the
uagents
library and the two protocols we previously defined:from uagents import Agent from uagents.setup import fund_agent_if_low restaurant = Agent( name="restaurant", port=8001, seed="restaurant secret phrase", endpoint=["http://127.0.0.1:8001/submit"], ) fund_agent_if_low(restaurant.wallet.address())
-
Let's build the
restaurant
agent from above protocols and set the table availability information:from protocols.book import book_proto from protocols.query import query_proto, TableStatus # build the restaurant agent from stock protocols restaurant.include(query_proto) restaurant.include(book_proto) TABLES = { 1: TableStatus(seats=2, time_start=16, time_end=22), 2: TableStatus(seats=4, time_start=19, time_end=21), 3: TableStatus(seats=4, time_start=17, time_end=19), }
-
We would then need to store the
TABLES
information in the restaurant agent and run it:for (number, status) in TABLES.items(): restaurant._storage.set(number, status.dict()) if __name__ == "__main__": restaurant.run()
-
Save the script.
The restaurant agent is now online and ready to receive messages.
The overall script should look as follows:
from uagents import Agent, Context
from uagents.setup import fund_agent_if_low
from protocols.book import book_proto
from protocols.query import query_proto, TableStatus
restaurant = Agent(
name="restaurant",
port=8001,
seed="restaurant secret phrase",
endpoint=["http://127.0.0.1:8001/submit"],
)
fund_agent_if_low(restaurant.wallet.address())
# build the restaurant agent from stock protocols
restaurant.include(query_proto)
restaurant.include(book_proto)
TABLES = {
1: TableStatus(seats=2, time_start=16, time_end=22),
2: TableStatus(seats=4, time_start=19, time_end=21),
3: TableStatus(seats=4, time_start=17, time_end=19),
}
# set the table availability information in the restaurant protocols
for (number, status) in TABLES.items():
restaurant._storage.set(number, status.dict())
if __name__ == "__main__":
restaurant.run()
User uAgent
We can finally define the script for our user uAgent querying and booking a table at the restaurant.
-
First of all, let's create a Python script in
booking_demo
folder, and name it:touch user.py
-
We then need to import necessary classes from the
uagents
library and the two protocols defined above. We also need the restaurant agent's address to be able to communicate with it:from uagents import Agent, Context from uagents.setup import fund_agent_if_low from protocols.book import BookTableRequest, BookTableResponse from protocols.query import ( QueryTableRequest, QueryTableResponse, ) RESTAURANT_ADDRESS = "agent1qw50wcs4nd723ya9j8mwxglnhs2kzzhh0et0yl34vr75hualsyqvqdzl990" user = Agent( name="user", port=8000, seed="user secret phrase", endpoint=["http://127.0.0.1:8000/submit"], ) fund_agent_if_low(user.wallet.address())
-
Let's then create the table query to generate the
QueryTableRequest
using therestaurant
address. If the request has not been completed before, we send the request to the restaurant agent. Then create aninterval()
function which performs a table query request on a defined period to the restaurant, to query the availability of a table given thetable_query
parameters:table_query = QueryTableRequest( guests=3, time_start=19, duration=2, ) @user.on_interval(period=3.0, messages=QueryTableRequest) async def interval(ctx: Context): completed = ctx.storage.get("completed") if not completed: await ctx.send(RESTAURANT_ADDRESS, table_query)
-
Define the message handler function for incoming
QueryTableResponse
messages from therestaurant
agent:@user.on_message(QueryTableResponse, replies={BookTableRequest}) async def handle_query_response(ctx: Context, sender: str, msg: QueryTableResponse): if len(msg.tables) > 0: ctx.logger.info("There is a free table, attempting to book one now") table_number = msg.tables[0] request = BookTableRequest( table_number=table_number, time_start=table_query.time_start, duration=table_query.duration, ) await ctx.send(sender, request) else: ctx.logger.info("No free tables - nothing more to do") ctx.storage.set("completed", True)
This function activates when a message is received back from the
restaurant
agent.handle_query_response()
will evaluate if there is a table available, and if so, respond with aBookTableRequest
to complete the reservation. -
Let's then define a function which will handle messages from the restaurant agent on whether the reservation was successful or not:
@user.on_message(BookTableResponse, replies=set()) async def handle_book_response(ctx: Context, _sender: str, msg: BookTableResponse): if msg.success: ctx.logger.info("Table reservation was successful") else: ctx.logger.info("Table reservation was UNSUCCESSFUL") ctx.storage.set("completed", True) if __name__ == "__main__": user.run()
-
Save the script.
The overall script should look as follows:
from protocols.book import BookTableRequest, BookTableResponse
from protocols.query import (
QueryTableRequest,
QueryTableResponse,
)
from uagents import Agent, Context
from uagents.setup import fund_agent_if_low
RESTAURANT_ADDRESS = "agent1qw50wcs4nd723ya9j8mwxglnhs2kzzhh0et0yl34vr75hualsyqvqdzl990"
user = Agent(
name="user",
port=8000,
seed="user secret phrase",
endpoint=["http://127.0.0.1:8000/submit"],
)
fund_agent_if_low(user.wallet.address())
table_query = QueryTableRequest(
guests=3,
time_start=19,
duration=2,
)
# This on_interval agent function performs a request on a defined period
@user.on_interval(period=3.0, messages=QueryTableRequest)
async def interval(ctx: Context):
completed = ctx.storage.get("completed")
if not completed:
await ctx.send(RESTAURANT_ADDRESS, table_query)
@user.on_message(QueryTableResponse, replies={BookTableRequest})
async def handle_query_response(ctx: Context, sender: str, msg: QueryTableResponse):
if len(msg.tables) > 0:
ctx.logger.info("There is a free table, attempting to book one now")
table_number = msg.tables[0]
request = BookTableRequest(
table_number=table_number,
time_start=table_query.time_start,
duration=table_query.duration,
)
await ctx.send(sender, request)
else:
ctx.logger.info("No free tables - nothing more to do")
ctx.storage.set("completed", True)
@user.on_message(BookTableResponse, replies=set())
async def handle_book_response(ctx: Context, _sender: str, msg: BookTableResponse):
if msg.success:
ctx.logger.info("Table reservation was successful")
else:
ctx.logger.info("Table reservation was UNSUCCESSFUL")
ctx.storage.set("completed", True)
if __name__ == "__main__":
user.run()
Run the scripts
Run the restaurant
agent and then the user
agent from different terminals:
-
Terminal 1:
python restaurant.py
-
Terminal 2:
python user.py
The output should be as follows, depending on the terminal:
- Restaurant
[restaurant]: Query: guests=3 time_start=19 duration=2. Available tables: [2].
- User
[ user]: There is a free table, attempting to book one now
[ user]: Table reservation was successful