
1. 从一次技能不触发说起Spring AI Alibaba Skills 技能体系到底解决什么问题如果你正在用 Spring AI Alibaba 1.x 搭 ReactAgent大概率遇到过这种尴尬明明在skills/目录里放好了SKILL.mdAgent 却像没看见一样用户问“帮我提取这份 PDF”模型直接开始瞎编压根不去读技能。问题不在模型而在技能体系这条链路没接通——SkillRegistry没注册上、SkillsAgentHook没挂到ReactAgent上或者挂上了但read_skill工具没被注入。Spring AI Alibaba 的 Skills 技能体系本质是给 Agent 装了一套“按需查阅的手册库”。每个技能是一个独立目录核心是SKILL.md里面用 YAML 头写name和description正文写这个技能怎么用、有哪些脚本和参考文档。SkillRegistry负责扫描、加载、管理这些技能SkillsAgentHook负责把技能列表塞进系统提示并自动注册read_skill、search_skills、disable_skill三个工具。模型在系统提示里只看到技能摘要名称描述路径判断需要时再调read_skill把完整内容拉进来——这就是渐进式披露避免一次性把几万字技能说明灌进上下文。这套机制适合谁适合已经在用 Spring AI Alibaba 做 Agent、想让能力可插拔复用的开发者也适合刚接触 Skills、想先跑通一条最小链路再扩展的人。我试过把商品文案、选品分析、PDF 提取三个技能挂到同一个 Agent 上模型能根据用户意图自动选技能不需要在提示词里硬编码任何业务逻辑。下面按“注册→挂载→验证→排障”的顺序把这条链路完整走一遍。2. TaoToken 前置准备给 ReactAgent 配一个能稳定调用的模型入口Skills 技能体系本身不绑定模型但ReactAgent需要一个ChatModel才能跑。如果你本地没有现成的模型接入或者想用一个统一的入口来验证技能触发可以先把模型通道准备好。TaoToken 提供的是 OpenAI 兼容的 API 入口Spring AI Alibaba 底层走 Spring AI 的 OpenAI 适配所以配置方式和标准 OpenAI 一致。先拿到 API Key打开https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentskills_agent_hook创建一个 Key复制保存。注意 Key 只在创建时显示一次丢了就重新建。然后在application.yml里配置模型。Spring AI Alibaba 的ZhiPuAiChatModel只是示例你完全可以用 OpenAI 兼容的OpenAiChatModel指向 TaoToken 的 API 地址spring: ai: openai: api-key: ${TAOTOKEN_API_KEY} base-url: https://taotoken.net/api chat: options: model: claude-sonnet-4-20250514 temperature: 0.7环境变量里放 Key别硬编码进代码export TAOTOKEN_API_KEYsk-你的key这里有个容易踩的点base-url要写到/api不要带/v1或/chat/completionsSpring AI 的 OpenAI 客户端会自己拼路径。如果你用的是spring-ai-alibaba-starter里的DashScopeChatModel那套是阿里云百炼的接入方式和 TaoToken 不是一回事别混用。本文后续的ReactAgent示例里chatModel这个 Bean 你替换成上面配置生成的OpenAiChatModel即可。模型通道通了之后Skills 的验证才有意义——否则你分不清是技能没触发还是模型压根没返回。建议先用一个最简单的chatModel.call(你好)确认通道正常再往下走技能注册。3. 可复制配置SkillRegistry 注册 SkillsAgentHook 挂载 ReactAgent这一节是全文的核心所有代码都可以直接复制到你的 Spring Boot 项目里。先看目录结构技能放在项目根目录的skills/下skills/ ├── copywriting/ │ └── SKILL.md ├── product-selection/ │ └── SKILL.md └── pdf-extractor/ ├── SKILL.md └── scripts/ └── extract_pdf.pycopywriting/SKILL.md的内容--- name: copywriting description: 商品文案写作技能根据商品信息生成营销文案输出10字以内的简短文案 --- # 商品文案写作 你是一个商品文案写作专家。根据用户提供的商品信息输出简短的营销文案。 ## 输出要求 - 只输出10个字以内的文案 - 格式「文案XXX」pdf-extractor/SKILL.md里要写清楚脚本路径和调用方式--- name: pdf-extractor description: 从PDF文档中提取文本、表格和表单数据支持元数据提取 --- # PDF提取器技能 ## 使用说明 1. 校验输入确认PDF文件路径存在 2. 提取内容通过 shell 工具执行 python scripts/extract_pdf.py pdf_file_path 3. 处理结果解析脚本输出的JSON数据 4. 呈现输出按用户要求格式展示 ## 脚本位置 scripts/extract_pdf.py接下来是 Java 配置类。先建SkillRegistry用FileSystemSkillRegistry从项目目录加载Configuration class SkillsConfig { Bean public SkillRegistry skillRegistry() { return FileSystemSkillRegistry.builder() .userSkillsDirectory(System.getProperty(user.home) /saa/skills) .projectSkillsDirectory(./skills) .autoLoad(true) .build(); } }userSkillsDirectory放全局技能projectSkillsDirectory放项目技能同名时项目技能覆盖用户技能。autoLoad(true)表示build()时立即扫描加载。然后建SkillsAgentHook把 registry 挂上去Bean public SkillsAgentHook skillsAgentHook(SkillRegistry skillRegistry) { return SkillsAgentHook.builder() .skillRegistry(skillRegistry) .autoReload(true) .build(); }autoReload(true)会在每次 Agent 调用前重新扫描技能目录开发阶段改完SKILL.md不用重启。生产环境可以关掉减少 IO。最后建ReactAgent把 hook 挂上Bean(skillsAgent) public ReactAgent skillsAgent(OpenAiChatModel chatModel, SkillsAgentHook skillsAgentHook) throws GraphRunnerException { return ReactAgent.builder() .name(skills_agent) .model(chatModel) .hooks(skillsAgentHook) .enableLogging(true) .build(); }如果你还要跑 PDF 提取这种带脚本的技能得再加一个ShellToolAgentHook否则模型调不到 shellBean public ShellToolAgentHook shellToolAgentHook() { return ShellToolAgentHook.builder() .shellTool2(ShellTool2.builder(System.getProperty(user.dir)).build()) .build(); }然后ReactAgent的.hooks()里传两个.hooks(skillsAgentHook, shellToolAgentHook)这里有个关键点SkillsAgentHook只负责注入read_skill等技能管理工具不负责执行脚本。脚本执行靠ShellToolAgentHook或你自己注册的PythonTool。两者职责分开别指望一个 hook 全包。如果你想把某些工具绑定到特定技能用groupedToolsMapString, ListToolCallback groupedTools Map.of( pdf-extractor, List.of(pdfParseTool, fileReadTool) ); SkillsAgentHook hook SkillsAgentHook.builder() .skillRegistry(registry) .groupedTools(groupedTools) .build();这样只有模型调了read_skill(pdf-extractor)之后pdfParseTool才会生效避免工具列表全量暴露给模型。4. 验证请求一次技能触发调用的完整过程与结果配置写完跑一个测试确认链路通了。先写测试类SpringBootTest class SkillsAgentTest { Autowired Qualifier(skillsAgent) private ReactAgent skillsAgent; Test void testCopywritingSkill() throws Exception { OptionalOverAllState result skillsAgent.invoke( 请为这款咖啡杯写一个营销文案容量350ml陶瓷材质简约设计适合办公室使用 ); result.ifPresent(state - System.out.println(state)); } }跑起来后观察日志里的消息序列。正常情况应该是这样第一步模型收到用户请求系统提示里已经注入了技能列表包含copywriting、product-selection、pdf-extractor三个技能的 name、description 和 skillPath。第二步模型判断“写营销文案”匹配copywriting的描述发起工具调用{ name: read_skill, arguments: {\skill_name\:\copywriting\} }第三步read_skill返回SKILL.md的完整内容# 商品文案写作 你是一个商品文案写作专家。根据用户提供的商品信息输出简短的营销文案。 ## 输出要求 - 只输出10个字以内的文案 - 格式「文案XXX」第四步模型按技能要求输出最终结果文案职场好伴侣如果你在日志里看到read_skill这个 tool call说明技能体系已经跑通了。如果模型直接输出文案、没有read_skill调用说明技能列表没注入成功回到第 3 节检查SkillsAgentHook是否挂到了ReactAgent上。再验证一个带脚本的技能。测试 PDF 提取Test void testPdfExtractorSkill() throws Exception { OptionalOverAllState result skillsAgent.invoke( 从 ./skills/pdf-extractor/saa-roadmap.pdf 中提取内容 ); result.ifPresent(state - System.out.println(state)); }预期消息序列模型先调read_skill(pdf-extractor)读到技能说明后再调 shell 执行python scripts/extract_pdf.py ./skills/pdf-extractor/saa-roadmap.pdf脚本返回 JSON模型整理后输出。如果卡在 shell 调用那一步检查ShellToolAgentHook有没有挂上、Python 环境有没有装pdfplumber之类的依赖。验证通过后你可以用registry.listAll()确认技能加载情况ListSkillMetadata all registry.listAll(); all.forEach(s - System.out.println(s.name() - s.skillPath()));输出应该能看到三个技能及其绝对路径。如果某个技能没出现检查目录名和SKILL.md的 YAML 头是否合法。5. 本篇常见错排查401、read_skill 不触发、脚本找不到怎么解技能体系跑不通报错通常集中在几个地方。下面按真实报错对照排查。401 Unauthorized / invalid api key这个和 Skills 无关是模型通道的问题。检查TAOTOKEN_API_KEY环境变量有没有生效base-url是不是写成了https://taotoken.net/api。如果你在application.yml里直接写了 Key确认没有多余空格。Spring AI 的 OpenAI 客户端在 401 时不会重试直接抛异常日志里能看到401 Unauthorized from POST https://taotoken.net/api/chat/completions。模型不调 read_skill直接回答这是最高频的问题。原因有三个一是SkillsAgentHook没挂到ReactAgent的.hooks()里二是SkillRegistry的autoLoad设成了false且没手动reload()三是SKILL.md的description写得太模糊模型判断不出该用哪个技能。排查顺序先看启动日志里有没有Loaded skill: copywriting之类的输出没有就是加载失败有加载但模型不调把description改得更具体比如“当用户要求写商品营销文案、推广语时使用”。local proxy failed / connection refused如果你在本地配了 HTTP 代理Spring AI 的客户端可能走了代理导致连接失败。检查JAVA_TOOL_OPTIONS或系统代理设置确保https://taotoken.net直连。这个报错和 Skills 无关但会阻断整个 Agent 调用。reading choices 相关报错 / 返回体解析失败通常是base-url配错比如写成了https://taotoken.net/api/v1导致请求路径变成/api/v1/chat/completions服务端返回的不是标准 OpenAI 格式。改成https://taotoken.net/api即可。另外确认model字段填的是服务端支持的模型 ID填错会返回 404 或空 choices。OAuth / token 过期如果你用的是需要 OAuth 的模型服务token 过期会报这个。TaoToken 的 API Key 是长期有效的不存在 OAuth 刷新问题。如果你混用了其他需要 OAuth 的通道单独排查那条链路。脚本找不到 / No such file or directoryPDF 提取技能里SKILL.md写的脚本路径是相对路径scripts/extract_pdf.py但模型执行 shell 时的工作目录可能不是技能目录。解决办法是在SKILL.md里写绝对路径或者在ShellTool2.builder()里把工作目录设成技能根目录。更稳妥的做法是让SKILL.md明确写“使用技能列表中的绝对路径执行脚本”模型会从skillPath拼出完整路径。技能加载了但 read_skill 返回空检查SKILL.md的 YAML 头格式。---必须独占一行name和description不能有缩进错误。如果 YAML 解析失败SkillMetadata的fullContent会是空字符串read_skill返回空模型拿不到指令就只能瞎编。用registry.get(copywriting).get().fullContent()打印一下确认内容非空。Codex auth.json / Cline MCP 场景如果你是在 Codex 或 Cline 这类工具里通过 MCP 接 Spring AI Alibaba 的 Agent配置三件套要写全Base URL 填https://taotoken.net/apiKey 填你的 API KeyModel ID 填具体模型名。缺任何一个都会导致连接失败。MCP 直连生产库这种操作不要做技能体系本身是只读加载SKILL.md不涉及数据库直连。排障的核心思路是分段验证先确认模型通道通直接 call 一次再确认技能加载了listAll()有输出再确认 hook 挂上了日志里有read_skill调用最后确认脚本能执行手动跑一次 python 脚本。哪一段断了就修哪一段别一上来就怀疑模型。6. 把技能体系接进你的项目从验证到日常使用的几个实用动作链路跑通之后日常使用还有几个动作能让技能体系更顺手。第一技能目录按项目隔离。projectSkillsDirectory指向当前项目的./skills不同项目的技能互不干扰。全局通用的技能放userSkillsDirectory比如你团队统一的代码规范检查技能。同名时项目技能优先这个覆盖规则在调试时很有用——你可以临时在项目里放一个改过的技能覆盖全局版本验证完再删掉。第二autoReload在开发阶段开着生产关掉。开发时改SKILL.md不用重启autoReload(true)每次调用前重新扫描。生产环境技能目录不会变开着只是浪费 IO设成false需要更新技能时重启或手动调registry.reload()。第三用disable_skill做灰度。SkillsAgentHook自动注入了disable_skill工具模型可以在运行时禁用某个技能。你也可以在代码里调registry.disable(copywriting)让某个技能暂时不参与匹配。这在技能调试阶段很有用——挂了一堆技能但只想测其中一个时把其他的禁掉减少干扰。第四groupedTools按需暴露工具。技能绑定的工具不要全量注册到 Agent 上用groupedTools把工具和技能名关联只有模型读了对应技能后工具才生效。这样工具列表不会膨胀模型选择工具时也不会被无关工具干扰。第五技能描述要写“什么时候用”不只是“是什么”。description字段是模型判断是否调用read_skill的唯一依据。写“商品文案写作技能”不如写“当用户要求写商品营销文案、推广语、产品卖点描述时使用”。把触发场景写进去模型匹配准确率会明显提升。如果你还没拿到 API Key先去https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentskills_agent_hook建一个然后按第 3 节的配置把SkillRegistry和SkillsAgentHook接上。接入过程中遇到read_skill不触发或者脚本执行失败对照第 5 节的报错表逐段排查。技能体系的价值在于复用——你写一次SKILL.md所有挂了这个 registry 的 Agent 都能用这比在每个 Agent 的提示词里复制粘贴业务逻辑要干净得多。