1. 从一次真实的踩坑说起:为什么单智能体搞不定旅游规划
你可能也遇到过这种场景:想用一个大模型一次性生成"北京三日游"的完整方案,结果它要么把天气编得离谱,要么景点和行程对不上号,改一处又崩另一处。我试过把提示词写到八百字,模型依然会在"下雨天安排户外徒步"这种低级错误上翻车。根本原因不在于模型不够强,而在于一个智能体同时扛了太多职责——它既要查天气、又要选景点、还要排日程,注意力被稀释,任何一环出错都会污染整条链路。
多智能体系统(Multi-Agent System)解决的正是这个问题。它的思路很朴素:把复杂任务拆成若干子任务,每个子任务交给一个"专家智能体",专家之间通过结构化的数据传递协作,最后由汇总智能体整合输出。就像一家旅行社,有专门查天气的、专门做景点调研的、专门排行程的,各司其职再拼成完整方案。
Google 开源的 ADK for Java(Agent Development Kit)把这套协作模式做成了可编排的组件:LlmAgent定义单个智能体,SequentialAgent和ParallelAgent负责编排执行顺序,outputKey机制让上游智能体的输出能被下游用占位符引用。对 Java 开发者来说,这意味着不用切换到 Python 生态,就能在企业级项目里落地多智能体。
这篇内容适合三类人:一是已经会写 Java、想入门 Agent 开发的后端工程师;二是想给现有系统加 AI 能力的架构师;三是被单智能体"幻觉"折磨过、想找结构化方案的同学。下面我会给出可复制的项目骨架、三个智能体的完整配置、运行验证步骤,以及如何通过 TaoToken 统一 Key/API 通道接入模型,让你跑通一次完整的协作规划任务。
2. TaoToken 前置:把模型接入这件事先理顺
在写智能体代码之前,有个容易被忽略但很关键的前置问题:模型通道。ADK for Java 默认走 Google AI Studio 的 API Key,但在实际项目里,你往往需要统一管理多个模型的调用、控制成本、或者让团队共用一套凭证。这时候用 TaoToken 做统一入口会省很多事。
TaoToken 是一个模型 API 聚合通道,官网在 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,它提供 OpenAI 兼容的接口格式,API 端点是 https://taotoken.net/api 。对 ADK 项目来说,你只需要把 base URL 和 Key 配好,智能体里的模型调用就会走这条通道,不用改业务代码。
具体操作分三步。第一步,登录控制台创建 API Key,地址是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite ,在"API Keys"页面生成一个 Key 并复制保存。第二步,如果你不确定该用哪个模型,可以先去模型对话页面 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite 试跑一下,确认模型能正常响应再写进代码。第三步,把 Key 和 base URL 写进项目的配置文件,后面智能体初始化时会读取。
注意:API Key 属于敏感凭证,不要硬编码进 Java 源码提交到仓库。建议用环境变量或
application.properties配合.gitignore管理。
如果你后续要做长期编码或 Agent 开发,可以考虑 Coding Plan,地址是 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite ,它针对高频调用场景做了额度优化。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,遇到参数问题可以对照查。
3. 可复制配置:ADK 项目骨架与三个智能体
3.1 Maven 依赖与项目结构
先建一个标准 Maven 项目,pom.xml里加入 ADK 和 Jackson:
<dependencies> <dependency> <groupId>com.google.adk</groupId> <artifactId>adk-java</artifactId> <version>0.3.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.15.2</version> </dependency> </dependencies>项目结构建议这样组织,职责清晰,后面加智能体不会乱:
travel-planner-multiagent/ ├── pom.xml ├── src/main/java/com/example/travel/ │ ├── TravelPlannerApplication.java │ ├── agents/ │ │ ├── WeatherAgent.java │ │ ├── AttractionAgent.java │ │ └── ItineraryAgent.java │ └── tools/ │ └── WeatherTool.java └── src/main/resources/application.propertiesapplication.properties里配置 TaoToken 通道:
taotoken.api.key=你的_API_KEY taotoken.api.base=https://taotoken.net/api taotoken.model=gemini-2.0-flash3.2 工具类:WeatherTool
智能体要调用外部能力,得先有工具。这里用一个模拟天气查询的工具,实际项目里替换成真实天气 API 即可:
package com.example.travel.tools; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import java.util.Random; public class WeatherTool { private static final ObjectMapper MAPPER = new ObjectMapper(); private static final Random RANDOM = new Random(); public JsonNode getWeather(String city) { String[] conditions = {"晴朗", "多云", "小雨", "大雨", "阴天"}; String condition = conditions[RANDOM.nextInt(conditions.length)]; int temperature = 5 + RANDOM.nextInt(30); ObjectNode result = MAPPER.createObjectNode(); result.put("city", city); result.put("condition", condition); result.put("temperature", temperature); return result; } public String getWeatherAdvice(JsonNode weather) { String condition = weather.get("condition").asText(); if (condition.contains("雨")) { return "建议携带雨具,选择室内活动"; } else if (condition.equals("晴朗")) { return "适合户外活动,注意防晒"; } return "天气适宜,可正常出行"; } }3.3 三个智能体的配置
天气智能体负责查天气并输出建议,关键是outputKey("weatherInfo"),这个键会被下游引用:
package com.example.travel.agents; import com.example.travel.tools.WeatherTool; import com.google.adk.agents.LlmAgent; public class WeatherAgent { public static LlmAgent create() { WeatherTool tool = new WeatherTool(); return LlmAgent.builder() .name("weather-agent") .description("查询目的地天气并提供出行建议") .instruction(""" 你是一个专业的天气查询助手。 根据用户提供的城市名称,使用WeatherTool查询天气, 返回包含温度、天气状况和出行建议的格式化报告。 """) .model("gemini-2.0-flash") .tools(tool) .outputKey("weatherInfo") .build(); } }景点智能体通过{weatherInfo}占位符拿到天气结果,再结合用户偏好推荐:
package com.example.travel.agents; import com.google.adk.agents.LlmAgent; public class AttractionAgent { public static LlmAgent create() { return LlmAgent.builder() .name("attraction-agent") .description("根据天气和用户偏好推荐景点") .instruction(""" 你是一个景点推荐专家。 根据天气信息 {weatherInfo} 和用户偏好(如历史文化、美食等), 推荐3-5个适合的景点,包含简介和建议游览时间。 """) .model("gemini-2.0-flash") .outputKey("attractions") .build(); } }行程智能体整合前两者的输出,生成每日行程:
package com.example.travel.agents; import com.google.adk.agents.LlmAgent; public class ItineraryAgent { public static LlmAgent create() { return LlmAgent.builder() .name("itinerary-agent") .description("整合天气和景点信息,生成完整行程") .instruction(""" 你是一个行程规划师。 根据天气信息 {weatherInfo} 和景点推荐 {attractions}, 结合用户天数要求,生成详细的每日行程,包含餐饮和交通建议。 """) .model("gemini-2.0-flash") .outputKey("itinerary") .build(); } }3.4 主类:并行 + 顺序混合编排
这是整个系统的核心。天气和景点两个智能体没有依赖关系,可以并行跑;行程智能体依赖前两者的结果,必须等它们完成后再执行。ADK 用ParallelAgent和SequentialAgent嵌套就能表达这个流程:
package com.example.travel; import com.example.travel.agents.AttractionAgent; import com.example.travel.agents.ItineraryAgent; import com.example.travel.agents.WeatherAgent; import com.google.adk.agents.LlmAgent; import com.google.adk.agents.ParallelAgent; import com.google.adk.agents.SequentialAgent; import com.google.adk.runners.FlowRunner; import com.google.adk.sessions.InMemorySessionStore; public class TravelPlannerApplication { public static void main(String[] args) { LlmAgent weatherAgent = WeatherAgent.create(); LlmAgent attractionAgent = AttractionAgent.create(); LlmAgent itineraryAgent = ItineraryAgent.create(); ParallelAgent researchAgent = ParallelAgent.builder() .name("research-agent") .description("并行收集天气和景点信息") .subAgents(weatherAgent, attractionAgent) .build(); SequentialAgent travelPlanner = SequentialAgent.builder() .name("travel-planner") .description("旅游规划师") .subAgents(researchAgent, itineraryAgent) .build(); FlowRunner runner = FlowRunner.builder() .sessionStore(new InMemorySessionStore()) .build(); String userQuery = "我想去北京旅游3天,喜欢历史文化和美食,帮我规划一下"; System.out.println("用户输入: " + userQuery); String result = runner.run(travelPlanner, userQuery); System.out.println("最终行程:\n" + result); } }4. 验证请求:跑通一次完整的协作规划
代码写完后,直接运行TravelPlannerApplication的main方法。如果 TaoToken 通道配置正确,你会看到控制台先打印用户输入,然后经过几秒的模型调用,输出类似下面的行程:
用户输入: 我想去北京旅游3天,喜欢历史文化和美食,帮我规划一下 最终行程: 【第一天】 上午:参观故宫博物院(历史文化的精髓,建议3-4小时) 中午:在故宫附近的四季民福烤鸭店享用北京烤鸭 下午:游览景山公园,俯瞰故宫全景 晚上:逛王府井小吃街,品尝北京特色小吃 【第二天】 上午:前往颐和园(皇家园林,建议3-4小时) 中午:在颐和园附近品尝老北京炸酱面 下午:参观圆明园遗址公园 晚上:观看老舍茶馆的京剧表演 【第三天】 上午:游览天坛公园(了解古代祭天文化) 中午:在前门大街品尝爆肚、卤煮等小吃 下午:逛大栅栏商业街,购买伴手礼验证成功的标志有三个:一是天气信息被景点推荐正确引用(比如天气是"小雨",景点推荐里应该出现室内场馆);二是景点列表和行程里的地点能对应上;三是行程天数符合用户要求的 3 天。如果这三点都满足,说明outputKey的传递链路是通的。
想单独验证模型通道是否正常,可以先去模型对话页面 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite 发一条测试消息,确认返回正常再排查代码。如果要做更复杂的编码类 Agent,Coding Plan 的额度更适合高频场景。
5. 本篇常见错排查
报错一:outputKey引用的占位符为空。现象是行程智能体拿到的{weatherInfo}是空字符串。原因通常是上游智能体的outputKey名字和下游instruction里的占位符拼写不一致,比如一个写weatherInfo、另一个写weather_info。排查方法:把三个智能体的outputKey和占位符列出来逐一比对,大小写敏感。
报错二:并行智能体输出互相覆盖。如果两个并行智能体用了同一个outputKey,后完成的会覆盖先完成的。解决方法是给每个智能体分配唯一的 key,比如weatherInfo和attractions分开。
报错三:模型调用返回 401 或 403。这是 TaoToken 通道配置问题。检查application.properties里的 Key 是否复制完整、base URL 是否写成https://taotoken.net/api(注意不要多加路径)。如果 Key 没问题,去控制台 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite 确认 Key 状态是否正常、额度是否充足。
报错四:FlowRunner报 session 相关异常。多半是InMemorySessionStore没有正确初始化,或者多次运行共用了同一个 store 导致状态污染。每次运行前新建一个 store 实例即可。
报错五:行程智能体忽略了天气建议。这不是代码错误,而是提示词问题。在instruction里明确要求"如果天气为雨,行程必须包含室内活动",模型会更严格地遵守约束。
6. 继续深入:从 Demo 到可用系统
跑通这个 Demo 后,你可以沿着几个方向扩展。一是把WeatherTool换成真实天气 API,让数据不再是随机的。二是引入LoopAgent,让行程智能体在检测到"景点距离过远"时自动重新规划。三是用 Sub-Agents 模式,让主智能体根据用户问题动态决定调用哪些子智能体,而不是固定流程。
如果你打算把这套东西接入现有 Java 企业应用,建议先把模型通道统一到 TaoToken,这样后续换模型、加额度、做监控都不用改业务代码。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 有完整的参数说明,Claude Code 相关的接入示例在 https://taotoken.net/claude-code?utm_source=taotoken_aicg_blog_end&utm_content=claude_code&utm_campaign=rewrite 也能找到参考。
多智能体的价值不在于智能体数量多,而在于职责边界清晰、数据流转可控。把这三个智能体的协作跑顺了,你再去拆解更复杂的业务场景,思路会清晰很多。