1. 为什么堆 Prompt 的 Agent 迟早要重构如果你正在用 Spring AI Alibaba 做企业级 AI Agent大概率经历过这个阶段系统提示词从 200 字涨到 2000 字再涨到 8000 字。每加一个能力就往里塞一段说明每改一个流程就动一次全局 Prompt。最后的结果是 Token 成本飙升、响应变慢、改一处崩三处团队里没人敢动那段提示词。Spring AI Alibaba Skills 就是来解决这个问题的。它把 Agent 的能力从「一大坨提示词」拆成一个个独立的技能包每个技能是一个带SKILL.md的目录Agent 先看到技能清单判断需要哪个技能后再按需加载完整说明。这套机制叫渐进式披露本质上是把认知负荷管理做进了工程结构里。这篇面向的是已经跑通 Spring AI Alibaba 基础对话、准备把 Agent 推向生产环境的开发者。我会给出可复制的config.toml骨架、Skills 注册配置以及通过 TaoToken 统一 Key 接入的完整链路最后用一个 Skills 调用验证配置是否真正生效。全程代码可直接落地不玩概念。2. TaoToken 前置统一 Key 与 API 通道准备在写 Skills 配置之前先把模型接入层固定下来。企业项目里最怕的就是每个模块各配一套 Key、各写一份 base_url后面换模型或加配额策略时到处改。TaoToken 在这里的角色是统一入口一个 Key 走所有模型调用base_url 固定Spring AI Alibaba 的OpenAiChatModel直接对接即可。你需要先拿到一个 API Key。登录 TaoToken 控制台在 API Keys 页面创建一个新 Key复制保存。这个 Key 后面会写进config.toml不要硬编码在 Java 代码里。关于地址记住两个官网入口https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentAPI 基址https://taotoken.net/api 这个不加 UTM直接用于配置模型对话调试可以用这个页面先确认 Key 有效https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite如果你后续要做长期编码类 Agent 或跑 Coding Plan对应的入口是https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewriteKey 管理页https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewriteClaude Code / Anthropic 兼容通道https://taotoken.net/claude-code-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaude_codeutm_campaignrewrite拿到 Key 之后先别急着写 Skills用一条 curl 确认通道是通的curl https://taotoken.net/api/v1/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -d { model: qwen-plus, messages: [{role: user, content: 回复 ok}] }返回里有choices[0].message.content就说明 Key 和通道都正常。这一步别跳过后面 Skills 报错时你能快速排除是模型层还是技能层的问题。3. 可复制配置config.toml 骨架与 Skills 注册Spring AI Alibaba 项目里模型和 Skills 的配置建议集中放在config.tomlJava 侧只读配置、不写死参数。下面这份骨架可以直接复制改掉 Key 和路径就能用。[model] provider openai-compatible base_url https://taotoken.net/api api_key ${TAOTOKEN_API_KEY} chat_model qwen-plus temperature 0.3 max_tokens 4096 [skills] # 技能根目录本地开发指向项目下的 skills 文件夹 project_skills_dir ./skills # 是否开启渐进式工具披露 progressive_tools true # 技能内容缓存 TTL秒 cache_ttl 1800 [skills.registry] # 可选值filesystem / classpath / oss type filesystem # classpath 模式下生效 classpath_path skills [agent] name enterprise-skills-agent # 单次会话最多激活技能数防止上下文膨胀 max_active_skills 5对应的 Java 配置绑定类ConfigurationProperties(prefix model) public class ModelProperties { private String baseUrl; private String apiKey; private String chatModel; private Double temperature; private Integer maxTokens; // getter / setter 省略 }模型 Bean 的构建把 base_url 指向 TaoTokenBean public ChatModel chatModel(ModelProperties props) { return OpenAiChatModel.builder() .baseUrl(props.getBaseUrl()) .apiKey(props.getApiKey()) .model(props.getChatModel()) .temperature(props.getTemperature()) .build(); }Skills 注册表按配置类型选择Bean public SkillRegistry skillRegistry(SkillsProperties props) { if (classpath.equals(props.getRegistry().getType())) { return ClasspathSkillRegistry.builder() .classpathPath(props.getRegistry().getClasspathPath()) .build(); } return FileSystemSkillRegistry.builder() .projectSkillsDirectory(props.getProjectSkillsDir()) .build(); }然后把注册表挂到 Agent 上Bean public ReactAgent skillsAgent(ChatModel chatModel, SkillRegistry registry) { SkillsAgentHook hook SkillsAgentHook.builder() .skillRegistry(registry) .build(); return ReactAgent.builder() .name(enterprise-skills-agent) .model(chatModel) .hooks(List.of(hook)) .build(); }一个最小可用的技能目录长这样skills/ └── docx/ └── SKILL.mdSKILL.md的 frontmatter 必须写清楚触发场景--- name: docx description: 用于创建、编辑 Word 文档.docx支持标题、表格、图片。触发关键词Word、.docx、报告、备忘录 --- # Word 文档生成技能 ## 使用说明 当用户需要生成 .docx 文档时使用本技能。 ## 工作流程 1. 确认文档标题与章节结构 2. 调用 writing_tool 生成正文 3. 输出文件路径并校验编码description是 AI 判断是否加载技能的唯一依据写得模糊等于没写。把触发关键词列全比写一段漂亮描述有用得多。4. 验证请求启动后跑通一次 Skills 链路配置写完必须验证 Skills 是否真的被加载和调用。启动应用后先发一条探测请求SpringBootTest class SkillsAgentTest { Autowired private ReactAgent skillsAgent; Test void shouldListSkills() { String reply skillsAgent.call(你有哪些技能); System.out.println(reply); assert reply.contains(docx); } }预期输出里会出现docx技能名。如果返回的是「我没有技能」或空列表说明注册表路径不对或SKILL.md的 frontmatter 格式有问题。第二步触发一次真实技能加载Test void shouldActivateDocxSkill() { String reply skillsAgent.call(帮我生成一份项目周报的 Word 文档); System.out.println(reply); }观察日志应该能看到类似read_skill(docx)的调用记录随后writing_tool被激活。这就是渐进式披露生效的标志技能内容在需要时才进入上下文工具在技能激活后才对模型可见。如果你想先用模型对话页面手动确认模型侧没问题可以走这个入口https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite验证通过后把max_active_skills调成 1 再跑一次确认多技能场景下不会互相污染上下文。这一步能提前暴露技能描述重叠的问题。5. 本篇常见错排查技能列表为空九成是project_skills_dir路径写错。用System.getProperty(user.dir)打印当前工作目录确认skills文件夹在正确位置。classpath 模式下检查src/main/resources/skills是否被打进 JAR。SKILL.md 解析失败frontmatter 必须用---包裹name和文件夹名一致。YAML 里冒号后面要有空格中文描述不要用未转义的特殊字符。模型返回 function.arguments 不是 JSONqwen 系列在工具调用时偶发引号转义问题。包一层兼容模型ChatModel compatible new QwenCompatibleChatModel(originalModel);工具激活了但调用失败检查groupedTools的 key 是否和技能name完全一致大小写敏感。key 写错工具永远不会出现。改了 SKILL.md 不生效文件系统模式有缓存重启应用或调用注册表的刷新方法。生产环境如果用了 Redis 缓存上传新技能后要清对应 userId 的缓存。Token 消耗没降下来检查是不是把长文档内容直接写进了SKILL.md。技能说明控制在 1.5k 到 2k tokens长参考资料放references/目录让模型按需读取。Windows 下中文乱码写文件时加 UTF-8 BOM路径用反斜杠或正斜杠统一避免混用 Unix 命令。6. 接入路径与后续动作Skills 跑通之后下一步通常是把注册表从本地文件系统换成 OSS 或数据库做多租户隔离。这时候 Key 和通道层不需要动因为 TaoToken 的 base_url 和 Key 已经固定在config.toml里换存储层不影响模型接入。如果你还在调试阶段先把 API Keys 和接入文档过一遍https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 和 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite准备做长期编码类 Agent 的直接看 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite我自己的习惯是每加一个新技能先跑一遍「你有哪些技能」的探测请求再跑一次真实触发两步都过才提交代码。这样能把配置问题和技能描述问题分开定位省掉大量翻日志的时间。