通用Agent架构:配置化注册、多Agent协作与成本控制
上一节认真做了一个 agent。这节回答更上层的问题:当 agent 从一个变成一群,架构要长成什么样?三条线展开——把单 agent 模式抽象成配置化注册(17章扩展的地基)、用多 Agent 协作模式解决单 Agent 的能力天花板、用成本控制守住账单。
先把概念阶梯摆正,后面所有模式才不乱:工具(一段确定性代码)→ agent(预制提示词+工具的自主决策体)→ 多 agent 编排(一群 agent 按某种控制权分配方式协作),一层层递进。单个 agent 与一堆工具的区别只在那份预制提示词;而多个 agent 要配合,就必须回答一个新问题——任务推进过程中,谁来掌控节奏。这正是本节的主线。
先看单 Agent 的三种失败模式
不是"能不能跑"的问题,是跑得越久越糟的问题。拿前两节的 agent 具体说:
失败一:上下文随工具结果膨胀。 docs-agent 的 search_docs 每次带回 5 段文档片段(每段 400 字),用户追问三轮,上下文里堆了几千 token 的检索结果——真正有用的结论埋在最前面。更糟的是这些结果跟着会话走:每轮对话全部重新进入模型上下文。上下文越长,注意力越分散,回答质量越低,费用还越高:每次工具调用都在给未来所有轮次交税。
失败二:工具太多,模型选择困难。 假设把课程里做过的所有工具挂到一个 agent 上:get_time、get_weather、search_docs,再加想象中的 query_order、analyze_code——8 个工具。模型每一步决策都要在 8 份工具描述里做选择,工具越多误调率越高(用户问天气却触发了文档检索这种事真实会发生)。还有更隐蔽的能力过宽:查文档的 agent 手里如果握着写操作工具,模型"热心"起来会替只是随口问问的用户改数据。
失败三:所有请求都用强模型,成本浪费。 "InputArea 的 disabled 怎么传"这种检索一句话能答的问题,和"帮我分析整个主题系统怎么重构"这种要多步推理的问题,用的是同一个强模型。强模型按复杂问题定价,而典型流量里七成是简单问题——全走强模型,等于打车去买菜。
三个失败模式指向同一个方向:任务要拆开,拆给不同角色、不同模型、不同上下文的多个 agent。但怎么拆、拆完谁说了算,正是多 Agent 开发的核心设计问题。
配置化注册:从"一个 agent"到"一群 agent"的架构地基
回看 16-01 重构后落下的机制,三个设计选择让"加 agent"这件事的成本趋近于零:
选择一:agent 即数据。 MastraAgentDefinition 是纯配置对象——id、instructions、tools、model 全是数据,不是类、不是继承体系。数据化的定义让注册、序列化、校验、动态创建都变得平凡。
选择二:注册表是唯一装配点。 buildAgentDefinitions(env) 返回数组,网关逐个构建注册。这个函数就是"这个服务有哪些 agent"的单一事实来源——16-02 加 docs-agent 只动了一行。新增能力的 diff 面积,是架构好坏最直观的度量。
选择三:工具与记忆可插拔。 tools 是字典注入,memory 是工厂函数——两者都是 agent 定义的普通字段。这个设计的价值马上兑现:17-01 的 MCP 工具进 tools 字典、17-02 的插件贡献 agent 定义进注册表、17-03 的记忆换 memory 工厂、17-04 的语义检索升级 tools 字典——17章四个主题全部接在这三个口上。
多 Agent 协作:先想清楚谁掌握控制权
一群平级 agent 各答各的还不叫系统。如果用过 n8n、Dify 或者字节的扣子(Coze),对"workflow"已经有体感——把节点拖进画布、连线定顺序,那就是步骤明确的流程编排:明确知道"先做什么后做什么"时才建工作流;步骤不明确、只给一堆工具让 AI 自己决定怎么调,那是 agent 的领地。Mastra 官方把多 Agent 协作归纳为四种模式,区分它们的核心维度只有一个:任务推进过程中,控制权在谁手里(Multi-agent systems 指南)。
四种模式一张图
| 模式 | 控制权在谁手里 | 适用场景 | 代价 | Mastra 实现 |
|---|---|---|---|---|
| Handoffs | 当前专家 | 下一专家应直接接管对话(如售前→技术支持) | 上下文移交管理变重 | agents + workflows + memory |
| Workflows | 执行图(代码) | 路径预先确定,要可预测可审计 | 任务变化时缺乏适应性 | Workflows(15-02 讲过的串行/并行/条件调度就是它的雏形) |
| Supervisor | 一个主 Agent | 开放式任务,边执行边决定委派谁 | 高度依赖委派质量和子agent边界清晰 | supervisor agents:agents 属性注册子agent |
| Council | 最终综合步骤 | 问题模糊/评审性/高风险,要多视角互查 | 刻意重复劳动,token 翻倍、延迟变长 | 无专用原语,workflow .parallel() + 综合步骤 |
实践中模式会组合:workflow 的某个步骤内嵌 supervisor;council 跑在 workflow 里,产出汇入最终审批。选型的判断依据是协调问题本身,不是任务的名字。
怎么选:三问决策树
一句总原则(官方原话的中译):能用一个 agent 就先用一个 agent;当额外的结构确实能改善质量、速度或可靠性时,才加结构。
Supervisor 深入:研究协调器实战
四种模式里 Supervisor 最常用也最有代表性——开放式任务(研究、排查、综合报告)在真实产品里占比最高。Mastra 官方的研究协调器教程是个最小完备案例:一个 supervisor 协调 research-agent(查事实)和 writing-agent(写成文),产出综合报告。拆开看它做对了什么。
架构:角色分工 + 委派控制设施
角色设计有两个反直觉但关键的点:
- 子 agent 的
description是给 supervisor 看的路由文档。 research-agent 的 description 写明"returns concise bullet-point summaries…does not write full articles"——正因为它声明了"不做什么",supervisor 才不会把写作任务错派给它。这印证了 16-02 的"三份提示词"方法论:instructions 之外,description 和 schema describe 同样是模型间协作的契约。 - 模型档位倒挂分配。 supervisor 用强模型(做规划、路由、综合——每次决策都影响全局),两个专家用轻模型(执行单一职责)。和直觉里"干活的用好的"相反,也和后面 Explorer/Executor 的分法一致。
supervisor 的定义:instructions 三段式 + delegation 设施
// packages/dev-server/src/agents/supervisor-agent.ts(对齐官方教程的结构)
import { Agent } from '@mastra/core/agent'
import { researchAgent } from './research-agent'
import { writingAgent } from './writing-agent'
export const supervisorAgent = new Agent({
id: 'supervisor-agent',
// 强模型做调度,轻模型做执行
model: resolveModelConfig({ model: env.model }),
// instructions 三段式:资源清单 / 委派策略 / 成功标准
instructions: `你协调研究与写作任务。
可用资源:
- research-agent:搜集事实与来源,返回要点清单,不成文
- writing-agent:把研究素材转为结构化文章
委派策略:
1. 研究类请求:先委派 research-agent 收集事实
2. 写作类请求:携带已有研究上下文联派 writing-agent
3. 综合报告:先 research 后 writing
成功标准:
- 用户请求的每个方面都被覆盖;信息准确且有来源;信息不足时继续收集而非硬编`,
agents: { researchAgent, writingAgent }, // ★子agent注册:supervisor模式的核心开关
memory: createMemory(),
defaultOptions: {
maxSteps: 10, // 步数预算:防无限循环(15-02 Orchestrator 的 maxSteps 同款思想)
delegation: {
// 委派前钩子:可以按子agent改写下行 prompt
onDelegationStart: async (context) => {
if (context.primitiveId === 'research-agent') {
return {
proceed: true,
modifiedPrompt: `${context.prompt}\n\n聚焦近期进展,包含统计数据。`,
}
}
return { proceed: true }
},
// 委派后钩子:失败时刹车并把反馈交回 supervisor
onDelegationComplete: async (context) => {
if (context.error) {
context.bail() // 停止后续委派
return { feedback: `委派 ${context.primitiveId} 失败:${context.error},换一种方式。` }
}
},
// 上下文裁剪:子agent只看到最近10条,隔离历史噪声
messageFilter: ({ messages }) => messages.slice(-10),
},
isTaskComplete: {
scorers: [taskCompleteScorer], // 完成度打分器
strategy: 'all',
},
},
})
typescript
instructions 的三段式(可用资源 / 委派策略 / 成功标准)就是 16-02 提示词迭代法在多 Agent 场景的复用:资源清单和子 agent 的 description 呼应,委派策略写死调度顺序,成功标准给"继续还是收工"提供判据。
delegation 三件套 + isTaskComplete 解决的正是手搓方案最难啃的三个问题:下行上下文的注入控制(onDelegationStart 改 prompt)、失败的处理语义(bail + feedback 回流而不是异常炸链)、"做完了没有"的判定(scorer 打分,不达标 supervisor 自动继续迭代)。另有 onIterationComplete 每轮迭代回调(日志里能看到 finishReason 与响应长度),适合做过程观测。scorer 来自 @mastra/core/evals,需要单独安装 @mastra/evals 包。
完成度 scorer 用 @mastra/core/evals 的 createScorer 写,规则打分零额外 token:
// packages/dev-server/src/mastra/scorers/task-complete-scorer.ts
import { createScorer } from '@mastra/core/evals'
export const taskCompleteScorer = createScorer({
id: 'task-complete',
name: 'Task Completeness',
}).generateScore(async (context) => {
const text = (context.run.output || '').toString()
const hasSubstantialContent = text.length > 500 // 有实质内容
const hasStructure = text.includes('\n\n') // 有段落结构
const hasContext = /\d{4}/.test(text) // 含年份等事实锚点
return hasSubstantialContent && hasStructure && hasContext ? 1 : 0
})
typescript
一轮完整委托的时序
把官方教程的执行日志画成时序图,supervisor 的"边走边决策"就看得很清楚——每轮 finishReason 是 tool-calls(还要继续委派)还是 stop(可以收工),由模型结合 scorer 结果决定:
对照执行日志的要点:委派对子 agent 是普通工具调用(日志里 finishReason 为 tool-calls),前端消息流里每个子 agent 对应一张委托卡片(16-02 的定制渲染直接复用),子 agent 内部的十几步过程不进用户视野——过程复杂度和展示复杂度解耦。
手搓 orchestrate 工具 vs Mastra 原生 Supervisor
聊天演示里的多 agent 协作走的是手搓工具路线——把"编排"实现成一个普通工具(仓库里叫 orchestrate,src/orchestration/orchestrate-tool.ts)。它和 Mastra 原生 supervisor 是同一思想的两种实现层次,放在一起看正好理解"原理 → 生产"的阶梯。
手搓版的核心:orchestrate 就是一个工具,description 就是路由文档——真实代码里的 description 原文值得逐句读:
编排子 agent(检索员/起草员/审查员)协作完成 ai-chat-ui 组件库相关任务,
返回各阶段产出、耗时与 token 用量。pattern 选用:
delegate(委托)——检索员查文档、起草员写稿两步走查,适合单一明确的问题;
parallel(并行)——三路检索员按组件用法/配置与主题定制/集成与数据流转并行取材,
适合需要多角度覆盖的问题;
pipeline(流水线)——检索、起草、审查,审查不过自动打回重写一次,适合对产出质量有要求的任务。
NOT TO USE:闲聊、与组件库无关的问题、无需协作即可回答的简单追问——不要调用本工具。
text
三个设计点都值得抄:WHEN/NOT TO USE 都写明(防误调的路由文档,呼应 16-02 "三份提示词");pattern 是枚举参数(模型只选形态,执行图由代码保证);整体兜底——execute 里 try/catch,异常转错误字符串返回,不抛出、不打断父 agent 链路。编排逻辑本体在 patterns.ts:零 Mastra 依赖的纯函数,输入子 agent 调用接口 AgentCall、输出 OrchestrationResult(各阶段 role/status/durationMs/usage/output)——编排是代码不是提示词,这也是它能在测试里裸跑的原因。三个子 agent(researcher 检索员/writer 起草员/reviewer 审查员)的定义在 orchestration/children.ts,仍是纯配置对象。
| 维度 | 手搓 orchestrate 工具 | Mastra 原生 Supervisor |
|---|---|---|
| 委派机制 | 自定义工具 + 子 agent 实例闭包注入(工厂创建一次,调用不重建) | agents 属性声明,框架内置 |
| 下行 prompt 控制 | 自己拼 description 字符串 | onDelegationStart 钩子按子agent改写 |
| 失败处理 | 错误转字符串返回(不打断循环) | bail() 刹车 + feedback 回流 supervisor |
| 上下文裁剪 | 手动截断 | messageFilter 声明式配置 |
| 完成度判定 | 无(靠 maxSteps 兜底) | isTaskComplete + scorers 自动续跑 |
| 适用 | 理解原理、极致可控、委派逻辑简单 | 生产默认选择,委派复杂或需要质量门禁 |
教学顺序故意的:先手搓一个几十行的 orchestrate 工具,把"委托的本质是工具调用"这件事钉死;再认识原生 supervisor——它没有魔法,只是把你在手搓版里迟早要写的四个补丁(prompt改写/失败语义/上下文裁剪/完成度校验)做成了声明式配置。看懂手搓版,才知道原生版每个配置项在救什么命。
Explorer/Executor 的角色切分原则不变(也是官方 supervisor 案例的同款分法):分界线是"能不能改东西"——
| 维度 | Explorer(探索者) | Executor(执行者) |
|---|---|---|
| 工具 | 只读(检索/查询类) | 全量(含写操作) |
| 模型 | 便宜快速(轻模型档) | 强模型 |
| 步数预算 | 5 步 | 15 步 |
| 面对用户 | 否 | 否(决策权只在父 Agent) |
一句提醒:委托不是免费的。每次子 Agent 调用是全新的模型运行,有自己的系统提示词和启动 token。父 Agent 三步内能干完的活,委派出去反而更贵更慢。
原生 Workflow 落地:pipeline 与 council 的最小实例
手搓版之后,同样的编排用 Mastra 原生 Workflow 又写了一遍(src/workflows/)——构成"手搓→原生"的教学阶梯:两层共用 orchestration/prompts.ts 的同一套 prompt 模板和 children.ts 的同一组三角色定义,行为同源。差异只在控制权的表达方式:手搓版靠代码 if/for,原生版靠 .then()/.branch()/.parallel() 声明执行图。
实例一:docs-pipeline(Workflows 模式)——检索 → 起草 → 审查 →(revise 时)打回重写一次 → 汇报,图结构与手搓 runPipeline 完全一致:
// packages/dev-server/src/workflows/docs-pipeline.ts(链式部分,steps 定义略)
return createWorkflow({
id: DOCS_PIPELINE_WORKFLOW_ID,
description: DOCS_PIPELINE_WORKFLOW_DESCRIPTION,
inputSchema: z.object({ task: z.string().describe('要回答的组件库问题') }),
outputSchema: z.object({ task, finalDraft, verdict, comment, revised }),
steps: [retrieve, draft, review, revise, report],
})
.then(retrieve)
.then(draft)
.then(review)
.branch([
// 1.60 的 branch 元组第二元素是单个 Step(非数组)
[async ({ inputData }) => inputData.verdict === 'revise', revise],
])
.then(report)
.commit()
typescript
几个值得停一下的细节:每步 inputSchema/outputSchema 用 zod 声明,步与步之间的数据契约编译期可查;review 的 verdict 是枚举(parseVerdict 从审查员首行的 [pass]/[revise] 契约解析);branch 后的 report 步用 getStepResult(review)/getStepResult(revise) 按步 id 取可选挂载的分支输出——1.60 的 branch 语义。
实例二:docs-council(Council 模式)——同一问题三路检索员并行取材,writer 综合裁决:
// packages/dev-server/src/workflows/docs-council.ts(核心骨架)
const retrieveApi = retrieveStep('retrieve_api', PARALLEL_ANGLES[0]) // 组件用法与 API
const retrieveTheme = retrieveStep('retrieve_theme', PARALLEL_ANGLES[1]) // 配置与主题定制
const retrieveData = retrieveStep('retrieve_data', PARALLEL_ANGLES[2]) // 集成与数据流转
return createWorkflow({ id: DOCS_COUNCIL_WORKFLOW_ID, /* schemas */ })
.parallel([retrieveApi, retrieveTheme, retrieveData]) // 三路并行
.then(synthesize) // .parallel() 输出以 step id 为 key 汇成对象
.commit()
typescript
synthesize 把三路结果按角度拼进 prompt(冲突之处注明来源角度),产出 finalReport。实测让它总结"组件库体系",finalReport 给出"围绕对话容器、消息展示、数据适配、主题定制组织"的结论——三路视角在 Studio 的时间线上同时起跑、各自耗时、全部完成后才进 synthesize,并行原语的执行形态一目了然。
第三个:docs-supervisor(Supervisor 模式的 workflow 形态)——主 agent 每轮经 structuredOutput(action: research/draft/done 三态枚举)决定委派谁,.dountil(delegate, 谓词) 循环到决策 done 或轮次上限(SUPERVISOR_MAX_ROUNDS = 6,步数预算防无限循环,与 maxSteps 同思想)。控制权始终在主 agent 手里——四模式定义在代码里的直接体现;每轮决策与子 agent 产出都在 trace 可见。
三个 workflow 的注册方式和 agent 一样是注册表:workflows/registry.ts 的 buildWorkflowRegistry(env) 是唯一事实来源,Studio 宿主(src/mastra/index.ts)与 HTTP 路由(src/routes/workflows.ts)都从这里取清单——新增 workflow = 一个工厂函数 + 注册表一行,与 16-01 的 agent 注册表完全同构。"机制长什么样,加东西就有多便宜"再次得到验证。
workflow 接进聊天应用:三种入口
workflow 在 Studio 里能跑能调试之后,怎么让产品用户用上?三种入口形态,按产品形态选:
- 独立入口页:workflow 当功能页提供,用户填 task 直接触发(适合工具型场景,如"PPT 制作""PDF 编辑"这类确定性流程——豆包/扣子的快捷指令就是这个形态);
- 顶层 supervisor 自动路由:聊天消息先进一个主管 agent,由它判断这条消息要不要触发 workflow——体验最顺,代价是多一层模型决策;
- 快捷按钮/指令前缀:界面上提供模式选择,用户点选"并行/流水线"后发送——课程 playground 走的这条(MultiAgentDemo 页)。
真实落地的关键一笔在协议层:routes/workflows.ts 把 workflow 的流事件经 event-mapping.ts 映射成 StreamChunk(step 起止复用 tool_call/tool_result 帧,finish 的 finalDraft/finalReport 走 text 帧,失败走 error 帧)——POST /api/workflows/:id/run 与 /api/chat 同一条 SSE 线协议,前端零改动复用全部渲染。写这个映射时还踩到一个版本坑:官方 .d.ts 的 WorkflowStreamEvent 落后于运行时(缺 stepName/startedAt 等实测字段),所以映射层用宽容结构接收、未知事件一律不发包——对外部库的流事件做防御性收窄,是接流的常规姿势。
成本控制四板斧
委托模式本身就是第一层省钱:七成检索类流量走轻模型,同代模型强/轻量档位输入价差通常 3-10 倍,这部分流量成本直接降 60% 以上。剩下三层是硬控制,设计都遵循一条原则:系统约束的技术表达,交给模型转成人话。
一、按角色选模型。 supervisor 强、专家轻;Explorer 轻、Executor 强。这在仓库里有现成实证:children.ts 的三角色里,researcher 是高频便宜活——env 配了 MASTRA_SUB_MODEL 就用这个次级模型(如 deepseek-chat),writer/reviewer 仍走主模型;.env.example 里对应的注释就写着"多 agent 编排演示'按角色选模型'"。产品侧的延伸(首句简单问题走便宜模型的动态路由)19-02 从计费角度再深化。
二、token 预算控制器。 两条线:单请求限额(防单次任务跑飞)和每日限额(防账单失控)。关键设计:超限不抛异常,返回人话提示交给模型转述——"这个任务太大了,试着拆成几个小问题分别问"。预算检查挂在 task 工具 execute 开头:预算不够就不 spawn 子 Agent,白跑一趟才是真浪费。
三、常见问答缓存。 文档助手这类场景,高频问题占很大比重。问题归一化(去空格标点、转小写)后哈希做 key,答案带 24h TTL。命中缓存零 token 消耗、首字延迟从秒级到毫秒级。两条纪律:答案标注"来自历史回答"(16-02 的诚实呈现原则),个性化数据不进缓存。
四、三级降级:绝不裸抛错误。 每一级都比上一级弱一点,但永远比报错强:
L3 文案与 16-02 的错误两层结构对上:用户话术在上、技术详情折叠在下。用户永远不该看到堆栈,但排查时技术信息一条不少。
四板斧的完整成本账本——一次对话多少 token、1000 DAU 月账单怎么算、定价怎么定——是 19-02 的主题,这里先把架构口留好:成本数据在网关层都是可观测的(usage 字段随每次响应返回)。
小结
- 三种失败模式:工具结果撑爆上下文(每次调用给未来轮次交税)、工具多了选择困难且能力过宽、简单问题用强模型烧钱——指向"任务要拆给多个 agent"
- 配置化注册三选择:agent 即数据、注册表是单一事实来源(加 agent 的 diff 面积是架构度量)、工具与记忆可插拔——17章三个主题全接在这三个口上
- 多 Agent 四模式:Handoffs(控制权随阶段转移)/ Workflows(路径写死在代码)/ Supervisor(主 Agent 全程掌控)/ Council(多视角独立作答+综合);选型看协调问题不看任务名,能单 agent 就别拆
- Supervisor 生产实现:子 agent 的 description 是路由文档(声明"不做什么"防错派)、模型档位倒挂(调度强/执行轻)、instructions 三段式(资源/策略/标准)、delegation 三件套(prompt改写/失败bail+feedback/上下文裁剪)+ isTaskComplete scorers 完成度自动续跑
- 手搓 vs 原生:task 工具钉死"委派本质是工具调用";原生 supervisor 把四个迟早要写的补丁做成声明式配置——看懂手搓版才知道原生版每个配置项在救什么命
- 成本四板斧:按角色选模型(researcher 走 MASTRA_SUB_MODEL 是仓库实证,省 60%+)、预算控制(超限转人话)、问答缓存(归一化+TTL+诚实标注)、三级降级(轻模型→缓存→友好错误,绝不裸抛)
- 手搓 orchestrate 工具:description 是路由文档(WHEN/NOT TO USE 都写明)、pattern 是枚举参数、编排逻辑零 Mastra 依赖纯函数、错误转字符串不打断链路
- 原生 Workflow 落地:docs-pipeline(.then/.branch 链式执行图、zod 步间契约、getStepResult 取分支输出)、docs-council(.parallel 三视角→synthesize)、docs-supervisor(structuredOutput 三态决策 + 轮次上限);workflow 注册表与 agent 注册表同构——"新增 workflow = 一工厂+一行"
- 接进聊天应用:独立入口页 / 顶层 supervisor 自动路由 / 快捷按钮三形态;流事件映射成 StreamChunk 同线协议,前端零改动
- 章末架构全景:8 包仓库、一条服务线、agent/workflow 定义收敛一处、扩展口预留三个——这套地基上,17章开始接生态(MCP/插件/记忆),18章做前沿交互,19章谈商业化
本章小结(16-04)从更高的视角收束整个 Agents 体系:Mastra 的分层架构、子代理扩展、可观测性落地。之后进入 17 章:MCP 工具系统(宿主视角)、插件系统、记忆系统——都是在这三个预留口上动手术。
↑