File size: 6,773 Bytes
7079daf 09fba6f 7079daf 09fba6f 7079daf 09fba6f 7079daf 09fba6f 7079daf 09fba6f 7079daf 09fba6f 7079daf 09fba6f 7079daf 09fba6f 7079daf | 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 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 | import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const manifestPath = path.join(__dirname, "benchlocal.pack.json");
const scenariosPath = path.join(__dirname, "scenarios.json");
export const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
function loadScenarios() {
return JSON.parse(fs.readFileSync(scenariosPath, "utf8"));
}
export function listScenarios() {
return loadScenarios().map((s) => ({
id: s.id,
name: s.name,
category: s.category || "General",
description: `[${(s.language || "ru").toUpperCase()}] ${s.name}`
}));
}
export function getScenarios() {
return listScenarios();
}
/**
* Валидатор: ищет строго заданный маркер "Мой ответ: (БУКВА)"
* Модель может свободно рассуждать, но в конце обязана выдать именно этот маркер.
* Весь промежуточный текст, формулы и размышления игнорируются.
*/
function validateResponse(scenario, rawText) {
const text = (rawText || "").trim();
const expected = (scenario.correctAnswer || "").trim().toUpperCase();
// Ищем маркер формата: "Мой ответ: (БУКВА)" (или англоязычный аналог "My answer: (LETTER)")
const pattern = /(?:Мой\s*ответ|My\s*answer):\s*\(([A-J])\)/gi;
const matches = [...text.matchAll(pattern)];
if (matches.length === 0) {
return {
pass: false,
score: 0,
extracted: null,
reason: `Нарушен формат: не найдена обязательная конструкция 'Мой ответ: (БУКВА)'. Окончание ответа: "${text.slice(-100)}"`
};
}
// Берем последнее вхождение маркера в тексте (итоговый вердикт модели)
const lastMatch = matches[matches.length - 1];
const extracted = lastMatch[1].toUpperCase();
if (extracted !== expected) {
return {
pass: false,
score: 0,
extracted,
reason: `Неверный ответ: модель выбрала '(${extracted})', ожидалось '(${expected})'`
};
}
return {
pass: true,
score: 100,
extracted,
reason: `Успех: корректно выбран вариант '(${extracted})' в строгом формате 'Мой ответ: (X)'`
};
}
function resolveHelpers(context) {
if (context && typeof context.getRequiredProvider === "function") {
return context;
}
if (context?.helpers && typeof context.helpers.getRequiredProvider === "function") {
return context.helpers;
}
return {
getRequiredProvider(providerId) {
const providers = context?.providers || [];
const found = Array.isArray(providers)
? providers.find((p) => p.id === providerId || p.provider === providerId)
: providers[providerId];
return found || { baseUrl: "http://127.0.0.1:11434/v1", apiKey: "none" };
},
getInferenceEndpoint(modelId) {
return context?.endpoints?.[modelId] || null;
}
};
}
export async function prepare(context) {
const helpers = resolveHelpers(context);
return {
async runScenario(input) {
const allScenarios = loadScenarios();
const scenario = allScenarios.find((s) => s.id === input.scenario.id);
if (!scenario) {
return {
scenarioId: input.scenario.id,
status: "fail",
score: 0,
summary: "Сценарий не найден",
rawLog: "error=Scenario not found"
};
}
const provider = helpers.getRequiredProvider(input.model.provider, { enabledOnly: true });
const inference = helpers.getInferenceEndpoint ? helpers.getInferenceEndpoint(input.model.id) : null;
const rawBaseUrl = (inference && inference.status === "running" ? inference.baseUrl : provider?.baseUrl) || "http://127.0.0.1:11434/v1";
const baseUrl = rawBaseUrl.replace(/\/+$/, "");
const apiKey = provider?.apiKey || "none";
const requestBody = {
model: input.model.model,
messages: [{ role: "user", content: scenario.prompt }],
temperature: 0
};
const response = await fetch(`${baseUrl}/chat/completions`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`
},
body: JSON.stringify(requestBody)
});
const data = await response.json();
const text = (data.choices?.[0]?.message?.content || "").trim();
const result = validateResponse(scenario, text);
const rawLog = [
`model=${input.model.model}`,
`category=${scenario.category}`,
`language=${scenario.language}`,
`scenario=${scenario.id} - ${scenario.name}`,
`prompt=${scenario.prompt}`,
`assistant_full_cot_response=${text}`,
`extracted_letter=${result.extracted || "NONE"}`,
`expected_letter=${scenario.correctAnswer}`,
`verdict=${result.pass ? "pass" : "fail"}`,
`score=${result.score}`,
`summary=${result.reason}`,
`\n--- RAW API RESPONSE ---`,
JSON.stringify(data, null, 2)
].join("\n");
return {
scenarioId: scenario.id,
category: scenario.category,
language: scenario.language,
status: result.pass ? "pass" : "fail",
score: result.score,
summary: result.reason,
rawLog: rawLog
};
},
async dispose() {}
};
}
export function scoreModelResults(results) {
const scored = results || [];
const total = scored.reduce((sum, r) => sum + (r.score || 0), 0);
const max = scored.length * 100;
const catMap = new Map();
for (const r of scored) {
const cat = r.category || "General";
if (!catMap.has(cat)) {
catMap.set(cat, { total: 0, count: 0 });
}
const entry = catMap.get(cat);
entry.total += r.score || 0;
entry.count += 1;
}
const categories = Array.from(catMap.entries()).map(([name, data]) => ({
name,
score: data.count > 0 ? Math.round((data.total / (data.count * 100)) * 100) : 0,
count: data.count
}));
return {
totalScore: max > 0 ? Math.round((total / max) * 100) : 0,
categories
};
}
export default {
manifest,
listScenarios,
getScenarios,
prepare,
createRuntime: prepare,
scoreModelResults
}; |