☰
SubAgent 基础:用 TaoToken 统一 Key 给子代理接上自主工具
2026/10/11 8:30:10 网站建设 项目流程

1. 从一次多步任务失败说起:SubAgent 与 McpAgentExecutor 的协作场景

如果你在 j-langchain 里写过带工具的 Agent,大概率遇到过这种尴尬:主 Agent 手里挂了七八个工具,天气、机票、酒店、汇率、翻译全塞在一起,模型每次选工具都像在抽奖。我试过把「查上海到成都的天气和机票」这种复合任务丢给单层 Agent,结果它经常只调一个get_weather就草草收尾,剩下的机票信息直接编造。问题不在模型笨,而在于工具边界太模糊——主 Agent 既要负责路由,又要负责执行,还要负责整合,上下文一长就顾此失彼。

SubAgent(子代理)就是为解决这类问题设计的。它和 Skill 最大的区别在于:Skill 只能从父 Agent 借用工具,自己没有工具所有权;而 SubAgent 拥有自己的工具集,可以独立配置 LLM,对主 Agent 暴露为一个普通 Tool。换句话说,Skill 是「从父 Agent 借工具干活」,SubAgent 是「带着自己的工具干活」。当子任务需要专属工具(比如内部 API 客户端、私有数据库连接),或者多个子任务工具互不交叉时,SubAgent 的隔离性优势就体现出来了。

本文聚焦 j-langchain 中 SubAgent 与 McpAgentExecutor 的协作方式,演示如何让子代理自主选择并调用工具完成多步任务。我会给出可复制的子代理注册与工具绑定配置,并用一次多工具串联调用验证自主决策链路是否生效。适合已经掌握 Skill 用法、希望构建拥有自有工具的独立子代理的 Java 开发者。核心检索词:SubAgent 子代理、自主工具、McpAgentExecutor、j-langchain 多步任务。

整个协作链路是这样的:主 Agent 由 McpAgentExecutor 驱动,它持有一个 master LLM 和若干 Tool;其中travel_researcher这个 Tool 实际上是一个 SubAgent 通过asTool()注册进来的。主 Agent 只看到一次travel_researcher调用,而 SubAgent 内部会自主完成天气、机票、酒店的多轮工具调用,最后把整合结果返回给主 Agent。这种「主 Agent 路由、子 Agent 执行」的分层结构,正是自主决策链路的核心。

2. 前置准备:用 TaoToken 统一 Key 打通 j-langchain 的模型调用

在写 SubAgent 配置之前,先把模型调用的 Key 统一掉。j-langchain 支持多种 LLM 后端,示例里常用ChatAliyun接 qwen-plus,但如果你同时要跑多个子代理、每个子代理可能用不同模型,逐个管理各家平台的 Key 会很痛苦。TaoToken 的价值就在这里:它提供统一的 API 入口,一个 Key 就能调用多种模型,Base URL 固定为https://taotoken.net/api,兼容 OpenAI 风格的请求格式。

你需要先拿到 Key。访问https://taotoken.net/api-keys(带 utm 参数:?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite)创建 API Key,然后在环境变量里配置。j-langchain 的ChatAliyun或通用 OpenAI 兼容客户端都可以指向这个 Base URL。我实测下来,把 Base URL 和 Key 统一后,主 Agent 和 SubAgent 切换模型只需要改一个 model 字符串,不用再动 Key 管理逻辑。

具体来说,你需要准备三件套:Base URL、API Key、Model ID。Base URL 用https://taotoken.net/api,API Key 从控制台复制,Model ID 按你实际要用的模型填(比如qwen-plus、gpt-4o-mini等)。这三件套在后面的SubAgentConfig和McpAgentExecutor里都会用到。如果你还没决定用哪个模型,可以先到模型对话页面(https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite)试一下不同模型的工具调用表现,再回来写配置。

环境变量建议这样设置,避免把 Key 硬编码进代码:

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

然后在 Java 代码里读取。j-langchain 的ChatAliyun.builder()支持自定义 baseUrl 和 apiKey,你可以这样接:

var llm = ChatAliyun.builder() .baseUrl(System.getenv("TAOTOKEN_BASE_URL")) .apiKey(System.getenv("TAOTOKEN_API_KEY")) .model("qwen-plus") .temperature(0f) .build();

这样主 Agent 和 SubAgent 共用同一个 LLM 实例或各自构建,Key 都从环境变量走。注意temperature(0f)在工具调用场景下很重要,能减少模型随机发挥导致的工具选择漂移。前置准备做完,就可以进入 SubAgent 的注册与工具绑定了。

3. 可复制配置:SubAgent 注册、工具绑定与 McpAgentExecutor 挂载

这一节给出完整的可复制配置。SubAgent 的配置来源有三种:classpath 下的AGENT.md、数据库读取、代码直接构造SubAgentConfig。三种来源对SubAgent.from()完全透明,你可以按场景选。生产环境推荐AGENT.md,因为配置文件可独立维护;测试或动态生成场景推荐代码构造。

先看AGENT.md的写法。它和SKILL.md格式相同,都是带 YAML 前言的 Markdown,放在src/test/resources/agents/travel-researcher/AGENT.md:

--- name: travel_researcher description: 旅行信息研究专家。负责综合查询目的地天气、机票、酒店信息,输出完整旅行建议。当用户需要获取旅行目的地综合信息时使用。 skills: - skills/travel-planner max-iterations: 15 --- 你是旅行信息研究专家,拥有专业的旅行规划知识。 你的职责是: 1. 理解用户的旅行需求,提取目的地城市 2. 调用可用工具查询天气、机票、酒店信息 3. 结合掌握的旅行规划知识,综合输出完整的旅行建议

前言字段说明:name是 SubAgent 标识符,也是注册到主 Agent 后的 Tool 名称;description是主 LLM 看到的 Tool 描述,直接决定路由准确度;skills是知识注入,不是工具调用,Skill 的 systemPrompt 和 references 会拼接到 SubAgent 的 system prompt 里;max-iterations是内部执行器最大迭代次数,防止死循环。

然后是工具绑定和 SubAgent 构建。假设你有一个TravelTools类,里面有三个方法:

public class TravelTools { public String getWeather(String city) { return city + ":晴,28~33°C,紫外线强"; } public String getFlightPrice(String city) { return "上海→" + city + ":¥1600,含15kg行李"; } public String getHotelPrice(String city) { return city + ":三星¥480/晚,四星¥950/晚"; } }

用buildTool把方法包装成工具,再绑定到 SubAgent:

TravelTools tools = new TravelTools(); SubAgentConfig config = ClasspathSubAgentConfigLoader.fromClasspath("agents/travel-researcher"); SubAgent researcher = SubAgent.from(config, chainActor) .llm(ChatAliyun.builder() .baseUrl(System.getenv("TAOTOKEN_BASE_URL")) .apiKey(System.getenv("TAOTOKEN_API_KEY")) .model("qwen-plus") .temperature(0f) .build()) .tools( buildTool("get_weather", "查询城市天气", "city: String", tools::getWeather), buildTool("get_flight_price", "查询机票价格", "city: String", tools::getFlightPrice), buildTool("get_hotel_price", "查询酒店均价", "city: String", tools::getHotelPrice) ) .onToolCall(tc -> System.out.println("[ToolCall] " + tc)) .onObservation(obs -> System.out.println("[Observation] " + obs)) .build();

关键点:SubAgent.from(config, chainActor)里的chainActor是执行器上下文,负责调度工具调用;.tools()注册的是 SubAgent 自有工具,这些工具不会暴露给主 Agent;.onToolCall()和.onObservation()是回调,用于观测内部决策链路。

接下来把 SubAgent 挂到主 Agent 上。主 Agent 用McpAgentExecutor构建,通过.subAgent(researcher)把子代理注册为普通 Tool:

McpAgentExecutor master = McpAgentExecutor.builder(chainActor) .llm(ChatAliyun.builder() .baseUrl(System.getenv("TAOTOKEN_BASE_URL")) .apiKey(System.getenv("TAOTOKEN_API_KEY")) .model("qwen-plus") .temperature(0f) .build()) .subAgent(researcher) .systemPrompt("你是旅行总助手,遇到旅行信息查询任务请使用 travel_researcher。") .onToolCall(tc -> System.out.println("[master] ToolCall: " + tc)) .onObservation(obs -> System.out.println("[master] Observation: " + obs)) .build();

如果你不想用AGENT.md,可以直接用代码构造SubAgentConfig,适合测试或动态生成:

SubAgentConfig config = SubAgentConfig.builder() .name("weather_flight_agent") .description("查询目的地天气和机票信息的专属 Agent") .systemPrompt(""" 你是出行信息专家。 收到城市名后,依次调用 get_weather 和 get_flight_price, 最后整合结果输出简洁的出行参考。 """) .build();

三种配置来源对SubAgent.from()完全透明,你可以混用。生产环境用AGENT.md便于维护,测试用代码构造便于快速迭代。配置写完后,下一步就是验证自主决策链路是否真的生效。

4. 验证请求:一次多工具串联调用看自主决策链路

配置写完不代表链路通了,必须用一次多工具串联调用验证。我准备了一个复合请求:「我想从上海出发去成都和西安,帮我查一下旅行信息」。这个请求需要 SubAgent 自主决定先查哪个城市、调哪些工具、调几次,最后整合两个城市的结果。

先看 SubAgent 独立运行的验证。不挂主 Agent,直接调用researcher.invoke():

String result = researcher.invoke("我想去三亚旅游,出发地上海"); System.out.println(result);

预期输出里能看到三次工具调用和三次观测:

[ToolCall] get_weather {"city":"三亚"} [Observation] 三亚:晴,28~33°C,紫外线强 [ToolCall] get_flight_price {"city":"三亚"} [Observation] 上海→三亚:¥1600,含15kg行李 [ToolCall] get_hotel_price {"city":"三亚"} [Observation] 三亚:三星¥480/晚,四星¥950/晚 ========== SubAgent 独立运行结果 ========== **三亚旅游建议** 天气:晴朗,28~33°C,紫外线强烈,建议携带防晒用品 机票:上海→三亚 ¥1600(含15kg行李) 住宿(3晚预算): - 三星酒店:¥480/晚 × 3晚 = ¥1440 - 四星酒店:¥950/晚 × 3晚 = ¥2850 综合预算:¥3040(三星)~ ¥4450(四星)

这里的关键是:SubAgent 没有等主 Agent 告诉它调哪个工具,而是根据 system prompt 和工具描述自主决定调用顺序。get_weather、get_flight_price、get_hotel_price三次调用是模型自己规划的,这就是自主决策链路生效的直接证据。

再看挂到主 Agent 后的验证。调用master.invoke():

String result = master.invoke("我想从上海出发去成都和西安,帮我查一下旅行信息").getText(); System.out.println(result);

主 Agent 视角的日志:

[master] ToolCall: travel_researcher {"input":"我想从上海出发去成都和西安,帮我查一下旅行信息"} [subagent:travel_researcher] ToolCall: get_weather {"city":"成都"} [subagent:travel_researcher] Observation: 成都:多云,18~26°C,下午有小雨 [subagent:travel_researcher] ToolCall: get_flight_price {"city":"成都"} [subagent:travel_researcher] Observation: 上海→成都:¥1200,含20kg行李 [subagent:travel_researcher] ToolCall: get_weather {"city":"西安"} [subagent:travel_researcher] Observation: 西安:晴,15~24°C,昼夜温差大 [subagent:travel_researcher] ToolCall: get_flight_price {"city":"西安"} [subagent:travel_researcher] Observation: 上海→西安:¥980,含20kg行李 [master] Observation: (SubAgent 整合后的旅行报告)

注意主 Agent 只看到一次travel_researcher工具调用,SubAgent 内部的六次工具调用对主 Agent 完全透明。这种透明性带来两个好处:主 Agent 的上下文不会被内部细节撑爆;SubAgent 可以独立调整工具和模型,不影响主 Agent 的路由逻辑。验证时重点看三件事:主 Agent 是否只调一次子代理、子代理是否自主完成多轮工具调用、最终结果是否整合了两个城市的信息。三件都满足,说明自主决策链路生效。

5. 本篇常见错排查:401、local proxy failed、reading choices 与 OAuth

配置和验证过程中,最容易卡在几个典型报错上。这一节按真实报错逐个排查。

401 Unauthorized:最常见的原因是 API Key 没读到或 Base URL 写错。检查System.getenv("TAOTOKEN_API_KEY")是否返回 null,如果是在 IDE 里跑,环境变量可能没注入,建议在运行配置里显式设置。Base URL 必须是https://taotoken.net/api,不要漏掉/api路径,也不要带末尾斜杠。如果 Key 是从控制台复制的,注意不要带多余空格。三件套(Base URL + Key + Model ID)任何一个错都会导致 401 或 404。

local proxy failed:这个报错通常出现在网络层,说明请求没到达 TaoToken 的 API 端点。先确认你的运行环境能正常访问https://taotoken.net/api,可以用 curl 测一下:

curl -X POST https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{"model":"qwen-plus","messages":[{"role":"user","content":"hi"}]}'

如果 curl 通但 Java 不通,检查是否有本地代理配置干扰了 HTTP 客户端。j-langchain 底层用的 HTTP 客户端可能读取了系统代理设置,把代理关掉再试。

reading choices 报错:这个报错说明请求发出去了、响应也回来了,但解析响应体时找不到choices字段。常见原因是 Base URL 指向了错误的端点,比如把/api写成了/api/v1导致路径重复,或者模型名写错导致服务端返回了错误结构。检查你的 Base URL 和 Model ID 是否匹配,Model ID 必须是 TaoToken 支持的模型标识。另外,如果响应体是流式的但客户端按非流式解析,也会出现这个错,确认stream参数和客户端配置一致。

OAuth 相关报错:如果你用的是需要 OAuth 的模型或客户端,报错通常提示 token 过期或 scope 不足。TaoToken 的 API Key 是 Bearer 方式,不需要 OAuth 流程。如果你在代码里混用了 OAuth 客户端配置,把它去掉,统一用Authorization: Bearer <key>头。检查你的 HTTP 客户端是否自动加了其他认证头,导致冲突。

工具调用不触发:如果 SubAgent 没有调用任何工具就直接返回,先检查description是否足够清晰。主 LLM 靠description决定是否路由到子代理,子代理靠工具描述决定调哪个工具。描述太模糊会导致模型跳过工具。其次检查temperature是否太高,建议设 0。最后检查max-iterations是否设得太小,导致子代理还没调完工具就到达迭代上限。

子代理结果没整合:如果主 Agent 收到了子代理返回但没整合进最终回答,检查systemPrompt是否明确要求使用子代理结果。主 Agent 的 system prompt 里要写清楚「遇到旅行信息查询任务请使用 travel_researcher,并基于其返回结果作答」。另外确认.subAgent(researcher)注册后,主 Agent 的 LLM 确实能看到这个 Tool,可以通过verbose(true)打印完整请求体确认。

排查时建议打开verbose(true),它会输出[subagent:<name>]前缀的日志,让你清楚看到子代理内部的每一次工具调用和观测。这个可观测性是 SubAgent 相比 Skill 的一个明显优势,生产环境监控尤其有用。

6. 把统一 Key 和子代理链路用起来

SubAgent 在 Skill 的基础上进一步提升了子代理的自主性:自有工具注册在 SubAgent 自身,不依赖父 Agent 持有;对主 Agent 暴露为普通 Tool,接口和 Skill 一致,可以混用;三种配置来源(classpath AGENT.md、数据库、代码构造)效果完全相同;verbose(true)输出[subagent:<name>]前缀日志,便于精细回调调试。

如果你要长期跑编码类或 Agent 类任务,建议把 TaoToken 的 Coding Plan 用起来(https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite),统一 Key 管理多个子代理的模型调用,省去逐个平台配置的麻烦。接入文档在https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite,里面有 Base URL、Key、Model ID 三件套的完整说明。

实际落地时,我建议先把travel_researcher这个例子跑通,确认自主决策链路生效,再按业务拆分子代理。每个子代理只挂自己需要的工具,description写清楚触发条件,max-iterations按任务复杂度设。主 Agent 的 system prompt 里明确路由规则,避免模型在多个子代理之间摇摆。这样一套分层结构跑起来,多步任务的完成率和可维护性都会比单层 Agent 好很多。

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

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

立即咨询