train-skill-tool.js
11.3 KB
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
/**
* train-skill-tool.js — Master 训练新 Skill 工具
*
* 让 Master Agent 可以触发完整的 Hunt→Digest→Evolve/SkillCreate 流水线。
* 训练产物写入 data/trained_skills/user_{id}/{skillName}/,按用户隔离。
*
* 使用场景:
* - 用户说"训练一个新 skill"
* - 用户说"使用 innovator 帮我创建 xxx skill"
* - 用户说"帮我重训/改进 xxx skill"
*/
const fs = require('fs');
const path = require('path');
/**
* 创建 train_skill 工具定义
*/
function createTrainSkillTool() {
// 延迟创建 JobManager(模块加载时 .data 目录可能尚未就绪)
let _jobManager = null;
function getJobManager() {
if (!_jobManager) {
const { JobManager } = require('../../services/job-manager');
_jobManager = new JobManager({
storePath: path.resolve(__dirname, '../../../.data/jobs-train.json'),
});
}
return _jobManager;
}
return {
name: 'train_skill',
description: `训练一个新 Skill 或重训一个已有 Skill。触发完整的 Hunt→Digest→Evolve 流水线:
- Hunt: 猎取领域知识(网络研究、资料收集)
- Digest: 消化提炼为评估基准(benchmark)
- Evolve/SkillCreate: 基于知识库和基准生成高质量 SKILL.md
适用场景:
- 用户说"训练/创建一个新 skill"
- 用户说"使用 innovator 训练 xxx"
- 用户说"帮我做一个新的 xxx skill"
注意:训练任务通常需要 5-10 分钟,此工具异步执行并返回 jobId。
用 list_retrain_jobs 工具可查看进度。`,
parameters: {
type: 'object',
required: ['skillName', 'description'],
properties: {
skillName: {
type: 'string',
description: 'Skill 名称(英文 kebab-case 或 snake_case,如 genlogo-prompt、code_reviewer)',
},
description: {
type: 'string',
description: 'Skill 的功能描述——它要做什么、用户输入什么、期望输出什么',
},
instructions: {
type: 'string',
description: '用户的额外训练要求或补充说明(可选)',
},
mode: {
type: 'string',
description: '"create"(新建 skill,默认)或 "retrain"(重训已有 skill)',
},
},
},
async execute(params, context) {
const { skillName, description, instructions = '', mode = 'create' } = params;
const config = require('../../config');
const jobManager = getJobManager();
const userId = context.userId || 'anonymous';
if (mode === 'retrain') {
return _executeRetrain(skillName, description, instructions, jobManager, config, userId);
}
return _executeCreate(skillName, description, instructions, jobManager, config, userId);
},
};
}
/**
* 执行新 Skill 创建(Hunt→Digest→SkillCreate)
*/
async function _executeCreate(skillName, description, instructions, jobManager, config, userId) {
const { InnovatorService } = require('../../services/innovator-service');
const { initTrainedSkillDir } = require('../../services/skill-dir-manager');
const skillDb = require('../../services/skill-db');
// 1. 初始化用户训练 skill 目录
const skillDir = initTrainedSkillDir(skillName, userId);
console.log(`[TrainSkill] 初始化目录: ${skillDir}`);
// 2. 创建 Job
const job = jobManager.createJob({
type: 'train-create',
skillName,
description,
status: 'queued',
progress: '准备启动训练流水线...',
userId: String(userId),
});
// 3. 注册到 DB(如果不存在)
try {
skillDb.registerSkill({
skillName,
description,
domain: skillName,
scope: '',
keywords: skillName.split(/[-_]/).join(','),
directory: skillDir,
userId: String(userId),
storageType: 'trained',
});
} catch (err) {
if (err.message.includes('already exists')) {
// 更新已有条目的 directory 指向
try { skillDb.updateSkill(skillName, { directory: skillDir, userId: String(userId), storageType: 'trained' }); } catch {}
} else {
console.warn(`[TrainSkill] registerSkill 警告: ${err.message}`);
}
}
// 4. 异步执行 pipeline(不阻塞 Master 回复)
_runTrainPipeline(job, skillName, description, instructions, jobManager, config, userId, skillDir).catch(err => {
console.error(`[TrainSkill] Pipeline 失败: ${err.message}`);
jobManager.updateJob(job.jobId, {
status: 'failed',
error: err.message,
progress: `训练失败: ${err.message}`,
});
});
return {
status: 'started',
jobId: job.jobId,
skillName,
userId: String(userId),
skillDir,
pipeline: 'Hunt → Digest → SkillCreate',
message: `已启动 "${skillName}" 的训练流水线。训练通常需要 5-10 分钟。`,
tip: '使用 list_retrain_jobs 工具可查看进度。',
};
}
/**
* 执行已有 Skill 重训(ReHunt→ReDigest→Evolve)
*/
async function _executeRetrain(skillName, description, instructions, jobManager, config, userId) {
const { InnovatorService } = require('../../services/innovator-service');
const { RetrainPipeline } = require('../../services/retrain-pipeline');
const { resolveTrainedSkillDir } = require('../../services/skill-dir-manager');
const skillsDir = config.skillsCodeDir;
const innovator = new InnovatorService({
skillsDir,
skillsDataDir: config.skillsDataDir,
trainedSkillsDir: config.trainedSkillsDir,
});
const skill = innovator.registry.getSkill(skillName);
if (!skill) {
return {
status: 'error',
message: `Skill "${skillName}" 不存在。请先用 mode="create" 创建。`,
};
}
// 创建 Job
const job = jobManager.createJob({
type: 'retrain',
skillName,
description: instructions || description,
status: 'queued',
progress: '准备启动重训流水线...',
userId: String(userId),
});
innovator.registry.updateSkill(skillName, { status: 'training' });
// 异步执行 retrain pipeline
const pipeline = new RetrainPipeline({
job,
skill,
innovator,
jobManager,
instructions: instructions || description,
userId,
});
pipeline.run().catch(err => {
console.error(`[TrainSkill] Retrain 失败: ${err.message}`);
jobManager.updateJob(job.jobId, {
status: 'failed',
error: err.message,
progress: `重训失败: ${err.message}`,
});
try { innovator.registry.updateSkill(skillName, { status: 'enable' }); } catch {}
});
return {
status: 'started',
jobId: job.jobId,
skillName,
userId: String(userId),
pipeline: 'ReHunt → ReDigest → Evolve',
message: `已启动 "${skillName}" 的重训流水线 (目标 ≥95 分)。`,
tip: '使用 list_retrain_jobs 工具可查看进度。',
};
}
/**
* 新 Skill 训练流水线(RL 闭环版本)
*
* 使用 EvolutionLoop 实现 Perceive → Plan → Execute → Evaluate → Reflect 循环,
* 替代原有的线性 Hunt→Digest→SkillCreate 直通流程。
*
* 产物写入 data/trained_skills/user_{id}/{skillName}/ 目录
*/
async function _runTrainPipeline(job, skillName, description, instructions, jobManager, config, userId, skillDir) {
const { ConversationLogger } = require('../../services/conversation-logger');
const taskMgr = require('../../services/skill-task-manager');
const { randomUUID } = require('crypto');
const { loadConfigFromSkills } = require('../core/skill-config-loader');
const AgentManager = require('../core/agent-manager');
const { InnovatorService } = require('../../services/innovator-service');
const { EvolutionLoop } = require('../../services/evolution-loop');
const skillsDir = config.skillsCodeDir;
const conversationsDir = config.conversationsDir || path.resolve(__dirname, '../../../conversations');
// 日志按用户隔离
const userConvDir = path.join(conversationsDir, `user-${userId}`);
const conversationId = `train-${job.jobId}`;
const messageId = `msg-${Date.now()}-${randomUUID().substring(0, 8)}`;
const logger = new ConversationLogger(userConvDir, conversationId, messageId, { userId });
taskMgr.initTask(skillDir, {
skillName,
description,
source: 'master-train',
jobId: job.jobId,
userId: String(userId),
pipelineType: 'evolution',
});
logger.ensureMeta(`[Train] ${skillName}: ${description.substring(0, 80)}`);
logger.logUserMessage(`[Train] 训练新 Skill (RL Loop): ${skillName}\n${description}\n${instructions}`);
// 加载 Agent 配置 — 使用系统 skills 目录(l3-hunt 等 agent 在 data/skills/ 下)
const backends = JSON.parse(fs.readFileSync(
path.join(path.resolve(__dirname, '../../config'), 'backends.json'), 'utf-8'
));
const agentConfig = loadConfigFromSkills(skillsDir, backends);
// Agent 的 workDir 设为用户训练目录的父目录(user_{id}/),
// 这样 agent 引用 {skillName}/hunt/knowhow.md 就指向正确路径
const userTrainDir = path.dirname(skillDir);
const manager = new AgentManager(agentConfig, userTrainDir);
try {
// ── EvolutionLoop: RL 闭环 ──
const loop = new EvolutionLoop({
skillName,
description,
skillDir,
instructions,
manager,
jobManager,
job,
logger,
taskMgr,
maxIterations: 5,
goalScore: 90,
onData: (agent, chunk) => {
if (chunk?.trim()) logger.logStreamChunk(agent, chunk);
},
});
jobManager.updateJob(job.jobId, {
status: 'evolving',
progress: '🔄 RL 进化循环启动...',
skillName,
conversationId,
});
const result = await loop.run();
// ── Publish ──
const innovator = new InnovatorService({
skillsDir,
skillsDataDir: config.skillsDataDir,
trainedSkillsDir: config.trainedSkillsDir,
});
innovator.autoRegisterNewSkills();
logger.logResult({
type: 'train_skill',
jobId: job.jobId,
skillName,
userId: String(userId),
skillDir,
evolutionResult: {
status: result.status,
finalScore: result.finalScore,
bestScore: result.bestScore,
iterations: result.iterations,
},
});
const statusEmoji = result.status === 'goal_achieved' ? '✅' : '⚠️';
const statusText = result.status === 'goal_achieved'
? `达标 (${result.finalScore}分)`
: result.status === 'stagnant'
? `停滞 (最佳 ${result.bestScore}分)`
: `迭代用尽 (最佳 ${result.bestScore}分)`;
jobManager.updateJob(job.jobId, {
status: 'completed',
progress: `${statusEmoji} 训练完成:${skillName} — ${statusText}`,
conversationId,
result: {
skillName,
userId: String(userId),
skillDir,
output: `Skill 训练完成 (RL Loop: ${result.iterations}轮, ${statusText})`,
evolutionStatus: result.status,
finalScore: result.finalScore,
bestScore: result.bestScore,
iterations: result.iterations,
},
});
try { await manager.closeAllSessions(); } catch {}
console.log(`[TrainSkill] ${statusEmoji} ${skillName} 训练完成 → ${skillDir} (${statusText})`);
} catch (err) {
logger.logError(err.message);
const nextStep = taskMgr.getNextPendingStep(skillDir);
if (nextStep) taskMgr.updateStep(skillDir, nextStep, 'failed', err.message);
try { await manager.closeAllSessions(); } catch {}
throw err;
}
}
module.exports = { createTrainSkillTool };