Agent Skills 深入解析:构建可插拔的智能体知识体系

0 阅读9分钟

综述篇介绍了Agent Skills的核心概念,本文将深入剖析其设计原理、完整规范、动态加载机制,以及生产环境落地的最佳实践。

环境准备

本文示例代码基于以下技术栈:

组件版本要求
JDK17+
Spring Boot3.2+
Spring AI2.0.0-M3+
spring-ai-agent-utils0.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) 策略:

part1-progressive-disclosure.drawio.png

核心优势

  1. 延迟加载:知识仅在需要时加载,节省Token
  2. 确定性:加载的是完整指令,而非检索片段
  3. 可维护:修改Markdown文件即可更新,无需重新部署
  4. 可复用:同一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 FrontmatterMarkdown 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 Glob to find relevant source files
  • Use Grep to 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.md for API design patterns
  • references/best-practices.md for 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

撰写原则

  1. 明确职责边界:说明何时使用、何时不使用
  2. 提供具体场景:列举典型用例
  3. 避免歧义:与其他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 加载流程

part1-skill-loading-flow.drawio.png

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 SkillsRAG
知识类型指令性知识(怎么做)陈述性知识(是什么)
加载方式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();

协作流程

  1. Skills决定做什么(如"按代码审查清单检查")
  2. 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的核心价值在于:

  1. 延迟加载 → 节省Token,按需激活
  2. 确定性指令 → 行为可预测,质量可控
  3. 可维护性 → Markdown文件,版本管理
  4. 可复用性 → 跨项目共享,知识沉淀

适用场景判断

part1-decision-tree.drawio.png


参考资料


本篇为Spring AI Agentic Patterns深入系列第一篇。下一篇将深入解析AskUserQuestionTool的交互设计模式。