08 - Skills 智能体

一、定位

SkillsReactAgent通用型智能体,将 Skills(技能)搜索文件文件系统BashGrep 多种能力按需挂载为工具,让 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() {
// 1. 构建 SkillManager(加载技能)
SkillConfig skillConfig = SkillConfig.builder()
.addDirectory(skillsDirectory)
.build();
SkillManager skillManager = SkillManager.create(skillConfig);

// 2. 将技能列表格式化为系统提示词
String skillsPrompt = skillManager.formatPrompt();

// 3. 创建 ReadSkillTool 作为独立工具
ToolCallback readSkillTool = ReadSkillTool.create(skillManager.getRegistry());

// 4. 合并工具
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 个(Skill1 个(read_skill
技能描述位置工具描述(Tool description)系统提示词
加载方式LLM 调用工具加载LLM 调用工具加载
灵活性

四、SkillManager 技能管理

agent/skills/manual/SkillManager.java 是手动模式下的技能管理器。

4.1 数据模型

SkillMetadataagent/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; // YAML frontmatter
private String content; // Markdown 内容
// ...
}

4.2 注册中心

FileSystemSkillRegistryregistry/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() {
// 扫描 skillsDirectory 下所有 SKILL.md
// 解析 frontmatter + content
}

@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>

五、SkillsTool 工具

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() {
// 1. 收集所有 skills
String skillsXml = this.skills.stream()
.map(Skill::toXml)
.collect(Collectors.joining("\n"));

// 2. 注册为名为 "Skill" 的工具
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/

六、ToolsToolSet 工具集

SkillsReactAgent 集成了多个工具:

工具来源用途
Tavily MCPwebSearchToolCallbacks联网搜索
FileContentServiceToolCallbacks.from()文件加载 + RAG
SkillsTool / ReadSkillToolSkillsTool.builder()技能加载
FileSystemToolsFileSystemTools.create()文件系统操作(read/write/edit/list/glob)
GrepToolGrepTool.create()内容搜索
BashToolBashTool.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;  // 重试间隔 10s

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

维度WebSearchFilePlanExecutePPTSkills
工具数量111+1+6+
循环轮数553状态机10
上下文压缩
事件类型55556
适用场景实时问答文档问答深度研究PPT生成通用任务