Files
centdix 3f5f211a22 add final context size metric to ai_evals harness (#9660)
Record finalContextTokens per attempt: the input-token total of the last
model request (input + cache-creation + cache-read), i.e. how full the
context window ended up. Complements the cumulative tokenUsage.prompt,
which conflates context size with loop-iteration count.

Captured generically in the shared frontend runEval via the chat loop's
lastIterationUsage, so it covers all frontend modes (global/flow/script/
app), plus CLI mode via the last assistant turn's usage. Aggregated as
average and max over passed attempts and printed in the run summary.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 23:21:28 +02:00

140 lines
4.6 KiB
TypeScript

import { readJsonFile } from "../core/files";
import type { BackendValidationSettings } from "../core/backendValidation";
import type { FrontendEvalModelConfig } from "../core/models";
import { validateScriptState } from "../core/validators";
import type { BenchmarkArtifactFile, ModeRunner } from "../core/types";
import { BackendPreviewClient } from "../adapters/frontend/backendPreview";
import { runScriptEval } from "../adapters/frontend/core/script/scriptEvalRunner";
import type { ScriptEvalState } from "../adapters/frontend/core/script/fileHelpers";
import { getFrontendApiKey } from "./frontendCommon";
import type { WindmillBackendSettings } from "../core/windmillBackendSettings";
export function createScriptModeRunner(
modelConfig: FrontendEvalModelConfig,
backendValidation: BackendValidationSettings | undefined,
backendSettings: WindmillBackendSettings,
): ModeRunner<ScriptEvalState, ScriptEvalState, ScriptEvalState> {
return {
mode: "script",
concurrency: 5,
judgeThreshold: 80,
async loadInitial(path) {
return path ? await readJsonFile<ScriptEvalState>(path) : undefined;
},
async loadExpected(path) {
return path ? await readJsonFile<ScriptEvalState>(path) : undefined;
},
async run(prompt, initial, context) {
if (!initial) {
throw new Error("Script evals require an initial script fixture");
}
const result = await runScriptEval(
prompt,
getFrontendApiKey(modelConfig.provider),
{
initialScript: initial,
maxIterations: context.evalCase?.runtime?.maxTurns,
provider: modelConfig.provider,
model: modelConfig.model,
backend: backendSettings,
runContext: context,
},
);
return {
success: result.success,
actual: result.script,
error: result.error,
assistantMessageCount: result.assistantMessageCount,
toolCallCount: result.toolCallCount,
toolsUsed: result.toolsUsed,
toolCallDetails: result.toolCallDetails,
skillsInvoked: [],
tokenUsage: result.tokenUsage,
finalContextTokens: result.finalContextTokens,
};
},
validate({ actual, initial, expected }) {
return validateScriptState({ actual, initial, expected });
},
async backendValidate({ evalCase, initial, actual, context }) {
if (backendValidation?.mode !== "preview") {
return null;
}
const previewClient = new BackendPreviewClient(backendValidation);
return await previewClient.withWorkspace(
evalCase.id,
context.attempt,
async (workspaceId) => {
const completedJob = await previewClient.runScriptPreview({
workspaceId,
content: actual.code,
args:
(evalCase.runtime?.backendPreview?.args as
| Record<string, unknown>
| undefined) ??
actual.args ??
initial?.args ??
{},
language: normalizePreviewLanguage(actual.lang),
path: toPreviewScriptPath(actual.path),
timeoutSeconds: evalCase.runtime?.backendPreview?.timeoutSeconds,
});
return {
checks: [
{
name: "backend script preview succeeded",
passed: completedJob.success,
details: completedJob.success
? `workspace=${workspaceId}`
: `workspace=${workspaceId}; job=${completedJob.id}`,
},
],
artifactFiles: [
{
path: "backend-preview.json",
content:
JSON.stringify(
{
workspaceId,
jobId: completedJob.id,
success: completedJob.success,
result: completedJob.result,
logs: completedJob.logs,
completedJob: completedJob.raw,
},
null,
2,
) + "\n",
},
],
};
},
);
},
buildArtifacts(actual): BenchmarkArtifactFile[] {
return [
{
path: "script.json",
content: JSON.stringify(actual, null, 2) + "\n",
},
{
path: actual.path,
content: actual.code,
},
];
},
};
}
function normalizePreviewLanguage(language: ScriptEvalState["lang"]): string {
return language === "bunnative" ? "bun" : language;
}
function toPreviewScriptPath(filePath: string): string {
return filePath.replace(/\.[^.\/]+$/, "");
}