先聊一个比较反直觉的认知:在 Multi-Agent 系统里,SubAgent 并不是和主 Agent 平起平坐的“同事”,它更像一个被封装好的特殊工具。我在用 Microsoft Agent Framework 搭建多智能体项目时最大的体会就是,主从模式(Hierarchical)本质上是把 SubAgent 当作另一种 Tool 来调用,主 Agent 负责拆解任务、调度分工,SubAgent 负责完成某个具体子任务并把结果交回来。这个思路一旦想通,后面很多设计决策都会顺很多。这篇文章我会从一个实际的多智能体客服项目出发,拆解 SubAgent 的原理、常见类型、注册方式,以及我在状态传递、上下文控制和成本优化上踩过的坑。如果你正准备用 Microsoft Agent Framework 做多 Agent 编排,或者只是想把已有的单 Agent 应用改造成多智能体结构,这篇应该能帮你少走不少弯路。
1. 为什么需要 SubAgent:主从模式与工具化思维
1.1 主 Agent 与 SubAgent 的分工逻辑
单个 Agent 的能力边界其实很容易摸到。当你的系统里同时要处理订单查询、退款判断、物流追踪、客户投诉分类,如果把这些指令和工具全部塞进同一个 Agent 的 System Prompt 里,结果往往是谁都做不好。模型在每轮推理时都要面对一大坨互相干扰的工具描述和规则,选错工具的概率会明显上升,上下文也很快被撑满。
这就是 SubAgent 存在的意义:它把复杂系统拆成“一个大脑 + 多个专业执行者”的结构。主 Agent 只负责理解用户意图、拆解任务、决定下一步调用谁;SubAgent 则各自维护独立的指令、工具、模型甚至记忆。你可以把主 Agent 理解成项目经理,SubAgent 是不同工种的专家。项目经理不需要会写代码,他只需要知道“这个活该找谁”,然后把需求丢给专家,最后把专家交付的结果整合给客户。
用 Microsoft Agent Framework 做这种主从结构非常直接。主 Agent 的指令里可以写清楚“遇到订单问题就调用订单查询 Agent,遇到退款问题就调用退款 Agent”,框架会在模型推理时自动把 SubAgent 暴露成可调用的对象。我刚开始设计时还是习惯性地把所有业务规则写在主 Agent 里,后来发现,规则越多,主 Agent 越“糊涂”。真正稳定的做法是把规则下沉到各个 SubAgent,主 Agent 只保留路由信息。
1.2 把 SubAgent 当作特殊 Tool 来设计
这是我这次项目里最重要的一条经验:SubAgent 本质上就是 Tool 的变体。 普通 Tool 是一段确定性的代码函数,输入参数、返回结果都由你自己控制;SubAgent 则是一个由模型驱动的、内部可以包含多轮推理和工具调用的“智能函数”。但从主 Agent 的视角看,两者完全一样——都是给模型一个名字、一段描述、一组参数,模型决定要不要调用它,调用完拿到结果,然后继续自己的推理。
这个视角有一个实际的好处:你在设计 SubAgent 的对外接口时,可以完全参照设计 Tool 的方式。名字要短、描述要清楚、参数要结构化、返回值要统一。我之前犯过一个很典型的错误:把一个 SubAgent 的 description 写成了产品说明书,密密麻麻介绍了这个 Agent 背后的团队、数据来源和技术实现。结果主 Agent 在决策时经常抓不住重点,偶尔还会因为描述里包含其他关键词而选错 Agent。后来我把 description 改成第一句话就说明“在什么情况下使用”,效果立刻好了很多。
本质上,SubAgent 的 description 是给模型看的,不是给人类看的。模型通过描述来判断“当前用户问题是否匹配这个 Agent 的职责”。所以描述应该像函数注释一样,重点写清楚输入条件和使用边界,而不是写背景故事。
1.3 什么场景适合 SubAgent,什么场景不适合
主从模式并不是万能解。我见过有人为了炫技,把一个“计算两数之和”的逻辑也包成 SubAgent,结果延迟高了十倍,成本也高了十倍,收益却为零。SubAgent 这种结构适合解决的是“需要模型理解力”的子任务,而不是“固定规则”的子任务。
适合的场景有几个共同点:首先是任务边界清晰,比如订单查询和退款申请是两套完全不同的流程,彼此之间不需要共享太多运行时状态;其次是子任务本身有复杂度,可能需要多轮推理、需要调用自己的工具、甚至需要和用户确认信息;第三是主 Agent 不需要关心子任务内部怎么执行,只需要拿到最终结果。
不适合的场景也很明显:任务简单固定、对延迟和成本极度敏感、子任务之间需要频繁共享大量动态变量。这些情况用普通 Tool 更合适。下面这张表是我在项目里用的判断逻辑,基本能覆盖大多数情况。
| 维度 | 普通 Tool | SubAgent |
|---|---|---|
| 内部实现 | 代码固定 | 模型推理 |
| 上下文消耗 | 低,只输入输出 | 高,自带指令和工具描述 |
| 延迟 | 毫秒级 | 秒级起步,多一次模型调用 |
| 调试难度 | 低,直接看函数 | 高,要查模型中间输出 |
| 适用任务 | 稳定可复现的规则操作 | 多变、需要理解力和判断力的任务 |
| 替换成本 | 低,改代码即可 | 高,改提示词后还要回归测试 |
我在客服项目里最后的结论是:能用普通 Tool 完成的功能,不要轻易上 SubAgent。SubAgent 的价值在于它内部有“智能”,如果子任务本身不需要智能,那它就只是给系统增加延迟和成本。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与最小工程骨架
2.1 安装与版本选择
Microsoft Agent Framework 目前的迭代速度非常快,版本和兼容性需要认真对待。我建议在虚拟环境里安装,避免和项目里的其他依赖冲突。以 Python 环境为例,安装命令大概是:
bash复制pip install microsoft-agent-framework
不过这里要特别提醒一句:不同版本的框架对 Agent 的初始化参数、Host 运行方式甚至模型输出格式的兼容性都不太一样。我最早随手装了最新版,结果发现旧项目里用的 instructions 字段在某个版本被改名成了 system_prompt,整个项目直接跑不起来。所以如果你不是从零开始,建议先锁定一个稳定版本,同时锁好对应的模型 SDK 版本。
bash复制pip install microsoft-agent-framework==0.1.x openai==1.x
顺便说一下,这里说的模型不一定是 OpenAI 的,Microsoft Agent Framework 也支持 Azure OpenAI、本地模型等。但不管你用哪个 provider,版本锁定的思路是一样的。模型返回的结构如果变了,框架解析会出各种奇怪的问题。
2.2 项目结构设计
多 Agent 项目最容易犯的错就是把所有 Agent 都堆在同一个文件里。刚开始写 Demo 没问题,但一旦 Agent 数量超过三四个,互相之间的工具依赖和配置会乱成一团。我建议按下面的结构组织:
text复制agents/
__init__.py
main_agent.py
order_agent.py
refund_agent.py
services/
tools.py
shared/
types.py
config.py
main_agent.py 里只定义主 Agent 和路由逻辑,order_agent.py 和 refund_agent.py 各自负责一个业务子域,services/tools.py 放公共工具函数,比如数据库查询、外部 API 调用。这样做的核心原因是:SubAgent 本身也是一个“应用”,它有自己的指令、工具和状态,把它当作独立模块来组织会清晰很多。
2.3 构建第一个 SubAgent 的代码骨架
下面这段代码我尽量做了简化,便于展示核心结构。实际项目里你还需要处理模型 API Key 等配置,但原理是一样的。
python复制# agents/order_agent.py
from agent_framework import Agent
order_agent = Agent(
name="order_query_agent",
instructions="""
你是一个订单查询专家。
当用户提供订单编号时,查询订单状态和物流信息。
如果信息不足,直接要求用户补充订单编号。
只输出 JSON,不要输出额外解释。
""",
model="gpt-4o",
)
python复制# agents/main_agent.py
from agent_framework import Agent
from agents.order_agent import order_agent
from agents.refund_agent import refund_agent
main_agent = Agent(
name="main_agent",
instructions="""
你是一个综合客服助手。
如果用户询问订单状态或物流信息,调用 order_query_agent。
如果用户申请退款,调用 refund_agent。
如果用户的问题不属于以上两类,直接回答。
""",
model="gpt-4o",
subagents=[order_agent, refund_agent],
)
运行入口也很简单,需要一个 Host 把 Agent 跑起来:
python复制from agent_framework import AgentHost
host = AgentHost()
response = host.run(
agent=main_agent,
input="订单编号 9527 现在到哪里了?",
)
print(response)
这段代码背后发生的事情不复杂:主 Agent 的模型收到用户消息后,根据 instructions 和 SubAgent 的描述,判断出应该调用 order_query_agent,框架会把 order_query_agent 当作一个工具执行,执行完再把它返回的内容交给主 Agent 的模型继续生成最终回复。整个过程中,主 Agent 看不见 order_query_agent 内部的工具调用和中间推理,它只看到最终返回结果。
如果你想让主 Agent 对“何时调用 SubAgent”有更精细的控制,可以显式地定义工具描述,而不是让框架自动生成。这在 Agent 名字不够语义化的时候特别有用:
python复制from agent_framework import ToolDefinition
order_tool = ToolDefinition(
name="order_query_agent",
description="当用户提供订单编号并询问物流状态时使用。输入参数为订单号。",
parameters={
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "用户提供的订单编号"
}
},
"required": ["order_id"]
},
target=order_agent,
)
main_agent = Agent(
name="main_agent",
instructions="你是一个综合客服助手。根据用户问题选择合适的工具。",
model="gpt-4o",
tools=[order_tool],
)
这里有个细节容易忽略:当 target 指向一个 Agent 时,框架执行 Tool 的流程就是启动那个 Agent,并等待它结束;当 target 指向一个普通函数时,框架就是直接调用函数。对于主 Agent 模型来说,这两种调用没有区别,但返回内容的格式和可靠性差别很大,这也是为什么我反复强调要把 SubAgent 当作 Tool 来设计。
2.4 运行时的完整调用循环
理解主从模式的运行机制,对排查问题特别重要。一次完整的 SubAgent 调用可以拆成下面几个步骤:
- 用户输入传给主 Agent。
- 主 Agent 的模型根据 instruction 和工具描述,生成一个工具调用请求,指向某个 SubAgent。
- 框架拦截这个调用请求,启动对应的 SubAgent。
- SubAgent 在它自己的上下文里执行推理,可能会调用自己的工具,也可能需要多轮处理。
- SubAgent 结束后,把最终返回内容作为“工具调用结果”交回主 Agent。
- 主 Agent 的模型拿到这个结果,继续推理,生成要返回给用户的文字。
在这个流程里,最关键的一点是步骤 5 到 6 的交接。SubAgent 的返回值会成为主 Agent 上下文的一部分,直接影响主 Agent 的最终回答。如果返回值是一大段废话,或者格式混乱,主 Agent 很容易被带偏。
3. SubAgent 的三种常见类型与注册方式
3.1 单轮任务型 SubAgent
最常见的 SubAgent 就是单轮任务型,输入一个明确的请求,返回一个明确的结果。订单查询就是典型场景:用户给出订单号,SubAgent 查询数据库,返回状态,结束。这种 Agent 的 instructions 要写得非常干净,强调“不要寒暄,不要解释,只返回结构化结果”。
我一般会让这种 Agent 返回 JSON 格式的数据,比如:
json复制{
"status": "success",
"data": {
"order_id": "9527",
"order_status": "shipped",
"eta": "2025-06-10"
}
}
为什么要强调 JSON?因为主 Agent 拿到这个结果后,可以直接根据 data 字段生成自然语言回复,不需要再去解析大段文本。如果 SubAgent 返回的是一段带情感色彩的完整句子,主 Agent 在转述时很容易添油加醋。
3.2 多轮会话型 SubAgent
还有一种 SubAgent 不是一次调用就能结束的,它可能需要和用户来回确认几轮信息。比如退款申请,通常需要收集订单号、退款原因、是否收到货物等。这种场景下,SubAgent 需要保持自己的多轮会话状态。
在主从模式下实现多轮会话型 SubAgent,关键是要搞清楚“谁在和用户对话”。有的框架允许 SubAgent 直接回复用户,主 Agent 只做中转;有的框架则要求 SubAgent 把“需要向用户确认的问题”返回给主 Agent,由主 Agent 去问用户,拿到答案后再调用 SubAgent。我个人更推荐后者,也就是让主 Agent 保持对外对话的入口。原因是主 Agent 还可以同时管理其他上下文,比如用户的历史偏好、当前会话的全局信息,这些不会因为切换到 SubAgent 而丢失。
但这样做的代价是,你需要额外处理状态传递。比如 SubAgent 第一次被调用后,记住了“用户要退货,但退款原因还没有说明”,第二次被调用时,这个状态必须能传回给同一个 SubAgent。如果框架没有自动记忆,你就要自己把上下文重新塞给 SubAgent,或者在 SubAgent 的配置里开启 session 级别的记忆能力。
3.3 带工具和记忆的 SubAgent
更复杂一点的 SubAgent 可以拥有自己的工具集和记忆。比如一个“售后分析 Agent”,它内部可以调用查询数据库的工具、调用 NLP 分类的工具,还可以引用历史工单。这种设计非常自然:主 Agent 只关心“是否需要做售后分析”,具体的分析细节由 SubAgent 自己搞定。
给 SubAgent 挂载工具和给普通 Agent 挂载工具的方法完全一样,在 Agent 初始化时传入 tools 列表即可。记忆的设置也一样,关键是要注意隔离。如果一个客户支持系统里,多个 SubAgent 共享同一个全局记忆对象,很容易出现用户 A 的数据被用户 B 的 SubAgent 读到。我踩过一次这个坑,后来强制按 session_id 维度隔离记忆存储,才算解决。
3.4 注册到主 Agent 的两种方式
SubAgent 注册到主 Agent 有两种常见方式。
第一种是静态注册,也就是上面代码里演示的,在 Agent 初始化时通过 subagents 或 tools 参数传入。这种方式结构清晰,适合拓扑固定的系统。缺点是不能在运行过程中灵活调整,如果你想临时新增一个 SubAgent,必须重新创建主 Agent。
第二种是动态注册,在 Host 运行期间,通过框架提供的注册接口把新的 SubAgent 挂到已有的主 Agent 上。代码逻辑大概是:
python复制host.register_tool_for_agent(
agent_id="main_agent",
tool=order_tool,
)
动态注册适合插件化、可扩展的场景。比如你的系统支持第三方业务接入,每个第三方都可以注册自己的 SubAgent 来处理专属任务。不过这里要提醒一句:不要在主 Agent 已经开始多轮会话后频繁注册或注销 SubAgent。模型在每一轮决策时都会重新扫描工具列表,如果工具列表变了,它之前做过的路由判断可能会产生混乱。我实际操作下来,最稳妥的做法是“配置完成后再开会话”,要改配置就重新创建会话。
4. 状态传递和上下文管理:最容易翻车的地方
4.1 子任务结果的返回格式约定
SubAgent 的返回值会进入主 Agent 的上下文,这个返回值几乎决定了主 Agent 最终回答的质量。我在项目里给所有 SubAgent 定了一个返回约定:统一 JSON,必须包含 status、data、message 三个字段。
json复制{
"status": "success",
"data": {},
"message": "订单已发货,预计6月10日送达。"
}
如果出错,返回:
json复制{
"status": "failed",
"data": {},
"message": "未查询到订单编号为 9527 的订单,请确认订单号是否输入正确。"
}
这个约定的好处是,主 Agent 不需要理解 SubAgent 内部逻辑,只需要嗅探 status 字段。status 是 failed 时,主 Agent 可以直接基于 message 向用户道歉并说明原因,而不是自己瞎编。如果所有 SubAgent 都遵守同一套格式,主 Agent 的 instructions 也可以写得很短:“如果工具调用返回 status=failed,直接把 message 内容回复给用户。”
你可能会问,模型输出不一定严格遵循 JSON 怎么办。我的做法是在 instructions 里强行要求,并且在代码里增加一个轻量校验:如果 SubAgent 输出不是合法 JSON,框架就把它当作字符串原样返回。实际测试中,只要 instructions 足够明确,模型遵守格式的概率在九成以上。
4.2 上下文透传和截断策略
SubAgent 的返回值只是上下文的一部分。更隐蔽的问题是,主 Agent 的上下文会不断累积所有曾经调用过的 SubAgent 输入输出。如果你的 SubAgent 返回了一个超长报告,比如市场分析 Agent 生成了篇五千字的文章,这个结果会一直留在主 Agent 的上下文中,后续每次主 Agent 推理都要把它重新读一遍,成本直线上升。
解决方案是设置返回值长度上限。很多框架支持对工具返回内容做截断,但截断后有一个新风险:主 Agent 如果发现数据不完整,可能会瞎猜。所以我在返回 JSON 里增加了一个 truncated 字段,如果内容超长被截断,就标记为 true,同时要求主 Agent 在 instructions 里明确:“如果工具返回结果显示 truncated=true,不要假设缺失的信息,应该直接告诉用户结果不完整。”
我还试过另一种策略:对于超长结果,SubAgent 先在内部做一次摘要,再把摘要返回给主 Agent。这个方法比直接截断更智能,但会额外消耗一次模型调用。具体怎么选,要看你对“结果准确度”和“成本”的权衡。我的原则是,用户真正关心的核心数据字段必须完整保留,其他描述性内容可以摘要。
4.3 用 transcript 做链路排查
在 Multi-Agent 系统里,排错最大的困难是“无法直观看到信息在哪一步丢的”。如果你只是打印最终回复,完全不知道主 Agent 选择 SubAgent 的原因,也不知道 SubAgent 返回了什么。
幸好 Microsoft Agent Framework 通常会在运行结果里携带完整的 transcript,也就是每一轮 Agent 之间交互的记录。我在调试时基本都会先拉一遍 transcript:
python复制result = host.run(main_agent, "订单 9527 怎么还没到?")
for event in result.transcript:
print(event.type)
print(event.agent_id)
print(event.message)
通过 transcript 可以清楚看到主 Agent 是先做了一次路线判断,再调用 order_query_agent,接着看 order_query_agent 返回的内容是什么,最后看主 Agent 怎么组织回复。遇到异常时,我至少九成的问题都能通过阅读 transcript 找到根因。比如发现主 Agent 根本没有调用 SubAgent,说明它的工具描述和用户意图不匹配;发现 SubAgent 返回了一长串报错,说明它的内部工具出了问题。不要一上来就改 prompt,先看数据流。
5. 成本、并发与错误处理
5.1 Token 成本模型与预算控制
Multi-Agent 系统比单 Agent 系统烧钱,这是不争的事实。每次 SubAgent 调用不仅消耗 SubAgent 自己的输入输出 token,还会让主 Agent 的上下文因为工具调用的结果而变长。对于主 Agent 来说,所有 SubAgent 的名字和描述也作为工具描述存在,每次推理都要扫描一遍。
我总结了一个比较粗的公式:单次用户请求的总成本大约等于主 Agent 多轮推理的 token 加上所有被调用 SubAgent 的完整会话 token。这个成本不是简单相加,因为主 Agent 每多调用一个 SubAgent,就会多一轮推理,每轮推理都会重新读取之前累积的上下文。
控制成本的核心思路是减少不必要的调用和降低单次调用的 token 体积。首先,精简 SubAgent 的 instructions,不要写长篇大论;其次,能用一个 SubAgent 完成的两步任务,就不要拆成两个 SubAgent;再次,给 SubAgent 设置合理的最大步数,防止它陷入内部循环。
另外一个小技巧是:简单子任务用便宜的小模型。比如订单查询这种结构化任务,用 gpt-4o-mini 和用 gpt-4o 的准确率差别不大,但成本差别很大。我在项目里会给不同 SubAgent 配置不同模型,主 Agent 用较强的模型负责路由,简单查询类 SubAgent 用小模型,复杂分析类 SubAgent 用强模型。
5.2 并发与超时设置
如果你的主 Agent 需要同时调用多个独立的 SubAgent,比如“请对比 A 和 B 两个订单的物流进度”,框架通常支持并发执行多个子任务。并发能大幅降低整体延迟,但也需要控制好资源占用。
实际项目中我会这样配置运行参数:
python复制from agent_framework import RunConfig
config = RunConfig(
max_concurrency=4,
subagent_timeout_seconds=60,
max_tool_rounds=10,
)
result = host.run(
main_agent,
"对比一下订单 9527 和 9528 的物流状态",
config=config,
)
max_concurrency 控制同时最多跑几个 SubAgent,避免瞬间打爆模型接口的限流;subagent_timeout_seconds 防止某个 SubAgent 卡死;max_tool_rounds 限制主 Agent 最多能调用工具的次数,防止出现“主 Agent 反复失活”的死循环。
超时时间不是越长越好。我一开始设成 300 秒,结果有个 SubAgent 内部卡在外部 API 调用上,整个请求拖着不结束,后面的排队任务全被堵住。后来我根据实际任务耗时把超时设置成 60 秒,并在 SubAgent 的 instructions 里强调“如果外部接口超过 10 秒未响应,返回超时错误”,反而让整个系统更稳定。
5.3 常见错误与边界情况
我整理了一份高频错误清单,都是实际项目里遇到并且解决了的,希望能帮你省点排查时间。
| 问题现象 | 可能原因 | 处理方法 |
|---|---|---|
| 主 Agent 完全不调用 SubAgent | SubAgent description 不明确,主 Agent 判断为无关工具 | 在 description 第一句直接写“在什么情况下使用” |
| SubAgent 返回结果正确,但主 Agent 回答驴唇不对马嘴 | 主 Agent instructions 没有约定如何消化工具结果 | 要求主 Agent 优先使用工具返回的 message 字段回复 |
| SubAgent 递归调用自己,进入死循环 | 没有设置递归限制或最大步数 | 配置 max_steps,并在 instructions 中禁止自我调用 |
| 多用户会话数据串了 | 多个会话共享同一个 SubAgent 状态 | 按 session_id 隔离 SubAgent 的 memory |
| 上下文溢出 | SubAgent 返回超长结果,主 Agent 一直累积 | 设置返回值长度上限,超长时做摘要或截断 |
| 并发调用时结果顺序错乱 | 多个 SubAgent 返回顺序不确定 | 让每个 SubAgent 在返回值中携带 request_id 或上游调用 id |
这些错误里,最常见也最隐蔽的是第二类。主 Agent 明明拿到了一个正确的工具返回值,但因为自己的指令里没有约束,它可能会基于自己的“想象”再补充一段内容,结果把正确的信息改错了。我的解决办法是在主 Agent 的 instructions 里写死:“当工具调用返回 status=success 时,你只需要把 data 中的信息用自然语言组织出来,不要添加任何不在 data 中的信息。”
6. 实测效果与我的工程建议
6.1 一个完整调用链路的时序
我实际做的客服项目里,有一个典型的复合请求:“查一下订单 9527,如果已经发货就帮我直接申请退款。”这个请求在单 Agent 系统里会非常麻烦,因为既涉及查询,又涉及条件判断和退款流程。用主从模式拆分后,整条链路变得很爽朗:
- 用户输入进入主 Agent。
- 主 Agent 首先调用
order_query_agent,查询订单状态。 order_query_agent返回{"status": "shipped"}。- 主 Agent 根据这个结果,判断“已发货”满足退款条件,于是调用
refund_agent。 refund_agent内部可能需要进一步确认退款原因,它会先把“请提供退款原因”返回给主 Agent。- 主 Agent 把这句话转述给用户,用户回复原因后,主 Agent 再次调用
refund_agent。 refund_agent最终返回退款受理成功的 JSON。- 主 Agent 把结果组织成自然语言回复用户。
这个过程里,主 Agent 相当于工作流引擎,SubAgent 是各个节点的执行者。每一步的数据都是显式传递的,哪里出错都容易定位。
6.2 性能对比:SubAgent 与普通 Tool 的选择依据
我在这个项目中实际对比过用普通 Tool 实现订单查询和用 SubAgent 实现订单查询的差异。普通 Tool 直接调用数据库接口,延迟约 50 毫秒,成本可以忽略不计;SubAgent 需要先由主 Agent 生成一个调用指令,再启动 SubAgent,SubAgent 内部再调用数据库,最终返回结果,整体延迟约 2-3 秒,还要消耗两次模型调用的 token。
差距这么明显,为什么我还要在某些地方用 SubAgent?因为有些子任务不是简单的“查库返回”就能搞定的,它需要理解用户表述中的模糊信息。比如用户说“我那个黑色的东西还没到”,这句话里的“黑色的东西”无法直接生成数据库查询参数,必须由一个具备理解能力的 SubAgent 先做实体识别,再转换成结构化查询条件。这已经超过了普通 Tool 的能力范围。
所以我的建议是,把系统里的任务分成两类:一类是“输入输出规则明确”的任务,用普通 Tool;另一类是“需要模型补全信息、做推理判断、多轮澄清”的任务,用 SubAgent。分类的结果不是一成不变的,随着逻辑越来越稳定,你也可以把某些 SubAgent 逐步替换成普通 Tool,这也是一个持续优化成本的过程。
6.3 最后几条实用经验
项目结束后,我最大的一个感受是:设计 SubAgent 时一定要克制。能少一个就少一个,能缩小职责就尽量缩小职责。给 SubAgent 命名要像给函数命名一样,一眼能看出它是干什么的;description 第一句就说明使用条件,后面才是补充信息;先在独立环境里把每个 SubAgent 测通,再挂到主 Agent 上,尽量不要让主 Agent 在调试过程中成为你的“第一现场”。
我还建议在主 Agent 的 instructions 里加一行兜底说明:“如果你不确定该调用哪个工具,直接向用户澄清,不要乱选。”这行能明显降低误调用率。多智能体系统的错误通常是“有逻辑的错误”,比单个 Agent 的“胡言乱语”更难查,但只要数据流设计得干净,绝大部分问题都能靠 transcript 定位。
另外,接受主从模式本身的局限也很重要。主 Agent 是所有信息的中枢,当 SubAgent 数量超过一定规模,主 Agent 的上下文选择负担会越来越大。到那时候你可能需要引入更复杂的编排方案,比如多级路由、分层 Agent 群组,甚至全局会话状态管理。但从现阶段看,用 Microsoft Agent Framework 把 SubAgent 管好,已经足够支撑大多数中大型业务场景。
