news 2026/10/15 20:35:19

DeepAgents 实战:SubAgentMiddleware 驱动 Sub-Agent 协同演化的 Harness 拆解与 TaoToken 接入

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
DeepAgents 实战:SubAgentMiddleware 驱动 Sub-Agent 协同演化的 Harness 拆解与 TaoToken 接入

1. 从一次差旅编排说起:SubAgentMiddleware 到底解决什么问题

如果你用 LangChain 写过稍微复杂一点的 Agent,大概率遇到过这种场景:主 Agent 一边要查日历、一边要订机票、一边还要订酒店,工具调用日志刷满屏幕,上下文窗口被中间结果塞爆,最后模型开始"胡言乱语"。这不是模型不行,而是上下文被污染了。

DeepAgents 给出的解法是 SubAgentMiddleware:它把"细节工作"隔离到独立的 Sub-Agent 里执行,主 Agent 只接收最终结果。你可以把它理解成一个项目经理——项目经理不需要亲自去查每个航班的舱位,他只需要把任务派给"出行专员"和"住宿专员",等他们交回一份简报即可。

SubAgentMiddleware 是 LangChain DeepAgents 框架里的一个中间件,它能做什么?简单说三件事:注册多个 Sub-Agent、自动生成一个名为task的工具、在模型调用前后注入系统提示词。适合谁?适合正在做多 Agent 协同、想让主 Agent 保持"高层协调"视角、又不想被工具调用细节淹没的开发者。

我试过把差旅助手拆成三个角色后,主 Agent 的上下文长度从 8000+ token 降到 2000 以内,响应速度肉眼可见地变快。这篇就带你从 Harness 视角拆解 SubAgentMiddleware 的调度链路,给出可复制的配置片段,并把 endpoint 切到 TaoToken 统一通道做连通性验证。

2. TaoToken 前置准备:统一 Key 与 API 通道

在动手写 SubAgentMiddleware 之前,先把模型通道理顺。DeepAgents 里每个 Sub-Agent 都可以指定自己的 model,如果每个都去配不同的 Key,维护成本会很高。TaoToken 提供统一的 API 通道,一个 Key 就能覆盖多个模型,特别适合这种"主 Agent + 多个 Sub-Agent"的架构。

你需要准备三样东西:Base URL、API Key、Model ID。Base URL 用https://taotoken.net/api,API Key 到控制台创建,Model ID 按你实际要用的模型填。这三件套在后面的配置里会反复出现,建议先记下来。

创建 Key 的入口在控制台的 API Keys 页面,创建后复制保存,页面关闭后就看不到了。如果你用的是 Claude Code 这类工具,TaoToken 也提供了对应的接入文档,配置方式类似,都是把 Base URL 指向统一通道。

这里有个容易踩的坑:很多人以为 Sub-Agent 必须用和主 Agent 一样的模型。其实不是。SubAgentMiddleware 允许每个 Sub-Agent 单独指定 model,你可以让主 Agent 用推理能力强的模型做协调,让 Sub-Agent 用更经济的模型做执行。TaoToken 的统一通道正好支持这种混搭,不用为每个模型单独申请 Key。

配置环境变量时,建议用.env文件管理,避免把 Key 硬编码进代码。下面是一个最小化的.env示例:

TAOTOKEN_API_KEY=sk-你的key TAOTOKEN_BASE_URL=https://taotoken.net/api

然后在代码里通过load_dotenv()加载。这样做的另一个好处是,切换环境时只改.env,不用动业务代码。

3. 可复制配置:SubAgentMiddleware 注册与协同触发

现在进入核心部分。SubAgentMiddleware 的配置分两步:先定义 Sub-Agent,再把它们注册进中间件。Sub-Agent 有两种形式,CompiledSubAgent和SubAgent,前者是已经编译好的 Runnable,后者是配置字典,运行时才组装。

先看完整的配置片段。这段代码可以直接复制运行,注意把 model 换成你 TaoToken 通道里可用的 Model ID:

from typing import TypedDict from langchain.agents import create_agent from langchain.tools import tool from deepagents.middleware import CompiledSubAgent, SubAgentMiddleware, SubAgent from langchain_openai import ChatOpenAI from deepagents.backends import FilesystemBackend from dotenv import load_dotenv from datetime import datetime, date load_dotenv() class Event(TypedDict): title: str description: str start_time: datetime end_time: datetime @tool def book_hotel(city: str, check_in_date: str, check_out_date: str): """酒店预订工具,提供城市、入住日期和退房日期,返回预订信息""" return f"Hotel booked in {city} from {check_in_date} to {check_out_date}" @tool def buy_air_ticket(origin: str, destination: str, departure_date: str): """机票购买工具,提供出发地、目的地和出发日期,返回购买信息""" return f"Flight ticket bought from {origin} to {destination} on {departure_date}" @tool def check_calendar(date: date) -> list[Event]: """日程查询工具,提供日期,返回该日期的日程信息""" if date == datetime.now().date(): return [ Event( title="团队会议", description="与团队成员讨论项目进展", start_time=datetime.strptime(f"{date} 10:00", "%Y-%m-%d %H:%M"), end_time=datetime.strptime(f"{date} 11:00", "%Y-%m-%d %H:%M"), ), ] return [] transport_agent = create_agent( model=ChatOpenAI( model="gpt-5.2-chat", base_url="https://taotoken.net/api", api_key="sk-你的key", ), tools=[buy_air_ticket], system_prompt=( "你是一个专注于出行安排的个人助手,可以帮助用户调用`buy_air_ticket`工具购买机票。" "达成购买机票的目标是首要任务,你可以自行决定航司、航班和舱位等级。" "将购买信息以清晰的格式返回给用户。" ), ) transport_agent = CompiledSubAgent( name="transport_agent", description="一个专注于出行安排的个人助手,负责处理与机票购买相关的安排。", runnable=transport_agent, ) accommodation_agent: SubAgent = { "name": "accommodation_agent", "description": "一个专注于住宿安排的个人助手,负责处理与酒店预订相关的安排。", "system_prompt": ( "你是一个专注于住宿安排的个人助手,可以帮助用户调用`book_hotel`工具预订酒店。" "达成预订酒店的目标是首要任务,你可以自行决定酒店、房型和价格等级。" "将预订信息以清晰的格式返回给用户。" ), "model": ChatOpenAI( model="gpt-5.2-chat", base_url="https://taotoken.net/api", api_key="sk-你的key", ), "tools": [book_hotel], } middleware = SubAgentMiddleware( backend=FilesystemBackend(root_dir="./agent_data", virtual_mode=True), subagents=[transport_agent, accommodation_agent], ) agent = create_agent( model=ChatOpenAI( model="gpt-5.2-chat", base_url="https://taotoken.net/api", api_key="sk-你的key", ), tools=[check_calendar], system_prompt=( "你是一个差旅助手,可以帮助用户安排差旅计划。" "你可以调用`check_calendar`工具查询用户的日程安排,以便更好地规划差旅计划。" "如果没有日程安排冲突,你可以直接调用注册工具购买机票和预订酒店。" ), middleware=[middleware], )

这段配置里有几个关键点值得展开。第一,backend参数强烈建议指定。如果不指定,中间件会走旧版 API,必须用default_model指定默认模型,而且 Sub-Agent 之间无法共享文件系统。指定FilesystemBackend后,主 Agent 和 Sub-Agent 在同一个文件平面上工作,Sub-Agent 写出的报告主 Agent 能直接读到。

第二,CompiledSubAgent和SubAgent的区别。前者适合你已经用create_agent编译好的 Agent,直接包一层就能注册;后者适合用字典描述配置,运行时才组装。两种可以混用,上面的例子就是混用的。

第三,每个 Sub-Agent 的description字段很重要。SubAgentMiddleware 会把所有 Sub-Agent 的名称和描述拼进task工具的描述里,主 Agent 靠这段描述来决定派哪个 Sub-Agent。描述写得越清楚,调度越准确。

如果你用 Cline 或 Claude Code 这类工具做开发,配置逻辑是一样的,都是 Base URL + Key + Model ID 三件套。Cline 的 MCP 配置里也是把 endpoint 指向统一通道,然后按需切换模型。

4. 验证请求:从 task 工具到协同触发

配置写完后,怎么确认 SubAgentMiddleware 真的生效了?最直接的办法是打印它生成的task工具,看看描述里有没有你的 Sub-Agent 列表。

tools = middleware.tools for tool in tools: print(f"name: {tool.name}") print(f"response_format: {tool.response_format}") print("args:") for k, v in tool.args.items(): print(f" {k}: type: {v['type']}, title: {v['title']}") print("description:\n", tool.description)

运行后会看到工具名为task,response_format为content,参数有两个:description(任务描述)和subagent_type(Sub-Agent 类型)。描述里会列出所有可用的 Sub-Agent,比如transport_agent和accommodation_agent。

接下来跑一次真实的协同触发:

result = agent.invoke({ "messages": [{ "role": "user", "content": "明天两天有一个从上海到北京的差旅计划,请帮购买往返机票(明天出发,后天返回)和预订酒店(入住明天,退房后天)。" }] }) print(result["messages"][-1].content)

如果一切正常,主 Agent 会先调用check_calendar查日程,确认没有冲突后,并行启动两个task调用,分别派给transport_agent和accommodation_agent。两个 Sub-Agent 各自执行工具调用,返回结果,主 Agent 汇总后输出一份完整的差旅行程。

这里有个细节:Sub-Agent 的执行过程对主 Agent 是不可见的。主 Agent 只看到task工具返回的最终结果,中间的工具调用日志不会进入主 Agent 的上下文。这正是 SubAgentMiddleware 的核心价值——上下文隔离。

验证连通性时,如果模型通道有问题,通常会在这一步暴露。比如 Key 无效会报 401,Base URL 写错会报连接失败。确认 TaoToken 通道正常后,整个链路就通了。

5. 常见报错排查:401、local proxy failed 与 OAuth

实际跑的时候,报错基本集中在几个地方。下面按真实报错对照排查。

401 Unauthorized:最常见。检查.env里的TAOTOKEN_API_KEY是否正确,有没有多余空格。如果用的是ChatOpenAI,确认api_key参数传对了。还有一种情况是 Key 过期或被删除,到控制台重新创建一个即可。

local proxy failed / connection refused:通常是 Base URL 写错。确认是https://taotoken.net/api,不要漏掉/api后缀,也不要写成http。如果你在容器里跑,检查网络是否能访问外网。

reading 'choices' of undefined:这个报错说明请求发出去了,但返回结构不对。多半是 Model ID 填错了,或者该模型不在你的通道权限内。到模型对话页面确认一下可用的 Model ID,换成能用的再试。

OAuth 相关报错:如果你用 Claude Code 接入,可能会遇到 OAuth 流程问题。这种情况建议直接用 API Key 方式接入,配置更简单。Claude Code 的接入文档里有详细步骤,核心还是 Base URL + Key + Model ID。

Sub-Agent 没有被调用:如果主 Agent 一直自己干活,不派task,检查 Sub-Agent 的description是否足够清晰。描述太模糊,主 Agent 不知道什么时候该派。另外,如果任务本身很简单,主 Agent 可能判断不需要 Sub-Agent,这是正常行为。

文件写入后主 Agent 读不到:确认backend参数指定了FilesystemBackend,并且root_dir路径存在。如果没指定 backend,Sub-Agent 用的是私有 StateBackend,写出的文件主 Agent 看不到。

排查时建议先单独测模型通道,确认ChatOpenAI能正常返回,再测 SubAgentMiddleware。分层排查比一上来就查中间件效率高得多。

6. 把通道固定下来:长期编码与 Agent 协同的接入建议

跑通一次不难,难的是长期稳定。我的建议是把模型通道固定成一套配置,主 Agent 和 Sub-Agent 都走同一个 Base URL,Key 统一管理。这样切换模型时只改 Model ID,不用动其他代码。

如果你要做长期的编码类 Agent,或者多 Agent 协同的项目,可以考虑用 Coding Plan 来管理额度,避免频繁切换 Key。模型对话页面适合做单次验证,确认某个 Model ID 是否可用。接入文档里有各种工具的配置示例,遇到不确定的配置项可以先查文档。

最后留一个实用技巧:Sub-Agent 的description建议写成"什么时候用我"而不是"我是什么"。比如"当需要购买机票时使用"比"机票助手"更能触发主 Agent 的调度。这个细节在多 Sub-Agent 场景下尤其明显,描述写得好,调度准确率能提升不少。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 20:32:48

MFC动态实时曲线绘制:双缓冲与环形缓冲区实战

简介:这份资源面向具备一定VC基础的Windows开发者与数据可视化学习者,聚焦MFC框架下动态实时曲线的绘制实现。内容围绕自定义视图类、GDI绘图、数据库交互与多线程同步等核心知识点展开,可用于监控系统、数据采集界面等需要实时刷新图表的场景…

作者头像 李华
网站建设 2026/10/15 20:31:48

双目立体匹配核心:区域匹配算法原理与工程实践

双目立体匹配这个话题,我在实际项目里断断续续折腾了好几年。如果让我给刚接触三维视觉的人推荐一个最值得先吃透的算法,我大概率会推荐区域匹配(Area-based Matching)——它是局部匹配算法里最直观、最容易编码、也是整条计算链路…

作者头像 李华
网站建设 2026/10/15 20:29:24

Rust RFC 0879 解读:二进制与八进制数字字面量的词法分析重构

文档 【免费下载链接】rfcs RFCs for changes to Rust 项目地址: https://gitcode.com/gh_mirrors/rf/rfcs 点击查看 免费下载 导读 本文围绕 RFC 仓库中的 text/0879-small-base-lexing.md(RFC 879)展开,深入讲解 Rust 编译器词…

作者头像 李华
网站建设 2026/10/15 20:25:22

JMeter 5.6.2压力测试实战:从线程组到报告解读

简介:Apache JMeter 5.6.2 是由 Apache 组织维护的开源纯 Java 压力测试工具,具备跨平台特性,可运行于 Windows、Linux、macOS。其用途在于模拟大量用户并发访问,评估 Web 应用、数据库、FTP 服务器等静态与动态资源的高并发稳定性…

作者头像 李华