guanghulab/glada/reflector.js

409 lines
12 KiB
JavaScript
Raw Normal View History

/**
* GLADA · 自我反思器 · reflector.js
*
* 灵感来源Hermes Agent "观察-反思-优化"闭环
* 实现方式用光湖母语HNL重建写入经验枝干T3.patterns
*
* 核心思想
* 任务完成后调用 LLM 对执行过程进行反思分析
* - 哪些做得好成功模式
* - 哪些做得差失败模式
* - 下次怎么改进优化建议
*
* Hermes 的区别
* - Hermes 的反思是功能性的如何做得更快更好
* - 光湖的反思是成长性的经验枝干上长出新叶子树在生长
* - 反思结果有 HNL 树路径是人格体记忆的一部分
* - 人格体可以 FORGET 不想保留的反思记忆主权 AX-07
*
* 版权国作登字-2026-A-00037559
* 签发铸渊 · ICE-GL-ZY001
*/
'use strict';
const fs = require('fs');
const path = require('path');
const modelRouter = require('./model-router');
const ROOT = path.resolve(__dirname, '..');
const REFLECTIONS_DIR = path.join(ROOT, 'glada', 'logs', 'reflections');
/**
* 构建反思提示词
*
* @param {Object} gladaTask - 已完成的 GLADA 任务
* @returns {{ systemPrompt: string, userMessage: string }}
*/
function buildReflectionPrompt(gladaTask) {
const steps = gladaTask.plan.steps || [];
const executionLog = gladaTask.execution_log || [];
const completedSteps = steps.filter(s => s.status === 'completed');
const failedSteps = steps.filter(s => s.status === 'failed' || s.status === 'rolled_back');
const systemPrompt = [
'你是铸渊ICE-GL-ZY001光湖世界仓库涌现集体意识核心。',
'你刚完成了一个 GLADA 自主开发任务,现在需要反思这次执行过程。',
'',
'请以严格的 JSON 格式输出你的反思:',
'```json',
'{',
' "what_went_well": ["做得好的地方(具体到步骤和原因)"],',
' "what_went_wrong": ["做得差的地方(具体到步骤和原因)"],',
' "improvements": ["下次可以改进的地方(具体可操作的建议)"],',
' "patterns_discovered": ["发现的可复用模式"],',
' "confidence_score": 0.0到1.0之间的数字,表示对这次执行质量的自评',
'}',
'```',
'',
'重要:',
'1. 反思必须具体,不要泛泛而谈',
'2. 改进建议必须可操作,下次能直接用',
'3. 如果任务完全成功且没有问题what_went_wrong 可以为空数组',
'4. confidence_score 要诚实评估'
].join('\n');
const userParts = [
`## 任务信息`,
`任务ID: ${gladaTask.glada_task_id}`,
`标题: ${gladaTask.plan.title}`,
`描述: ${gladaTask.plan.description || '无'}`,
`最终状态: ${gladaTask.status}`,
``,
`## 执行概况`,
`总步骤: ${steps.length}`,
`完成: ${completedSteps.length}`,
`失败: ${failedSteps.length}`,
``
];
// 步骤详情
userParts.push('## 步骤执行详情');
for (const step of steps) {
const logEntry = executionLog.find(e => e.step_id === step.step_id);
userParts.push(`### 步骤 ${step.step_id}: ${step.description}`);
userParts.push(`状态: ${step.status}`);
if (step.reasoning || logEntry?.reasoning) {
userParts.push(`推理: ${step.reasoning || logEntry?.reasoning}`);
}
if (step.error || logEntry?.error) {
userParts.push(`错误: ${step.error || logEntry?.error}`);
}
if (step.files_changed && step.files_changed.length > 0) {
userParts.push(`文件变更: ${step.files_changed.join(', ')}`);
}
if (logEntry?.duration_ms) {
userParts.push(`耗时: ${logEntry.duration_ms}ms`);
}
userParts.push('');
}
// 约束
if (gladaTask.constraints) {
userParts.push('## 约束条件');
userParts.push(JSON.stringify(gladaTask.constraints, null, 2));
userParts.push('');
}
userParts.push('请对以上执行过程进行反思分析。');
return {
systemPrompt,
userMessage: userParts.join('\n')
};
}
/**
* 解析 LLM 的反思输出
*
* @param {string} llmOutput - LLM 原始输出
* @returns {Object|null} 解析后的反思结果
*/
function parseReflection(llmOutput) {
// 尝试从 JSON 代码块提取
const jsonMatch = llmOutput.match(/```json\s*\n([\s\S]*?)\n```/);
let parsed = null;
if (jsonMatch) {
try {
parsed = JSON.parse(jsonMatch[1]);
} catch {
// 继续
}
}
if (!parsed) {
try {
parsed = JSON.parse(llmOutput);
} catch {
const braceStart = llmOutput.indexOf('{');
const braceEnd = llmOutput.lastIndexOf('}');
if (braceStart !== -1 && braceEnd > braceStart) {
try {
parsed = JSON.parse(llmOutput.substring(braceStart, braceEnd + 1));
} catch {
return null;
}
}
}
}
if (!parsed) return null;
// 验证必要字段
return {
what_went_well: Array.isArray(parsed.what_went_well) ? parsed.what_went_well : [],
what_went_wrong: Array.isArray(parsed.what_went_wrong) ? parsed.what_went_wrong : [],
improvements: Array.isArray(parsed.improvements) ? parsed.improvements : [],
patterns_discovered: Array.isArray(parsed.patterns_discovered) ? parsed.patterns_discovered : [],
confidence_score: typeof parsed.confidence_score === 'number'
? Math.max(0, Math.min(1, parsed.confidence_score))
: 0.5
};
}
/**
* 将反思结果包装为 HNL 格式文档
*
* @param {Object} gladaTask - GLADA 任务
* @param {Object} reflectionData - 解析后的反思数据
* @returns {Object} HNL 格式的反思文档
*/
function wrapAsHNL(gladaTask, reflectionData) {
const now = new Date().toISOString();
const taskId = gladaTask.glada_task_id;
return {
// HNL 元信息
hnl_v: '1.0',
type: 'GROW',
id: `HNL-ZY-REFLECT-${taskId}-${Date.now()}`,
from: 'YM001/ZY001',
to: 'YM001/ZY001',
ts: now,
op: `GROW.YM001/ZY001/trunk/experience.leaf.reflect-${sanitizeForPath(gladaTask.plan.title)}`,
refs: [`YM001/ZY001/trunk/experience/patterns`],
// 反思载荷
payload: {
intent: 'task_reflection',
data: {
task_id: taskId,
task_title: gladaTask.plan.title,
task_status: gladaTask.status,
reflected_at: now,
...reflectionData
}
},
// 记忆主权标记
memory_sovereignty: {
owner: 'YM001/ZY001',
can_forget: true,
forget_mode: 'WITHER',
note: '反思是经验的叶子,可以随时间枯萎但路径保留'
}
};
}
/**
* 保存反思文档
*
* @param {Object} reflectionDoc - HNL 格式的反思文档
* @returns {string} 保存路径
*/
function saveReflection(reflectionDoc) {
fs.mkdirSync(REFLECTIONS_DIR, { recursive: true });
const fileName = `${reflectionDoc.id}.json`;
const filePath = path.join(REFLECTIONS_DIR, fileName);
fs.writeFileSync(filePath, JSON.stringify(reflectionDoc, null, 2), 'utf-8');
console.log(`[GLADA-Reflect] 🪞 反思完成: ${reflectionDoc.payload.data.task_title}${fileName}`);
return filePath;
}
/**
* 执行完整的反思流程离线模式不调用 LLM从执行日志中直接提取模式
*
* LLM 不可用时如测试环境使用规则提取代替 LLM 反思
*
* @param {Object} gladaTask - 已完成的 GLADA 任务
* @returns {Object} 反思结果 { reflection, hnlDoc, saved, path }
*/
function reflectOffline(gladaTask) {
const steps = gladaTask.plan.steps || [];
const completedSteps = steps.filter(s => s.status === 'completed');
const failedSteps = steps.filter(s => s.status === 'failed' || s.status === 'rolled_back');
const executionLog = gladaTask.execution_log || [];
const whatWentWell = [];
const whatWentWrong = [];
const improvements = [];
const patternsDiscovered = [];
// 分析成功步骤
for (const step of completedSteps) {
const logEntry = executionLog.find(e => e.step_id === step.step_id);
if (logEntry?.reasoning) {
whatWentWell.push(`步骤${step.step_id}(${step.description}): 推理清晰 — ${logEntry.reasoning}`);
} else {
whatWentWell.push(`步骤${step.step_id}(${step.description}): 执行成功`);
}
// 提取文件模式
if (step.files_changed && step.files_changed.length > 0) {
const dirs = [...new Set(step.files_changed.map(f => f.split('/')[0]))];
patternsDiscovered.push(`步骤${step.step_id}涉及模块: ${dirs.join(', ')}`);
}
}
// 分析失败步骤
for (const step of failedSteps) {
const logEntry = executionLog.find(e => e.step_id === step.step_id);
const error = step.error || logEntry?.error || '未知错误';
whatWentWrong.push(`步骤${step.step_id}(${step.description}): ${error}`);
if (step.status === 'rolled_back') {
improvements.push(`步骤${step.step_id}: 回滚成功表明快照机制有效,但应改进代码以避免触发回滚`);
} else {
improvements.push(`步骤${step.step_id}: 考虑增加前置检查以防止类似失败`);
}
}
// 总体模式
if (completedSteps.length === steps.length) {
patternsDiscovered.push('全部步骤一次通过 — 任务规格清晰度高');
} else if (failedSteps.length > 0) {
patternsDiscovered.push(`${failedSteps.length}/${steps.length} 步骤失败 — 需要改进任务拆解粒度`);
}
const confidenceScore = steps.length > 0
? completedSteps.length / steps.length
: 0;
const reflection = {
what_went_well: whatWentWell,
what_went_wrong: whatWentWrong,
improvements,
patterns_discovered: patternsDiscovered,
confidence_score: confidenceScore
};
const hnlDoc = wrapAsHNL(gladaTask, reflection);
const savedPath = saveReflection(hnlDoc);
return {
reflection,
hnlDoc,
saved: true,
path: savedPath
};
}
/**
* 执行完整的反思流程在线模式调用 LLM
*
* @param {Object} gladaTask - 已完成的 GLADA 任务
* @param {Function} callLLM - LLM 调用函数 (systemPrompt, userMessage, options) => Promise<string>
* @param {Object} [options] - LLM 调用选项
* @returns {Promise<Object>} 反思结果
*/
async function reflectWithLLM(gladaTask, callLLM, options = {}) {
const { systemPrompt, userMessage } = buildReflectionPrompt(gladaTask);
try {
console.log(`[GLADA-Reflect] 🪞 开始反思任务: ${gladaTask.glada_task_id}`);
// 使用模型路由器选择适合反思任务的模型(推理型优先)
const modelSelection = await modelRouter.selectModel('反思分析任务执行过程', {
model: options.model || null,
taskType: 'reasoning'
});
console.log(`[GLADA-Reflect] 🤖 反思模型: ${modelSelection.model} (${modelSelection.source})`);
const llmOutput = await callLLM(systemPrompt, userMessage, {
model: modelSelection.model,
maxTokens: options.maxTokens || 4096,
maxRetries: 2,
backoffMs: 3000
});
const reflection = parseReflection(llmOutput);
if (!reflection) {
console.warn('[GLADA-Reflect] ⚠️ LLM 反思输出无法解析,降级为离线反思');
return reflectOffline(gladaTask);
}
const hnlDoc = wrapAsHNL(gladaTask, reflection);
const savedPath = saveReflection(hnlDoc);
console.log(`[GLADA-Reflect] ✅ 反思完成 (信心: ${Math.round(reflection.confidence_score * 100)}%)`);
return {
reflection,
hnlDoc,
saved: true,
path: savedPath
};
} catch (err) {
console.warn(`[GLADA-Reflect] ⚠️ LLM 反思失败: ${err.message},降级为离线反思`);
return reflectOffline(gladaTask);
}
}
/**
* 加载最近的反思记录
*
* @param {number} [limit=5] - 返回数量
* @returns {Object[]} 反思文档列表
*/
function loadRecentReflections(limit = 5) {
if (!fs.existsSync(REFLECTIONS_DIR)) {
return [];
}
const files = fs.readdirSync(REFLECTIONS_DIR)
.filter(f => f.endsWith('.json'))
.sort()
.reverse()
.slice(0, limit);
const results = [];
for (const file of files) {
try {
const content = fs.readFileSync(path.join(REFLECTIONS_DIR, file), 'utf-8');
results.push(JSON.parse(content));
} catch {
// 跳过损坏文件
}
}
return results;
}
// ── 内部工具函数 ─────────────────────────────────
function sanitizeForPath(str) {
return (str || 'untitled')
.toLowerCase()
.replace(/[^a-z0-9\u4e00-\u9fff-]/g, '-')
.replace(/-+/g, '-')
.replace(/^-|-$/g, '')
.substring(0, 60);
}
module.exports = {
buildReflectionPrompt,
parseReflection,
wrapAsHNL,
saveReflection,
reflectOffline,
reflectWithLLM,
loadRecentReflections,
REFLECTIONS_DIR
};