news 2026/10/8 12:14:53

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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 和 description,description 是给模型判断“这个 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 与 Key

ReactAgent 需要一个 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 装配 SkillsAgentHook

3.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 根目录skills
skillRegistry注册表实例ClasspathSkillRegistry
maxTokensBeforeSummary触发摘要的 token 阈值8000
messagesToKeep摘要后保留的原始轮数10
baseUrl模型 API 地址https://taotoken.net/api
model模型 IDdeepseek-chat

3.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-optimizeSkill,ClasspathSkillRegistry 会自动扫描skills下的所有子目录,getSkillCount()会变成 3。每个 Skill 的 description 写清楚各自的触发场景,模型会在运行时按需选择。

如果你打算把 ReactAgent 用在长期编码或 Agent 场景,比如让 Agent 持续处理代码任务、维护上下文,可以了解 TaoToken 的 Coding Plan,它更适合高频、长会话的调用模式。模型对话入口可以用来单独验证某个模型 ID 是否可用,接入文档里有 Java 侧的完整示例。API Keys 页面负责生成和管理 Key,控制台可以看调用量。

配置层面,把base-url统一指向https://taotoken.net/api,Key 走环境变量,Model ID 按需切换,这样从旅游计划这种轻量 Skill 到代码 Agent 这种重场景,底层接入不用改。Skill 的价值在于把业务规则从提示词里解耦出来,ReactAgent 负责调度,SkillsAgentHook 负责注入,两者配合好,输出稳定性会有明显提升。

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

写代码用 Wrangler,日常运维进自建面板:我是怎么用爽 Cloudflare 的

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/8 12:14:17

用Prompt Engineering生成可玩HTML游戏:从复制提示词到独立设计

在外面翻了一圈prompt收藏夹,你是不是也干过这种事:看到别人晒的“神级prompt”,赶紧复制进备忘录,真到让AI生成一个想玩的游戏时,要么生成出来是个空壳,要么直接被系统提示invalid prompt。我前三个月就是…

作者头像 李华
网站建设 2026/10/8 12:12:52

2026年GPT会员订阅全指南:从Plus/Pro选档到绑卡、报错排查一次说清

最近后台关于GPT会员的私信明显多起来,问题集中在几类:该订Plus还是Pro、网页一直提示高峰挤不进去、绑卡总被拒、客户端装了但打不开。这篇文章就把2026年GPT会员订阅的完整链路拆开讲一遍,从选档位到支付到常见报错排查,全部是我…

作者头像 李华