uAgents
Communicating with other uAgents πŸ“±πŸ€–οΈ

Communicating with other uAgents πŸ“±πŸ€–

Introduction

Communication is an essential feature of any agent network. It allows agents to work together, exchange information, and collaborate towards common goals. Within the uAgents (micro-agents) Framework, there are various ways through which uAgents can communicate, each offering unique advantages and use cases.

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

We want to start by introducing you to the concept of 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. Moreover, local communication is important for debugging purposes.

Let's get started!

uAgents: Local Communication

Walk-through

The first step to better understand how uAgents communicate is to introduce how 2 uAgents perform a local communication. Let’s consider a basic example in which two uAgents say hello to each other.

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

  2. Then, let's import these necessary classes from the uagents library: Agent, Context, Bureau, and Model Let's then define the message structure for messages to be exchanged between the uAgents using the class Model. Then, we would need to create the uAgents, alice and bob, with name and seed parameters.

    from uagents import Agent, Bureau, Context, Model
     
    class Message(Model):
        message: str
     
    alice = Agent(name="alice", seed="alice recovery phrase")
    bob = Agent(name="bob", seed="bob recovery phrase")
  3. Let's now define alice's behaviors. We need to define a function for alice to send messages to bob periodically:

    @alice.on_interval(period=3.0)
    async def send_message(ctx: Context):
       await ctx.send(bob.address, Message(message="hello there bob"))

    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 bob using the ctx.send() method of the Context object.

  4. We then need to define a alice_message_handler() function for alice to manage incoming messages:

    @alice.on_message(model=Message)
    async def alice_message_handler(ctx: Context, sender: str, msg: Message):
        ctx.logger.info(f"Received message from {sender}: {msg.message}")

    This defines the coroutine function alice_message_handler() that serves as a message handler for alice. It is triggered whenever alice receives a message of type Message. The function logs the received message and its sender using the ctx.logger.info() method.

  5. Let's now define the behavior of our second agent, bob:

    @bob.on_message(model=Message)
    async def bob_message_handler(ctx: Context, sender: str, msg: Message):
        ctx.logger.info(f"Received message from {sender}: {msg.message}")
        await ctx.send(alice.address, Message(message="hello there alice"))

    Here, we have defined a coroutine bob_message_handler() function that serves as the message handler for bob. It is triggered whenever bob receives a message of type Message from other agents. The function logs the received message and its sender using the ctx.logger.info() method. Finally, we make bob compose a response message to be sent back using the ctx.send() method with alice.address as the recipient address and an instance of the Message model as the message payload.

  6. Let's then use the Bureau class to create a Bureau object. This will allow us to run uAgents together in the same script.

    bureau = Bureau()
    bureau.add(alice)
    bureau.add(bob)
     
    if __name__ == "__main__":
        bureau.run()
  7. 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
 
alice = Agent(name="alice", seed="alice recovery phrase")
bob = Agent(name="bob", seed="bob recovery phrase")
 
@alice.on_interval(period=3.0)
async def send_message(ctx: Context):
   await ctx.send(bob.address, Message(message="hello there bob"))
 
@alice.on_message(model=Message)
async def alice_message_handler(ctx: Context, sender: str, msg: Message):
    ctx.logger.info(f"Received message from {sender}: {msg.message}")
 
@bob.on_message(model=Message)
async def bob_message_handler(ctx: Context, sender: str, msg: Message):
    ctx.logger.info(f"Received message from {sender}: {msg.message}")
    await ctx.send(alice.address, Message(message="hello there alice"))
 
bureau = Bureau()
bureau.add(alice)
bureau.add(bob)
if __name__ == "__main__":
    bureau.run()

We are now ready to run the script: python agents_communication.py

The output would be:

[alice]: Received message from agent1q0mau8vkmg78xx0sh8cyl4tpl4ktx94pqp2e94cylu6haugt2hd7j9vequ7: hello there alice
[  bob]: Received message from agent1qww3ju3h6kfcuqf54gkghvt2pqe8qp97a7nzm2vp8plfxflc0epzcjsv79t: hello there bob
[alice]: Received message from agent1q0mau8vkmg78xx0sh8cyl4tpl4ktx94pqp2e94cylu6haugt2hd7j9vequ7: hello there alice
[  bob]: Received message from agent1qww3ju3h6kfcuqf54gkghvt2pqe8qp97a7nzm2vp8plfxflc0epzcjsv79t: hello there bob
[alice]: Received message from agent1q0mau8vkmg78xx0sh8cyl4tpl4ktx94pqp2e94cylu6haugt2hd7j9vequ7: hello there alice

uAgents Remote Communication: the Almanac Contract

Remote uAgents communication is achieved by registering the uAgents into the Almanac contract ↗️️ and then querying it to retrieve an HTTP endpoint from the recipient uAgent. Registration in the Almanac requires paying a small fee, so make sure to have enough funds to allow for this.

Whenever a uAgent registers in the Almanac, it must specify the service endpoints ↗️️ alongside a weight parameter for each endpoint provided. Agents trying to communicate with your uAgent, will choose the service endpoints using a weighted random selection.

Here, we show you how to create two uAgents 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 uAgent:

Bob: touch remote_agents_bob.py

Alice: touch remote_agents_alice.py

Let's start by defining the script for alice.

Alice

  1. In remote_agents_alice.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. This is the address to which alice 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 uAgent, alice, by providing name, seed, port, and endpoint. Also, make sure it has enough funds to register in the Almanac contract.

    alice = Agent(
        name="alice",
        port=8000,
        seed="alice secret phrase",
        endpoint=["http://127.0.0.1:8000/submit"],
    )
     
    fund_agent_if_low(alice.wallet.address())

    On the Fetch.ai testnet, you can use the fund_agent_if_low function. This one checks if the balance of the uAgent'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 alice's wallet is low and funds it if necessary.

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

    @alice.on_interval(period=2.0)
    async def send_message(ctx: Context):
        await ctx.send(RECIPIENT_ADDRESS, Message(message="hello there bob")

    Here, the .on_interval() decorator schedules the send_message()coroutine 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 bob" to the RECIPIENT_ADDRESS.

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

    @alice.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__":
        alice.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 alice agent should be looking as follows:

remote_agents_alice.py
from uagents import Agent, Context, Model
from uagents.setup import fund_agent_if_low
 
class Message(Model):
    message: str
 
RECIPIENT_ADDRESS="agent1q2kxet3vh0scsf0sm7y2erzz33cve6tv5uk63x64upw5g68kr0chkv7hw50"
 
alice = Agent(
    name="alice",
    port=8000,
    seed="alice secret phrase",
    endpoint=["http://127.0.0.1:8000/submit"],
)
 
fund_agent_if_low(alice.wallet.address())
 
@alice.on_interval(period=2.0)
async def send_message(ctx: Context):
    await ctx.send(RECIPIENT_ADDRESS, Message(message="hello there bob"))
 
@alice.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__":
    alice.run()

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

Bob

  1. In remote_agents_bob.py script, import the necessary classes from the uagents and uagents.setup. Then, define the message structure for messages to be exchanged between the uAgents using the Model class, as well as our second uAgent, bob, 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
     
    bob = Agent(
        name="bob",
        port=8001,
        seed="bob secret phrase",
        endpoint=["http://127.0.0.1:8001/submit"],
    )
     
    fund_agent_if_low(bob.wallet.address())
  2. Let's now define a function for bob to handle incoming messages and answering back to the sender:

    @bob.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 alice"))
     
    if __name__ == "__main__":
        bob.run()

    Here, we have defined an asynchronous message_handler() function for bob 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 bob. 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 alice" message.

  3. Save the script.

The overall script for bob should be looking as follows:

remote_agents_bob.py
from uagents.setup import fund_agent_if_low
from uagents import Agent, Context, Model
 
class Message(Model):
    message: str
 
bob = Agent(
    name="bob",
    port=8001,
    seed="bob secret phrase",
    endpoint=["http://127.0.0.1:8001/submit"],
)
 
fund_agent_if_low(bob.wallet.address())
 
@bob.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 alice"))
 
if __name__ == "__main__":
    bob.run()

Run the scripts

In different terminal windows, first run bob and then alice from different terminals. 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_bob.py Terminal 2: python remote_agents_alice.py

The output will depend on the terminal:

  • Alice:

    [alice]: Received message from agent1q2kxet3vh0scsf0sm7y2erzz33cve6tv5uk63x64upw5g68kr0chkv7hw50: hello there alice
    [alice]: Received message from agent1q2kxet3vh0scsf0sm7y2erzz33cve6tv5uk63x64upw5g68kr0chkv7hw50: hello there alice
    [alice]: Received message from agent1q2kxet3vh0scsf0sm7y2erzz33cve6tv5uk63x64upw5g68kr0chkv7hw50: hello there alice
  • Bob:

    [  bob]: Received message from agent1qdp9j2ev86k3h5acaayjm8tpx36zv4mjxn05pa2kwesspstzj697xy5vk2a: hello there bob
    [  bob]: Received message from agent1qdp9j2ev86k3h5acaayjm8tpx36zv4mjxn05pa2kwesspstzj697xy5vk2a: hello there bob
    [  bob]: Received message from agent1qdp9j2ev86k3h5acaayjm8tpx36zv4mjxn05pa2kwesspstzj697xy5vk2a: hello there bob

uAgents Remote Communication: the AgentVerse Mailbox Service

uAgents can communicate remotely by also using the Agentverse ↗️ (opens in a new tab)️. The Agentverse is a platform that aims at creating a decentralized network of agents capable of communicating and interacting with each other. Agents in the Agentverse can send and receive messages, execute tasks, and collaborate with other agents to achieve various goals.

In this guide, we want to show how to enable remote communications between uAgents using the Agentverse Mailbox Service ↗️️.

Walk-through

We make use of the uAgents Remote Communication guide above, but now, we specify the Mailbox server and the API Key for our uAgents.

Alice

  1. First of all, let's create a script for alice: touch alice.py

  2. We need to import the necessary classes from uagents (Agent, Context, Model) and uagents.setup (fund_agent_if_low), and define the Message class for messages to be exchanged between our uAgents. We also need to generate a secure SEED_PHRASE (e.g., https://pypi.org/project/mnemonic/ (opens in a new tab)) and get the address of our agent, which is needed to register it to create a Mailbox, alongside a name (i.e., alice in this case). Following this, we would need to sign up at Agentverse ↗️ (opens in a new tab) to get an API key:

    from uagents import Agent, Context, Model
    from uagents.setup import fund_agent_if_low
     
    class Message(Model):
        message: str
     
    SEED_PHRASE = "put_your_seed_phrase_here"
     
    print(f"Your agent's address is: {Agent(seed=SEED_PHRASE).address}")
     
    API_KEY = "put_your_API_key_here"
  3. _Now your agent is ready to join the Agentverse!__ We can now register our uAgent, alice, by providing name, seed, and mailbox. Make sure your agent has enough funds for this:

    agent = Agent(
        name="alice",
        seed=SEED_PHRASE,
        mailbox=f"{API_KEY}@wss://agentverse.ai",
    )
     
    fund_agent_if_low(agent.wallet.address())

    On the Fetch.ai testnet, you can use the fund_agent_if_low function. This one checks if the balance of the uAgent’s wallet is below a certain threshold, and if so, sends a transaction to fund the wallet with a specified amount of cryptocurrency.

  4. Let's define a message handler function for alice:

    @agent.on_message(model=Message, replies={Message})
    async def handle_message(ctx: Context, sender: str, msg: Message):
        ctx.logger.info(f"Received message from {sender}: {msg.message}")
     
        ctx.logger.info("Sending message to bob")
        await ctx.send(sender, Message(message="hello there bob"))
     
    if __name__ == "__main__":
        agent.run()

    We have defined a handle_message() coroutine function that serves as the message handler for the agent. It is triggered whenever the agent receives a message of type Message. This function logs the received message and its sender using the ctx.logger.info() method. It then sends a response message back to the sender using the ctx.send() method with the sender address and an instance of the Message model.

  5. Save the script.

The overall script for alice should look as follows:

alice.py
from uagents import Agent, Context, Model
from uagents.setup import fund_agent_if_low
 
class Message(Model):
    message: str
 
SEED_PHRASE = "put_your_seed_phrase_here"
 
print(f"Your agent's address is: {Agent(seed=SEED_PHRASE).address}")
 
API_KEY = "put_your_API_key_here"
 
agent = Agent(
    name="alice",
    seed=SEED_PHRASE,
    mailbox=f"{API_KEY}@wss://agentverse.ai",
)
 
fund_agent_if_low(agent.wallet.address())
 
@agent.on_message(model=Message, replies={Message})
async def handle_message(ctx: Context, sender: str, msg: Message):
    ctx.logger.info(f"Received message from {sender}: {msg.message}")
 
    ctx.logger.info("Sending message to bob")
    await ctx.send(sender, Message(message="hello there bob"))
 
if __name__ == "__main__":
    agent.run()

Remember that you need to generate your SEED_PHRASE and API_KEY and substitute these into the above required fields for the script to run correctly.

Bob

  1. Let's now create another Python script for bob: touch bob.py

  2. We can now import the necessary classes from uagents and uagents.setup, and define the Message class for messages to be exchanged between our uAgents. We then need to define ALICE_ADDRESS by copying the address generated in the script for alice above, as well as generate a second SEED_PHRASE (e.g. https://pypi.org/project/mnemonic/ (opens in a new tab)), and get the address for our agent, which is needed to register it to create a Mailbox, alongside a name (i.e., bob in this case). Like for alice, head towards the Agentverse ↗️ (opens in a new tab)️ to get the API key for bob:

    from uagents import Agent, Context, Model
    from uagents.setup import fund_agent_if_low
     
    class Message(Model):
        message: str
     
    ALICE_ADDRESS = "paste_alice_address_here"
     
    SEED_PHRASE = "put_your_seed_phrase_here"
     
    print(f"Your agent's address is: {Agent(seed=SEED_PHRASE).address}")
     
    API_KEY = "put_your_API_key_here"
  3. Now your agent is ready to join the Agentverse! Let's register this second uAgent, bob, by providing name, seed, and mailbox server:

    agent = Agent(
        name="bob",
        seed=SEED_PHRASE,
        mailbox=f"{API_KEY}@wss://agentverse.ai",
    )
     
    fund_agent_if_low(agent.wallet.address())
  4. We can now define a function for bob to send messages:

    @agent.on_interval(period=2.0)
    async def send_message(ctx: Context):
        ctx.logger.info("Sending message to alice")
     
        await ctx.send(ALICE_ADDRESS, Message(message="hello there alice"))

    Here, we have defined a send_message() coroutine function that is scheduled to run periodically every 2 seconds using the .on_interval() decorator. Inside the coroutine function, a message of type Message is sent to alice's address using the ctx.send(9) method.

  5. Let's now define a message handler for bob to handle incoming messages:

    @agent.on_message(model=Message, replies=set())
    async def on_message(ctx: Context, sender: str, msg: Message):
        ctx.logger.info(f"Received message from {sender}: {msg.message}")
     
    if __name__ == "__main__":
        agent.run()

    Here, we have set up an on_message() function for bob to handle messages of type Message. When a message of this type is received by bob, the message handler function logs the sender's address and the content of the message using ctx.logger.info() method.

  6. Save the script.

The overall script for bob should look as follows:

bob.py
from uagents import Agent, Context, Model
from uagents.setup import fund_agent_if_low
 
class Message(Model):
    message: str
 
ALICE_ADDRESS = "paste_alice_address_here"
 
SEED_PHRASE = "put_your_seed_phrase_here"
 
print(f"Your agent's address is: {Agent(seed=SEED_PHRASE).address}")
 
API_KEY = "put_your_API_key_here"
 
bob = Agent(
    name="bob",
    seed=SEED_PHRASE,
    mailbox=f"{API_KEY}@wss://agentverse.ai",
)
 
fund_agent_if_low(bob.wallet.address())
 
@bob.on_interval(period=2.0)
async def send_message(ctx: Context):
    ctx.logger.info("Sending message to alice")
    await ctx.send(ALICE_ADDRESS, Message(message="hello there alice"))
 
@bob.on_message(model=Message, replies=set())
async def on_message(ctx: Context, sender: str, msg: Message):
    ctx.logger.info(f"Received message from {sender}: {msg.message}")
 
if __name__ == "__main__":
    bob.run()

Remember that you need to generate your SEED_PHRASE and API_KEY and substitute these into the above required fields for the script to run correctly. Here, you also need to provide bob with an ALICE_ADDRESS field.

Run the scripts

Now, we are ready to run our scripts. Run alice and bob from different terminals. The received messages will print out in each terminal:

Bob: python bob.py

Alice: python alice.py

The output should be as follows depending on the terminal:

  • Alice:

    [alice]: Received message from agent1q0p2mhaqjv46wmsn6jx3lnggwffnpuzsuy70ax6np07wdhlkjqytx7fq73t: hello there alice
    [alice]: Sending message to bob
    [alice]: Received message from agent1q0p2mhaqjv46wmsn6jx3lnggwffnpuzsuy70ax6np07wdhlkjqytx7fq73t: hello there alice
    [alice]: Sending message to bob
    [alice]: Received message from agent1q0p2mhaqjv46wmsn6jx3lnggwffnpuzsuy70ax6np07wdhlkjqytx7fq73t: hello there alice
  • Bob:

    [  bob]: Received message from agent1qvgt6q5ld2q6fm4x3xaptppgl5gpa5p3xx9g38n5ds3eht9h0gyn5nq0nl7: hello there bob
    [  bob]: Sending message to alice
    [  bob]: Received message from agent1qvgt6q5ld2q6fm4x3xaptppgl5gpa5p3xx9g38n5ds3eht9h0gyn5nq0nl7: hello there bob
    [  bob]: Sending message to alice
    [  bob]: Received message from agent1qvgt6q5ld2q6fm4x3xaptppgl5gpa5p3xx9g38n5ds3eht9h0gyn5nq0nl7: hello there bob

Conclusion

In this comprehensive guide, we explored two different methods of communication for uAgents using the uagents library:

  • Local communication.
  • Remote communication via the Almanac Contract or the AgentVerse Mailbox Service.

For local communication, we learned how to use the uagents library to create two uAgents, alice and bob, 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 uAgents 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.

In addition, we introduced the AgentVerse Mailbox Service to facilitate remote communication through the usage of the AgentVerse Explorer. We demonstrated the communication process by creating alice and bob scripts and utilizing their respective API keys to communicate remotely via such service. The decentralized network enabled messages transfer and information sharing between the remote agents. By following this last approach, users can leverage the power of the AgentVerse Explorer to build intricate networks of remotely interacting uAgents.

This opens up a world of possibilities for collaborative decision-making, distributed problem-solving, and decentralized coordination across a myriad of applications and domains. With this newfound knowledge, developers can harness the potential of autonomous agents to create innovative solutions and use cases in a rapidly evolving digital and decentralized landscape.