Claude Code 本地 Skill 与 Reference 调用大模型
一、技能概述
1.1 核心定义
Claude Code 是 Anthropic 官方推出的命令行工具,通过自然语言交互帮助开发者完成软件工程任务。本技能聚焦于如何在 Claude Code 中利用本地 Skill 系统和Reference 参考知识库来调用大语言模型,实现精准的领域知识问答与任务执行。
Skill(技能)是 Claude Code 的核心扩展机制,允许开发者将领域知识、指令模板和参考文档打包成可复用的单元,供 LLM 在对话中调用。Reference(参考知识库)则是 Skill 的重要组成部分,提供了 LLM 回答问题所需的事实依据和上下文信息。
参考来源:Anthropic 官方 Claude Code 文档、Spring AI 官方文档
1.2 学习背景
在 AI 面试平台项目中,需要实现多方向面试出题(Java后端、算法、前端、系统设计等)、结合简历信息生成个性化问题、调用大模型进行答案评估。
传统的做法是硬编码 Prompt,但这种方式难以维护、扩展性差。通过 Skill + Reference 机制,可以将领域知识与调用逻辑分离,实现知识模块化、动态加载和灵活分发。
1.3 发展历程与行业对比
| 维度 |
Claude Code Skill |
OpenAI GPTs |
LangChain Agents |
| 知识组织 |
Markdown + YAML |
JSON 配置 |
Python 代码 |
| Reference 机制 |
文件引用 + 分类索引 |
矢量数据库 |
文档加载器 |
| 调用方式 |
Tool Call 自动触发 |
手动选择 |
工具链编排 |
| 适用场景 |
面试问答、代码审查 |
通用对话 |
复杂推理任务 |
二、核心知识点
2.1 Skill 系统架构
2.1.1 目录结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
resources/skills/
├── _shared/
│ └── references/ # 跨 Skill 共享参考知识
│ ├── java.md
│ ├── mysql.md
│ ├── redis.md
│ └── spring.md
├── java-backend/
│ ├── SKILL.md # Skill 主定义文件
│ ├── skill.meta.yml # 元数据配置
│ └── references/ # Skill 私有参考(可选)
├── algorithm/
├── frontend/
└── system-design/
|
2.1.2 SKILL.md 格式规范
SKILL.md 采用 YAML front matter + Markdown body 的混合格式:
1
2
3
4
5
6
7
8
9
10
11
|
---
name: java-backend
description: 用于 Java 后端面试出题,涵盖集合、并发、JVM 等核心知识
---
# Overview
你是一位资深的 Java 后端面试官...
# Instructions
1. 优先结合候选人简历中的技术栈出题
2. 每道题需包含难度评级和考察点
|
front matter 字段说明:
| 字段 |
类型 |
必填 |
说明 |
| name |
string |
是 |
Skill 唯一标识符 |
| description |
string |
是 |
Skill 功能描述,供 LLM 判断调用时机 |
1
2
3
4
5
6
7
8
9
10
|
displayName: Java 后端开发
display:
icon: ☕
gradient: from-blue-500 to-indigo-500
categories:
- key: JAVA
label: Java
priority: CORE
ref: java.md
shared: true
|
2.2 Reference 参考知识库
2.2.1 Reference 文件格式
Reference 文件为纯 Markdown,支持多级标题和代码块。内容示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
## Java 集合框架
### ArrayList vs LinkedList
| 特性 | ArrayList | LinkedList |
|------|-----------|------------|
| 查找复杂度 | O(1) | O(n) |
| 插入复杂度 | O(n) | O(1) |
### 扩容机制
```java
int newCapacity = oldCapacity + (oldCapacity >> 1); // 1.5倍
```
! 注意:扩容因子 1.5 是为了减少频繁扩容带来的性能损耗
|
2.2.2 分类索引机制
_shared/references/ 下的文件会被索引为共享知识,所有 Skill 均可访问:
1
2
3
4
5
6
|
categories:
- key: JAVA
label: Java
priority: CORE
ref: java.md # 指向 _shared/references/java.md
shared: true
|
当 LLM 调用 Skill 工具时,Agent Utils 会根据 skill.meta.yml 中的 categories 配置,将对应的 Reference 内容注入到上下文中。
2.3 Spring AI 集成
2.3.1 LlmProviderRegistry 多 Provider 管理
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
|
@Configuration
public class LlmProviderRegistry {
private final Map<String, ChatClient> clientCache = new ConcurrentHashMap<>();
// 获取带 SkillsTool 的 ChatClient(面试出题用)
public ChatClient getChatClientOrDefault(String provider) {
return clientCache.computeIfAbsent(provider, this::createChatClient);
}
// 获取不带 SkillsTool 的 ChatClient(语音面试用)
public ChatClient getPlainChatClient(String provider) {
return clientCache.computeIfAbsent(provider + "-plain", this::createPlainChatClient);
}
private ChatClient createChatClient(String provider) {
var config = providers.get(provider);
var chatModel = OpenAiChatModel.builder()
.baseUrl(config.getBaseUrl())
.apiKey(config.getApiKey())
.model(config.getModel())
.build();
return ChatClient.builder(chatModel)
.defaultTools(interviewSkillsToolCallback()) // 注入 SkillsTool
.build();
}
}
|
2.3.2 配置示例
1
2
3
4
5
6
7
8
9
10
11
12
|
app:
ai:
default-provider: dashscope
providers:
dashscope:
base-url: https://dashscope.aliyuncs.com/compatible-mode
api-key: ${AI_BAILIAN_API_KEY}
model: qwen-plus
lmstudio:
base-url: http://localhost:1234
api-key: lm-studio
model: qwen2.5-7b-instruct
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
### 2.4 StructuredOutputInvoker 结构化输出
#### 2.4.1 核心原理
结构化输出通过以下机制保证 JSON 格式正确:
1. **BeanOutputConverter**:根据 Java record 类定义自动生成 JSON Schema
2. **重试机制**:解析失败时注入修复指令 + 上次错误原因
3. **Format 注入**:将 JSON Schema 以注释形式追加到 system prompt
#### 2.4.2 输出格式生成
```java
// 定义接收 DTO
private record QuestionListDTO(List<QuestionDTO> questions) {}
private record QuestionDTO(String question, String type, String category,
String topicSummary, List<String> followUps) {}
// 创建 converter
BeanOutputConverter<QuestionListDTO> outputConverter =
new BeanOutputConverter<>(QuestionListDTO.class);
// outputConverter.getFormat() 返回 JSON Schema 说明
|
三、实操步骤
3.1 环境准备
3.1.1 项目依赖
确保 build.gradle 包含以下依赖:
1
2
3
4
5
6
7
8
|
dependencies {
// Spring AI Core
implementation 'org.springframework.ai:spring-ai-openai:1.0.0'
// Agent Utils(SkillsTool)
implementation 'org.springframework.ai:spring-ai-agent-utils:1.0.0'
// Micrometer 监控
implementation 'io.micrometer:micrometer-registry-prometheus'
}
|
3.1.2 配置文件
在 application.yml 中配置 AI 提供者:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
app:
ai:
default-provider: dashscope
structured-max-attempts: 2
structured-include-last-error: true
structured-retry-use-repair-prompt: true
providers:
dashscope:
base-url: https://dashscope.aliyuncs.com/compatible-mode
api-key: ${AI_BAILIAN_API_KEY}
model: qwen-plus
agent-utils:
skills-root: classpath:skills
|
3.2 创建自定义 Skill
3.2.1 步骤一:创建目录结构
1
2
|
mkdir -p resources/skills/java-backend/references
mkdir -p resources/skills/java-backend/img
|
3.2.2 步骤二:编写 SKILL.md
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
---
name: java-backend
description: Java 后端面试出题技能,涵盖集合、并发、JVM、Spring 生态等
---
# Role
你是一位拥有 10 年经验的 Java 后端技术专家...
# Capabilities
1. 结合简历信息生成个性化面试题
2. 根据难度级别调整题目复杂度
# Output Format
每个问题包含:
- question: 主问题
- type: SINGLE_CHOICE / MULTIPLE_CHOICE / CODING / DISCUSSION
- category: 问题分类
- topicSummary: 考察知识点摘要
- followUps: 追问列表
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
displayName: Java 后端开发
display:
icon: ☕
gradient: from-blue-500 to-indigo-500
categories:
- key: JAVA
label: Java 基础
priority: CORE
ref: java.md
shared: true
- key: CONCURRENT
label: 并发编程
priority: CORE
ref: concurrent.md
shared: false
|
3.2.4 步骤四:编写 Reference 文件
resources/skills/_shared/references/java.md:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
## Java 集合框架
### List 接口实现类对比
| 类 | 底层结构 | 查找 | 插入/删除 | 线程安全 |
|----|----------|------|-----------|----------|
| ArrayList | 动态数组 | O(1) | O(n) | 否 |
| LinkedList | 双向链表 | O(n) | O(1) | 否 |
| Vector | 动态数组 | O(1) | O(n) | 是 |
### 扩容机制
```java
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // 1.5倍
}
> ! 警示:避免在 ArrayList 头部频繁插入删除操作,时间复杂度 O(n)
|
3.3.1 AgentUtilsConfiguration
1
2
3
4
5
6
7
8
9
10
11
|
```java
@Configuration
public class AgentUtilsConfiguration {
@Bean("interviewSkillsToolCallback")
public ToolCallback interviewSkillsToolCallback(ResourceLoader resourceLoader) {
return SkillsTool.builder()
.addSkillsResource(resourceLoader.getResource("classpath:skills"))
.build();
}
}
|
3.3.2 注入到 LlmProviderRegistry
1
2
3
4
5
|
@Bean
public LlmProviderRegistry llmProviderRegistry(
@Qualifier("interviewSkillsToolCallback") ToolCallback callback) {
return new LlmProviderRegistry(callback);
}
|
3.4 完整调用示例
3.4.1 面试出题服务
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
32
33
34
35
36
37
38
39
40
|
@Service
@RequiredArgsConstructor
public class InterviewQuestionService {
private final LlmProviderRegistry llmProviderRegistry;
private final StructuredOutputInvoker structuredOutputInvoker;
private static final String SYSTEM_PROMPT_PATH = "prompts/interview-question-skill.st";
private static final String USER_PROMPT_PATH = "prompts/interview-question-skill-user.st";
public QuestionListDTO generateQuestions(GenerateQuestionRequest request) {
// 1. 获取 ChatClient(已注入 SkillsTool)
ChatClient chatClient = llmProviderRegistry.getChatClientOrDefault("dashscope");
// 2. 定义接收 DTO
BeanOutputConverter<QuestionListDTO> converter =
new BeanOutputConverter<>(QuestionListDTO.class);
// 3. 加载并渲染模板
String systemPrompt = loadTemplate(SYSTEM_PROMPT_PATH).render()
+ "\n\n" + converter.getFormat();
String userPrompt = loadTemplate(USER_PROMPT_PATH).render(Map.of(
"questionCount", request.getCount(),
"skillName", request.getSkillName()
));
// 4. 调用(自动重试 + 结构化解析)
return structuredOutputInvoker.invoke(
chatClient, systemPrompt, userPrompt, converter,
ErrorCode.INTERVIEW_QUESTION_GENERATION_FAILED,
"出题失败:", "面试出题", log
);
}
private PromptTemplate loadTemplate(String path) {
return new PromptTemplate(
resourceLoader.getResource(path).getContentAsString(StandardCharsets.UTF_8)
);
}
}
|
3.4.2 Prompt 模板示例
resources/prompts/interview-question-skill.st:
1
2
3
4
5
6
7
8
|
# System Prompt
你是一位专业的面试官。请根据以下信息生成面试题目。
## Skill 指令
调用 Skill 工具获取 {skillName} 的参考知识
## 输出格式
{outputFormat}
|
resources/prompts/interview-question-skill-user.st:
1
2
3
4
5
6
7
8
9
10
11
|
# User Prompt
请生成 {questionCount} 道面试题。
## 难度级别
{difficultyDescription}
## 面试方向
{skillName}({skillDescription})
## 已考知识点(避免重复)
{historicalSection}
|
3.5 结果验证
3.5.1 成功验证
1
2
3
|
QuestionListDTO result = interviewQuestionService.generateQuestions(request);
assert result.questions().size() == request.getCount();
assert result.questions().get(0).followUps() != null;
|
3.5.2 日志输出
1
2
|
INFO InterviewQuestionService - 出题成功:方向=Java后端, 题目数=6, 耗时=1.2s
INFO StructuredOutputInvoker - 结构化输出成功:attempt=1, model=qwen-plus
|
四、问题与解决方案
4.1 常见问题
4.1.1 LLM 不触发 Skill 调用
问题描述:对话中 LLM 没有自动调用 Skill 工具,知识库内容未注入上下文。
排查步骤:
- 检查
SKILL.md 的 description 字段是否清晰表达了 Skill 功能
- 确认
skill.meta.yml 的 categories 配置了有效的 ref 指向
- 验证
LlmProviderRegistry 创建 ChatClient 时传入了 SkillsTool callback
- 查看日志确认 Skill 加载成功
1
2
|
grep "SkillsTool" application.log
# 期望输出:SkillsTool initialized with X skills
|
解决方案:确保 SkillsTool 被正确注册为 default tool callback
1
2
3
4
5
6
|
// LlmProviderRegistry.java
private ChatClient createChatClient(String provider) {
return ChatClient.builder(chatModel)
.defaultTools(interviewSkillsToolCallback) // 关键:注入
.build();
}
|
4.1.2 结构化输出解析失败
问题描述:LLM 返回的 JSON 格式不符合 DTO 定义,重试多次仍失败。
完整报错:
1
2
|
Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException:
Cannot deserialize value of type `java.util.List<QuestionDTO>` from Array
|
排查步骤:
- 检查 DTO 定义与
outputConverter.getFormat() 输出是否匹配
- 确认 Prompt 中是否正确注入了 JSON Schema
- 查看重试日志定位失败原因
1
2
|
grep "StructuredOutputInvoker" application.log
# 期望输出:Retry attempt=X, lastError=XXX
|
解决方案:启用修复型重试提示词
1
2
3
4
5
6
|
app:
ai:
structured-retry-use-repair-prompt: true
structured-retry-append-strict-json-instruction: true
structured-include-last-error: true
structured-error-message-max-length: 200
|
4.2 拓展问题
4.2.1 如何支持离线场景?
方案:使用本地 LLM(如 LM Studio)替代云端 API
1
2
3
4
5
6
7
8
|
app:
ai:
default-provider: lmstudio
providers:
lmstudio:
base-url: http://localhost:1234
api-key: lm-studio
model: qwen2.5-7b-instruct
|
4.2.2 如何动态切换 Skill 目录?
方案:通过配置属性注入不同的 SkillsTool 根路径
1
2
3
4
5
6
7
8
|
@Bean("customSkillsToolCallback")
public ToolCallback customSkillsToolCallback(
@Value("${app.skills.root}") String skillsRoot,
ResourceLoader resourceLoader) {
return SkillsTool.builder()
.addSkillsResource(resourceLoader.getResource(skillsRoot))
.build();
}
|
五、学习总结与拓展
5.1 核心收获
- Skill 系统设计:通过 YAML front matter + Markdown body 格式,实现领域知识的模块化封装
- Reference 索引机制:共享 Reference 与私有 Reference 分层管理,节省 Token 消耗
- 结构化输出原理:通过 JSON Schema 生成 + 修复型重试提示词,确保 LLM 输出格式可靠
- 多 Provider 架构:LlmProviderRegistry 统一管理多个 AI 提供者,支持本地/云端灵活切换
5.2 未掌握的知识点
5.3 拓展方向
-
进阶学习路径:基础使用 → 多 Skill 编排 → 动态 Skill 加载 → Tool Call 性能优化
-
相关技术栈:
- Spring AI + Vector DB(知识库向量化)
- Claude Code + MCP(Model Context Protocol)
- ReAct 模式(推理 + 行动协同)
-
行业前沿应用:
- 大厂 AI 面试系统:结合简历解析 + 实时语音 + 多维度评估
- 法律/医疗专业问答:领域知识库 + 引用溯源
5.4 资源推荐
附录:关键文件清单
| 文件路径 |
用途 |
common/ai/LlmProviderRegistry.java |
多 Provider 注册与缓存 |
common/ai/StructuredOutputInvoker.java |
结构化输出 + 重试封装 |
common/ai/AgentUtilsConfiguration.java |
SkillsTool Bean 配置 |
modules/interview/skill/InterviewSkillService.java |
Skill 加载与解析服务 |
resources/skills/*/SKILL.md |
Skill 主定义文件 |
resources/skills/*/skill.meta.yml |
Skill 元数据配置 |
resources/skills/_shared/references/*.md |
共享参考知识库 |
resources/prompts/*.st |
Prompt 模板文件 |