mutator.js 10.4 KB
/**
 * Darwinian Mutator — 达尔文式变异算子
 *
 * 变异来源:
 *   1. 随机扰动 (随机丢弃 5-20% + shuffle)
 *   2. Agent 智能变异 (LLM 生成新知识片段)
 *   3. 学习事件注入 (如有 learning_events 可用)
 *
 * 父代基因组保持不变,变异创建新子代。
 */
const evolutionQueries = require('../queries').evolutionQueries;
const { generateMutationKnowledge } = require('../evaluator');

// ════════════════════════════════════════
// 工具函数
// ════════════════════════════════════════

function parseDim(raw) {
  if (!raw) return {};
  return typeof raw === 'string' ? JSON.parse(raw) : raw;
}

function deepClone(obj) {
  return JSON.parse(JSON.stringify(obj));
}

/**
 * 随机扰动: 随机丢弃/重排数组元素
 */
function perturbArray(arr, dropRate = 0.1, shuffle = false) {
  if (!Array.isArray(arr) || arr.length === 0) return arr;

  let result = arr.filter(() => Math.random() > dropRate);

  // 保底: 至少保留一半
  if (result.length < arr.length / 2) {
    const shuffled = [...arr].sort(() => Math.random() - 0.5);
    result = shuffled.slice(0, Math.ceil(arr.length / 2));
  }

  if (shuffle) {
    for (let i = result.length - 1; i > 0; i--) {
      const j = Math.floor(Math.random() * (i + 1));
      [result[i], result[j]] = [result[j], result[i]];
    }
  }

  return result;
}

// ════════════════════════════════════════
// 维度→可变异字段映射
// ════════════════════════════════════════

const DIMENSION_FIELDS = {
  mental_models: ['causalChains', 'ironLaws', 'analogies'],
  pattern_library: ['solutionPatterns', 'problemPatterns', 'classificationRules'],
  judgment: ['decisionRules', 'exclusions', 'boundaries', 'tradeoffs'],
  tool_fluency: ['coreTools', 'idioms', 'dataAccess', 'automations'],
  taste: ['qualitySignals', 'exemplars', 'outputStandards'],
  scar_tissue: ['failures', 'antiPatterns', 'antiRationalizations'],
};

// ════════════════════════════════════════
// 核心变异函数
// ════════════════════════════════════════

/**
 * 对基因组应用随机扰动
 */
function applyPerturbation(child, dropRate) {
  for (const [dim, fields] of Object.entries(DIMENSION_FIELDS)) {
    // 因果链和铁律更珍贵,少丢弃
    const rate = dim === 'mental_models' ? dropRate * 0.5 : dropRate;
    for (const field of fields) {
      if (child[dim][field]) {
        child[dim][field] = perturbArray(child[dim][field], rate, Math.random() > 0.7);
      }
    }
  }
}

/**
 * 用 Agent 为指定维度生成新知识片段
 */
async function applyAgentMutation(child, genome, dimension) {
  try {
    const result = await generateMutationKnowledge(genome, dimension);
    const newEntries = result.newEntries || [];

    if (newEntries.length === 0) return 0;

    // 找到该维度中最适合注入的字段
    const fields = DIMENSION_FIELDS[dimension] || [];
    const targetField = fields.find(f => Array.isArray(child[dimension][f])) || fields[0];

    if (targetField) {
      if (!Array.isArray(child[dimension][targetField])) {
        child[dimension][targetField] = [];
      }
      child[dimension][targetField].push(...newEntries.map(e => ({
        ...e,
        mutationSource: 'agent',
      })));
    }

    return newEntries.length;
  } catch (err) {
    console.error(`  ⚠️  Agent 变异 ${dimension} 失败: ${err.message}`);
    return 0;
  }
}

/**
 * 注入 learning_events(如有可用)
 */
function applyLearningEvents(child, scarLearnings, patternLearnings, mentalModelLearnings) {
  if (!child.scar_tissue.failures) child.scar_tissue.failures = [];
  if (!child.scar_tissue.antiPatterns) child.scar_tissue.antiPatterns = [];
  if (!child.pattern_library.solutionPatterns) child.pattern_library.solutionPatterns = [];

  for (const event of scarLearnings) {
    const content = typeof event.content === 'string' ? JSON.parse(event.content) : event.content;
    child.scar_tissue.failures.push({
      incident: content.rawError?.slice(0, 200) || content.lesson,
      lesson: content.lesson,
      preventionRule: content.preventionRule,
      mutationSource: 'darwinian',
    });
    if (content.severity === 'high') {
      child.scar_tissue.antiPatterns.push({
        pattern: content.category,
        correction: content.preventionRule,
        mutationSource: 'darwinian',
      });
    }
  }

  for (const event of patternLearnings) {
    const content = typeof event.content === 'string' ? JSON.parse(event.content) : event.content;
    child.pattern_library.solutionPatterns.push({ ...content, mutationSource: 'darwinian' });
  }

  if (!child.mental_models.causalChains) child.mental_models.causalChains = [];
  if (!child.mental_models.analogies) child.mental_models.analogies = [];
  for (const event of (mentalModelLearnings || [])) {
    const content = typeof event.content === 'string' ? JSON.parse(event.content) : event.content;
    if (content.type === 'causalChain') child.mental_models.causalChains.push({ ...content, mutationSource: 'darwinian' });
    else if (content.type === 'analogy') child.mental_models.analogies.push({ ...content, mutationSource: 'darwinian' });
  }
}

/**
 * 创建一个变异子代
 *
 * @param {object} parent - 父代基因组
 * @param {object} options
 * @param {number} options.dropRate - 随机丢弃率 (0-1)
 * @param {boolean} options.useAgent - 是否使用 Agent 智能变异
 * @param {string} options.agentDimension - Agent 变异的目标维度
 * @param {object[]} options.scarLearnings - 伤疤学习事件
 * @param {object[]} options.patternLearnings - 模式学习事件
 * @param {object[]} options.mentalModelLearnings - 心智模型学习事件
 * @returns {object} 子代 6 维数据
 */
async function mutateGenome(parent, options = {}) {
  const {
    dropRate = 0.1,
    useAgent = false,
    agentDimension = null,
    scarLearnings = [],
    patternLearnings = [],
    mentalModelLearnings = [],
  } = options;

  // 1. 深拷贝父代
  const child = {};
  for (const dim of Object.keys(DIMENSION_FIELDS)) {
    child[dim] = deepClone(parseDim(parent[dim]));
  }

  // 2. 随机扰动
  applyPerturbation(child, dropRate);

  // 3. Agent 智能变异(如启用)
  if (useAgent && agentDimension) {
    await applyAgentMutation(child, parent, agentDimension);
  }

  // 4. 注入学习事件
  applyLearningEvents(child, scarLearnings, patternLearnings, mentalModelLearnings);

  return child;
}

// ════════════════════════════════════════
// 公开 API
// ════════════════════════════════════════

/**
 * 为 Run 中所有存活基因组创建变异子代
 *
 * 每个子代的变异策略随机选择:
 *   - 30% 纯随机扰动 (强 drop)
 *   - 40% Agent 智能变异 (选一个弱维度增强)
 *   - 30% 学习事件注入 (如有可用)
 */
async function applyMutations(runId, generation) {
  const parents = await evolutionQueries.getGenomesByRun(runId, { aliveOnly: true });
  if (parents.length === 0) return [];

  // 收集可用的学习事件
  const scarEvents = await evolutionQueries.getLearningEvents(runId, { dimension: 'scar_tissue', applied: false });
  const patternEvents = await evolutionQueries.getLearningEvents(runId, { dimension: 'pattern_library', applied: false });
  const mmEvents = await evolutionQueries.getLearningEvents(runId, { dimension: 'mental_models', applied: false });
  const hasLearnings = scarEvents.length + patternEvents.length + mmEvents.length > 0;

  console.log(`  🧬 变异 ${parents.length} 个父代: ${scarEvents.length} 伤疤 + ${patternEvents.length} 模式 + ${mmEvents.length} 心智模型`);

  const dimensions = Object.keys(DIMENSION_FIELDS);
  const children = [];

  for (const parent of parents) {
    const roll = Math.random();
    let strategy;
    let options = { dropRate: 0.05 + Math.random() * 0.15 };

    if (roll < 0.3) {
      // 纯随机扰动 (强 drop)
      strategy = 'perturb';
      options.dropRate = 0.1 + Math.random() * 0.2;
    } else if (roll < 0.7) {
      // Agent 智能变异
      strategy = 'agent';
      options.useAgent = true;
      // 随机选一个维度
      options.agentDimension = dimensions[Math.floor(Math.random() * dimensions.length)];
    } else if (hasLearnings) {
      // 学习事件注入
      strategy = 'learning';
      const samplingRate = 0.6 + Math.random() * 0.3;
      const sample = (events) => events.filter(e =>
        e.genome_id === parent.id || (!e.genome_id && Math.random() < samplingRate),
      );
      options.scarLearnings = sample(scarEvents);
      options.patternLearnings = sample(patternEvents);
      options.mentalModelLearnings = sample(mmEvents);
    } else {
      // 没有学习事件,改用 Agent
      strategy = 'agent';
      options.useAgent = true;
      options.agentDimension = dimensions[Math.floor(Math.random() * dimensions.length)];
    }

    try {
      const childDims = await mutateGenome(parent, options);
      const child = await evolutionQueries.insertGenome({
        runId,
        skillName: parent.skill_name,
        generation,
        parentIds: [parent.id],
        mentalModels: childDims.mental_models,
        patternLibrary: childDims.pattern_library,
        judgment: childDims.judgment,
        toolFluency: childDims.tool_fluency,
        taste: childDims.taste,
        scarTissue: childDims.scar_tissue,
      });

      children.push(child);
      console.log(`    ${parent.id.slice(0, 6)} → ${child.id.slice(0, 6)} [${strategy}${options.agentDimension ? ':' + options.agentDimension : ''}]`);
    } catch (err) {
      console.error(`    ❌ ${parent.id.slice(0, 6)} 变异失败: ${err.message}`);
    }
  }

  // 标记学习事件已应用
  const allEventIds = [...scarEvents, ...patternEvents, ...mmEvents].map(e => e.id);
  if (allEventIds.length > 0) {
    await evolutionQueries.markLearningApplied(allEventIds);
  }

  console.log(`  ✅ 变异完成: ${children.length} 个子代`);
  return children;
}

module.exports = {
  mutateGenome,
  perturbArray,
  applyMutations,
};