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.
import asyncio
import httpx
from any_agent import AgentConfig, AnyAgent
from any_agent.config import MCPStdio
from any_agent.serving import A2AServingConfig
# 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(
"tinyagent", # 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],
),
)
(time_task, time_server) = await time.serve_async(
A2AServingConfig(port=0, endpoint="/time")
)
weather_expert = await AnyAgent.create_async(
"tinyagent",
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",
),
)
(weather_task, weather_server) = await weather_expert.serve_async(
A2AServingConfig(port=0, endpoint="/location_recommender")
)
weather_port = weather_server.servers[0].sockets[0].getsockname()[1]
time_port = time_server.servers[0].sockets[0].getsockname()[1]
max_attempts = 20
poll_interval = 0.5
attempts = 0
time_url = f"http://localhost:{time_port}"
weather_url = f"http://localhost:{weather_port}"
async with httpx.AsyncClient() as client:
while True:
try:
# Try to make a basic GET request to check if server is responding
await client.get(time_url, timeout=1.0)
print(f"Server is ready at {weather_url}")
break
except (httpx.RequestError, httpx.TimeoutException):
# Server not ready yet, continue polling
pass
await asyncio.sleep(poll_interval)
attempts += 1
if attempts >= max_attempts:
msg = f"Could not connect to the servers. Tried {max_attempts} times with {poll_interval} second interval."
raise ConnectionError(msg)
Server is ready at http://localhost:61370
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_async
function which retrieves the info about the agent and allows you to call it.
from any_agent import AgentConfig, AnyAgent
from any_agent.tools import a2a_tool_async
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_async(
f"http://localhost:{time_port}/time",
http_kwargs={
"timeout": 5
}, # This gives the time agent up to 5 seconds to respond to each request
),
await a2a_tool_async(
f"http://localhost:{weather_port}/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="tinyagent",
agent_config=config,
)
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": "Use the available tools to obtain additional information to answer the query." │ │ │ │ }, │ │ │ │ { │ │ │ │ "role": "user", │ │ │ │ "content": "Where should I go this weekend to ski?" │ │ │ │ } │ │ │ │ ] │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ │ ╭─ OUTPUT ────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ [ │ │ │ │ { │ │ │ │ "tool.name": "call_time_agent", │ │ │ │ "tool.args": "{\"query\":\"current date and time\"}" │ │ │ │ } │ │ │ │ ] │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ │ ╭─ USAGE ─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ { │ │ │ │ "input_tokens": 237, │ │ │ │ "output_tokens": 18 │ │ │ │ } │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭──────────────────────────────────────────── CALL_LLM: gpt-4.1-nano ─────────────────────────────────────────────╮ │ ╭─ INPUT ─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ [ │ │ │ │ { │ │ │ │ "role": "system", │ │ │ │ "content": "You are an agent - please keep going until the user's query is completely resolved, before │ │ │ │ }, │ │ │ │ { │ │ │ │ "role": "user", │ │ │ │ "content": "current date and time" │ │ │ │ } │ │ │ │ ] │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ │ ╭─ OUTPUT ────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ [ │ │ │ │ { │ │ │ │ "tool.name": "get_current_time", │ │ │ │ "tool.args": "{\"timezone\":\"America/New_York\"}" │ │ │ │ } │ │ │ │ ] │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ │ ╭─ USAGE ─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ { │ │ │ │ "input_tokens": 226, │ │ │ │ "output_tokens": 18 │ │ │ │ } │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭──────────────────────────────────────── EXECUTE_TOOL: get_current_time ─────────────────────────────────────────╮ │ ╭─ Input ─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ { │ │ │ │ "timezone": "America/New_York" │ │ │ │ } │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ │ ╭─ OUTPUT ────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ { │ │ │ │ "timezone": "America/New_York", │ │ │ │ "datetime": "2025-06-13T13:27:49-04:00", │ │ │ │ "is_dst": true │ │ │ │ } │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭──────────────────────────────────────────── CALL_LLM: gpt-4.1-nano ─────────────────────────────────────────────╮ │ ╭─ OUTPUT ────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ The current date and time in the America/New_York timezone is June 13, 2025, 1:27 PM. How can I assist you │ │ │ │ further? │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ │ ╭─ USAGE ─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ { │ │ │ │ "input_tokens": 294, │ │ │ │ "output_tokens": 36 │ │ │ │ } │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭───────────────────────────────────────── EXECUTE_TOOL: call_time_agent ─────────────────────────────────────────╮ │ ╭─ Input ─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ { │ │ │ │ "query": "current date and time" │ │ │ │ } │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ │ ╭─ OUTPUT ────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ { │ │ │ │ "contextId": "5ebef1a7-9444-4f75-94ac-1dc0d6d5397e", │ │ │ │ "history": [ │ │ │ │ { │ │ │ │ "contextId": "5ebef1a7-9444-4f75-94ac-1dc0d6d5397e", │ │ │ │ "messageId": "3e8fec67e7054f968604f36bc454d822", │ │ │ │ "parts": [ │ │ │ │ { │ │ │ │ "text": "current date and time" │ │ │ │ } │ │ │ │ ], │ │ │ │ "role": "user", │ │ │ │ "taskId": "54da7aad-44f8-4b5f-8679-43d5920f3bf5" │ │ │ │ } │ │ │ │ ], │ │ │ │ "id": "54da7aad-44f8-4b5f-8679-43d5920f3bf5", │ │ │ │ "status": { │ │ │ │ "message": { │ │ │ │ "contextId": "5ebef1a7-9444-4f75-94ac-1dc0d6d5397e", │ │ │ │ "messageId": "8060fe58-0e15-4fd0-b776-27fd3477018b", │ │ │ │ "parts": [ │ │ │ │ { │ │ │ │ "text": "{\"result\":\"The current date and time in the America/New_York timezone is June 13, 202 │ │ │ │ } │ │ │ │ ], │ │ │ │ "role": "agent", │ │ │ │ "taskId": "54da7aad-44f8-4b5f-8679-43d5920f3bf5" │ │ │ │ }, │ │ │ │ "state": "completed", │ │ │ │ "timestamp": "2025-06-13T17:27:51.777479+00:00" │ │ │ │ } │ │ │ │ } │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭──────────────────────────────────────────── CALL_LLM: gpt-4.1-mini ─────────────────────────────────────────────╮ │ ╭─ OUTPUT ────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ [ │ │ │ │ { │ │ │ │ "tool.name": "call_weather_expert", │ │ │ │ "tool.args": "{\"query\":\"best ski location to visit in mid June\"}" │ │ │ │ } │ │ │ │ ] │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ │ ╭─ USAGE ─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ { │ │ │ │ "input_tokens": 579, │ │ │ │ "output_tokens": 23 │ │ │ │ } │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭──────────────────────────────────────────── 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 location to visit in mid June" │ │ │ │ } │ │ │ │ ] │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ │ ╭─ OUTPUT ────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ For skiing in mid-June, you'll want to consider locations with high-altitude terrain or glaciers that offer │ │ │ │ year-round skiing. Some of the top destinations are: │ │ │ │ │ │ │ │ 1 Zermatt, Switzerland: Known for its glacier skiing, Zermatt offers reliable snow conditions year-round. │ │ │ │ 2 Whistler Blackcomb, Canada: While the main ski season ends in late spring, some glacier skiing options │ │ │ │ might still be available early in the season. │ │ │ │ 3 Tignes, France: Home to the Grande Motte glacier, it provides summer skiing opportunities. │ │ │ │ 4 Mammoth Mountain, USA: Has a glacier and high altitude terrain suitable for summer skiing. │ │ │ │ 5 Mount Hood, Oregon, USA: Offers summer skiing on the Palmer Glacier. │ │ │ │ │ │ │ │ Would you like me to provide current conditions or recommend the best place based on specific preferences │ │ │ │ like terrain, scenery, or amenities? │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ │ ╭─ USAGE ─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ { │ │ │ │ "input_tokens": 53, │ │ │ │ "output_tokens": 172 │ │ │ │ } │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭─────────────────────────────────────── EXECUTE_TOOL: call_weather_expert ───────────────────────────────────────╮ │ ╭─ Input ─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ { │ │ │ │ "query": "best ski location to visit in mid June" │ │ │ │ } │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ │ ╭─ OUTPUT ────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ Error calling tool: HTTP Error 503: Network communication error: │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭──────────────────────────────────────────── CALL_LLM: gpt-4.1-mini ─────────────────────────────────────────────╮ │ ╭─ OUTPUT ────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ It seems that the ski location expert service is currently unavailable. However, since today is June 13, │ │ │ │ 2025, and it is summer in the Northern Hemisphere, typical ski resorts in places like the Alps, Rockies, or │ │ │ │ the Northeastern US might not have good snow conditions for skiing. │ │ │ │ │ │ │ │ For skiing in June, you might consider high-altitude or glacier ski resorts where snow remains year-round. │ │ │ │ Some well-known options include: │ │ │ │ │ │ │ │ • Zermatt or Saas Fee in Switzerland (glacier skiing in the Alps) │ │ │ │ • Whistler Blackcomb in Canada (sometimes has summer glacier skiing) │ │ │ │ • Timberline Lodge in Oregon, USA (summer skiing on the mountain) │ │ │ │ │ │ │ │ Would you like more information on any of these or other types of activities to do this weekend? │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ │ ╭─ USAGE ─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ { │ │ │ │ "input_tokens": 626, │ │ │ │ "output_tokens": 154 │ │ │ │ } │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
It seems that the ski location expert service is currently unavailable. However, since today is June 13, 2025, and it is summer in the Northern Hemisphere, typical ski resorts in places like the Alps, Rockies, or the Northeastern US might not have good snow conditions for skiing. For skiing in June, you might consider high-altitude or glacier ski resorts where snow remains year-round. Some well-known options include: - Zermatt or Saas Fee in Switzerland (glacier skiing in the Alps) - Whistler Blackcomb in Canada (sometimes has summer glacier skiing) - Timberline Lodge in Oregon, USA (summer skiing on the mountain) Would you like more information on any of these or other types of activities to do this weekend?
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
time_task.cancel()
time_wait_count = 0
max_wait_seconds = 10
while True:
if time_task.done():
print("Agent server task has completed!")
break
elif time_wait_count >= max_wait_seconds:
print(
f"Timed out after {max_wait_seconds} seconds waiting for time server to exit"
)
break
else:
print("Waiting for server to exit")
await asyncio.sleep(1)
time_wait_count += 1
weather_server.should_exit = True
weather_task.cancel()
weather_wait_count = 0
while True:
if weather_task.done():
print("Agent server task has completed!")
break
elif weather_wait_count >= max_wait_seconds:
print(
f"Timed out after {max_wait_seconds} seconds waiting for weather server to exit"
)
break
else:
print("Waiting for server to exit")
await asyncio.sleep(1)
weather_wait_count += 1
Waiting for server to exit Agent server task has completed! Waiting for server to exit Agent server task has completed!