技能系统
技能系统允许通过 Markdown 文件定义复杂任务的执行步骤,LLM 按照步骤调用工具完成任务。
概述
什么是技能
技能是对复杂任务的抽象,包含:
- 触发场景(什么情况下使用)
- 执行步骤(按什么顺序调用工具)
- 参数说明(每个工具需要什么参数)
- 输出格式(结果应该如何呈现)
为什么需要技能
| 场景 | 没有技能 | 有技能 |
|---|---|---|
| 复杂任务 | LLM 需要多轮试错 | 按步骤一次完成 |
| 参数传递 | LLM 可能遗漏参数 | 步骤中明确参数来源 |
| 结果格式 | 格式不统一 | 模板化输出 |
| 可维护性 | 改 Prompt 需重新调优 | 改 Markdown 即可 |
技能定义
文件位置
技能定义文件放在 resources/skills/ 目录下:
src/main/resources/skills/
├── recommend_manual.md # 功法推荐
├── recommend_task.md # 任务推荐
└── ...文件格式
markdown
# 功法推荐
根据修士的境界、灵根、身份,推荐最适合修炼的功法。
**触发场景**:推荐功法|适合修炼的功法|能买什么功法
## 前置条件
**如果已知修士详细信息**,可直接使用,无需查询。
**如果未知修士信息**,调用工具查询:
调用工具:`query_user_detail`
${tool:query_user_detail}
## 执行步骤
### 步骤1:查询功法列表
调用工具:`page_manuals`
${tool:page_manuals}
**参数建议**:
- `realmMin`、`realmMax`:设为修士当前境界
- `element`:设为修士灵根属性
### 步骤2:查询兑换规则
调用工具:`query_exchange_rules`
${tool:query_exchange_rules}
## 分析阶段
综合以上信息,按以下维度筛选和排序:
### 1. 匹配度评估
| 匹配等级 | 条件 | 说明 |
|------|------|------|
| 完美匹配 | 境界匹配 + 灵根属性匹配 | 最适合修炼 |
## 输出格式
请按以下结构输出推荐结果:
### 🌟 强烈推荐
| 功法名 | 品级 | 属性 | 匹配度 | 价格 |
|-----|----|----|-----|------|关键元素
1. 标题
第一行的一级标题作为技能名称:
markdown
# 功法推荐2. 触发场景
**触发场景**: 后面的内容用于匹配用户意图:
markdown
**触发场景**:推荐功法|适合修炼的功法|能买什么功法多个触发词用 | 分隔。
3. 工具占位符
${tool:工具名} 会被自动替换为工具的详细参数格式:
markdown
${tool:query_user_detail}替换后:
【工具信息】
名称:query_user_detail
描述:查询修士的详细信息
权限要求:峰主
【参数说明】
{
"type": "object",
"properties": {
"userName": {
"type": "string",
"description": "修士的道号或姓名"
}
},
"required": ["userName"]
}4. 执行步骤
清晰的步骤指导 LLM 如何执行:
markdown
### 步骤1:查询功法列表
调用工具:`page_manuals`
**参数建议**:
- `realmMin`、`realmMax`:设为修士当前境界技能加载
AgentCapabilityRegistry
技能在应用启动时自动加载:
java
@Component
public class AgentCapabilityRegistry implements SmartInitializingSingleton {
private static final String SKILLS_LOCATION = "classpath*:skills/*.md";
private final Map<String, String> skillGuides = new ConcurrentHashMap<>();
private final Map<String, SkillMeta> skillMetas = new ConcurrentHashMap<>();
@Override
public void afterSingletonsInstantiated() {
registerTools();
loadSkills();
}
private void loadSkills() {
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources(SKILLS_LOCATION);
for (Resource resource : resources) {
loadSkillFromMarkdown(resource);
}
}
private void loadSkillFromMarkdown(Resource resource) {
String filename = resource.getFilename();
String skillId = filename.substring(0, filename.length() - 3); // 去掉 .md
String content = new String(resource.getInputStream().readAllBytes(), UTF_8);
// 提取元信息
SkillMeta meta = extractMeta(skillId, content);
skillMetas.put(skillId, meta);
// 替换工具占位符
String guide = replaceToolPlaceholders(content);
skillGuides.put(skillId, guide);
}
}元信息提取
从 Markdown 中提取技能元信息:
java
private SkillMeta extractMeta(String skillId, String content) {
String name = skillId;
String description = "";
String trigger = "";
for (String line : content.split("\n")) {
line = line.trim();
if (line.startsWith("# ")) {
name = line.substring(2).trim(); // 标题作为名称
} else if (line.startsWith("**触发场景**:")) {
trigger = line.substring("**触发场景**:".length()).trim();
} else if (description.isEmpty() && !line.startsWith("#") && !line.isEmpty()) {
description = line; // 第一段非空文本作为描述
}
}
return new SkillMeta(skillId, name, description, trigger);
}工具占位符替换
将 ${tool:xxx} 替换为工具的详细参数格式:
java
private String replaceToolPlaceholders(String content) {
Matcher matcher = TOOL_PLACEHOLDER.matcher(content);
StringBuilder result = new StringBuilder();
while (matcher.find()) {
String toolName = matcher.group(1);
String toolSchema = getToolSchema(toolName);
matcher.appendReplacement(result, Matcher.quoteReplacement(toolSchema));
}
matcher.appendTail(result);
return result.toString();
}技能使用
LLM 调用流程
System Prompt 中的技能列表
java
private String buildSystemPrompt(AgentContext context, XiuxianUserEntity user) {
String skillList = capabilityRegistry.getSkillList();
// ...
}
// getSkillList() 返回:
// - **功法推荐** (recommend_manual): 根据修士的境界、灵根、身份,推荐最适合修炼的功法。
// 触发场景:推荐功法|适合修炼的功法
// - **任务推荐** (recommend_task): 根据修士的境界和身份,推荐适合的任务。
// 触发场景:推荐任务|适合我的任务最佳实践
1. 明确触发场景
使用多个同义词,提高匹配率:
markdown
**触发场景**:推荐功法|适合修炼的功法|能买什么功法|有什么功法适合2. 步骤清晰
每个步骤包含:
- 步骤名称
- 调用工具
- 参数建议
- 预期结果
markdown
### 步骤1:查询修士信息
调用工具:`query_user_detail`
**参数建议**:
- `userName`:当前用户名
**预期结果**:获取境界、灵根类型、灵根属性、灵石余额3. 参数来源明确
说明参数从哪里获取:
markdown
**如果已知修士详细信息**(境界、灵根类型),可直接使用,无需查询。
**如果未知修士信息**,调用工具查询:
- 从步骤1的结果中提取境界、灵根类型4. 输出格式模板
提供输出格式示例,保证结果一致性:
markdown
## 输出格式
### 🌟 强烈推荐(完美匹配且能购买)
| 功法名 | 品级 | 属性 | 匹配度 | 灵石价格 | 推荐支付方式 |
|-----|----|----|-----|------|--------|5. 引导用户
在结果末尾添加引导:
markdown
> 💰 **资源不足?** 可以通过以下方式获取:
> - 完成任务获取灵石和贡献点
> - 输入「推荐任务」查看适合你的任务示例:任务推荐
markdown
# 任务推荐
根据修士的境界、身份、当前状态,推荐最适合的任务。
**触发场景**:推荐任务|适合我的任务|有什么任务|任务推荐
## 前置条件
调用工具:`query_user_detail`
${tool:query_user_detail}
从结果中提取:**境界、身份、状态**。
## 执行步骤
### 步骤1:查询任务列表
调用工具:`page_tasks`
${tool:page_tasks}
**参数建议**:
- `status`:设为 `1`(进行中的任务)
- `realmMin`、`realmMax`:设为修士当前境界范围
### 步骤2:查询任务成员
对于感兴趣的任务,调用工具:`query_task_members`
${tool:query_task_members}
## 分析阶段
### 1. 境界匹配
| 任务等级 | 适合境界 |
|------|------|
| S级 | 金丹及以上 |
| A级 | 筑基及以上 |
| B级 | 练气及以上 |
### 2. 状态判断
- 状态为"正常":可接取
- 状态为"闭关中":建议先出关
- 状态为"任务中":建议先完成当前任务
## 输出格式
### 📋 推荐任务
| 任务名 | 等级 | 奖励 | 截止时间 | 匹配度 |
|-----|----|----|------|------|
### 💡 建议
根据您的当前状态,建议...下一步
- 工具开发 - 开发业务工具
- Meta-Tool 模式 - 理解工具调用机制