AI Agents
Communicating with other agents ๐Ÿ“ฑ๐Ÿค–๏ธ
Bookmark

Communicating with other agents ๐Ÿ“ฑ๐Ÿค–

Introduction

Communication is an essential feature agent network. It allows agents to work together, exchange information, and forms an organic marketplace.

In this guide, we will explore two methods of communication between agents:

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.

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.

  1. First of all, let's create a Python script for this task and name it by running: touch agents_communication.py

  2. Then, we import Agent, Context, Bureau, and Model from the uagents library and we then define the message structure for messages to be exchanged between the agents using the class Model:

    from 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.

  3. Now we create two agent instances, sigmar and slaanesh, with name and seed parameters:

    sigmar = 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.

  4. Let's now define sigmar's behaviors. We need to define a function for sigmar to send messages to slaanesh periodically:

    @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.

  5. We then need to define a sigmar_message_handler() function for sigmar to manage incoming messages:

    @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.

  6. Let's now define the behavior of our second agent, slaanesh:

    @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"))

    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:

    await ctx.send(sender.address, Message(message="hello there sigmar"))
  7. Let's then use the Bureau class to create a Bureau object. This will allow us to run agents together in the same script:

    bureau = Bureau()
    bureau.add(sigmar)
    bureau.add(slaanesh)
     
    if __name__ == "__main__":
        bureau.run()
  8. Save the script.

The complete script should be looking as follows:

agents_communication.py
from 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:

[sigmar]: Received message from agent1q0mau8vkmg78xx0sh8cyl4tpl4ktx94pqp2e94cylu6haugt2hd7j9vequ7: hello there sigmar
[  slaanesh]: Received message from agent1qww3ju3h6kfcuqf54gkghvt2pqe8qp97a7nzm2vp8plfxflc0epzcjsv79t: hello there slaanesh
[sigmar]: Received message from agent1q0mau8vkmg78xx0sh8cyl4tpl4ktx94pqp2e94cylu6haugt2hd7j9vequ7: hello there sigmar
[  slaanesh]: Received message from agent1qww3ju3h6kfcuqf54gkghvt2pqe8qp97a7nzm2vp8plfxflc0epzcjsv79t: hello there slaanesh
[sigmar]: Received message from agent1q0mau8vkmg78xx0sh8cyl4tpl4ktx94pqp2e94cylu6haugt2hd7j9vequ7: hello there sigmar

Agents Remote Communication: the Almanac Contract

To speak, search or be found, your agent must register to the Almanac contract โ†—๏ธ. Agents then query this 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: touch remote_agents_slaanesh.py

Sigmar: touch remote_agents_sigmar.py

Let's start by defining the script for sigmar.

Sigmar

  1. In remote_agents_sigmar.py script, we would need to import the necessary classes from the uagents (Agent, Context, and Model) and from uagents.setup (fund_agent_if_low). We then need to define the message structure for messages to be exchanged between agents using the class Model, as well as the RECIPIENT_ADDRESS (slaanesh's address). Note that if you don't know slaanesh's address yet, you can use print(slaanesh.address) after defining agent slaanesh to get this information. This is the address towards which sigmar will send messages:

    from uagents import Agent, Context, Model
    from uagents.setup import fund_agent_if_low
     
    class Message(Model):
        message: str
     
    RECIPIENT_ADDRESS="agent1q2kxet3vh0scsf0sm7y2erzz33cve6tv5uk63x64upw5g68kr0chkv7hw50"
  2. Let's now create our agent, sigmar, by providing name, seed, port, and endpoint. Also, make sure it has enough funds to register in the Almanac contract:

    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())

    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.

  3. We are ready to define sigmar's behaviors. Let's start with a function for sigmar to send messages:

    @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.

  4. We then need to define a function for sigmar to handle incoming messages from other agents:

    @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.

  5. We can now save the script.

The overall script for sigmar agent should be looking as follows:

remote_agents_sigmar.py
from uagents import Agent, Context, Model
from uagents.setup import fund_agent_if_low
 
class Message(Model):
    message: str
 
RECIPIENT_ADDRESS="agent1q2kxet3vh0scsf0sm7y2erzz33cve6tv5uk63x64upw5g68kr0chkv7hw50"
 
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()

We can now proceed by writing the script for agent slaanesh.

Slaanesh

  1. In remote_agents_slaanesh.py script, import the necessary classes from the uagents and uagents.setup. Then, define the message structure for messages to be exchanged between the agents using the Model class, as well as our second uAgent, slaanesh, by providing name, seed, port, and endpoint. Make sure it has enough funds to register in the Almanac contract:

    from uagents import Agent, Context, Model
    from uagents.setup import fund_agent_if_low
     
    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())
  2. Let's now define a function for slaanesh to handle incoming messages and answering back to the sender:

    @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.

  3. Save the script.

The overall script for slaanesh should be looking as follows:

remote_agents_slaanesh.py
from 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()

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:

    [sigmar]: Received message from agent1q2kxet3vh0scsf0sm7y2erzz33cve6tv5uk63x64upw5g68kr0chkv7hw50: hello there sigmar
    [sigmar]: Received message from agent1q2kxet3vh0scsf0sm7y2erzz33cve6tv5uk63x64upw5g68kr0chkv7hw50: hello there sigmar
    [sigmar]: Received message from agent1q2kxet3vh0scsf0sm7y2erzz33cve6tv5uk63x64upw5g68kr0chkv7hw50: hello there sigmar
  • Slaanesh:

    [  slaanesh]: Received message from agent1qdp9j2ev86k3h5acaayjm8tpx36zv4mjxn05pa2kwesspstzj697xy5vk2a: hello there slaanesh
    [  slaanesh]: Received message from agent1qdp9j2ev86k3h5acaayjm8tpx36zv4mjxn05pa2kwesspstzj697xy5vk2a: hello there slaanesh
    [  slaanesh]: Received message from agent1qdp9j2ev86k3h5acaayjm8tpx36zv4mjxn05pa2kwesspstzj697xy5vk2a: 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.

Was this page helpful?

Bookmark