Communicating with other agents
Introduction
Communication is an essential feature within any agents network. Communication allows agents to work together, exchange information, and forms an organic marketplace.
In this guide, we will explore two methods of communication between agents:
- Local communication.
- Remote communication via the Almanac Contract .
Let's start with local communication. This is the first step you would need to undertake to familiarize yourself with the code syntax we will be using in the remote communication section.
Local communication is important for debugging purposes.
Prerequisites
Make sure you have read the following resources before going on with this guide:
- Quick Start Guide for uAgents Framework
- Creating your first agent
- Agents address
- Almanac contract
- Register in Almanac
Imports needed
Agents: Local Communication
Walk-through
The first step to better understand how agents communicate is to introduce how 2 agents perform a local communication. Let's consider a basic example in which two agents say hello to each other.
-
First of all, let's create a Python script for this task:
windowsecho. > agents_communication.py
-
Then, we import
Agent
,Context
,Bureau
, andModel
from the uagents library and we then define the message structure for messages to be exchanged between the agents using the classModel
:
agent_communication.pyfrom uagents import Agent, Bureau, Context, Model class Message(Model): message: str
The Message
class defines the structure of message we can receive. In this example it's just a string, but it could be a simple integer, or a complex object too.
- Now we create two agent instances,
sigmar
andslaanesh
, withname
andseed
parameters:
agent_communication.pysigmar = Agent(name="sigmar", seed="sigmar recovery phrase") slaanesh = Agent(name="slaanesh", seed="slaanesh recovery phrase")
In this example we're running multiple agents from one file.
- Let's now define
sigmar
's behaviors. We need to define a function forsigmar
to send messages toslaanesh
periodically:
agent_communication.py@sigmar.on_interval(period=3.0) async def send_message(ctx: Context): await ctx.send(slaanesh.address, Message(message="hello there slaanesh"))
We can use the .on_interval()
decorator to define a coroutine send_message()
function that will be called every 3 seconds. The coroutine function sends a message to slaanesh
using the ctx.send()
method of the Context
object.
- We then need to define a
sigmar_message_handler()
function forsigmar
to manage incoming messages:
agent_communication.py@sigmar.on_message(model=Message) async def sigmar_message_handler(ctx: Context, sender: str, msg: Message): ctx.logger.info(f"Received message from {sender}: {msg.message}")
This defines the coroutine function sigmar_message_handler()
that serves as a message handler for sigmar
. It is triggered whenever sigmar
receives a message of type Message
.
- Let's now define the behavior of our second agent,
slaanesh
:
agent_communication.py@slaanesh.on_message(model=Message) async def slaanesh_message_handler(ctx: Context, sender: str, msg: Message): ctx.logger.info(f"Received message from {sender}: {msg.message}")
Same as sigmar
however, we make slaanesh
compose a response message to be sent back using the ctx.send()
method with sigmar.address
as the recipient address and an instance of the Message
model as the message payload.
It would also be valid to respond to the sender:
agent_communication.pyawait ctx.send(sigmar.address, Message(message="hello there sigmar"))
- Let's then use the
Bureau
class to create aBureau
object. This will allow us to run agents together in the same script:
agent_communication.pybureau = Bureau() bureau.add(sigmar) bureau.add(slaanesh) if __name__ == "__main__": bureau.run()
- Save the script.
The complete script should be looking as follows:
agent_communication.pyfrom uagents import Agent, Bureau, Context, Model class Message(Model): message: str sigmar = Agent(name="sigmar", seed="sigmar recovery phrase") slaanesh = Agent(name="slaanesh", seed="slaanesh recovery phrase") @sigmar.on_interval(period=3.0) async def send_message(ctx: Context): await ctx.send(slaanesh.address, Message(message="hello there slaanesh")) @sigmar.on_message(model=Message) async def sigmar_message_handler(ctx: Context, sender: str, msg: Message): ctx.logger.info(f"Received message from {sender}: {msg.message}") @slaanesh.on_message(model=Message) async def slaanesh_message_handler(ctx: Context, sender: str, msg: Message): ctx.logger.info(f"Received message from {sender}: {msg.message}") await ctx.send(sigmar.address, Message(message="hello there sigmar")) bureau = Bureau() bureau.add(sigmar) bureau.add(slaanesh) if __name__ == "__main__": bureau.run()
We are now ready to run the script: python agents_communication.py
The output would be:
INFO: [bureau]: Starting server on http://0.0.0.0:8000 (Press CTRL+C to quit) INFO: [sigmar]: Received message from agent1q0mau8vkmg78xx0sh8cyl4tpl4ktx94pqp2e94cylu6haugt2hd7j9vequ7: hello there sigmar INFO: [slaanesh]: Received message from agent1qww3ju3h6kfcuqf54gkghvt2pqe8qp97a7nzm2vp8plfxflc0epzcjsv79t: hello there slaanesh INFO: [sigmar]: Received message from agent1q0mau8vkmg78xx0sh8cyl4tpl4ktx94pqp2e94cylu6haugt2hd7j9vequ7: hello there sigmar INFO: [slaanesh]: Received message from agent1qww3ju3h6kfcuqf54gkghvt2pqe8qp97a7nzm2vp8plfxflc0epzcjsv79t: hello there slaanesh INFO: [sigmar]: Received message from agent1q0mau8vkmg78xx0sh8cyl4tpl4ktx94pqp2e94cylu6haugt2hd7j9vequ7: hello there sigmar
Agents Remote Communication: the Almanac Contract
An agent must register to the Almanac contract to communicate, to search for other agents or be found. Agents can query this contract to retrieve an HTTP endpoint for a recipient agent. Registration in the Almanac requires paying a small fee, so make sure to have enough funds to allow for this. You can query the Almanac now, by using the search feature on Agentverse (opens in a new tab).
Whenever an agent registers in the Almanac, it must specify the service endpoints alongside a weight parameter for each endpoint provided. Agents trying to communicate with your agent will choose the service endpoints using a weighted random selection.
Here, we show you how to create two agents and make them remotely communicate by registering and using the Almanac Contract.
Walk-through
The first step would be to create two different Python scripts for this task, each one representing a remote agent:
Slaanesh:
windowsecho. > remote_agents_slaanesh.py
Sigmar:
windowsecho. > remote_agents_sigmar.py
Let's start by defining the script for sigmar.
Sigmar
- In
remote_agents_sigmar.py
script, we would need to import the necessary classes from theuagents
(Agent
,Context
, andModel
) and fromuagents.setup
(fund_agent_if_low
). We then need to define the message structure for messages to be exchanged between agents using the classModel
, as well as theRECIPIENT_ADDRESS
(slaanesh's address). Note that if you don't know slaanesh's address yet, you can useprint(slaanesh.address)
after defining agentslaanesh
to get this information. This is the address towards whichsigmar
will send messages:
remote_agents_sigmar.pyfrom uagents import Agent, Context, Model from uagents.setup import fund_agent_if_low class Message(Model): message: str RECIPIENT_ADDRESS = "agent1qvm7v76zs6w2x90xvq99yc5xh7c2thjtm44zc09me556zxnra627gkf4zum"
- Let's now create our agent,
sigmar
, by providingname
,seed
,port
, andendpoint
. Also, make sure it has enough funds to register in the Almanac contract:
remote_agents_sigmar.pysigmar = Agent( name="sigmar", port=8000, seed="sigmar secret phrase", endpoint=["http://127.0.0.1:8000/submit"], ) fund_agent_if_low(sigmar.wallet.address())
On the Fetch.ai testnet, you can use the fund_agent_if_low
function. This checks if the balance of the agent's wallet is below a certain threshold, and if so, sends a transaction to fund the wallet with a specified amount of cryptocurrency. In this case, it checks if the balance of sigmar
's wallet is low and funds it if necessary.
- We are ready to define
sigmar
's behaviors. Let's start with a function forsigmar
to send messages:
remote_agents_sigmar.py@sigmar.on_interval(period=2.0) async def send_message(ctx: Context): await ctx.send(RECIPIENT_ADDRESS, Message(message="hello there slaanesh"))
Here, the .on_interval()
decorator schedules the send_message()
function to be run every 2 seconds. Inside the function, there is an asynchronous call indicated by the ctx.send()
method. This call sends a message with the content "hello there slaanesh"
to the RECIPIENT_ADDRESS
.
- We then need to define a function for
sigmar
to handle incoming messages from other agents:
remote_agents_sigmar.py@sigmar.on_message(model=Message) async def message_handler(ctx: Context, sender: str, msg: Message): ctx.logger.info(f"Received message from {sender}: {msg.message}") if __name__ == "__main__": sigmar.run()
Here, we have used the .on_message()
decorator to register the message_handler()
coroutine function as a handler for incoming messages of type Message
.
The message_handler()
function takes three arguments: ctx
, sender
, and msg
. Inside this function, we call the ctx.logger.info()
method to log information about the received message, including the sender and message content.
- We can now save the script.
The overall script for sigmar agent should be looking as follows:
remote_agents_sigmar.pyfrom uagents import Agent, Context, Model from uagents.setup import fund_agent_if_low class Message(Model): message: str RECIPIENT_ADDRESS = "agent1qvm7v76zs6w2x90xvq99yc5xh7c2thjtm44zc09me556zxnra627gkf4zum" sigmar = Agent( name="sigmar", port=8000, seed="sigmar secret phrase", endpoint=["http://127.0.0.1:8000/submit"], ) fund_agent_if_low(sigmar.wallet.address()) @sigmar.on_interval(period=2.0) async def send_message(ctx: Context): await ctx.send(RECIPIENT_ADDRESS, Message(message="hello there slaanesh")) @sigmar.on_message(model=Message) async def message_handler(ctx: Context, sender: str, msg: Message): ctx.logger.info(f"Received message from {sender}: {msg.message}") if __name__ == "__main__": sigmar.run()
Remember that you need to provide the name
, seed
, port
, endpoint
and RECIPIENT_ADDRESS
parameters to correctly run this code.
We can now proceed by writing the script for agent slaanesh
.
Slaanesh
- In
remote_agents_slaanesh.py
script, import the necessary classes from theuagents
anduagents.setup
. Then, define the message structure for messages to be exchanged between the agents using theModel
class, as well as our second uAgent,slaanesh
, by providingname
,seed
,port
, andendpoint
. Make sure it has enough funds to register in the Almanac contract:
remote_agents_slaanesh.pyfrom uagents.setup import fund_agent_if_low from uagents import Agent, Context, Model class Message(Model): message: str slaanesh = Agent( name="slaanesh", port=8001, seed="slaanesh secret phrase", endpoint=["http://127.0.0.1:8001/submit"], ) fund_agent_if_low(slaanesh.wallet.address())
- Let's now define a function for
slaanesh
to handle incoming messages and answering back to the sender:
remote_agents_slaanesh.py@slaanesh.on_message(model=Message) async def message_handler(ctx: Context, sender: str, msg: Message): ctx.logger.info(f"Received message from {sender}: {msg.message}") await ctx.send(sender, Message(message="hello there sigmar")) if __name__ == "__main__": slaanesh.run()
Here, we have defined an asynchronous message_handler()
function for slaanesh to handle incoming messages from other uAgents. The function is decorated with .on_message()
, and it is triggered whenever a message of type Message
is received by slaanesh
. When a message is received, the handler function logs the sender's address and the content of the message. It then sends a response back to the sender using the ctx.send()
with a new message. The response message contains the Message
data model with a "hello there sigmar"
message.
- Save the script.
The overall script for slaanesh
should be looking as follows:
remote_agents_slaanesh.pyfrom uagents.setup import fund_agent_if_low from uagents import Agent, Context, Model class Message(Model): message: str slaanesh = Agent( name="slaanesh", port=8001, seed="slaanesh secret phrase", endpoint=["http://127.0.0.1:8001/submit"], ) fund_agent_if_low(slaanesh.wallet.address()) @slaanesh.on_message(model=Message) async def message_handler(ctx: Context, sender: str, msg: Message): ctx.logger.info(f"Received message from {sender}: {msg.message}") await ctx.send(sender, Message(message="hello there sigmar")) if __name__ == "__main__": slaanesh.run()
Remember that you need to provide the name
, seed
, port
and endpoint
parameters to correctly run this code.
Run the scripts
In different terminal windows, first run slaanesh
and then sigmar
. They will register automatically in the Almanac contract using their funds. The received messages will print out in each terminal:
Terminal 1: python remote_agents_slaanesh.py
Terminal 2: python remote_agents_sigmar.py
The output will depend on the terminal:
-
Sigmar:
INFO: [sigmar]: Registering on almanac contract... INFO: [sigmar]: Registering on almanac contract...complete INFO: [sigmar]: Received message from agent1qvm7v76zs6w2x90xvq99yc5xh7c2thjtm44zc09me556zxnra627gkf4zum: hello there sigmar INFO: [sigmar]: Received message from agent1qvm7v76zs6w2x90xvq99yc5xh7c2thjtm44zc09me556zxnra627gkf4zum: hello there sigmar INFO: [sigmar]: Received message from agent1qvm7v76zs6w2x90xvq99yc5xh7c2thjtm44zc09me556zxnra627gkf4zum: hello there sigmar
-
Slaanesh:
INFO: [slaanesh]: Registering on almanac contract... INFO: [slaanesh]: Registering on almanac contract...complete INFO: [slaanesh]: Starting server on http://0.0.0.0:8001 (Press CTRL+C to quit) INFO: [slaanesh]: Received message from agent1qvwqu6a0km09mq4f6j6kmke9smswmgcergmml9a54av9449rqtmmxy4qwe6: hello there slaanesh INFO: [slaanesh]: Received message from agent1qvwqu6a0km09mq4f6j6kmke9smswmgcergmml9a54av9449rqtmmxy4qwe6: hello there slaanesh INFO: [slaanesh]: Received message from agent1qvwqu6a0km09mq4f6j6kmke9smswmgcergmml9a54av9449rqtmmxy4qwe6: hello there slaanesh
Before we go on...
As we touched on before in Register in Almanac , when the agent uses .run()
function this tells the uagents
library to register the agent to the Almanac. It's simple, agents initialize themselves, and register to a service which acts as a search engine for agents (the Almanac) then, when agents receive messages they can respond.
Conclusion
In this guide, we explored two different methods of communication for Agents using the uagents
library:
- Local communication.
- Remote communication via the Almanac Contract.
For local communication, we learned how to use the uagents
library to create two agents, sigmar
and slaanesh
, and enable them to exchange messages with one another. We defined the message structure using the Model
class and implemented message handlers for both agents. By running the script we observed their real-time message exchange.
Next, we delved into remote communication, which facilitates interaction between agents through the Almanac Contract. This method requires registering the agents in the Almanac Contract and querying for HTTP endpoints for communication. By running the scripts separately, we could observe the real-time messages exchange, fostering a decentralized network of interacting agents.
With this, we suspect you're ready to start building agents, as part of multi agent system the Almanac allows; awesome. If you want to go further though, take a look at the message verification and sending tokens , after-all you do want to be sure you are speaking to who you think you are, and agents getting paid is awesome.