/** * EvolutionQueries - 进化系统数据查询层 * * 使用方式: * const { evolutionQueries, initQueries } = require('@mas/evolution'); * initQueries(knexInstance); */ let knex = null; const RUNS = 'evolution_runs'; const GENOMES = 'skill_genomes'; const EVALUATIONS = 'fitness_evaluations'; const BENCHMARKS = 'evolution_benchmarks'; const LEARNING = 'learning_events'; function _knex(...args) { if (!knex) throw new Error('EvolutionQueries: knex 未初始化,请先调用 initQueries(knexInstance)'); return knex(...args); } Object.defineProperty(_knex, 'fn', { get() { return knex.fn; } }); class EvolutionQueries { // ══════════════════════════════════════ // Evolution Runs // ══════════════════════════════════════ async createRun(skillName, config = {}) { const [row] = await _knex(RUNS) .insert({ skill_name: skillName, config: JSON.stringify(config), max_generations: config.maxGenerations || 20, population_size: config.populationSize || 8, }) .returning('*'); return row; } async getRuns({ page = 1, pageSize = 20, status, skillName } = {}) { const base = _knex(RUNS); if (status) base.where({ status }); if (skillName) base.where({ skill_name: skillName }); const [{ count }] = await base.clone().count(); const total = parseInt(count); const runs = await base.clone() .orderBy('created_at', 'desc') .limit(pageSize) .offset((page - 1) * pageSize); return { runs, total, page, pageSize, totalPages: Math.ceil(total / pageSize) }; } async getRun(runId) { return _knex(RUNS).where({ id: runId }).first(); } async updateRun(runId, updates) { await _knex(RUNS).where({ id: runId }).update(updates); } // ══════════════════════════════════════ // Skill Genomes // ══════════════════════════════════════ async insertGenome(genome) { const [row] = await _knex(GENOMES) .insert({ run_id: genome.runId, skill_name: genome.skillName, generation: genome.generation, parent_ids: genome.parentIds || [], mental_models: JSON.stringify(genome.mentalModels || {}), pattern_library: JSON.stringify(genome.patternLibrary || {}), judgment: JSON.stringify(genome.judgment || {}), tool_fluency: JSON.stringify(genome.toolFluency || {}), taste: JSON.stringify(genome.taste || {}), scar_tissue: JSON.stringify(genome.scarTissue || {}), fitness_scores: JSON.stringify(genome.fitnessScores || {}), overall_fitness: genome.overallFitness, is_elite: genome.isElite || false, }) .returning('*'); return row; } async getGenomesByRun(runId, { generation, aliveOnly = false } = {}) { const query = _knex(GENOMES).where({ run_id: runId }); if (generation !== undefined) query.andWhere({ generation }); if (aliveOnly) query.andWhere({ is_alive: true }); return query.orderBy('overall_fitness', 'desc'); } async getGenome(genomeId) { return _knex(GENOMES).where({ id: genomeId }).first(); } async getEliteGenomes(runId) { return _knex(GENOMES) .where({ run_id: runId, is_elite: true }) .orderBy('overall_fitness', 'desc'); } async getBestGenome(runId) { return _knex(GENOMES) .where({ run_id: runId }) .orderBy('overall_fitness', 'desc') .first(); } async updateGenomeFitness(genomeId, fitnessScores, overallFitness) { await _knex(GENOMES).where({ id: genomeId }).update({ fitness_scores: JSON.stringify(fitnessScores), overall_fitness: overallFitness, }); } async markElite(genomeId, isElite = true) { await _knex(GENOMES).where({ id: genomeId }).update({ is_elite: isElite }); } async killGenomes(genomeIds) { await _knex(GENOMES).whereIn('id', genomeIds).update({ is_alive: false }); } /** * 更新基因组的单个维度数据 (F8: 统一 DB 访问层) */ async updateGenomeDimension(genomeId, dimension, data) { const serialized = typeof data === 'string' ? data : JSON.stringify(data); await _knex(GENOMES).where({ id: genomeId }).update({ [dimension]: serialized }); } // ══════════════════════════════════════ // Fitness Evaluations // ══════════════════════════════════════ async insertEvaluation(evaluation) { const [row] = await _knex(EVALUATIONS) .insert({ genome_id: evaluation.genomeId, run_id: evaluation.runId, benchmark_id: evaluation.benchmarkId, generation: evaluation.generation, probe_results: JSON.stringify(evaluation.probeResults || {}), dimension_scores: JSON.stringify(evaluation.dimensionScores || {}), overall_score: evaluation.overallScore, weights: JSON.stringify(evaluation.weights || {}), total_tool_calls: evaluation.totalToolCalls, redundant_calls: evaluation.redundantCalls, execution_time_ms: evaluation.executionTimeMs, token_usage: evaluation.tokenUsage, }) .returning('*'); return row; } async getEvaluationsByRun(runId, { generation } = {}) { const query = _knex(EVALUATIONS).where({ run_id: runId }); if (generation !== undefined) query.andWhere({ generation }); return query.orderBy('evaluated_at', 'desc'); } async getEvaluationsByGenome(genomeId) { return _knex(EVALUATIONS) .where({ genome_id: genomeId }) .orderBy('evaluated_at', 'desc'); } // ══════════════════════════════════════ // Benchmarks // ══════════════════════════════════════ async upsertBenchmark(benchmark) { const existing = await _knex(BENCHMARKS).where({ id: benchmark.id }).first(); if (existing) { await _knex(BENCHMARKS).where({ id: benchmark.id }).update({ description: benchmark.description, scenario: benchmark.scenario || null, evaluation_criteria: benchmark.evaluationCriteria ? JSON.stringify(benchmark.evaluationCriteria) : undefined, expected_behaviors: benchmark.expectedBehaviors || undefined, difficulty: benchmark.difficulty, probes: JSON.stringify(benchmark.probes || []), pass_threshold: benchmark.passThreshold, is_active: benchmark.isActive !== false, updated_at: _knex.fn.now(), }); } else { await _knex(BENCHMARKS).insert({ id: benchmark.id, skill_name: benchmark.skillName, description: benchmark.description, scenario: benchmark.scenario || null, evaluation_criteria: benchmark.evaluationCriteria ? JSON.stringify(benchmark.evaluationCriteria) : '{}', expected_behaviors: benchmark.expectedBehaviors || [], difficulty: benchmark.difficulty || 1, probes: JSON.stringify(benchmark.probes || []), pass_threshold: benchmark.passThreshold || 0.6, }); } return _knex(BENCHMARKS).where({ id: benchmark.id }).first(); } async getBenchmarks(skillName) { const query = _knex(BENCHMARKS).where({ is_active: true }); if (skillName) query.andWhere({ skill_name: skillName }); return query.orderBy('difficulty'); } // ══════════════════════════════════════ // Learning Events // ══════════════════════════════════════ async insertLearning(event) { const [row] = await _knex(LEARNING) .insert({ genome_id: event.genomeId, run_id: event.runId, event_type: event.eventType, dimension: event.dimension, content: JSON.stringify(event.content), source: event.source, }) .returning('*'); return row; } async getLearningEvents(runId, { dimension, eventType, applied } = {}) { const query = _knex(LEARNING).where({ run_id: runId }); if (dimension) query.andWhere({ dimension }); if (eventType) query.andWhere({ event_type: eventType }); if (applied !== undefined) query.andWhere({ applied }); return query.orderBy('created_at', 'desc'); } async markLearningApplied(eventIds) { await _knex(LEARNING).whereIn('id', eventIds).update({ applied: true }); } // ══════════════════════════════════════ // Dashboard 聚合查询 // ══════════════════════════════════════ async getRunStats(runId) { const run = await this.getRun(runId); if (!run) return null; const [genomeCount] = await _knex(GENOMES).where({ run_id: runId }).count(); const [aliveCount] = await _knex(GENOMES).where({ run_id: runId, is_alive: true }).count(); const [evalCount] = await _knex(EVALUATIONS).where({ run_id: runId }).count(); const [learningCount] = await _knex(LEARNING).where({ run_id: runId }).count(); const best = await this.getBestGenome(runId); // 每代最佳适应度趋势 const fitnessTrend = await _knex(GENOMES) .select('generation') .max('overall_fitness as best_fitness') .avg('overall_fitness as avg_fitness') .where({ run_id: runId }) .whereNotNull('overall_fitness') .groupBy('generation') .orderBy('generation'); // 各维度平均分趋势 const latestGeneration = run.current_generation; const currentGenomes = await this.getGenomesByRun(runId, { generation: latestGeneration, aliveOnly: true, }); const dimensionAvg = {}; const dimensions = ['mentalModels', 'patternLibrary', 'judgment', 'toolFluency', 'taste', 'scarTissue']; for (const dim of dimensions) { const scores = currentGenomes .map(g => { const fs = typeof g.fitness_scores === 'string' ? JSON.parse(g.fitness_scores) : g.fitness_scores; return fs?.[dim]; }) .filter(s => s !== undefined && s !== null); dimensionAvg[dim] = scores.length > 0 ? scores.reduce((a, b) => a + b, 0) / scores.length : null; } // 学习事件按维度统计 const learningByDimension = await _knex(LEARNING) .select('dimension') .count('* as count') .where({ run_id: runId }) .groupBy('dimension'); return { run, totalGenomes: parseInt(genomeCount.count), aliveGenomes: parseInt(aliveCount.count), totalEvaluations: parseInt(evalCount.count), totalLearnings: parseInt(learningCount.count), bestGenome: best, fitnessTrend, dimensionAvg, learningByDimension, }; } async getOverview() { const runs = await _knex(RUNS) .select('id', 'skill_name', 'status', 'current_generation', 'max_generations', 'best_fitness', 'created_at') .orderBy('created_at', 'desc') .limit(50); const [totalRuns] = await _knex(RUNS).count(); const [activeRuns] = await _knex(RUNS).where({ status: 'running' }).count(); const [totalGenomes] = await _knex(GENOMES).count(); const [totalLearnings] = await _knex(LEARNING).count(); return { runs, stats: { totalRuns: parseInt(totalRuns.count), activeRuns: parseInt(activeRuns.count), totalGenomes: parseInt(totalGenomes.count), totalLearnings: parseInt(totalLearnings.count), }, }; } } const evolutionQueries = new EvolutionQueries(); function initQueries(knexInstance) { knex = knexInstance; } module.exports = { evolutionQueries, initQueries };