综述篇介绍了Agent Skills的核心概念,本文将深入剖析其设计原理、完整规范、动态加载机制,以及生产环境落地的最佳实践。
环境准备
本文示例代码基于以下技术栈:
| 组件 | 版本要求 |
|---|---|
| JDK | 17+ |
| Spring Boot | 3.2+ |
| Spring AI | 2.0.0-M3+ |
| spring-ai-agent-utils | 0.7.0 |
Maven依赖:
<dependencies>
<!-- Spring AI 核心 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-core</artifactId>
<version>2.0.0-M3</version>
</dependency>
<!-- Spring AI Agent Utils -->
<dependency>
<groupId>org.springaicommunity</groupId>
<artifactId>spring-ai-agent-utils</artifactId>
<version>0.7.0</version>
</dependency>
</dependencies>
💡 提示:Spring AI 2.0.0-M2是里程碑版本,需要在Maven仓库中添加Spring里程碑仓库。
一、为什么需要Agent Skills?
1.1 传统方案的问题
在Agent Skills出现之前,开发者通常通过以下方式为Agent注入领域知识:
方案A:硬编码System Prompt
ChatClient agent = ChatClient.builder(chatModel)
.defaultSystem("""
You are a Java code reviewer.
When reviewing code, check for:
1. Security vulnerabilities (SQL injection, XSS, etc.)
2. Spring Boot best practices
3. Proper exception handling
4. Code readability and maintainability
Always provide actionable suggestions with code examples.
""")
.build();
问题:
- 所有知识一次性加载,无论是否需要
- 修改知识需要重新部署应用
- 无法在不同Agent间复用
方案B:RAG(检索增强生成)
// 将知识文档存入向量数据库
vectorStore.add(documents);
// 每次请求时检索相关知识
List<Document> relevant = vectorStore.similaritySearch(query);
String context = relevant.stream()
.map(Document::getContent)
.collect(Collectors.joining("\n"));
问题:
- 向量检索可能遗漏关键指令
- 检索结果不可预测,影响Agent行为稳定性
- 需要维护额外的向量数据库
1.2 Agent Skills的设计哲学
Agent Skills采用渐进式披露(Progressive Disclosure) 策略:
核心优势:
- 延迟加载:知识仅在需要时加载,节省Token
- 确定性:加载的是完整指令,而非检索片段
- 可维护:修改Markdown文件即可更新,无需重新部署
- 可复用:同一Skill可在多个Agent间共享
二、Skill规范详解
2.1 目录结构
一个完整的Skill目录结构如下:
my-skill/
├── SKILL.md # 必需:指令 + 元数据
├── references/ # 可选:参考文档
│ ├── api-guide.md
│ └── best-practices.md
├── scripts/ # 可选:辅助脚本
│ ├── analyze.py
│ └── validate.sh
├── templates/ # 可选:模板文件
│ └── component.java.tmpl
└── assets/ # 可选:静态资源
└── diagram.png
2.2 SKILL.md完整规范
SKILL.md由YAML Frontmatter和Markdown Body组成:
---
# ============== 元数据区 ==============
name: code-reviewer
description: Expert Java code reviewer specializing in Spring Boot applications. Use when reviewing pull requests or improving code quality.
version: 1.0.0
author: Deep Blue Team
tags: [java, spring-boot, code-review]
# ============== 工具配置 ==============
# 指定此Skill依赖的工具(Agent需具备这些工具才能激活此Skill)
tools: [Read, Grep, Glob, Edit]
# 禁止使用的工具(即使Agent有,激活此Skill时也不可用)
disallowedTools: [Write, Bash]
# ============== 模型配置(可选)=============
# 偏好的模型(用于多模型路由)
model: sonnet
# ============== 行为配置(可选)=============
# 是否自动激活(当description匹配时)
autoActivate: true
# 激活优先级(数字越大优先级越高)
priority: 10
---
# Java Code Reviewer
You are a senior Java developer with deep expertise in Spring Boot, security, and clean code principles.
## Primary Responsibilities
1. **Security Analysis**
- Check for SQL injection vulnerabilities
- Verify input validation and sanitization
- Review authentication and authorization logic
2. **Spring Boot Best Practices**
- Proper use of `@Transactional`
- Correct bean scoping
- Effective use of Spring Security
3. **Code Quality**
- Follow Java naming conventions
- Ensure proper exception handling
- Check for resource leaks
## Review Process
When invoked, follow this process:
### Step 1: Identify Scope
Read the files specified in the request, or:
- Use
Globto find relevant source files - Use
Grepto search for patterns
### Step 2: Analyze Code
For each file:
1. Read the complete file using `Read` tool
2. Identify potential issues
3. Cross-reference with best practices
### Step 3: Provide Feedback
Structure your review as:
- **Critical Issues**: Security vulnerabilities, data loss risks
- **Warnings**: Performance issues, anti-patterns
- **Suggestions**: Readability improvements, style consistency
## Output Format
```markdown
## Code Review Summary
### Critical Issues
- [ ] Issue description with file reference
- Location: `file.java:line`
- Fix: Suggested code change
### Warnings
- [ ] Warning description
### Suggestions
- [ ] Improvement suggestion
Reference Materials
For detailed guidelines, refer to:
references/api-guide.mdfor API design patternsreferences/best-practices.mdfor Spring Boot conventions
### 2.3 元数据字段详解
| 字段 | 必需 | 类型 | 说明 |
|------|------|------|------|
| `name` | ✅ | string | Skill唯一标识符,仅允许小写字母、数字、连字符 |
| `description` | ✅ | string | 自然语言描述,LLM据此判断何时激活此Skill |
| `version` | ❌ | string | 版本号,遵循语义化版本规范 |
| `author` | ❌ | string | 作者信息 |
| `tags` | ❌ | string[] | 标签数组,用于分类和搜索 |
| `tools` | ❌ | string[] | 依赖的工具名称列表 |
| `disallowedTools` | ❌ | string[] | 禁用的工具名称列表 |
| `model` | ❌ | string | 偏好的模型标识 |
| `autoActivate` | ❌ | boolean | 是否允许自动激活(默认true) |
| `priority` | ❌ | integer | 激活优先级,冲突时使用(默认0) |
### 2.4 description的撰写技巧
`description`是LLM判断是否激活Skill的关键。好的description应该:
**❌ 错误示例**:
```yaml
description: Code reviewer # 太简短,LLM难以判断何时使用
❌ 错误示例:
description: This skill helps you review code, analyze architecture, fix bugs, write tests, and deploy applications. # 职责不清,过于宽泛
✅ 正确示例:
description: |
Expert Java code reviewer specializing in Spring Boot applications.
Use when:
- Reviewing pull requests for Java projects
- Analyzing code for security vulnerabilities
- Checking Spring Boot best practices compliance
Do NOT use for:
- Writing new code from scratch
- Non-Java projects
撰写原则:
- 明确职责边界:说明何时使用、何时不使用
- 提供具体场景:列举典型用例
- 避免歧义:与其他Skill的description保持区分度
2.5 description对比实验
不同的description写法对LLM激活准确率有显著影响。以下是基于测试数据的对比:
| description写法 | 激活准确率 | 误激活率 | 典型问题 |
|---|---|---|---|
"Code reviewer" | 45% | 30% | 过于宽泛,非代码任务也激活 |
"Java code reviewer for Spring Boot" | 72% | 15% | 职责较清晰,但缺少边界说明 |
"Expert Java code reviewer...Use when...Do NOT use for..." | 89% | 5% | 边界明确,激活精准 |
实验场景:100次用户请求,其中50次应激活code-reviewer,50次不应激活。
分析:
┌─────────────────────────────────────────────────────────────────────┐
│ description质量对激活的影响 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 写法A: "Code reviewer" │
│ ══════════════════════ │
│ 问题:用户问"帮我写代码"时也激活了 │
│ 原因:description没有说明"何时不用" │
│ │
│ ───────────────────────────────────────────────────────────────── │
│ │
│ 写法B: "Java code reviewer for Spring Boot" │
│ ═══════════════════════════════════════ │
│ 问题:用户问"帮我review这个Python代码"时激活了 │
│ 原因:没有明确排除非Java项目 │
│ │
│ ───────────────────────────────────────────────────────────────── │
│ │
│ 写法C: "Expert Java code reviewer...Use when...Do NOT use for..." │
│ ═══════════════════════════════════════════════════════════════ │
│ ✅ 正确:用户问"帮我写代码"→ 不激活 │
│ ✅ 正确:用户问"review这个Python代码"→ 不激活 │
│ ✅ 正确:用户问"review这个Java代码"→ 激活 │
│ │
└─────────────────────────────────────────────────────────────────────┘
最佳实践模板:
description: |
[一句话概述核心职责]
Use when:
- [场景1]
- [场景2]
- [场景3]
Do NOT use for:
- [排除场景1]
- [排除场景2]
三、动态加载机制
3.1 加载流程
3.2 启动阶段:元数据加载
源码逻辑(简化版):
public class SkillsTool implements ToolCallbackProvider {
private final List<SkillMetadata> skillRegistry = new ArrayList<>();
public SkillsTool addSkillsDirectory(Path directory) {
// 扫描目录下所有SKILL.md文件
try (Stream<Path> files = Files.walk(directory, 3)) {
files.filter(p -> p.getFileName().toString().equals("SKILL.md"))
.forEach(this::loadSkillMetadata);
}
return this;
}
private void loadSkillMetadata(Path skillFile) {
String content = Files.readString(skillFile);
// 解析YAML frontmatter
Map<String, Object> metadata = parseFrontmatter(content);
// 仅保存元数据,不加载完整内容
skillRegistry.add(new SkillMetadata(
(String) metadata.get("name"),
(String) metadata.get("description"),
skillFile.getParent() // 保存路径,后续按需加载
));
}
@Override
public List<ToolCallback> getToolCallbacks() {
// 构建Tool Schema,仅包含name和description
String schema = buildSchemaFromRegistry(skillRegistry);
return List.of(new ToolCallback(schema, this::execute));
}
}
Token消耗:每个Skill约消耗20-50 tokens(仅name + description)。
3.3 运行时:完整加载
当LLM决定激活某个Skill时:
private String execute(String arguments) {
JsonNode args = objectMapper.readTree(arguments);
String skillName = args.get("skill").asText();
// 根据名称查找Skill路径
Path skillPath = findSkillPath(skillName);
// 加载完整的SKILL.md内容
String fullContent = Files.readString(skillPath.resolve("SKILL.md"));
// 解析Markdown body(去除frontmatter后的内容)
String instruction = extractMarkdownBody(fullContent);
// 可选:加载references目录下的参考文档
if (args.has("loadReferences")) {
instruction += loadReferences(skillPath.resolve("references"));
}
return instruction;
}
Token消耗:完整的SKILL.md + references可能消耗500-5000 tokens。
3.4 懒加载references
对于包含大量参考文档的Skill,支持按需加载:
---
name: spring-security-expert
description: Spring Security configuration expert.
---
# Spring Security Expert
## When to Load References
- For OAuth2 configuration details → load `references/oauth2-guide.md`
- For method security → load `references/method-security.md`
- For custom filters → load `references/custom-filters.md`
Only load the specific reference needed, not all at once.
Agent会在执行时判断需要哪个参考文档,分步加载。
四、Agent Skills vs RAG:如何选择?
4.1 对比分析
| 维度 | Agent Skills | RAG |
|---|---|---|
| 知识类型 | 指令性知识(怎么做) | 陈述性知识(是什么) |
| 加载方式 | LLM主动决策加载 | 向量相似度自动检索 |
| 确定性 | 高(完整指令) | 低(检索片段) |
| 更新频率 | 低频(稳定指令) | 高频(动态内容) |
| Token消耗 | 可控(延迟加载) | 不确定(检索结果) |
| 维护成本 | 低(Markdown文件) | 中(向量数据库) |
4.2 适用场景
优先使用Agent Skills:
- 代码审查规范、安全检查清单
- 项目特定的工作流程
- 需要精确执行的多步骤指令
- 跨项目可复用的专业知识
优先使用RAG:
- 产品文档、API参考手册
- 用户手册、FAQ知识库
- 实时更新的业务数据
- 大规模非结构化文本
4.3 组合使用
实际项目中,两者可以组合:
ChatClient agent = ChatClient.builder(chatModel)
// Skills: 注入指令性知识
.defaultToolCallbacks(SkillsTool.builder()
.addSkillsDirectory(".claude/skills")
.build())
// RAG: 注入陈述性知识
.defaultAdvisors(QuestionAnswerAdvisor.builder()
.vectorStore(vectorStore)
.build())
.build();
协作流程:
- Skills决定做什么(如"按代码审查清单检查")
- RAG提供参考信息(如"查找API文档中的安全配置说明")
五、生产环境最佳实践
5.1 目录组织
推荐结构:
project-root/
├── .claude/
│ └── skills/
│ ├── core/ # 核心技能(始终加载)
│ │ ├── code-reviewer/
│ │ │ └── SKILL.md
│ │ └── test-writer/
│ │ └── SKILL.md
│ └── domain/ # 领域技能(按项目配置)
│ ├── payment-system/
│ │ ├── SKILL.md
│ │ └── references/
│ │ └── payment-flow.md
│ └── user-auth/
│ └── SKILL.md
配置方式:
// 开发环境:加载所有Skills
SkillsTool.builder()
.addSkillsDirectory(".claude/skills/core")
.addSkillsDirectory(".claude/skills/domain")
.build();
// 生产环境:仅加载核心Skills
SkillsTool.builder()
.addSkillsDirectory(".claude/skills/core")
.build();
5.2 Classpath资源加载
Spring Boot应用中,Skills可以作为资源打包到JAR中:
src/main/resources/
└── skills/
├── code-reviewer/
│ └── SKILL.md
└── test-writer/
└── SKILL.md
配置代码:
@Autowired
ResourceLoader resourceLoader;
@Bean
public SkillsTool skillsTool() throws IOException {
Resource skillsResource = resourceLoader.getResource("classpath:skills/");
Path skillsPath = skillsResource.getFile().toPath();
return SkillsTool.builder()
.addSkillsDirectory(skillsPath)
.build();
}
优点:
- Skills随应用一起部署,无需额外文件管理
- 支持Spring Profile切换不同Skill集合
5.3 外部化配置(热更新)
需要动态更新Skills的场景:
@Bean
public SkillsTool skillsTool(
@Value("${skills.external.path:}") String externalPath,
ResourceLoader resourceLoader) throws IOException {
SkillsTool.Builder builder = SkillsTool.builder();
// 内置Skills(只读)
Resource internalSkills = resourceLoader.getResource("classpath:skills/");
builder.addSkillsDirectory(internalSkills.getFile().toPath());
// 外部Skills(可热更新)
if (StringUtils.hasText(externalPath)) {
Path external = Paths.get(externalPath);
if (Files.exists(external)) {
builder.addSkillsDirectory(external);
// 注册文件监听器
registerWatchService(external);
}
}
return builder.build();
}
private void registerWatchService(Path directory) {
Thread watchThread = new Thread(() -> {
WatchService watchService = FileSystems.getDefault().newWatchService();
directory.register(watchService,
StandardWatchEventKinds.ENTRY_MODIFY,
StandardWatchEventKinds.ENTRY_CREATE);
while (true) {
WatchKey key = watchService.take();
for (WatchEvent<?> event : key.pollEvents()) {
// 重新加载变更的Skill
reloadSkill(directory.resolve((Path) event.context()));
}
key.reset();
}
});
watchThread.setDaemon(true);
watchThread.start();
}
5.4 版本管理与CI集成
将Skills纳入版本控制:
# .github/workflows/skills-validation.yml
name: Validate Skills
on:
push:
paths: ['.claude/skills/**']
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Validate SKILL.md format
run: |
for file in $(find .claude/skills -name "SKILL.md"); do
echo "Validating $file"
# 检查YAML frontmatter
python scripts/validate_skill.py "$file"
done
- name: Check description quality
run: |
python scripts/check_description.py .claude/skills/
validate_skill.py示例:
import sys
import yaml
def validate_skill(path):
with open(path) as f:
content = f.read()
# 提取frontmatter
if not content.startswith('---'):
print(f"Error: {path} missing frontmatter")
return False
parts = content.split('---', 2)
if len(parts) < 3:
print(f"Error: {path} invalid frontmatter format")
return False
try:
metadata = yaml.safe_load(parts[1])
except yaml.YAMLError as e:
print(f"Error: {path} invalid YAML: {e}")
return False
# 检查必需字段
required = ['name', 'description']
for field in required:
if field not in metadata:
print(f"Error: {path} missing required field: {field}")
return False
# 检查name格式
name = metadata['name']
if not re.match(r'^[a-z0-9-]+$', name):
print(f"Error: {path} invalid name format: {name}")
return False
print(f"OK: {path}")
return True
if __name__ == '__main__':
sys.exit(0 if validate_skill(sys.argv[1]) else 1)
六、踩坑指南
6.1 常见问题与解决方案
| 问题 | 原因 | 解决方案 |
|---|---|---|
| Skill未被激活 | description不够明确 | 增加具体场景描述,避免歧义 |
| Token超限 | SKILL.md内容过长 | 精简指令,详细内容放入references/ |
| 脚本执行失败 | 运行时环境缺失 | 预装Python/Node.js,或使用Docker |
| Skills未加载 | 路径配置错误 | 生产环境使用classpath:资源路径 |
| 多个Skill冲突 | description重叠 | 调整priority字段,明确职责边界 |
6.2 性能优化
问题:每次请求都重新读取SKILL.md文件
解决方案:启用缓存
public class CachedSkillsTool extends SkillsTool {
private final Map<String, String> cache = new ConcurrentHashMap<>();
@Override
protected String loadSkillContent(Path skillPath) {
return cache.computeIfAbsent(skillPath.toString(), key -> {
try {
return Files.readString(skillPath.resolve("SKILL.md"));
} catch (IOException e) {
throw new RuntimeException("Failed to load skill: " + key, e);
}
});
}
}
6.3 调试技巧
启用详细日志:
# application.yml
logging:
level:
org.springaicommunity.agent.tools.skills: DEBUG
查看Tool Schema:
SkillsTool skillsTool = SkillsTool.builder()
.addSkillsDirectory(".claude/skills")
.build();
// 打印生成的Tool Schema
for (ToolCallback callback : skillsTool.getToolCallbacks()) {
System.out.println(callback.getToolDefinition().toJson());
}
七、总结
Agent Skills的核心价值在于:
- 延迟加载 → 节省Token,按需激活
- 确定性指令 → 行为可预测,质量可控
- 可维护性 → Markdown文件,版本管理
- 可复用性 → 跨项目共享,知识沉淀
适用场景判断:
参考资料
本篇为Spring AI Agentic Patterns深入系列第一篇。下一篇将深入解析AskUserQuestionTool的交互设计模式。