SubAgent实战:基于Microsoft Agent Framework的多智能体架构设计

发布时间:2026/9/10 11:42:19
SubAgent实战:基于Microsoft Agent Framework的多智能体架构设计 上周复盘一个智能客服项目业务方给我看了一段对话记录用户问“华东区七月销售额为什么环比跌了12%”助手先是从数据库拉出三百多条订单明细然后试图在同一个上下文里完成清洗、对比、归因分析最后生成三页报告。结果不用猜也知道输出数据对不上号结论一半是幻觉。问题不是模型不行而是你让同一个 Agent 在同一个上下文窗口里干了太多事。这也是我最近把项目从“单 Agent”改造成“SubAgent”的直接原因改造用的框架是 Microsoft Agent Framework整体属于典型的 Multi-Agent 架构。这篇文章不是翻译官方文档是我自己这段时间踩坑、重写、调试之后的完整记录。如果你也在用 Agent Framework 做多智能体或者正打算把一个越来越臃肿的单体 Agent 拆开这篇能帮你省掉至少一周的试错时间。我会先把为什么必须拆 SubAgent 的原理讲透再给一份可直接抄作业的代码最后单独列一节专门说那些官方文档里不会写的坑。1. 为什么 SubAgent 能救回你的上下文窗口1.1 单 Agent 的天花板不是模型能力而是上下文很多人觉得智能体“笨”是模型不行我一开始也这么想。但后来把一次完整失败请求的 token 消耗拉出来看发现问题出在“同一个上下文要装太多东西”。一个陌生的用户问题进来Agent 需要同时理解问题、回忆历史、看几万字的系统提示、再调用十几个工具、把工具返回的长文本和原始问题塞在一起做推理。上下文窗口就像一张办公桌桌面就那么大你让一个员工在同一张桌子上既查报表、又做数据分析、还要写汇报正文他一定会把不同用途的文件叠在一起最后给你一份充满幻觉的结论。解决思路其实很朴素拆人。与其让一个 Agent 做所有事不如让父 Agent 做“理解问题和分发任务”把脏活累活交给专门的 SubAgent。SubAgent 可以有自己的系统提示、自己的工具列表、自己独立的上下文干完活只把精华结论返回给父 Agent。办公桌一下子就不挤了。1.2 层级式 Multi-Agent 和群聊式框架的本质区别Multi-Agent 并不是新概念。AutoGen 的 GroupChat、LangGraph 的图编排我都用过它们的核心模式是“多个人围着一张桌子讨论”。这种模式适合头脑风暴但真实业务里会出现一个很头疼的问题每条消息都要广播给在场所有 Agenttoken 消耗是指数级的而且容易出现“两个 Agent 互相甩锅、第三个插进来岔开话题”的失控局面。Microsoft Agent Framework 选的是另一条路层级式。父 Agent 是唯一的决策者SubAgent 是被调度的执行者。你可以理解为“主管接需求拆任务派给不同专家专家各自完成后交回结果主管汇总输出”。这种模式天然适合企业内部流程类的智能体查询、分析、生成、审核每一步都有明确的负责人不会出现群聊式框架里那种角色边界模糊的问题。1.3 SubAgent 和“函数调用”到底差在哪里有人会问既然父 Agent 通过 LLM 的函数调用就能调工具为什么还要包装一层 Agent我的回答是函数是死的Agent 是活的。一个工具函数只能严格按参数执行它对输入的理解能力为零而 SubAgent 本身就是一个小型 Agent只要有足够描述它可以理解模糊的请求、结合自己的系统提示做判断、甚至自主决定调用自己的内部工具。换句话说SubAgent 是一个可以独立维护、独立扩展、独立测试的执行单元这比在父 Agent 里堆几十个函数要清爽得多。2. Agent Framework 里的四种角色谁当爹谁干活2.1 AgentBase 派生体系AgentRoot 和 AgentWorkerMicrosoft Agent Framework 的 SDK 里所有智能体都继承自AgentBase但实际业务里你只需要关心它的两个子类AgentRoot和AgentWorker。名字已经说明了定位AgentRoot 是“根”是入口是能调配资源、定义工作流、发起调度的角色AgentWorker 是“干活的”是被 Root 调用、执行具体任务的执行者。我最初犯过一个概念性错误以为哪个 Agent 都能随便调用别的 Agent。实际上框架对能力边界做得很清楚——AgentWorker 默认不具备调度其他 Agent 的能力它只能执行自己的任务如果你想写一个既会干活又能指挥别人干活的 Agent它应该继承 AgentRoot。这个边界设计不是为了限制你而是为了避免“每个 Agent 都在互相调用”导致的循环失控。Agent 类型是否可作为 SubAgent是否能调度其他 Agent典型用途AgentBase可以不具备非常简单的原子任务AgentRoot可以但通常作为父 Agent是可配置 Workflow 和 SubAgent入口编排、任务分发AgentWorker可以最常用的 SubAgent否查询数据库、调用 API、生成文档等2.2 Workflow 与 AgentThread静态编排和动态调度是两回事Agent Framework 同时提供了两种编排手段很多人容易搞混。第一种是Workflow它是在代码里预先定义好的一组 Agent 步骤典型形式是AgentThreadThread 里可以串行放多个AgentStep也可以用AgentBranch做并行分支。这种是静态编排适合流程完全确定的场景比如“先查库存再算价格最后生成订单”。第二种是动态调度也就是父 Agent 自己判断“这个问题应该交给哪个 SubAgent”。这依赖 LLM 的工具调用能力父 Agent 把每个 SubAgent 的描述注册成可供模型选择的工具模型推理时自主发起调用。两者不是互斥的我在实际项目里经常混用外层用 Workflow 固定大流程内部某个步骤用动态 SubAgent 分发。这样既有确定性又有灵活性。2.3 AgentContract 和 AgentContext父子通信的两条管道父子 Agent 之间怎么传数据这是所有 Multi-Agent 设计最容易翻车的地方。Agent Framework 给出的答案是AgentContract和AgentContext。AgentContext是父子共享的执行上下文可以传递状态、会话 ID、用户身份等全局信息AgentContract则是子 Agent 干完活之后返回给父 Agent 的“交付物”本质是一个字典结构的协议对象。我的经验是AgentContext要小只放跨 Agent 都要用的全局信息AgentContract要走精简原则把很长的原始数据放在内部字段里把给父 Agent 做决策用的结论放在summary字段里。这样可以避免父 Agent 的上下文被子 Agent 返回的大量原始数据打爆。3. 实战把“销售数据问答”拆成三个 Agent3.1 场景拆解和分工设计先确立要做什么。我拿实际项目里的“销售数据问答助手”举例它的典型问题是“华东区七月销售额为什么环比跌了12%”。这个问题的处理链路是先查 CRM 订单库再把查出来的明细做汇总分析最后生成一段面向业务的口语化回答。如果用一个单体 Agent 做它只能把查询出来的几百条原始记录全部塞进一个上下文然后再开始分析结果就是上下文里垃圾数据太多推理质量直线下降。拆开之后是三个角色。父 AgentSalesAssistantAgent负责任务理解和最终回答CrmDataAgent是查询型 SubAgent负责对接数据库或 CRM APISalesAnalysisAgent是分析型 SubAgent负责对结构化数据做指标计算和归因分析。每个 SubAgent 的系统提示都非常聚焦CrmDataAgent 不需要懂什么叫环比SalesAnalysisAgent 也不需要知道数据库表结构。3.2 环境准备和依赖安装我用的环境是 Python 3.11 Agent Framework Python SDK0.3.x需要准备一个可用的 LLM 服务OpenAI 兼容即可。pip install agent-framework如果你要跑完整示例还需要一个 OpenAI 兼容的 Key 环境变量。SDK 内部默认走 OpenAI 的接口协议配置了OPENAI_API_KEY就能直接用如果是 Azure OpenAI额外设置AZURE_OPENAI_KEY和 endpoint 即可。提示Agent Framework 的 Python SDK 更新比较快不同小版本的构造函数和命名可能有出入。我这边代码以 0.3.x 的实际写法为准整体思路不受版本影响。3.3 定义两个 SubAgent查询 Agent 和分析 AgentSubAgent 的定义很直接继承AgentWorker实现execute方法同时通过AgentMetadata告诉父 Agent“我是谁、我能干什么”。AgentMetadata里的name和description是整个多智能体系统的关键因为父 Agent 里的 LLM 就是靠这两个字段来判断该调用谁的。from agent_framework import AgentWorker, AgentContext, AgentContract, AgentMetadata class CrmDataAgent(AgentWorker): 查询型子代理对接 CRM 订单数据 metadata AgentMetadata( namecrm_data_agent, description查询 CRM 系统中的客户交易数据。 当用户需要查看具体订单、销售额、品类明细时使用 不支持趋势分析和归因判断。 ) async def execute(self, context: AgentContext, task: dict) - AgentContract: customer_id task.get(customer_id) date_range task.get(date_range) filters task.get(filters, {}) # 这里在真实项目中调用内部 CRM API 或数据库 rows query_crm_orders(customer_id, date_range, filters) summary ( f已返回 {len(rows)} 条订单记录 f总销售额 {sum(r[amount] for r in rows)} 元 明细见 data 字段。 ) return AgentContract( statussuccess, summarysummary, datarows[:100], # 只保留前 100 条防止上下文爆炸 )接着是分析型子代理。它接收 CrmDataAgent 返回的结构化数据计算环比、同比、品类贡献度然后输出结论。class SalesAnalysisAgent(AgentWorker): 分析型子代理对结构化数据做指标计算和归因分析 metadata AgentMetadata( namesales_analysis_agent, description分析销售数据计算环比、同比、品类贡献度。 接收表格型销售数据返回分析结论不做原始数据查询。 ) async def execute(self, context: AgentContext, task: dict) - AgentContract: rows task.get(data, []) # 在这里计算环比、同比、各品类占比等 result compute_metrics(rows) summary ( f华东区七月销售额环比下降 {abs(result[mom_change]):.1f}% f主要原因是家用电器品类下降贡献了 {result[top_contributor]:.0%} 的降幅。 ) return AgentContract( statussuccess, summarysummary, metricsresult, )这两个 SubAgent 各自维持自己的一套系统提示和内部逻辑。注意我刻意在 CrmDataAgent 的 description 里加了“不支持趋势分析和归因判断”这是防止父 Agent 把分析类问题误派给查询类子代理。3.4 定义父 Agent用 SubAgent 列表完成自动路由父 Agent 继承AgentRoot通过sub_agents把自己的两个子代理挂载进来。框架的 Schedule 会自动把这些 SubAgent 注册成可供 LLM 调用的工具列表。当用户问题进来父 Agent 的 LLM 判断“需要先取数”就会自动调用crm_data_agent拿到数据后再判断“需要算指标”又自动调用sales_analysis_agent。整个过程不需要写 if-else。from agent_framework import AgentRoot, AgentContext, AgentContract class SalesAssistantAgent(AgentRoot): sub_agents [ CrmDataAgent, SalesAnalysisAgent, ] async def execute(self, context: AgentContext, user_request: str) - AgentContract: # 框架会把 sub_agents 转成可供 LLM 选择的工具列表 # 用户问题进来后模型按需发起函数调用Schedule 调度对应 SubAgent result await context.llm.complete( system_prompt( 你是销售数据分析助手。 先判断用户是否需要查询数据如果需要调用 crm_data_agent 拿到数据后调用 sales_analysis_agent 做指标分析 最后基于子代理返回的 summary 生成自然语言回答。 ), user_inputuser_request, toolscontext.schedule.build_tools(self.sub_agents), ) return AgentContract(statussuccess, answerresult.output)这里有一个很关键的设计点父 Agent 的系统提示里明确写了调用顺序“先查数据再分析”但没写具体怎么算环比。真正算数的逻辑在 SalesAnalysisAgent 内部父 Agent 只负责流程编排和最终表达。这样做的另一个好处是以后换了计算逻辑只需要改 SalesAnalysisAgent完全不影响父 Agent。3.5 运行结果parent 只拿到 summary不被明细淹没实际运行这段代码后日志里的调用链路会非常清晰用户问题进入 SalesAssistantAgent模型判断先调用 crm_data_agentCrmDataAgent 执行完返回 summary 加最多 100 条明细模型拿到 summary 后接着调用 sales_analysis_agentSalesAnalysisAgent 返回指标分析摘要最终 SalesAssistantAgent 基于第一轮和第二轮的 summary 生成回答。我对比过改造前后的 token 消耗。单体 Agent 处理同样的问题需要把几百条订单记录全部塞进主上下文一次调用的 token 经常冲上几万拆成 SubAgent 之后父 Agent 的上下文里始终只有两份 summary总 token 稳定在几千到一万出头。回答质量反而更高因为父 Agent 不再需要从几百行原始数据里硬找规律。4. 父 Agent 怎么知道该用哪个 SubAgent元数据与契约设计4.1 name 和 description 就是 SubAgent 的“简历”很多人写完 SubAgent 就跑发现父 Agent 压根不调用它或者每次都调用同一个子代理。十有八九是AgentMetadata里的 description 写得不到位。父 Agent 的底层模型是靠 name 和 description 做工具选择的这段描述就是子代理的简历。简历写得太宽泛模型什么活都派给它写得太窄模型根本不知道有它。我的建议是 description 分三部分第一句是这个 SubAgent 的核心能力第二句是它的输入条件第三句明确列出“不做什么”。我在 CrmDataAgent 里写“不支持趋势分析和归因判断”就是为了让模型在遇到分析类问题时自动绕过它。这个“负向限定”最容易被忽略但对路由准确率的提升非常明显。4.2 AgentContract 字段设计结论给父 Agent明细留给自己子 Agent 返回的 AgentContract 会整体塞进父 Agent 的上下文。所以它的每个字段都是要付 token 的。我之前吃过亏CrmDataAgent 把完整的订单明细放在 summary 里返回父 Agent 的上下文瞬间多了两万 token不仅贵而且把模型的注意力稀释了。正确的做法是给 AgentContract 分两类字段。summary里只放父 Agent 做决策需要的结论比如“已返回 128 条记录总销售额 52 万”data、metrics这类结构化字段放详细的数据但父 Agent 并不会主动把它们全部读进推理上下文。父 Agent 用第一层 summary 判断下一步该调用谁只在需要细节时才去读 data。字段内容是否建议用途summary一句到三句的结论性摘要必填父 Agent 做下一步决策的依据statussuccess / error / retry必填父 Agent 和调度器判断是否重试data结构化原始数据按需给下游 Agent 或最终用户用metrics指标计算结果按需后续 Agent 进一步分析使用error出错原因、堆栈摘要出错时必填错误恢复和日志排查4.3 父子之间的上下文传递不是无限透明的Agent Framework 的 AgentContext 是父子共享的但共享的是“上下文状态”不是无脑压缩的整个对话历史。当你让 SubAgent 执行一个异步任务时它拿到的其实是父 Agent 传入的任务描述和 AgentContext而它自己的对话历史是独立的。这个隔离非常关键SalesAnalysisAgent 不需要知道用户问过什么历史问题它只需要知道“拿到这堆数据算出环比”。如果子代理继承了父 Agent 的全部历史对话token 成本会成倍增长而且子代理会被无关上下文干扰。5. 最容易翻车的五个 SubAgent 细节5.1 子代理被误调用description 写太宽我在 4.1 里已经提过这里说一个真实案例。最初我把 SalesAnalysisAgent 的描述写成“分析销售数据”结果用户问“最近三个月哪些品类卖得好”父 Agent 直接把问题丢给 SalesAnalysisAgent而它根本不知道这些数据从哪来拿到 task 里的 data 是空的开始一本正经地胡编。后来我在描述里加了一句“必须在拿到 crm_data_agent 返回的数据后才使用”误调用率立刻降下来了。description 要写成一段“路由提示”而不是功能简介。5.2 上下文串扰多个 SubAgent 共用一个 context当父 Agent 并发调度多个 SubAgent 处理不同任务时很容易出问题。比如同时跑“华东区分析”和“华南区分析”如果两个 SubAgent 用的是同一个 AgentContext中间数据可能互相覆盖。我的解决方法是给每个 SubAgent 的任务上下文加一个唯一 task_id所有中间数据都按 task_id 隔离存储。这属于架构上的强制约定不能只靠“小心写代码”。5.3 递归调用和循环调用没有设置深度限制AgentRoot 可以包含 AgentWorker但一个 AgentWorker 如果本身也被配置成 AgentRoot或者某个 Agent 的工具列表里包含了另一个能调度它的 Agent就可能出现 A 调 B、B 调 A 的死循环。有一次我本地调试父 Agent 连续递归调用了四次同一个子代理最后把 token 拉爆了。从那以后我养成了两个习惯给所有 SubAgent 调用加最大嵌套深度在 AgentContract 里传parent_agent层级字段下游 Agent 如果发现层级超过阈值直接返回“需要人工介入”。5.4 并发调度导致 token 成本不可控AgentBranch 做并行分支确实能缩短整体延迟但并行意味着多个 LLM 调用同时计费。我做过一次压测三个子代理并行执行每个返回 2000 token 的分析加上父 Agent 的汇总单次请求成本大约是串行模式的 1.8 倍。如果你对成本敏感要给每个 SubAgent 单独限制max_tokens并且在父 Agent 一侧设置全局 token 预算超过预算强制走降级逻辑只返回数据不生成分析。5.5 子代理内部报错后父 Agent 假装成功最隐蔽的坑在这里子 Agent 在执行过程中调了一个 API接口超时了返回了很长的错误堆栈但父 Agent 的模型在生成最终回答时看到 context 里有这个错误堆栈没有如实汇报反而自己编了一个“接口修复成功”的假结果。我的做法是在 AgentContract 里强制要求子代理出错时设置statuserror同时把错误摘要截断到 200 字以内父 Agent 的系统提示里明确写“如果子代理返回 error必须如实告知用户问题原因禁止猜测成功”。设置之后这个“假装成功”的问题基本绝迹。6. 从本地 Demo 到线上 Gateway 部署6.1 Agent Gateway 把整棵 Agent 树发布成服务本地跑通不是结束业务方要的是接口。Agent Framework 提供 Agent Gateway 组件可以把父 Agent 发布成标准 HTTP 服务。我这边用 Docker 起了一个 Gateway 容器只暴露父 Agent 的入口SubAgent 全部在 Gateway 内部管理。这样对调用方来说就是普通的 REST API 或 Event Stream不需要知道背后有几棵 Agent 树。6.2 用 Agent Analytics 观察每个 SubAgent 的调用链上生产之后不能只看最终回答好不好还得知道每个 SubAgent 被调用了几次、每次消耗多少 token、延迟多少。Agent Analytics 能输出每个父 Agent 的完整调用链包括每次 SubAgent 调用的输入摘要、输出摘要、模型名、耗时。我在灰度阶段就看一个指标SubAgent 的“误调用率”即父 Agent 调用某个子代理但子代理返回 no-op 或无效结果的比例。这个比例超过 15%基本就是 description 写得有问题需要回调。6.3 不是所有 SubAgent 都得用同一个模型很多人会忽略SubAgent 完全可以配置不同的模型。我的默认做法是查询类子代理用快速低价模型因为它的任务就是拉数据、写摘要推理量不大分析类子代理用强推理模型因为归因分析对模型能力要求高父 Agent 必须用支持函数调用的模型否则整个动态路由就失灵了。模型混用在 Agent Framework 里不需要改代码只需要在创建不同 SubAgent 时传入不同的 model 配置即可但要注意总成本是各层累加的不能在单个 Agent 层只看自己的消耗。写到这里说句实在话SubAgent 这套机制最大的价值不是“多了一个 Agent”而是它逼着你重新思考任务边界。我在项目里最明显的感受是拆之前所有逻辑都搅在一起谁也不敢动拆之后每个子代理都变成了可以独立升级的模块——查询逻辑变了只改 CrmDataAgent分析算法变了只改 SalesAnalysisAgent父 Agent 几乎不用碰。如果你现在维护的单体 Agent 也开始频繁出现“上下文不够用”“模型输出不稳定”的问题与其继续往系统提示里加规则不如试试把这个 Agent 拆成一个根节点加几个 SubAgent。刚开始会有点别扭但跑通之后你会觉得这才是多智能体该有的结构。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询