@gleanwork/mcp-server-tester 1.0.1 → 1.1.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/dist/cli/index.js +1 -1
- package/dist/fixtures/mcp.js +1 -1
- package/dist/fixtures/mcp.js.map +1 -1
- package/dist/index-BlqLvjnP.d.cts +4322 -0
- package/dist/index-BlqLvjnP.d.ts +4322 -0
- package/dist/index.cjs +767 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +551 -4365
- package/dist/index.d.ts +551 -4365
- package/dist/index.js +757 -44
- package/dist/index.js.map +1 -1
- package/dist/reporters/mcpReporter.cjs +313 -2
- package/dist/reporters/mcpReporter.cjs.map +1 -1
- package/dist/reporters/mcpReporter.d.cts +427 -3
- package/dist/reporters/mcpReporter.d.ts +427 -3
- package/dist/reporters/mcpReporter.js +313 -2
- package/dist/reporters/mcpReporter.js.map +1 -1
- package/dist/reporters/ui-dist/app.js +16 -16
- package/dist/reporters/ui-dist/styles.css +1 -1
- package/dist/types/index.cjs +19 -0
- package/dist/types/index.cjs.map +1 -0
- package/dist/types/index.d.cts +9 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/types/index.js +17 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +8 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from 'fs/promises';
|
|
2
|
-
import { readFile, mkdir, writeFile } from 'fs/promises';
|
|
2
|
+
import { readFile, mkdir, writeFile, readdir } from 'fs/promises';
|
|
3
3
|
import * as path2 from 'path';
|
|
4
|
-
import { dirname } from 'path';
|
|
4
|
+
import { dirname, join } from 'path';
|
|
5
5
|
import * as z4 from 'zod/v4';
|
|
6
6
|
import { ZodFirstPartyTypeKind } from 'zod/v3';
|
|
7
7
|
import { z } from 'zod';
|
|
@@ -3099,6 +3099,20 @@ var init_dist3 = __esm({
|
|
|
3099
3099
|
};
|
|
3100
3100
|
}
|
|
3101
3101
|
});
|
|
3102
|
+
|
|
3103
|
+
// src/assertions/validators/types.ts
|
|
3104
|
+
var SnapshotSanitizers = {
|
|
3105
|
+
/** Replaces Unix timestamps (seconds and milliseconds) with a stable placeholder */
|
|
3106
|
+
TIMESTAMP: "timestamp",
|
|
3107
|
+
/** Replaces UUID v1-v5 strings with a stable placeholder */
|
|
3108
|
+
UUID: "uuid",
|
|
3109
|
+
/** Replaces ISO 8601 date/datetime strings with a stable placeholder */
|
|
3110
|
+
ISO_DATE: "iso-date",
|
|
3111
|
+
/** Replaces MongoDB ObjectId strings with a stable placeholder */
|
|
3112
|
+
OBJECT_ID: "objectId",
|
|
3113
|
+
/** Replaces JWT tokens with a stable placeholder */
|
|
3114
|
+
JWT: "jwt"
|
|
3115
|
+
};
|
|
3102
3116
|
var MCPHostCapabilitiesSchema = z.object({
|
|
3103
3117
|
sampling: z.record(z.string(), z.unknown()).optional(),
|
|
3104
3118
|
roots: z.object({
|
|
@@ -4384,7 +4398,7 @@ function escapeHtml(text) {
|
|
|
4384
4398
|
|
|
4385
4399
|
// package.json
|
|
4386
4400
|
var package_default = {
|
|
4387
|
-
version: "1.
|
|
4401
|
+
version: "1.1.1"};
|
|
4388
4402
|
|
|
4389
4403
|
// src/mcp/clientFactory.ts
|
|
4390
4404
|
function getRetryAfterDelayMs(err) {
|
|
@@ -5908,20 +5922,6 @@ async function validateJudge(response, config) {
|
|
|
5908
5922
|
}
|
|
5909
5923
|
}
|
|
5910
5924
|
|
|
5911
|
-
// src/assertions/validators/types.ts
|
|
5912
|
-
var SnapshotSanitizers = {
|
|
5913
|
-
/** Replaces Unix timestamps (seconds and milliseconds) with a stable placeholder */
|
|
5914
|
-
TIMESTAMP: "timestamp",
|
|
5915
|
-
/** Replaces UUID v1-v5 strings with a stable placeholder */
|
|
5916
|
-
UUID: "uuid",
|
|
5917
|
-
/** Replaces ISO 8601 date/datetime strings with a stable placeholder */
|
|
5918
|
-
ISO_DATE: "iso-date",
|
|
5919
|
-
/** Replaces MongoDB ObjectId strings with a stable placeholder */
|
|
5920
|
-
OBJECT_ID: "objectId",
|
|
5921
|
-
/** Replaces JWT tokens with a stable placeholder */
|
|
5922
|
-
JWT: "jwt"
|
|
5923
|
-
};
|
|
5924
|
-
|
|
5925
5925
|
// src/mcp/fixtures/mcpFixture.ts
|
|
5926
5926
|
var DEFAULT_CALL_TIMEOUT_MS = 3e4;
|
|
5927
5927
|
function withCallTimeout(promise, ms, opName) {
|
|
@@ -6811,8 +6811,8 @@ Hint: run \`getMissingDependencyMessage('${provider}')\` or check docs/mcp-host.
|
|
|
6811
6811
|
Hint: check your API key environment variable (e.g. ANTHROPIC_API_KEY, GOOGLE_APPLICATION_CREDENTIALS).`;
|
|
6812
6812
|
}
|
|
6813
6813
|
if (raw.includes("404") || raw.includes("Not Found") || raw.toLowerCase().includes("model") && raw.toLowerCase().includes("not found")) {
|
|
6814
|
-
return `MCP host simulation failed:
|
|
6815
|
-
Hint:
|
|
6814
|
+
return `MCP host simulation failed: ${raw}
|
|
6815
|
+
Hint: a 404 usually means the model id is wrong or retired, or a base-URL override (e.g. ANTHROPIC_BASE_URL / OPENAI_BASE_URL pointing at a gateway) is routing requests somewhere that doesn't serve this model. Verify the model id and that no unexpected *_BASE_URL is set.`;
|
|
6816
6816
|
}
|
|
6817
6817
|
if (raw.includes("ENOTFOUND") || raw.includes("fetch failed") || raw.includes("ECONNREFUSED")) {
|
|
6818
6818
|
return `MCP host simulation failed: network error.
|
|
@@ -6910,12 +6910,18 @@ function createVercelOrchestrator() {
|
|
|
6910
6910
|
tools[toolName] = {
|
|
6911
6911
|
description: mcpTool.description ?? "",
|
|
6912
6912
|
inputSchema: jsonSchema2(rawSchema),
|
|
6913
|
-
execute: async (args) => {
|
|
6913
|
+
execute: async (args, opts) => {
|
|
6914
6914
|
const mcpStart = Date.now();
|
|
6915
6915
|
const result2 = await mcp.callTool(toolName, args);
|
|
6916
6916
|
mcpDurationMs += Date.now() - mcpStart;
|
|
6917
|
-
|
|
6918
|
-
|
|
6917
|
+
const output = extractText(result2);
|
|
6918
|
+
allToolCalls.push({
|
|
6919
|
+
id: opts?.toolCallId,
|
|
6920
|
+
name: toolName,
|
|
6921
|
+
arguments: args,
|
|
6922
|
+
output
|
|
6923
|
+
});
|
|
6924
|
+
return output;
|
|
6919
6925
|
}
|
|
6920
6926
|
};
|
|
6921
6927
|
}
|
|
@@ -6937,10 +6943,17 @@ function createVercelOrchestrator() {
|
|
|
6937
6943
|
totalCostUsd: 0,
|
|
6938
6944
|
durationMs: llmDurationMs
|
|
6939
6945
|
} : void 0;
|
|
6940
|
-
const conversationHistory = (result.steps ?? []).
|
|
6941
|
-
|
|
6942
|
-
|
|
6943
|
-
|
|
6946
|
+
const conversationHistory = (result.steps ?? []).flatMap((step) => {
|
|
6947
|
+
if (step.toolCalls?.length > 0) {
|
|
6948
|
+
return step.toolCalls.map(
|
|
6949
|
+
(tc) => ({
|
|
6950
|
+
role: "tool",
|
|
6951
|
+
toolCallId: tc.toolCallId
|
|
6952
|
+
})
|
|
6953
|
+
);
|
|
6954
|
+
}
|
|
6955
|
+
return step.text ? [{ role: "assistant", content: step.text }] : [];
|
|
6956
|
+
});
|
|
6944
6957
|
return {
|
|
6945
6958
|
success: true,
|
|
6946
6959
|
toolCalls: allToolCalls,
|
|
@@ -6967,6 +6980,7 @@ function parseStreamJson(stdout) {
|
|
|
6967
6980
|
const lines = stdout.split("\n").filter((line) => line.trim().length > 0);
|
|
6968
6981
|
const toolCalls = [];
|
|
6969
6982
|
const textParts = [];
|
|
6983
|
+
let usage;
|
|
6970
6984
|
const conversationHistory = [];
|
|
6971
6985
|
for (const line of lines) {
|
|
6972
6986
|
let event;
|
|
@@ -6995,20 +7009,38 @@ function parseStreamJson(stdout) {
|
|
|
6995
7009
|
for (const block of event.message.content) {
|
|
6996
7010
|
if (block.type === "tool_result") {
|
|
6997
7011
|
const content = typeof block.content === "string" ? block.content : JSON.stringify(block.content);
|
|
6998
|
-
|
|
7012
|
+
const call = block.tool_use_id ? toolCalls.find((tc) => tc.id === block.tool_use_id) : void 0;
|
|
7013
|
+
if (call) {
|
|
7014
|
+
call.output = content;
|
|
7015
|
+
conversationHistory.push({ role: "tool", toolCallId: call.id });
|
|
7016
|
+
} else {
|
|
7017
|
+
conversationHistory.push({ role: "tool", content });
|
|
7018
|
+
}
|
|
6999
7019
|
}
|
|
7000
7020
|
}
|
|
7001
7021
|
}
|
|
7002
|
-
if (event.type === "result"
|
|
7003
|
-
if (textParts.length === 0) {
|
|
7022
|
+
if (event.type === "result") {
|
|
7023
|
+
if (typeof event.result === "string" && textParts.length === 0) {
|
|
7004
7024
|
textParts.push(event.result);
|
|
7005
7025
|
}
|
|
7026
|
+
if (event.usage) {
|
|
7027
|
+
usage = {
|
|
7028
|
+
inputTokens: event.usage.input_tokens ?? 0,
|
|
7029
|
+
outputTokens: event.usage.output_tokens ?? 0,
|
|
7030
|
+
totalCostUsd: event.total_cost_usd ?? 0,
|
|
7031
|
+
durationMs: event.duration_ms ?? 0,
|
|
7032
|
+
durationApiMs: event.duration_api_ms,
|
|
7033
|
+
cacheReadInputTokens: event.usage.cache_read_input_tokens,
|
|
7034
|
+
cacheCreationInputTokens: event.usage.cache_creation_input_tokens
|
|
7035
|
+
};
|
|
7036
|
+
}
|
|
7006
7037
|
}
|
|
7007
7038
|
if (event.type === "result" && event.is_error === true) {
|
|
7008
7039
|
return {
|
|
7009
7040
|
success: false,
|
|
7010
7041
|
toolCalls,
|
|
7011
|
-
error: typeof event.result === "string" ? event.result : "CLI host reported an error"
|
|
7042
|
+
error: typeof event.result === "string" ? event.result : "CLI host reported an error",
|
|
7043
|
+
usage
|
|
7012
7044
|
};
|
|
7013
7045
|
}
|
|
7014
7046
|
}
|
|
@@ -7020,7 +7052,8 @@ function parseStreamJson(stdout) {
|
|
|
7020
7052
|
success: true,
|
|
7021
7053
|
toolCalls,
|
|
7022
7054
|
response: response || void 0,
|
|
7023
|
-
conversationHistory: conversationHistory.length > 0 ? conversationHistory : void 0
|
|
7055
|
+
conversationHistory: conversationHistory.length > 0 ? conversationHistory : void 0,
|
|
7056
|
+
usage
|
|
7024
7057
|
};
|
|
7025
7058
|
}
|
|
7026
7059
|
function createJsonParser(paths) {
|
|
@@ -7266,6 +7299,216 @@ function buildBaselinePassMap(baseline) {
|
|
|
7266
7299
|
}
|
|
7267
7300
|
return map;
|
|
7268
7301
|
}
|
|
7302
|
+
var KIND_DIRS = {
|
|
7303
|
+
"eval-runner-result": "eval-runs",
|
|
7304
|
+
"reporter-run": "reporter-runs",
|
|
7305
|
+
"eval-run-comparison": "comparisons/eval-runs",
|
|
7306
|
+
"server-comparison": "comparisons/servers"
|
|
7307
|
+
};
|
|
7308
|
+
function createEvalResultStore(config) {
|
|
7309
|
+
if (config.provider === "file") {
|
|
7310
|
+
return new FileEvalResultStore(config);
|
|
7311
|
+
}
|
|
7312
|
+
return new GCSEvalResultStore(config);
|
|
7313
|
+
}
|
|
7314
|
+
function resolveEvalResultStore(store) {
|
|
7315
|
+
return isEvalResultStore(store) ? store : createEvalResultStore(store);
|
|
7316
|
+
}
|
|
7317
|
+
function isEvalResultStore(value) {
|
|
7318
|
+
return typeof value === "object" && value !== null && "saveArtifact" in value && "loadArtifact" in value && "loadLatestArtifact" in value && "listArtifacts" in value;
|
|
7319
|
+
}
|
|
7320
|
+
function createStoredEvalArtifact(options) {
|
|
7321
|
+
const createdAt = options.createdAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
7322
|
+
return {
|
|
7323
|
+
schemaVersion: 1,
|
|
7324
|
+
kind: options.kind,
|
|
7325
|
+
id: options.id ?? createDefaultArtifactId(createdAt),
|
|
7326
|
+
createdAt,
|
|
7327
|
+
metadata: {
|
|
7328
|
+
...defaultEnvironmentMetadata(),
|
|
7329
|
+
...options.metadata ?? {}
|
|
7330
|
+
},
|
|
7331
|
+
data: options.data
|
|
7332
|
+
};
|
|
7333
|
+
}
|
|
7334
|
+
function createDefaultArtifactId(timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
7335
|
+
const safeTimestamp = timestamp.replace(/[:.]/g, "-");
|
|
7336
|
+
const runNumber = process.env.GITHUB_RUN_NUMBER;
|
|
7337
|
+
const sha = process.env.GITHUB_SHA?.slice(0, 12);
|
|
7338
|
+
const suffix = runNumber ?? sha;
|
|
7339
|
+
return suffix ? `${safeTimestamp}-${suffix}` : safeTimestamp;
|
|
7340
|
+
}
|
|
7341
|
+
function defaultEnvironmentMetadata() {
|
|
7342
|
+
return {
|
|
7343
|
+
...process.env.GITHUB_SHA !== void 0 && {
|
|
7344
|
+
gitHash: process.env.GITHUB_SHA
|
|
7345
|
+
},
|
|
7346
|
+
...process.env.GITHUB_REF_NAME !== void 0 && {
|
|
7347
|
+
branch: process.env.GITHUB_REF_NAME
|
|
7348
|
+
},
|
|
7349
|
+
...process.env.GITHUB_RUN_NUMBER !== void 0 && {
|
|
7350
|
+
runNumber: process.env.GITHUB_RUN_NUMBER
|
|
7351
|
+
},
|
|
7352
|
+
...process.env.GITHUB_EVENT_NAME !== void 0 && {
|
|
7353
|
+
trigger: process.env.GITHUB_EVENT_NAME
|
|
7354
|
+
}
|
|
7355
|
+
};
|
|
7356
|
+
}
|
|
7357
|
+
var FileEvalResultStore = class {
|
|
7358
|
+
dir;
|
|
7359
|
+
constructor(config) {
|
|
7360
|
+
this.dir = config.dir;
|
|
7361
|
+
}
|
|
7362
|
+
async saveArtifact(artifact) {
|
|
7363
|
+
const artifactDir = join(this.dir, KIND_DIRS[artifact.kind]);
|
|
7364
|
+
await mkdir(artifactDir, { recursive: true });
|
|
7365
|
+
const serialized = JSON.stringify(artifact, null, 2);
|
|
7366
|
+
await writeFile(
|
|
7367
|
+
join(artifactDir, `${artifact.id}.json`),
|
|
7368
|
+
serialized,
|
|
7369
|
+
"utf8"
|
|
7370
|
+
);
|
|
7371
|
+
await writeFile(join(artifactDir, "latest.json"), serialized, "utf8");
|
|
7372
|
+
}
|
|
7373
|
+
async loadArtifact(kind, id) {
|
|
7374
|
+
const raw = await readFile(
|
|
7375
|
+
join(this.dir, KIND_DIRS[kind], `${id}.json`),
|
|
7376
|
+
"utf8"
|
|
7377
|
+
);
|
|
7378
|
+
return JSON.parse(raw);
|
|
7379
|
+
}
|
|
7380
|
+
async loadLatestArtifact(kind) {
|
|
7381
|
+
try {
|
|
7382
|
+
const raw = await readFile(
|
|
7383
|
+
join(this.dir, KIND_DIRS[kind], "latest.json"),
|
|
7384
|
+
"utf8"
|
|
7385
|
+
);
|
|
7386
|
+
return JSON.parse(raw);
|
|
7387
|
+
} catch (error) {
|
|
7388
|
+
if (isMissingFileError(error)) {
|
|
7389
|
+
return null;
|
|
7390
|
+
}
|
|
7391
|
+
throw error;
|
|
7392
|
+
}
|
|
7393
|
+
}
|
|
7394
|
+
async listArtifacts(kind, options = {}) {
|
|
7395
|
+
let files;
|
|
7396
|
+
try {
|
|
7397
|
+
files = await readdir(join(this.dir, KIND_DIRS[kind]));
|
|
7398
|
+
} catch (error) {
|
|
7399
|
+
if (isMissingFileError(error)) {
|
|
7400
|
+
return [];
|
|
7401
|
+
}
|
|
7402
|
+
throw error;
|
|
7403
|
+
}
|
|
7404
|
+
const summaries = await Promise.all(
|
|
7405
|
+
files.filter((f) => f.endsWith(".json") && f !== "latest.json").map(async (file) => {
|
|
7406
|
+
const raw = await readFile(
|
|
7407
|
+
join(this.dir, KIND_DIRS[kind], file),
|
|
7408
|
+
"utf8"
|
|
7409
|
+
);
|
|
7410
|
+
return toSummary(JSON.parse(raw));
|
|
7411
|
+
})
|
|
7412
|
+
);
|
|
7413
|
+
return summaries.sort((a, b) => b.createdAt.localeCompare(a.createdAt)).slice(0, options.limit);
|
|
7414
|
+
}
|
|
7415
|
+
};
|
|
7416
|
+
var GCSEvalResultStore = class {
|
|
7417
|
+
bucketName;
|
|
7418
|
+
prefix;
|
|
7419
|
+
storage;
|
|
7420
|
+
constructor(config) {
|
|
7421
|
+
this.bucketName = config.bucket;
|
|
7422
|
+
this.prefix = trimSlashes(config.prefix ?? "");
|
|
7423
|
+
}
|
|
7424
|
+
async saveArtifact(artifact) {
|
|
7425
|
+
const bucket = await this.getBucket();
|
|
7426
|
+
const serialized = JSON.stringify(artifact, null, 2);
|
|
7427
|
+
await bucket.file(
|
|
7428
|
+
this.objectPath(
|
|
7429
|
+
artifact.kind,
|
|
7430
|
+
`${encodeURIComponent(artifact.id)}.json`
|
|
7431
|
+
)
|
|
7432
|
+
).save(serialized, {
|
|
7433
|
+
contentType: "application/json",
|
|
7434
|
+
resumable: false,
|
|
7435
|
+
validation: false
|
|
7436
|
+
});
|
|
7437
|
+
await bucket.file(this.objectPath(artifact.kind, "latest.json")).save(serialized, {
|
|
7438
|
+
contentType: "application/json",
|
|
7439
|
+
resumable: false,
|
|
7440
|
+
validation: false
|
|
7441
|
+
});
|
|
7442
|
+
}
|
|
7443
|
+
async loadArtifact(kind, id) {
|
|
7444
|
+
const bucket = await this.getBucket();
|
|
7445
|
+
const file = bucket.file(
|
|
7446
|
+
this.objectPath(kind, `${encodeURIComponent(id)}.json`)
|
|
7447
|
+
);
|
|
7448
|
+
const [buffer] = await file.download();
|
|
7449
|
+
return JSON.parse(buffer.toString("utf8"));
|
|
7450
|
+
}
|
|
7451
|
+
async loadLatestArtifact(kind) {
|
|
7452
|
+
const bucket = await this.getBucket();
|
|
7453
|
+
const file = bucket.file(this.objectPath(kind, "latest.json"));
|
|
7454
|
+
const [exists] = await file.exists();
|
|
7455
|
+
if (!exists) return null;
|
|
7456
|
+
const [buffer] = await file.download();
|
|
7457
|
+
return JSON.parse(buffer.toString("utf8"));
|
|
7458
|
+
}
|
|
7459
|
+
async listArtifacts(kind, options = {}) {
|
|
7460
|
+
const bucket = await this.getBucket();
|
|
7461
|
+
const [files] = await bucket.getFiles({
|
|
7462
|
+
prefix: this.objectPath(kind, "")
|
|
7463
|
+
});
|
|
7464
|
+
const summaries = await Promise.all(
|
|
7465
|
+
files.filter(
|
|
7466
|
+
(f) => f.name.endsWith(".json") && !f.name.endsWith("/latest.json")
|
|
7467
|
+
).map(async (file) => {
|
|
7468
|
+
const [buffer] = await file.download();
|
|
7469
|
+
return toSummary(
|
|
7470
|
+
JSON.parse(buffer.toString("utf8"))
|
|
7471
|
+
);
|
|
7472
|
+
})
|
|
7473
|
+
);
|
|
7474
|
+
return summaries.sort((a, b) => b.createdAt.localeCompare(a.createdAt)).slice(0, options.limit);
|
|
7475
|
+
}
|
|
7476
|
+
async getBucket() {
|
|
7477
|
+
if (!this.storage) {
|
|
7478
|
+
const moduleName = "@google-cloud/storage";
|
|
7479
|
+
let Storage;
|
|
7480
|
+
try {
|
|
7481
|
+
const mod = await import(moduleName);
|
|
7482
|
+
Storage = mod.Storage;
|
|
7483
|
+
} catch (error) {
|
|
7484
|
+
throw new Error(
|
|
7485
|
+
`GCS result storage requires the optional \`@google-cloud/storage\` package. Install it and authenticate with Application Default Credentials via GOOGLE_APPLICATION_CREDENTIALS.
|
|
7486
|
+
Original error: ${error instanceof Error ? error.message : String(error)}`
|
|
7487
|
+
);
|
|
7488
|
+
}
|
|
7489
|
+
this.storage = new Storage();
|
|
7490
|
+
}
|
|
7491
|
+
return this.storage.bucket(this.bucketName);
|
|
7492
|
+
}
|
|
7493
|
+
objectPath(kind, filename) {
|
|
7494
|
+
const parts = [this.prefix, KIND_DIRS[kind], filename].filter(Boolean);
|
|
7495
|
+
return parts.join("/");
|
|
7496
|
+
}
|
|
7497
|
+
};
|
|
7498
|
+
function toSummary(artifact) {
|
|
7499
|
+
return {
|
|
7500
|
+
kind: artifact.kind,
|
|
7501
|
+
id: artifact.id,
|
|
7502
|
+
createdAt: artifact.createdAt,
|
|
7503
|
+
metadata: artifact.metadata
|
|
7504
|
+
};
|
|
7505
|
+
}
|
|
7506
|
+
function trimSlashes(value) {
|
|
7507
|
+
return value.replace(/^\/+|\/+$/g, "");
|
|
7508
|
+
}
|
|
7509
|
+
function isMissingFileError(error) {
|
|
7510
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
7511
|
+
}
|
|
7269
7512
|
var execFileAsync = promisify(execFile);
|
|
7270
7513
|
async function execFileNoThrow(file, args) {
|
|
7271
7514
|
try {
|
|
@@ -7312,6 +7555,41 @@ function sumUsage(a, b) {
|
|
|
7312
7555
|
}
|
|
7313
7556
|
|
|
7314
7557
|
// src/evals/evalRunner.ts
|
|
7558
|
+
function createToolOverrideMCP(mcp, variant) {
|
|
7559
|
+
return {
|
|
7560
|
+
...mcp,
|
|
7561
|
+
async listTools() {
|
|
7562
|
+
const tools = await mcp.listTools();
|
|
7563
|
+
const knownToolNames = new Set(tools.map((tool2) => tool2.name));
|
|
7564
|
+
const unknownToolNames = Object.keys(variant.tools).filter(
|
|
7565
|
+
(name15) => !knownToolNames.has(name15)
|
|
7566
|
+
);
|
|
7567
|
+
if (unknownToolNames.length > 0) {
|
|
7568
|
+
throw new Error(
|
|
7569
|
+
`[mcp-server-tester] toolOverrides variant "${variant.id}" references unknown tool(s): ` + unknownToolNames.join(", ")
|
|
7570
|
+
);
|
|
7571
|
+
}
|
|
7572
|
+
return tools.map((tool2) => {
|
|
7573
|
+
const override = variant.tools[tool2.name];
|
|
7574
|
+
if (!override) {
|
|
7575
|
+
return tool2;
|
|
7576
|
+
}
|
|
7577
|
+
return {
|
|
7578
|
+
...tool2,
|
|
7579
|
+
...override.description !== void 0 && {
|
|
7580
|
+
description: override.description
|
|
7581
|
+
},
|
|
7582
|
+
...override.inputSchema !== void 0 && {
|
|
7583
|
+
inputSchema: override.inputSchema
|
|
7584
|
+
}
|
|
7585
|
+
};
|
|
7586
|
+
});
|
|
7587
|
+
},
|
|
7588
|
+
async callTool(name15, args) {
|
|
7589
|
+
return mcp.callTool(name15, args);
|
|
7590
|
+
}
|
|
7591
|
+
};
|
|
7592
|
+
}
|
|
7315
7593
|
async function executeToolCall(evalCase, mcp) {
|
|
7316
7594
|
const mode = evalCase.mode || "direct";
|
|
7317
7595
|
try {
|
|
@@ -7496,9 +7774,12 @@ async function runExpectBlockValidations(expectBlock, response, config) {
|
|
|
7496
7774
|
}
|
|
7497
7775
|
return { expectations: results, toolPrecision, toolRecall };
|
|
7498
7776
|
}
|
|
7499
|
-
function buildRequest(evalCase) {
|
|
7777
|
+
function buildRequest(evalCase, toolOverrideVariantId) {
|
|
7500
7778
|
const request = {};
|
|
7501
7779
|
if (evalCase.description) request.description = evalCase.description;
|
|
7780
|
+
if (toolOverrideVariantId !== void 0) {
|
|
7781
|
+
request.toolOverrideVariantId = toolOverrideVariantId;
|
|
7782
|
+
}
|
|
7502
7783
|
if (evalCase.mode === "mcp_host") {
|
|
7503
7784
|
if (evalCase.scenario) request.scenario = evalCase.scenario;
|
|
7504
7785
|
if (evalCase.mcpHostConfig) {
|
|
@@ -7563,7 +7844,7 @@ async function runSingleIteration(evalCase, context, options) {
|
|
|
7563
7844
|
toolName: evalCase.scenario != null ? "mcp_host" : evalCase.toolName ?? "unknown",
|
|
7564
7845
|
source: "eval",
|
|
7565
7846
|
pass: didCasePass(error, expectationResults),
|
|
7566
|
-
request: buildRequest(evalCase),
|
|
7847
|
+
request: buildRequest(evalCase, options.toolOverrideVariantId),
|
|
7567
7848
|
response,
|
|
7568
7849
|
error,
|
|
7569
7850
|
expectations: expectationResults,
|
|
@@ -7642,7 +7923,8 @@ async function runEvalCase(evalCase, context, options = {}) {
|
|
|
7642
7923
|
authType: context.mcp.authType,
|
|
7643
7924
|
project: context.mcp.project,
|
|
7644
7925
|
durationMs: 0,
|
|
7645
|
-
tags: evalCase.tags
|
|
7926
|
+
tags: evalCase.tags,
|
|
7927
|
+
request: buildRequest(evalCase, options.toolOverrideVariantId)
|
|
7646
7928
|
};
|
|
7647
7929
|
const totalHostUsage = iterationResults.reduce(
|
|
7648
7930
|
(acc, r) => sumUsage(acc, r.hostUsage),
|
|
@@ -7689,6 +7971,7 @@ async function getGitHash() {
|
|
|
7689
7971
|
const result = await execFileNoThrow("git", ["rev-parse", "HEAD"]);
|
|
7690
7972
|
return result.status === 0 ? result.stdout.trim() : void 0;
|
|
7691
7973
|
}
|
|
7974
|
+
var warnedNoTestInfo = false;
|
|
7692
7975
|
async function runEvalDataset(options, context) {
|
|
7693
7976
|
const {
|
|
7694
7977
|
dataset,
|
|
@@ -7701,11 +7984,15 @@ async function runEvalDataset(options, context) {
|
|
|
7701
7984
|
filterTags,
|
|
7702
7985
|
saveResultsTo,
|
|
7703
7986
|
omitResponsesFromBaseline = true,
|
|
7987
|
+
redactStoredResponses,
|
|
7988
|
+
resultStore,
|
|
7704
7989
|
baselineResultsFrom,
|
|
7990
|
+
toolOverrides,
|
|
7705
7991
|
mcpHostModel,
|
|
7706
7992
|
judgeModel
|
|
7707
7993
|
} = options;
|
|
7708
7994
|
const startTime = Date.now();
|
|
7995
|
+
const effectiveContext = toolOverrides ? { ...context, mcp: createToolOverrideMCP(context.mcp, toolOverrides) } : context;
|
|
7709
7996
|
const allSchemas = {
|
|
7710
7997
|
...dataset.schemas,
|
|
7711
7998
|
...schemas
|
|
@@ -7737,9 +8024,10 @@ async function runEvalDataset(options, context) {
|
|
|
7737
8024
|
}
|
|
7738
8025
|
}
|
|
7739
8026
|
const effectiveCase = withIterations.judgeReps === void 0 && defaultJudgeReps !== void 0 ? { ...withIterations, judgeReps: defaultJudgeReps } : withIterations;
|
|
7740
|
-
const result2 = await runEvalCase(effectiveCase,
|
|
8027
|
+
const result2 = await runEvalCase(effectiveCase, effectiveContext, {
|
|
7741
8028
|
datasetName: dataset.name,
|
|
7742
|
-
schemas: allSchemas
|
|
8029
|
+
schemas: allSchemas,
|
|
8030
|
+
toolOverrideVariantId: toolOverrides?.id
|
|
7743
8031
|
});
|
|
7744
8032
|
if (onCaseComplete) {
|
|
7745
8033
|
await onCaseComplete(result2);
|
|
@@ -7764,6 +8052,9 @@ async function runEvalDataset(options, context) {
|
|
|
7764
8052
|
gitHash,
|
|
7765
8053
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7766
8054
|
packageVersion: package_default.version,
|
|
8055
|
+
...toolOverrides !== void 0 && {
|
|
8056
|
+
toolOverrideVariantId: toolOverrides.id
|
|
8057
|
+
},
|
|
7767
8058
|
...mcpHostModel !== void 0 && { mcpHostModel },
|
|
7768
8059
|
...judgeModel !== void 0 && { judgeModel }
|
|
7769
8060
|
};
|
|
@@ -7782,7 +8073,7 @@ async function runEvalDataset(options, context) {
|
|
|
7782
8073
|
};
|
|
7783
8074
|
if (baselineResultsFrom) {
|
|
7784
8075
|
try {
|
|
7785
|
-
const baseline = await loadBaseline(baselineResultsFrom);
|
|
8076
|
+
const baseline = typeof baselineResultsFrom === "string" ? await loadBaseline(baselineResultsFrom) : await loadStoredBaseline(baselineResultsFrom, resultStore);
|
|
7786
8077
|
const baselinePassRate = baseline.total > 0 ? baseline.passed / baseline.total : 0;
|
|
7787
8078
|
const baselineMap = buildBaselinePassMap(baseline);
|
|
7788
8079
|
const currentCaseIds = result.caseResults.map((cr) => cr.id);
|
|
@@ -7810,7 +8101,7 @@ async function runEvalDataset(options, context) {
|
|
|
7810
8101
|
result.deltaPassRate = result.total > 0 ? result.passed / result.total - baselinePassRate : 0;
|
|
7811
8102
|
} catch (err) {
|
|
7812
8103
|
console.warn(
|
|
7813
|
-
`[mcp-server-tester] Could not load baseline from ${baselineResultsFrom}: ${err instanceof Error ? err.message : String(err)}`
|
|
8104
|
+
`[mcp-server-tester] Could not load baseline from ${formatBaselineRef(baselineResultsFrom)}: ${err instanceof Error ? err.message : String(err)}`
|
|
7814
8105
|
);
|
|
7815
8106
|
}
|
|
7816
8107
|
}
|
|
@@ -7825,22 +8116,84 @@ async function runEvalDataset(options, context) {
|
|
|
7825
8116
|
result.datasetToolF1 = avgPrec + avgRecall > 0 ? 2 * avgPrec * avgRecall / (avgPrec + avgRecall) : 0;
|
|
7826
8117
|
}
|
|
7827
8118
|
if (saveResultsTo) {
|
|
7828
|
-
|
|
7829
|
-
|
|
7830
|
-
|
|
8119
|
+
if (typeof saveResultsTo === "string") {
|
|
8120
|
+
await saveBaseline(result, saveResultsTo, {
|
|
8121
|
+
omitResponses: omitResponsesFromBaseline
|
|
8122
|
+
});
|
|
8123
|
+
} else {
|
|
8124
|
+
await saveStoredEvalResult(result, saveResultsTo, {
|
|
8125
|
+
resultStore,
|
|
8126
|
+
omitResponses: redactStoredResponses ?? true,
|
|
8127
|
+
metadata: {
|
|
8128
|
+
datasetName: dataset.name,
|
|
8129
|
+
...toolOverrides?.id !== void 0 && {
|
|
8130
|
+
toolOverrideVariantId: toolOverrides.id
|
|
8131
|
+
},
|
|
8132
|
+
...mcpHostModel !== void 0 && { mcpHostModel },
|
|
8133
|
+
...judgeModel !== void 0 && { judgeModel },
|
|
8134
|
+
...gitHash !== void 0 && { gitHash },
|
|
8135
|
+
packageVersion: package_default.version
|
|
8136
|
+
}
|
|
8137
|
+
});
|
|
8138
|
+
}
|
|
7831
8139
|
}
|
|
7832
8140
|
if (context.testInfo) {
|
|
7833
8141
|
await context.testInfo.attach("mcp-test-results", {
|
|
7834
8142
|
contentType: "application/json",
|
|
7835
8143
|
body: Buffer.from(JSON.stringify({ caseResults }))
|
|
7836
8144
|
});
|
|
7837
|
-
} else if (caseResults.length > 0) {
|
|
8145
|
+
} else if (caseResults.length > 0 && !warnedNoTestInfo) {
|
|
8146
|
+
warnedNoTestInfo = true;
|
|
7838
8147
|
console.warn(
|
|
7839
8148
|
"[mcp-server-tester] runEvalDataset: testInfo not provided \u2014 results will not appear in the MCP reporter.\nTo enable reporting, pass testInfo from the Playwright test function:\n await runEvalDataset({ dataset }, { mcp, testInfo });"
|
|
7840
8149
|
);
|
|
7841
8150
|
}
|
|
7842
8151
|
return result;
|
|
7843
8152
|
}
|
|
8153
|
+
async function loadStoredBaseline(baselineResultsFrom, resultStore) {
|
|
8154
|
+
if (!resultStore) {
|
|
8155
|
+
throw new Error("resultStore is required for store-backed baselines");
|
|
8156
|
+
}
|
|
8157
|
+
const store = resolveEvalResultStore(resultStore);
|
|
8158
|
+
const artifact = baselineResultsFrom.ref === "latest" ? await store.loadLatestArtifact("eval-runner-result") : await store.loadArtifact(
|
|
8159
|
+
"eval-runner-result",
|
|
8160
|
+
baselineResultsFrom.ref.id
|
|
8161
|
+
);
|
|
8162
|
+
if (!artifact) {
|
|
8163
|
+
throw new Error("No latest eval run artifact found");
|
|
8164
|
+
}
|
|
8165
|
+
return artifact.data;
|
|
8166
|
+
}
|
|
8167
|
+
async function saveStoredEvalResult(result, saveResultsTo, options) {
|
|
8168
|
+
if (!options.resultStore) {
|
|
8169
|
+
throw new Error("resultStore is required for store-backed saves");
|
|
8170
|
+
}
|
|
8171
|
+
const store = resolveEvalResultStore(options.resultStore);
|
|
8172
|
+
const data = options.omitResponses ? omitResponsesFromResult(result) : result;
|
|
8173
|
+
const id = saveResultsTo.ref && saveResultsTo.ref !== "latest" ? saveResultsTo.ref.id : void 0;
|
|
8174
|
+
await store.saveArtifact(
|
|
8175
|
+
createStoredEvalArtifact({
|
|
8176
|
+
kind: "eval-runner-result",
|
|
8177
|
+
id,
|
|
8178
|
+
data,
|
|
8179
|
+
metadata: options.metadata
|
|
8180
|
+
})
|
|
8181
|
+
);
|
|
8182
|
+
}
|
|
8183
|
+
function omitResponsesFromResult(result) {
|
|
8184
|
+
return {
|
|
8185
|
+
...result,
|
|
8186
|
+
caseResults: result.caseResults.map(
|
|
8187
|
+
({ response: _response, ...rest }) => rest
|
|
8188
|
+
)
|
|
8189
|
+
};
|
|
8190
|
+
}
|
|
8191
|
+
function formatBaselineRef(baselineResultsFrom) {
|
|
8192
|
+
if (typeof baselineResultsFrom === "string") {
|
|
8193
|
+
return baselineResultsFrom;
|
|
8194
|
+
}
|
|
8195
|
+
return baselineResultsFrom.ref === "latest" ? "resultStore latest" : `resultStore ${baselineResultsFrom.ref.id}`;
|
|
8196
|
+
}
|
|
7844
8197
|
|
|
7845
8198
|
// src/evals/serverComparison.ts
|
|
7846
8199
|
async function runServerComparison(options, contextA, contextB) {
|
|
@@ -7882,7 +8235,7 @@ async function runServerComparison(options, contextA, contextB) {
|
|
|
7882
8235
|
}
|
|
7883
8236
|
const total = cases.length;
|
|
7884
8237
|
const decidedCases = aWins + bWins + ties;
|
|
7885
|
-
|
|
8238
|
+
const comparison = {
|
|
7886
8239
|
dataset: options.dataset.name,
|
|
7887
8240
|
total,
|
|
7888
8241
|
aWins,
|
|
@@ -7899,6 +8252,366 @@ async function runServerComparison(options, contextA, contextB) {
|
|
|
7899
8252
|
serverBResult: resultB,
|
|
7900
8253
|
durationMs: Date.now() - startTime
|
|
7901
8254
|
};
|
|
8255
|
+
if (options.comparisonStore) {
|
|
8256
|
+
await saveServerComparison({
|
|
8257
|
+
store: options.comparisonStore,
|
|
8258
|
+
comparison,
|
|
8259
|
+
id: options.comparisonId,
|
|
8260
|
+
metadata: {
|
|
8261
|
+
datasetName: options.dataset.name,
|
|
8262
|
+
...options.comparisonMetadata ?? {}
|
|
8263
|
+
},
|
|
8264
|
+
redactStoredResponses: options.redactStoredResponses
|
|
8265
|
+
});
|
|
8266
|
+
}
|
|
8267
|
+
return comparison;
|
|
8268
|
+
}
|
|
8269
|
+
async function saveServerComparison(options) {
|
|
8270
|
+
const store = resolveEvalResultStore(options.store);
|
|
8271
|
+
const data = options.redactStoredResponses ? redactResponses(options.comparison) : options.comparison;
|
|
8272
|
+
const artifact = createStoredEvalArtifact({
|
|
8273
|
+
kind: "server-comparison",
|
|
8274
|
+
id: options.id,
|
|
8275
|
+
data,
|
|
8276
|
+
metadata: {
|
|
8277
|
+
datasetName: options.comparison.dataset,
|
|
8278
|
+
...options.metadata ?? {}
|
|
8279
|
+
}
|
|
8280
|
+
});
|
|
8281
|
+
await store.saveArtifact(artifact);
|
|
8282
|
+
return artifact;
|
|
8283
|
+
}
|
|
8284
|
+
function redactResponses(value) {
|
|
8285
|
+
return JSON.parse(
|
|
8286
|
+
JSON.stringify(
|
|
8287
|
+
value,
|
|
8288
|
+
(key, currentValue) => key === "response" ? void 0 : currentValue
|
|
8289
|
+
)
|
|
8290
|
+
);
|
|
8291
|
+
}
|
|
8292
|
+
|
|
8293
|
+
// src/evals/evalRunComparison.ts
|
|
8294
|
+
function compareEvalRuns(options) {
|
|
8295
|
+
const { baseline, candidate, labels } = options;
|
|
8296
|
+
const candidateMap = new Map(
|
|
8297
|
+
candidate.caseResults.map((result) => [result.id, result])
|
|
8298
|
+
);
|
|
8299
|
+
const cases = [];
|
|
8300
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
8301
|
+
for (const baselineCase of baseline.caseResults) {
|
|
8302
|
+
seenIds.add(baselineCase.id);
|
|
8303
|
+
const candidateCase = candidateMap.get(baselineCase.id);
|
|
8304
|
+
if (!candidateCase) {
|
|
8305
|
+
cases.push({
|
|
8306
|
+
id: baselineCase.id,
|
|
8307
|
+
outcome: "MISSING_FROM_CANDIDATE",
|
|
8308
|
+
baseline: baselineCase
|
|
8309
|
+
});
|
|
8310
|
+
continue;
|
|
8311
|
+
}
|
|
8312
|
+
cases.push({
|
|
8313
|
+
id: baselineCase.id,
|
|
8314
|
+
outcome: compareCaseOutcome(baselineCase.pass, candidateCase.pass),
|
|
8315
|
+
baseline: baselineCase,
|
|
8316
|
+
candidate: candidateCase
|
|
8317
|
+
});
|
|
8318
|
+
}
|
|
8319
|
+
for (const candidateCase of candidate.caseResults) {
|
|
8320
|
+
if (seenIds.has(candidateCase.id)) {
|
|
8321
|
+
continue;
|
|
8322
|
+
}
|
|
8323
|
+
cases.push({
|
|
8324
|
+
id: candidateCase.id,
|
|
8325
|
+
outcome: "MISSING_FROM_BASELINE",
|
|
8326
|
+
candidate: candidateCase
|
|
8327
|
+
});
|
|
8328
|
+
}
|
|
8329
|
+
const baselinePassRate = passRate(baseline);
|
|
8330
|
+
const candidatePassRate = passRate(candidate);
|
|
8331
|
+
return {
|
|
8332
|
+
baselineLabel: labels?.baseline ?? "baseline",
|
|
8333
|
+
candidateLabel: labels?.candidate ?? candidate.metadata?.toolOverrideVariantId ?? "candidate",
|
|
8334
|
+
baselinePassRate,
|
|
8335
|
+
candidatePassRate,
|
|
8336
|
+
deltaPassRate: candidatePassRate - baselinePassRate,
|
|
8337
|
+
...metricDelta(
|
|
8338
|
+
"ToolPrecision",
|
|
8339
|
+
baseline.datasetToolPrecision,
|
|
8340
|
+
candidate.datasetToolPrecision
|
|
8341
|
+
),
|
|
8342
|
+
...metricDelta(
|
|
8343
|
+
"ToolRecall",
|
|
8344
|
+
baseline.datasetToolRecall,
|
|
8345
|
+
candidate.datasetToolRecall
|
|
8346
|
+
),
|
|
8347
|
+
...metricDelta("ToolF1", baseline.datasetToolF1, candidate.datasetToolF1),
|
|
8348
|
+
cases,
|
|
8349
|
+
improvedCases: cases.filter((c) => c.outcome === "IMPROVED"),
|
|
8350
|
+
regressedCases: cases.filter((c) => c.outcome === "REGRESSED"),
|
|
8351
|
+
unchangedPasses: cases.filter((c) => c.outcome === "UNCHANGED_PASS"),
|
|
8352
|
+
unchangedFailures: cases.filter((c) => c.outcome === "UNCHANGED_FAIL"),
|
|
8353
|
+
missingFromBaseline: cases.filter(
|
|
8354
|
+
(c) => c.outcome === "MISSING_FROM_BASELINE"
|
|
8355
|
+
),
|
|
8356
|
+
missingFromCandidate: cases.filter(
|
|
8357
|
+
(c) => c.outcome === "MISSING_FROM_CANDIDATE"
|
|
8358
|
+
)
|
|
8359
|
+
};
|
|
8360
|
+
}
|
|
8361
|
+
async function loadStoredEvalRunnerResult(storeLike, ref) {
|
|
8362
|
+
const store = resolveEvalResultStore(storeLike);
|
|
8363
|
+
const artifact = ref === "latest" ? await store.loadLatestArtifact("eval-runner-result") : await store.loadArtifact(
|
|
8364
|
+
"eval-runner-result",
|
|
8365
|
+
ref.id
|
|
8366
|
+
);
|
|
8367
|
+
if (!artifact) {
|
|
8368
|
+
throw new Error("No latest eval run artifact found");
|
|
8369
|
+
}
|
|
8370
|
+
return artifact;
|
|
8371
|
+
}
|
|
8372
|
+
async function saveEvalRunComparison(options) {
|
|
8373
|
+
const store = resolveEvalResultStore(options.store);
|
|
8374
|
+
const data = options.redactStoredResponses ? redactResponses2(options.comparison) : options.comparison;
|
|
8375
|
+
const artifact = createStoredEvalArtifact({
|
|
8376
|
+
kind: "eval-run-comparison",
|
|
8377
|
+
id: options.id,
|
|
8378
|
+
data,
|
|
8379
|
+
metadata: {
|
|
8380
|
+
labels: {
|
|
8381
|
+
baseline: options.comparison.baselineLabel,
|
|
8382
|
+
candidate: options.comparison.candidateLabel
|
|
8383
|
+
},
|
|
8384
|
+
...options.metadata ?? {}
|
|
8385
|
+
}
|
|
8386
|
+
});
|
|
8387
|
+
await store.saveArtifact(artifact);
|
|
8388
|
+
return artifact;
|
|
8389
|
+
}
|
|
8390
|
+
function compareCaseOutcome(baselinePass, candidatePass) {
|
|
8391
|
+
if (!baselinePass && candidatePass) return "IMPROVED";
|
|
8392
|
+
if (baselinePass && !candidatePass) return "REGRESSED";
|
|
8393
|
+
return baselinePass ? "UNCHANGED_PASS" : "UNCHANGED_FAIL";
|
|
8394
|
+
}
|
|
8395
|
+
function passRate(result) {
|
|
8396
|
+
return result.total > 0 ? result.passed / result.total : 0;
|
|
8397
|
+
}
|
|
8398
|
+
function metricDelta(name15, baselineValue, candidateValue) {
|
|
8399
|
+
const result = {};
|
|
8400
|
+
if (baselineValue !== void 0) {
|
|
8401
|
+
result[`baseline${name15}`] = baselineValue;
|
|
8402
|
+
}
|
|
8403
|
+
if (candidateValue !== void 0) {
|
|
8404
|
+
result[`candidate${name15}`] = candidateValue;
|
|
8405
|
+
}
|
|
8406
|
+
if (baselineValue !== void 0 && candidateValue !== void 0) {
|
|
8407
|
+
result[`delta${name15}`] = candidateValue - baselineValue;
|
|
8408
|
+
}
|
|
8409
|
+
return result;
|
|
8410
|
+
}
|
|
8411
|
+
function redactResponses2(value) {
|
|
8412
|
+
return JSON.parse(
|
|
8413
|
+
JSON.stringify(
|
|
8414
|
+
value,
|
|
8415
|
+
(key, currentValue) => key === "response" ? void 0 : currentValue
|
|
8416
|
+
)
|
|
8417
|
+
);
|
|
8418
|
+
}
|
|
8419
|
+
|
|
8420
|
+
// src/evals/variantExperiment.ts
|
|
8421
|
+
async function runVariantExperiment(options, context) {
|
|
8422
|
+
const metric = options.metric ?? "passRate";
|
|
8423
|
+
const maxRounds = options.maxRounds ?? 1;
|
|
8424
|
+
const minImprovement = options.minImprovement ?? 0;
|
|
8425
|
+
const allowRegressions = options.allowRegressions ?? false;
|
|
8426
|
+
const internalContext = {
|
|
8427
|
+
mcp: context.mcp,
|
|
8428
|
+
expect: context.expect
|
|
8429
|
+
};
|
|
8430
|
+
const baseline = await runEvalDataset(
|
|
8431
|
+
buildRunOptions(options, void 0),
|
|
8432
|
+
internalContext
|
|
8433
|
+
);
|
|
8434
|
+
const baselineValue = readMetric(baseline, metric);
|
|
8435
|
+
if (baselineValue === void 0) {
|
|
8436
|
+
throw new Error(
|
|
8437
|
+
`Metric '${metric}' is unavailable: the dataset produced no tool precision/recall data. Add mcp_host cases with toolsTriggered expectations, or use metric 'passRate'.`
|
|
8438
|
+
);
|
|
8439
|
+
}
|
|
8440
|
+
const rounds = [];
|
|
8441
|
+
let bestSoFar;
|
|
8442
|
+
let bestAttempted;
|
|
8443
|
+
let reason = "max-rounds";
|
|
8444
|
+
for (let round = 0; round < maxRounds; round++) {
|
|
8445
|
+
const variants = await gatherVariants(options, {
|
|
8446
|
+
round,
|
|
8447
|
+
baseline,
|
|
8448
|
+
metric,
|
|
8449
|
+
history: rounds,
|
|
8450
|
+
bestSoFar
|
|
8451
|
+
});
|
|
8452
|
+
if (variants.length === 0) {
|
|
8453
|
+
reason = round === 0 ? "no-variants" : "no-improvement";
|
|
8454
|
+
break;
|
|
8455
|
+
}
|
|
8456
|
+
const candidates = [];
|
|
8457
|
+
for (const variant of variants) {
|
|
8458
|
+
const candidate = await scoreVariant(
|
|
8459
|
+
options,
|
|
8460
|
+
internalContext,
|
|
8461
|
+
baseline,
|
|
8462
|
+
baselineValue,
|
|
8463
|
+
metric,
|
|
8464
|
+
allowRegressions,
|
|
8465
|
+
variant
|
|
8466
|
+
);
|
|
8467
|
+
candidates.push(candidate);
|
|
8468
|
+
bestAttempted = pickBetter(bestAttempted, candidate, true);
|
|
8469
|
+
}
|
|
8470
|
+
const roundBest = candidates.reduce(
|
|
8471
|
+
(best, candidate) => pickBetter(best, candidate, false),
|
|
8472
|
+
void 0
|
|
8473
|
+
);
|
|
8474
|
+
rounds.push({ round, candidates, best: roundBest });
|
|
8475
|
+
if (roundBest) {
|
|
8476
|
+
const improvement = roundBest.metricValue - (bestSoFar?.metricValue ?? baselineValue);
|
|
8477
|
+
bestSoFar = pickBetter(bestSoFar, roundBest, false);
|
|
8478
|
+
if (improvement < minImprovement) {
|
|
8479
|
+
reason = "no-improvement";
|
|
8480
|
+
break;
|
|
8481
|
+
}
|
|
8482
|
+
}
|
|
8483
|
+
}
|
|
8484
|
+
const winner = bestSoFar;
|
|
8485
|
+
const proposalSource = winner ?? bestAttempted;
|
|
8486
|
+
const proposal = proposalSource ? buildProposal(metric, baselineValue, proposalSource, winner !== void 0) : void 0;
|
|
8487
|
+
const result = {
|
|
8488
|
+
metric,
|
|
8489
|
+
baseline,
|
|
8490
|
+
rounds,
|
|
8491
|
+
winner,
|
|
8492
|
+
proposal,
|
|
8493
|
+
converged: true,
|
|
8494
|
+
reason
|
|
8495
|
+
};
|
|
8496
|
+
if (context.testInfo) {
|
|
8497
|
+
const surfaceRun = winner?.result ?? bestAttempted?.result ?? baseline;
|
|
8498
|
+
await context.testInfo.attach("mcp-test-results", {
|
|
8499
|
+
contentType: "application/json",
|
|
8500
|
+
body: Buffer.from(
|
|
8501
|
+
JSON.stringify({ caseResults: surfaceRun.caseResults })
|
|
8502
|
+
)
|
|
8503
|
+
});
|
|
8504
|
+
await context.testInfo.attach("mcp-variant-experiment", {
|
|
8505
|
+
contentType: "application/json",
|
|
8506
|
+
body: Buffer.from(
|
|
8507
|
+
JSON.stringify(buildExperimentData(result, baselineValue))
|
|
8508
|
+
)
|
|
8509
|
+
});
|
|
8510
|
+
}
|
|
8511
|
+
return result;
|
|
8512
|
+
}
|
|
8513
|
+
function buildExperimentData(result, baselineValue) {
|
|
8514
|
+
return {
|
|
8515
|
+
metric: result.metric,
|
|
8516
|
+
baselineValue,
|
|
8517
|
+
bestValue: result.winner?.metricValue ?? result.proposal?.candidateValue ?? baselineValue,
|
|
8518
|
+
rounds: result.rounds.map((round) => {
|
|
8519
|
+
const best = round.best ?? round.candidates[0];
|
|
8520
|
+
return {
|
|
8521
|
+
round: round.round,
|
|
8522
|
+
variantId: best?.variant.id ?? "(none)",
|
|
8523
|
+
metricValue: best?.metricValue ?? baselineValue,
|
|
8524
|
+
metricDelta: best?.metricDelta ?? 0,
|
|
8525
|
+
disqualified: best?.disqualified ?? false
|
|
8526
|
+
};
|
|
8527
|
+
}),
|
|
8528
|
+
winnerVariantId: result.winner?.variant.id,
|
|
8529
|
+
recommendation: result.proposal?.recommendation,
|
|
8530
|
+
reason: result.reason
|
|
8531
|
+
};
|
|
8532
|
+
}
|
|
8533
|
+
async function gatherVariants(options, context) {
|
|
8534
|
+
if (context.round === 0 && options.variants && options.variants.length > 0) {
|
|
8535
|
+
return options.variants;
|
|
8536
|
+
}
|
|
8537
|
+
if (options.proposeVariants) {
|
|
8538
|
+
return options.proposeVariants(context);
|
|
8539
|
+
}
|
|
8540
|
+
return [];
|
|
8541
|
+
}
|
|
8542
|
+
async function scoreVariant(options, context, baseline, baselineValue, metric, allowRegressions, variant) {
|
|
8543
|
+
const result = await runEvalDataset(
|
|
8544
|
+
buildRunOptions(options, variant),
|
|
8545
|
+
context
|
|
8546
|
+
);
|
|
8547
|
+
const comparison = compareEvalRuns({
|
|
8548
|
+
baseline,
|
|
8549
|
+
candidate: result,
|
|
8550
|
+
labels: { candidate: variant.id }
|
|
8551
|
+
});
|
|
8552
|
+
const metricValue = readMetric(result, metric) ?? baselineValue;
|
|
8553
|
+
const disqualified = !allowRegressions && comparison.regressedCases.length > 0;
|
|
8554
|
+
return {
|
|
8555
|
+
variant,
|
|
8556
|
+
result,
|
|
8557
|
+
comparison,
|
|
8558
|
+
metricValue,
|
|
8559
|
+
metricDelta: metricValue - baselineValue,
|
|
8560
|
+
disqualified
|
|
8561
|
+
};
|
|
8562
|
+
}
|
|
8563
|
+
function pickBetter(incumbent, challenger, includeDisqualified) {
|
|
8564
|
+
if (!includeDisqualified && challenger.disqualified) {
|
|
8565
|
+
return incumbent;
|
|
8566
|
+
}
|
|
8567
|
+
if (!incumbent) {
|
|
8568
|
+
return challenger;
|
|
8569
|
+
}
|
|
8570
|
+
return challenger.metricValue > incumbent.metricValue ? challenger : incumbent;
|
|
8571
|
+
}
|
|
8572
|
+
function buildProposal(metric, baselineValue, source, isWinner) {
|
|
8573
|
+
let recommendation;
|
|
8574
|
+
if (isWinner) {
|
|
8575
|
+
recommendation = source.metricDelta > 0 ? "apply" : "inconclusive";
|
|
8576
|
+
} else {
|
|
8577
|
+
recommendation = source.disqualified ? "reject" : "inconclusive";
|
|
8578
|
+
}
|
|
8579
|
+
return {
|
|
8580
|
+
variantId: source.variant.id,
|
|
8581
|
+
metric,
|
|
8582
|
+
baselineValue,
|
|
8583
|
+
candidateValue: source.metricValue,
|
|
8584
|
+
delta: source.metricDelta,
|
|
8585
|
+
toolChanges: source.variant.tools,
|
|
8586
|
+
improvedCaseIds: source.comparison.improvedCases.map((c) => c.id),
|
|
8587
|
+
regressedCaseIds: source.comparison.regressedCases.map((c) => c.id),
|
|
8588
|
+
recommendation
|
|
8589
|
+
};
|
|
8590
|
+
}
|
|
8591
|
+
function readMetric(result, metric) {
|
|
8592
|
+
switch (metric) {
|
|
8593
|
+
case "passRate":
|
|
8594
|
+
return result.total > 0 ? result.passed / result.total : 0;
|
|
8595
|
+
case "toolF1":
|
|
8596
|
+
return result.datasetToolF1;
|
|
8597
|
+
case "toolPrecision":
|
|
8598
|
+
return result.datasetToolPrecision;
|
|
8599
|
+
case "toolRecall":
|
|
8600
|
+
return result.datasetToolRecall;
|
|
8601
|
+
}
|
|
8602
|
+
}
|
|
8603
|
+
function buildRunOptions(options, toolOverrides) {
|
|
8604
|
+
return {
|
|
8605
|
+
dataset: options.dataset,
|
|
8606
|
+
toolOverrides,
|
|
8607
|
+
defaultLlmIterations: options.defaultLlmIterations,
|
|
8608
|
+
defaultJudgeReps: options.defaultJudgeReps,
|
|
8609
|
+
concurrency: options.concurrency,
|
|
8610
|
+
filterTags: options.filterTags,
|
|
8611
|
+
schemas: options.schemas,
|
|
8612
|
+
mcpHostModel: options.mcpHostModel,
|
|
8613
|
+
judgeModel: options.judgeModel
|
|
8614
|
+
};
|
|
7902
8615
|
}
|
|
7903
8616
|
|
|
7904
8617
|
// src/spec/conformanceChecks.ts
|
|
@@ -8072,6 +8785,6 @@ function formatCapabilities(capabilities) {
|
|
|
8072
8785
|
return parts.length > 0 ? parts.join(", ") : "none declared";
|
|
8073
8786
|
}
|
|
8074
8787
|
|
|
8075
|
-
export { BUILT_IN_RUBRICS, CLIOAuthClient, DiscoveryError, ENV_VAR_NAMES, EvalCaseSchema, EvalDatasetSchema, MCPConfigSchema, MCP_PROTOCOL_VERSION, PlaywrightOAuthClientProvider, SnapshotSanitizers, clearJudgeRegistry, closeMCPClient, createJudge, createMCPClientForConfig, createMCPFixture, createTokenAuthHeaders, discoverAuthorizationServer, discoverProtectedResource, expect, extractText, getMissingDependencyMessage, getRegisteredJudge, getResponseSizeBytes, hasValidTokens, injectTokens, isBuiltInRubric, isHttpConfig, isProviderAvailable, isStdioConfig, isTokenExpired, isTokenExpiringSoon, loadBaseline, loadEvalDataset, loadEvalDatasetFromObject, loadTokens, loadTokensFromEnv, test2 as mcpAuthTest, normalizeToolResponse, normalizeWhitespace, performClientCredentialsFlow, performOAuthSetup, performOAuthSetupIfNeeded, refreshAccessToken, registerJudge, resolveRubric, runConformanceChecks, runEvalCase, runEvalDataset, runServerComparison, saveBaseline, simulateMCPHost, test, validateAccessToken, validateError, validateEvalCase, validateEvalDataset, validateJudge, validateMCPConfig, validatePattern, validateResponse, validateSchema, validateSize, validateText, validateToolCallCount, validateToolCalls };
|
|
8788
|
+
export { BUILT_IN_RUBRICS, CLIOAuthClient, DiscoveryError, ENV_VAR_NAMES, EvalCaseSchema, EvalDatasetSchema, FileEvalResultStore, GCSEvalResultStore, MCPConfigSchema, MCP_PROTOCOL_VERSION, PlaywrightOAuthClientProvider, SnapshotSanitizers, clearJudgeRegistry, closeMCPClient, compareEvalRuns, createDefaultArtifactId, createEvalResultStore, createJudge, createMCPClientForConfig, createMCPFixture, createStoredEvalArtifact, createTokenAuthHeaders, defaultEnvironmentMetadata, discoverAuthorizationServer, discoverProtectedResource, expect, extractText, getMissingDependencyMessage, getRegisteredJudge, getResponseSizeBytes, hasValidTokens, injectTokens, isBuiltInRubric, isEvalResultStore, isHttpConfig, isProviderAvailable, isStdioConfig, isTokenExpired, isTokenExpiringSoon, loadBaseline, loadEvalDataset, loadEvalDatasetFromObject, loadStoredEvalRunnerResult, loadTokens, loadTokensFromEnv, test2 as mcpAuthTest, normalizeToolResponse, normalizeWhitespace, performClientCredentialsFlow, performOAuthSetup, performOAuthSetupIfNeeded, refreshAccessToken, registerJudge, resolveEvalResultStore, resolveRubric, runConformanceChecks, runEvalCase, runEvalDataset, runServerComparison, runVariantExperiment, saveBaseline, saveEvalRunComparison, saveServerComparison, simulateMCPHost, test, validateAccessToken, validateError, validateEvalCase, validateEvalDataset, validateJudge, validateMCPConfig, validatePattern, validateResponse, validateSchema, validateSize, validateText, validateToolCallCount, validateToolCalls };
|
|
8076
8789
|
//# sourceMappingURL=index.js.map
|
|
8077
8790
|
//# sourceMappingURL=index.js.map
|