CodeBench: file-by-file build-validointi vasta kun kaikki tiedostot valmiina

- Vaihe 1: generoi kaikki 4 tiedostoa peräkkäin (konteksti kasvaa)
- Vaihe 2: go build kaikilla tiedostoilla → virheet per tiedosto → korjaus
- Ratkaisee: models.go yksinään ei käänny (ei main-funktiota)
- Virheet ryhmitellään tiedostoittain, korjataan vain viallinen tiedosto
This commit is contained in:
2026-04-15 00:21:36 +03:00
parent 3b1a02a9af
commit a3ea0c2fda

View File

@@ -379,76 +379,90 @@ async function runPipeline(model, scenario, round = 1) {
const codeTokens = isConvert ? 8192 : (LANG === 'rust' ? 12288 : LANG === 'go' ? 10240 : 8192); const codeTokens = isConvert ? 8192 : (LANG === 'rust' ? 12288 : LANG === 'go' ? 10240 : 8192);
let files; let files;
// File-by-file: generoi yksi tiedosto kerrallaan + välitön validointi // File-by-file: generoi yksi tiedosto kerrallaan, build-validointi kun kaikki valmiina
if (FILE_BY_FILE && LCONF.fileByFile) { if (FILE_BY_FILE && LCONF.fileByFile) {
const fbf = LCONF.fileByFile; const fbf = LCONF.fileByFile;
const MAX_FILE_FIX = 2; const MAX_BUILD_FIX = 2;
console.log(` [3/5] Koodigenerointi (file-by-file, ${fbf.length} tiedostoa)...`); console.log(` [3/5] Koodigenerointi (file-by-file, ${fbf.length} tiedostoa)...`);
files = {}; files = {};
let context = ''; let context = '';
// Vaihe 1: generoi jokainen tiedosto
for (const fileDef of fbf) { for (const fileDef of fbf) {
const contextBlock = context ? `\nEXISTING CODE:\n${context}\n` : ''; const contextBlock = context ? `\nEXISTING CODE:\n${context}\n` : '';
const basePrompt = `${goldenExample}\n---\n\nPROJECT REQUIREMENTS:\n${req.text}\n\nJSON SPECIFICATION:\n${JSON.stringify(spec, null, 2)}\n${contextBlock}\nWrite ONLY the file "${fileDef.name}": ${fileDef.desc}\nOutput raw code, no markdown fences, no explanations. Start with "package main".`; const filePrompt = `${goldenExample}\n---\n\nPROJECT REQUIREMENTS:\n${req.text}\n\nJSON SPECIFICATION:\n${JSON.stringify(spec, null, 2)}\n${contextBlock}\nWrite ONLY the file "${fileDef.name}": ${fileDef.desc}\nOutput raw code, no markdown fences, no explanations. Start with "package main".`;
let code = ''; console.log(` [3/5] → ${fileDef.name}...`);
for (let attempt = 0; attempt <= MAX_FILE_FIX; attempt++) { const fileResp = await ollamaChat(model, filePrompt, CODE_SYSTEM, 2048);
const prompt = attempt === 0 ? basePrompt
: `Fix the following Go compilation errors in "${fileDef.name}". Return ONLY the corrected file, no explanations.\n\nERRORS:\n${code.__buildErrors}\n\nCURRENT FILE:\n${code}\n\nOTHER FILES:\n${context}`;
const label = attempt === 0 ? fileDef.name : `${fileDef.name} (fix ${attempt})`;
console.log(` [3/5] → ${label}...`);
const fileResp = await ollamaChat(model, prompt, CODE_SYSTEM, 2048);
timings.push(fileResp); timings.push(fileResp);
code = fileResp.text let code = fileResp.text
.replace(/^```(?:go|golang)?\s*\n?/m, '').replace(/\n?```\s*$/m, '') .replace(/^```(?:go|golang)?\s*\n?/m, '').replace(/\n?```\s*$/m, '')
.replace(/^(?:Here|Sure|Below|This|The|I )[\s\S]*?(?=package\s)/m, '') .replace(/^(?:Here|Sure|Below|This|The|I )[\s\S]*?(?=package\s)/m, '')
.trim(); .trim();
if (!code) break; if (code) {
files[fileDef.name] = code + '\n';
context += `=== ${fileDef.name} ===\n${code}\n\n`;
const loc = code.split('\n').length; const loc = code.split('\n').length;
console.log(` [3/5] ${fileResp.tokens} tok, ${loc} lines, ${fileResp.tokPerSec.toFixed(0)} tok/s`); console.log(` [3/5] ${fileResp.tokens} tok, ${loc} lines, ${fileResp.tokPerSec.toFixed(0)} tok/s`);
}
}
// Välitön validointi: kirjoita tiedostot levylle ja aja go build // Vaihe 2: go build -validointi + per-tiedosto korjaus
if (LANG === 'go' && !fileDef.name.endsWith('_test.go')) { if (LANG === 'go') {
files[fileDef.name] = code + '\n'; for (let buildRound = 0; buildRound < MAX_BUILD_FIX; buildRound++) {
// Kirjoita kaikki tähänastiset tiedostot + go.mod // Kirjoita kaikki tiedostot levylle
const goldenMod = readFileSync(join(GOLDEN_DIR, 'todo-go', 'go.mod'), 'utf-8'); const goldenMod = readFileSync(join(GOLDEN_DIR, 'todo-go', 'go.mod'), 'utf-8');
const modName = spec.project_name?.replace(/[^a-z0-9-]/gi, '-') || 'generated-api'; const modName = spec.project_name?.replace(/[^a-z0-9-]/gi, '-') || 'generated-api';
writeFileSync(join(dir, 'go.mod'), goldenMod.replace(/^module\s+\S+/m, `module ${modName}`)); writeFileSync(join(dir, 'go.mod'), goldenMod.replace(/^module\s+\S+/m, `module ${modName}`));
for (const [fn, c] of Object.entries(files)) { for (const [fn, c] of Object.entries(files)) {
writeFileSync(join(dir, fn), c); writeFileSync(join(dir, fn), c);
} }
// go build tarkistus
try { try {
execSync( execSync(
`docker run --rm --entrypoint sh -v "${dir}:/src:ro" ${LCONF.dockerImage} -c "cp -r /src/* . && go mod tidy 2>&1 && go build ./... 2>&1"`, `docker run --rm --entrypoint sh -v "${dir}:/src:ro" ${LCONF.dockerImage} -c "cp -r /src/* . && go mod tidy 2>&1 && go build ./... 2>&1"`,
{ timeout: 60000, encoding: 'utf-8', stdio: 'pipe' } { timeout: 60000, encoding: 'utf-8', stdio: 'pipe' }
); );
console.log(` [3/5] ✓ kääntyy`); console.log(` [3/5] ✓ kääntyy`);
break; // OK — seuraava tiedosto
} catch (e) {
const buildErrors = (e.stdout || e.stderr || '').split('\n').filter(l => /\.go:\d+/.test(l)).slice(0, 10).join('\n');
if (!buildErrors || attempt >= MAX_FILE_FIX) {
console.log(` [3/5] ⚠ käännösvirhe (ei korjata)`);
break; break;
} catch (e) {
const allErrors = (e.stdout || e.stderr || '').split('\n').filter(l => /\.go:\d+/.test(l));
if (allErrors.length === 0) { console.log(` [3/5] ⚠ build failed`); break; }
// Ryhmittele virheet tiedostoittain
const errorsByFile = {};
for (const line of allErrors) {
const m = line.match(/\.\/(\S+\.go):\d+/);
if (m) { (errorsByFile[m[1]] = errorsByFile[m[1]] || []).push(line); }
}
const filesToFix = Object.keys(errorsByFile).filter(f => !f.endsWith('_test.go'));
if (filesToFix.length === 0) break;
console.log(` [3/5] ✗ ${allErrors.length} errors in ${filesToFix.join(', ')} → fixing`);
for (const fname of filesToFix) {
const errors = errorsByFile[fname].slice(0, 10).join('\n');
const fixPrompt = `Fix the following Go compilation errors in "${fname}". Return ONLY the corrected file, no explanations.\n\nERRORS:\n${errors}\n\nCURRENT FILE:\n${files[fname]}\n\nOTHER FILES:\n${Object.entries(files).filter(([f]) => f !== fname).map(([f, c]) => `=== ${f} ===\n${c}`).join('\n\n')}`;
console.log(` [3/5] → ${fname} (fix)...`);
const fixResp = await ollamaChat(model, fixPrompt, CODE_SYSTEM, 2048);
timings.push(fixResp);
let fixed = fixResp.text
.replace(/^```(?:go|golang)?\s*\n?/m, '').replace(/\n?```\s*$/m, '')
.replace(/^(?:Here|Sure|Below|This|The|I )[\s\S]*?(?=package\s)/m, '')
.trim();
if (fixed) {
files[fname] = fixed + '\n';
console.log(` [3/5] ${fixResp.tokens} tok, ${fixed.split('\n').length} lines`);
} }
console.log(` [3/5] ✗ ${buildErrors.split('\n').length} errors → fixing`);
code.__buildErrors = buildErrors;
result.fixRounds++; result.fixRounds++;
} }
} else { }
break; // Testitiedostoa ei validoida go buildilla
} }
} }
if (code && typeof code === 'string') { // Päivitä context lopullisilla tiedostoilla
files[fileDef.name] = code + '\n'; context = Object.entries(files).map(([fn, c]) => `=== ${fn} ===\n${c}`).join('\n\n');
context += `=== ${fileDef.name} ===\n${code}\n\n`;
}
}
writeFileSync(`${dir}/_code_raw.txt`, context); writeFileSync(`${dir}/_code_raw.txt`, context);
result.promptChars = CODE_SYSTEM.length + (context.length || 0); result.promptChars = CODE_SYSTEM.length + (context.length || 0);
result.promptTokensEst = Math.round(result.promptChars / 4); result.promptTokensEst = Math.round(result.promptChars / 4);