task 工具跑 SubAgent 任务:Key 用 TaoToken
2026/9/14 7:21:29 网站建设 项目流程

当 Agent 要动手实现一个订单模块时,它需要读需求文档、看现有代码结构、写业务实现、自己 review 一遍,再跑测试。这一路下来的代码 diff、测试输出和 review 意见全堆在同一条 messages[] 里,几轮之后模型开始“失忆”,连前面刚确认的设计决策都记不住。这种上下文污染正是 SubAgent 编排要解决的痛处,而完整跑通这套 task 工具调起 SubAgent 的流程,需要一条真实可用的模型 API。TaoToken 提供了现成的兼容通道:打开 https://taotoken.net/?utm_source=taotoken_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 指到 TaoToken

2.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_source=taotoken_aicg_blog_end 创建的 Key。ANTHROPIC_MODEL 不要凭印象猜一个模型名,也不要使用带个人主观推断出日期后缀的 ID,打开模型广场复制当前可用的模型 ID 填进去。

不用 Claude Code 的话,环境变量是同样的三个名字:

export ANTHROPIC_BASE_URL="https://taotoken.net/api" export ANTHROPIC_AUTH_TOKEN="YOUR_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( model=MODEL, system="你是一个专注完成单个子任务的助手。", messages=messages, tools=sub_tools, max_tokens=8000, ) 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 f"Unknown 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 401:Key 没在官网创建或环境变量没生效

如果你在跑原文示例时遇到 401 Unauthorized,先检查 ANTHROPIC_AUTH_TOKEN 是不是真的设置了,值是不是从 https://taotoken.net/?utm_source=taotoken_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_source=taotoken_aicg_blog_end 的模型广场,找到你需要的模型,把它的 ID 原样复制到 ANTHROPIC_MODEL。配好后可以先用一个最小调用验证:

response = client.messages.create( model=MODEL, max_tokens=100, messages=[{"role": "user", "content": "回复 OK"}], ) print(response.content)

能正常打印文本,说明通道已经通了,再回去跑 SubAgent 的完整示例。

6. 完整跑一遍订单模块 Demo,回控制台核对用量

6.1 主 Agent 编排串行依赖的场景

把上面的代码拼起来,就是一个可运行的 SubAgent 示例。主 Agent 收到“帮我实现一个订单模块”的需求后,先调用 task 工具派 CodeWriterAgent;拿到代码结论后,再调用 task 派 CodeReviewerAgent;review 通过后,派 TestAgent 写单元测试。三次 task 调用串行执行,每次都会创建一个独立上下文的 SubAgent。

你可以把这段流程放在本地项目里跑一次,观察主 Agent 的上下文中是否只保留了每个 SubAgent 返回的结论文本,而不是它们各自几十条工具调用记录。跑完以后,如果想知道这次编排实际消耗了多少模型请求,回到官网控制台去看用量明细,主 Agent 和 SubAgent 的每次 messages.create 都会记录在案。

6.2 用量明细能看出编排质量

控制台里能对应用量确认一件事:一次订单模块开发,到底产生了多少次模型请求,其中多少次是主 Agent 的规划调度,多少次是 SubAgent 的子任务推理。如果 SubAgent 一轮任务消耗了 30 次请求,说明这个子任务复杂度较高,或者工具调用陷入反复试错,可以根据这个数据回头优化 prompt 或拆分粒度。

到这里,原文的 task 工具调 SubAgent 的完整示例就算真正落地了。你也可以把这套方式延伸到自己的项目里,把代码审查、测试生成、文档撰写分别拆成独立 SubAgent,用 TaoToken 作为统一模型通道,保持主 Agent 上下文长期干净。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询