ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

Spring AI Alibaba ReactAgent 用 Skill 生成旅游计划:SkillsAgentHook 配置与验证

Spring AI Alibaba ReactAgent 用 Skill 生成旅游计划:SkillsAgentHook 配置与验证 1. 从一次“Skill 没被触发”的排查说起Spring AI Alibaba 的 ReactAgent 本身已经能调工具但当你希望它按一套固定业务规范输出内容时光靠 systemPrompt 会越写越长、越写越乱。Skill 机制解决的正是这个问题把“旅游计划该怎么生成”这类领域知识从提示词里抽出来放进独立的 SKILL.md由 SkillsAgentHook 在运行时按需注入。这篇要聊的就是 ReactAgent 通过 Skill 生成旅游计划的完整落地路径核心检索词是 Spring AI Alibaba ReactAgent Skill 配置适合已经在用 Spring AI Alibaba 搭 Agent、但发现提示词维护成本越来越高的同学。我试过的第一个坑很典型SKILL.md 写好了ClasspathSkillRegistry 也注册了日志里 Skills loaded 数量也对但发一句“帮我规划去成都的旅游”模型压根没走 Skill直接自己编了一段行程。问题不在 Skill 内容而在 Hook 的装配顺序和触发条件。ReactAgent 的 hooks 是一个链式结构SummarizationHook 和 SkillsAgentHook 谁先谁后、SkillRegistry 的 classpathPath 指向哪里、SKILL.md 的 name 是否和文件夹名严格一致任何一处不对Skill 就是“加载了但不生效”。所以这篇不打算只贴一段 AgentConfig 就完事而是按“问题场景 → 前置准备 → 可复制配置 → 验证请求 → 报错排查 → 后续接入”的顺序走一遍。你会看到 SKILL.md 的 front matter 怎么写、ClasspathSkillRegistry 怎么指路径、SkillsAgentHook 怎么和 SummarizationHook 共存、以及一次真实的旅游计划请求返回了什么。中间涉及模型接入的部分我会用 TaoToken 的 API 作为示例因为它的 Base URL 和 Key 管理方式对 Java 侧比较友好配置片段可以直接抄。先明确一件事Skill 不是工具。工具是 WeatherTool、SearchTool 这种带 Tool 注解、能被模型 function call 的方法Skill 是一段结构化的领域说明告诉模型“遇到旅游规划类请求时按这个模板和规则来”。SkillsAgentHook 的作用是在合适的时机把匹配到的 Skill 内容拼进上下文。理解这一点后面的配置就不会迷路。2. 前置准备Skill 目录、SKILL.md 与模型接入2.1 目录结构约定Spring AI Alibaba 的 ClasspathSkillRegistry 默认从 classpath 下读取 Skill。工程里通常是这样的结构src/main/resources/ skills/ travel-assistant/ SKILL.md注意两点第一skills是根目录ClasspathSkillRegistry.builder().classpathPath(skills) 指的就是它第二travel-assistant这个文件夹名必须和 SKILL.md 里 front matter 的name完全一致大小写、连字符都不能差。我见过有人文件夹叫travel_assistant、name 写travel-assistant结果 Skill 加载数量是 0日志还不报错排查半天。2.2 SKILL.md 的 front matter 写法SKILL.md 分两部分YAML front matter 和正文。front matter 至少要有 name 和 descriptiondescription 是给模型判断“这个 Skill 该不该用”的依据所以要写清楚触发场景。--- name: travel-assistant description: 当用户需要规划旅游行程时使用此技能。用户只需提供目的地技能将自动生成3-5天行程未指定天数时默认3天包含每日详细安排、花费明细可以使用 search_tool 查询目的地景点和特色美食并调用天气工具提供穿衣指数及出行提醒。 --- ## 一、功能说明 本技能用于生成可落地的旅游行程包含行程、预算、天气穿衣建议三部分。 ## 二、触发方式 核心触发词旅游规划、行程安排、XX旅游攻略、XX穿衣建议、XX旅游预算。 ## 三、核心规则 1. 目的地必填未提供时持续追问。 2. 游玩天数控制在3-5天未明确时默认3天。 3. 每日行程包含上午、下午、晚上三个时段。 4. 预算需包含每日明细及总预算提供穷游/舒适/轻奢三档。 5. 必须调用天气工具输出穿衣及出行提醒。正文部分就是你的业务规则写得越具体模型输出越稳定。上面这段是精简版实际项目里可以把行程模板、预算格式、话术都写进去模型会照着执行。2.3 模型接入Base URL 与 KeyReactAgent 需要一个 ChatModel。示例里用的是 DeepSeekChatModel但如果你想让模型走统一的 API 网关可以把 Base URL 指向 TaoToken 的 API 地址Key 用平台生成的。这样切换模型时只改配置不动代码。在application.yml里spring: ai: deepseek: api-key: ${TAOTOKEN_API_KEY} base-url: https://taotoken.net/api对应的环境变量在启动前设置好。Key 的获取路径是 TaoToken 控制台的 API Keys 页面模型 ID 按你实际要用的填比如deepseek-chat。这三件套——Base URL、Key、Model ID——在后面的配置和排查里会反复出现先记住。3. 可复制配置AgentConfig 装配 SkillsAgentHook3.1 完整 AgentConfig.java这是核心配置类改动集中在 ReactAgent 的 builder 链上。注意 hooks 里 SummarizationHook 和 SkillsAgentHook 的顺序以及 SkillRegistry 的构建方式。package com.david.springalibabareactagentdemo.config; import com.alibaba.cloud.ai.graph.agent.ReactAgent; import com.alibaba.cloud.ai.graph.agent.hook.skills.SkillsAgentHook; import com.alibaba.cloud.ai.graph.agent.hook.summarization.SummarizationHook; import com.alibaba.cloud.ai.graph.checkpoint.savers.redis.RedisSaver; import com.alibaba.cloud.ai.graph.skills.registry.SkillRegistry; import com.alibaba.cloud.ai.graph.skills.registry.classpath.ClasspathSkillRegistry; import com.david.springalibabareactagentdemo.tools.SearchTool; import com.david.springalibabareactagentdemo.tools.WeatherTool; import org.redisson.api.RedissonClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.deepseek.DeepSeekChatModel; import org.springframework.ai.deepseek.api.DeepSeekApi; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration public class AgentConfig { Logger log LoggerFactory.getLogger(AgentConfig.class); Value(${spring.ai.deepseek.api-key}) private String apiKey; Value(${spring.ai.deepseek.base-url:https://taotoken.net/api}) private String baseUrl; Bean public ReactAgent reactAgent(RedissonClient redissonClient) { DeepSeekApi deepSeekApi DeepSeekApi.builder() .apiKey(apiKey) .baseUrl(baseUrl) .build(); ChatModel chatModel DeepSeekChatModel.builder() .deepSeekApi(deepSeekApi) .build(); SkillRegistry registry ClasspathSkillRegistry.builder() .classpathPath(skills) .build(); SkillsAgentHook skillHook SkillsAgentHook.builder() .skillRegistry(registry) .build(); log.info(Skills loaded: {}, skillHook.getSkillCount()); return ReactAgent.builder() .name(ai_agent) .model(chatModel) .tools(new WeatherTool().toolCallback(), new SearchTool().toolCallback()) .systemPrompt( 你是一个博学的智能聊天助手必须调用工具获取信息不能编造答案。 调用工具后根据结果回答用户。 ) .saver(RedisSaver.builder().redisson(redissonClient).build()) .hooks( SummarizationHook.builder() .model(chatModel) .maxTokensBeforeSummary(8000) .messagesToKeep(10) .build(), skillHook ) .build(); } }3.2 关键参数对照配置项作用常见取值classpathPathSkill 根目录skillsskillRegistry注册表实例ClasspathSkillRegistrymaxTokensBeforeSummary触发摘要的 token 阈值8000messagesToKeep摘要后保留的原始轮数10baseUrl模型 API 地址https://taotoken.net/apimodel模型 IDdeepseek-chat3.3 为什么 hooks 顺序有讲究SummarizationHook 负责在对话变长时压缩历史SkillsAgentHook 负责注入 Skill。如果 Skill 注入发生在摘要之前摘要可能会把 Skill 内容也当成普通对话压掉放在后面Skill 的注入更稳定。示例里把 skillHook 放在 SummarizationHook 之后实测下来触发率明显更稳。另外SkillRegistry 是单例构建的不要在每次请求里 new。ClasspathSkillRegistry 在 build 时就把 classpath 下的 SKILL.md 扫了一遍getSkillCount()返回的就是扫到的数量。启动日志里看到Skills loaded: 1说明 travel-assistant 被正确识别了。4. 验证请求一次旅游计划生成的全过程4.1 启动与日志确认服务启动后控制台会打印Skills loaded: 1如果这里是 0先别急着调接口回到第 2 节检查文件夹名和 name 是否一致。数量对了再往下走。4.2 发起请求用一个简单的 Controller 暴露接口或者直接用测试类调 ReactAgent。请求内容就是一句自然语言帮我规划去长沙的旅游5月5日到5月8日4天4.3 返回结果片段模型先调用了 SearchTool 查长沙景点和美食再调 WeatherTool 查天气最后按 SKILL.md 里的模板输出。返回结构大致如下## 长沙4天3晚经典行程 出行时间5月5日~5月8日 总预算参考约1300元/人舒适版不含往返大交通 ### 天气情况 | 日期 | 天气 | 温度 | | 5/5 | 多云 | 16~27℃ | | 5/6 | 多云 | 17~28℃ | | 5/7 | 晴转多云 | 19~30℃ | | 5/8 | 多云转阴 | 18~28℃ | ### 第1天抵达 → 太平老街 → 五一广场 上午抵达长沙入住五一广场附近酒店 下午逛太平老街 晚上五一广场、坡子街推荐茶颜悦色、黑色经典臭豆腐 当日花费住宿200 餐饮80 交通20 300元 ### 总预算明细 住宿600 餐饮360 交通100 门票80 伴手礼100 约1240元/人 ### 穿衣建议 白天短袖早晚备薄外套穿舒适运动鞋。4.4 怎么判断 Skill 真的被触发了看三个信号第一输出里有 SKILL.md 规定的固定结构比如“上午/下午/晚上”三段式、预算三档、天气穿衣提醒第二模型确实调用了 WeatherTool返回里有具体温度和穿衣指数第三追问话术和 SKILL.md 里写的一致比如没给天数时会说“我默认给你安排3天经典行程”。如果输出是自由发挥的散文没有固定模板那大概率 Skill 没生效去第 5 节排查。5. 本篇常见错排查401、Skill 数量为 0、OAuth 报错5.1 401 Unauthorized最常见的原因是 Key 没读到或 Base URL 写错。检查application.yml里的api-key是否被环境变量正确覆盖以及base-url是否指向https://taotoken.net/api。如果 Key 是从控制台复制的注意别带多余空格。401 报错信息里通常会带invalid api key看到这个就先去 API Keys 页面重新生成一个。5.2 Skills loaded: 0三个检查点文件夹名和 name 是否一致SKILL.md 是否在resources/skills/travel-assistant/下front matter 的---是否成对出现。YAML 解析失败时ClasspathSkillRegistry 会静默跳过不报错所以数量为 0 时优先怀疑格式。5.3 local proxy failed这个报错通常出现在网络层说明请求没到达 API 地址。检查base-url是否被本地代理配置覆盖或者环境变量里有没有残留的代理设置。Java 侧可以显式设置-Dhttp.proxyHost为空来排除。5.4 reading choices 相关报错如果日志里出现reading choices或choices字段解析失败多半是模型返回格式和客户端预期不一致。确认 Model ID 填的是deepseek-chat这类标准值不要填成自定义别名。Base URL、Key、Model ID 三件套对齐后这个报错一般会消失。5.5 OAuth 报错OAuth 类报错通常和鉴权方式有关。如果你用的是 API Key 模式不要同时开 OAuth 流程。检查配置里是否混入了client-id、client-secret这类字段有的话删掉只保留api-key。5.6 Skill 加载了但不触发如果Skills loaded: 1但模型不走 Skill检查 description 是否写得太泛。description 是模型判断是否使用 Skill 的唯一依据要包含明确的触发词比如“旅游规划”“行程安排”。另外systemPrompt 里如果写了“直接回答不要使用技能”之类的限制也会压制 Skill 触发。6. 后续接入从单 Skill 到多 Skill 与 Coding Plan跑通一个 travel-assistant 之后扩展方向很自然再加一个code-reviewSkill、一个sql-optimizeSkillClasspathSkillRegistry 会自动扫描skills下的所有子目录getSkillCount()会变成 3。每个 Skill 的 description 写清楚各自的触发场景模型会在运行时按需选择。如果你打算把 ReactAgent 用在长期编码或 Agent 场景比如让 Agent 持续处理代码任务、维护上下文可以了解 TaoToken 的 Coding Plan它更适合高频、长会话的调用模式。模型对话入口可以用来单独验证某个模型 ID 是否可用接入文档里有 Java 侧的完整示例。API Keys 页面负责生成和管理 Key控制台可以看调用量。配置层面把base-url统一指向https://taotoken.net/apiKey 走环境变量Model ID 按需切换这样从旅游计划这种轻量 Skill 到代码 Agent 这种重场景底层接入不用改。Skill 的价值在于把业务规则从提示词里解耦出来ReactAgent 负责调度SkillsAgentHook 负责注入两者配合好输出稳定性会有明显提升。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表