Skip to content

Commit 919befb

Browse files
committed
fix(qa): gate character eval on suite summary
1 parent d034e96 commit 919befb

2 files changed

Lines changed: 74 additions & 10 deletions

File tree

extensions/qa-lab/src/character-eval.test.ts

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,17 +90,51 @@ function createConcurrencyGate(expectedActive: number) {
9090
};
9191
}
9292

93-
function makeSuiteResult(params: { outputDir: string; model: string; transcript: string }) {
93+
async function makeSuiteResult(params: {
94+
outputDir: string;
95+
model: string;
96+
transcript: string;
97+
resultStatus?: "pass" | "fail";
98+
summaryStatus?: "pass" | "fail";
99+
summaryFailedCount?: number;
100+
}) {
101+
const resultStatus = params.resultStatus ?? "pass";
102+
const summaryStatus = params.summaryStatus ?? resultStatus;
103+
const summaryFailedCount = params.summaryFailedCount ?? (summaryStatus === "fail" ? 1 : 0);
104+
const summaryPath = path.join(params.outputDir, "qa-suite-summary.json");
105+
await fs.mkdir(params.outputDir, { recursive: true });
106+
await fs.writeFile(
107+
summaryPath,
108+
`${JSON.stringify(
109+
{
110+
counts: {
111+
total: 1,
112+
passed: summaryFailedCount > 0 ? 0 : 1,
113+
failed: summaryFailedCount,
114+
},
115+
scenarios: [
116+
{
117+
name: "Character vibes",
118+
status: summaryStatus,
119+
steps: [],
120+
},
121+
],
122+
},
123+
null,
124+
2,
125+
)}\n`,
126+
"utf8",
127+
);
94128
return {
95129
outputDir: params.outputDir,
96130
reportPath: path.join(params.outputDir, "qa-suite-report.md"),
97-
summaryPath: path.join(params.outputDir, "qa-suite-summary.json"),
131+
summaryPath,
98132
report: "# report",
99133
watchUrl: "http://127.0.0.1:43124",
100134
scenarios: [
101135
{
102136
name: "Character vibes",
103-
status: "pass",
137+
status: resultStatus,
104138
steps: [
105139
{
106140
name: `transcript for ${params.model}`,
@@ -430,6 +464,33 @@ describe("runQaCharacterEval", () => {
430464
});
431465
});
432466

467+
it("marks candidates failed when the suite summary has failed scenarios", async () => {
468+
const runSuite = vi.fn(async (params: CharacterRunSuiteParams) =>
469+
makeSuiteResult({
470+
outputDir: params.outputDir,
471+
model: params.primaryModel,
472+
transcript: "USER Alice: hi\n\nASSISTANT openclaw: outwardly fine",
473+
summaryStatus: "fail",
474+
summaryFailedCount: 1,
475+
}),
476+
);
477+
const runJudge = makeRunJudge([
478+
{ model: "openai/gpt-5.5", rank: 1, score: 0.5, summary: "failed" },
479+
]);
480+
481+
const result = await runQaCharacterEval({
482+
repoRoot: tempRoot,
483+
outputDir: path.join(tempRoot, "character"),
484+
models: ["openai/gpt-5.5"],
485+
judgeModels: ["openai/gpt-5.5"],
486+
runSuite,
487+
runJudge,
488+
});
489+
490+
expect(result.runs[0]?.status).toBe("fail");
491+
expect(result.runs[0]?.error).toBeUndefined();
492+
});
493+
433494
it("marks raw tool failure transcripts as failed output", async () => {
434495
const runSuite = vi.fn(async (params: CharacterRunSuiteParams) =>
435496
makeSuiteResult({

extensions/qa-lab/src/character-eval.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import fs from "node:fs/promises";
33
import path from "node:path";
44
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
55
import { normalizeStringEntries, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
6-
import { runQaManualLane } from "./manual-lane.runtime.js";
76
import { isQaFastModeModelRef, type QaProviderMode } from "./model-selection.js";
87
import {
98
QA_FRONTIER_CHARACTER_EVAL_MODELS,
@@ -13,7 +12,7 @@ import {
1312
} from "./providers/live-frontier/character-eval.js";
1413
import type { QaThinkingLevel } from "./qa-gateway-config.js";
1514
import { extractQaVisibleReplyLeakText } from "./reply-failure.js";
16-
import { runQaSuiteFromRuntime } from "./suite-launch.runtime.js";
15+
import { readQaSuiteFailedScenarioCountFromFile } from "./suite-summary.js";
1716
import type { QaSuiteResult } from "./suite.js";
1817

1918
const DEFAULT_CHARACTER_SCENARIO_ID = "character-vibes-gollum";
@@ -412,6 +411,7 @@ async function defaultRunJudge(params: {
412411
prompt: string;
413412
timeoutMs: number;
414413
}) {
414+
const { runQaManualLane } = await import("./manual-lane.runtime.js");
415415
const result = await runQaManualLane({
416416
repoRoot: params.repoRoot,
417417
providerMode: "live-frontier",
@@ -425,6 +425,11 @@ async function defaultRunJudge(params: {
425425
return result.reply;
426426
}
427427

428+
async function defaultRunSuite(params: Parameters<RunSuiteFn>[0]) {
429+
const { runQaSuiteFromRuntime } = await import("./suite-launch.runtime.js");
430+
return await runQaSuiteFromRuntime(params);
431+
}
432+
428433
function renderCharacterEvalReport(params: {
429434
scenarioId: string;
430435
startedAt: Date;
@@ -519,7 +524,7 @@ export async function runQaCharacterEval(params: QaCharacterEvalParams) {
519524
const runsDir = path.join(outputDir, "runs");
520525
await fs.mkdir(runsDir, { recursive: true });
521526

522-
const runSuite = params.runSuite ?? runQaSuiteFromRuntime;
527+
const runSuite = params.runSuite ?? defaultRunSuite;
523528
const candidateConcurrency = normalizeConcurrency(
524529
params.candidateConcurrency,
525530
DEFAULT_CHARACTER_EVAL_CONCURRENCY,
@@ -560,10 +565,8 @@ export async function runQaCharacterEval(params: QaCharacterEvalParams) {
560565
});
561566
const transcript = extractTranscript(result);
562567
const transcriptFailure = detectTranscriptFailure(transcript);
563-
const status =
564-
result.scenarios.some((scenario) => scenario.status === "fail") || transcriptFailure
565-
? "fail"
566-
: "pass";
568+
const failedScenarioCount = await readQaSuiteFailedScenarioCountFromFile(result.summaryPath);
569+
const status = failedScenarioCount > 0 || transcriptFailure ? "fail" : "pass";
567570
const run = {
568571
model,
569572
status,

0 commit comments

Comments
 (0)