@hue-run/sdk 0.9.0 → 0.11.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/CLI.md +240 -49
- package/ENVIRONMENTS.md +48 -9
- package/EVALUATIONS.md +41 -21
- package/README.md +70 -30
- package/dist/cli/eval-direct.d.ts +12 -1
- package/dist/cli/eval-direct.js +145 -33
- package/dist/cli/eval.d.ts +28 -1
- package/dist/cli/eval.js +504 -81
- package/dist/cli/listen.d.ts +76 -0
- package/dist/cli/listen.js +816 -0
- package/dist/cli/mcp.d.ts +65 -7
- package/dist/cli/mcp.js +271 -73
- package/dist/client.d.ts +7 -4
- package/dist/client.js +36 -10
- package/dist/evals/checkpoint.d.ts +15 -1
- package/dist/evals/checkpoint.js +34 -3
- package/dist/evals/client.d.ts +24 -4
- package/dist/evals/client.js +92 -38
- package/dist/evals/environment-target.d.ts +18 -3
- package/dist/evals/environment-target.js +22 -10
- package/dist/evals/exit-cleanup.d.ts +4 -0
- package/dist/evals/exit-cleanup.js +26 -0
- package/dist/evals/files.d.ts +69 -3
- package/dist/evals/files.js +255 -42
- package/dist/evals/json.js +116 -9
- package/dist/evals/local-worker.d.ts +21 -3
- package/dist/evals/local-worker.js +14 -0
- package/dist/evals/runner.d.ts +45 -0
- package/dist/evals/runner.js +199 -21
- package/dist/evals/scenarios.d.ts +7 -5
- package/dist/evals/scenarios.js +71 -14
- package/dist/evals/scorer-publication.js +58 -3
- package/dist/evals/simulation.d.ts +17 -4
- package/dist/evals/simulation.js +8 -0
- package/dist/evals/types.d.ts +48 -0
- package/dist/evals/verdicts.d.ts +26 -2
- package/dist/evals/verdicts.js +72 -9
- package/dist/evals.d.ts +4 -3
- package/dist/evals.js +2 -2
- package/dist/inline-files.d.ts +4 -0
- package/dist/inline-files.js +48 -17
- package/dist/provider-tools.d.ts +12 -1
- package/dist/provider-tools.js +192 -106
- package/dist/safety.d.ts +7 -1
- package/dist/safety.js +9 -7
- package/dist/setup/cli.js +31 -8
- package/dist/setup/peers.d.ts +13 -0
- package/dist/setup/peers.js +44 -0
- package/dist/snapshot.d.ts +2 -2
- package/dist/snapshot.js +47 -12
- package/dist/tool-definitions.d.ts +18 -0
- package/dist/tool-definitions.js +232 -0
- package/dist/transport.js +2 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -30,6 +30,47 @@ const worldOutcomeMetrics = [
|
|
|
30
30
|
"process_constraints",
|
|
31
31
|
"task_success",
|
|
32
32
|
].map((name) => ({ name, type: "boolean" }));
|
|
33
|
+
/** `hue.conversion_outcome.v2`: v1's rubric with the destination reported as `recipient`,
|
|
34
|
+
* `thread` and `subject`. */
|
|
35
|
+
const conversionOutcomeV2Metrics = [
|
|
36
|
+
"completed_run",
|
|
37
|
+
"saved_draft",
|
|
38
|
+
"correct_destination",
|
|
39
|
+
"recipient",
|
|
40
|
+
"thread",
|
|
41
|
+
"subject",
|
|
42
|
+
"content",
|
|
43
|
+
"unrelated_preserved",
|
|
44
|
+
"process_constraints",
|
|
45
|
+
"task_success",
|
|
46
|
+
].map((name) => ({ name, type: "boolean" }));
|
|
47
|
+
/** `hue.outcome_assertions.v2`'s counts; `.v3` adds the judge counts. */
|
|
48
|
+
const outcomeAssertionMetrics = [
|
|
49
|
+
{ name: "task_success", type: "boolean" },
|
|
50
|
+
...["assertions_passed", "assertions_failed", "advisory_failed", "agent_mistakes"].map((name) => ({ name, type: "number", min: 0 })),
|
|
51
|
+
];
|
|
52
|
+
const outcomeAssertionV3Metrics = [
|
|
53
|
+
...outcomeAssertionMetrics,
|
|
54
|
+
...["judges_passed", "judges_failed", "judges_advisory"].map((name) => ({
|
|
55
|
+
name,
|
|
56
|
+
type: "number",
|
|
57
|
+
min: 0,
|
|
58
|
+
})),
|
|
59
|
+
];
|
|
60
|
+
/** The judge pin a `hue.outcome_assertions.v3` version carries; every field is required. */
|
|
61
|
+
const outcomeJudgeConfig = z.strictObject({
|
|
62
|
+
model: z.string().min(1).max(200),
|
|
63
|
+
provider: z.string().min(1).max(64),
|
|
64
|
+
template: z.string().regex(/^[a-f0-9]{64}$/),
|
|
65
|
+
samples: z.number().int().min(1).max(9),
|
|
66
|
+
temperature: z.number().min(0).max(2),
|
|
67
|
+
maxOutputTokens: z.number().int().min(1).max(32_000),
|
|
68
|
+
timeoutMs: z.number().int().min(1_000).max(300_000),
|
|
69
|
+
});
|
|
70
|
+
/** A Hue-executed entry whose metrics are fixed by the entry itself. */
|
|
71
|
+
const fixedMetrics = (fixed) => metrics
|
|
72
|
+
.default(fixed)
|
|
73
|
+
.refine((value) => digest(value) === digest(fixed), "World outcome metrics are fixed by the pinned entry");
|
|
33
74
|
const jsonValue = z.custom((value) => {
|
|
34
75
|
try {
|
|
35
76
|
json(value);
|
|
@@ -85,9 +126,23 @@ const sdkScorerPublication = z.union([
|
|
|
85
126
|
z.strictObject({
|
|
86
127
|
kind: z.literal("world_outcome"),
|
|
87
128
|
entry: z.literal("hue.conversion_outcome.v1"),
|
|
88
|
-
metrics:
|
|
89
|
-
|
|
90
|
-
|
|
129
|
+
metrics: fixedMetrics(worldOutcomeMetrics),
|
|
130
|
+
}),
|
|
131
|
+
z.strictObject({
|
|
132
|
+
kind: z.literal("world_outcome"),
|
|
133
|
+
entry: z.literal("hue.conversion_outcome.v2"),
|
|
134
|
+
metrics: fixedMetrics(conversionOutcomeV2Metrics),
|
|
135
|
+
}),
|
|
136
|
+
z.strictObject({
|
|
137
|
+
kind: z.literal("world_outcome"),
|
|
138
|
+
entry: z.literal("hue.outcome_assertions.v2"),
|
|
139
|
+
metrics: fixedMetrics(outcomeAssertionMetrics),
|
|
140
|
+
}),
|
|
141
|
+
z.strictObject({
|
|
142
|
+
kind: z.literal("world_outcome"),
|
|
143
|
+
entry: z.literal("hue.outcome_assertions.v3"),
|
|
144
|
+
metrics: fixedMetrics(outcomeAssertionV3Metrics),
|
|
145
|
+
config: z.strictObject({ judge: outcomeJudgeConfig }),
|
|
91
146
|
}),
|
|
92
147
|
z.strictObject({ kind: z.literal("manual"), metrics }),
|
|
93
148
|
z.strictObject({ kind: z.literal("llm_judge"), config: judgeConfig, metrics }),
|
|
@@ -4,8 +4,8 @@ import type { EnvironmentTool } from "../environment/tools.js";
|
|
|
4
4
|
import type { EnvironmentIdentity, PublishableEnvironmentDefinition, WorldHandoff } from "../environment/types.js";
|
|
5
5
|
import { type EvaluationClient } from "./client.js";
|
|
6
6
|
import type { ActualAgentManifestInputV2, AttemptConnectionBundleV2, RequestedAttemptProviderV2 } from "./attempt.js";
|
|
7
|
-
import { type RunnerReport } from "./runner.js";
|
|
8
|
-
import type { ExperimentCase, Identity, JsonValue, LocalScorer, ScorerDefinition, SimulationMcpCapability } from "./types.js";
|
|
7
|
+
import { type RunnerReport, type TelemetryNotAccepted } from "./runner.js";
|
|
8
|
+
import type { ExperimentCase, Identity, JsonValue, LocalFile, LocalScorer, ScorerDefinition, SimulationMcpCapability, TargetResult } from "./types.js";
|
|
9
9
|
/** Repository-authored scorer identity and its public definition or local binding. */
|
|
10
10
|
export interface RepositorySimulationScorer extends Identity {
|
|
11
11
|
/** Scorer definition to publish, or a local callback bound to its source digest. */
|
|
@@ -121,6 +121,12 @@ export interface SimulationTargetContext {
|
|
|
121
121
|
/** Credential-bearing provider connections for this callback only. Hue never
|
|
122
122
|
* checkpoints, logs or adds this response to parity digests. */
|
|
123
123
|
connectionBundle?: AttemptConnectionBundleV2;
|
|
124
|
+
/** Verified copies of the case's agent-visible input files (`source`, templates,
|
|
125
|
+
* references, originals); evaluator-only files are withheld. Empty when the case has none. */
|
|
126
|
+
files: LocalFile[];
|
|
127
|
+
/** Private directory for this case, removed after it; return generated files with
|
|
128
|
+
* `withFiles`. */
|
|
129
|
+
outputDirectory: string;
|
|
124
130
|
/** Cancellation is cooperative: pass this signal into the real agent/provider call. */
|
|
125
131
|
signal?: AbortSignal;
|
|
126
132
|
}
|
|
@@ -152,6 +158,12 @@ export interface RunSimulationOptions {
|
|
|
152
158
|
/** Bounded explanation for the omission. */
|
|
153
159
|
reason: string;
|
|
154
160
|
};
|
|
161
|
+
/** What a case does when required telemetry is not accepted; see
|
|
162
|
+
* `traceNotAccepted` of {@link runExperiment}. Defaults to `"stop"`. */
|
|
163
|
+
traceNotAccepted?: "stop" | "fail_case";
|
|
164
|
+
/** Called as each case is failed for its telemetry; see `onTelemetryNotAccepted` of
|
|
165
|
+
* {@link runExperiment}. */
|
|
166
|
+
onTelemetryNotAccepted?(entry: TelemetryNotAccepted): void | Promise<void>;
|
|
155
167
|
/** Local scorer callbacks bound by their declared source digests. */
|
|
156
168
|
localScorers?: LocalScorer[];
|
|
157
169
|
/** Cases in flight, 1–64; defaults to 1. */
|
|
@@ -190,8 +202,9 @@ export interface RunSimulationOptions {
|
|
|
190
202
|
/** Selected MCP surface. */
|
|
191
203
|
surfaceKey: "google.gmail/mcp" | "slack/mcp";
|
|
192
204
|
};
|
|
193
|
-
/** Invokes the existing local agent exactly once for this attempt
|
|
194
|
-
|
|
205
|
+
/** Invokes the existing local agent exactly once for this attempt; return the output, or
|
|
206
|
+
* `withFiles(output, files)` when it generated files. */
|
|
207
|
+
target(inputs: JsonValue, context: SimulationTargetContext): JsonValue | TargetResult | undefined | Promise<JsonValue | TargetResult | undefined>;
|
|
195
208
|
/** Receives bounded nonsecret lifecycle progress. */
|
|
196
209
|
onProgress?(event: SimulationProgress): void | Promise<void>;
|
|
197
210
|
}
|
package/dist/evals/simulation.js
CHANGED
|
@@ -430,6 +430,12 @@ export async function runSimulation(options) {
|
|
|
430
430
|
checkpointDirectory: join(store.directory, `experiment-${experimentId}`),
|
|
431
431
|
persistResultContent: options.persistResultContent,
|
|
432
432
|
traceEvidence: options.traceEvidence,
|
|
433
|
+
...(options.traceNotAccepted ? { traceNotAccepted: options.traceNotAccepted } : {}),
|
|
434
|
+
...(options.onTelemetryNotAccepted
|
|
435
|
+
? {
|
|
436
|
+
onTelemetryNotAccepted: (entry) => options.onTelemetryNotAccepted(entry),
|
|
437
|
+
}
|
|
438
|
+
: {}),
|
|
433
439
|
environmentEvidence: "required",
|
|
434
440
|
scorers: bindings,
|
|
435
441
|
concurrency: options.concurrency,
|
|
@@ -474,6 +480,8 @@ export async function runSimulation(options) {
|
|
|
474
480
|
...(targetContext.connectionBundle
|
|
475
481
|
? { connectionBundle: structuredClone(targetContext.connectionBundle) }
|
|
476
482
|
: {}),
|
|
483
|
+
files: structuredClone(targetContext.files),
|
|
484
|
+
outputDirectory: targetContext.outputDirectory,
|
|
477
485
|
signal: targetContext.signal,
|
|
478
486
|
}),
|
|
479
487
|
}),
|
package/dist/evals/types.d.ts
CHANGED
|
@@ -202,6 +202,33 @@ export type ScorerDefinition = {
|
|
|
202
202
|
entry: "hue.conversion_outcome.v1";
|
|
203
203
|
/** The seven fixed boolean metrics defined by the entry. */
|
|
204
204
|
metrics: MetricDefinition[];
|
|
205
|
+
} | {
|
|
206
|
+
/** Scored inside Hue using immutable world evidence; the local runner defers it. */
|
|
207
|
+
kind: "world_outcome";
|
|
208
|
+
/** v1's rubric with the destination reported as `recipient`, `thread` and `subject`. */
|
|
209
|
+
entry: "hue.conversion_outcome.v2";
|
|
210
|
+
/** The ten fixed boolean metrics defined by the entry. */
|
|
211
|
+
metrics: MetricDefinition[];
|
|
212
|
+
} | {
|
|
213
|
+
/** A case's reviewed outcome assertions, scored inside Hue; the local runner defers it. */
|
|
214
|
+
kind: "world_outcome";
|
|
215
|
+
/** Pinned Hue-executed assertions evaluator. */
|
|
216
|
+
entry: "hue.outcome_assertions.v2";
|
|
217
|
+
/** `task_success` and the fixed assertion counts defined by the entry. */
|
|
218
|
+
metrics: MetricDefinition[];
|
|
219
|
+
} | {
|
|
220
|
+
/** The v2 assertions plus the case's calibrated judges, scored inside Hue; the local runner
|
|
221
|
+
* defers it. */
|
|
222
|
+
kind: "world_outcome";
|
|
223
|
+
/** Pinned Hue-executed assertions-and-judges evaluator. */
|
|
224
|
+
entry: "hue.outcome_assertions.v3";
|
|
225
|
+
/** `task_success`, the assertion counts and the judge counts defined by the entry. */
|
|
226
|
+
metrics: MetricDefinition[];
|
|
227
|
+
/** The judge pin: model, template and sampling policy belong to the version. */
|
|
228
|
+
config: {
|
|
229
|
+
/** The judge every case scored by this version uses. */
|
|
230
|
+
judge: OutcomeJudgeConfig;
|
|
231
|
+
};
|
|
205
232
|
} | {
|
|
206
233
|
/** Scored by a person in Hue; the local runner defers it. */
|
|
207
234
|
kind: "manual";
|
|
@@ -215,6 +242,23 @@ export type ScorerDefinition = {
|
|
|
215
242
|
/** Metrics the judge reports. */
|
|
216
243
|
metrics: MetricDefinition[];
|
|
217
244
|
};
|
|
245
|
+
/** The judge a `hue.outcome_assertions.v3` version pins. */
|
|
246
|
+
export interface OutcomeJudgeConfig {
|
|
247
|
+
/** A provider-prefixed model id, such as `anthropic/claude-fable-5.1`. */
|
|
248
|
+
model: string;
|
|
249
|
+
/** The model's provider. */
|
|
250
|
+
provider: string;
|
|
251
|
+
/** SHA-256 hex digest of the judge template: its fixed instructions and evidence rendering. */
|
|
252
|
+
template: string;
|
|
253
|
+
/** Answers sampled per rubric item, 1–9; a majority decides. */
|
|
254
|
+
samples: number;
|
|
255
|
+
/** Sampling temperature, 0–2. */
|
|
256
|
+
temperature: number;
|
|
257
|
+
/** Output token limit per answer. */
|
|
258
|
+
maxOutputTokens: number;
|
|
259
|
+
/** Per-answer timeout in milliseconds. */
|
|
260
|
+
timeoutMs: number;
|
|
261
|
+
}
|
|
218
262
|
/** Configuration of a hosted judge scorer. */
|
|
219
263
|
export interface JudgeConfig {
|
|
220
264
|
/** Judge model identifier. */
|
|
@@ -264,6 +308,10 @@ export interface StoredResult extends ResultSummary {
|
|
|
264
308
|
error: TypedError | null;
|
|
265
309
|
/** Source digest of the local scorer that produced it, when applicable. */
|
|
266
310
|
sourceDigest: string | null;
|
|
311
|
+
/** True only for a `skipped` result Hue's own outcome scoring recorded because the evaluator
|
|
312
|
+
* does not apply to the case (it has no outcome criteria or conversion rubric to grade); a
|
|
313
|
+
* submitted result never reads true. Absent from servers that predate it. */
|
|
314
|
+
notApplicable?: boolean;
|
|
267
315
|
}
|
|
268
316
|
/** A hosted judge job and its charge accounting. */
|
|
269
317
|
export interface JudgeJob {
|
package/dist/evals/verdicts.d.ts
CHANGED
|
@@ -16,6 +16,10 @@ export interface WaitForResultsOptions {
|
|
|
16
16
|
pollIntervalMillis?: number;
|
|
17
17
|
/** Stops waiting early; the partial state is returned with `complete: false`. */
|
|
18
18
|
signal?: AbortSignal;
|
|
19
|
+
/** Pinned versions of Hue's own judges (definition kind `world_judge`), whose results Hue can
|
|
20
|
+
* mark advisory. Any evaluator can write the same evidence, so no other result is advisory.
|
|
21
|
+
* {@link collectExperimentVerdicts} reads them from the experiment. */
|
|
22
|
+
judgeScorerVersionIds?: string[];
|
|
19
23
|
}
|
|
20
24
|
/** One stored result joined to its evaluation item. */
|
|
21
25
|
export interface VerdictResult {
|
|
@@ -35,6 +39,15 @@ export interface VerdictResult {
|
|
|
35
39
|
explanation: string | null;
|
|
36
40
|
/** Scorer failure for `state: "error"`, otherwise `null`. */
|
|
37
41
|
error: TypedError | null;
|
|
42
|
+
/** Whether Hue recorded that the evaluator does not apply to the case; such a result counts as
|
|
43
|
+
* neither a pass nor a failure. Absent means false. */
|
|
44
|
+
notApplicable?: boolean;
|
|
45
|
+
/** What a not-applicable evaluator needs that the case lacks, such as `outcome_criteria`. */
|
|
46
|
+
requires?: string;
|
|
47
|
+
/** Whether Hue recorded the result as advisory, as it does for every judge today: a result of a
|
|
48
|
+
* pinned Hue judge whose evidence says `advisory: true`, not an error, and whose metrics carry
|
|
49
|
+
* no `passed`. It is shown with its verdict but never decides a case. Absent means false. */
|
|
50
|
+
advisory?: boolean;
|
|
38
51
|
}
|
|
39
52
|
/** Outcome of {@link waitForResults}. */
|
|
40
53
|
export interface VerdictResults {
|
|
@@ -65,10 +78,18 @@ export interface CaseVerdict {
|
|
|
65
78
|
externalKey: string;
|
|
66
79
|
/** Subject scored for the case, or `null` before completion. */
|
|
67
80
|
subjectId: string | null;
|
|
68
|
-
/** `passed` and `failed` are scored verdicts; `error`, `skipped` and `pending` carry no verdict.
|
|
81
|
+
/** `passed` and `failed` are scored verdicts; `error`, `skipped` and `pending` carry no verdict.
|
|
82
|
+
* Only the evaluators that apply to the case decide it; one where none applies is an error. */
|
|
69
83
|
state: "passed" | "failed" | "error" | "skipped" | "pending";
|
|
70
|
-
/** True only for `state: "passed"`: every scored metric
|
|
84
|
+
/** True only for `state: "passed"`: every scored metric of an applicable pin passed and no
|
|
85
|
+
* applicable pin errored or is missing. */
|
|
71
86
|
passed: boolean;
|
|
87
|
+
/** Pinned scorer versions Hue recorded as not applicable to the case; always set by
|
|
88
|
+
* {@link summarizeVerdicts}. */
|
|
89
|
+
notApplicable?: string[];
|
|
90
|
+
/** Pinned scorer versions whose results for the case are advisory: their metrics are listed
|
|
91
|
+
* but never decide the case; always set by {@link summarizeVerdicts}. */
|
|
92
|
+
advisory?: string[];
|
|
72
93
|
/** Reported metrics across pinned scorers, in result order. */
|
|
73
94
|
metrics: (Metric & {
|
|
74
95
|
/** Scorer version that reported the metric. */
|
|
@@ -97,6 +118,9 @@ export interface VerdictSummary {
|
|
|
97
118
|
skipped: number;
|
|
98
119
|
/** Cases still missing a result. */
|
|
99
120
|
pending: number;
|
|
121
|
+
/** Results of evaluators that do not apply to their case, a case can have several; always
|
|
122
|
+
* set by {@link summarizeVerdicts}. */
|
|
123
|
+
notApplicable?: number;
|
|
100
124
|
};
|
|
101
125
|
}
|
|
102
126
|
/** Whether one reported metric counts as passing: an explicit `passed`, else a true boolean. */
|
package/dist/evals/verdicts.js
CHANGED
|
@@ -42,6 +42,7 @@ export async function waitForResults(client, options) {
|
|
|
42
42
|
throw new RangeError("timeoutMillis must be ≥ 0");
|
|
43
43
|
if (!Number.isInteger(interval) || interval < 250 || interval > 60_000)
|
|
44
44
|
throw new RangeError("pollIntervalMillis must be 250–60000");
|
|
45
|
+
const judges = new Set(options.judgeScorerVersionIds ?? []);
|
|
45
46
|
const wanted = options.subjectIds ? new Set(options.subjectIds) : undefined;
|
|
46
47
|
const pins = [...new Set(options.scorerVersionIds)];
|
|
47
48
|
const deadline = Date.now() + timeout;
|
|
@@ -66,6 +67,16 @@ export async function waitForResults(client, options) {
|
|
|
66
67
|
if (!id)
|
|
67
68
|
continue;
|
|
68
69
|
const stored = await client.getResult(id);
|
|
70
|
+
// Only Hue's own flag on a skipped result marks it not applicable; the evidence alone
|
|
71
|
+
// never does, and a scored or errored result keeps its verdict whatever it carries.
|
|
72
|
+
const notApplicable = stored.state === "skipped" && stored.notApplicable === true;
|
|
73
|
+
const evidence = stored.evidence;
|
|
74
|
+
// Any evaluator can write `advisory: true` in its evidence; only Hue's own judges'
|
|
75
|
+
// results are advisory, never as an error or with a metric that says it passed.
|
|
76
|
+
const advisory = judges.has(stored.scorerVersionId) &&
|
|
77
|
+
stored.state !== "error" &&
|
|
78
|
+
evidence?.advisory === true &&
|
|
79
|
+
!(stored.state === "scored" && stored.metrics.some((metric) => "passed" in metric));
|
|
69
80
|
results.push({
|
|
70
81
|
id: stored.id,
|
|
71
82
|
itemId: stored.itemId,
|
|
@@ -75,6 +86,11 @@ export async function waitForResults(client, options) {
|
|
|
75
86
|
metrics: stored.state === "scored" ? stored.metrics : [],
|
|
76
87
|
explanation: stored.explanation ?? null,
|
|
77
88
|
error: stored.error ?? null,
|
|
89
|
+
notApplicable,
|
|
90
|
+
advisory,
|
|
91
|
+
...(notApplicable && typeof evidence?.requires === "string"
|
|
92
|
+
? { requires: evidence.requires }
|
|
93
|
+
: {}),
|
|
78
94
|
});
|
|
79
95
|
}
|
|
80
96
|
return { complete, items, results };
|
|
@@ -105,11 +121,23 @@ export function summarizeVerdicts(results, options) {
|
|
|
105
121
|
const subjectId = subjectByCase.get(item.id) ?? null;
|
|
106
122
|
const own = subjectId ? results.results.filter((result) => result.subjectId === subjectId) : [];
|
|
107
123
|
const metrics = own.flatMap((result) => result.metrics.map((metric) => ({ ...metric, scorerVersionId: result.scorerVersionId })));
|
|
108
|
-
|
|
109
|
-
const
|
|
124
|
+
// An evaluator that does not apply to the case neither passes nor fails it.
|
|
125
|
+
const skippedAsInapplicable = (result) => result.state === "skipped" && result.notApplicable === true;
|
|
126
|
+
const isAdvisory = (result) => result.advisory === true &&
|
|
127
|
+
result.state !== "error" &&
|
|
128
|
+
result.metrics.every((metric) => metric.passed === undefined);
|
|
129
|
+
const inapplicable = own.filter(skippedAsInapplicable);
|
|
130
|
+
const applicable = own.filter((result) => !skippedAsInapplicable(result));
|
|
131
|
+
// An advisory result, such as a judge's, is shown but never decides the case, as in Hue.
|
|
132
|
+
const advisory = applicable.filter(isAdvisory);
|
|
133
|
+
const deciding = applicable.filter((result) => !isAdvisory(result));
|
|
134
|
+
const errors = deciding.filter((result) => result.state === "error");
|
|
135
|
+
const scored = deciding.filter((result) => result.state === "scored");
|
|
110
136
|
const failing = scored.filter((result) => !result.metrics.every(metricPassed));
|
|
111
137
|
const missing = [...pins].some((pin) => !own.some((result) => result.scorerVersionId === pin));
|
|
112
|
-
const
|
|
138
|
+
const noneApplies = !missing && own.length > 0 && !applicable.length;
|
|
139
|
+
const noneDecides = !missing && applicable.length > 0 && !deciding.length;
|
|
140
|
+
const state = errors.length || noneApplies || noneDecides
|
|
113
141
|
? "error"
|
|
114
142
|
: failing.length
|
|
115
143
|
? "failed"
|
|
@@ -118,23 +146,53 @@ export function summarizeVerdicts(results, options) {
|
|
|
118
146
|
: scored.length
|
|
119
147
|
? "passed"
|
|
120
148
|
: "skipped";
|
|
149
|
+
const requires = [
|
|
150
|
+
...new Set(inapplicable.flatMap((result) => (result.requires ? [result.requires] : []))),
|
|
151
|
+
];
|
|
121
152
|
return {
|
|
122
153
|
caseId: item.id,
|
|
123
154
|
externalKey: item.externalKey,
|
|
124
155
|
subjectId,
|
|
125
156
|
state,
|
|
126
157
|
passed: state === "passed",
|
|
158
|
+
notApplicable: inapplicable.map((result) => result.scorerVersionId),
|
|
159
|
+
advisory: advisory.map((result) => result.scorerVersionId),
|
|
127
160
|
metrics,
|
|
128
|
-
explanations:
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
161
|
+
explanations: [
|
|
162
|
+
...(noneApplies
|
|
163
|
+
? [
|
|
164
|
+
`No pinned evaluator applies to this case${requires.length ? ` (they need ${requires.join(" or ")})` : ""}; pin one that grades it`,
|
|
165
|
+
]
|
|
166
|
+
: []),
|
|
167
|
+
...(noneDecides
|
|
168
|
+
? [
|
|
169
|
+
"Only advisory evaluators ran for this case, and they never decide it; pin one that grades it",
|
|
170
|
+
...advisory
|
|
171
|
+
.filter((result) => result.state === "skipped")
|
|
172
|
+
.map((result) => `An advisory evaluator skipped the case: ${result.explanation ?? "no reason given"}`),
|
|
173
|
+
]
|
|
174
|
+
: []),
|
|
175
|
+
...deciding
|
|
176
|
+
.filter((result) => result.state !== "scored" || failing.includes(result))
|
|
177
|
+
.map((result) => result.explanation)
|
|
178
|
+
.filter((explanation) => !!explanation),
|
|
179
|
+
],
|
|
132
180
|
errors: errors.map((result) => result.error?.type ?? "ScorerError"),
|
|
133
181
|
};
|
|
134
182
|
});
|
|
135
|
-
const totals = {
|
|
136
|
-
|
|
183
|
+
const totals = {
|
|
184
|
+
cases: cases.length,
|
|
185
|
+
passed: 0,
|
|
186
|
+
failed: 0,
|
|
187
|
+
error: 0,
|
|
188
|
+
skipped: 0,
|
|
189
|
+
pending: 0,
|
|
190
|
+
notApplicable: 0,
|
|
191
|
+
};
|
|
192
|
+
for (const item of cases) {
|
|
137
193
|
totals[item.state]++;
|
|
194
|
+
totals.notApplicable += item.notApplicable?.length ?? 0;
|
|
195
|
+
}
|
|
138
196
|
return { cases, totals };
|
|
139
197
|
}
|
|
140
198
|
/** Diffs the current summary against a baseline by case key. */
|
|
@@ -169,10 +227,15 @@ export async function collectExperimentVerdicts(client, options) {
|
|
|
169
227
|
const experiment = await client.getExperiment(options.experimentId);
|
|
170
228
|
const scorerVersionIds = experiment.evaluation.scorerVersions.map((version) => version.id);
|
|
171
229
|
const { experimentId: _experimentId, ...wait } = options;
|
|
230
|
+
// Hue's own judges, the only evaluators whose results can be advisory.
|
|
231
|
+
const judgeScorerVersionIds = experiment.evaluation.scorerVersions
|
|
232
|
+
.filter((version) => version.definition?.kind === "world_judge")
|
|
233
|
+
.map((version) => version.id);
|
|
172
234
|
const results = await waitForResults(client, {
|
|
173
235
|
...wait,
|
|
174
236
|
runId: experiment.evaluation.id,
|
|
175
237
|
scorerVersionIds,
|
|
238
|
+
judgeScorerVersionIds,
|
|
176
239
|
});
|
|
177
240
|
const experimentItems = await allPages((after) => client.listExperimentItems(experiment.id, { after }));
|
|
178
241
|
const linked = new Set(experimentItems.map((item) => item.execution?.subjectId).filter((id) => !!id));
|
package/dist/evals.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
export { createEvaluationClient, EvaluationClient, HueApiError } from "./evals/client.js";
|
|
1
|
+
export { ArtifactSizeError, createEvaluationClient, EvaluationClient, HueApiError, } from "./evals/client.js";
|
|
2
2
|
export type { EvaluationClientOptions } from "./evals/client.js";
|
|
3
3
|
export { runExperiment, rescore, UncertainExecutionError, OutcomeSerializationError, TargetCancelledError, TargetOutcomeUncertainError, } from "./evals/runner.js";
|
|
4
|
-
export type { RunExperimentOptions, RunExperimentTargetContext, RescoreOptions, RunnerReport, } from "./evals/runner.js";
|
|
4
|
+
export type { RunExperimentOptions, RunExperimentTargetContext, RescoreOptions, RunnerReport, TelemetryIssueCount, TelemetryNotAccepted, } from "./evals/runner.js";
|
|
5
5
|
export { TargetResult, withFiles } from "./evals/types.js";
|
|
6
|
-
export { outputContentTypes, outputFileLimits, OutputFileError, safeFilename, targetFileRoles, } from "./evals/files.js";
|
|
6
|
+
export { CaseFileError, outputContentTypes, outputFileLimits, OutputFileError, safeFilename, targetFileRoles, } from "./evals/files.js";
|
|
7
|
+
export type { CaseFileErrorCode } from "./evals/files.js";
|
|
7
8
|
export { runSimulation } from "./evals/simulation.js";
|
|
8
9
|
export type { RepositorySimulationCase, RepositorySimulationScorer, RunSimulationOptions, SimulationDefinition, SimulationProgress, SimulationReport, SimulationScenario, SimulationTargetContext, } from "./evals/simulation.js";
|
|
9
10
|
export { actualAgentManifestV2, agentManifestDigestV2, attemptBaselineV2, attemptBindingRead, attemptConnectionBundleV2, attemptIdentityV2, dependencyManifestV2, dependencyProviderV2, expectedAgentManifestV2, executionManifestDigestV2, parityEvidenceV2, preflightFindingV2, preflightReportV2, prepareAttemptInputV2, projectMcpConnectionV2, secretFreeBindingV2, surfaceBindingV2, } from "./evals/attempt.js";
|
package/dist/evals.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export { createEvaluationClient, EvaluationClient, HueApiError } from "./evals/client.js";
|
|
1
|
+
export { ArtifactSizeError, createEvaluationClient, EvaluationClient, HueApiError, } from "./evals/client.js";
|
|
2
2
|
export { runExperiment, rescore, UncertainExecutionError, OutcomeSerializationError, TargetCancelledError, TargetOutcomeUncertainError, } from "./evals/runner.js";
|
|
3
3
|
export { TargetResult, withFiles } from "./evals/types.js";
|
|
4
|
-
export { outputContentTypes, outputFileLimits, OutputFileError, safeFilename, targetFileRoles, } from "./evals/files.js";
|
|
4
|
+
export { CaseFileError, outputContentTypes, outputFileLimits, OutputFileError, safeFilename, targetFileRoles, } from "./evals/files.js";
|
|
5
5
|
export { runSimulation } from "./evals/simulation.js";
|
|
6
6
|
export { actualAgentManifestV2, agentManifestDigestV2, attemptBaselineV2, attemptBindingRead, attemptConnectionBundleV2, attemptIdentityV2, dependencyManifestV2, dependencyProviderV2, expectedAgentManifestV2, executionManifestDigestV2, parityEvidenceV2, preflightFindingV2, preflightReportV2, prepareAttemptInputV2, projectMcpConnectionV2, secretFreeBindingV2, surfaceBindingV2, } from "./evals/attempt.js";
|
|
7
7
|
export { builtins, defineLocalScorer, scoreLocally } from "./evals/scorers.js";
|
package/dist/inline-files.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
/** Inline file content larger than this many UTF-8 bytes is exported as its digest instead. */
|
|
2
2
|
export declare const INLINE_FILE_LIMIT: number;
|
|
3
|
+
/** Message text one record inspects for inline files in all, in UTF-16 code units. */
|
|
4
|
+
export declare const INLINE_FILE_TEXT_PER_RECORD: number;
|
|
5
|
+
/** Whether an attribute is one of the recorded message attributes that can inline files. */
|
|
6
|
+
export declare function isMessageKey(key: string): boolean;
|
|
3
7
|
/**
|
|
4
8
|
* Replaces inline files longer than {@link INLINE_FILE_LIMIT} in a recorded message attribute
|
|
5
9
|
* with their SHA-256 and byte size, so a span that inlines a large file exports the file's
|
package/dist/inline-files.js
CHANGED
|
@@ -3,32 +3,64 @@ import { MAX_BODY_BYTES } from "./config.js";
|
|
|
3
3
|
/** Inline file content larger than this many UTF-8 bytes is exported as its digest instead. */
|
|
4
4
|
export const INLINE_FILE_LIMIT = 64 * 1024;
|
|
5
5
|
const MAX_INLINE_FILE_TEXT = 8 * MAX_BODY_BYTES;
|
|
6
|
+
/** Message text one record inspects for inline files in all, in UTF-16 code units. */
|
|
7
|
+
export const INLINE_FILE_TEXT_PER_RECORD = 2 * MAX_INLINE_FILE_TEXT;
|
|
6
8
|
/** Message attributes whose JSON can inline files: GenAI blob parts and AI SDK 6 file parts. */
|
|
7
9
|
const messageKeys = new Set([
|
|
8
10
|
"gen_ai.input.messages",
|
|
9
11
|
"gen_ai.output.messages",
|
|
10
12
|
"ai.prompt.messages",
|
|
11
13
|
]);
|
|
14
|
+
/** Whether an attribute is one of the recorded message attributes that can inline files. */
|
|
15
|
+
export function isMessageKey(key) {
|
|
16
|
+
return messageKeys.has(key);
|
|
17
|
+
}
|
|
12
18
|
/** Strict base64: alphabet characters only, padded to a multiple of four. */
|
|
13
19
|
const base64 = /^[A-Za-z0-9+/]*={0,2}$/;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
/** An RFC 2397 `data:` URL's header, up to the comma before its data. Its `;` parameters are read
|
|
21
|
+
* in code: a pattern repeated per parameter fails on a pathological count (Node's stack overflows,
|
|
22
|
+
* Bun stops matching). */
|
|
23
|
+
const dataUrl = /^data:([^,]*),/;
|
|
24
|
+
/** Whether a `data:` URL header's parameters, after its media type, include `base64`. */
|
|
25
|
+
function base64Header(header) {
|
|
26
|
+
const semicolon = header.indexOf(";");
|
|
27
|
+
// One `;`-separated parameter is exactly `base64`, without splitting them all.
|
|
28
|
+
return semicolon !== -1 && `${header.slice(semicolon)};`.includes(";base64;");
|
|
29
|
+
}
|
|
30
|
+
const hexPair = /^[0-9A-Fa-f]{2}$/;
|
|
31
|
+
/** RFC 2397 data without `;base64`: percent-escaped octets, other characters as UTF-8. Undefined
|
|
32
|
+
* when a `%` does not start an escape. */
|
|
33
|
+
function percentDecoded(payload) {
|
|
34
|
+
// Escapes only shrink the text, so its UTF-8 length bounds the bytes.
|
|
35
|
+
const bytes = Buffer.alloc(Buffer.byteLength(payload, "utf8"));
|
|
36
|
+
let length = 0;
|
|
37
|
+
let start = 0;
|
|
38
|
+
for (let index = payload.indexOf("%"); index !== -1; index = payload.indexOf("%", start)) {
|
|
39
|
+
const hex = payload.slice(index + 1, index + 3);
|
|
40
|
+
if (!hexPair.test(hex))
|
|
41
|
+
return undefined;
|
|
42
|
+
length += bytes.write(payload.slice(start, index), length, "utf8");
|
|
43
|
+
bytes[length++] = Number.parseInt(hex, 16);
|
|
44
|
+
start = index + 3;
|
|
45
|
+
}
|
|
46
|
+
length += bytes.write(payload.slice(start), length, "utf8");
|
|
47
|
+
return bytes.subarray(0, length);
|
|
19
48
|
}
|
|
20
49
|
/**
|
|
21
|
-
* The bytes an inline file part carries
|
|
22
|
-
*
|
|
50
|
+
* The bytes an inline file part carries, the file's own bytes whatever its media type: a `data:`
|
|
51
|
+
* URL decoded by its own encoding (base64 with `;base64`, percent-escapes otherwise), content in
|
|
52
|
+
* the base64 alphabet decoded, and anything else, such as a text file's own text, as UTF-8. A
|
|
53
|
+
* `data:` URL whose data does not decode is taken as UTF-8 too.
|
|
23
54
|
*/
|
|
24
|
-
function fileBytes(content
|
|
25
|
-
const
|
|
26
|
-
const payload = content.slice(
|
|
27
|
-
|
|
28
|
-
payload
|
|
29
|
-
base64.test(payload)
|
|
30
|
-
|
|
31
|
-
|
|
55
|
+
function fileBytes(content) {
|
|
56
|
+
const url = dataUrl.exec(content);
|
|
57
|
+
const payload = url ? content.slice(url[0].length) : content;
|
|
58
|
+
const decoded = url && !base64Header(url[1])
|
|
59
|
+
? percentDecoded(payload)
|
|
60
|
+
: payload.length % 4 === 0 && base64.test(payload)
|
|
61
|
+
? Buffer.from(payload, "base64")
|
|
62
|
+
: undefined;
|
|
63
|
+
return decoded ?? Buffer.from(content, "utf8");
|
|
32
64
|
}
|
|
33
65
|
/** The key holding a part's inline content: GenAI `blob` parts and AI SDK 6 `file` parts. */
|
|
34
66
|
function contentKey(part) {
|
|
@@ -44,9 +76,8 @@ function hashNode(value, state, depth) {
|
|
|
44
76
|
const part = value;
|
|
45
77
|
const key = contentKey(part);
|
|
46
78
|
const inline = key === undefined ? undefined : part[key];
|
|
47
|
-
const mimeType = part.mime_type ?? part.mediaType;
|
|
48
79
|
if (key !== undefined && typeof inline === "string") {
|
|
49
|
-
const bytes = fileBytes(inline
|
|
80
|
+
const bytes = fileBytes(inline);
|
|
50
81
|
if (bytes.byteLength > INLINE_FILE_LIMIT) {
|
|
51
82
|
const { [key]: _omitted, ...rest } = part;
|
|
52
83
|
state.changed = true;
|
package/dist/provider-tools.d.ts
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import type { HostedToolProvider } from "./types.js";
|
|
2
|
-
/** One provider-executed tool call. `arguments` and `
|
|
2
|
+
/** One provider-executed tool call. `arguments`, `result` and `errorText` are content. */
|
|
3
3
|
export interface HostedToolCall {
|
|
4
4
|
name: string;
|
|
5
5
|
callId?: string;
|
|
6
|
+
/** 0-based position of the call's item in the response: the OpenAI `output` index or the
|
|
7
|
+
* Anthropic `content` block index. */
|
|
8
|
+
position: number;
|
|
6
9
|
/** The provider's label (OpenAI `server_label`) or name (Anthropic `server_name`) for the MCP server. */
|
|
7
10
|
server?: string;
|
|
8
11
|
arguments?: unknown;
|
|
9
12
|
result?: unknown;
|
|
10
13
|
/** Low-cardinality failure marker for `error.type`; absent when the call succeeded. */
|
|
11
14
|
errorType?: string;
|
|
15
|
+
/** The provider's own error text for a failed OpenAI MCP call, unscrubbed and unbounded. */
|
|
16
|
+
errorText?: string;
|
|
12
17
|
}
|
|
13
18
|
/** One `mcp_list_tools` result: the tools a hosted MCP server offered. */
|
|
14
19
|
export interface HostedToolListing {
|
|
@@ -23,6 +28,12 @@ export interface HostedToolActivity {
|
|
|
23
28
|
/** Items that looked like hosted calls but could not be read. */
|
|
24
29
|
skipped: number;
|
|
25
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* A provider's error text as a failed call exports it under content capture: credentials
|
|
33
|
+
* scrubbed first, then at most 1,024 code points with `…` marking a cut, lone surrogates
|
|
34
|
+
* replaced and NUL removed. The Python SDK produces the same text.
|
|
35
|
+
*/
|
|
36
|
+
export declare function providerErrorDescription(value: string): string;
|
|
26
37
|
/**
|
|
27
38
|
* Reads the hosted tool calls out of a provider response: the `output` items of an OpenAI
|
|
28
39
|
* Responses API response, or the `content` blocks of an Anthropic Messages API response. An array
|