Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/web/scripts/evaluate-search.ts

Raw
import { setTimeout as delay } from "node:timers/promises";
import {
	type ExtensionContext,
	ModelRegistry,
	ModelRuntime,
} from "@earendil-works/pi-coding-agent";
import { buildProviders, type WebSearchOptions } from "../search.js";
import { saveJsonResult } from "../store.js";

// Repeatable live comparison, not an overall search-quality benchmark.
// Resolve existing credentials through Pi; never log credentials or response errors.
const cases = [
	{
		query: "FastAPI response model return type documentation",
		target: "fastapi.tiangolo.com/tutorial/response-model",
	},
	{
		query: "Node.js readFile filesystem official documentation",
		target: "nodejs.org/api/fs.html",
	},
	{
		query: "Rust async fn traits stabilized 1.75 limitations",
		target: "blog.rust-lang.org/2023/12/21/async-fn-rpit-in-traits",
	},
	{
		query: "Nushell get optional column ignore missing documentation",
		target: "nushell.sh/commands/docs/get.html",
	},
	{
		query:
			"Bundesnetzagentur Internet zu langsam Minderungsrecht Nachweis Breitbandmessung",
		target: "bundesnetzagentur.de",
	},
];
const allSources = ["google", "parallel", "codex", "minimax", "ddg", "zai"];
const runtime = await ModelRuntime.create({
	allowModelNetwork: false,
	signal: AbortSignal.timeout(30_000),
});
const ctx = { modelRegistry: new ModelRegistry(runtime) } as ExtensionContext;
const runs: object[] = [];
const blocked = new Set<string>();
const report = {
	at: new Date().toISOString(),
	node: process.version,
	platform: process.platform,
	count: 5,
	timeoutMs: 45_000,
	cases,
	runs,
};

for (const [index, sample] of cases.entries()) {
	const options: WebSearchOptions = {
		query: sample.query,
		count: report.count,
		timeout: report.timeoutMs,
		ctx,
	};
	const providers = await buildProviders(options);
	// Rotate order to reduce a consistent warmup/order advantage.
	const order = [
		...providers.slice(index % providers.length),
		...providers.slice(0, index % providers.length),
	];
	for (const source of allSources.filter(
		(source) => !providers.some((provider) => provider.source === source),
	))
		runs.push({ query: index, source, status: "not configured" });
	for (const provider of order) {
		if (blocked.has(provider.source)) {
			runs.push({
				query: index,
				source: provider.source,
				status: "skipped after auth/quota/challenge failure",
			});
			continue;
		}
		options.signal = AbortSignal.timeout(report.timeoutMs);
		const start = performance.now();
		try {
			const results = (await provider.search()).slice(0, report.count);
			const hit = results.findIndex((result) => {
				try {
					const url = new URL(result.url);
					return `${url.hostname}${url.pathname}`.includes(sample.target);
				} catch {
					return false;
				}
			});
			const record = {
				query: index,
				source: provider.source,
				status: results.length ? "ok" : "empty",
				ms: Math.round(performance.now() - start),
				count: results.length,
				targetRank: hit < 0 ? null : hit + 1,
				snippetChars: results.reduce(
					(sum, result) => sum + result.snippet.length,
					0,
				),
				results,
			};
			runs.push(record);
			console.log(
				JSON.stringify({
					...record,
					results: results.map(({ title, url }) => ({ title, url })),
				}),
			);
		} catch (error) {
			const err = error as Error & { status?: number; code?: string };
			const http =
				err.status ??
				(Number(err.message?.match(/(?:HTTP|Codex search)\s+(\d{3})/)?.[1]) ||
					undefined);
			const category = /captcha|traffic block|unusual traffic/i.test(
				err.message,
			)
				? "challenge"
				: /timeout|abort/i.test(err.name + err.message)
					? "timeout/abort"
					: "error";
			const record = {
				query: index,
				source: provider.source,
				status: category,
				ms: Math.round(performance.now() - start),
				http,
				code: err.code,
			};
			runs.push(record);
			console.log(JSON.stringify(record));
			if (category === "challenge" || [401, 403, 429].includes(http ?? 0))
				blocked.add(provider.source);
		}
		await delay(2100);
	}
}
const saved = await saveJsonResult(
	"web-search-evaluation",
	process.cwd(),
	report,
);
console.log(`Full evaluation: ${saved.fullOutputPath}`);