@hue-run/sdk 0.2.2 → 0.3.1
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 +52 -0
- package/ENVIRONMENTS.md +24 -6
- package/EVALUATIONS.md +69 -2
- package/README.md +20 -0
- package/dist/environment/client.d.ts +2 -2
- package/dist/environment/types.d.ts +33 -1
- package/dist/evals/client.d.ts +30 -1
- package/dist/evals/client.js +16 -0
- package/dist/evals/environment-target.d.ts +84 -0
- package/dist/evals/environment-target.js +201 -0
- package/dist/evals/local-worker.d.ts +88 -0
- package/dist/evals/local-worker.js +171 -0
- package/dist/evals/runner.d.ts +1 -1
- package/dist/evals/runner.js +22 -12
- package/dist/evals/scorer-publication.js +17 -1
- package/dist/evals/scorers.d.ts +10 -4
- package/dist/evals/scorers.js +11 -8
- package/dist/evals/simulation.d.ts +6 -6
- package/dist/evals/simulation.js +45 -222
- package/dist/evals/types.d.ts +39 -1
- package/dist/evals.d.ts +2 -0
- package/dist/evals.js +1 -0
- package/dist/setup/checkpoint.d.ts +14 -0
- package/dist/setup/checkpoint.js +186 -0
- package/dist/setup/cli.d.ts +2 -0
- package/dist/setup/cli.js +150 -0
- package/dist/setup/detect.d.ts +3 -0
- package/dist/setup/detect.js +146 -0
- package/dist/setup/machine.d.ts +109 -0
- package/dist/setup/machine.js +43 -0
- package/dist/setup/render.d.ts +16 -0
- package/dist/setup/render.js +111 -0
- package/dist/setup/runner.d.ts +101 -0
- package/dist/setup/runner.js +117 -0
- package/dist/setup/types.d.ts +145 -0
- package/dist/setup/types.js +2 -0
- package/dist/setup.d.ts +6 -0
- package/dist/setup.js +6 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +12 -1
- package/setup-events.schema.json +134 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { HueClient } from "../client.js";
|
|
2
|
+
import type { EnvironmentClient } from "../environment/client.js";
|
|
3
|
+
import type { EnvironmentTool } from "../environment/tools.js";
|
|
4
|
+
import type { ActualAgentManifestInputV2, AttemptConnectionBundleV2, RequestedAttemptProviderV2 } from "./attempt.js";
|
|
5
|
+
import type { EvaluationClient } from "./client.js";
|
|
6
|
+
import { type RunnerReport } from "./runner.js";
|
|
7
|
+
import type { ExperimentCase, JsonValue, LocalAgentRegistration, LocalScorer } from "./types.js";
|
|
8
|
+
/** Candidate-visible context for one queued local agent execution. */
|
|
9
|
+
export interface LocalAgentTargetContext {
|
|
10
|
+
/** Frozen candidate configuration, cloned before invocation. */
|
|
11
|
+
config: JsonValue;
|
|
12
|
+
/** Identity only. Expected outcomes, metadata and world definitions are evaluator-private. */
|
|
13
|
+
item: Pick<ExperimentCase, "id" | "externalKey">;
|
|
14
|
+
/** Identity of this target execution. */
|
|
15
|
+
executionId: string;
|
|
16
|
+
/** Stable world identity for adapter control operations such as coverage reporting. */
|
|
17
|
+
environmentRunId: string;
|
|
18
|
+
/** Trace identities without mutable span or grading data. */
|
|
19
|
+
trace: {
|
|
20
|
+
/** OpenTelemetry trace identifier. */
|
|
21
|
+
traceId: string;
|
|
22
|
+
/** Root execution span identifier. */
|
|
23
|
+
spanId: string;
|
|
24
|
+
};
|
|
25
|
+
/** Short-lived, execution-scoped hosted tools for model providers that execute MCP remotely. */
|
|
26
|
+
mcp: {
|
|
27
|
+
/** Execution-scoped MCP endpoint. */
|
|
28
|
+
url: string;
|
|
29
|
+
/** Short-lived bearer, never the project service key. */
|
|
30
|
+
token: string;
|
|
31
|
+
/** Capability expiry as an ISO timestamp. */
|
|
32
|
+
expiresAt: string;
|
|
33
|
+
};
|
|
34
|
+
/** Credential-bearing provider connections for this callback only. Hue never
|
|
35
|
+
* checkpoints, logs or adds this response to parity digests. */
|
|
36
|
+
connectionBundle?: AttemptConnectionBundleV2;
|
|
37
|
+
}
|
|
38
|
+
/** Fixed local callback, clients and durable queue-worker settings. */
|
|
39
|
+
export interface RunLocalAgentOptions {
|
|
40
|
+
/** Evaluation client for the worker project. */
|
|
41
|
+
client: EvaluationClient;
|
|
42
|
+
/** Environment client for the same origin and project. */
|
|
43
|
+
environmentClient: EnvironmentClient;
|
|
44
|
+
/** Application telemetry client; required trace exports are acknowledged. */
|
|
45
|
+
hue: HueClient;
|
|
46
|
+
/** Private durable directory for worker identity and execution checkpoints. */
|
|
47
|
+
checkpointDirectory: string;
|
|
48
|
+
/** Fixed agent key and revision registered for queued runs. */
|
|
49
|
+
agent: LocalAgentRegistration;
|
|
50
|
+
/** Local scorer bindings matching the published source digests. */
|
|
51
|
+
scorers?: LocalScorer[];
|
|
52
|
+
/** Cases in flight, between 1 and 16; defaults to 1. */
|
|
53
|
+
concurrency?: number;
|
|
54
|
+
/** Polling interval in milliseconds, 250–60000; defaults to 2000. */
|
|
55
|
+
pollIntervalMillis?: number;
|
|
56
|
+
/** Stops polling cooperatively; does not cancel an active callback. */
|
|
57
|
+
signal?: AbortSignal;
|
|
58
|
+
/** Useful for one-shot jobs and deterministic acceptance. Omit to keep polling. */
|
|
59
|
+
maxRuns?: number;
|
|
60
|
+
/** Opt into the experiment's immutable V2 provider profile. These three values are
|
|
61
|
+
* validated together before the worker polls; no endpoint or credential is supplied here. */
|
|
62
|
+
actualAgentManifest?: ActualAgentManifestInputV2 | ((context: {
|
|
63
|
+
/** Frozen experiment configuration. */
|
|
64
|
+
config: JsonValue;
|
|
65
|
+
/** Full frozen case for resolving actual nonsecret evidence before candidate projection. */
|
|
66
|
+
item: ExperimentCase;
|
|
67
|
+
/** Cooperative worker stop signal. */
|
|
68
|
+
signal?: AbortSignal;
|
|
69
|
+
}) => ActualAgentManifestInputV2 | Promise<ActualAgentManifestInputV2>);
|
|
70
|
+
/** Exact provider instances and ordered surfaces asserted for strict preflight. */
|
|
71
|
+
requestedProviders?: RequestedAttemptProviderV2[];
|
|
72
|
+
/** Requested MCP surface projected to the backwards-compatible `context.mcp`. */
|
|
73
|
+
mcpSurface?: {
|
|
74
|
+
/** Provider instance selected from `requestedProviders`. */
|
|
75
|
+
providerInstanceKey: string;
|
|
76
|
+
/** Selected MCP surface. */
|
|
77
|
+
surfaceKey: "google.gmail/mcp" | "slack/mcp";
|
|
78
|
+
};
|
|
79
|
+
/** Invokes the existing agent against isolated tools and candidate-safe context. */
|
|
80
|
+
target(inputs: JsonValue, tools: Record<string, EnvironmentTool>, context: LocalAgentTargetContext): JsonValue | undefined | Promise<JsonValue | undefined>;
|
|
81
|
+
/** Called after the experiment and queue completion are acknowledged. */
|
|
82
|
+
onCompleted?(report: RunnerReport): void | Promise<void>;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Starts an outbound-only worker for one fixed local agent entry point. Hue chooses
|
|
86
|
+
* only the registered key/revision; no command or source is received from the cloud.
|
|
87
|
+
*/
|
|
88
|
+
export declare function runLocalAgent(options: RunLocalAgentOptions): Promise<void>;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { join, resolve } from "node:path";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { CheckpointStore } from "./checkpoint.js";
|
|
4
|
+
import { pinRequestedAttemptV2, requestedAttemptV2, runEnvironmentTarget, } from "./environment-target.js";
|
|
5
|
+
import { runExperiment, OutcomeSerializationError, TargetOutcomeUncertainError, UncertainExecutionError, } from "./runner.js";
|
|
6
|
+
/** Allowlist the candidate surface instead of forwarding the generic evaluation context. */
|
|
7
|
+
function localAgentTargetContext(context) {
|
|
8
|
+
return {
|
|
9
|
+
config: structuredClone(context.config),
|
|
10
|
+
item: { id: context.item.id, externalKey: context.item.externalKey },
|
|
11
|
+
executionId: context.executionId,
|
|
12
|
+
environmentRunId: context.environmentRunId,
|
|
13
|
+
trace: { traceId: context.trace.traceId, spanId: context.trace.spanId },
|
|
14
|
+
mcp: {
|
|
15
|
+
url: context.mcp.url,
|
|
16
|
+
token: context.mcp.token,
|
|
17
|
+
expiresAt: context.mcp.expiresAt,
|
|
18
|
+
},
|
|
19
|
+
...(context.connectionBundle
|
|
20
|
+
? { connectionBundle: structuredClone(context.connectionBundle) }
|
|
21
|
+
: {}),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function validInterval(value) {
|
|
25
|
+
const interval = value ?? 2_000;
|
|
26
|
+
if (!Number.isInteger(interval) || interval < 250 || interval > 60_000)
|
|
27
|
+
throw new RangeError("pollIntervalMillis must be 250–60000");
|
|
28
|
+
return interval;
|
|
29
|
+
}
|
|
30
|
+
function stopReason(signal) {
|
|
31
|
+
return signal?.reason instanceof Error
|
|
32
|
+
? signal.reason
|
|
33
|
+
: new Error("Local worker stopped", { cause: signal?.reason });
|
|
34
|
+
}
|
|
35
|
+
function wait(milliseconds, signal) {
|
|
36
|
+
if (signal?.aborted)
|
|
37
|
+
return Promise.reject(stopReason(signal));
|
|
38
|
+
return new Promise((resolve, reject) => {
|
|
39
|
+
const timeout = setTimeout(done, milliseconds);
|
|
40
|
+
const aborted = () => {
|
|
41
|
+
clearTimeout(timeout);
|
|
42
|
+
reject(stopReason(signal));
|
|
43
|
+
};
|
|
44
|
+
function done() {
|
|
45
|
+
signal?.removeEventListener("abort", aborted);
|
|
46
|
+
resolve();
|
|
47
|
+
}
|
|
48
|
+
signal?.addEventListener("abort", aborted, { once: true });
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
function needsAttention(error, seen = new Set()) {
|
|
52
|
+
if (error instanceof TargetOutcomeUncertainError ||
|
|
53
|
+
error instanceof UncertainExecutionError ||
|
|
54
|
+
error instanceof OutcomeSerializationError)
|
|
55
|
+
return true;
|
|
56
|
+
if (!(error instanceof AggregateError) || seen.has(error))
|
|
57
|
+
return false;
|
|
58
|
+
seen.add(error);
|
|
59
|
+
return error.errors.some((nested) => needsAttention(nested, seen));
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Starts an outbound-only worker for one fixed local agent entry point. Hue chooses
|
|
63
|
+
* only the registered key/revision; no command or source is received from the cloud.
|
|
64
|
+
*/
|
|
65
|
+
export async function runLocalAgent(options) {
|
|
66
|
+
const requestedConfiguration = requestedAttemptV2(options);
|
|
67
|
+
const interval = validInterval(options.pollIntervalMillis);
|
|
68
|
+
const maxRuns = options.maxRuns ?? Number.POSITIVE_INFINITY;
|
|
69
|
+
if (!(maxRuns === Number.POSITIVE_INFINITY || (Number.isInteger(maxRuns) && maxRuns > 0)))
|
|
70
|
+
throw new RangeError("maxRuns must be a positive integer");
|
|
71
|
+
if (options.environmentClient.baseUrl !== options.client.baseUrl)
|
|
72
|
+
throw new Error("Environments and evaluations must use the same Hue origin");
|
|
73
|
+
const directory = resolve(options.checkpointDirectory);
|
|
74
|
+
const project = await options.client.checkConnection();
|
|
75
|
+
const store = await CheckpointStore.acquire(directory, {
|
|
76
|
+
kind: "local-agent-worker",
|
|
77
|
+
projectId: project.id,
|
|
78
|
+
baseUrl: options.client.baseUrl,
|
|
79
|
+
agent: { key: options.agent.key, revision: options.agent.revision },
|
|
80
|
+
});
|
|
81
|
+
try {
|
|
82
|
+
let workerId = await store.read("worker-id");
|
|
83
|
+
if (workerId !== undefined &&
|
|
84
|
+
!/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(workerId))
|
|
85
|
+
throw new Error("Invalid local worker identity");
|
|
86
|
+
if (workerId === undefined) {
|
|
87
|
+
workerId = randomUUID();
|
|
88
|
+
await store.write("worker-id", workerId);
|
|
89
|
+
}
|
|
90
|
+
let completed = 0;
|
|
91
|
+
while (completed < maxRuns && !options.signal?.aborted) {
|
|
92
|
+
const agent = await options.client.registerLocalAgent({
|
|
93
|
+
...options.agent,
|
|
94
|
+
capabilities: options.agent.capabilities ?? ["environment:v1"],
|
|
95
|
+
scorerDigests: options.agent.scorerDigests ??
|
|
96
|
+
options.scorers?.map((item) => item.definition.sourceDigest) ??
|
|
97
|
+
[],
|
|
98
|
+
});
|
|
99
|
+
const claim = await options.client.claimLocalAgentRun({ agentId: agent.id, workerId });
|
|
100
|
+
if (!claim) {
|
|
101
|
+
await wait(interval, options.signal);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const heartbeat = setInterval(() => {
|
|
105
|
+
void options.client
|
|
106
|
+
.heartbeatLocalAgentRun({ runId: claim.runId, workerId })
|
|
107
|
+
.catch(() => undefined);
|
|
108
|
+
}, Math.min(15_000, Math.max(1_000, interval)));
|
|
109
|
+
let experimentFinished = false;
|
|
110
|
+
try {
|
|
111
|
+
const requested = requestedConfiguration
|
|
112
|
+
? pinRequestedAttemptV2(requestedConfiguration, (await options.client.getExperiment(claim.experimentId)).config)
|
|
113
|
+
: undefined;
|
|
114
|
+
const report = await runExperiment({
|
|
115
|
+
client: options.client,
|
|
116
|
+
hue: options.hue,
|
|
117
|
+
experimentId: claim.experimentId,
|
|
118
|
+
checkpointDirectory: join(directory, `experiment-${claim.experimentId}`),
|
|
119
|
+
persistResultContent: true,
|
|
120
|
+
environmentEvidence: "required",
|
|
121
|
+
traceEvidence: { mode: "required" },
|
|
122
|
+
scorers: options.scorers,
|
|
123
|
+
concurrency: options.concurrency,
|
|
124
|
+
target: (inputs, context) => runEnvironmentTarget({
|
|
125
|
+
client: options.client,
|
|
126
|
+
environmentClient: options.environmentClient,
|
|
127
|
+
hue: options.hue,
|
|
128
|
+
inputs,
|
|
129
|
+
context,
|
|
130
|
+
requested,
|
|
131
|
+
signal: options.signal,
|
|
132
|
+
target: (targetInputs, targetContext) => options.target(structuredClone(targetInputs), targetContext.tools, localAgentTargetContext(targetContext)),
|
|
133
|
+
}),
|
|
134
|
+
});
|
|
135
|
+
// From this point onward the experiment outcome is authoritative. If reporting the
|
|
136
|
+
// queue completion fails, leave the claim intact for checkpointed recovery instead of
|
|
137
|
+
// rewriting a successful experiment as an agent failure.
|
|
138
|
+
experimentFinished = true;
|
|
139
|
+
await options.client.completeLocalAgentRun({
|
|
140
|
+
runId: claim.runId,
|
|
141
|
+
workerId,
|
|
142
|
+
state: "completed",
|
|
143
|
+
});
|
|
144
|
+
await options.onCompleted?.(report);
|
|
145
|
+
completed++;
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
// A durable outcome can still need completion/result uploads. Leave operational
|
|
149
|
+
// failures claimed so the same worker can resume them through its checkpoints.
|
|
150
|
+
// Attention is terminal in the queue and is reserved for explicit unsafe-to-resume
|
|
151
|
+
// outcomes that require operator intervention.
|
|
152
|
+
if (!experimentFinished && needsAttention(error))
|
|
153
|
+
await options.client
|
|
154
|
+
.completeLocalAgentRun({
|
|
155
|
+
runId: claim.runId,
|
|
156
|
+
workerId,
|
|
157
|
+
state: "attention",
|
|
158
|
+
failureType: error instanceof Error ? error.name.slice(0, 200) : "WorkerError",
|
|
159
|
+
})
|
|
160
|
+
.catch(() => undefined);
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
163
|
+
finally {
|
|
164
|
+
clearInterval(heartbeat);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
finally {
|
|
169
|
+
await store.release();
|
|
170
|
+
}
|
|
171
|
+
}
|
package/dist/evals/runner.d.ts
CHANGED
|
@@ -90,7 +90,7 @@ export interface RunnerReport {
|
|
|
90
90
|
subjectIds: string[];
|
|
91
91
|
/** Result IDs uploaded by this call. */
|
|
92
92
|
resultIds: string[];
|
|
93
|
-
/**
|
|
93
|
+
/** Pins without a local implementation, left pending for their authorized executor. */
|
|
94
94
|
deferredScorerVersionIds: string[];
|
|
95
95
|
}
|
|
96
96
|
/**
|
package/dist/evals/runner.js
CHANGED
|
@@ -4,7 +4,7 @@ import { HueExportError } from "../transport.js";
|
|
|
4
4
|
import { loadEnvironmentEvidence } from "./environment-evidence.js";
|
|
5
5
|
import { CheckpointStore } from "./checkpoint.js";
|
|
6
6
|
import { json, uuid } from "./json.js";
|
|
7
|
-
import { persistedScore, scoreLocally, validateScorerBindings } from "./scorers.js";
|
|
7
|
+
import { persistedScore, scoreLocally, validateScorerBindings, isLocallyExecutable, } from "./scorers.js";
|
|
8
8
|
/**
|
|
9
9
|
* Thrown when a case has a started attempt without a saved outcome. The runner never reruns the
|
|
10
10
|
* target; inspect the execution and authorize a new attempt explicitly through `startExecution`.
|
|
@@ -105,7 +105,8 @@ async function pool(items, concurrency, execute) {
|
|
|
105
105
|
async function scoresFor(versions, context, options, executionId) {
|
|
106
106
|
const scores = [];
|
|
107
107
|
let environmentUnavailable = false;
|
|
108
|
-
if (options.environmentEvidence === "required"
|
|
108
|
+
if (options.environmentEvidence === "required" &&
|
|
109
|
+
versions.some((version) => isLocallyExecutable(version.definition))) {
|
|
109
110
|
try {
|
|
110
111
|
context = {
|
|
111
112
|
...context,
|
|
@@ -117,8 +118,8 @@ async function scoresFor(versions, context, options, executionId) {
|
|
|
117
118
|
}
|
|
118
119
|
}
|
|
119
120
|
for (const version of versions) {
|
|
120
|
-
//
|
|
121
|
-
if (version.definition
|
|
121
|
+
// Every pin without a local implementation belongs to another executor.
|
|
122
|
+
if (!isLocallyExecutable(version.definition))
|
|
122
123
|
continue;
|
|
123
124
|
const score = persistedScore(environmentUnavailable && version.definition.kind === "local_code"
|
|
124
125
|
? { state: "error", error: { type: "EnvironmentEvidenceUnavailable" } }
|
|
@@ -136,8 +137,16 @@ async function scoresFor(versions, context, options, executionId) {
|
|
|
136
137
|
}
|
|
137
138
|
return scores;
|
|
138
139
|
}
|
|
139
|
-
async function uploadScores(options, runId, scores, save) {
|
|
140
|
-
|
|
140
|
+
async function uploadScores(options, runId, scores, save, versions) {
|
|
141
|
+
// A previous SDK may have checkpointed a placeholder for an unknown kind.
|
|
142
|
+
// Keep its evidence intact, but never upload or report it as a local result.
|
|
143
|
+
const local = scores.filter((score) => {
|
|
144
|
+
const pin = versions.find((version) => version.id === score.payload.scorerVersionId);
|
|
145
|
+
if (!pin)
|
|
146
|
+
throw new Error("Saved result references an unpinned scorer version");
|
|
147
|
+
return isLocallyExecutable(pin.definition);
|
|
148
|
+
});
|
|
149
|
+
for (const score of local) {
|
|
141
150
|
if (score.receipt)
|
|
142
151
|
continue;
|
|
143
152
|
if (!score.payload.evaluationItemId)
|
|
@@ -149,6 +158,7 @@ async function uploadScores(options, runId, scores, save) {
|
|
|
149
158
|
score.receipt = result.ids;
|
|
150
159
|
await save();
|
|
151
160
|
}
|
|
161
|
+
return local.flatMap((score) => score.receipt ?? []);
|
|
152
162
|
}
|
|
153
163
|
/**
|
|
154
164
|
* Runs every case of a frozen experiment through `target` on this machine, completes each
|
|
@@ -204,7 +214,7 @@ export async function runExperiment(options) {
|
|
|
204
214
|
subjectIds: [],
|
|
205
215
|
resultIds: [],
|
|
206
216
|
deferredScorerVersionIds: versions
|
|
207
|
-
.filter((version) =>
|
|
217
|
+
.filter((version) => !isLocallyExecutable(version.definition))
|
|
208
218
|
.map((version) => version.id),
|
|
209
219
|
};
|
|
210
220
|
try {
|
|
@@ -352,9 +362,9 @@ export async function runExperiment(options) {
|
|
|
352
362
|
score.payload.evaluationItemId = prepared.completion.evaluationItemId;
|
|
353
363
|
await save();
|
|
354
364
|
}
|
|
355
|
-
await uploadScores(options, experiment.evaluation.id, prepared.scores, save);
|
|
365
|
+
const results = await uploadScores(options, experiment.evaluation.id, prepared.scores, save, versions);
|
|
356
366
|
report.subjectIds.push(prepared.completion.subjectId);
|
|
357
|
-
report.resultIds.push(...
|
|
367
|
+
report.resultIds.push(...results);
|
|
358
368
|
});
|
|
359
369
|
let finish = await store.read("finish");
|
|
360
370
|
if (!finish) {
|
|
@@ -395,7 +405,7 @@ export async function rescore(options) {
|
|
|
395
405
|
subjectIds: [],
|
|
396
406
|
resultIds: [],
|
|
397
407
|
deferredScorerVersionIds: run.scorerVersions
|
|
398
|
-
.filter((version) =>
|
|
408
|
+
.filter((version) => !isLocallyExecutable(version.definition))
|
|
399
409
|
.map((version) => version.id),
|
|
400
410
|
};
|
|
401
411
|
try {
|
|
@@ -419,9 +429,9 @@ export async function rescore(options) {
|
|
|
419
429
|
await store.write(file, saved);
|
|
420
430
|
}
|
|
421
431
|
const current = saved;
|
|
422
|
-
await uploadScores(options, run.id, current.scores, () => store.write(file, current));
|
|
432
|
+
const results = await uploadScores(options, run.id, current.scores, () => store.write(file, current), run.scorerVersions);
|
|
423
433
|
report.subjectIds.push(item.subjectId);
|
|
424
|
-
report.resultIds.push(...
|
|
434
|
+
report.resultIds.push(...results);
|
|
425
435
|
});
|
|
426
436
|
return report;
|
|
427
437
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { json } from "./json.js";
|
|
2
|
+
import { json, digest } from "./json.js";
|
|
3
3
|
const metricName = z
|
|
4
4
|
.string()
|
|
5
5
|
.min(1)
|
|
@@ -21,6 +21,15 @@ const metric = z.discriminatedUnion("type", [
|
|
|
21
21
|
}),
|
|
22
22
|
]);
|
|
23
23
|
const metrics = z.array(metric).min(1);
|
|
24
|
+
const worldOutcomeMetrics = [
|
|
25
|
+
"completed_run",
|
|
26
|
+
"saved_draft",
|
|
27
|
+
"correct_destination",
|
|
28
|
+
"content",
|
|
29
|
+
"unrelated_preserved",
|
|
30
|
+
"process_constraints",
|
|
31
|
+
"task_success",
|
|
32
|
+
].map((name) => ({ name, type: "boolean" }));
|
|
24
33
|
const jsonValue = z.custom((value) => {
|
|
25
34
|
try {
|
|
26
35
|
json(value);
|
|
@@ -73,6 +82,13 @@ const sdkScorerPublication = z.union([
|
|
|
73
82
|
sourceDigest: z.string(),
|
|
74
83
|
metrics,
|
|
75
84
|
}),
|
|
85
|
+
z.strictObject({
|
|
86
|
+
kind: z.literal("world_outcome"),
|
|
87
|
+
entry: z.literal("hue.conversion_outcome.v1"),
|
|
88
|
+
metrics: metrics
|
|
89
|
+
.default(worldOutcomeMetrics)
|
|
90
|
+
.refine((value) => digest(value) === digest(worldOutcomeMetrics), "World outcome metrics are fixed by the pinned entry"),
|
|
91
|
+
}),
|
|
76
92
|
z.strictObject({ kind: z.literal("manual"), metrics }),
|
|
77
93
|
z.strictObject({ kind: z.literal("llm_judge"), config: judgeConfig, metrics }),
|
|
78
94
|
]);
|
package/dist/evals/scorers.d.ts
CHANGED
|
@@ -17,12 +17,18 @@ export declare function defineLocalScorer(options: {
|
|
|
17
17
|
}): LocalScorer;
|
|
18
18
|
/** Check local callbacks before target invocation; never execute downloaded source code. */
|
|
19
19
|
export declare function validateScorerBindings(versions: ScorerVersion[], scorers?: LocalScorer[]): void;
|
|
20
|
+
/** Only implementations this SDK owns may produce local results. Unknown pins are deferred. */
|
|
21
|
+
export declare function isLocallyExecutable(definition: {
|
|
22
|
+
kind: string;
|
|
23
|
+
entry?: string;
|
|
24
|
+
}): definition is Extract<ScorerDefinition, {
|
|
25
|
+
kind: "builtin" | "local_code";
|
|
26
|
+
}>;
|
|
20
27
|
/**
|
|
21
|
-
* Scores one subject
|
|
22
|
-
*
|
|
23
|
-
* returned as sanitized error scores.
|
|
28
|
+
* Scores one subject locally using a known built-in or a bound `local_code` callback.
|
|
29
|
+
* Scorer failures are returned as sanitized error scores.
|
|
24
30
|
*
|
|
25
|
-
* @throws TypeError for
|
|
31
|
+
* @throws TypeError for pins without a local implementation; their authorized executor owns scoring.
|
|
26
32
|
*/
|
|
27
33
|
export declare function scoreLocally(version: ScorerVersion, context: ScoreContext, options?: {
|
|
28
34
|
scorers?: LocalScorer[];
|
package/dist/evals/scorers.js
CHANGED
|
@@ -99,17 +99,22 @@ export function validateScorerBindings(versions, scorers = []) {
|
|
|
99
99
|
throw new Error("A pinned local scorer has no matching language/source/entrypoint/metric binding");
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
|
+
/** Only implementations this SDK owns may produce local results. Unknown pins are deferred. */
|
|
103
|
+
export function isLocallyExecutable(definition) {
|
|
104
|
+
return (definition.kind === "local_code" ||
|
|
105
|
+
(definition.kind === "builtin" &&
|
|
106
|
+
["hue.exact_match.v1", "hue.includes.v1", "hue.json_schema.v1"].includes(definition.entry ?? "")));
|
|
107
|
+
}
|
|
102
108
|
/**
|
|
103
|
-
* Scores one subject
|
|
104
|
-
*
|
|
105
|
-
* returned as sanitized error scores.
|
|
109
|
+
* Scores one subject locally using a known built-in or a bound `local_code` callback.
|
|
110
|
+
* Scorer failures are returned as sanitized error scores.
|
|
106
111
|
*
|
|
107
|
-
* @throws TypeError for
|
|
112
|
+
* @throws TypeError for pins without a local implementation; their authorized executor owns scoring.
|
|
108
113
|
*/
|
|
109
114
|
export async function scoreLocally(version, context, options = {}) {
|
|
110
115
|
const definition = version.definition;
|
|
111
|
-
if (definition
|
|
112
|
-
throw new TypeError("
|
|
116
|
+
if (!isLocallyExecutable(definition))
|
|
117
|
+
throw new TypeError("This scorer must be deferred to its authorized executor; do not submit a local result");
|
|
113
118
|
const timeout = options.schemaTimeoutMillis ?? 2000;
|
|
114
119
|
if (!Number.isInteger(timeout) || timeout < 100 || timeout > 60_000)
|
|
115
120
|
throw new RangeError("schemaTimeoutMillis must be 100–60000");
|
|
@@ -133,8 +138,6 @@ export async function scoreLocally(version, context, options = {}) {
|
|
|
133
138
|
if (environment !== undefined)
|
|
134
139
|
validateEnvironmentEvidence(environment);
|
|
135
140
|
const owned = structuredClone(context);
|
|
136
|
-
if (definition.kind === "manual")
|
|
137
|
-
return skip("Manual scoring requires a human session");
|
|
138
141
|
if (definition.kind === "local_code") {
|
|
139
142
|
const binding = options.scorers?.find((local) => digest(local.definition) === digest(definition));
|
|
140
143
|
if (!binding)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { HueClient } from "../client.js";
|
|
2
2
|
import { type EnvironmentClient } from "../environment/client.js";
|
|
3
|
-
import {
|
|
4
|
-
import type {
|
|
3
|
+
import type { EnvironmentTool } from "../environment/tools.js";
|
|
4
|
+
import type { EnvironmentIdentity, PublishableEnvironmentDefinition } from "../environment/types.js";
|
|
5
5
|
import { type EvaluationClient } from "./client.js";
|
|
6
|
-
import {
|
|
6
|
+
import type { ActualAgentManifestInputV2, AttemptConnectionBundleV2, RequestedAttemptProviderV2 } from "./attempt.js";
|
|
7
7
|
import { type RunnerReport } from "./runner.js";
|
|
8
8
|
import type { ExperimentCase, Identity, JsonValue, LocalScorer, ScorerDefinition, SimulationMcpCapability } from "./types.js";
|
|
9
9
|
/** Repository-authored scorer identity and its public definition or local binding. */
|
|
@@ -40,7 +40,7 @@ export type SimulationScenario = {
|
|
|
40
40
|
/** Environment identity and authored world definition. */
|
|
41
41
|
environment: EnvironmentIdentity & {
|
|
42
42
|
/** Definition normalized and published as an immutable version. */
|
|
43
|
-
definition:
|
|
43
|
+
definition: PublishableEnvironmentDefinition;
|
|
44
44
|
};
|
|
45
45
|
/** Cases published into one frozen dataset version. */
|
|
46
46
|
cases: RepositorySimulationCase[];
|
|
@@ -92,8 +92,8 @@ export type SimulationProgress = {
|
|
|
92
92
|
export interface SimulationTargetContext {
|
|
93
93
|
/** Frozen experiment configuration. */
|
|
94
94
|
config: JsonValue;
|
|
95
|
-
/**
|
|
96
|
-
item: ExperimentCase
|
|
95
|
+
/** Candidate-visible identity. References, metadata and source pins stay with grading. */
|
|
96
|
+
item: Pick<ExperimentCase, "id" | "externalKey">;
|
|
97
97
|
/** Target execution identity. */
|
|
98
98
|
executionId: string;
|
|
99
99
|
/** Stable world identity for adapter control operations such as coverage reporting. */
|