evolution.js
5.53 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
/**
* Evolution API Routes
* 进化逻辑从 @mas/evolution 共享包导入。
*/
const express = require('express');
const router = express.Router();
const {
evolutionQueries, forageSkills, RunController,
crossoverGeneration, selectGeneration, evaluateRunFitness,
} = require('@mas/evolution');
// ── 通用 async handler ──
const h = (fn) => async (req, res) => {
try { await fn(req, res); }
catch (e) { console.error(`[Evolution] ${req.method} ${req.path}:`, e.message); res.status(500).json({ error: e.message }); }
};
// ── AgentManager 工厂 ──
function _createAgentManager() {
const AgentManager = require('../src/core/agent-manager');
const path = require('path');
const fs = require('fs');
let backendsConfig = {};
try { backendsConfig = JSON.parse(fs.readFileSync(path.join(__dirname, '../config/backends.json'), 'utf-8')); } catch {}
const clients = {};
for (const [name, launcher] of Object.entries(backendsConfig.launchers || {})) {
clients[name] = {
name, command: launcher.command, acpArgs: launcher.args || [],
acpProtocol: name.includes('claude') ? 'claude-stream' : 'acp',
};
}
return new AgentManager({
agents: { evaluator: { name: 'evaluator', type: 'executor', client: 'kimi', deploy: 'local', mode: 'cli', prompt: null, description: '进化引擎 LLM 评估器' } },
clients, ...backendsConfig,
}, process.cwd());
}
function _makeController(opts = {}) {
return new RunController({ agentManager: _createAgentManager(), ...opts });
}
// ── Queries ──
router.get('/overview', h(async (req, res) => res.json(await evolutionQueries.getOverview())));
router.get('/runs', h(async (req, res) => {
const { page, pageSize, status, skillName } = req.query;
res.json(await evolutionQueries.getRuns({ page: parseInt(page) || 1, pageSize: parseInt(pageSize) || 20, status, skillName }));
}));
router.get('/runs/:id', h(async (req, res) => {
const data = await evolutionQueries.getRunStats(req.params.id);
data ? res.json(data) : res.status(404).json({ error: 'Run not found' });
}));
router.get('/runs/:runId/genomes', h(async (req, res) => {
const { generation, aliveOnly } = req.query;
res.json(await evolutionQueries.getGenomesByRun(req.params.runId, {
generation: generation !== undefined ? parseInt(generation) : undefined,
aliveOnly: aliveOnly === 'true',
}));
}));
router.get('/genomes/:id', h(async (req, res) => {
const genome = await evolutionQueries.getGenome(req.params.id);
genome ? res.json(genome) : res.status(404).json({ error: 'Genome not found' });
}));
router.get('/runs/:runId/evaluations', h(async (req, res) => {
const { generation } = req.query;
res.json(await evolutionQueries.getEvaluationsByRun(req.params.runId, {
generation: generation !== undefined ? parseInt(generation) : undefined,
}));
}));
router.get('/benchmarks', h(async (req, res) => res.json(await evolutionQueries.getBenchmarks(req.query.skillName))));
router.post('/benchmarks', h(async (req, res) => res.json(await evolutionQueries.upsertBenchmark(req.body))));
router.get('/runs/:runId/learnings', h(async (req, res) => {
res.json(await evolutionQueries.getLearningEvents(req.params.runId, { dimension: req.query.dimension, eventType: req.query.eventType }));
}));
// ── Actions ──
router.post('/actions/forage', h(async (req, res) => {
const results = await forageSkills(req.body.skillsDir || undefined);
res.json({ success: true, count: results.length, genomes: results });
}));
router.post('/actions/run-phase1', h(async (req, res) => {
const { skillName, skillsDir } = req.body;
if (!skillName) return res.status(400).json({ error: 'skillName is required' });
res.json(await _makeController({ skillsDir }).runPhase1(skillName));
}));
router.post('/actions/run-phase2', h(async (req, res) => {
const { runId, generations } = req.body;
if (!runId) return res.status(400).json({ error: 'runId is required' });
res.json(await _makeController().runPhase2(runId, generations || 5));
}));
router.post('/actions/crossover', h(async (req, res) => {
const { runId, generation } = req.body;
if (!runId || generation === undefined) return res.status(400).json({ error: 'runId and generation are required' });
const children = await crossoverGeneration(runId, generation);
res.json({ success: true, count: children.length, children: children.map(c => ({ id: c.id, skill_name: c.skill_name })) });
}));
router.post('/actions/select', h(async (req, res) => {
const { runId, eliteCount, survivalRate } = req.body;
if (!runId) return res.status(400).json({ error: 'runId is required' });
res.json({ success: true, ...await selectGeneration(runId, { eliteCount, survivalRate }) });
}));
router.post('/actions/deploy', h(async (req, res) => {
const { runId, skillsDir } = req.body;
if (!runId) return res.status(400).json({ error: 'runId is required' });
const controller = _makeController({
skillsDir: skillsDir || require('path').join(require('os').homedir(), '.gemini/antigravity/skills'),
});
res.json(await controller.deploy(runId));
}));
router.post('/actions/evaluate-fitness', h(async (req, res) => {
const { runId } = req.body;
if (!runId) return res.status(400).json({ error: 'runId is required' });
const results = await evaluateRunFitness(runId);
const best = results.reduce((max, r) => r.overall > max.overall ? r : max, results[0]);
res.json({
success: true, evaluated: results.length, bestFitness: best?.overall,
results: results.map(r => ({ genomeId: r.genomeId, overall: r.overall, ...r.dimensionScores })),
});
}));
module.exports = router;