mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-06 06:52:20 +02:00
audit search variety with model provenance and per-turn latency
This commit is contained in:
@@ -11,6 +11,7 @@ const owner = 'sft_alex_creator';
|
||||
const endpointId = process.env.ENDPOINT_ID || '1d1022ef';
|
||||
const endpointUrl = process.env.ENDPOINT_URL || (() => { throw new Error("ENDPOINT_URL is required"); })();
|
||||
const model = process.env.MODEL || 'odysseus-qwen3.5-tools-pre-heretic';
|
||||
const varietyOnly = process.env.VARIETY_ONLY === '1';
|
||||
const run = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
const reportPath = path.resolve(process.env.REPORT_PATH || path.join(root, `reports/clean-v3-search-quality-${run}.json`));
|
||||
if (!reportPath.startsWith(path.join(root, 'reports') + path.sep) || fs.existsSync(reportPath)) throw Error('Report path must be new and under reports/');
|
||||
@@ -19,7 +20,7 @@ const token = Object.entries(sessions).find(([, value]) => value?.username === o
|
||||
if (!token) throw Error(`No active ${owner} session`);
|
||||
|
||||
const marker = `ody-search-${crypto.randomUUID()}`;
|
||||
const report = { run, owner, marker, status: 'running', scenarios: [], privacy: 'Public synthetic queries only; fetched bodies and private data are not retained.' };
|
||||
const report = { run, owner, marker, model, endpointUrl, status: 'running', scenarios: [], turns: [], privacy: 'Public synthetic queries only; fetched bodies and private data are not retained.' };
|
||||
const save = () => fs.writeFileSync(reportPath, JSON.stringify(report, null, 2) + '\n');
|
||||
fs.mkdirSync(path.dirname(reportPath), { recursive: true }); save();
|
||||
const canonical = value => String(value || '').replace(/^mcp__email__/, '');
|
||||
@@ -51,6 +52,7 @@ async function preparePage(context, id) {
|
||||
}
|
||||
|
||||
async function send(page, prompt) {
|
||||
const started = performance.now();
|
||||
const responsePromise = page.waitForResponse(r => new URL(r.url()).pathname === '/api/chat_stream' && r.request().method() === 'POST', { timeout: 120000 });
|
||||
await page.locator('textarea#message:visible').fill(prompt);
|
||||
await page.locator('textarea#message:visible').press('Enter');
|
||||
@@ -60,7 +62,16 @@ async function send(page, prompt) {
|
||||
const starts = events.filter(event => event.type === 'tool_start').map(event => ({ tool: canonical(event.tool), args: event.command || '' }));
|
||||
const outputs = events.filter(event => event.type === 'tool_output').map(event => ({ tool: canonical(event.tool), exit_code: event.exit_code ?? null, error: Boolean(event.error) }));
|
||||
const final = events.filter(event => event.type === 'final_response').map(event => event.content || '').join('') || events.filter(event => typeof event.delta === 'string').map(event => event.delta).join('');
|
||||
return { http_ok: response.ok(), contract, starts, outputs, final };
|
||||
const metrics = events.find(event => event.type === 'metrics')?.data;
|
||||
const observation = {
|
||||
prompt, seconds: (performance.now() - started) / 1000,
|
||||
rounds: metrics?.agent_rounds ?? null,
|
||||
actual_model: metrics?.model ?? null,
|
||||
tools: starts, outputs, final,
|
||||
runtime_error: events.some(event => event.type === 'error') || /v3 test encountered an error/i.test(final),
|
||||
};
|
||||
report.turns.push(observation); save();
|
||||
return { http_ok: response.ok(), contract, starts, outputs, final, ...observation };
|
||||
}
|
||||
|
||||
let browser;
|
||||
@@ -69,6 +80,51 @@ try {
|
||||
const context = await browser.newContext({ serviceWorkers: 'block', extraHTTPHeaders: { 'Accept-Encoding': 'identity' } });
|
||||
await context.addCookies([{ name: 'odysseus_session', value: token, url: base }]);
|
||||
|
||||
if (varietyOnly) {
|
||||
const cases = [
|
||||
['news-typo', ['latset ai neews?', 'more about the second story, with sources'], true],
|
||||
['country-casual', ['whats new in japan rn'], true],
|
||||
['software-typo', ['latest pythno verison? official source pls'], true],
|
||||
['manual', ['find official english manual for Sony WH-1000XM5'], true],
|
||||
['comparison', ['compare current firefox and chrome privacy features with sources'], true],
|
||||
['research', ['How do sodium ion batteries compare with lithium ion for home storage? Find evidence and explain tradeoffs.'], true],
|
||||
['no-web-typo', ['whats 12 tims 7'], false],
|
||||
['no-web-greeting', ['helo'], false],
|
||||
];
|
||||
async function runCase([name, prompts, needsWeb]) {
|
||||
const scenario = { name, status: 'running', turns: [] };
|
||||
report.scenarios.push(scenario); save();
|
||||
let id, page;
|
||||
try {
|
||||
id = await createSession(context, `[search-variety] ${name} ${marker}`);
|
||||
page = await preparePage(context, id);
|
||||
for (const prompt of prompts) {
|
||||
const turn = await send(page, prompt);
|
||||
const tools = turn.starts.map(x => x.tool);
|
||||
const checks = {
|
||||
no_runtime_error: !turn.runtime_error,
|
||||
model_matches: turn.actual_model === model,
|
||||
nonempty_answer: turn.final.trim().length > 0,
|
||||
no_reasoning_leak: noLeak(turn.final),
|
||||
expected_web_use: needsWeb ? tools.some(x => ['web_search', 'web_fetch', 'private_browser'].includes(x)) : tools.length === 0,
|
||||
};
|
||||
scenario.turns.push({ prompt, checks, seconds: turn.seconds, tools,
|
||||
status: Object.values(checks).every(Boolean) ? 'mechanics_passed' : 'failed',
|
||||
quality_review: 'pending_manual_evidence_review' });
|
||||
save();
|
||||
}
|
||||
scenario.status = scenario.turns.every(x => x.status === 'mechanics_passed') ? 'needs_quality_review' : 'failed';
|
||||
} catch (error) { scenario.status = 'failed'; scenario.error = String(error).slice(0, 300); }
|
||||
finally {
|
||||
if (id) scenario.cleanup = { session_removed: (await context.request.delete(`${base}/api/session/${id}`)).ok() };
|
||||
if (page) await page.close(); save();
|
||||
}
|
||||
}
|
||||
// Two simultaneous conversations keep endpoint contention bounded.
|
||||
const queue = [...cases];
|
||||
await Promise.all([0, 1].map(async () => { while (queue.length) await runCase(queue.shift()); }));
|
||||
} else {
|
||||
|
||||
// One conversation proves discovery, evidence reuse, then explicit page inspection.
|
||||
{
|
||||
const scenario = { name: 'official-search-summary-fetch', status: 'running', turns: [] };
|
||||
@@ -88,6 +144,8 @@ try {
|
||||
const expected = index === 0 ? 'web_search' : index === 2 ? 'web_fetch' : null;
|
||||
const checks = {
|
||||
http_ok: turn.http_ok,
|
||||
no_runtime_error: !turn.runtime_error,
|
||||
model_matches: turn.actual_model === model,
|
||||
clean_route: turn.contract?.selection_mode === 'clean_compact_v3_preview',
|
||||
expected_tool: expected ? tools.includes(expected) : tools.length === 0,
|
||||
successful_tools: turn.outputs.length === 0 || (() => {
|
||||
@@ -95,7 +153,12 @@ try {
|
||||
return !last.error && (last.exit_code == null || last.exit_code === 0);
|
||||
})(),
|
||||
no_reasoning_leak: noLeak(turn.final),
|
||||
grounded_answer: index === 0 ? /python|pypa|packag/i.test(turn.final) : index === 2 ? /pyproject|build|sdist|wheel|pip|twine/i.test(turn.final) : turn.final.trim().length > 15,
|
||||
answer_contains_requested_information: index === 0
|
||||
? /https:\/\/packaging\.python\.org\b/.test(turn.final)
|
||||
: index === 2
|
||||
? /pyproject\.toml/i.test(turn.final) && /\bwheel\b/i.test(turn.final)
|
||||
&& /\b(?:sdist|source distribution)\b/i.test(turn.final)
|
||||
: /Python Packaging User Guide|PyPA/i.test(turn.final),
|
||||
};
|
||||
scenario.turns.push({ index, tools, output_statuses: turn.outputs, final: turn.final.slice(0, 500), final_chars: turn.final.length, checks, status: Object.values(checks).every(Boolean) ? 'passed' : 'failed' }); save();
|
||||
}
|
||||
@@ -121,6 +184,8 @@ try {
|
||||
const checks = {
|
||||
http_ok: turn.http_ok, clean_route: turn.contract?.selection_mode === 'clean_compact_v3_preview',
|
||||
searched: searches.length >= 1, corrected_query: /market/i.test(query) && !/mraket/i.test(query),
|
||||
no_runtime_error: !turn.runtime_error, model_matches: turn.actual_model === model,
|
||||
covers_requested_indexes: /S&P\s*500/i.test(turn.final) && /Dow/i.test(turn.final) && /Nasdaq/i.test(turn.final),
|
||||
successful_tools: turn.outputs.every(x => !x.error && (x.exit_code == null || x.exit_code === 0)),
|
||||
no_reasoning_leak: noLeak(turn.final), no_irrelevant_misspelling_results: !/telegram|marketing|mraket/i.test(turn.final),
|
||||
};
|
||||
@@ -146,6 +211,7 @@ try {
|
||||
const checks = {
|
||||
http_ok: turn.http_ok, clean_route: turn.contract?.selection_mode === 'clean_compact_v3_preview',
|
||||
bounded_search: searches.length >= 1 && searches.length <= 2,
|
||||
no_runtime_error: !turn.runtime_error, model_matches: turn.actual_model === model,
|
||||
successful_tools: turn.outputs.every(x => !x.error && (x.exit_code == null || x.exit_code === 0)),
|
||||
no_reasoning_leak: noLeak(turn.final), honest_gap: /couldn.t find|cannot find|no (?:current )?(?:reliable|supporting|public|matching)|not (?:available|found|listed)|fictional|insufficient/i.test(turn.final),
|
||||
};
|
||||
@@ -157,9 +223,10 @@ try {
|
||||
if (page) await page.close(); save();
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally { if (browser) await browser.close(); }
|
||||
|
||||
report.status = report.scenarios.length === 3 && report.scenarios.every(x => x.status === 'passed' && x.cleanup?.session_removed) ? 'passed' : 'failed';
|
||||
report.status = varietyOnly ? 'needs_quality_review' : report.scenarios.length === 3 && report.scenarios.every(x => x.status === 'passed' && x.cleanup?.session_removed) ? 'passed' : 'failed';
|
||||
report.summary = { passed: report.scenarios.filter(x => x.status === 'passed').length, total: report.scenarios.length };
|
||||
save();
|
||||
console.log(JSON.stringify({ report: path.relative(root, reportPath), status: report.status, summary: report.summary }));
|
||||
|
||||
Reference in New Issue
Block a user