Overview
The A2A Python SDK provides a Python client library for building agents that interact with A2A agents following the Agent2Agent (A2A) Protocol. Use this SDK to build agents that:- Discover and connect to StackOne’s A2A agents
- Send messages and receive responses
- Manage multi-turn conversations with context
Installation
Install the A2A SDK usinguv or pip:
- uv
- pip
Copy
Ask AI
uv add a2a-sdk
Quick Start
Get an Agent Card, send a Message, and print the response.Copy
Ask AI
import base64
import uuid
import httpx
from a2a.client import A2ACardResolver, A2AClient
from a2a.types import (
AgentCard,
Part,
Message,
MessageSendParams,
SendMessageRequest,
SendMessageResponse,
)
BASE64_ENCODED_STACKONE_API_KEY: str = base64.b64encode(b"<stackone_api_key>:").decode()
STACKONE_ACCOUNT_ID: str = "<account_id>"
async def main() -> None:
"""
Fetch an Agent Card, send a message, and print the response.
"""
async with httpx.AsyncClient(
headers={
"Authorization": f"Basic {BASE64_ENCODED_STACKONE_API_KEY}",
"x-account-id": STACKONE_ACCOUNT_ID
}
) as http_client:
# Fetch the Agent Card
card_resolver: A2ACardResolver = A2ACardResolver(
http_client,
"https://a2a.stackone.com"
)
agent_card: AgentCard = await card_resolver.get_agent_card()
print(agent_card.model_dump_json(indent=4))
# Initialize client
client: A2AClient = await A2AClient(http_client, agent_card)
# Send a message
message_request: SendMessageRequest = SendMessageRequest(
params=MessageSendParams(
message=Message(
message_id=str(uuid.uuid4()),
parts=[Part(type="text", text="List the first 5 employees")],
role="user",
)
)
)
response: SendMessageResponse = await client.send_message(message_request)
print(response.model_dump_json(indent=4))
if __name__ == "__main__":
import asyncio
asyncio.run(main())
Getting Agent Cards
During agent initialisation, fetch an Agent Card for every A2A agent you want to send messages to. This is typically a setup step as opposed to a tool.Ensure to include the agent information in the agent’s context (e.g. system message).
Copy
Ask AI
import base64
import json
from typing import Any
import httpx
from a2a.client import A2ACardResolver
from a2a.types import AgentCard
BASE64_ENCODED_STACKONE_API_KEY: str = base64.b64encode(b"<stackone_api_key>:").decode()
async def fetch_agent_cards(account_ids: list[str]) -> dict[str, AgentCard]:
"""
Fetch Agent Cards from StackOne A2A agents for multiple accounts.
The Agent URL is fixed to https://a2a.stackone.com and dynamically loads
an Agent Card based on the account ID.
Args:
account_ids (list[str]): List of StackOne account IDs
Returns:
dict[str, AgentCard]: Dictionary mapping account IDs to their Agent Cards
"""
agent_cards: dict[str, AgentCard] = {}
for account_id in account_ids:
try:
async with httpx.AsyncClient(
headers={
"Authorization": f"Basic {BASE64_ENCODED_STACKONE_API_KEY}",
"x-account-id": account_id
}
) as http_client:
card_resolver: A2ACardResolver = A2ACardResolver(
http_client,
"https://a2a.stackone.com"
)
card: AgentCard = await card_resolver.get_agent_card()
agent_cards[card.name] = card
except Exception as e:
print(f"Failed to fetch agent card for account {account_id}: {e}")
return agent_cards
async def get_a2a_agent_info(agent_cards: dict[str, AgentCard]) -> str:
"""
Get A2A agent information for agent context.
Args:
agent_cards (dict[str, AgentCard]): Dictionary mapping account IDs to their Agent Cards
Returns:
str: Agent information for LLM context
"""
a2a_agent_info: list[dict[str, Any]] = []
for agent_card in agent_cards.values():
a2a_agent_info.append({
"name": agent_card.name,
"description": agent_card.description,
"skills": [
skill.name for skill in (agent_card.skills or [])
]
})
return "\n".join([
"<agents>",
json.dumps(a2a_agent_info, indent=4),
"</agents>"
])
async def main() -> None:
"""
Fetch agent cards from multiple StackOne accounts and format for LLM context.
"""
# Fetch agent cards from multiple StackOne accounts
account_ids: list[str] = ["<account_id_1>", "<account_id_2>", "<account_id_3>"]
agent_cards: dict[str, AgentCard] = await fetch_agent_cards(account_ids)
# Get agent information for LLM context (e.g. system message)
a2a_agent_info: str = await get_a2a_agent_info(agent_cards)
print(a2a_agent_info)
if __name__ == "__main__":
import asyncio
asyncio.run(main())
Sending Messages
To send messages to A2A agents, give your agent access to a tool like below.The tool below gives the agent relatively high autonomy.
You might want to abstract context IDs and task IDs outside of the tool.
Copy
Ask AI
import uuid
from typing import Any, List, Tuple
import base64
import httpx
from a2a.client import A2AClient
from a2a.types import (
AgentCard,
JSONRPCErrorResponse,
Message,
MessageSendParams,
Part,
SendMessageRequest,
SendMessageResponse,
Task,
)
BASE64_ENCODED_STACKONE_API_KEY: str = base64.b64encode(b"<stackone_api_key>:").decode()
STACKONE_ACCOUNT_ID: str = "<account_id>"
# Dictionary mapping account IDs to their Agent Cards
agent_cards: dict[str, AgentCard] = {}
async def send_message_to_agent(
agent_name: str,
message: str,
context_id: str | None = None,
task_id: str | None = None
) -> dict[str, Any]:
"""
Send a message to an agent and receive a response. The response can be a Task, Message, or Error.
To start a new conversation, set `context_id` and `task_id` to None.
To continue a conversation, set `context_id` to the `context_id` of the previous conversation.
- If the previous Task has not terminated (input_required, auth_required), set `task_id` to the `task_id` of the previous Task.
- If the previous Task has terminated (completed, canceled, rejected, or failed), set `task_id` to None.
Args:
agent_name (str): The name of the agent to send the message to
message (str): The message text to send
context_id (str | None): The context ID
task_id (str | None): The task ID
Returns:
dict[str, Any]: A dictionary containing the response from the agent
"""
agent_card: List[Tuple[str, AgentCard]] = [(account_id, agent_card) for account_id, agent_card in agent_cards.items() if agent_card.name == agent_name]
if not len(agent_card):
raise ValueError(f"Agent {agent_name} not found")
account_id: str
agent_card: AgentCard
account_id, agent_card = agent_card[0]
with httpx.AsyncClient(
headers={
"Authorization": f"Basic {BASE64_ENCODED_STACKONE_API_KEY}",
"x-account-id": account_id
}
) as http_client:
client: A2AClient = await A2AClient(http_client, agent_card)
send_message_request: SendMessageRequest = SendMessageRequest(
params=MessageSendParams(
message=Message(
message_id=str(uuid.uuid4()),
parts=[Part(type="text", text=message)],
role="user",
),
context_id=context_id,
task_id=task_id
)
)
response: SendMessageResponse = await client.send_message(send_message_request)
# Handle error
if isinstance(response.root, JSONRPCErrorResponse):
return response.root
result: Message | Task = response.root.result
if isinstance(result, Message):
# Agent responded with a Message (i.e. didn't start a Task)
# Return the Message
return {
"Message": result.model_dump()
}
else:
# Agent responded with a Task (i.e. started a Task)
# Tasks contain the whole message history. We typically want the
# Task's status, and Artifacts (if the Task has completed).
return {
"Task": {
"status": result.status.model_dump(),
"artifacts": [artifact.model_dump() for artifact in result.artifacts],
"context_id": result.context_id,
"task_id": result.id,
}
}