
当 Agent 要动手实现一个订单模块时它需要读需求文档、看现有代码结构、写业务实现、自己 review 一遍再跑测试。这一路下来的代码 diff、测试输出和 review 意见全堆在同一条 messages[] 里几轮之后模型开始“失忆”连前面刚确认的设计决策都记不住。这种上下文污染正是 SubAgent 编排要解决的痛处而完整跑通这套 task 工具调起 SubAgent 的流程需要一条真实可用的模型 API。TaoToken 提供了现成的兼容通道打开 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 创建 YOUR_API_KEY把 Base URL 填成 https://taotoken.net/api主 Agent 和 SubAgent 共用这把 Key就能把原文的 SubAgent 示例原样跑起来。1. 为什么 SubAgent 需要自己的上下文1.1 中间过程的“废料”让主 Agent 越来越笨主 Agent 每多执行一步消息列表就多一串新内容。bash 命令的输出、read_file 读进来的源码、edit_file 产生的 diff、测试脚本的报错堆栈模型在下一次推理时都要重新“读”一遍才能继续决策。到了第 8 轮、第 10 轮上下文窗口被这些中间产物吃掉大半模型开始丢掉前面的结论甚至回头重复问已经确认过的问题。这不是模型能力不行而是上下文里塞了太多与最终交付无关的过程数据。比如让 Agent 写一个订单模块真正对用户有价值的只有最终代码和简要说明但执行过程中读过的十几个文件、跑过的三次测试、改过的五版 diff都是为了让模型走到最终答案的“脚手架”。脚手架留在原地大楼就没法继续盖。1.2 分而治之主 Agent 拆任务SubAgent 交结论因此更合理的结构是让每个子任务拥有独立的上下文。主 Agent 只负责拆解任务、派发、汇总具体活交给 SubAgent 去干主 Agent ├── CodeWriterAgent → 只写代码返回代码 简要说明 ├── CodeReviewerAgent → 只审查代码返回 review 意见 ├── TestAgent → 只写单元测试返回测试代码 覆盖率 └── DocumentAgent → 只写文档返回文档草稿每个 SubAgent 在它自己的 messages[] 里工作干完活只把结论文本交还主 Agent。这样主 Agent 的上下文始终只有三部分用户原始需求、各 SubAgent 返回的精炼结论、最终汇总回复。上下文规模不会随着子任务复杂度线性膨胀。还需要注意协作顺序。SubAgent 之间的关系不一定是并行的CodeReviewerAgent 必须等 CodeWriterAgent 写完代码才能开工TestAgent 也要等实现完毕。这种情况下主 Agent 需要串行编排先派 CodeWriter拿到返回结果后再派 Reviewer。task 工具的同步返回特性天然适合这种先依赖后执行的编排方式。2. 配模型通道把 Claude Code 的 Base URL 指到 TaoToken2.1 创建 API Key 时先分清官网和接口地址在开始改代码之前先把模型通道配好。官网落地页 TaoToken 负责注册、创建 API Key、查看模型广场和用量统计而填进代码和工具的 Base URL 是 https://taotoken.net/api末尾不要加 /v1。这两个地址容易搞混。落地页是给人操作的接口地址是给程序请求的。不少人习惯性在接口地址后面补一个 /v1结果请求路径变成 /api/v1/messages多了一层路由直接连不上。记法很简单页面地址只管注册和看数据程序地址只填 https://taotoken.net/api不带任何额外路径。2.2 settings.json 与环境变量两种配置方式如果你用 Claude Code 做执行工具直接在 ~/.claude/settings.json 里写入{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_AUTH_TOKEN: YOUR_API_KEY, ANTHROPIC_MODEL: 以 TaoToken 模型广场显示的模型 ID 为准 } }其中 ANTHROPIC_AUTH_TOKEN 就是你在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 创建的 Key。ANTHROPIC_MODEL 不要凭印象猜一个模型名也不要使用带个人主观推断出日期后缀的 ID打开模型广场复制当前可用的模型 ID 填进去。不用 Claude Code 的话环境变量是同样的三个名字export ANTHROPIC_BASE_URLhttps://taotoken.net/api export ANTHROPIC_AUTH_TOKENYOUR_API_KEY export ANTHROPIC_MODEL从模型广场复制的模型 ID配好之后Python 代码里的 Anthropic 客户端会自动读取这三个环境变量。你不需要在代码里硬编码地址和 Key也避免把密钥提交进 Git 仓库。3. 给主 Agent 加 task 工具SubAgent 才能被调起3.1 TOOLS 里新增 task 声明原文的第一步是给主 Agent 增加一个 task 工具。这个工具的作用是让主 Agent 在推理过程中“感知”到它可以召唤 SubAgent。工具声明只需要一个字符串参数 description也就是任务描述TOOLS [ {name: bash, type: function, description: 执行 shell 命令}, {name: read_file, type: function, description: 读取文件内容}, {name: write_file, type: function, description: 写入文件}, {name: edit_file, type: function, description: 编辑文件}, {name: glob, type: function, description: 按模式查找文件}, {name: todo_write, type: function, description: 记录待办事项}, { name: task, type: function, description: 启动一个 SubAgent 处理复杂子任务只返回最终结论。, input_schema: { type: object, properties: { description: {type: string} }, required: [description] } }, ] TOOL_HANDLERS[task] spawn_subagent主 Agent 在规划阶段如果判断某个子任务适合独立处理就会调用 task 工具把任务描述作为参数传进去。tool_use 块被触发后TOOL_HANDLERS 里的 spawn_subagent 被执行主 Agent 自己的推理循环暂停等待 SubAgent 返回结论。3.2 spawn_subagent 的独立 messages[] 与 30 轮上限第二步是真正实现 spawn_subagent。这里的关键是创建一个全新的 AgentLoop它的 messages[] 与主 Agent 完全隔离工具列表里也没有 task 工具——防止 SubAgent 再召唤 SubAgent 造成无限递归。def spawn_subagent(description: str) - str: # SubAgent 只用基础工具没有 task避免递归套娃 sub_tools [ {name: bash, type: function, description: 执行 shell 命令}, {name: read_file, type: function, description: 读取文件内容}, {name: write_file, type: function, description: 写入文件}, {name: edit_file, type: function, description: 编辑文件}, {name: glob, type: function, description: 按模式查找文件}, ] # 全新的消息列表和主 Agent 的上下文完全隔离 messages [{role: user, content: description}] for _ in range(30): response client.messages.create( modelMODEL, system你是一个专注完成单个子任务的助手。, messagesmessages, toolssub_tools, max_tokens8000, ) messages.append({ role: assistant, content: response.content }) # 模型不再调用工具说明任务已完成 if response.stop_reason ! tool_use: break # 逐个执行工具调用把结果追加回 SubAgent 的消息列表 results [] for block in response.content: if block.type tool_use: handler SUB_HANDLERS.get(block.name) output ( handler(**block.input) if handler else fUnknown tool: {block.name} ) results.append({ type: tool_result, tool_use_id: block.id, content: str(output), }) messages.append({role: user, content: results}) # 只返回最后的文本结论中间过程全部丢弃 return extract_text(messages[-1][content])这段代码里 client 就是上一节配好的 Anthropic 客户端MODEL 读取自 ANTHROPIC_MODEL 环境变量。SubAgent 进入自己的推理-工具循环后最多允许 30 轮迭代一旦 stop_reason 不再是 tool_use就说明模型认为任务已经完成循环结束。每次循环的 messages.append 都在构建 SubAgent 自己的上下文轨迹。这些轨迹不会回流到主 Agent因为函数最后一步 extract_text 只取最后一条消息里的文本内容返回。中间那些 shell 输出、文件读取、测试日志都会随着函数返回被垃圾回收。4. extract_text从几十轮工具调用里只留一句结论4.1 上下文隔离之外的压缩收益extract_text 是整套机制里最重要的一环。一个 SubAgent 执行订单模块任务时可能产生 20 到 30 条消息包含代码 diff、编译报错、review 意见、多次修改后的最终文件内容。如果把这些全部返回给主 Agent上下文隔离就失去了意义。extract_text 做的是把最后一条 assistant 消息中的文本块拼接起来通常就是模型总结出的一段话“代码已生成包含 OrderService 和 OrderController 两个类覆盖创建、查询、取消三个接口。”主 Agent 拿到的永远是这种压缩后的结论而不是 SubAgent 的原始工作记录。这个压缩收益是叠加式的。每个 SubAgent 独立执行自己的子任务各自产生几十条中间消息但最终只向主 Agent 交回几行文本。主 Agent 的上下文增长速率从“跟子任务复杂度成正比”降为“跟子任务数量近似线性但系数极小”这正是 SubAgent 能支撑复杂多步任务的根本原因。4.2 工具调用与 SubAgent 的本质区别有人会问主 Agent 自己就能调用 bash、read_file 这些工具为什么还要多包一层 SubAgent区别在于工具调用是单次函数SubAgent 是完整的推理循环。主 Agent 调 bash 执行一条命令拿到输出结束。这是一次性的动作不需要规划、反思、迭代。SubAgent 不同它接收一个目标描述进入自己的 AgentLoop思考下一步做什么、调用工具、观察结果、再思考、再调用直到产出最终答案。这个循环可能包含几十轮工具调用每一轮都在 SubAgent 自己的上下文里完成。打个比方工具调用是让同事帮你查个文件一分钟搞定SubAgent 是让同事独立负责一个项目他要自己规划步骤、执行、验证最后交一份报告给你。报告背后的过程你看不见也不需要看见。5. 复现时最常见的两个报错401 和模型 ID 找不到5.1 401Key 没在官网创建或环境变量没生效如果你在跑原文示例时遇到 401 Unauthorized先检查 ANTHROPIC_AUTH_TOKEN 是不是真的设置了值是不是从 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 创建出来的完整 Key。很多人把 settings.json 写好后忘记重启终端或重新加载环境变量导致进程里还是旧的配置。再检查一遍有没有把 Key 直接硬编码进代码。硬编码容易出两种问题一是字符串里多了空格或换行二是把官网登录密码当成了 API Key。API Key 在官网的创建入口生成生成后只显示一次注意复制完整。5.2 模型 ID 报错别猜名字以模型广场为准第二类常见报错是模型不存在比如“model not found”或者 404。这类问题通常是 ANTHROPIC_MODEL 填了一个自己想当然的 ID。模型 ID 不是靠记忆猜的模型广场上挂什么就填什么不要使用凭印象推断、包含疑似日期后缀的名字。打开 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 的模型广场找到你需要的模型把它的 ID 原样复制到 ANTHROPIC_MODEL。配好后可以先用一个最小调用验证response client.messages.create( modelMODEL, max_tokens100, messages[{role: user, content: 回复 OK}], ) print(response.content)能正常打印文本说明通道已经通了再回去跑 SubAgent 的完整示例。6. 完整跑一遍订单模块 Demo回控制台核对用量6.1 主 Agent 编排串行依赖的场景把上面的代码拼起来就是一个可运行的 SubAgent 示例。主 Agent 收到“帮我实现一个订单模块”的需求后先调用 task 工具派 CodeWriterAgent拿到代码结论后再调用 task 派 CodeReviewerAgentreview 通过后派 TestAgent 写单元测试。三次 task 调用串行执行每次都会创建一个独立上下文的 SubAgent。你可以把这段流程放在本地项目里跑一次观察主 Agent 的上下文中是否只保留了每个 SubAgent 返回的结论文本而不是它们各自几十条工具调用记录。跑完以后如果想知道这次编排实际消耗了多少模型请求回到官网控制台去看用量明细主 Agent 和 SubAgent 的每次 messages.create 都会记录在案。6.2 用量明细能看出编排质量控制台里能对应用量确认一件事一次订单模块开发到底产生了多少次模型请求其中多少次是主 Agent 的规划调度多少次是 SubAgent 的子任务推理。如果 SubAgent 一轮任务消耗了 30 次请求说明这个子任务复杂度较高或者工具调用陷入反复试错可以根据这个数据回头优化 prompt 或拆分粒度。到这里原文的 task 工具调 SubAgent 的完整示例就算真正落地了。你也可以把这套方式延伸到自己的项目里把代码审查、测试生成、文档撰写分别拆成独立 SubAgent用 TaoToken 作为统一模型通道保持主 Agent 上下文长期干净。