@wix/pathgrade 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/pathgrade.js +2 -0
- package/dist/affected/anchor.d.ts +18 -0
- package/dist/affected/anchor.js +42 -0
- package/dist/affected/config.d.ts +25 -0
- package/dist/affected/config.js +98 -0
- package/dist/affected/format.d.ts +45 -0
- package/dist/affected/format.js +87 -0
- package/dist/affected/git.d.ts +33 -0
- package/dist/affected/git.js +68 -0
- package/dist/affected/glob.d.ts +17 -0
- package/dist/affected/glob.js +25 -0
- package/dist/affected/meta.d.ts +26 -0
- package/dist/affected/meta.js +158 -0
- package/dist/affected/select.d.ts +30 -0
- package/dist/affected/select.js +101 -0
- package/dist/affected/sidecar.d.ts +32 -0
- package/dist/affected/sidecar.js +86 -0
- package/dist/affected/types.d.ts +43 -0
- package/dist/affected/types.js +7 -0
- package/dist/agents/claude/ask-user-answer-store.d.ts +28 -0
- package/dist/agents/claude/ask-user-answer-store.js +29 -0
- package/dist/agents/claude/ask-user-bridge.d.ts +56 -0
- package/dist/agents/claude/ask-user-bridge.js +162 -0
- package/dist/agents/claude/sdk-message-projector.d.ts +43 -0
- package/dist/agents/claude/sdk-message-projector.js +208 -0
- package/dist/agents/claude/sdk-options.d.ts +52 -0
- package/dist/agents/claude/sdk-options.js +79 -0
- package/dist/agents/claude.d.ts +56 -0
- package/dist/agents/claude.js +143 -0
- package/dist/agents/codex-app-server/agent.d.ts +35 -0
- package/dist/agents/codex-app-server/agent.js +388 -0
- package/dist/agents/codex-app-server/fixtures/app-server-client.d.ts +19 -0
- package/dist/agents/codex-app-server/fixtures/app-server-client.js +104 -0
- package/dist/agents/codex-app-server/fixtures/run-gate.d.ts +25 -0
- package/dist/agents/codex-app-server/fixtures/run-gate.js +48 -0
- package/dist/agents/codex-app-server/protocol/ClientRequest.d.ts +27 -0
- package/dist/agents/codex-app-server/protocol/ClientRequest.js +7 -0
- package/dist/agents/codex-app-server/protocol/DynamicToolCallParams.d.ts +9 -0
- package/dist/agents/codex-app-server/protocol/DynamicToolCallParams.js +4 -0
- package/dist/agents/codex-app-server/protocol/GrantedPermissionProfile.d.ts +6 -0
- package/dist/agents/codex-app-server/protocol/GrantedPermissionProfile.js +7 -0
- package/dist/agents/codex-app-server/protocol/McpElicitationRequestParams.d.ts +23 -0
- package/dist/agents/codex-app-server/protocol/McpElicitationRequestParams.js +8 -0
- package/dist/agents/codex-app-server/protocol/Op.d.ts +11 -0
- package/dist/agents/codex-app-server/protocol/Op.js +5 -0
- package/dist/agents/codex-app-server/protocol/PermissionsRequestApprovalParams.d.ts +10 -0
- package/dist/agents/codex-app-server/protocol/PermissionsRequestApprovalParams.js +6 -0
- package/dist/agents/codex-app-server/protocol/PermissionsRequestApprovalResponse.d.ts +15 -0
- package/dist/agents/codex-app-server/protocol/PermissionsRequestApprovalResponse.js +4 -0
- package/dist/agents/codex-app-server/protocol/SandboxMode.d.ts +1 -0
- package/dist/agents/codex-app-server/protocol/SandboxMode.js +4 -0
- package/dist/agents/codex-app-server/protocol/ServerRequest.d.ts +42 -0
- package/dist/agents/codex-app-server/protocol/ServerRequest.js +7 -0
- package/dist/agents/codex-app-server/protocol/ThreadStartParams.d.ts +45 -0
- package/dist/agents/codex-app-server/protocol/ThreadStartParams.js +10 -0
- package/dist/agents/codex-app-server/protocol/ToolRequestUserInputAnswer.d.ts +6 -0
- package/dist/agents/codex-app-server/protocol/ToolRequestUserInputAnswer.js +4 -0
- package/dist/agents/codex-app-server/protocol/ToolRequestUserInputOption.d.ts +7 -0
- package/dist/agents/codex-app-server/protocol/ToolRequestUserInputOption.js +4 -0
- package/dist/agents/codex-app-server/protocol/ToolRequestUserInputParams.d.ts +10 -0
- package/dist/agents/codex-app-server/protocol/ToolRequestUserInputParams.js +4 -0
- package/dist/agents/codex-app-server/protocol/ToolRequestUserInputQuestion.d.ts +12 -0
- package/dist/agents/codex-app-server/protocol/ToolRequestUserInputQuestion.js +4 -0
- package/dist/agents/codex-app-server/protocol/ToolRequestUserInputResponse.d.ts +9 -0
- package/dist/agents/codex-app-server/protocol/ToolRequestUserInputResponse.js +4 -0
- package/dist/agents/codex-app-server/protocol/TurnCompletedNotification.d.ts +5 -0
- package/dist/agents/codex-app-server/protocol/TurnCompletedNotification.js +4 -0
- package/dist/agents/codex-app-server/protocol/index.d.ts +16 -0
- package/dist/agents/codex-app-server/protocol/index.js +11 -0
- package/dist/agents/codex-app-server/transport.d.ts +84 -0
- package/dist/agents/codex-app-server/transport.js +246 -0
- package/dist/agents/codex-app-server/wire-translators.d.ts +19 -0
- package/dist/agents/codex-app-server/wire-translators.js +52 -0
- package/dist/agents/codex.d.ts +7 -0
- package/dist/agents/codex.js +164 -0
- package/dist/agents/cursor.d.ts +39 -0
- package/dist/agents/cursor.js +233 -0
- package/dist/agents/registry.d.ts +17 -0
- package/dist/agents/registry.js +27 -0
- package/dist/agents/transcript-agent.d.ts +14 -0
- package/dist/agents/transcript-agent.js +59 -0
- package/dist/analytics/engine.d.ts +18 -0
- package/dist/analytics/engine.js +66 -0
- package/dist/commands/affected.d.ts +31 -0
- package/dist/commands/affected.js +150 -0
- package/dist/commands/analyze.d.ts +21 -0
- package/dist/commands/analyze.js +167 -0
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.js +242 -0
- package/dist/commands/preview-reactions.d.ts +10 -0
- package/dist/commands/preview-reactions.js +88 -0
- package/dist/commands/preview.d.ts +5 -0
- package/dist/commands/preview.js +17 -0
- package/dist/commands/report.d.ts +39 -0
- package/dist/commands/report.js +108 -0
- package/dist/commands/run-args.d.ts +22 -0
- package/dist/commands/run-args.js +69 -0
- package/dist/commands/run-changed.d.ts +26 -0
- package/dist/commands/run-changed.js +138 -0
- package/dist/commands/validate.d.ts +13 -0
- package/dist/commands/validate.js +233 -0
- package/dist/core/mcp-mock.d.ts +3 -0
- package/dist/core/mcp-mock.js +23 -0
- package/dist/core/mcp-mock.types.d.ts +15 -0
- package/dist/core/mcp-mock.types.js +1 -0
- package/dist/core/skills.d.ts +15 -0
- package/dist/core/skills.js +76 -0
- package/dist/evals/discovery.d.ts +7 -0
- package/dist/evals/discovery.js +69 -0
- package/dist/mcp-mock-server.d.ts +1 -0
- package/dist/mcp-mock-server.js +116 -0
- package/dist/pathgrade.d.ts +10 -0
- package/dist/pathgrade.js +243 -0
- package/dist/plugin/index.d.ts +9 -0
- package/dist/plugin/index.js +81 -0
- package/dist/plugin/lifecycle.d.ts +45 -0
- package/dist/plugin/lifecycle.js +146 -0
- package/dist/plugin/reporter.d.ts +36 -0
- package/dist/plugin/reporter.js +275 -0
- package/dist/plugin/setup.d.ts +1 -0
- package/dist/plugin/setup.js +3 -0
- package/dist/providers/copy-filter.d.ts +18 -0
- package/dist/providers/copy-filter.js +66 -0
- package/dist/providers/credentials.d.ts +36 -0
- package/dist/providers/credentials.js +202 -0
- package/dist/providers/mcp-config.d.ts +34 -0
- package/dist/providers/mcp-config.js +55 -0
- package/dist/providers/sandbox-exec.d.ts +8 -0
- package/dist/providers/sandbox-exec.js +82 -0
- package/dist/providers/sandbox.d.ts +22 -0
- package/dist/providers/sandbox.js +158 -0
- package/dist/providers/sandboxed-claude-spawn.d.ts +24 -0
- package/dist/providers/sandboxed-claude-spawn.js +57 -0
- package/dist/providers/workspace.d.ts +15 -0
- package/dist/providers/workspace.js +73 -0
- package/dist/reporters/browser.d.ts +2 -0
- package/dist/reporters/browser.js +85 -0
- package/dist/reporters/cli.d.ts +2 -0
- package/dist/reporters/cli.js +131 -0
- package/dist/reporters/diagnostics.d.ts +46 -0
- package/dist/reporters/diagnostics.js +147 -0
- package/dist/reporters/github-comment.d.ts +67 -0
- package/dist/reporters/github-comment.js +252 -0
- package/dist/reporters/loader.d.ts +8 -0
- package/dist/reporters/loader.js +36 -0
- package/dist/reporters/results-path.d.ts +2 -0
- package/dist/reporters/results-path.js +10 -0
- package/dist/reporters/verbose-emitter.d.ts +69 -0
- package/dist/reporters/verbose-emitter.js +103 -0
- package/dist/sdk/agent-crash.d.ts +19 -0
- package/dist/sdk/agent-crash.js +20 -0
- package/dist/sdk/agent-resolution.d.ts +12 -0
- package/dist/sdk/agent-resolution.js +21 -0
- package/dist/sdk/agent-result-log.d.ts +19 -0
- package/dist/sdk/agent-result-log.js +45 -0
- package/dist/sdk/agent.d.ts +4 -0
- package/dist/sdk/agent.js +354 -0
- package/dist/sdk/ask-bus/bus.d.ts +21 -0
- package/dist/sdk/ask-bus/bus.js +129 -0
- package/dist/sdk/ask-bus/handler.d.ts +31 -0
- package/dist/sdk/ask-bus/handler.js +244 -0
- package/dist/sdk/ask-bus/parsers.d.ts +9 -0
- package/dist/sdk/ask-bus/parsers.js +30 -0
- package/dist/sdk/ask-bus/projection.d.ts +26 -0
- package/dist/sdk/ask-bus/projection.js +45 -0
- package/dist/sdk/ask-bus/types.d.ts +69 -0
- package/dist/sdk/ask-bus/types.js +1 -0
- package/dist/sdk/chat.d.ts +29 -0
- package/dist/sdk/chat.js +93 -0
- package/dist/sdk/conversation-window.d.ts +14 -0
- package/dist/sdk/conversation-window.js +38 -0
- package/dist/sdk/converse.d.ts +42 -0
- package/dist/sdk/converse.js +329 -0
- package/dist/sdk/eval-runtime.d.ts +10 -0
- package/dist/sdk/eval-runtime.js +17 -0
- package/dist/sdk/evaluate.d.ts +13 -0
- package/dist/sdk/evaluate.js +325 -0
- package/dist/sdk/index.d.ts +31 -0
- package/dist/sdk/index.js +20 -0
- package/dist/sdk/judge-pipeline.d.ts +7 -0
- package/dist/sdk/judge-pipeline.js +178 -0
- package/dist/sdk/judge-prompt-builder.d.ts +7 -0
- package/dist/sdk/judge-prompt-builder.js +76 -0
- package/dist/sdk/judge-tool-runner.d.ts +19 -0
- package/dist/sdk/judge-tool-runner.js +51 -0
- package/dist/sdk/judge-tool-session.d.ts +47 -0
- package/dist/sdk/judge-tool-session.js +213 -0
- package/dist/sdk/judge-tools.d.ts +29 -0
- package/dist/sdk/judge-tools.js +283 -0
- package/dist/sdk/managed-session.d.ts +50 -0
- package/dist/sdk/managed-session.js +110 -0
- package/dist/sdk/persona.d.ts +5 -0
- package/dist/sdk/persona.js +33 -0
- package/dist/sdk/reaction-loader.d.ts +4 -0
- package/dist/sdk/reaction-loader.js +119 -0
- package/dist/sdk/reaction-preview.d.ts +6 -0
- package/dist/sdk/reaction-preview.js +94 -0
- package/dist/sdk/run-scorer.d.ts +10 -0
- package/dist/sdk/run-scorer.js +124 -0
- package/dist/sdk/runtime-policy.d.ts +21 -0
- package/dist/sdk/runtime-policy.js +89 -0
- package/dist/sdk/scorer-utils.d.ts +6 -0
- package/dist/sdk/scorer-utils.js +39 -0
- package/dist/sdk/scorers.d.ts +64 -0
- package/dist/sdk/scorers.js +80 -0
- package/dist/sdk/snapshots.d.ts +41 -0
- package/dist/sdk/snapshots.js +148 -0
- package/dist/sdk/types.d.ts +416 -0
- package/dist/sdk/types.js +17 -0
- package/dist/sdk/visible-turn.d.ts +4 -0
- package/dist/sdk/visible-turn.js +23 -0
- package/dist/tool-events.d.ts +45 -0
- package/dist/tool-events.js +167 -0
- package/dist/types.d.ts +372 -0
- package/dist/types.js +37 -0
- package/dist/utils/cli.d.ts +55 -0
- package/dist/utils/cli.js +131 -0
- package/dist/utils/env.d.ts +8 -0
- package/dist/utils/env.js +26 -0
- package/dist/utils/llm-mocks.d.ts +43 -0
- package/dist/utils/llm-mocks.js +108 -0
- package/dist/utils/llm-providers/anthropic.d.ts +2 -0
- package/dist/utils/llm-providers/anthropic.js +132 -0
- package/dist/utils/llm-providers/cli.d.ts +20 -0
- package/dist/utils/llm-providers/cli.js +162 -0
- package/dist/utils/llm-providers/openai.d.ts +2 -0
- package/dist/utils/llm-providers/openai.js +56 -0
- package/dist/utils/llm-types.d.ts +107 -0
- package/dist/utils/llm-types.js +1 -0
- package/dist/utils/llm.d.ts +36 -0
- package/dist/utils/llm.js +209 -0
- package/dist/utils/shutdown.d.ts +15 -0
- package/dist/utils/shutdown.js +41 -0
- package/dist/utils/timeout.d.ts +8 -0
- package/dist/utils/timeout.js +32 -0
- package/dist/viewer.html +1178 -0
- package/package.json +88 -0
- package/templates/eval.ts.template +28 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import { extractSkillsFromLog } from '../tool-events.js';
|
|
2
|
+
import { getRuntime } from './eval-runtime.js';
|
|
3
|
+
import { runJudgePipeline } from './judge-pipeline.js';
|
|
4
|
+
import { runScorer } from './run-scorer.js';
|
|
5
|
+
import { createLLMClient } from '../utils/llm.js';
|
|
6
|
+
import { sandboxExec } from '../providers/sandbox-exec.js';
|
|
7
|
+
import { buildTranscript, loadRunSnapshot, WorkspaceMissingError } from './snapshots.js';
|
|
8
|
+
import fs from 'fs-extra';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
export class EvalScorerError extends Error {
|
|
11
|
+
scorerErrors;
|
|
12
|
+
result;
|
|
13
|
+
constructor(result, scorerErrors) {
|
|
14
|
+
super(formatScorerErrorMessage(scorerErrors));
|
|
15
|
+
this.name = 'EvalScorerError';
|
|
16
|
+
this.result = result;
|
|
17
|
+
this.scorerErrors = scorerErrors;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Run scorers against a trial and compute a weighted average score.
|
|
22
|
+
*
|
|
23
|
+
* Pipeline phases:
|
|
24
|
+
* 1. check + score scorers (parallel, deterministic)
|
|
25
|
+
* 2. judge scorers (parallel, LLM calls) — skipped if fail-fast triggered
|
|
26
|
+
* 3. toolUsage scorers (parallel) — skipped if fail-fast triggered
|
|
27
|
+
*
|
|
28
|
+
* Fail-fast: if any check scores 0 and failFast !== false, phases 2-3 are skipped.
|
|
29
|
+
* score() returning 0 does NOT trigger fail-fast.
|
|
30
|
+
*/
|
|
31
|
+
// Track which agents have already had conversation tokens attributed.
|
|
32
|
+
// Local per-call: no module-level state, no cleanup export needed.
|
|
33
|
+
function makeEvaluateAgent() {
|
|
34
|
+
const conversationAttributed = new WeakSet();
|
|
35
|
+
return async function evaluateAgent(agent, scorers, opts) {
|
|
36
|
+
const toolEvents = agent.log
|
|
37
|
+
.filter((e) => e.type === 'tool_event' && e.tool_event)
|
|
38
|
+
.map((e) => e.tool_event);
|
|
39
|
+
const ctx = {
|
|
40
|
+
workspace: agent.workspace,
|
|
41
|
+
log: agent.log,
|
|
42
|
+
transcript: agent.transcript(),
|
|
43
|
+
toolEvents,
|
|
44
|
+
runCommand: (cmd) => agent.exec(cmd),
|
|
45
|
+
artifacts: createSessionArtifacts(agent.workspace, toolEvents),
|
|
46
|
+
};
|
|
47
|
+
const trackedLLM = opts?.llm ?? agent.llm;
|
|
48
|
+
// Snapshot conversation tokens BEFORE running scorers, for first-eval attribution.
|
|
49
|
+
const before = trackedLLM.tokenUsage ?? { inputTokens: 0, outputTokens: 0 };
|
|
50
|
+
// Snapshot conversation cost too. AgentImpl's `sendTurn` accumulates
|
|
51
|
+
// per-turn `costUsd` onto `trackedLLM` via `addCost`, so by the time
|
|
52
|
+
// `evaluate()` runs the pre-evaluate cost is the conversation's
|
|
53
|
+
// accumulated agent-turn cost.
|
|
54
|
+
const beforeCostUsd = trackedLLM.costUsd ?? 0;
|
|
55
|
+
// measure() returns the delta consumed by this evaluate call.
|
|
56
|
+
const { result: evalResult, tokens: deltaTokenUsage } = trackedLLM.measure
|
|
57
|
+
? await trackedLLM.measure(() => evaluateWithContext(ctx, scorers, { ...opts, llm: trackedLLM }))
|
|
58
|
+
: await (async () => {
|
|
59
|
+
const r = await evaluateWithContext(ctx, scorers, { ...opts, llm: trackedLLM });
|
|
60
|
+
return { result: r, tokens: r.tokenUsage ?? { inputTokens: 0, outputTokens: 0 } };
|
|
61
|
+
})();
|
|
62
|
+
// Attribute conversation tokens on the first evaluate() for this agent.
|
|
63
|
+
const isFirstEval = !conversationAttributed.has(agent);
|
|
64
|
+
const conversationTokens = isFirstEval && (before.inputTokens > 0 || before.outputTokens > 0)
|
|
65
|
+
? { conversation_input_tokens: before.inputTokens, conversation_output_tokens: before.outputTokens }
|
|
66
|
+
: undefined;
|
|
67
|
+
// Same first-eval attribution rule for cost. Omitted entirely when
|
|
68
|
+
// no conversation cost was captured (Codex / Cursor today).
|
|
69
|
+
const conversationCost = isFirstEval && beforeCostUsd > 0
|
|
70
|
+
? { conversation_cost_usd: beforeCostUsd }
|
|
71
|
+
: undefined;
|
|
72
|
+
if (isFirstEval)
|
|
73
|
+
conversationAttributed.add(agent);
|
|
74
|
+
const recordedResult = {
|
|
75
|
+
...evalResult,
|
|
76
|
+
tokenUsage: deltaTokenUsage,
|
|
77
|
+
trial: buildTrialResult(agent.log, { ...evalResult, tokenUsage: deltaTokenUsage }, conversationTokens, conversationCost),
|
|
78
|
+
};
|
|
79
|
+
getRuntime().onResult(recordedResult, agent);
|
|
80
|
+
maybeThrowOnScorerErrors(recordedResult, opts?.onScorerError ?? 'skip');
|
|
81
|
+
return recordedResult;
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const evaluateAgent = makeEvaluateAgent();
|
|
85
|
+
async function fromSnapshot(snapshotPath, scorers, opts) {
|
|
86
|
+
const snapshot = await loadRunSnapshot(snapshotPath);
|
|
87
|
+
const trackedLLM = opts?.llm ?? createLLMClient({ adapters: [{
|
|
88
|
+
name: 'runtime', isAvailable: async () => true,
|
|
89
|
+
call: (prompt, callOpts) => getRuntime().llm.call(prompt, callOpts),
|
|
90
|
+
}] });
|
|
91
|
+
const ctx = {
|
|
92
|
+
workspace: snapshot.workspace ?? '',
|
|
93
|
+
log: snapshot.log,
|
|
94
|
+
transcript: buildTranscript(snapshot.messages),
|
|
95
|
+
toolEvents: snapshot.toolEvents,
|
|
96
|
+
runCommand: async (cmd) => {
|
|
97
|
+
const workspace = snapshot.workspace;
|
|
98
|
+
if (!workspace) {
|
|
99
|
+
throw new WorkspaceMissingError('Snapshot does not include a workspace path');
|
|
100
|
+
}
|
|
101
|
+
if (!(await fs.pathExists(workspace))) {
|
|
102
|
+
throw new WorkspaceMissingError(`Snapshot workspace does not exist on disk: ${workspace}`);
|
|
103
|
+
}
|
|
104
|
+
return sandboxExec(cmd, { cwd: workspace, env: getProcessEnv() });
|
|
105
|
+
},
|
|
106
|
+
artifacts: createSessionArtifacts(snapshot.workspace ?? '', snapshot.toolEvents),
|
|
107
|
+
};
|
|
108
|
+
const evalResult = await evaluateWithContext(ctx, scorers, { ...opts, llm: trackedLLM });
|
|
109
|
+
const recordedResult = {
|
|
110
|
+
...evalResult,
|
|
111
|
+
trial: buildTrialResult(snapshot.log, evalResult),
|
|
112
|
+
};
|
|
113
|
+
maybeThrowOnScorerErrors(recordedResult, opts?.onScorerError ?? 'skip');
|
|
114
|
+
return recordedResult;
|
|
115
|
+
}
|
|
116
|
+
async function evaluateWithContext(ctx, scorers, opts) {
|
|
117
|
+
const failFast = opts?.failFast ?? true;
|
|
118
|
+
const trackedLLM = opts?.llm ?? createLLMClient({ adapters: [{
|
|
119
|
+
name: 'runtime', isAvailable: async () => true,
|
|
120
|
+
call: (prompt, callOpts) => getRuntime().llm.call(prompt, callOpts),
|
|
121
|
+
}] });
|
|
122
|
+
const onScorerError = opts?.onScorerError ?? 'skip';
|
|
123
|
+
const phase1 = [];
|
|
124
|
+
const phase2 = [];
|
|
125
|
+
const phase3 = [];
|
|
126
|
+
for (const g of scorers) {
|
|
127
|
+
switch (g.type) {
|
|
128
|
+
case 'check':
|
|
129
|
+
case 'score':
|
|
130
|
+
phase1.push(g);
|
|
131
|
+
break;
|
|
132
|
+
case 'judge':
|
|
133
|
+
phase2.push(g);
|
|
134
|
+
break;
|
|
135
|
+
case 'tool_usage':
|
|
136
|
+
phase3.push(g);
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const results = [];
|
|
141
|
+
const phase1Results = await Promise.all(phase1.map((g) => runScorer(g, ctx)));
|
|
142
|
+
results.push(...phase1Results);
|
|
143
|
+
const anyCheckFailed = failFast && phase1Results.some((r) => r.type === 'check' && r.status !== 'error' && r.score === 0);
|
|
144
|
+
if (anyCheckFailed) {
|
|
145
|
+
for (const g of [...phase2, ...phase3]) {
|
|
146
|
+
results.push(makeSkipped(g));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
const judgeResults = await runJudgePipeline(phase2, ctx, { llm: trackedLLM });
|
|
151
|
+
results.push(...judgeResults);
|
|
152
|
+
const phase3Results = await Promise.all(phase3.map((g) => runScorer(g, ctx)));
|
|
153
|
+
results.push(...phase3Results);
|
|
154
|
+
}
|
|
155
|
+
const tokenUsage = trackedLLM.tokenUsage ?? { inputTokens: 0, outputTokens: 0 };
|
|
156
|
+
const scoringResults = onScorerError === 'skip'
|
|
157
|
+
? results.filter((result) => result.status !== 'error')
|
|
158
|
+
: results;
|
|
159
|
+
const totalWeight = scoringResults.reduce((sum, r) => sum + r.weight, 0);
|
|
160
|
+
const weightedSum = scoringResults.reduce((sum, r) => sum + r.score * r.weight, 0);
|
|
161
|
+
const score = totalWeight > 0 ? weightedSum / totalWeight : 0;
|
|
162
|
+
return {
|
|
163
|
+
score,
|
|
164
|
+
scorers: results,
|
|
165
|
+
tokenUsage,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
function maybeThrowOnScorerErrors(result, mode) {
|
|
169
|
+
if (mode !== 'fail')
|
|
170
|
+
return;
|
|
171
|
+
const scorerErrors = result.scorers.filter((entry) => entry.status === 'error');
|
|
172
|
+
if (scorerErrors.length > 0) {
|
|
173
|
+
throw new EvalScorerError(result, scorerErrors);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function createSessionArtifacts(workspace, toolEvents) {
|
|
177
|
+
const matchingArtifacts = (opts) => {
|
|
178
|
+
const actions = opts?.actions ?? ['write_file', 'edit_file'];
|
|
179
|
+
const seen = new Set();
|
|
180
|
+
const matches = [];
|
|
181
|
+
for (const event of toolEvents) {
|
|
182
|
+
if (!actions.includes(event.action))
|
|
183
|
+
continue;
|
|
184
|
+
const absolutePath = extractArtifactPath(event);
|
|
185
|
+
if (!absolutePath)
|
|
186
|
+
continue;
|
|
187
|
+
const relativePath = normalizeArtifactPath(workspace, absolutePath);
|
|
188
|
+
if (!matchesArtifactPattern(relativePath, opts?.pattern))
|
|
189
|
+
continue;
|
|
190
|
+
if (seen.has(relativePath))
|
|
191
|
+
continue;
|
|
192
|
+
seen.add(relativePath);
|
|
193
|
+
matches.push({ path: relativePath, absolutePath });
|
|
194
|
+
}
|
|
195
|
+
return matches;
|
|
196
|
+
};
|
|
197
|
+
return {
|
|
198
|
+
list: (opts) => matchingArtifacts(opts).map((artifact) => artifact.path),
|
|
199
|
+
read: async (artifactPath) => {
|
|
200
|
+
const absolutePath = resolveArtifactPath(workspace, artifactPath);
|
|
201
|
+
return fs.readFile(absolutePath, 'utf8');
|
|
202
|
+
},
|
|
203
|
+
latest: async (opts) => {
|
|
204
|
+
const matches = matchingArtifacts(opts);
|
|
205
|
+
const latest = matches[matches.length - 1];
|
|
206
|
+
if (!latest)
|
|
207
|
+
return null;
|
|
208
|
+
return {
|
|
209
|
+
path: latest.path,
|
|
210
|
+
content: await fs.readFile(latest.absolutePath, 'utf8'),
|
|
211
|
+
};
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
function extractArtifactPath(event) {
|
|
216
|
+
const candidates = [
|
|
217
|
+
event.arguments?.path,
|
|
218
|
+
event.arguments?.file,
|
|
219
|
+
event.arguments?.file_path,
|
|
220
|
+
];
|
|
221
|
+
for (const candidate of candidates) {
|
|
222
|
+
if (typeof candidate === 'string' && candidate.trim().length > 0) {
|
|
223
|
+
return candidate;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const summaryMatch = event.summary.match(/^(?:read_file|write_file|edit_file)\s+(.+)$/);
|
|
227
|
+
return summaryMatch?.[1] ?? null;
|
|
228
|
+
}
|
|
229
|
+
function normalizeArtifactPath(workspace, artifactPath) {
|
|
230
|
+
if (!workspace || !path.isAbsolute(artifactPath)) {
|
|
231
|
+
return artifactPath;
|
|
232
|
+
}
|
|
233
|
+
const relativePath = path.relative(workspace, artifactPath);
|
|
234
|
+
if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
|
|
235
|
+
return artifactPath;
|
|
236
|
+
}
|
|
237
|
+
return relativePath;
|
|
238
|
+
}
|
|
239
|
+
function resolveArtifactPath(workspace, artifactPath) {
|
|
240
|
+
if (path.isAbsolute(artifactPath)) {
|
|
241
|
+
return artifactPath;
|
|
242
|
+
}
|
|
243
|
+
if (!workspace) {
|
|
244
|
+
throw new WorkspaceMissingError('Session artifact reads require a workspace for relative paths');
|
|
245
|
+
}
|
|
246
|
+
return path.join(workspace, artifactPath);
|
|
247
|
+
}
|
|
248
|
+
function matchesArtifactPattern(artifactPath, pattern) {
|
|
249
|
+
if (!pattern)
|
|
250
|
+
return true;
|
|
251
|
+
if (typeof pattern === 'string')
|
|
252
|
+
return artifactPath.includes(pattern);
|
|
253
|
+
return pattern.test(artifactPath);
|
|
254
|
+
}
|
|
255
|
+
function buildTrialResult(log, result, conversationTokens, conversationCost) {
|
|
256
|
+
const nCommands = log.filter((entry) => entry.type === 'command').length;
|
|
257
|
+
const skills = extractSkillsFromLog(log);
|
|
258
|
+
return {
|
|
259
|
+
trial_id: 0,
|
|
260
|
+
reward: result.score,
|
|
261
|
+
scorer_results: result.scorers.map(toTrialScorerResult),
|
|
262
|
+
duration_ms: 0,
|
|
263
|
+
n_commands: nCommands,
|
|
264
|
+
input_tokens: result.tokenUsage?.inputTokens ?? 0,
|
|
265
|
+
output_tokens: result.tokenUsage?.outputTokens ?? 0,
|
|
266
|
+
...conversationTokens,
|
|
267
|
+
// `total_cost_usd` is intentionally NOT emitted here: total cost is
|
|
268
|
+
// conservative — emitted only when every included component has a
|
|
269
|
+
// known cost. Today judge LLM providers expose no cost, so a partial
|
|
270
|
+
// total would mislead consumers. `conversation_cost_usd` is the only
|
|
271
|
+
// guaranteed cost surface; future judge-cost work unlocks the total
|
|
272
|
+
// field.
|
|
273
|
+
...conversationCost,
|
|
274
|
+
session_log: [...log],
|
|
275
|
+
...(skills.length > 0 ? { skills_used: skills } : {}),
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
function getProcessEnv() {
|
|
279
|
+
const env = {};
|
|
280
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
281
|
+
if (typeof value === 'string') {
|
|
282
|
+
env[key] = value;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return env;
|
|
286
|
+
}
|
|
287
|
+
export const evaluate = Object.assign(evaluateAgent, {
|
|
288
|
+
fromSnapshot,
|
|
289
|
+
});
|
|
290
|
+
function toTrialScorerResult(result) {
|
|
291
|
+
return {
|
|
292
|
+
scorer_type: mapScorerType(result.type),
|
|
293
|
+
score: result.score,
|
|
294
|
+
weight: result.weight,
|
|
295
|
+
details: result.details ?? '',
|
|
296
|
+
status: result.status,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
function mapScorerType(type) {
|
|
300
|
+
switch (type) {
|
|
301
|
+
case 'check':
|
|
302
|
+
case 'score':
|
|
303
|
+
return 'deterministic';
|
|
304
|
+
case 'judge':
|
|
305
|
+
return 'llm_rubric';
|
|
306
|
+
case 'tool_usage':
|
|
307
|
+
return 'tool_usage';
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
function makeSkipped(scorer) {
|
|
311
|
+
return {
|
|
312
|
+
name: scorer.name,
|
|
313
|
+
type: scorer.type,
|
|
314
|
+
score: 0,
|
|
315
|
+
weight: scorer.weight ?? 1,
|
|
316
|
+
details: 'skipped (fail-fast)',
|
|
317
|
+
status: 'skipped',
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
function formatScorerErrorMessage(scorerErrors) {
|
|
321
|
+
const joined = scorerErrors
|
|
322
|
+
.map((result) => `${result.name}: ${result.details ?? 'unknown error'}`)
|
|
323
|
+
.join('; ');
|
|
324
|
+
return `Scorer evaluation failed: ${joined}`;
|
|
325
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export { createAgent } from './agent.js';
|
|
2
|
+
export { resolveAgentName, resolveCodexTransport, InvalidTransportEnvError, } from './agent-resolution.js';
|
|
3
|
+
export { AgentCrashError } from './agent-crash.js';
|
|
4
|
+
export { check, score, judge, toolUsage } from './scorers.js';
|
|
5
|
+
export { evaluate, EvalScorerError } from './evaluate.js';
|
|
6
|
+
export { RUN_SNAPSHOT_VERSION, buildRunSnapshot, loadRunSnapshot, SnapshotParseError, SnapshotVersionError, WorkspaceMissingError, } from './snapshots.js';
|
|
7
|
+
export { createPersona } from './persona.js';
|
|
8
|
+
export { createConversationWindow } from './conversation-window.js';
|
|
9
|
+
export { runJudgePipeline } from './judge-pipeline.js';
|
|
10
|
+
export { runScorer } from './run-scorer.js';
|
|
11
|
+
export { previewReactions } from './reaction-preview.js';
|
|
12
|
+
export { setRuntime, resetRuntime } from './eval-runtime.js';
|
|
13
|
+
export { DEFAULT_COPY_IGNORE } from '../providers/copy-filter.js';
|
|
14
|
+
export { createAskBus, requireAskBusForLiveBatches, AskBusTimeoutError } from './ask-bus/bus.js';
|
|
15
|
+
export { toAskUserToolEvent } from './ask-bus/projection.js';
|
|
16
|
+
export type { AskUserToolEvent, AskUserToolEventArguments, AskUserToolEventQuestionArgument, } from './ask-bus/projection.js';
|
|
17
|
+
export { buildAskBatchLogEntries } from './agent-result-log.js';
|
|
18
|
+
export { getAgentCapabilities } from './types.js';
|
|
19
|
+
export type { AgentTransport, AgentCapabilities, AgentName } from './types.js';
|
|
20
|
+
export type { AskBus, AskBatch, AskQuestion, AskOption, AskAnswer, AskResolution, AskBatchSnapshot, AskAnswerSnapshot, AskResolutionSnapshot, AskHandle, AskHandler, AskSource, AskLifecycle, AskAnswerSource, Unsubscribe as AskBusUnsubscribe, } from './ask-bus/types.js';
|
|
21
|
+
export type { Agent, AgentOptions, Message, Scorer, CheckScorer, ScoreScorer, JudgeScorer, ToolUsageScorer, ScorerContext, EvalResult, ScorerResultEntry, ScorerStatus, ChatSession, ConversationResult, ConverseOptions, Reaction, TextReaction, AskUserReaction, AskUserQuestion, AskUserOption, ReactionPreviewEntry, TextReactionPreviewEntry, AskUserReactionPreviewEntry, ReactionPreviewResult, ReactionPreviewTurn, Persona, PersonaConfig, ConversationWindowConfig, PathgradePluginOptions, PathgradeMeta, TurnTiming, TokenUsage, EvaluateOptions, ReactionPreviewStatus, } from './types.js';
|
|
22
|
+
export type { ConversationWindow, ConversationWindowOptions } from './conversation-window.js';
|
|
23
|
+
export type { JudgePipelineOptions } from './judge-pipeline.js';
|
|
24
|
+
export type { RunScorerOptions } from './run-scorer.js';
|
|
25
|
+
export type { OnScorerErrorMode } from './evaluate.js';
|
|
26
|
+
export type { RunSnapshot } from './snapshots.js';
|
|
27
|
+
export type { LLMPort, EvalRuntime } from './eval-runtime.js';
|
|
28
|
+
export { createLLMClient, ProviderNotSupportedError } from '../utils/llm.js';
|
|
29
|
+
export type { CreateLLMClientOptions, LLMProviderAdapter, TokenUsage as LLMTokenUsage } from '../utils/llm.js';
|
|
30
|
+
export { createMockLLM } from '../utils/llm-mocks.js';
|
|
31
|
+
export type { CreateMockLLMOptions, MockResponse, MockLLM } from '../utils/llm-mocks.js';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// @wix/pathgrade — public API for writing evaluations
|
|
2
|
+
export { createAgent } from './agent.js';
|
|
3
|
+
export { resolveAgentName, resolveCodexTransport, InvalidTransportEnvError, } from './agent-resolution.js';
|
|
4
|
+
export { AgentCrashError } from './agent-crash.js';
|
|
5
|
+
export { check, score, judge, toolUsage } from './scorers.js';
|
|
6
|
+
export { evaluate, EvalScorerError } from './evaluate.js';
|
|
7
|
+
export { RUN_SNAPSHOT_VERSION, buildRunSnapshot, loadRunSnapshot, SnapshotParseError, SnapshotVersionError, WorkspaceMissingError, } from './snapshots.js';
|
|
8
|
+
export { createPersona } from './persona.js';
|
|
9
|
+
export { createConversationWindow } from './conversation-window.js';
|
|
10
|
+
export { runJudgePipeline } from './judge-pipeline.js';
|
|
11
|
+
export { runScorer } from './run-scorer.js';
|
|
12
|
+
export { previewReactions } from './reaction-preview.js';
|
|
13
|
+
export { setRuntime, resetRuntime } from './eval-runtime.js';
|
|
14
|
+
export { DEFAULT_COPY_IGNORE } from '../providers/copy-filter.js';
|
|
15
|
+
export { createAskBus, requireAskBusForLiveBatches, AskBusTimeoutError } from './ask-bus/bus.js';
|
|
16
|
+
export { toAskUserToolEvent } from './ask-bus/projection.js';
|
|
17
|
+
export { buildAskBatchLogEntries } from './agent-result-log.js';
|
|
18
|
+
export { getAgentCapabilities } from './types.js';
|
|
19
|
+
export { createLLMClient, ProviderNotSupportedError } from '../utils/llm.js';
|
|
20
|
+
export { createMockLLM } from '../utils/llm-mocks.js';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { JudgeScorer, ScorerContext, ScorerResultEntry } from './types.js';
|
|
2
|
+
import type { LLMPort } from '../utils/llm-types.js';
|
|
3
|
+
export interface JudgePipelineOptions {
|
|
4
|
+
/** Override the LLM. Defaults to getRuntime().llm at call time. */
|
|
5
|
+
llm?: LLMPort;
|
|
6
|
+
}
|
|
7
|
+
export declare function runJudgePipeline(judges: JudgeScorer | JudgeScorer[], ctx: ScorerContext, opts?: JudgePipelineOptions): Promise<ScorerResultEntry[]>;
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { getRuntime } from './eval-runtime.js';
|
|
2
|
+
import { runJudgeWithTools } from './judge-tool-runner.js';
|
|
3
|
+
import { buildBatchedJudgePrompt, buildJudgePrompt } from './judge-prompt-builder.js';
|
|
4
|
+
import { clamp, getErrorMessage } from './scorer-utils.js';
|
|
5
|
+
export async function runJudgePipeline(judges, ctx, opts) {
|
|
6
|
+
const list = Array.isArray(judges) ? judges : [judges];
|
|
7
|
+
const llm = opts?.llm ?? getRuntime().llm;
|
|
8
|
+
// Partition BEFORE model grouping: tool-using judges always run individually
|
|
9
|
+
// against the tool-use runner. Batching would silently produce wrong scores
|
|
10
|
+
// for a tool-using judge sharing a model group with a plain judge.
|
|
11
|
+
const toolJudges = list.filter(hasTools);
|
|
12
|
+
const plainJudges = list.filter((j) => !hasTools(j));
|
|
13
|
+
const toolResultsPromise = Promise.all(toolJudges.map((j) => runJudgeWithToolsEntry(j, ctx, llm)));
|
|
14
|
+
const plainResultsPromise = runPlainJudges(plainJudges, ctx, llm);
|
|
15
|
+
const [toolResults, plainResults] = await Promise.all([toolResultsPromise, plainResultsPromise]);
|
|
16
|
+
// Preserve original order.
|
|
17
|
+
const byName = new Map();
|
|
18
|
+
for (const r of [...toolResults, ...plainResults])
|
|
19
|
+
byName.set(r.name, r);
|
|
20
|
+
return list.map((j) => byName.get(j.name));
|
|
21
|
+
}
|
|
22
|
+
function hasTools(j) {
|
|
23
|
+
return Array.isArray(j.tools) && j.tools.length > 0;
|
|
24
|
+
}
|
|
25
|
+
async function runJudgeWithToolsEntry(scorer, ctx, llm) {
|
|
26
|
+
const result = await runJudgeWithTools(scorer, ctx, llm);
|
|
27
|
+
return result.entry;
|
|
28
|
+
}
|
|
29
|
+
async function runPlainJudges(judges, ctx, llm) {
|
|
30
|
+
if (judges.length === 0)
|
|
31
|
+
return [];
|
|
32
|
+
if (judges.length <= 1) {
|
|
33
|
+
return Promise.all(judges.map((g) => runJudgeScorer(g, ctx, llm)));
|
|
34
|
+
}
|
|
35
|
+
// Group judges by model — only batch judges with the same model
|
|
36
|
+
const byModel = new Map();
|
|
37
|
+
for (const j of judges) {
|
|
38
|
+
const key = j.model ?? '__default__';
|
|
39
|
+
const group = byModel.get(key) ?? [];
|
|
40
|
+
group.push(j);
|
|
41
|
+
byModel.set(key, group);
|
|
42
|
+
}
|
|
43
|
+
// If all judges have different models, run individually
|
|
44
|
+
if (byModel.size === judges.length) {
|
|
45
|
+
return Promise.all(judges.map((g) => runJudgeScorer(g, ctx, llm)));
|
|
46
|
+
}
|
|
47
|
+
const groupResults = await Promise.all([...byModel.values()].map(async (group) => {
|
|
48
|
+
if (group.length === 1) {
|
|
49
|
+
return [await runJudgeScorer(group[0], ctx, llm)];
|
|
50
|
+
}
|
|
51
|
+
// Attempt batched execution
|
|
52
|
+
const batchResults = await runBatchedJudges(group, ctx, llm);
|
|
53
|
+
if (batchResults) {
|
|
54
|
+
return batchResults;
|
|
55
|
+
}
|
|
56
|
+
// Fallback to individual calls
|
|
57
|
+
return Promise.all(group.map((g) => runJudgeScorer(g, ctx, llm)));
|
|
58
|
+
}));
|
|
59
|
+
return groupResults.flat();
|
|
60
|
+
}
|
|
61
|
+
// --- Internal helpers ---
|
|
62
|
+
async function runBatchedJudges(judges, ctx, llm) {
|
|
63
|
+
const resolvedInputs = await Promise.all(judges.map((judge) => resolveJudgeInput(judge, ctx)));
|
|
64
|
+
const prompt = buildBatchedJudgePrompt(judges, ctx, resolvedInputs);
|
|
65
|
+
try {
|
|
66
|
+
const response = await llm.call(prompt, { model: judges[0].model, cacheControl: true });
|
|
67
|
+
const cleaned = response.text.replace(/```(?:json)?\s*/g, '').replace(/```/g, '').trim();
|
|
68
|
+
const arrayMatch = cleaned.match(/\[[\s\S]*\]/);
|
|
69
|
+
if (!arrayMatch)
|
|
70
|
+
return null;
|
|
71
|
+
const parsed = JSON.parse(arrayMatch[0]);
|
|
72
|
+
if (!Array.isArray(parsed) || parsed.length !== judges.length)
|
|
73
|
+
return null;
|
|
74
|
+
return judges.map((j, i) => {
|
|
75
|
+
const entry = parsed[i];
|
|
76
|
+
return {
|
|
77
|
+
name: j.name,
|
|
78
|
+
type: 'judge',
|
|
79
|
+
score: clamp(parseFloat(String(entry.score)) || 0),
|
|
80
|
+
weight: j.weight,
|
|
81
|
+
details: entry.details || entry.reasoning || 'No details provided',
|
|
82
|
+
status: 'ok',
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async function runJudgeScorer(scorer, ctx, llm) {
|
|
91
|
+
const input = await resolveJudgeInput(scorer, ctx);
|
|
92
|
+
const prompt = buildJudgePrompt(scorer, ctx, input);
|
|
93
|
+
const retries = getRetryCount(scorer.retry);
|
|
94
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
95
|
+
try {
|
|
96
|
+
const response = await llm.call(prompt, { model: scorer.model, cacheControl: true });
|
|
97
|
+
return parseJudgeResponse(response.text, scorer);
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
if (attempt === retries) {
|
|
101
|
+
return {
|
|
102
|
+
name: scorer.name,
|
|
103
|
+
type: 'judge',
|
|
104
|
+
score: 0,
|
|
105
|
+
weight: scorer.weight,
|
|
106
|
+
details: getErrorMessage(error),
|
|
107
|
+
status: 'error',
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
await wait(1000 * (2 ** attempt));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
name: scorer.name,
|
|
115
|
+
type: 'judge',
|
|
116
|
+
score: 0,
|
|
117
|
+
weight: scorer.weight,
|
|
118
|
+
details: 'Judge retry loop exited unexpectedly',
|
|
119
|
+
status: 'error',
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function parseJudgeResponse(text, scorer) {
|
|
123
|
+
try {
|
|
124
|
+
const cleaned = text.replace(/```(?:json)?\s*/g, '').replace(/```/g, '').trim();
|
|
125
|
+
const jsonMatch = cleaned.match(/\{[\s\S]*\}/);
|
|
126
|
+
if (jsonMatch) {
|
|
127
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
128
|
+
return {
|
|
129
|
+
name: scorer.name,
|
|
130
|
+
type: 'judge',
|
|
131
|
+
score: clamp(parseFloat(parsed.score) || 0),
|
|
132
|
+
weight: scorer.weight,
|
|
133
|
+
details: parsed.details || parsed.reasoning || 'No details provided',
|
|
134
|
+
status: 'ok',
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
const scoreMatch = text.match(/"score"\s*:\s*([\d.]+)/);
|
|
140
|
+
if (scoreMatch) {
|
|
141
|
+
return {
|
|
142
|
+
name: scorer.name,
|
|
143
|
+
type: 'judge',
|
|
144
|
+
score: clamp(parseFloat(scoreMatch[1]) || 0),
|
|
145
|
+
weight: scorer.weight,
|
|
146
|
+
details: 'Parsed score from truncated LLM response',
|
|
147
|
+
status: 'ok',
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
name: scorer.name,
|
|
153
|
+
type: 'judge',
|
|
154
|
+
score: 0,
|
|
155
|
+
weight: scorer.weight,
|
|
156
|
+
details: `Failed to parse LLM response: ${text.substring(0, 200)}`,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
async function resolveJudgeInput(scorer, ctx) {
|
|
160
|
+
if (!scorer.input)
|
|
161
|
+
return undefined;
|
|
162
|
+
if (typeof scorer.input === 'function') {
|
|
163
|
+
return scorer.input(ctx);
|
|
164
|
+
}
|
|
165
|
+
return scorer.input;
|
|
166
|
+
}
|
|
167
|
+
function getRetryCount(retry) {
|
|
168
|
+
if (retry === true) {
|
|
169
|
+
return 1;
|
|
170
|
+
}
|
|
171
|
+
if (typeof retry === 'number' && Number.isFinite(retry)) {
|
|
172
|
+
return Math.max(0, Math.floor(retry));
|
|
173
|
+
}
|
|
174
|
+
return 0;
|
|
175
|
+
}
|
|
176
|
+
function wait(ms) {
|
|
177
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
178
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { JudgeScorer, ScorerContext } from './types.js';
|
|
2
|
+
export declare function buildJudgePrompt(scorer: JudgeScorer, ctx: ScorerContext, input?: Record<string, unknown>): string;
|
|
3
|
+
export declare function buildBatchedJudgePrompt(judges: JudgeScorer[], ctx: ScorerContext, inputs: Array<Record<string, unknown> | undefined>): string;
|
|
4
|
+
export declare function buildToolUseJudgePrompt(scorer: JudgeScorer, ctx: ScorerContext): {
|
|
5
|
+
system: string;
|
|
6
|
+
user: string;
|
|
7
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export function buildJudgePrompt(scorer, ctx, input) {
|
|
2
|
+
const sections = [];
|
|
3
|
+
sections.push(`## Session Transcript\n${ctx.transcript}`);
|
|
4
|
+
if (scorer.includeToolEvents && ctx.toolEvents.length > 0) {
|
|
5
|
+
sections.push(`## Tool Events\n${formatToolEvents(ctx)}`);
|
|
6
|
+
}
|
|
7
|
+
if (input) {
|
|
8
|
+
for (const [key, value] of Object.entries(input)) {
|
|
9
|
+
const body = typeof value === 'string' ? value : JSON.stringify(value, null, 2);
|
|
10
|
+
sections.push(`## ${key}\n${body}`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return `You are an evaluation judge. Score the following agent session on a scale from 0.0 to 1.0 based on the rubric below.
|
|
14
|
+
|
|
15
|
+
${sections.join('\n\n')}
|
|
16
|
+
|
|
17
|
+
## Rubric
|
|
18
|
+
${scorer.rubric}
|
|
19
|
+
|
|
20
|
+
Respond with ONLY a JSON object: {"score": <number>, "details": "<brief explanation>"}`;
|
|
21
|
+
}
|
|
22
|
+
function formatToolEvents(ctx) {
|
|
23
|
+
return ctx.toolEvents
|
|
24
|
+
.map((event) => {
|
|
25
|
+
const turn = event.turnNumber ? `turn ${event.turnNumber}` : 'instruction';
|
|
26
|
+
return `- ${turn}: ${event.action} via ${event.providerToolName} (${event.provider})`;
|
|
27
|
+
})
|
|
28
|
+
.join('\n');
|
|
29
|
+
}
|
|
30
|
+
export function buildBatchedJudgePrompt(judges, ctx, inputs) {
|
|
31
|
+
const sections = [];
|
|
32
|
+
sections.push(`## Session Transcript\n${ctx.transcript}`);
|
|
33
|
+
if (judges.some((j) => j.includeToolEvents) && ctx.toolEvents.length > 0) {
|
|
34
|
+
sections.push(`## Tool Events\n${formatToolEvents(ctx)}`);
|
|
35
|
+
}
|
|
36
|
+
const rubrics = judges.map((j, i) => {
|
|
37
|
+
const parts = [`### Rubric ${i + 1}: "${j.name}"\n${j.rubric}`];
|
|
38
|
+
const input = inputs[i];
|
|
39
|
+
if (input) {
|
|
40
|
+
for (const [key, value] of Object.entries(input)) {
|
|
41
|
+
const body = typeof value === 'string' ? value : JSON.stringify(value, null, 2);
|
|
42
|
+
parts.push(`#### ${key}\n${body}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return parts.join('\n\n');
|
|
46
|
+
}).join('\n\n');
|
|
47
|
+
return `You are an evaluation judge. Score the following agent session on each rubric below, from 0.0 to 1.0.
|
|
48
|
+
|
|
49
|
+
${sections.join('\n\n')}
|
|
50
|
+
|
|
51
|
+
${rubrics}
|
|
52
|
+
|
|
53
|
+
Respond with ONLY a JSON array, one entry per rubric in order:
|
|
54
|
+
[{"scorer_name": "<name>", "score": <number>, "details": "<brief explanation>"}, ...]`;
|
|
55
|
+
}
|
|
56
|
+
export function buildToolUseJudgePrompt(scorer, ctx) {
|
|
57
|
+
const system = [
|
|
58
|
+
'You are an evaluation judge with access to workspace-reading tools.',
|
|
59
|
+
'Use the tools to gather the evidence you need before scoring.',
|
|
60
|
+
'When you have enough evidence, reply with a final score as a fenced JSON block:',
|
|
61
|
+
'```json',
|
|
62
|
+
'{"score": <number 0..1>, "details": "<one-paragraph rationale citing evidence>"}',
|
|
63
|
+
'```',
|
|
64
|
+
'Do not include any other text after the JSON block.',
|
|
65
|
+
].join('\n');
|
|
66
|
+
const parts = [];
|
|
67
|
+
parts.push('## Session Transcript');
|
|
68
|
+
parts.push(ctx.transcript);
|
|
69
|
+
if (scorer.includeToolEvents && ctx.toolEvents.length > 0) {
|
|
70
|
+
parts.push('## Tool Events');
|
|
71
|
+
parts.push(formatToolEvents(ctx));
|
|
72
|
+
}
|
|
73
|
+
parts.push('## Rubric');
|
|
74
|
+
parts.push(scorer.rubric);
|
|
75
|
+
return { system, user: parts.join('\n\n') };
|
|
76
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ToolCapableLLMPort } from '../utils/llm-types.js';
|
|
2
|
+
import type { JudgeScorer, ScorerContext, ScorerResultEntry, TokenUsage } from './types.js';
|
|
3
|
+
import { type JudgeErrorCode, type JudgeToolCallRecord, type ToolExecutor } from './judge-tool-session.js';
|
|
4
|
+
export type { JudgeErrorCode, JudgeToolCallRecord };
|
|
5
|
+
export interface RunJudgeWithToolsResult {
|
|
6
|
+
entry: ScorerResultEntry;
|
|
7
|
+
rounds: number;
|
|
8
|
+
toolCalls: JudgeToolCallRecord[];
|
|
9
|
+
tokenUsage: TokenUsage;
|
|
10
|
+
wallTimeMs: number;
|
|
11
|
+
}
|
|
12
|
+
export interface RunJudgeWithToolsOptions {
|
|
13
|
+
maxRounds?: number;
|
|
14
|
+
/** Override the per-tool timeout (ms). Default 10_000. */
|
|
15
|
+
toolTimeoutMs?: number;
|
|
16
|
+
/** Override tool execution; used by tests that need to simulate slow or buggy tools. */
|
|
17
|
+
toolExecutor?: ToolExecutor;
|
|
18
|
+
}
|
|
19
|
+
export declare function runJudgeWithTools(scorer: JudgeScorer, ctx: ScorerContext, llm: ToolCapableLLMPort, opts?: RunJudgeWithToolsOptions): Promise<RunJudgeWithToolsResult>;
|