evolution-memory.js 4.94 KB
/**
 * EvolutionMemory — Skill 进化历史记忆模块
 *
 * 记录每个 Skill 的进化迭代历史,包括策略、分数、教训。
 * 为 RL Loop 的 Reason & Plan 阶段提供经验参考。
 *
 * 存储位置: {skillDir}/evolution_memory.json
 */

const fs = require('fs');
const path = require('path');

const MEMORY_FILE = 'evolution_memory.json';
const MAX_RECORDS = 50; // 保留最近 50 条迭代记录

class EvolutionMemory {
  /**
   * @param {string} skillDir - Skill 目录绝对路径
   */
  constructor(skillDir) {
    this.skillDir = skillDir;
    this.filePath = path.join(skillDir, MEMORY_FILE);
    this._data = null;
  }

  /**
   * 读取完整历史
   * @returns {{ iterations: Array, bestScore: number|null, totalAttempts: number }}
   */
  getHistory() {
    const data = this._load();
    return {
      iterations: data.iterations || [],
      bestScore: data.bestScore || null,
      totalAttempts: data.totalAttempts || 0,
    };
  }

  /**
   * 从历史失败中提取教训
   * @returns {string[]} 教训列表
   */
  getLessons() {
    const data = this._load();
    const lessons = [];
    for (const iter of (data.iterations || [])) {
      if (iter.lessonsLearned && Array.isArray(iter.lessonsLearned)) {
        lessons.push(...iter.lessonsLearned);
      }
    }
    // 去重
    return [...new Set(lessons)];
  }

  /**
   * 获取历史最高分时的策略
   * @returns {object|null}
   */
  getBestStrategy() {
    const data = this._load();
    if (!data.iterations || data.iterations.length === 0) return null;

    let best = null;
    for (const iter of data.iterations) {
      if (!best || (iter.score && iter.score > (best.score || 0))) {
        best = iter;
      }
    }
    return best ? best.strategy : null;
  }

  /**
   * 获取最近 N 轮的弱项维度 (用于聚焦)
   * @param {number} n
   * @returns {string[]}
   */
  getRecentWeakDimensions(n = 3) {
    const data = this._load();
    const iterations = data.iterations || [];
    const recent = iterations.slice(-n);
    const dims = [];
    for (const iter of recent) {
      if (iter.weakDimensions && Array.isArray(iter.weakDimensions)) {
        dims.push(...iter.weakDimensions);
      }
    }
    // 按频次排序
    const freq = {};
    for (const d of dims) { freq[d] = (freq[d] || 0) + 1; }
    return Object.entries(freq)
      .sort((a, b) => b[1] - a[1])
      .map(e => e[0]);
  }

  /**
   * 记录一轮进化结果
   * @param {object} record
   * @param {number} record.iteration - 迭代轮次
   * @param {object} record.strategy - 使用的策略
   * @param {number} record.score - 本轮分数
   * @param {string[]} [record.weakDimensions] - 弱项维度
   * @param {string[]} [record.strongDimensions] - 强项维度
   * @param {object[]} [record.adjustments] - 调整记录
   * @param {string[]} [record.lessonsLearned] - 教训
   */
  record(record) {
    const data = this._load();

    const entry = {
      ...record,
      timestamp: new Date().toISOString(),
    };

    data.iterations = data.iterations || [];
    data.iterations.push(entry);

    // 更新聚合指标
    data.totalAttempts = (data.totalAttempts || 0) + 1;
    if (!data.bestScore || (record.score && record.score > data.bestScore)) {
      data.bestScore = record.score;
    }

    // 裁剪历史
    if (data.iterations.length > MAX_RECORDS) {
      data.iterations = data.iterations.slice(-MAX_RECORDS);
    }

    this._save(data);
  }

  /**
   * 检测是否处于停滞 (连续 N 轮分数变化 < threshold)
   * @param {number} rounds - 检查的轮次数
   * @param {number} threshold - 分数变化阈值
   * @returns {boolean}
   */
  isStagnant(rounds = 3, threshold = 2) {
    const data = this._load();
    const iterations = data.iterations || [];
    if (iterations.length < rounds) return false;

    const recent = iterations.slice(-rounds);
    const scores = recent.map(r => r.score).filter(s => typeof s === 'number');
    if (scores.length < rounds) return false;

    for (let i = 1; i < scores.length; i++) {
      if (Math.abs(scores[i] - scores[i - 1]) >= threshold) {
        return false;
      }
    }
    return true;
  }

  // ── 内部方法 ──

  _load() {
    if (this._data) return this._data;
    try {
      if (fs.existsSync(this.filePath)) {
        this._data = JSON.parse(fs.readFileSync(this.filePath, 'utf-8'));
        return this._data;
      }
    } catch (err) {
      console.warn(`[EvolutionMemory] 读取失败: ${err.message}`);
    }
    this._data = { iterations: [], bestScore: null, totalAttempts: 0 };
    return this._data;
  }

  _save(data) {
    try {
      const dir = path.dirname(this.filePath);
      if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
      fs.writeFileSync(this.filePath, JSON.stringify(data, null, 2), 'utf-8');
      this._data = data;
    } catch (err) {
      console.error(`[EvolutionMemory] 保存失败: ${err.message}`);
    }
  }
}

module.exports = { EvolutionMemory, MEMORY_FILE, MAX_RECORDS };