介绍一下这篇文章,在底层是如何实现sub agent的

回复 群友 3131 这篇是 Vercel AI SDK 的 Agents 文档里讲的 subagent(子 agent)章节。它没讲怎么调度、怎么路由,核心就一句:subagent 不是框架的特殊功能,就是一个普通工具调用。 底层实现,从下往上说: 一,subagent 就是一个 ToolLoopAgent 实例。文档里先 new 一个 researchSubagent,给它自己的 model、instructions、tools。这就是一个完整 agent,只不过它是被别处调用,而不是直接面向用户。所以 sub agent 可以单独换模型、单独限工具集——你之前问的“派个别的模型的 sub agent 审代码”,在 AI SDK 里就是给这个 ToolLoopAgent 的 model 字段填另一个模型,一行配置的事。 二,挂载方式是 tool 的 execute 里调它。父 agent 的 tools 里放一个 researchTool,description 写“深度研究某个问题”,inputSchema 只有一个 task 字符串;execute 里 await researchSubagent.generate({ prompt: task }),返回 result.text。就这么点东西,没有专门的 registerSubagent API。父模型看到的是一个名为 research 的工具,决定要不要调、调几次、并行调几次,全走工具调用那一套常规机制。 三,上下文隔离是天然结果,不是额外机制。每个 subagent 调用开一个新的 context window,不继承主 agent 已经积累的那一大坨对话。这正是它能跑几十万 token 做探索、而主对话不被污染的原因。需要历史的话,execute 签名里有 messages,可以手动拼进去,但文档明确说别滥用,滥用等于放弃隔离。 四,回传给主模型的内容可以裁剪,关键在 toModelOutput。工具调用的完整输出(subagent 每一步的工具调用、中间结果)会留在消息历史里给前端渲染,但 toModelOutput 可以把它映射成很小的文本,比如只取最后一条 text part 当摘要。于是 UI 上用户看到 subagent 全程干活,主模型只吃那一千 token 的总结,十万 token 的探索成本不进主上下文。前提是得在 subagent 的 instructions 里明确要求“结束时写一份完整摘要作为最终回复”,否则它可能只回一句 Done,你就没东西可裁。 五,流式进度用的是 preliminary tool result 机制。把 execute 从普通 async 函数改成 async function*,每次 yield 一个结果,前端就收到一次暂定输出。注意每次 yield 是整体替换而不是追加,所以要配 readUIMessageStream:把 subagent 的 stream 转成 UIMessage 流,它内部累积,每次产出一条“到目前为止的完整消息”,你 yield 出去,前端直接整条替换。UI 侧靠 tool part 的 state 和 preliminary 标记判断:output-available 且 preliminary 为 true 就是在流,false 就是终态。 六,取消靠 abortSignal 透传。父请求被 abort 时信号传到 subagent,subagent 抛 AbortError,工具执行失败,主循环停下。但这样会留下没有结果的工具调用,所以下一轮转消息时要用 convertToModelMessages 配 ignoreIncompleteToolCalls 把这类残缺调用过滤掉,否则模型会抱怨工具调用不完整。 七,两个明确的限制。一是 subagent 的工具不能走审批流,toolApproval 这类人工确认在 subagent 里不可用,所有工具必须自动执行——这跟我之前说的“审核 sub agent 最好只读、别给写权限”是同一件事的另一面:既然没法在 subagent 里加人工确认,那就只能靠给它只读工具集来兜底。二是没有内置的并行调度器,文档里说的“开多个 subagent 同时研究”实际上就是让主模型在同一条消息里发多个工具调用,能不能并行取决于你的工具执行层是不是并发跑,SDK 本身没替你调度。 一句话概括:AI SDK 把 subagent 实现成了“工具里再跑一个 agent,加一个输出裁剪口”,隔离和并行都是这个结构自然产生的效果,而不是额外的运行时抽象。和 Claude Code 那种内置 Task/Agent 工具的框架比,AI SDK 这边是你要什么自己拼,可控但得自己搭。