引言
Agent Skills 旨在将模块化、可复用的能力注入到智能体中。本文聚焦于如何在 Spring AI 环境下快速集成 Skills,并解析其背后的实现机制。
环境准备
Maven 依赖
官方文档建议 Spring AI 版本不低于 2.0.0-M2。以下是项目所需的依赖配置:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.0.2</version>
<relativePath/>
</parent>
<properties>
<java.version>21</java.version>
<spring-ai.version>2.0.0-M2</spring-ai.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
<!-- 引入社区实现的 skills 工具 -->
<dependency>
<groupId>org.springaicommunity</groupId>
<artifactId>spring-ai-agent-utils</artifactId>
<version>0.4.2</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
注:实测 Spring Boot 3.5.10、JDK 17 配合 Spring AI 1.1.2 也能运行,具体视环境稳定性而定。
配置文件
server:
port: 8080
spring:
application:
name: pocketmind-server
ai:
chat:
client:
observations:
log-prompt: true
log-completion: true
openai:
api-key: xxxx # 替换为你的 API Key
base-url: xxxx # 替换为你的 Base URL,无需 /v1
chat:
options:
model: deepseek-chat # 替换为你使用的模型名称
示例采用 OpenAI 兼容接口,若需适配 Anthropic 等其他厂商,请参照对应文档调整配置。
定义 Skill
在根目录下创建技能目录,结构如下:
my-skill/
├── SKILL.md # 必需:指令 + 元数据
├── scripts/ # 可选:可执行代码
├── references/ # 可选:文档
└── assets/ # 可选:模板、资源
SKILL.md 文件必须包含 FrontMatter(元信息)和正文内容。FrontMatter 用于描述技能名称和用途,正文则是具体的 Prompt 指令。
---
name: code-reviewer
description: Reviews Java code for best practices, security issues, and Spring Framework conventions. Use when user asks to review, analyze, or audit code
---
# Code Reviewer
## Instructions
When reviewing code:
1. Check **for** security vulnerabilities (SQL injection, XSS, etc.)
2. Verify Spring Boot best practices (proper use of @Service, @Repository, etc.)
3. Look **for** potential null pointer exceptions
4. Suggest improvements **for** readability and maintainability
5. Provide specific line-by-line feedback with code examples
接入实现
Controller 配置
通过 SkillsTool 注册技能目录,并配合 ChatClient 使用。
import org.springaicommunity.agent.tools.FileSystemTools;
import org.springaicommunity.agent.tools.ShellTools;
import org.springaicommunity.agent.tools.SkillsTool;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/demo")
public class SkillController {
private final ChatClient chatClient;
public SkillController(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder
.defaultToolCallbacks(SkillsTool.builder()
.addSkillsDirectory(".claude/skills")
// 也可以使用下面这个
//.addSkillsResource(resourceLoader.getResource("classpath:.claude/skills"))
.build())
.defaultTools(FileSystemTools.builder().build())
.defaultTools(ShellTools.builder().build())
.defaultToolContext(Map.of("foo", "bar")) // 添加工具上下文,防止构建报错
.build();
}
/**
* 测试 skill 流程
*/
@PostMapping("/skill")
public String chat(@RequestBody String message) {
return chatClient.prompt().user(message).call().content();
}
}
这里的关键在于 ChatClient.Builder 的构建过程。defaultToolCallbacks 负责加载已组装好的工具包(含逻辑与 Schema),defaultTools 注册系统级工具以支持动态发现,而 defaultToolContext 则提供了必要的上下文参数,避免框架初始化时的潜在错误。请求链路通过 .user() 加载提示词,由框架内部处理 LLM 调用,最后通过 .content() 获取结果。
源码解析
目录设置
SkillsTool 的 Builder 模式支持添加多个技能路径。
public static class Builder {
private List<Skill> skills = new ArrayList<>();
private String toolDescriptionTemplate = TOOL_DESCRIPTION_TEMPLATE;
protected Builder() {}
public Builder addSkillsResources(List<Resource> skillsRootPaths) { ... }
public Builder addSkillsDirectory(String skillsRootDirectory) { ... }
// ...
}
toolDescriptionTemplate 用于定义技能描述的展示格式。
加载元数据
加载器会递归查找指定文件夹下的 SKILL.md 文件。
private static List<Skill> skills(String rootDirectory) throws IOException {
Path rootPath = Paths.get(rootDirectory);
if (!Files.exists(rootPath)) { ... }
List<Skill> skillFiles = new ArrayList<>();
try (Stream<Path> paths = Files.walk(rootPath)) {
paths.filter(Files::isRegularFile)
.filter(path -> path.getFileName().toString().equals("SKILL.md"))
.forEach(path -> {
try {
String markdown = Files.readString(path, StandardCharsets.UTF_8);
MarkdownParser parser = new MarkdownParser(markdown);
skillFiles.add(new Skill(path, parser.getFrontMatter(), parser.getContent()));
} catch (IOException e) { ... }
});
}
return skillFiles;
}
解析过程分为两部分:FrontMatter 提取技能名称和描述,Content 提取具体的 Prompt 指令。
工具调用
当 AI 决定调用特定技能时,触发 SkillsFunction。
public static class SkillsFunction implements Function<SkillsInput, String> {
private Map<String, Skill> skillsMap;
// ...
@Override
public String apply(SkillsInput input) {
Skill skill = this.skillsMap.get(input.command());
if (skill != null) {
var skillBaseDirectory = skill.path().getParent().toString();
return "Base directory for this skill: %s\n\n%s".formatted(skillBaseDirectory, skill.content());
}
return "Skill not found: " + input.command();
}
}
返回的内容包含基础路径和技能正文,AI 据此读取操作指南或脚本。至此,基于 SKILL.md 的技能调用机制便完整实现了。

