@velum-labs/routekit-eval-service 1.0.16 → 1.0.18
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/dist/effect-api.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export type { CompileDimensionEvidenceMatrixInput, CompiledDimensionEvidenceMatrix, DimensionComparisonEvidenceInput } from "./dimension-evidence.js";
|
|
2
2
|
export { compileDimensionEvidenceMatrix, DimensionEvidenceCompilationError, wilsonLowerBound95 } from "./dimension-evidence.js";
|
|
3
3
|
export { EvalServiceComparisonError, EvalServiceConfigurationError, EvalServiceEstimateError, EvalServicePolicyError, EvalServicePublicationError, EvalServiceSpendLimitError, EvalServiceValidationError } from "./errors.js";
|
|
4
|
-
export type { RouteKitEvalServiceOptions } from "./production-runner.js";
|
|
4
|
+
export type { RouteKitEvalGatewayCallEvent, RouteKitEvalServiceOptions } from "./production-runner.js";
|
|
5
5
|
export { EvalServiceCredentialError, makeRouteKitEvalServiceLayer } from "./production-runner.js";
|
|
6
6
|
export type { DimensionMatrixQualificationInput, DimensionMatrixQualificationResult, DimensionMatrixSuite, EvalComparisonEstimate, EvalComparisonMode, EvalRunConfiguration, EvalServiceConfiguration, EvalServiceError, EvalServiceShape, EvalSuiteInspection } from "./service.js";
|
|
7
7
|
export { EvalService } from "./service.js";
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export type { CompileDimensionEvidenceMatrixInput, CompiledDimensionEvidenceMatrix, DimensionComparisonEvidenceInput } from "./dimension-evidence.js";
|
|
2
2
|
export { compileDimensionEvidenceMatrix, DimensionEvidenceCompilationError, wilsonLowerBound95 } from "./dimension-evidence.js";
|
|
3
3
|
export { EvalServiceComparisonError, EvalServiceConfigurationError, EvalServiceEstimateError, EvalServicePolicyError, EvalServicePublicationError, EvalServiceSpendLimitError, EvalServiceValidationError } from "./errors.js";
|
|
4
|
-
export type { RouteKitEvalServiceOptions } from "./production-runner.js";
|
|
4
|
+
export type { RouteKitEvalGatewayCallEvent, RouteKitEvalServiceOptions } from "./production-runner.js";
|
|
5
5
|
export { EvalServiceCredentialError, makeRouteKitEvalServiceLayer } from "./production-runner.js";
|
|
6
6
|
export type { DimensionMatrixQualificationInput, DimensionMatrixQualificationResult, DimensionMatrixSuite, EvalComparisonEstimate, EvalComparisonMode, EvalRunConfiguration, EvalServiceConfiguration, EvalServiceError, EvalServiceShape, EvalSuiteInspection } from "./service.js";
|
|
7
7
|
export { EvalService } from "./service.js";
|
package/dist/layer-options.d.ts
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import type { NodeTestExecutionOptions } from "@velum-labs/routekit-eval-engine";
|
|
2
|
+
import type { Effect } from "effect";
|
|
3
|
+
export type RouteKitEvalGatewayCallEvent = {
|
|
4
|
+
readonly observationId: string;
|
|
5
|
+
readonly phase: "issued";
|
|
6
|
+
readonly role: "candidate" | "judge";
|
|
7
|
+
} | {
|
|
8
|
+
readonly callId?: string;
|
|
9
|
+
readonly observationId: string;
|
|
10
|
+
readonly phase: "completed";
|
|
11
|
+
readonly role: "candidate" | "judge";
|
|
12
|
+
};
|
|
2
13
|
export type RouteKitEvalServiceOptions = Omit<NodeTestExecutionOptions, "bridgeOrigin"> & {
|
|
3
14
|
/**
|
|
4
15
|
* Injected RouteKit data-plane credential.
|
|
@@ -15,6 +26,12 @@ export type RouteKitEvalServiceOptions = Omit<NodeTestExecutionOptions, "bridgeO
|
|
|
15
26
|
* stale or missing timeout.
|
|
16
27
|
*/
|
|
17
28
|
readonly timeoutMs?: number;
|
|
29
|
+
/**
|
|
30
|
+
* Observe the exact parent-owned HTTP client used by the child gateway
|
|
31
|
+
* bridge. The callback runs when an attributed request is issued and again
|
|
32
|
+
* when a model call id is available from its response.
|
|
33
|
+
*/
|
|
34
|
+
readonly observeGatewayCall?: (event: RouteKitEvalGatewayCallEvent) => Effect.Effect<void>;
|
|
18
35
|
/**
|
|
19
36
|
* Run the node:test child from a temporary copy of the reviewed suite.
|
|
20
37
|
*
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Layer } from "effect";
|
|
2
2
|
import { HttpClient } from "effect/unstable/http";
|
|
3
|
-
import type { RouteKitEvalServiceOptions } from "./layer-options.js";
|
|
3
|
+
import type { RouteKitEvalGatewayCallEvent, RouteKitEvalServiceOptions } from "./layer-options.js";
|
|
4
4
|
import { EvalService, type EvalServiceConfiguration } from "./service.js";
|
|
5
|
-
export type { RouteKitEvalServiceOptions };
|
|
5
|
+
export type { RouteKitEvalGatewayCallEvent, RouteKitEvalServiceOptions };
|
|
6
6
|
declare const EvalServiceCredentialError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
7
7
|
readonly _tag: "EvalServiceCredentialError";
|
|
8
8
|
} & Readonly<A>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { layer as NodeServicesLayer } from "@effect/platform-node/NodeServices";
|
|
2
|
+
import { EVAL_ATTRIBUTION_HEADER } from "@velum-labs/routekit-eval-contracts";
|
|
2
3
|
import { EvalEngineExecutionError, EvalExecutionPort, makeEvalEngineLayer, makeRouteKitEvalExecutionPortService } from "@velum-labs/routekit-eval-engine";
|
|
3
4
|
import { Data, Effect, FileSystem, Layer, Path, Stream } from "effect";
|
|
4
5
|
import { HttpClient } from "effect/unstable/http";
|
|
@@ -8,11 +9,46 @@ export class EvalServiceCredentialError extends Data.TaggedError("EvalServiceCre
|
|
|
8
9
|
return this.detail;
|
|
9
10
|
}
|
|
10
11
|
}
|
|
12
|
+
const MODEL_CALL_ID_HEADER = "x-routekit-model-call-id";
|
|
13
|
+
const gatewayCallRole = (raw) => {
|
|
14
|
+
if (raw === undefined)
|
|
15
|
+
return undefined;
|
|
16
|
+
try {
|
|
17
|
+
const decoded = JSON.parse(raw);
|
|
18
|
+
return decoded.role === "candidate" || decoded.role === "judge" ? decoded.role : undefined;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
const observeGatewayCalls = (client, observe) => {
|
|
25
|
+
let nextObservationId = 0;
|
|
26
|
+
return HttpClient.transform(client, (responseEffect, request) => {
|
|
27
|
+
const role = gatewayCallRole(request.headers[EVAL_ATTRIBUTION_HEADER]);
|
|
28
|
+
if (role === undefined)
|
|
29
|
+
return responseEffect;
|
|
30
|
+
return Effect.gen(function* () {
|
|
31
|
+
nextObservationId += 1;
|
|
32
|
+
const observationId = `gateway-call-${String(nextObservationId)}`;
|
|
33
|
+
yield* observe({ observationId, phase: "issued", role });
|
|
34
|
+
const response = yield* responseEffect;
|
|
35
|
+
const rawCallId = response.headers[MODEL_CALL_ID_HEADER]?.trim();
|
|
36
|
+
yield* observe({
|
|
37
|
+
observationId,
|
|
38
|
+
phase: "completed",
|
|
39
|
+
role,
|
|
40
|
+
...(rawCallId === undefined || rawCallId.length === 0 ? {} : { callId: rawCallId })
|
|
41
|
+
});
|
|
42
|
+
return response;
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
};
|
|
11
46
|
const makeProductionExecutionPort = (options) => Effect.gen(function* () {
|
|
12
47
|
const fs = yield* FileSystem.FileSystem;
|
|
13
48
|
const bearerCredential = options.bearerCredential?.trim();
|
|
14
49
|
if (bearerCredential === undefined || bearerCredential.length === 0) {
|
|
15
50
|
return EvalExecutionPort.of({
|
|
51
|
+
nodeTestExecPath: options.execPath ?? globalThis.process.execPath,
|
|
16
52
|
execute: () => Stream.fail(new EvalEngineExecutionError({
|
|
17
53
|
cause: new EvalServiceCredentialError({
|
|
18
54
|
detail: "RouteKit Eval comparison execution requires an injected bearer credential."
|
|
@@ -21,7 +57,10 @@ const makeProductionExecutionPort = (options) => Effect.gen(function* () {
|
|
|
21
57
|
}))
|
|
22
58
|
});
|
|
23
59
|
}
|
|
24
|
-
const
|
|
60
|
+
const baseHttpClient = yield* HttpClient.HttpClient;
|
|
61
|
+
const httpClient = options.observeGatewayCall === undefined
|
|
62
|
+
? baseHttpClient
|
|
63
|
+
: observeGatewayCalls(baseHttpClient, options.observeGatewayCall);
|
|
25
64
|
const paths = yield* Path.Path;
|
|
26
65
|
const execution = makeRouteKitEvalExecutionPortService({
|
|
27
66
|
bearerCredential,
|
|
@@ -31,6 +70,7 @@ const makeProductionExecutionPort = (options) => Effect.gen(function* () {
|
|
|
31
70
|
...(options.execPath === undefined ? {} : { execPath: options.execPath })
|
|
32
71
|
}, httpClient);
|
|
33
72
|
return EvalExecutionPort.of({
|
|
73
|
+
nodeTestExecPath: execution.nodeTestExecPath,
|
|
34
74
|
execute: (input) => {
|
|
35
75
|
const deadlineInput = options.timeoutMs === undefined
|
|
36
76
|
? input
|
|
@@ -201,6 +201,7 @@ test("production runner executes candidate and judge traffic through the live en
|
|
|
201
201
|
].join("\n"));
|
|
202
202
|
await writeManifest(root, ["openai/cheap"], "openai/judge", ["support-case"]);
|
|
203
203
|
const calls = [];
|
|
204
|
+
const observed = [];
|
|
204
205
|
const gateway = createServer((incoming, outgoing) => {
|
|
205
206
|
void (async () => {
|
|
206
207
|
const body = JSON.parse(await readBody(incoming));
|
|
@@ -212,7 +213,10 @@ test("production runner executes candidate and judge traffic through the live en
|
|
|
212
213
|
const content = body.model === "openai/judge"
|
|
213
214
|
? JSON.stringify({ pass: true, reason: "helpful", score: 0.9 })
|
|
214
215
|
: "Helpful answer";
|
|
215
|
-
outgoing.writeHead(200, {
|
|
216
|
+
outgoing.writeHead(200, {
|
|
217
|
+
"content-type": "application/json",
|
|
218
|
+
"x-routekit-model-call-id": `model-call-${String(calls.length)}`
|
|
219
|
+
});
|
|
216
220
|
outgoing.end(JSON.stringify({
|
|
217
221
|
model: body.model,
|
|
218
222
|
choices: [{ message: { role: "assistant", content } }]
|
|
@@ -223,13 +227,38 @@ test("production runner executes candidate and judge traffic through the live en
|
|
|
223
227
|
const address = gateway.address();
|
|
224
228
|
assert.ok(address !== null && typeof address !== "string");
|
|
225
229
|
try {
|
|
226
|
-
const result = await Effect.runPromise(withEvalService({
|
|
230
|
+
const result = await Effect.runPromise(withEvalService({
|
|
231
|
+
bearerCredential: "parent-only-token",
|
|
232
|
+
observeGatewayCall: (event) => Effect.sync(() => {
|
|
233
|
+
observed.push({
|
|
234
|
+
phase: event.phase,
|
|
235
|
+
role: event.role,
|
|
236
|
+
...(event.phase === "completed" && event.callId !== undefined
|
|
237
|
+
? { callId: event.callId }
|
|
238
|
+
: {})
|
|
239
|
+
});
|
|
240
|
+
})
|
|
241
|
+
}, (service) => service.runComparison({
|
|
227
242
|
...requestFor(suite, `http://127.0.0.1:${String(address.port)}`),
|
|
228
243
|
candidateModels: ["openai/cheap"]
|
|
229
244
|
}, "pilot")).pipe(Effect.provide(NodeHttpClient.layerUndici)));
|
|
230
245
|
assert.deepEqual(calls.map(({ model }) => model), ["openai/cheap", "openai/judge"]);
|
|
231
246
|
assert.equal(calls.every(({ authorization }) => authorization === "Bearer parent-only-token"), true);
|
|
232
247
|
assert.equal(calls.every(({ maxOutputTokens }) => maxOutputTokens === 1_024), true);
|
|
248
|
+
assert.deepEqual(observed, [
|
|
249
|
+
{ phase: "issued", role: "candidate" },
|
|
250
|
+
{
|
|
251
|
+
callId: "model-call-1",
|
|
252
|
+
phase: "completed",
|
|
253
|
+
role: "candidate"
|
|
254
|
+
},
|
|
255
|
+
{ phase: "issued", role: "judge" },
|
|
256
|
+
{
|
|
257
|
+
callId: "model-call-2",
|
|
258
|
+
phase: "completed",
|
|
259
|
+
role: "judge"
|
|
260
|
+
}
|
|
261
|
+
]);
|
|
233
262
|
assert.deepEqual(result.models.map(({ model }) => model), ["openai/cheap"]);
|
|
234
263
|
assert.equal(JSON.stringify(result).includes("parent-only-token"), false);
|
|
235
264
|
}
|
|
@@ -284,6 +313,38 @@ test("production runner applies the host timeout at node:test instead of a stale
|
|
|
284
313
|
await new Promise((resolve, reject) => gateway.close((error) => (error === undefined ? resolve() : reject(error))));
|
|
285
314
|
}
|
|
286
315
|
});
|
|
316
|
+
test("production runner uses its selected Node runtime for validation before observation", async () => {
|
|
317
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "routekit-eval-runner-spawn-failure-"));
|
|
318
|
+
roots.push(root);
|
|
319
|
+
const suite = path.join(root, "support.eval.ts");
|
|
320
|
+
await writeFile(suite, [
|
|
321
|
+
'import { test } from "node:test";',
|
|
322
|
+
'import { setupAgent } from "routekit/eval";',
|
|
323
|
+
'test("support case", async () => {',
|
|
324
|
+
' await setupAgent({ model: "openai/cheap" }).run({ prompt: "Help", caseId: "support-case" });',
|
|
325
|
+
"});"
|
|
326
|
+
].join("\n"));
|
|
327
|
+
await writeManifest(root, ["openai/cheap"], "openai/judge", ["support-case"]);
|
|
328
|
+
const observed = [];
|
|
329
|
+
const exit = await Effect.runPromise(withEvalService({
|
|
330
|
+
bearerCredential: "parent-only-token",
|
|
331
|
+
execPath: path.join(root, "missing-node"),
|
|
332
|
+
observeGatewayCall: (event) => Effect.sync(() => {
|
|
333
|
+
observed.push(event);
|
|
334
|
+
}),
|
|
335
|
+
timeoutMs: 600_000
|
|
336
|
+
}, (service) => service.runComparison({
|
|
337
|
+
...requestFor(suite),
|
|
338
|
+
candidateModels: ["openai/cheap"]
|
|
339
|
+
}, "full")).pipe(Effect.provide(NodeHttpClient.layerUndici), Effect.exit));
|
|
340
|
+
assert.equal(Exit.isFailure(exit), true);
|
|
341
|
+
assert.deepEqual(observed, []);
|
|
342
|
+
if (Exit.isFailure(exit)) {
|
|
343
|
+
assert.match(String(exit.cause), /validation failed/iu);
|
|
344
|
+
assert.match(String(exit.cause), /could not be loaded safely through node:test/iu);
|
|
345
|
+
assert.match(String(exit.cause), /missing-node/iu);
|
|
346
|
+
}
|
|
347
|
+
});
|
|
287
348
|
test("production runner isolates qualification from a project SDK with a shorter hidden abort", async () => {
|
|
288
349
|
const root = await mkdtemp(path.join(os.tmpdir(), "routekit-eval-runner-isolated-sdk-"));
|
|
289
350
|
roots.push(root);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velum-labs/routekit-eval-service",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.18",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/velum-labs/routekit.git",
|
|
@@ -32,10 +32,10 @@
|
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@effect/platform-node": "4.0.0-rc.108",
|
|
34
34
|
"effect": "4.0.0-rc.108",
|
|
35
|
-
"@velum-labs/routekit-eval-contracts": "1.0.
|
|
36
|
-
"@velum-labs/routekit-eval-engine": "1.0.
|
|
37
|
-
"@velum-labs/routekit-eval-store": "1.0.
|
|
38
|
-
"@velum-labs/routekit-registry": "1.0.
|
|
35
|
+
"@velum-labs/routekit-eval-contracts": "1.0.18",
|
|
36
|
+
"@velum-labs/routekit-eval-engine": "1.0.18",
|
|
37
|
+
"@velum-labs/routekit-eval-store": "1.0.18",
|
|
38
|
+
"@velum-labs/routekit-registry": "1.0.18"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"typescript": "7.0.2"
|