Files
wehub-resource-sync cb15c5e0d8
Release / Check for new version (push) Has been cancelled
Release / Build macOS ARM64 (push) Has been cancelled
Release / Build macOS x64 (push) Has been cancelled
Release / Build Linux ARM64 (push) Has been cancelled
Release / Build Linux musl ARM64 (push) Has been cancelled
Release / Build Linux musl x64 (push) Has been cancelled
Release / Build Linux x64 (push) Has been cancelled
Release / Build Windows x64 (push) Has been cancelled
Release / Publish to npm (push) Has been cancelled
Release / Publish sandbox package to npm (push) Has been cancelled
Release / Create GitHub Release (push) Has been cancelled
CI / Rust (windows-latest - x86_64-pc-windows-msvc) (push) Has been cancelled
CI / Native E2E Tests (push) Has been cancelled
CI / Windows Integration Test (push) Has been cancelled
CI / Global Install (macos-latest) (push) Has been cancelled
CI / Global Install (ubuntu-latest) (push) Has been cancelled
CI / Version Sync Check (push) Has been cancelled
CI / Rust (push) Has been cancelled
CI / Dashboard (push) Has been cancelled
CI / Sandbox Package (push) Has been cancelled
CI / Rust (macos-latest - aarch64-apple-darwin) (push) Has been cancelled
CI / Rust (macos-latest - x86_64-apple-darwin) (push) Has been cancelled
CI / Global Install (windows-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:35:58 +08:00

141 lines
3.4 KiB
TypeScript

import type {
EvalCase,
PatternResult,
JudgeResult,
EvalResult,
Provider,
ProviderOptions,
} from "./types.ts";
import { claudeProvider } from "./claude.ts";
function testPatterns(
response: string,
evalCase: EvalCase,
): { pass: boolean; results: PatternResult[] } {
const results: PatternResult[] = [];
let pass = true;
for (const pattern of evalCase.expectedPatterns) {
const regex = new RegExp(pattern, "is");
const matched = regex.test(response);
results.push({ pattern, matched, type: "expected" });
if (!matched) pass = false;
}
if (evalCase.forbiddenPatterns) {
for (const pattern of evalCase.forbiddenPatterns) {
const regex = new RegExp(pattern, "is");
const matched = regex.test(response);
results.push({ pattern, matched, type: "forbidden" });
if (matched) pass = false;
}
}
return { pass, results };
}
const JUDGE_PROMPT_TEMPLATE = `You are an eval judge scoring an AI agent's response to a browser automation task.
The agent was given a task and a skill file that instructs it to use agent-browser CLI commands.
Score the response on a scale of 1-5 based on the rubric below.
Rubric:
{rubric}
Response to judge:
<response>
{response}
</response>
Reply with ONLY a JSON object (no markdown fences, no other text):
{{"score": <1-5>, "reasoning": "<one sentence>"}}`;
const JUDGE_MODEL = "anthropic/claude-opus-4.6";
async function runLLMJudge(
response: string,
rubric: string,
options: ProviderOptions,
): Promise<JudgeResult> {
const prompt = JUDGE_PROMPT_TEMPLATE.replace("{rubric}", rubric).replace(
"{response}",
response,
);
// Judge always uses Claude regardless of eval provider
const result = await claudeProvider.callRaw(prompt, {
model: JUDGE_MODEL,
timeout: options.timeout ?? 30_000,
});
if (result.error) {
return { score: 0, reasoning: `Judge error: ${result.error}` };
}
try {
const cleaned = result.output.replace(/```json\n?|```\n?/g, "").trim();
const parsed = JSON.parse(cleaned);
return {
score: Math.max(0, Math.min(5, Number(parsed.score) || 0)),
reasoning: String(parsed.reasoning || ""),
};
} catch {
return {
score: 0,
reasoning: `Failed to parse judge response: ${result.output.slice(0, 200)}`,
};
}
}
export async function evaluate(
evalCase: EvalCase,
provider: Provider,
options: { model?: string; judge?: boolean; timeout?: number } = {},
): Promise<EvalResult> {
const providerOptions: ProviderOptions = {
model: options.model,
timeout: options.timeout,
};
const response = await provider.call(
evalCase.prompt,
providerOptions,
evalCase.context,
);
if (response.error) {
return {
caseId: evalCase.id,
caseName: evalCase.name,
category: evalCase.category,
pass: false,
patternResults: [],
response: "",
durationMs: response.durationMs,
error: response.error,
};
}
const { pass, results } = testPatterns(response.output, evalCase);
let judge: JudgeResult | undefined;
if (options.judge && evalCase.rubric) {
judge = await runLLMJudge(
response.output,
evalCase.rubric,
providerOptions,
);
}
return {
caseId: evalCase.id,
caseName: evalCase.name,
category: evalCase.category,
pass,
patternResults: results,
judge,
response: response.output,
durationMs: response.durationMs,
};
}