配套代码:
dotnet/samples/02-agents/A2A/A2AAgent_AsFunctionTool/Program.cs
参考风格:MAF 快速上手系列「用 Executor 与 Edge 编排流程」那集的讲法
适用人群:刚接触 Microsoft Agent Framework(MAF)/ A2A,不想被一堆术语劝退的同学
一、先讲人话:这集到底在干啥?
前面玩的都是"一个智能体自己闷头干"。这集要升级了——
让一个智能体去"召唤"另一个智能体来帮忙,而且召唤的方式,跟调用一个普通函数 / 工具一模一样。
这就是 Program.cs 干的事:它启动一个主智能体,然后把这个主智能体连到一个跑在别处的 A2A 智能体上,让主智能体把那个远程智能体**当成自己的一个"工具"**来用。
打个最土的比方你就懂了:
- 主智能体 = 你(项目经理,负责跟老板/用户对话)
- 远程 A2A 智能体 = 隔壁的专家老王(活儿干得溜,但在另一间办公室)
- Agent Card(智能体名片) = 老王的工位牌 / 简历(写着他叫啥、擅长啥、怎么找到他)
AsAIFunction()= 你把老王"接线"到你桌上的呼叫按钮上,按一下,活儿就派给他了,回来的结果也直接显示在屏幕上
一句话总结本集灵魂:用 A2A 协议,把一个远程智能体"伪装"成本地的一个函数工具。
二、核心概念(先认脸,不背定义)
-
A2A 协议(Agent-to-Agent)
一套"智能体之间互相打交道"的标准协议。有了它,不管老王的智能体是用什么框架写的、跑在哪台机器上,只要他支持 A2A,你就能跟他对话。相当于两个不同国家的人,约定好都说"普通话"。 -
Agent Card(智能体名片)
远程智能体的"电子简历"。里面写明了:他叫什么名字、能干什么(skills)、服务地址在哪。这是 A2A 规定的"自我介绍标准格式"。 -
A2ACardResolver(名片快递员)
你给它一个网址(远程智能体的 host),它跑过去把那张 Agent Card 取回来。 -
AsAIAgent()(翻译官)
把取回来的 Agent Card 变成 MAF 框架里能直接用的AIAgent对象。到这一步,远程老王在你代码里已经是一个"可对话的智能体"了。 -
AsAIFunction()(变形术)★★★ 本集最关键的魔法
把上面的AIAgent再包一层,伪装成主智能体眼里的"一个函数工具"。这一步让主智能体以为自己只是在调一个普通 tool,实际上背后是跨网络去叫了另一个智能体。 -
主智能体 +
AIProjectClient
主智能体跑在 Azure AI Foundry 上(由大模型驱动),它手里拿着工具箱,其中之一就是我们刚包装好的"远程 A2A 智能体"。它自己决定:用户的问题到底要不要叫老王来答。
三、代码逐行拆解(配人话注释)
// ① 先问清楚三件事:在哪、用啥模型、老王住哪
var endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT")
?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
var model = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
var a2aAgentHost = Environment.GetEnvironmentVariable("A2A_AGENT_HOST")
?? throw new InvalidOperationException("A2A_AGENT_HOST is not set.");
白话:先确认三个配置——
endpoint(我在哪个 Foundry 项目)、model(用哪个大模型)、a2aAgentHost(要呼叫的老王住在哪个网址)。后两个缺了就直接报错罢工。
// ② 派快递员去拿老王的"名片"
A2ACardResolver agentCardResolver = new(new Uri(a2aAgentHost));
AgentCard agentCard = await agentCardResolver.GetAgentCardAsync();
白话:把老王的网址交给
A2ACardResolver,它跑过去把 Agent Card(名片)取回来。这张名片里写着老王会干啥、怎么联系他。
// ③ 把名片翻译成 MAF 能用的"智能体对象"
AIAgent a2aAgent = agentCard.AsAIAgent();
白话:
AsAIAgent()就是翻译官——把标准名片变成框架里能直接对话的AIAgent。注意:到这儿它还是"另一个智能体",还没挂到主智能体上。
// ④ 创建主智能体,并把远程 A2A 智能体当成"一个工具"塞给它 ★重头戏
AIAgent agent = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential())
.AsAIAgent(
model: model,
instructions: "You are a helpful assistant. Use the available tools to answer the user's questions.",
tools: [a2aAgent.AsAIFunction()] // ← 把老王包成工具,放进主智能体的工具箱
);
白话逐个拆:
AIProjectClient+DefaultAzureCredential:连上 Azure Foundry(用你当前的登录身份做鉴权)。AsAIAgent(...):把主智能体创建出来,给它一句指令"你是个好帮手,用工具回答问题"。tools: [a2aAgent.AsAIFunction()]:这就是灵魂一行。把"远程老王智能体"用AsAIFunction()包一下,放进了主智能体的工具箱。从此主智能体调老王,就跟调一个普通函数没两样。
// ⑤ 开工
Console.WriteLine(await agent.RunAsync("What can you help me with?"));
白话:问主智能体一句"你能帮啥"。主智能体会自己判断:这个问题要不要叫老王(远程 A2A 智能体)来答。要的话,它就通过 A2A 协议把任务派过去,把老王的回答收回来,整理好还给你。
四、一张图看懂数据怎么流
用户提问 "What can you help me with?"
│
▼
┌─────────────────────┐
│ 主智能体 (Foundry) │ ← 大模型驱动,负责思考和决策
│ 工具箱里有: │
│ [A2A 老王工具] │
└─────────┬───────────┘
│ (判断:这题得问老王)
│ a2aAgent.AsAIFunction() ← 伪装成普通工具调用
▼
┌─────────────────────┐
│ 远程 A2A 智能体(老王)│ ← 通过 A2A 协议跨网络通信
│ (另一台机器/另一框架)│
└─────────┬───────────┘
│ 返回结果
▼
主智能体整理答案 → 打印给你
注意:主智能体自己决定要不要叫老王。它不是无脑转发,而是"觉得这个工具能帮上忙"时才去调。这就是 LLM 驱动的 tool calling。
五、把"工作流"那集的思路接上来(对比着学,记忆更牢)
那篇讲 Executor / Edge 的文章里,咱们说**"工作流 = 做什么(Executor) + 怎么流(Edge)"**。本集的"主智能体调远程 A2A 智能体"其实是一脉相承的:
| 那集(工作流) | 这集(A2A 当工具) |
|---|---|
| 把一串处理步骤(每个 Executor)用 Edge 串成流水线 | 把"调用另一个智能体"这件事,包装成了一个工具(Tool) |
| 每个 Executor 干一件本地的小活 | 这个"工具"背后是跨网络去叫另一个智能体 |
将来如果你愿意,完全可以把它塞进工作流的某个 Executor 里——也就是说:
"流水线里的某一步处理"不再是本地代码,而是"去叫另一个智能体来干"。
这就是把单智能体 → 多智能体协作,或者把本地步骤 → 远程协作的桥梁。AsAIFunction() 就是那座桥。
一句话记忆:AsAIFunction() 是"把别的智能体伪装成本地工具"的魔法;A2A 是"两个智能体跨网络通信"的协议。
六、动手改造(建议你自己跑一遍才叫真懂)
前置条件
- 一个能跑的远程 A2A 智能体(比如同系列
A2AAgent那个 sample,先把它起在本地某个端口,例如http://localhost:8000)。 - 一个 Azure AI Foundry 项目,记下它的
endpoint。 - 在自己的 shell 里设好环境变量:
$env:FOUNDRY_PROJECT_ENDPOINT = "https://你的foundry.projects.ai.com"
$env:FOUNDRY_MODEL = "gpt-5.4-mini"
$env:A2A_AGENT_HOST = "http://localhost:8000" # 远程 A2A 智能体的地址
然后 dotnet run 就能看到主智能体把问题派给老王、再把答案带回来的全过程。
改造练习
- 练习 1(看路由):把最后那句提问换成更具体的,比如
"帮我查一下今天北京天气"(前提是老王智能体支持这类技能),观察主智能体是否会去呼叫它。 - 练习 2(改人设):把
instructions从"You are a helpful assistant..."改得更"强势"一点,比如要求"必须先调用工具、再总结",看看主智能体行为有啥差异。 - 练习 3(进阶·接回工作流):回忆 Executor 那集,把
a2aAgent.AsAIFunction()当作一个工具,放进某个 Executor 的HandleAsync内部——让"流水线某一步"实际去召唤远程智能体。这一步做完,你就把两集串起来了。
七、教学要点小结(背这 6 条就够了)
- A2A = 智能体之间的标准通信协议,跨框架、跨机器都能聊。
- Agent Card = 远程智能体的"简历/名片",
A2ACardResolver负责去取它。 AsAIAgent()= 把名片变成本地可用的智能体对象(翻译官)。AsAIFunction()= 把远程智能体"伪装"成主智能体手里的普通工具,这是本集的灵魂一行。- 主智能体跑在 Azure Foundry(大模型驱动),它自己决定什么时候该去呼叫远程老王(LLM 驱动的 tool calling)。
DefaultAzureCredential适合本地开发;上线请换更具体的凭据(如ManagedIdentityCredential),避免鉴权探活慢、还有潜在安全风险。
一句话收尾:这集教会你的,是"让智能体不再单打独斗"的第一招——把队友(另一个智能体)变成自己手边随手可按的工具。