如果有人问我 2025 年上半年 .NET 方向最值得关注的新东西,我会毫不犹豫提名 Microsoft Agent Framework(MAF)。原因很简单:它把 Multi-Agent 的编排成本一下子拉低到了“写配置文件 + 少量注册代码”就能搞定,而且还内置了 SubAgent 这种原生子代理模式。我从公共预览版开始落地,陆陆续续跑了几个真实业务场景,最大的体会是——单 Agent 解决不了的问题,拆成子代理之后,效果往往是质的差别。
这篇文章不打算重复官方文档里的 Hello World,我直接把 0.1 到 1 的完整过程写出来:从包选型、YAML 定义、宿主注册,到子代理的动态任务调度、状态共享、可观测性踩坑,全部摊开讲。如果你正准备把一个逐渐失控的单 Agent 重构为可维护的多代理系统,这应该能省你不少弯路。
1. 为什么单 Agent 撑不起复杂业务,而 SubAgent 可以
先从一个很典型的现象说起。很多人做 Agent 的第一版都极其顺利:一个模型、几百行指令、几个 tool,业务就跑通了。但上线三个月后开始变味——业务方不断往里加需求,你不断往 instructions 里追加行为约束,prompt 越来越长,模型开始“顾此失彼”,工具调用的选择也越来越飘。最后你发现,系统不是被模型限制卡死的,是被指令冲突和上下文污染卡死的。
1.1 单 Agent 的四个天花板
我自己的体感是,单 Agent 架构有四个早晚会撞上的天花板:
- 上下文窗口瓶颈:所有工具结果、历史对话、业务规则最后都堆进同一个 context,窗口再大也不够用。尤其当工具是网页抓取、代码检索这类高 token 消耗操作时,很快就把预算烧光了。
- 指令优先级冲突:同一个 Agent 既要“扮演客服安抚情绪”,又要“严格按 API 返回结果给事实”,还要“输出 JSON”。模型在多个目标之间做取舍,行为就变得不可预测。
- 失败爆炸半径大:一次工具调用异常或一次错误的上下文拼接,会直接污染整个对话轮次,而且很难定位到底哪一步出的问题。
- 难以并行和复用:单个 Agent 是串行工作的,天然无法把独立子任务拆给多个并发执行者。
这些问题的本质,是把“一个负责决策的大脑”和“多个负责执行的肢体”硬塞进了同一个进程、同一个 context。而 Multi-Agent 架构,尤其是 SubAgent 模式,恰恰是把决策和执行拆开。
1.2 MAF 里的四种多代理模式
Microsoft Agent Framework 在架构上把协作模式分成了几类,理解这个分类很重要,因为它决定了你的系统设计:
| 模式 | 核心特点 | 适用场景 |
|---|---|---|
| Single Agent | 一个代理独立完成任务 | 简单问答、单工具调用 |
| SubAgent | 主代理在运行时动态创建子代理,把子任务委派出去 | 任务可变性高、子任务边界清晰 |
| Teams | 多个代理相对固定地分组协作,有明确的角色和会话规则 | 客服团队、固定流程会议 |
| Workflows | 代理之间通过预定义的工作流/状态机连接 | 审批流、数据管道、定时任务 |
SubAgent 和 Teams 的区别很微妙,很多初学者会混淆。Teams 模式下,代理集合基本是预先确定的,它们的角色像一个固定阵容;而 SubAgent 模式下,上层代理是按需创建下层代理的,同一个上层代理,面对不同的任务,可以动态拉起完全不同的子代理组合。这种动态性才是 SubAgent 最值钱的地方。
1.3 什么场景才真的需要 SubAgent
不是所有项目都需要 Multi-Agent。如果任务链条短、工具数量少、交互模式固定,老老实实用单 Agent 反而更稳。我自己的判断标准有三条:
- 业务存在明显的子任务边界,比如“先检索、再写作、再审校”这种可以切成阶段的流程。
- 子任务之间存在隔离需求,某个子任务的失败不应该污染主任务状态。
- 你希望独立演进某个能力,比如换掉“写作”这个子模块的模型或提示词,而不影响其他部分。
满足其中两条,SubAgent 模式就值得认真考虑。
2. Microsoft Agent Framework 与原有多代理框架的差异到底在哪
如果你之前接触过 Semantic Kernel、AutoGen、LangGraph、CrewAI 这类东西,可能会问:MAF 和它们到底有什么本质区别?直接说结论:MAF 不是又一个“Agent 框架”,它更像一个Agent 运行时与编排层,目标是把上层的 AI Agent 框架和下层的企业基础设施粘合起来。
2.1 从 Semantic Kernel 到 Agent Framework 的演进
MAF 和 Semantic Kernel(SK)的关系比较有意思。SK 解决的是“如何让模型和代码、记忆、工具更好地协作”,它的核心抽象是 Kernel、Plugin、Function,本质上还停留在“函数调用”这一层。你在 SK 里写 Multi-Agent,仍然需要自己去编排消息循环、自己管理代理生命周期、自己处理跨代理状态。
MAF 则是在 SK 之上做了一层面向 Agent 生命周期与协作的抽象。它提供了:
AgentFactory:统一创建代理的入口,代理定义可以是 YAML。AgentContext:给代理运行提供计时器、遥测、请求响应等基础设施。AgentRuntime:负责代理任务的分发、长时运行、水平扩展。- 声明式能力:代理的定义不写在代码里,而是写在 YAML 配置里,代码只负责注册和启动。
这个设计直接改变了开发方式。以前你要理解整套 agent 内部逻辑,必须去读代码;现在团队里一个新的开发者,光看 agents.yaml 就能知道系统有哪些角色、各自干什么。
2.2 “框架无关”与统一 Worker 接口
MAF 还有一个很激进的特性:代理元框架无关。它允许你接入 Semantic Kernel 的 Agent、OpenAI 的 Agent SDK、LangChain 的 Agent,甚至 Carbon 等第三方运行时,然后统一暴露成AgentWorker接口。这意味着你团队里有人用 SK 写代理、有人用 LangChain 写代理,上层编排不用改,这是很多框架做不到的。
在我实际使用中,这个特性的价值主要在渐进式迁移。我们有一个老系统是用别的框架写的 Agent,迁移到 MAF 时不需要全部推翻重写,只要把代理外面包一层 Worker 适配,就能被 MAF 的编排层托管,业务侧无感。
2.3 和其他主流框架的横向对比
| 框架 | Agent 定义方式 | 子代理支持 | 运行时 | 企业特性 |
|---|---|---|---|---|
| Microsoft Agent Framework | YAML 声明式 | 原生 SubAgent 模式 | 支持分布式 Runtime | 强,含治理、可观测性 |
| Semantic Kernel | 代码为主 | 需自行实现 | 无独立运行时 | 一般 |
| AutoGen | 代码为主 | 支持对话式多代理 | 无独立运行时 | 弱 |
| LangGraph | 代码/图定义 | 通过图节点实现 | 有平台但生态较新 | 中等 |
| CrewAI | 代码为主 | 角色分工 | 无独立运行时 | 弱 |
表格可能有点简单粗暴,但方向是真实的:MAF 最大的差异化不在于“能建多代器”,而在于它把多代理从“代码调出来的”变成了“配置声明出来的”,并配了一个真正的运行时。
3. 环境准备与最小 Demo:先把一个 Agent 跑起来
聊再多架构,不如先把一个 Agent 跑起来。MAF 公共预览版目前主要支持 .NET,我用的是 .NET 8,建议至少安装 SDK 8 以上,用 VS 2022 17.12 或直接命令行都可以。
3.1 项目初始化与包选型
创建一个控制台项目:
dotnet new console -n SubAgentDemo cd SubAgentDemo然后引入核心包。公共预览阶段,我实际用的是这几个包:
dotnet add package Microsoft.Agent.Framework dotnet add package Microsoft.Agent.Framework.Extensions dotnet add package Microsoft.Agent.Framework.Extensions.SubAgents dotnet add package Microsoft.SemanticKernel注意:SubAgents 扩展在早期是以独立包发布的,后续版本可能合入主包,这很正常。装的时候建议直接看 NuGet 上的最新版本,以 0.x 的预览版本号为主。
3.2 配置模型连接
我用的是 Azure OpenAI,需要在项目里配置客户端凭据。可以在appsettings.json里写,也可以用环境变量。我倾向用环境变量,避免把密钥放进仓库:
export AZURE_OPENAI_API_KEY="你的key" export AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/"MAF 的模型配置是放在 YAML 里的,但 provider 相关的连接信息会从宿主配置取。这块和 SK 的习惯一致,算是对老玩家的友好设计。
3.3 编写最简 Host 引导代码
MAF 应用本质上是一个 Generic Host 应用。先写一个最简入口,确保框架能启动:
using Microsoft.Agent.Framework; using Microsoft.Agent.Framework.Extensions; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; var builder = Host.CreateApplicationBuilder(args); builder.UseAgentFramework(); var host = builder.Build(); await host.StartAsync(); var factory = host.Services.GetRequiredService<AgentFactory>(); // 这里的 "echo" 是我们即将定义的一个代理名称 var agent = factory.CreateAgent( Path.Combine(AppContext.BaseDirectory, "agents.yaml"), "echo" ); await agent.RunAsync("你好,帮我确认系统正常。"); await host.StopAsync();在项目目录下放一个agents.yaml,并设置“复制到输出目录”:
name: echo description: A simple echo agent for smoke testing instructions: | 你是一个简单的测试代理,用户说什么,你就如实确认你收到了什么。 model: provider: azureopenai name: gpt-4o-mini跑起来之后,如果控制台能看到模型回复,说明环境链路通了。这一步虽然看起来原始,但能帮你在引入 Multi-Agent 之前就排除掉“模型连接、YAML 解析、Host 装配”这类基础问题。
3.4 常见的启动坑
我在第一步踩过一个比较典型的坑:CreateAgent的路径是基于当前工作目录的,而不是项目目录。如果使用dotnet run从项目根运行通常没问题,但如果是发布后从别的目录启动(比如 systemd 服务),路径就会失配。建议像我上面那样,用AppContext.BaseDirectory拼出绝对路径,同时把 YAML 文件的“复制到输出目录”属性改为“如果较新则复制”。
4. 用 YAML 定义“整套”Agent 配置的实战写法
YAML 定义在整个 MAF 里地位很高。它不只是配置文件,更是代理的“身份契约”。把代理的定义和实现解耦,最大的好处是:调优行为不用改代码,改 YAML 就行。但这也意味着 YAML 写得好不好,直接决定系统的上限。
4.1 Agent YAML 的核心字段
以我们实际用的子代理为例,拆解一下字段含义:
schema_version: 1 name: researcher description: 负责检索资料、整理事实依据的研究型子代理 instructions: | 你是一个研究助手。你会收到一个研究任务,任务是主代理分配给你的。 请按照以下步骤执行: 1. 确认任务中的主题和范围 2. 调用可用的检索工具获取信息 3. 输出结构化的资料摘要,包含来源、关键事实、引用 注意:你只需要输出研究结果,不要尝试写作或审校。 model: provider: azureopenai name: gpt-4o-mini skills: - name: web_search type: nativename:代理的唯一标识,CreateAgent和AddSubAgent里都靠它定位。description:这个字段不只是给人看的,更是给上层代理“选择谁”看的。上层代理是通过 description 来判断某个子代理是否适合当前子任务的。instructions:子代理的系统提示词,只对它自己的 context 生效。model:可以给子代理指定不同的模型。便宜的模型跑量,贵的模型做决策,这是多代理系统成本控制的重要杠杆。skills:代理能用的工具集合。MAF 里技能可以是原生代码技能,也可以是 SK 插件,还可以是 MAF 内置的 AgentTask、AgentCache 这类特殊技能。
4.2 description 的“面向选择器”写法
这里有个很重要的经验:description 是写给“调度者/上层代理”看的,不是写给用户看的。很多人习惯写“我是研究助手”,这太泛了。更好的写法是明确说明“我负责什么、我能接受什么任务、我会产出什么格式”。比如:
description: > 检索型子代理。擅长联网搜索、知识库检索与事实核查。 当主代理需要背景资料、数据支撑、来源引用时,调用本代理。 输入为研究主题,输出为带来源的结构化摘要。写得越具体,上层代理的调度准确率就越高。我甚至建议在 description 里写明“什么情况不要用我”,比如“不要用我处理情感类内容”,这能在一定程度上减少误调度。
4.3 instructions 的隔离与边界
子代理的 instructions 和主代理的 instructions 是隔离的,这一点一定要吃透。子代理看不到主代理的全部对话历史,只会收到主代理通过 AgentTask 工具传过来的任务描述。这个隔离既是优点也是约束:优点是不会被无关上下文污染;约束是你要在任务描述里把上下文讲清楚,否则子代理会“断章取义”。
所以在设计子代理时,我会在 instructions 里固定两个小节:输入解读和输出协议。输入解读告诉子代理“你会收到什么格式的任务”;输出协议告诉它“你必须返回什么结构”。这样主代理传参时只需要按约定填内容,子代理不会自由发挥。
4.4 用多个 YAML 组织代理群
我建议一个系统使用一个主 YAML 文件,或按目录分隔:
config/ agents.yaml # 主代理定义 sub_agents/ researcher.yaml writer.yaml reviewer.yaml主代理和子代理可以放在不同文件里。AddSubAgent注册时分别传路径即可。注意,主代理定义里不会显式列出“我有哪些子代理”,子代理列表是通过宿主注册注入到运行时的,主代理在运行时靠 tool 感知它们。这个设计和很多人想当然的“父引子”不同,一开始我也理解错了。
5. 构建一个真实的多代理系统:稿件生产流水线
概念讲完,上实战。我用一个“稿件生产系统”来做完整示例:主代理接收一个写作主题,动态拉起三个子代理——研究者、写手、审核者——分别完成资料检索、初稿生成、质量审校。这个场景足够典型,既涉及动态调度,也涉及状态共享,还涉及终止条件。
5.1 定义三个子代理
先定义sub_agents/researcher.yaml:
name: researcher description: > 研究型子代理。擅长检索信息、归纳事实、提供结构化资料。 当主代理需要背景资料、数据支撑、来源引用时,调用本代理。 输入为研究主题,输出为带来源的结构化资料摘要。 instructions: | 你是研究子代理。你会收到主代理传来的研究主题。 步骤: 1. 解读主题,提取关键检索词。 2. 使用 web_search 技能检索至少 3 个有效来源。 3. 输出 JSON 格式的研究报告: { "topic": "主题", "key_findings": ["关键结论"], "sources": [{"title": "标题", "url": "地址"}] } 不要编写正文,不要给建议,只输出事实与来源。 model: provider: azureopenai name: gpt-4o-mini定义sub_agents/writer.yaml:
name: writer description: > 写作型子代理。擅长将研究资料改写为流畅的科普/技术稿件。 当研究资料已准备好、需要成稿时,调用本代理。 输入为研究摘要与写作要求,输出为符合要求的完整文章。 instructions: | 你是写作子代理。你会收到研究摘要和写作要求。 步骤: 1. 以研究摘要中的事实为准,不虚构数据。 2. 按照要求的篇幅、语气、结构输出文章。 3. 用 Markdown 输出,标题清晰。 如果研究摘要缺失,明确说明“缺少资料”,不要强行编造。 model: provider: azureopenai name: gpt-4o定义sub_agents/reviewer.yaml:
name: reviewer description: > 审核型子代理。擅长检查文章的事实准确性、逻辑连贯性、格式规范。 当初稿完成、需要质量把关时,调用本代理。 输入为待审核文章,输出为审核意见与修改建议。 instructions: | 你是审核子代理。你会收到一篇文章。 请检查以下维度: 1. 事实是否有依据,是否有明显的无根据断言。 2. 逻辑是否连贯,段落衔接是否自然。 3. 格式是否符合 Markdown 规范。 4. 是否满足要求中的篇幅。 输出审核意见,格式: 【通过/需修改】 问题列表: - 严重问题 - 建议修改 model: provider: azureopenai name: gpt-4o-mini5.2 定义主代理
主代理不直接干活,它扮演的是“拆解任务 + 分配 + 聚合结果”的角色:
name: editorial_coordinator description: 稿件生产主代理,负责把写作任务拆解给研究、写作、审核子代理。 instructions: | 你是稿件生产协调者。你会收到用户的写作请求。 流程: 1. 先启动 researcher 子代理,传入写作主题,取得研究资料。 2. 再启动 writer 子代理,传入研究资料和写作要求,取得初稿。 3. 最后启动 reviewer 子代理,传入初稿,取得审核意见。 4. 如果审核意见要求修改,将修改意见连同初稿传给 writer,重新生成。 5. 汇总最终稿件与审核结论,在回复中展示: - 最终稿件 - 资料来源(如果有) - 审核结论 注意: - 子代理之间不要互相传递原始对话历史,只传递任务所需的摘要。 - 如果某一步子代理任务失败,重试一次,仍失败则向用户说明。 model: provider: azureopenai name: gpt-4o5.3 在宿主中注册子代理并运行
接下来是代码装配。在Program.cs里通过AddSubAgent注册子代理:
using Microsoft.Agent.Framework; using Microsoft.Agent.Framework.Extensions; using Microsoft.Agent.Framework.Extensions.SubAgents; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; var builder = Host.CreateApplicationBuilder(args); var appBuilder = builder.UseAgentFramework(); // 注册子代理,state 参数指向 YAML 文件 appBuilder.AddSubAgent( state: Path.Combine(AppContext.BaseDirectory, "config/sub_agents/researcher.yaml") ); appBuilder.AddSubAgent( state: Path.Combine(AppContext.BaseDirectory, "config/sub_agents/writer.yaml") ); appBuilder.AddSubAgent( state: Path.Combine(AppContext.BaseDirectory, "config/sub_agents/reviewer.yaml") ); var host = builder.Build(); await host.StartAsync(); var factory = host.Services.GetRequiredService<AgentFactory>(); var coordinator = factory.CreateAgent( Path.Combine(AppContext.BaseDirectory, "config/agents.yaml"), "editorial_coordinator" ); await coordinator.RunAsync( "写一篇介绍 Microsoft Agent Framework 的科普文章,目标读者是 .NET 开发者,1500 字左右。" ); await host.StopAsync();代码本身不复杂。真正的工作量在 YAML 的指令设计里。运行时里发生的事是:主代理判断任务需要拆解,于是调用 AgentTask 工具,传入 researcher 的描述与输入,MAF 运行时动态创建 researcher 实例;研究结果返回后,主代理再启动 writer,依此类推。
5.4 流水线成功的关键:任务描述即接口
这个系统跑起来之后,我发现最核心的优化点是任务描述的“接口化”。主代理在调度时传给子代理的文本,就是子代理的输入接口。这个接口是否稳定,直接影响子代理的输出质量。
我会给每个子代理在 instructions 里规定输入格式,同时在主代理 instructions 里也写清楚“传给 researcher 的主题要包含哪些字段”。比如:
传给 researcher: 主题:{topic} 额外要求:{requirements}这样即使主代理换了模型、换了提示词,只要它按这个格式传参,子代理就不容易跑偏。这是我从实践中悟到的一条经验:多代理系统的稳定性,不取决于单个代理多聪明,而取决于代理之间的接口契约多清晰。
6. 子代理编排细节:动态任务、缓存共享与终止控制
上面那个例子能跑,但离“生产可用”还差一步——你无法控制子代理之间的状态共享、并行关系和终止行为。MAF 的 SubAgent 模式里,这几个问题分别由三个工具解决:AgentTask、AgentCache、AgentTerminate。
6.1 AgentTask:子代理的创建与执行
AgentTask 是 MAF 的内置技能,也是 SubAgent 模式的触发器。主代理调用它时,本质上是向运行时提交了一个“子代理任务”。这个任务包含:
- 目标子代理的名称或描述。
- 传给子代理的输入内容。
- 期望的输出形式。
运行时会负责实例化子代理、执行、回收结果。这种“任务式调度”有几个好处:一来任务可以被记录、追踪、重试;二来任务可以在不同的进程甚至主机上执行;三来任务天然支持排队,避免多个子任务同时打爆 API 的 rate limit。
在我做的实测里,同一批子任务配置了并发后,稿件生产流水线从串行的 40 秒左右降到了 22 秒左右,收益很明显。并行是把双刃剑,并发太高可能触发模型服务限流,我的建议是先保守并发数,观察延迟和错误率再逐步调整。
6.2 AgentCache:让子代理之间共享高频上下文
子代理的 context 是隔离的,但很多场景下它们需要共享一些高频信息:比如用户身份、业务规则、术语表。如果每个子代理任务都带着完整术语表,token 成本会指数级上升。
AgentCache 工具就是干这个的。它可以让你在子代理之间共享一部分只读上下文,类似于给所有子代理带了一张“公共记忆卡”。我实际用下来,最适合放进去的是:
- 全局业务术语与缩写解释。
- 统一的输出格式约束。
- 需要保持一致的事实基准,比如公司名称、产品或版本号。
但不要什么垃圾都丢进去。缓存内容会被所有子代理读取,一旦更新,可能影响正在运行的一批任务。我建议缓存只放“高频、稳定、只读”的内容,可变的状态还是通过任务参数传递。
6.3 AgentTerminate:多代理的刹车
多代理系统最怕的是死循环:主代理反复修改、反复再生成,token 烧完还没结束。MAF 提供了 AgentTerminate 工具,让代理显式表达“任务完成”的语义。
在稿件生产流水线里,我给主代理的指令就是“审核通过或重试一次后,必须调用 AgentTerminate 结束任务”。这个指令虽然简单,但能避免很多失控情况。我还结合了超时机制:子代理任务如果超过预设时长,运行时强制终止。这些强制边界在预览版阶段尤其重要,因为多代理行为的不确定性和单代理不是一个量级。
6.4 状态传递的最佳实践
子代理之间传递状态,我建议遵循三条原则:
- 只传摘要,不传原文。上一个子代理的完整输出往往包含大量无关内容,传摘要能显著省 token。
- 用结构化格式传递。比如 JSON,字段明确,解析也方便。纯文本容易导致下一环误解。
- 传递时带版本或来源标记。尤其在写作流水线里,审核意见引用初稿某一段时,最好带上段落编号,否则子代理不知道你指的是哪一句。
有一段时间我们的流水线产出经常“逻辑断裂”,排查下来发现是子代理之间传的是大段聊天记录,writer 分不清哪些是资料、哪些是历史消息。改成结构化摘要后,问题立刻消失。这类问题在单 Agent 时代是不存在的,算是多代理特有的“沟通税”。
7. 可观测性与线上排错:别让多代理变成黑盒
多代理系统最大的运维噩梦是黑盒:你只看到用户发了一条消息,系统内部发生了 4 次子代理调用、3 次工具回调,然后用户说“结果不对”,你根本不知道哪一环出了问题。所以从一开始,就要把可观测性当成功能来做,而不是事后补救。
7.1 接入 OpenTelemetry 与运行时遥测
MAF 对 OpenTelemetry 支持是内建的。你只需要在 Host 里加上 tracing 即可:
builder.Services.AddOpenTelemetry() .WithTracing(tracing => { tracing.AddSource("Microsoft.Agent.Framework") .AddConsoleExporter(); });加了之后,每次代理任务、子代理任务、工具调用都会输出带 trace 的记录。你可以看到完整调用链:主代理启动了哪个子代理、传了什么任务、子代理调用了哪些工具、耗时多少、在哪一环失败。这套信息比任何日志都好使。
实际排查的时候,我最常用的视图是“按任务划分的耗时瀑布”。一眼扫过去,就能发现是研究阶段慢,还是写作阶段慢,还是某个工具调用拖后腿。曾经有一个场景,整体响应时间 90% 花在检索工具上,给子代理换了一个更快的召回接口后,直接缩短了 30 秒。
7.2 三个高频问题与对应解法
问题一:子代理“答非所问”。
现象:子代理回复的内容看起来正确,但格式完全不合规。根因往往在 YAML 的 instructions 没有明确输出协议,或者 task 输入里没有提供足够的输出样例。解法:在 instructions 里给出一个具体输出样例,模型照样例输出的概率会大幅提升。
问题二:主代理迟迟不调用子代理,自己硬干。
现象:你定义了一堆子代理,但主代理绕过它们直接回答。根因通常是 description 写得太笼统,主代理认为“自己处理更直接”。解法:强化主代理 instructions 里的流程,并明确告诫“不要自己执行子任务,必须分派”。同时 description 要写得能让主代理一眼判断“这事该给谁”。
问题三:任务超时,整个对话卡死。
现象:某个子代理一直不结束。根因可能是子代理陷入了自我修正循环,或者模型服务本身超时。解法:给子任务设置明确的超时时间,并训练主代理在超时后走降级路径(比如“重试一次,不行就明确告诉用户”)。我在生产环境里的做法是,每个子任务都配上最大执行次数和超时阈值,宁可返回“部分成功”,也不要无限等待。
7.3 日志与审计的额外注意点
多代理系统里,日志不是越多越好,而是越结构化越好。我建议从第一天起,就给每条日志打上task_id、agent_name、attempt这几个标签。只有这样才能把分散在多个子代理中的日志串起来。预览版阶段,我也吃过日志没有统一 trace id 的亏,排查问题时只能靠时间戳猜测先后顺序,效率极低。
如果你把 Agent Runtime 部署成分布式模式,这个问题会更突出,因为日志会散落在不同主机上。尽早接入集中式日志平台(比如 ELK 或者云原生日志服务),会比事后补救省力得多。
8. 我的落地体会与接下来的计划
从 Single Agent 迁到 SubAgent 模式,我最大的感受是:架构变轻了,心智负担变重了。以前是“一个模型什么都要懂”,现在是“每个模型只干一点,但你要把边界划清楚”。这个过程很像把一个全能型员工的职责拆成一支小团队——每个人职责更纯粹,但团队的默契需要设计。
8.1 迁移时最值得注意的三件事
先说第一件:不要一步到位。哪怕你已经确定最终要用多代理,也建议先让系统在单代理模式下跑通,再逐步抽离子模块。直接上多代理,出问题你都不知道是模型的问题还是链路的问题。
第二件:子代理的模型选择要分档次。不是所有子代理都要用最强模型。我的经验是,“检索类”和“格式化类”的子代理用 gpt-4o-mini 这类小模型完全够,只有“决策类”和“终审类”的才值得上大模型。这样一套流水线跑下来,token 成本能省 40% 以上。
第三件:接口契约大于一切。与其花时间调一个子代理的 prompt,不如花时间把子代理之间的输入输出格式定死。稳定的接口比聪明的模型更能提升系统整体的可靠性。
8.2 后面我打算怎么扩展
我现在还在持续关注 MAF 的两个能力:一是Agent Runtime 的分布式模式,把子代理任务真正分发到多台机器上执行,解决单机并发瓶颈;二是Long Running Steps,让子代理任务可以挂起、等待外部事件、再恢复执行——这个对涉及人工审批的业务流程价值会非常大。
Calendars 技能我也在试,可以让代理在特定时间点被唤醒执行任务,比如每天早上九点自动跑一次“数据汇总 + 简报生成”,这就是 SubAgent 和定时工作流的组合玩法。
8.3 一个小建议:先手动构造一次“伪多代理”跑通业务
如果你暂时不想引入 MAF,或者团队还没准备好看 .NET 代码,可以先做一个“伪多代理”模拟:用代码把任务拆成多个步骤,每个步骤调用一次独立的模型 prompt,步骤之间传 JSON 数据。把这条链路跑通后,再把每一步替换成真正的 SubAgent,迁移会顺滑很多。
这本质上就是多代理系统的雏形。理解了这个雏形,再看 MAF 的 YAML 配置,你会发现它不过是在替你处理那些“调度、生命周期、状态共享、失败重试”的枯燥工作。换个角度说,MAF 的价值不是发明了多代理,而是让多代理从“专家才能玩的东西”变成了“配置就能落地的东西”。
如果你是 .NET 技术栈、又恰好被单 Agent 的各种限制折磨过,花一个周末把 MAF 的 SubAgent 模式试一遍,大概率会打开新思路。