Use an Agent as a tool for another agent.¶
Multi-Agent systems are complicated! Enter the A2A protocol by Google: this protocol allows for simpler communication between agents, and easily enables the use of an agent as a tool for another agent. In this tutorial we'll show how you can create a few agents with any-agent and have an agent be provided as a tool for the other agent.
This tutorial assumes basic familiarity with any-agent: if you haven't used any-agent before you may also find the Creating your first agent cookbook to be useful. You also may find the other A2A related cookbook to be useful: Serve an Agent with A2A
Note: because this tutorial relies upon advanced stdio/stderr communication using the MCP Server, it cannot be run on Google Colab.
Install Dependencies¶
any-agent uses the python asyncio module to support async functionality. When running in Jupyter notebooks, this means we need to enable the use of nested event loops. We'll install any-agent and enable this below using nest_asyncio.
%pip install 'any-agent[a2a]'
import nest_asyncio
nest_asyncio.apply()
import os
from getpass import getpass
# This notebook communicates with OpenAI GPT models using the OpenAI API.
if "OPENAI_API_KEY" not in os.environ:
print("OPENAI_API_KEY not found in environment!")
api_key = getpass("Please enter your OPENAI_API_KEY: ")
os.environ["OPENAI_API_KEY"] = api_key
print("OPENAI_API_KEY set for this session!")
else:
print("OPENAI_API_KEY found in environment.")
OPENAI_API_KEY found in environment.
Configure the first two agents and serve them over A2A¶
Let's give our first two "helper" agents some very simple capabilities. For this demo, we'll use the async method agent.serve_async
so that we can easily run all of the agents from inside the notebook in a single python process.
from any_agent import AgentConfig, AnyAgent
from any_agent.config import MCPStdio, ServingConfig, TracingConfig
# This MCP Tool relies upon uvx https://docs.astral.sh/uv/getting-started/installation/
time_tool = MCPStdio(
command="uvx",
args=["mcp-server-time", "--local-timezone=America/New_York"],
tools=[
"get_current_time",
],
)
time = await AnyAgent.create_async(
"openai", # See all options in https://mozilla-ai.github.io/any-agent/
AgentConfig(
model_id="gpt-4.1-nano",
name="time_agent",
description="I'm an agent to help with getting the time",
tools=[time_tool],
),
tracing=TracingConfig(console=False),
)
(time_task, time_server) = await time.serve_async(
ServingConfig(port=5000, endpoint="/time")
)
weather_expert = await AnyAgent.create_async(
"google",
AgentConfig(
model_id="gpt-4.1-nano",
name="weather_expert",
instructions="You're an expert that is an avid skier, recommend a great location to ski given a time of the year",
description="I'm an agent that is an expert in recommending my favorite location given a time of the year",
),
tracing=TracingConfig(console=False),
)
(weather_task, weather_server) = await weather_expert.serve_async(
ServingConfig(port=5001, endpoint="/location_recommender")
)
Configure and use the Third Agent¶
Now that the first two agents are serving over A2A, our main agent can be given access to use it just like a tool! In order to do this you use the a2a_tool
function which retrieves the info about the agent and allows you to call it.
from any_agent import AgentConfig, AnyAgent
from any_agent.config import TracingConfig
from any_agent.tools import a2a_tool
config = AgentConfig(
model_id="gpt-4.1-mini",
instructions="Use the available tools to obtain additional information to answer the query.",
description="The orchestrator that can use other agents via tools using the A2A protocol.",
tools=[
await a2a_tool(
"http://localhost:5000/time",
http_kwargs={
"timeout": 5
}, # This gives the time agent up to 5 seconds to respond to each request
),
await a2a_tool(
"http://localhost:5001/location_recommender",
http_kwargs={
"timeout": 5
}, # This gives the weather agent up to 5 seconds to respond to each request
),
],
)
agent = await AnyAgent.create_async(
agent_framework="agno",
agent_config=config,
tracing=TracingConfig(console=True),
)
agent_trace = await agent.run_async("Where should I go this weekend to ski?")
print(agent_trace.final_output)
╭──────────────────────────────────────────── CALL_LLM: gpt-4.1-mini ─────────────────────────────────────────────╮ │ ╭─ INPUT ─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ [ │ │ │ │ { │ │ │ │ "role": "system", │ │ │ │ "content": "<instructions>\nUse the available tools to obtain additional information to answer the quer │ │ │ │ }, │ │ │ │ { │ │ │ │ "role": "user", │ │ │ │ "content": "Where should I go this weekend to ski?" │ │ │ │ } │ │ │ │ ] │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ │ ╭─ OUTPUT ────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ [ │ │ │ │ { │ │ │ │ "tool.name": "call_time_agent", │ │ │ │ "tool.args": "{\"query\":\"current date\"}" │ │ │ │ } │ │ │ │ ] │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ │ ╭─ USAGE ─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ { │ │ │ │ "input_tokens": 187, │ │ │ │ "output_tokens": 16, │ │ │ │ "input_cost": 7.48e-05, │ │ │ │ "output_cost": 2.56e-05 │ │ │ │ } │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭──────────────────────────────────────────── CALL_LLM: gpt-4.1-nano ─────────────────────────────────────────────╮ │ ╭─ INPUT ─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ [ │ │ │ │ { │ │ │ │ "role": "user", │ │ │ │ "content": "current date" │ │ │ │ } │ │ │ │ ] │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ │ ╭─ OUTPUT ────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ [ │ │ │ │ { │ │ │ │ "tool.name": "get_current_time", │ │ │ │ "tool.args": "{\"timezone\":\"America/New_York\"}" │ │ │ │ } │ │ │ │ ] │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ │ ╭─ USAGE ─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ { │ │ │ │ "input_tokens": 212, │ │ │ │ "output_tokens": 18, │ │ │ │ "input_cost": 2.12e-05, │ │ │ │ "output_cost": 7.2e-06 │ │ │ │ } │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭──────────────────────────────────────── EXECUTE_TOOL: get_current_time ─────────────────────────────────────────╮ │ ╭─ Input ─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ { │ │ │ │ "timezone": "America/New_York" │ │ │ │ } │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ │ ╭─ OUTPUT ────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ { │ │ │ │ "type": "text", │ │ │ │ "text": "{\n \"timezone\": \"America/New_York\",\n \"datetime\": \"2025-05-30T16:53:17-04:00\",\n \"is │ │ │ │ "annotations": null │ │ │ │ } │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭──────────────────────────────────────────── CALL_LLM: gpt-4.1-nano ─────────────────────────────────────────────╮ │ ╭─ OUTPUT ────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ The current date is May 30, 2025. │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ │ ╭─ USAGE ─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ { │ │ │ │ "input_tokens": 295, │ │ │ │ "output_tokens": 13, │ │ │ │ "input_cost": 2.95e-05, │ │ │ │ "output_cost": 5.199999999999999e-06 │ │ │ │ } │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭───────────────────────────────────────── EXECUTE_TOOL: call_time_agent ─────────────────────────────────────────╮ │ ╭─ Input ─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ { │ │ │ │ "query": "current date" │ │ │ │ } │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ │ ╭─ OUTPUT ────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ { │ │ │ │ "id": "f991f48f8b124be593a04453ad7686d2", │ │ │ │ "jsonrpc": "2.0", │ │ │ │ "result": { │ │ │ │ "contextId": null, │ │ │ │ "kind": "message", │ │ │ │ "messageId": "25252436-9aa4-473d-b818-8e1f98478b17", │ │ │ │ "metadata": null, │ │ │ │ "parts": [ │ │ │ │ { │ │ │ │ "kind": "text", │ │ │ │ "metadata": null, │ │ │ │ "text": "The current date is May 30, 2025." │ │ │ │ } │ │ │ │ ], │ │ │ │ "referenceTaskIds": null, │ │ │ │ "role": "agent", │ │ │ │ "taskId": null │ │ │ │ } │ │ │ │ } │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭──────────────────────────────────────────── CALL_LLM: gpt-4.1-mini ─────────────────────────────────────────────╮ │ ╭─ OUTPUT ────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ [ │ │ │ │ { │ │ │ │ "tool.name": "call_weather_expert", │ │ │ │ "tool.args": "{\"query\":\"best ski locations in late May\"}" │ │ │ │ } │ │ │ │ ] │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ │ ╭─ USAGE ─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ { │ │ │ │ "input_tokens": 323, │ │ │ │ "output_tokens": 21, │ │ │ │ "input_cost": 0.0001292, │ │ │ │ "output_cost": 3.36e-05 │ │ │ │ } │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭──────────────────────────────────────────── CALL_LLM: gpt-4.1-nano ─────────────────────────────────────────────╮ │ ╭─ INPUT ─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ [ │ │ │ │ { │ │ │ │ "role": "system", │ │ │ │ "content": "You're an expert that is an avid skier, recommend a great location to ski given a time of t │ │ │ │ }, │ │ │ │ { │ │ │ │ "role": "user", │ │ │ │ "content": "best ski locations in late May" │ │ │ │ } │ │ │ │ ] │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ │ ╭─ OUTPUT ────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ In late May, most European ski resorts have closed for the season, but there are still excellent options │ │ │ │ for late-season skiing, especially at high-altitude or glacial locations. Here are some of the best ski │ │ │ │ destinations to consider: │ │ │ │ │ │ │ │ 1 Zermatt, Switzerland │ │ │ │ • Zermatt offers year-round skiing on the Theodulgletscher glacier, making it one of the top choices │ │ │ │ for late May. The high altitude (up to 3,883 meters) ensures good snow conditions. │ │ │ │ 2 Les Deux Alpes, France │ │ │ │ • This resort has a large glacier that typically stays open through May. Its extensive ski area and │ │ │ │ reliable snow make it a popular late-season destination. │ │ │ │ 3 Hintertux Glacier, Austria │ │ │ │ • Open 365 days a year, Hintertux offers excellent skiing conditions in late May. It's known for │ │ │ │ diverse terrain and consistent snowpack. │ │ │ │ 4 Tignes and Val d'Isère, France │ │ │ │ • These neighboring resorts, connected via the Espace Killy, remain open into May thanks to their high │ │ │ │ altitude and glacier skiing options. │ │ │ │ 5 Whistler Blackcomb, Canada │ │ │ │ • Usually operating until late May or early June, Whistler offers extensive terrain and reliable spring │ │ │ │ snow conditions, especially on the Blackcomb Glacier. │ │ │ │ │ │ │ │ Tip: Always check current snow conditions and resort opening dates before planning your trip, as weather │ │ │ │ patterns can affect glacier conditions and overall access. │ │ │ │ │ │ │ │ Would you like tailored recommendations based on your preferred country or specific ski experience? │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ │ ╭─ USAGE ─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ { │ │ │ │ "input_tokens": 53, │ │ │ │ "output_tokens": 310, │ │ │ │ "input_cost": 5.3e-06, │ │ │ │ "output_cost": 0.000124 │ │ │ │ } │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭─────────────────────────────────────── EXECUTE_TOOL: call_weather_expert ───────────────────────────────────────╮ │ ╭─ Input ─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ { │ │ │ │ "query": "best ski locations in late May" │ │ │ │ } │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ │ ╭─ OUTPUT ────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ { │ │ │ │ "id": "4869c8af002b4f7ab7eecc255a61349a", │ │ │ │ "jsonrpc": "2.0", │ │ │ │ "result": { │ │ │ │ "contextId": null, │ │ │ │ "kind": "message", │ │ │ │ "messageId": "06602e9b-74f8-491d-81bf-f4f3dccbcbac", │ │ │ │ "metadata": null, │ │ │ │ "parts": [ │ │ │ │ { │ │ │ │ "kind": "text", │ │ │ │ "metadata": null, │ │ │ │ "text": "In late May, most European ski resorts have closed for the season, but there are still exc │ │ │ │ } │ │ │ │ ], │ │ │ │ "referenceTaskIds": null, │ │ │ │ "role": "agent", │ │ │ │ "taskId": null │ │ │ │ } │ │ │ │ } │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭──────────────────────────────────────────── CALL_LLM: gpt-4.1-mini ─────────────────────────────────────────────╮ │ ╭─ OUTPUT ────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ For skiing this weekend in late May, your best options are high-altitude or glacier ski resorts where snow │ │ │ │ conditions are still good. Some top recommendations are: │ │ │ │ │ │ │ │ 1 Zermatt, Switzerland - Year-round skiing on the Theodulgletscher glacier with high altitude. │ │ │ │ 2 Les Deux Alpes, France - Large glacier that stays open through May. │ │ │ │ 3 Hintertux Glacier, Austria - Open 365 days with consistent snow. │ │ │ │ 4 Tignes and Val d'Isère, France - High altitude with glacier skiing options. │ │ │ │ 5 Whistler Blackcomb, Canada - Usually open until late May or early June with reliable snow. │ │ │ │ │ │ │ │ Would you like recommendations tailored to a specific country or type of ski experience? │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ │ ╭─ USAGE ─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ { │ │ │ │ "input_tokens": 790, │ │ │ │ "output_tokens": 144, │ │ │ │ "input_cost": 0.000316, │ │ │ │ "output_cost": 0.0002304 │ │ │ │ } │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
For skiing this weekend in late May, your best options are high-altitude or glacier ski resorts where snow conditions are still good. Some top recommendations are: 1. Zermatt, Switzerland - Year-round skiing on the Theodulgletscher glacier with high altitude. 2. Les Deux Alpes, France - Large glacier that stays open through May. 3. Hintertux Glacier, Austria - Open 365 days with consistent snow. 4. Tignes and Val d'Isère, France - High altitude with glacier skiing options. 5. Whistler Blackcomb, Canada - Usually open until late May or early June with reliable snow. Would you like recommendations tailored to a specific country or type of ski experience?
Shut down the servers and the agents¶
The following code can be used to gracefully shut down the agents that are serving via A2A.
import asyncio
time_server.should_exit = True
while True:
if time_task.done():
print("Agent server task has completed!")
break
else:
print("Waiting for server to exit")
await asyncio.sleep(1)
weather_server.should_exit = True
while True:
if weather_task.done():
print("Agent server task has completed!")
break
else:
print("Waiting for server to exit")
await asyncio.sleep(1)
Waiting for server to exit Agent server task has completed! Waiting for server to exit Agent server task has completed!