|
| 1 | +--- |
| 2 | +title: Skills 技能 |
| 3 | +description: 在 Spring AI Alibaba 中使用 Agent Skills 实现技能的渐进式披露,扩展智能体能力。 |
| 4 | +keywords: [Skills, Agent Skills, 技能, 渐进式披露, SkillRegistry, read_skill, SkillsAgentHook] |
| 5 | +--- |
| 6 | + |
| 7 | +# Skills 技能 |
| 8 | + |
| 9 | +Skills 是可复用的指令与上下文包,智能体在相关任务时会自动发现并使用。通过 **SkillRegistry** 管理技能、**SkillsAgentHook** 注册 `read_skill` 工具并注入技能列表到系统提示,模型在需要时调用 `read_skill(skill_name)` 按需加载完整内容。 |
| 10 | + |
| 11 | +## 核心概念 |
| 12 | + |
| 13 | +### 渐进式披露 |
| 14 | + |
| 15 | +系统提示中先只注入技能列表(name、description、skillPath);模型判断需要某技能时调用 `read_skill(skill_name)` 加载完整 SKILL.md;再按需访问技能目录下的资源或使用与该技能绑定的工具。 |
| 16 | + |
| 17 | +### Skill 目录结构 |
| 18 | + |
| 19 | +每个技能一个子目录,必须包含 `SKILL.md`: |
| 20 | + |
| 21 | +```text |
| 22 | +skill-name/ |
| 23 | +├── SKILL.md # 必需 |
| 24 | +├── references/ # 可选 |
| 25 | +├── examples/ |
| 26 | +└── scripts/ |
| 27 | +``` |
| 28 | + |
| 29 | +### SKILL.md 格式规范 |
| 30 | + |
| 31 | +```yaml |
| 32 | +--- |
| 33 | +name: skill-name |
| 34 | +description: This skill should be used when... |
| 35 | +--- |
| 36 | + |
| 37 | +# 技能名称 |
| 38 | +正文:功能说明、使用方法、可用资源列表等。 |
| 39 | +``` |
| 40 | + |
| 41 | +**必需字段**:`name`(建议小写字母、数字、连字符,最长 64 字符)、`description`(超长会被截断)。 |
| 42 | + |
| 43 | +--- |
| 44 | + |
| 45 | +## 在 Agent 中使用 Skills |
| 46 | + |
| 47 | +### 使用 FileSystemSkillRegistry |
| 48 | + |
| 49 | +智能体支持从本地文件系统中加载 skills 技能,以下示例假设 `skills` 在进程工作目录,如: |
| 50 | + |
| 51 | +```text |
| 52 | +skills/ |
| 53 | +├── pdf-extractor/ |
| 54 | + ├── SKILL.md |
| 55 | + ├── references/ |
| 56 | + └── scripts/ |
| 57 | +``` |
| 58 | + |
| 59 | +<Code language="java" title="FileSystemSkillRegistry + SkillsAgentHook"> |
| 60 | +{`SkillRegistry registry = FileSystemSkillRegistry.builder() |
| 61 | + .projectSkillsDirectory(System.getProperty("user.dir") + "/skills") |
| 62 | + .build(); |
| 63 | + |
| 64 | +SkillsAgentHook hook = SkillsAgentHook.builder() |
| 65 | + .skillRegistry(registry) |
| 66 | + .build(); |
| 67 | + |
| 68 | +ReactAgent agent = ReactAgent.builder() |
| 69 | + .name("skills-agent") |
| 70 | + .model(chatModel) |
| 71 | + .saver(new MemorySaver()) |
| 72 | + .hooks(List.of(hook)) |
| 73 | + .build(); |
| 74 | + |
| 75 | +agent.call("请介绍你有哪些技能");`} |
| 76 | +</Code> |
| 77 | + |
| 78 | +目录配置:`userSkillsDirectory(String|Resource)`、`projectSkillsDirectory(String|Resource)`;不设置时用户级默认 `~/saa/skills`,项目级默认 `./skills`,同名技能“项目级别”覆盖“用户级别”。 |
| 79 | + |
| 80 | +### 使用 ClasspathSkillRegistry |
| 81 | + |
| 82 | +技能放在 `src/main/resources/skills` 或随 JAR 打包。可选 `.basePath("/tmp")` 指定 JAR 内资源复制到的目录(默认 `/tmp`)。 |
| 83 | + |
| 84 | +<Code language="java" title="ClasspathSkillRegistry"> |
| 85 | +{`SkillRegistry registry = ClasspathSkillRegistry.builder() |
| 86 | + .classpathPath("skills") |
| 87 | + .build(); |
| 88 | + |
| 89 | +SkillsAgentHook hook = SkillsAgentHook.builder() |
| 90 | + .skillRegistry(registry) |
| 91 | + .build(); |
| 92 | + |
| 93 | +ReactAgent agent = ReactAgent.builder() |
| 94 | + .name("skills-agent") |
| 95 | + .model(chatModel) |
| 96 | + .hooks(List.of(hook)) |
| 97 | + .build();`} |
| 98 | +</Code> |
| 99 | + |
| 100 | +### 完整集成示例(Skills + Python + Shell) |
| 101 | + |
| 102 | +技能常需配合脚本执行(如技能目录下的 Python 脚本)和 Shell 命令。下面示例使用 **ClasspathSkillRegistry** 加载技能、**SkillsAgentHook** 提供 `read_skill`、**ShellToolAgentHook** 提供 Shell 工具、**PythonTool** 提供 Python 执行能力,Agent 可根据技能说明读取并处理技能目录下的文件。 |
| 103 | + |
| 104 | +<Code language="java" title="Skills + Python + Shell 完整集成"> |
| 105 | +{`import com.alibaba.cloud.ai.graph.agent.ReactAgent; |
| 106 | +import com.alibaba.cloud.ai.graph.agent.hook.skills.SkillsAgentHook; |
| 107 | +import com.alibaba.cloud.ai.graph.agent.hook.shelltool.ShellToolAgentHook; |
| 108 | +import com.alibaba.cloud.ai.graph.agent.tools.PythonTool; |
| 109 | +import com.alibaba.cloud.ai.graph.agent.tools.ShellTool2; |
| 110 | +import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver; |
| 111 | +import com.alibaba.cloud.ai.graph.skills.registry.classpath.ClasspathSkillRegistry; |
| 112 | +import com.alibaba.cloud.ai.graph.skills.registry.SkillRegistry; |
| 113 | + |
| 114 | +// 1. 技能注册表:从 classpath:skills 加载(如 src/main/resources/skills/) |
| 115 | +SkillRegistry registry = ClasspathSkillRegistry.builder() |
| 116 | + .classpathPath("skills") |
| 117 | + .build(); |
| 118 | + |
| 119 | +// 2. Skills Hook:注册 read_skill 工具并注入技能列表到系统提示 |
| 120 | +SkillsAgentHook skillsHook = SkillsAgentHook.builder() |
| 121 | + .skillRegistry(registry) |
| 122 | + .build(); |
| 123 | + |
| 124 | +// 3. Shell Hook:提供 Shell 命令执行(工作目录可指定,如当前工程目录) |
| 125 | +ShellToolAgentHook shellHook = ShellToolAgentHook.builder() |
| 126 | + .shellTool2(ShellTool2.builder(System.getProperty("user.dir")).build()) |
| 127 | + .build(); |
| 128 | + |
| 129 | +// 4. 构建 Agent:同时挂载 Skills Hook、Shell Hook 和 Python 工具 |
| 130 | +ReactAgent agent = ReactAgent.builder() |
| 131 | + .name("skills-integration-agent") |
| 132 | + .model(chatModel) |
| 133 | + .saver(new MemorySaver()) |
| 134 | + .tools(PythonTool.createPythonToolCallback(PythonTool.DESCRIPTION)) |
| 135 | + .hooks(List.of(skillsHook, shellHook)) |
| 136 | + .enableLogging(true) |
| 137 | + .build(); |
| 138 | + |
| 139 | +// 5. 调用示例:用户请求处理技能目录下的文件时,模型可先 read_skill 再按技能说明调用 Python/Shell |
| 140 | +String skillFilePath = "/path/to/skills/pdf-extractor/saa-roadmap.pdf"; // 实际路径来自技能目录或 hook.listSkills() |
| 141 | +AssistantMessage response = agent.call("请从 " + skillFilePath + " 文件中提取关键信息。");`} |
| 142 | +</Code> |
| 143 | + |
| 144 | +- **SkillRegistry**:`FileSystemSkillRegistry` 用 `projectSkillsDirectory(path)` 或 `ClassPathResource("skills")`;`ClasspathSkillRegistry` 用 `classpathPath("skills")`。 |
| 145 | +- **ShellTool2**:`ShellTool2.builder(workDir).build()`,`workDir` 为 Shell 执行的工作目录(如 `System.getProperty("user.dir")`)。 |
| 146 | +- **PythonTool**:`PythonTool.createPythonToolCallback(PythonTool.DESCRIPTION)` 即够用,如需自定义描述可传第二个参数。 |
| 147 | +- 技能列表中会包含每个技能的 `skillPath`,模型可用该路径拼出技能目录下文件的绝对路径并交给 Python/Shell 处理。 |
| 148 | + |
| 149 | +--- |
| 150 | + |
| 151 | + |
| 152 | +## 高级用法 |
| 153 | + |
| 154 | +### 渐进式工具 Tool 披露 |
| 155 | + |
| 156 | +通过将工具与 Skill 技能名绑定,可以做到工具跟随 Skill 实现渐进式披露:仅当模型对该技能调用了 `read_skill` 后,对应工具才会加入当次请求,实现按需暴露。激活后该技能的工具在会话后续轮次中仍可用。 |
| 157 | + |
| 158 | +<Code language="java" title="groupedTools 绑定工具到技能"> |
| 159 | +{`Map<String, List<ToolCallback>> groupedTools = Map.of( |
| 160 | + "my-skill", // 与 SKILL.md 的 name 一致,如 'pdf-extractor' |
| 161 | + List.of(myTool) |
| 162 | +); |
| 163 | + |
| 164 | +SkillsAgentHook hook = SkillsAgentHook.builder() |
| 165 | + .skillRegistry(registry) |
| 166 | + .groupedTools(groupedTools) |
| 167 | + .build();`} |
| 168 | +</Code> |
| 169 | + |
| 170 | +### 生产环境配置 |
| 171 | + |
| 172 | +#### 自动重载技能 |
| 173 | + |
| 174 | +<Code language="java" title="启用技能自动重载"> |
| 175 | +{`SkillsAgentHook hook = SkillsAgentHook.builder() |
| 176 | + .skillRegistry(registry) |
| 177 | + .autoReload(true) |
| 178 | + .build();`} |
| 179 | +</Code> |
| 180 | + |
| 181 | +每次 Agent 执行前会调用 `registry.reload()`(若实现支持;不支持则抛 `UnsupportedOperationException`,Hook 会捕获并打 debug 日志)。 |
| 182 | + |
| 183 | +> 注意,每次 Agent 执行可能包含多次模型推理,`registry.reload()` 仅会在第一次推理时执行并加载最新的 skills,这样能保证同一次 Agent 执行时行为的连续性。 |
| 184 | +
|
| 185 | +#### 用户级与项目级目录 |
| 186 | + |
| 187 | +<Code language="java" title="用户级与项目级技能目录"> |
| 188 | +{`SkillRegistry registry = FileSystemSkillRegistry.builder() |
| 189 | + .userSkillsDirectory("/home/user/saa/skills") |
| 190 | + .projectSkillsDirectory("/app/project/skills") |
| 191 | + .build();`} |
| 192 | +</Code> |
| 193 | + |
| 194 | +同名技能项目覆盖用户。 |
| 195 | + |
| 196 | +#### 自定义系统提示模板 |
| 197 | + |
| 198 | +SAA 框架内置了 Skill Prompt 模板,用来引导实现 Skill 的渐进式披露。用户可结合自己系统的 Skill 组织方式定制 Prompt 模板。 |
| 199 | + |
| 200 | +<Code language="java" title="自定义技能系统提示模板"> |
| 201 | +{`SystemPromptTemplate customTemplate = SystemPromptTemplate.builder() |
| 202 | + .template("## 可用技能\\n{skills_list}\\n\\n## 加载说明\\n{skills_load_instructions}") |
| 203 | + .build(); |
| 204 | + |
| 205 | +FileSystemSkillRegistry registry = FileSystemSkillRegistry.builder() |
| 206 | + .projectSkillsDirectory("./skills") |
| 207 | + .systemPromptTemplate(customTemplate) |
| 208 | + .build();`} |
| 209 | +</Code> |
| 210 | + |
| 211 | +模板变量:`{skills_list}`、`{skills_load_instructions}`。 |
| 212 | + |
| 213 | +### 拓展 SkillRegistry 实现 |
| 214 | + |
| 215 | +实现 `SkillRegistry` 接口(`get`、`listAll`、`contains`、`size`、`readSkillContent`、`getSkillLoadInstructions`、`getRegistryType`、`getSystemPromptTemplate`,可选 `reload()`)即可接入现有 Skills 体系。`SkillMetadata` 需包含 `name`、`description`、`skillPath`(及可选 `source`)。可参考 `AbstractSkillRegistry`、`FileSystemSkillRegistry`、`ClasspathSkillRegistry`。 |
| 216 | + |
| 217 | +--- |
| 218 | + |
| 219 | +## 在 Graph 中使用 Skills |
| 220 | + |
| 221 | +除在 **ReactAgent** 上通过 **SkillsAgentHook** 使用 Skills 外,在基于 **Graph** 或 **ChatClient** 的链路中,可通过 **ChatClient** 配合 **SkillPromptAugmentAdvisor**(`spring-ai-alibaba-graph-core`)将技能列表注入系统提示,实现渐进式披露的「技能发现」部分。 |
| 222 | + |
| 223 | +### 使用 ChatClient + SkillPromptAugmentAdvisor |
| 224 | + |
| 225 | +`SkillPromptAugmentAdvisor` 是 Spring AI 的 `Advisor`,在每次请求的 `before` 阶段将技能元数据(name、description、skillPath)注入系统提示,使模型知晓可用技能及加载说明;模型需要完整 SKILL.md 时,需配合 `read_skill` 工具(可由 SkillsAgentHook 提供或自行注册)。 |
| 226 | + |
| 227 | +<Code language="java" title="ChatClient + SkillPromptAugmentAdvisor"> |
| 228 | +{`import org.springframework.ai.chat.client.ChatClient; |
| 229 | +import com.alibaba.cloud.ai.graph.advisors.SkillPromptAugmentAdvisor; |
| 230 | + |
| 231 | +// 方式一:指定技能目录(字符串路径),Advisor 内部创建 FileSystemSkillRegistry |
| 232 | +SkillPromptAugmentAdvisor skillAdvisor = SkillPromptAugmentAdvisor.builder() |
| 233 | + .projectSkillsDirectory("./skills") // 或绝对路径 /path/to/skills |
| 234 | + // .userSkillsDirectory("~/saa/skills") // 可选,默认 ~/saa/skills |
| 235 | + .lazyLoad(false) // 可选,true 则首次请求时再加载技能 |
| 236 | + .build(); |
| 237 | + |
| 238 | +ChatClient chatClient = ChatClient.builder(chatModel) |
| 239 | + .defaultAdvisors(skillAdvisor) |
| 240 | + .build(); |
| 241 | + |
| 242 | +// 调用时系统提示中会包含可用技能列表 |
| 243 | +String response = chatClient.prompt() |
| 244 | + .user("请介绍你有哪些技能") |
| 245 | + .call() |
| 246 | + .content();`} |
| 247 | +</Code> |
| 248 | + |
| 249 | +<Code language="java" title="使用已有 SkillRegistry 构建 SkillPromptAugmentAdvisor"> |
| 250 | +{`import com.alibaba.cloud.ai.graph.skills.registry.SkillRegistry; |
| 251 | +import com.alibaba.cloud.ai.graph.skills.registry.filesystem.FileSystemSkillRegistry; |
| 252 | + |
| 253 | +SkillRegistry registry = FileSystemSkillRegistry.builder() |
| 254 | + .projectSkillsDirectory("./skills") |
| 255 | + .build(); |
| 256 | + |
| 257 | +SkillPromptAugmentAdvisor skillAdvisor = SkillPromptAugmentAdvisor.builder() |
| 258 | + .skillRegistry(registry) |
| 259 | + .build(); |
| 260 | + |
| 261 | +ChatClient chatClient = ChatClient.builder(chatModel) |
| 262 | + .defaultAdvisors(skillAdvisor) |
| 263 | + .build();`} |
| 264 | +</Code> |
| 265 | + |
| 266 | +**说明**: |
| 267 | + |
| 268 | +- **SkillPromptAugmentAdvisor** 仅负责在系统提示中注入技能列表与加载说明,不注册 `read_skill` 工具。若需模型按需读取完整 SKILL.md,请在 ChatClient/Graph 中额外注册 `read_skill`(例如使用带 SkillsAgentHook 的 Agent 节点,或单独将 `ReadSkillTool` 注册为工具)。 |
| 269 | +- **Graph 中的 Agent 节点**:若节点内部使用 `ChatClient`,可在构建该 `ChatClient` 时加入 `SkillPromptAugmentAdvisor`;若节点使用 ReactAgent,则直接使用 **SkillsAgentHook**(会同时注入技能列表并注册 `read_skill`)。 |
| 270 | +- 技能通常需配合脚本执行(如技能目录下的 Python 脚本)和 Shell 命令才能在生产环境中正常使用。 |
| 271 | + |
| 272 | +--- |
| 273 | + |
| 274 | +## 最佳实践与性能建议 |
| 275 | + |
| 276 | +- **控制 SKILL.md 大小**:单文件建议约 1.5k–2k tokens,长内容放 `references/` 并在正文中列路径。 |
| 277 | +- **技能名称一致**:`name`、`read_skill` 参数、`groupedTools` 的 key 保持一致。 |
| 278 | +- **按需使用 groupedTools**:仅需「随技能激活」的工具用 groupedTools,其余用 Agent 的 `.tools()` 即可。 |
| 279 | +- **常用 API**:`hook.listSkills()`、`hook.hasSkill(name)`、`hook.getSkillCount()`;`registry.reload()`(ClasspathSkillRegistry 支持)。 |
0 commit comments