08 - Skills 智能体
一、定位
SkillsReactAgent 是通用型智能体,将 Skills(技能)、搜索、文件、文件系统、Bash、Grep 多种能力按需挂载为工具,让 LLM 自动判断使用哪个工具/Skill。
二、核心特性
| 特性 | 说明 |
|---|
| 多工具融合 | 通过 ToolMergeUtils.mergeTools 合并所有工具 |
| Skills 机制 | 加载 SKILL.md 技能文件,按需激活 |
| 上下文压缩 | 双层压缩(micro + auto)防止超长对话 |
| 事件流输出 | Thinking/Text/ToolStart/ToolEnd/Error/Complete 6 类事件 |
| 重试机制 | maxRetries 支持工具调用失败重试 |
三、两种 Skills 模式
3.1 模式 1:Spring AI 原生(initSkillsReactAgent)
通过 SkillsTool 将 Skills 注册为真实的 Tool 回调:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| private SkillsReactAgent initSkillsReactAgent() { ToolCallback[] allTools = ToolMergeUtils.mergeTools( webSearchToolCallbacks, ToolCallbacks.from(fileContentService), new ToolCallback[]{SkillsTool.builder() .addSkillsDirectory(skillsDirectory) .build()}, FileSystemTools.create(), GrepTool.create(), BashTool.create() );
return SkillsReactAgent.builder() .name("skills") .chatModel(chatModel) .tools(allTools) .sessionService(sessionService) .taskManager(taskManager) .contextPolicy(ContextPolicy.defaults()) .build(); }
|
3.2 模式 2:手动 Skills(initManualSkillsReactAgent)
通过 SkillManager + SkillPromptFormatter 将技能列表格式化为系统提示词的一部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| private SkillsReactAgent initManualSkillsReactAgent() { SkillConfig skillConfig = SkillConfig.builder() .addDirectory(skillsDirectory) .build(); SkillManager skillManager = SkillManager.create(skillConfig);
String skillsPrompt = skillManager.formatPrompt();
ToolCallback readSkillTool = ReadSkillTool.create(skillManager.getRegistry());
ToolCallback[] allTools = ToolMergeUtils.mergeTools( webSearchToolCallbacks, ToolCallbacks.from(fileContentService), new ToolCallback[]{readSkillTool}, FileSystemTools.create(), GrepTool.create(), BashTool.create() );
return SkillsReactAgent.builder() .name("manual-skills") .chatModel(chatModel) .tools(allTools) .systemPrompt(skillsPrompt) .maxRounds(10) .build(); }
|
对比:
| 维度 | 原生 Skills | 手动 Skills |
|---|
| 工具数量 | 1 个(Skill) | 1 个(read_skill) |
| 技能描述位置 | 工具描述(Tool description) | 系统提示词 |
| 加载方式 | LLM 调用工具加载 | LLM 调用工具加载 |
| 灵活性 | 高 | 中 |
四、SkillManager 技能管理
agent/skills/manual/SkillManager.java 是手动模式下的技能管理器。
4.1 数据模型
SkillMetadata(agent/skills/manual/model/SkillMetadata.java):
1 2 3 4 5 6 7 8
| public class SkillMetadata { private String name; private String description; private String basePath; private Map<String, Object> frontMatter; private String content; }
|
4.2 注册中心
FileSystemSkillRegistry(registry/FileSystemSkillRegistry.java)从文件系统加载所有 SKILL.md:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class FileSystemSkillRegistry extends AbstractSkillRegistry { @Override public List<SkillMetadata> listAll() { }
@Override public SkillMetadata get(String name) { }
@Override public String readSkillContent(String name) { } }
|
4.3 提示词格式化
SkillPromptFormatter.format(skills) 生成 XML 格式的技能列表:
1 2 3 4 5 6 7 8 9 10 11 12
| <skills> <skill> <name>pptx</name> <description>生成专业演示文稿</description> <basePath>C:\skills\pptx</basePath> </skill> <skill> <name>data-analysis</name> <description>数据分析与可视化</description> <basePath>C:\skills\data-analysis</basePath> </skill> </skills>
|
tool/SkillsTool.java 是 Spring AI 原生 Skills 工具。
5.1 工作原理
SkillsTool 会把 skillsDirectory 下所有的 SKILL.md 文件作为技能元数据注册到 Spring AI:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public static class Builder { private final List<Skill> skills = new ArrayList<>();
public Builder addSkillsDirectory(String skillsRootDirectory) { return addSkillsDirectories(List.of(skillsRootDirectory)); }
public ToolCallback build() { String skillsXml = this.skills.stream() .map(Skill::toXml) .collect(Collectors.joining("\n"));
return FunctionToolCallback.builder("Skill", new SkillsFunction(toSkillsMap(this.skills))) .description(this.toolDescriptionTemplate.formatted(skillsXml)) .inputType(SkillsInput.class) .build(); } }
|
5.2 工具描述模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| 在当前会话中加载一个技能(Skill)。本工具的唯一作用是:传入技能名称,获取该技能的完整提示词和工作目录。
<什么是技能> 技能是一段专业的提示词,包含特定领域的知识、工作流程和操作指令。 每个技能通常还附带参考文件、模板、脚本等资源,存放在技能工作目录中。 </什么是技能>
<技能的完整使用流程> 第一步 — 判断是否需要技能: 当用户要求完成某项任务时,先检查下方 <可用技能列表> 中是否有匹配的技能。 如果有匹配的技能,进入第二步;如果没有,直接用你自身能力回答即可。
第二步 — 通过本工具加载技能: 调用本工具,传入技能的 name 字段值(仅传名称,不含任何参数)。 调用后你会收到:技能工作目录路径 + 技能的完整提示词内容。
第三步 — 阅读并理解技能提示词: 仔细阅读技能返回的完整提示词,理解该技能的工作流程和要求。
第四步 — 按技能提示词执行任务: 严格按照技能提示词中的指令和流程来完成任务。 ... </技能的完整使用流程>
<可用技能列表> <skill> <name>pptx</name> <description>...</description> </skill> </可用技能列表>
|
5.3 SKILL.md 格式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| --- name: pptx description: 生成专业演示文稿 ---
# PPT 生成技能
## 工作流程 1. 接收用户需求 2. 选择模板 3. 生成大纲 4. ...
## 参考资源 - 模板列表: templates/ - 示例: examples/
|
SkillsReactAgent 集成了多个工具:
| 工具 | 来源 | 用途 |
|---|
| Tavily MCP | webSearchToolCallbacks | 联网搜索 |
FileContentService | ToolCallbacks.from() | 文件加载 + RAG |
SkillsTool / ReadSkillTool | SkillsTool.builder() | 技能加载 |
FileSystemTools | FileSystemTools.create() | 文件系统操作(read/write/edit/list/glob) |
GrepTool | GrepTool.create() | 内容搜索 |
BashTool | BashTool.create() | Shell 执行 |
详见 13 工具集
七、上下文压缩集成
SkillsReactAgent 通过 ContextPolicy 启用双层压缩(详见 12 上下文压缩):
1
| .contextPolicy(ContextPolicy.defaults())
|
双层策略:
- Layer 1 (micro_compact):每轮自动执行,替换旧工具结果和长参数为占位符
- Layer 2 (auto_compact):token 超阈值(默认 60000)时触发,用 LLM 摘要替换所有旧消息
八、事件流输出
SkillsReactAgent 输出 6 类事件(相比其他 Agent 增加了 ToolStart/ToolEnd):
1 2 3 4 5 6 7 8 9 10
| public class AgentStreamEvent { public enum Type { THINKING, TEXT, TOOL_START, TOOL_END, ERROR, COMPLETE } }
|
九、重试机制
1 2 3 4 5 6
| private static final long RETRY_INTERVAL_MS = 10000;
public SkillsReactAgent(..., int maxRetries, ...) { this.maxRetries = maxRetries; }
|
工具调用失败时会自动重试最多 maxRetries 次。
十、执行流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| 用户问题 ↓ SkillsReactAgent.execute(conversationId, query, fileId) ↓ initTimers / registerTask / saveQuestion ↓ 构造 messages(System + History + Question) ↓ ┌──── ReAct 循环 (最多 maxRounds 轮) ────┐ │ 1. chatClient.stream() │ │ 2. processChunk() │ │ - ToolCall? → 记录 toolName │ │ - Text? → ThinkTagParser 解析 │ │ 3. finishRound() │ │ 4. executeToolCalls() │ │ - 失败则重试 maxRetries 次 │ │ 5. 上下文压缩(每轮) │ │ - microCompact(始终执行) │ │ - autoCompact(超阈值时执行) │ │ 6. scheduleRound() │ └────────────────────────────────────┘ ↓ doFinally(): - saveSessionResult() - taskManager.stopTask()
|
十一、典型请求/响应
请求:
1
| GET /agent/skills/stream?query=帮我查找当前目录的Java文件&conversationId=skills-001
|
SSE 响应(简化):
1 2 3 4 5 6 7 8 9
| data: {"type":"thinking","content":"用户想查找Java文件..."}
data: {"type":"toolStart","name":"glob_files","args":{"pattern":"**/*.java"}}
data: {"type":"toolEnd","name":"glob_files","result":"src/App.java\nsrc/Main.java\n..."}
data: {"type":"text","content":"找到以下Java文件:..."}
data: {"type":"recommend","content":"[\"统计代码行数\",\"运行单元测试\"]","count":2}
|
十二、Skills 目录配置
application.yml:
1 2
| skills: directory: C:\Users\Lenovo\.claude\skills
|
目录结构:
1 2 3 4 5 6 7 8 9 10
| skills/ ├── pptx/ │ ├── SKILL.md │ ├── templates/ │ └── examples/ ├── data-analysis/ │ ├── SKILL.md │ └── scripts/ └── web-search/ └── SKILL.md
|
每个子目录的 SKILL.md 自动注册为一个技能。
十三、对比其他 Agent
| 维度 | WebSearch | File | PlanExecute | PPT | Skills |
|---|
| 工具数量 | 1 | 1 | 1+ | 1+ | 6+ |
| 循环轮数 | 5 | 5 | 3 | 状态机 | 10 |
| 上下文压缩 | ❌ | ❌ | ✅ | ❌ | ✅ |
| 事件类型 | 5 | 5 | 5 | 5 | 6 |
| 适用场景 | 实时问答 | 文档问答 | 深度研究 | PPT生成 | 通用任务 |