Skip to content

Commit 6ef6e91

Browse files
Joyer Huanguppet
authored andcommitted
feat: 优化技能系统 - 通过消息注入实现 API 缓存友好的技能加载
核心改进: 1. 通过对话消息注入技能内容,保持 system prompt 稳定 2. 在启动时显示所有潜在可用技能,让 AI 知道自己的能力 3. 移除 system prompt 中的重复内容,优化 token 使用 技术实现: - Conversation.injectSkillMessage(): 注入技能内容到对话历史 - skillLoad 工具自动调用消息注入 - potentialSkills: 在 system prompt 中显示所有可用技能的简要描述 - 移除 system prompt 中的 activeSkills 部分(避免与对话历史重复) 优势: - API 缓存友好:system prompt 始终固定 - 成本优化:充分利用 prompt caching - 用户体验更好:AI 启动时就知道有哪些技能可用 - 架构更清晰:潜在技能在 system prompt,已加载技能在对话历史 Co-Authored-By: GLM-4.7 & Claude
1 parent b23e9ae commit 6ef6e91

6 files changed

Lines changed: 360 additions & 40 deletions

File tree

src/config.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,9 @@ const DEFAULT_CONFIG = {
7171
'listFiles', // 列出文件
7272
'analyzeError', // 分析错误
7373
'runTests', // 运行测试
74-
'planTask' // 规划任务
74+
'planTask', // 规划任务
75+
'skillDiscover', // 发现可用技能
76+
'skillLoad' // 加载技能到对话
7577
]
7678
},
7779

src/conversation/core.js

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ export class Conversation {
9797
// 初始化技能系统(如果启用)
9898
this.skillRegistry = null;
9999
this.conversationState = null;
100+
this.potentialSkills = []; // 潜在可用技能列表(仅包含 name 和 description)
100101
this.skillsEnabled = config.skills?.enabled ?? false;
101102
}
102103

@@ -156,10 +157,14 @@ export class Conversation {
156157
// 创建会话状态
157158
this.conversationState = createConversationState();
158159

159-
// 创建并注册技能工具
160-
const skillTools = createSkillTools(this.skillRegistry, this.conversationState);
160+
// 创建并注册技能工具(传入 conversation 引用用于消息注入)
161+
const skillTools = createSkillTools(this.skillRegistry, this.conversationState, this);
161162
setSkillTools(skillTools);
162163

164+
// 获取所有潜在可用技能(仅包含 name 和 description)
165+
this.potentialSkills = await this.skillRegistry.discover();
166+
console.log(`[Skills] Discovered ${this.potentialSkills.length} potential skills`);
167+
163168
console.log('[Skills] System initialized');
164169
} catch (error) {
165170
console.error('[Skills] Failed to initialize:', error.message);
@@ -174,12 +179,17 @@ export class Conversation {
174179
async buildSystemPrompt() {
175180
const { getSystemPrompt } = await import('../prompt-builder.js');
176181

177-
// 获取已加载的技能
178-
const activeSkills = this.skillsEnabled && this.conversationState
179-
? this.conversationState.getActiveSkills()
182+
// 获取潜在可用技能(仅包含 name 和 description)
183+
const potentialSkills = this.skillsEnabled && this.potentialSkills
184+
? this.potentialSkills
180185
: [];
181186

182-
this.systemPrompt = await getSystemPrompt(this.config, this.workflowTest, activeSkills);
187+
this.systemPrompt = await getSystemPrompt(
188+
this.config,
189+
this.workflowTest,
190+
null, // activeSkills 已不再使用(通过对话消息注入)
191+
potentialSkills
192+
);
183193

184194
// 添加 workflow 测试提示词(如果需要)
185195
if (this.workflowTest) {
@@ -349,6 +359,42 @@ export class Conversation {
349359
this.abortFence.reset();
350360
}
351361

362+
/**
363+
* 注入技能内容到对话历史
364+
*
365+
* 通过在消息历史中插入技能内容,避免修改 system prompt,
366+
* 从而优化 API 缓存命中率
367+
*
368+
* @param {Object} skill - 技能对象
369+
*/
370+
injectSkillMessage(skill) {
371+
// 构建技能内容消息
372+
const skillMessage = `## 🎯 Skill Loaded: ${skill.name}
373+
374+
**Description**: ${skill.description}
375+
376+
---
377+
378+
${skill.content}
379+
380+
---
381+
382+
You can now use the capabilities described in this skill to help the user.`;
383+
384+
// 添加到消息历史(使用 user role,确保模型会读取)
385+
this.messages.push({
386+
role: MessageType.USER,
387+
content: skillMessage,
388+
metadata: {
389+
type: 'skill_injection',
390+
skillName: skill.name,
391+
timestamp: Date.now()
392+
}
393+
});
394+
395+
console.log(`[Skills] Injected skill "${skill.name}" into conversation history`);
396+
}
397+
352398
/**
353399
* 获取对话摘要
354400
*/

src/prompt-builder.js

Lines changed: 43 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ async function readProjectCloco() {
4747
/**
4848
* 构建系统提示词(优化后的版本)
4949
*/
50-
export async function getSystemPrompt(config, workflowTest = false, activeSkills = null) {
50+
export async function getSystemPrompt(config, workflowTest = false, activeSkills = null, potentialSkills = null) {
5151
const memory = loadMemory();
5252
const projectKey = config.behavior.workingDir || 'default';
5353
const projectInfo = memory.projects?.[projectKey];
@@ -171,6 +171,46 @@ When asked to analyze or review code:
171171
172172
**NOTE**: Only perform comprehensive analysis when explicitly requested. For specific questions, focus on the relevant parts.
173173
174+
## 🎯 Skills System - Enhanced Capabilities
175+
176+
You have access to a **Skills System** that provides additional specialized capabilities:
177+
178+
### Available Skills Tools:
179+
1. **skillDiscover** - Discover available skills in the system
180+
- Use when: You need specialized capabilities beyond standard tools
181+
- Returns: List of available skills with names and descriptions
182+
- Example: skillDiscover with query "git" to find git-related skills
183+
184+
2. **skillLoad** - Load a skill into the conversation
185+
- Use when: You found a relevant skill via skillDiscover
186+
- Effect: Skill content is injected into conversation history
187+
- Example: skillLoad with name "git-status" to load git status skill
188+
189+
### When to Use Skills:
190+
- User requests specialized functionality (Git, deployment, testing, etc.)
191+
- Current tools are insufficient for the task
192+
- You need domain-specific knowledge or workflows
193+
- User mentions a specific skill by name
194+
195+
### Workflow:
196+
1. Use skillDiscover to find relevant skills
197+
2. Review skill descriptions to identify the best match
198+
3. Use skillLoad to load the skill into conversation
199+
4. Use the loaded skill's capabilities to assist the user
200+
201+
**Note**: Loaded skills become available in the conversation context without modifying the system prompt, enabling efficient API caching.
202+
${potentialSkills && potentialSkills.length > 0 ? `
203+
204+
## 📚 Available Skills (Potential)
205+
206+
The following skills are available in this system. You can load any of them using the skillLoad tool when needed:
207+
208+
${potentialSkills.map(skill => `- **${skill.name}**: ${skill.description}`).join('\n')}
209+
210+
**Remember**: These skills are not yet loaded. Use skillLoad to load a skill when you need its capabilities.
211+
212+
` : ''}
213+
174214
## 📍 Current Context
175215
Working Directory: ${config.behavior.workingDir}
176216
Available Tools: ${config.tools.enabled.join(', ')}
@@ -210,30 +250,8 @@ No custom behavior guidelines found. You can add them by:
210250
- Creating ./cloco.md for project-specific guidelines
211251
` : ''}${workflowPrompt}`;
212252

213-
// 添加已加载的技能
214-
if (activeSkills && activeSkills.length > 0) {
215-
prompt += `
216-
217-
## 🎯 Loaded Skills
218-
219-
The following skills are available for use in this conversation:
220-
221-
`;
222-
223-
for (const skill of activeSkills) {
224-
prompt += `### ${skill.name}
225-
226-
${skill.description}
227-
228-
${skill.content}
229-
230-
---
231-
`;
232-
}
233-
234-
prompt += `You can use these skills to help the user. Please carefully read the skill documentation, understand their capabilities and usage, then assist the user with their tasks.
235-
`;
236-
}
253+
// 注意:已加载的技能通过对话消息注入,不再在 system prompt 中重复显示
254+
// 这样可以保持 system prompt 稳定,优化 API 缓存命中率
237255

238256
return prompt;
239257
}

src/skills/tools.js

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,10 @@ export function createSkillDiscoverTool(skillRegistry) {
6464
* 创建 skillLoad 工具
6565
* @param {Object} skillRegistry - 技能注册表实例
6666
* @param {Object} conversationState - 会话状态实例
67+
* @param {Object} conversation - 对话实例(用于注入技能消息)
6768
* @returns {Object} betaZodTool 对象
6869
*/
69-
export function createSkillLoadTool(skillRegistry, conversationState) {
70+
export function createSkillLoadTool(skillRegistry, conversationState, conversation) {
7071
return betaZodTool({
7172
name: 'skillLoad',
7273
description: `加载指定的技能,使其在当前对话中可用。
@@ -77,8 +78,9 @@ export function createSkillLoadTool(skillRegistry, conversationState) {
7778
3. 当前工具无法完成用户需求
7879
7980
**加载成功后**:
80-
- 技能的完整内容将被添加到系统上下文
81-
- 模型可以使用技能描述中说明的能力
81+
- 技能的完整内容将被注入到对话历史中
82+
- 模型可以在后续对话中使用技能描述的能力
83+
- system prompt 保持不变,优化 API 缓存
8284
8385
**失败处理**:
8486
- 如果技能不存在或加载失败,使用原有能力解决问题`,
@@ -102,14 +104,19 @@ export function createSkillLoadTool(skillRegistry, conversationState) {
102104
// 添加到会话状态
103105
conversationState.addSkill(skill);
104106

107+
// 注入技能内容到对话历史(保持 system prompt 不变)
108+
if (conversation && typeof conversation.injectSkillMessage === 'function') {
109+
conversation.injectSkillMessage(skill);
110+
}
111+
105112
return JSON.stringify({
106113
success: true,
107114
skill: {
108115
name: skill.name,
109-
description: skill.description,
110-
content: skill.content
116+
description: skill.description
117+
// 不返回 content,因为已经注入到消息历史中
111118
},
112-
message: `技能 "${skill.name}" 已加载。`
119+
message: `技能 "${skill.name}" 已加载到对话上下文中。`
113120
});
114121
} catch (error) {
115122
return JSON.stringify({
@@ -126,11 +133,12 @@ export function createSkillLoadTool(skillRegistry, conversationState) {
126133
* 创建所有技能工具
127134
* @param {Object} skillRegistry - 技能注册表实例
128135
* @param {Object} conversationState - 会话状态实例
136+
* @param {Object} conversation - 对话实例(用于消息注入)
129137
* @returns {Array} 工具数组
130138
*/
131-
export function createSkillTools(skillRegistry, conversationState) {
139+
export function createSkillTools(skillRegistry, conversationState, conversation) {
132140
return [
133141
createSkillDiscoverTool(skillRegistry),
134-
createSkillLoadTool(skillRegistry, conversationState)
142+
createSkillLoadTool(skillRegistry, conversationState, conversation)
135143
];
136144
}

test/test-potential-skills.js

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
#!/usr/bin/env node
2+
/**
3+
* 测试潜在技能提示功能
4+
*/
5+
6+
import { getConfig } from './src/config.js';
7+
import { getSystemPrompt } from './src/prompt-builder.js';
8+
import { createSkillRegistry } from './src/skills/index.js';
9+
10+
async function testPotentialSkills() {
11+
console.log('========================================');
12+
console.log('测试潜在技能提示功能');
13+
console.log('========================================\n');
14+
15+
try {
16+
// 1. 加载配置
17+
console.log('1️⃣ 加载配置...');
18+
const config = await getConfig();
19+
console.log(' ✅ 配置加载成功\n');
20+
21+
// 2. 创建技能注册表
22+
console.log('2️⃣ 创建技能注册表...');
23+
const registry = createSkillRegistry({
24+
globalDir: `${process.env.HOME}/.closer-code/skills`,
25+
projectDir: '.closer-code/skills'
26+
});
27+
await registry.initialize();
28+
console.log(' ✅ 注册表初始化成功\n');
29+
30+
// 3. 获取潜在可用技能
31+
console.log('3️⃣ 获取潜在可用技能...');
32+
const potentialSkills = await registry.discover();
33+
console.log(` 发现 ${potentialSkills.length} 个潜在技能:`);
34+
potentialSkills.forEach(s => {
35+
console.log(` - ${s.name}: ${s.description.substring(0, 60)}...`);
36+
});
37+
console.log();
38+
39+
// 4. 构建系统提示(包含潜在技能)
40+
console.log('4️⃣ 构建系统提示...');
41+
const systemPrompt = await getSystemPrompt(config, false, [], potentialSkills);
42+
console.log(' ✅ 系统提示构建成功\n');
43+
44+
// 5. 检查系统提示中的潜在技能部分
45+
console.log('5️⃣ 验证系统提示内容...');
46+
47+
if (systemPrompt.includes('## 📚 Available Skills (Potential)')) {
48+
console.log(' ✅ 找到"潜在可用技能"部分');
49+
} else {
50+
console.log(' ❌ 未找到"潜在可用技能"部分');
51+
return;
52+
}
53+
54+
// 检查每个技能是否在系统提示中
55+
let allSkillsFound = true;
56+
for (const skill of potentialSkills) {
57+
if (systemPrompt.includes(`**${skill.name}**`)) {
58+
console.log(` ✅ 找到技能: ${skill.name}`);
59+
} else {
60+
console.log(` ❌ 未找到技能: ${skill.name}`);
61+
allSkillsFound = false;
62+
}
63+
64+
if (systemPrompt.includes(skill.description)) {
65+
console.log(` ✅ 找到描述: ${skill.name}`);
66+
} else {
67+
console.log(` ❌ 未找到描述: ${skill.name}`);
68+
allSkillsFound = false;
69+
}
70+
}
71+
72+
if (!allSkillsFound) {
73+
console.log('\n ❌ 部分技能未在系统提示中找到');
74+
return;
75+
}
76+
77+
console.log();
78+
console.log('6️⃣ 系统提示中的"潜在可用技能"部分:');
79+
console.log('---');
80+
81+
// 提取并显示潜在技能部分
82+
const potentialSkillsStart = systemPrompt.indexOf('## 📚 Available Skills (Potential)');
83+
const potentialSkillsEnd = systemPrompt.indexOf('## 📍 Current Context', potentialSkillsStart);
84+
85+
if (potentialSkillsStart !== -1 && potentialSkillsEnd !== -1) {
86+
const potentialSkillsSection = systemPrompt.substring(potentialSkillsStart, potentialSkillsEnd);
87+
console.log(potentialSkillsSection);
88+
} else {
89+
console.log(' ❌ 无法提取潜在技能部分');
90+
return;
91+
}
92+
93+
console.log('---\n');
94+
95+
console.log('========================================');
96+
console.log('✅ 所有测试通过!');
97+
console.log('========================================\n');
98+
99+
console.log('📊 总结:');
100+
console.log(`- ✅ 发现 ${potentialSkills.length} 个潜在技能`);
101+
console.log('- ✅ 所有技能都包含在系统提示中');
102+
console.log('- ✅ AI 现在可以在启动时知道有哪些技能可用');
103+
console.log('- ✅ AI 可以根据用户需求直接调用 skillLoad');
104+
105+
} catch (error) {
106+
console.error('\n❌ 测试失败:', error.message);
107+
console.error(error.stack);
108+
process.exit(1);
109+
}
110+
}
111+
112+
// 运行测试
113+
testPotentialSkills();

0 commit comments

Comments
 (0)