在智能体中使用A2A工具:让大模型自己决定"该找谁帮忙"

写在前面
前面三篇文章把A2A的基础都讲了------设计动机、原生SDK实战、HelloAgents封装。但有一个问题还没深入聊:A2A连接建立之后,大模型到底是怎么决定"这个任务我该自己做,还是该委托给其他Agent"的?
这是A2A在智能体中真正落地的关键。协议只是通道,真正的"智能"体现在决策上------Agent要能判断自己的能力边界,知道什么时候该求助,求助该找谁。
这篇文章我就来聊聊这个话题。我会从最核心的思路讲起,然后给一个完整的代码例子,最后分享一些让Agent协作更稳定的技巧。
一、核心思路:把其他Agent变成"工具"
要理解A2A在智能体中的使用方式,首先要转变一个观念:其他Agent对你的大模型来说,就是一个"工具"。
跟MCP工具一样,A2A Agent也可以被封装成大模型可调用的Function。大模型看到这个Function的描述,判断"这个任务需要调用它",然后输出一个工具调用请求,你的代码执行这个请求------通过A2A协议把任务发给目标Agent,等结果回来,再塞给大模型。
这个思路跟MCP工具调用完全一样,区别只在于:
- MCP工具是"死"的,你传什么参数它返回什么结果
- A2A Agent是"活"的,它有自己的大模型,能理解任务、多轮对话、自主处理
但对调用方的大模型来说,它不需要知道这些区别。它只需要知道:"有一个叫'机票助手'的工具,输入目的地和日期,能返回机票信息。"至于是不是Agent在背后处理,它不关心。
这种设计的好处是:你不需要为A2A单独设计一套决策机制,直接复用大模型已有的Function Calling能力就行。
二、怎么把A2A Agent注册为LLM工具
具体怎么做呢?我以OpenAI的Function Calling格式为例,看看怎么把一个A2A Agent包装成工具:
python
class A2ATool:
"""把A2A Agent包装成大模型可调用的工具"""
def __init__(self, agent_client, tool_name, tool_description):
self.client = agent_client # A2AClient实例
self.tool_name = tool_name
self.tool_description = tool_description
def to_llm_tool(self):
"""转换成大模型的Function Calling格式"""
return {
"type": "function",
"function": {
"name": self.tool_name,
"description": self.tool_description,
"parameters": {
"type": "object",
"properties": {
"task": {
"type": "string",
"description": "要委托的任务描述,尽量详细"
},
"context": {
"type": "string",
"description": "相关上下文信息(可选)"
}
},
"required": ["task"]
}
}
}
async def execute(self, arguments):
"""执行工具调用:通过A2A把任务发给目标Agent"""
task_input = {
"task": arguments.get("task", ""),
"context": arguments.get("context", "")
}
result = await self.client.create_task_and_wait(
input=task_input,
timeout=60
)
if result.success:
return str(result.output)
else:
return f"任务失败:{result.error}"
然后在你的Agent里,把所有可用的A2A Agent都注册成工具:
python
class MyAgent:
def __init__(self):
self.a2a_tools = []
self.llm_tools = []
async def register_agent(self, agent_url, tool_name, description):
"""注册一个A2A Agent为工具"""
client = await A2AClient.connect(agent_url)
tool = A2ATool(client, tool_name, description)
self.a2a_tools.append(tool)
self.llm_tools.append(tool.to_llm_tool())
async def setup(self):
# 注册机票Agent
await self.register_agent(
agent_url="http://localhost:8001",
tool_name="book_flight",
description="查询和预订机票。输入:目的地、日期、舱位偏好。输出:航班信息或预订结果。"
)
# 注册酒店Agent
await self.register_agent(
agent_url="http://localhost:8002",
tool_name="book_hotel",
description="查询和预订酒店。输入:城市、入住日期、离店日期、价格偏好。输出:酒店列表或预订结果。"
)
这样一来,大模型在做决策的时候,就能看到book_flight和book_hotel两个工具。它会根据用户的问题,自主决定调用哪个、怎么传参数。
三、大模型的决策过程:什么时候该委托?
把Agent注册成工具之后,大模型的决策过程其实跟普通工具调用是一样的。但因为A2A Agent比较"重"(调用成本高、耗时长),你需要在工具描述上做一些引导,让大模型做出更合理的决策。
我总结了几个引导大模型正确决策的技巧:
技巧1:在描述里写清楚"什么时候该用"。
不要只写"查询机票",要写"当用户需要查询或预订机票时使用,不要用于查询酒店或火车票"。明确的使用场景能大幅减少误调用。
技巧2:写清楚"什么时候不该用"。
比如"仅在用户明确要求查询实时机票信息时使用,不要用于回答一般性的航空知识问题"。排除场景跟包含场景同样重要。
技巧3:说明输入要求。
"调用前必须确认用户的目的地和出发日期,如果用户没说,先询问用户,不要猜测。"这样能避免大模型在信息不全的情况下贸然调用。
技巧4:说明输出格式。
"返回结果包含航班号、时间、价格,直接转述给用户即可,不需要额外处理。"让大模型知道拿到结果后该怎么处理。
我做过对比,加上这些引导之后,大模型的工具调用准确率提升了差不多30%。以前经常出现"用户问天气它调机票工具"的情况,加了描述之后基本没有了。
四、多Agent协作的完整流程
我们来看一个完整的多Agent协作流程。假设用户说"帮我安排一下下周去上海的出差,订机票和酒店"。
整个流程是这样的:
第1步:大模型分析任务
大模型收到用户请求,判断需要调用两个工具:book_flight和book_hotel。它可能先调用机票,也可能两个并行调用(取决于大模型的能力)。
第2步:委托给机票Agent
大模型输出工具调用:
json
{
"name": "book_flight",
"arguments": {
"task": "查询下周从北京到上海的机票,用户偏好上午的航班,经济舱",
"context": "用户是商务出差,公司报销,预算不是问题"
}
}
你的代码通过A2A把这个任务发给机票Agent。机票Agent收到任务后,可能会:
- 调用自己的MCP工具(航班查询API)
- 如果信息不全,可能会通过A2A回复要求补充信息
- 处理完之后返回结果
第3步:委托给酒店Agent
类似地,大模型调用book_hotel工具,把酒店预订任务委托给酒店Agent。
第4步:汇总结果
两个Agent都返回结果后,大模型把结果整理成自然语言,回复给用户:
"已为您查询到以下方案:
航班:国航CA1501,下周一08:30起飞,票价580元
酒店:如家酒店(人民广场店),标准间299元/晚
需要我帮您预订吗?"
第5步:后续交互
如果用户说"就订这个",大模型会再次调用对应的Agent工具,完成预订。
整个过程中,主Agent不需要知道机票和酒店是怎么查的,它只需要负责"委托"和"汇总"。具体的执行由各个专业Agent完成。这就是多Agent协作的精髓------分工明确,各司其职。
五、一个完整的代码例子
光说不练假把式,我写一个简化但完整的例子,把上面的流程落地:
python
import asyncio
import json
from helloagents import A2AClient
class A2ATool:
def __init__(self, client, name, description):
self.client = client
self.name = name
self.description = description
def to_function(self):
return {
"type": "function",
"function": {
"name": self.name,
"description": self.description,
"parameters": {
"type": "object",
"properties": {
"task": {"type": "string", "description": "任务描述"}
},
"required": ["task"]
}
}
}
async def run(self, task):
result = await self.client.create_task_and_wait(
input={"task": task}, timeout=60
)
return json.dumps(result.output, ensure_ascii=False) if result.success else f"失败:{result.error}"
class TravelAgent:
def __init__(self, llm_client):
self.llm = llm_client
self.tools = []
self.tool_map = {}
async def register_agent(self, url, name, desc):
client = await A2AClient.connect(url)
tool = A2ATool(client, name, desc)
self.tools.append(tool.to_function())
self.tool_map[name] = tool
async def chat(self, user_message):
messages = [
{"role": "system", "content": """你是一个旅行助手。
你可以调用机票和酒店工具来帮助用户。
调用工具前确认用户需求明确,信息不全时先询问。
拿到结果后用自然语言总结给用户。"""},
{"role": "user", "content": user_message}
]
while True:
response = await self.llm.chat(messages, tools=self.tools)
if not response.tool_calls:
return response.content
for call in response.tool_calls:
tool_name = call.function.name
args = json.loads(call.function.arguments)
tool = self.tool_map[tool_name]
print(f"[委托任务] {tool_name}: {args['task']}")
result = await tool.run(args["task"])
print(f"[收到结果] {result[:100]}...")
messages.append({"role": "assistant", "content": None, "tool_calls": [call]})
messages.append({"role": "tool", "tool_call_id": call.id, "content": result})
# 使用示例
async def main():
# 假设llm_client是你自己封装的大模型客户端
agent = TravelAgent(llm_client)
# 注册两个A2A Agent
await agent.register_agent(
"http://localhost:8001",
"flight_assistant",
"查询和预订机票。当用户需要机票信息时调用,输入任务描述,返回航班信息。"
)
await agent.register_agent(
"http://localhost:8002",
"hotel_assistant",
"查询和预订酒店。当用户需要酒店信息时调用,输入任务描述,返回酒店信息。"
)
# 跟用户对话
reply = await agent.chat("帮我查下周三从北京到上海的机票,顺便推荐一家酒店")
print(f"\n最终回复:{reply}")
if __name__ == "__main__":
asyncio.run(main())
这个例子虽然简化了大模型客户端的部分,但核心逻辑是完整的------注册Agent工具、大模型决策调用、A2A委托执行、结果汇总。你可以把llm_client换成你实际用的大模型API。
六、处理协作中的异常
多Agent协作比单Agent复杂,出错的地方也更多。我总结了几种常见异常和处理方式:
异常1:目标Agent不在线。
A2AClient连接失败,或者连接上了但任务超时。处理方式:重试2-3次,还是失败的话告诉用户"该服务暂时不可用",并提供替代方案(比如建议用户手动查询)。
异常2:Agent返回结果格式不对。
你期望返回JSON,结果Agent回了一段自然语言。处理方式:在工具描述里明确要求返回格式,同时在代码里做容错------如果解析失败,把原始文本传给大模型,让它自己理解。
异常3:Agent之间产生分歧。
比如机票Agent说"这个时间没有航班",酒店Agent说"这个日期酒店满房",两个结果矛盾。处理方式:把两个结果都给大模型,让它判断怎么跟用户解释,或者尝试调整方案。
异常4:循环调用。
A委托B,B又委托A,形成死循环。处理方式:给任务加一个"深度"参数,每委托一次深度+1,超过阈值就终止。我一般设最大深度为3层。
异常5:部分成功部分失败。
机票查到了,酒店没查到。处理方式:把成功的部分先告诉用户,失败的部分说明原因并询问是否需要换个方式。
这些异常处理看起来琐碎,但在生产环境中非常重要。多Agent系统的稳定性,很大程度上取决于异常处理做得好不好。
七、几个让协作更顺畅的技巧
最后分享几个我在实践中总结的技巧:
技巧1:给每个Agent设定清晰的"能力边界"。
在System Prompt里明确告诉主Agent:"机票Agent只负责机票,酒店Agent只负责酒店,不要让它们做超出能力范围的事。"边界清晰,协作才不会乱。
技巧2:控制同时协作的Agent数量。
不要一次让大模型调用五六个Agent,信息太多它处理不过来。我一般控制在2-3个并行,更多的话分批处理。
技巧3:在Agent之间传递结构化信息。
不要让Agent A返回一大段自然语言,Agent B再去解析。尽量用结构化的JSON传递关键信息,减少理解偏差。
技巧4:记录协作日志。
把每次Agent调用的输入、输出、耗时都记下来。出问题的时候,日志是最好的排查工具。我还会定期分析日志,看看哪些Agent经常被调用、哪些经常失败、哪些从来没被用过。
技巧5:给用户"可见性"。
多Agent协作的时候,用户可能等很久。最好在界面上显示"正在查询机票...""正在查询酒店...",让用户知道系统在干活,而不是卡死了。
写在最后
把A2A用在智能体里,本质上就是"把其他Agent当成工具来用"。这个思路简单,但真正做好不容易------你需要精心设计工具描述、处理各种异常、优化协作流程。
我的体会是:不要一开始就搞很复杂的多Agent系统。先从两个Agent的协作开始,跑通流程、踩完坑,再逐步增加Agent数量。多Agent的复杂度是指数级增长的,Agent越多,调试和维护越困难。
A2A协议给了我们Agent间通信的标准,但"怎么让Agent协作得好"是一个工程问题,需要在实践中不断调优。希望这篇文章里的经验能帮你少走点弯路。
这篇也是10.3节A2A协议实战的最后一篇。从设计动机到原生实战,再到框架封装和智能体集成,A2A的核心内容我们都过了一遍。接下来会进入10.4节,聊聊ANP协议------Agent网络中的服务发现。
你们在做多Agent协作的时候,遇到过什么有意思的问题?或者有什么好用的技巧?欢迎在评论区交流。
下一篇预告:ANP协议目标。我们会开始学习ANP协议,看看它是怎么解决Agent网络中的服务发现和组织问题的。