@honua/mcp-server 0.0.10-alpha.0 → 0.1.0-beta.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/README.md +259 -15
- package/certification/geospatial-mcp-schemas/PROVENANCE.md +41 -0
- package/certification/geospatial-mcp-schemas/README.md +90 -0
- package/certification/geospatial-mcp-schemas/common/geoprocessing-error.schema.json +45 -0
- package/certification/geospatial-mcp-schemas/conformance/manifest.schema.json +79 -0
- package/certification/geospatial-mcp-schemas/index.json +390 -0
- package/certification/geospatial-mcp-schemas/resources/app-package.schema.json +20 -0
- package/certification/geospatial-mcp-schemas/resources/app-template.schema.json +15 -0
- package/certification/geospatial-mcp-schemas/resources/artifact.schema.json +69 -0
- package/certification/geospatial-mcp-schemas/resources/deployment.schema.json +27 -0
- package/certification/geospatial-mcp-schemas/resources/map-package.schema.json +21 -0
- package/certification/geospatial-mcp-schemas/resources/map-template.schema.json +15 -0
- package/certification/geospatial-mcp-schemas/resources/provenance.schema.json +50 -0
- package/certification/geospatial-mcp-schemas/resources/published-service.schema.json +21 -0
- package/certification/geospatial-mcp-schemas/resources/result-package.schema.json +64 -0
- package/certification/geospatial-mcp-schemas/resources/style.schema.json +12 -0
- package/certification/geospatial-mcp-schemas/resources/theme.schema.json +12 -0
- package/certification/geospatial-mcp-schemas/resources/workspace.schema.json +63 -0
- package/certification/geospatial-mcp-schemas/tools/apply_style_preset.schema.json +27 -0
- package/certification/geospatial-mcp-schemas/tools/buffer_features.schema.json +60 -0
- package/certification/geospatial-mcp-schemas/tools/cancel_job.schema.json +15 -0
- package/certification/geospatial-mcp-schemas/tools/clarify_intent.schema.json +69 -0
- package/certification/geospatial-mcp-schemas/tools/compose_mixed_protocol_map.schema.json +21 -0
- package/certification/geospatial-mcp-schemas/tools/create_app_package.schema.json +33 -0
- package/certification/geospatial-mcp-schemas/tools/create_map_package.schema.json +41 -0
- package/certification/geospatial-mcp-schemas/tools/edit_features.schema.json +74 -0
- package/certification/geospatial-mcp-schemas/tools/execute_plan.schema.json +64 -0
- package/certification/geospatial-mcp-schemas/tools/export_dataset.schema.json +60 -0
- package/certification/geospatial-mcp-schemas/tools/geocode_address.schema.json +36 -0
- package/certification/geospatial-mcp-schemas/tools/geocode_addresses.schema.json +34 -0
- package/certification/geospatial-mcp-schemas/tools/get_style.schema.json +40 -0
- package/certification/geospatial-mcp-schemas/tools/ground_candidates.schema.json +66 -0
- package/certification/geospatial-mcp-schemas/tools/ingest_dataset.schema.json +45 -0
- package/certification/geospatial-mcp-schemas/tools/join_features.schema.json +73 -0
- package/certification/geospatial-mcp-schemas/tools/list_capabilities.schema.json +21 -0
- package/certification/geospatial-mcp-schemas/tools/list_layers.schema.json +14 -0
- package/certification/geospatial-mcp-schemas/tools/overlay_features.schema.json +53 -0
- package/certification/geospatial-mcp-schemas/tools/plan_analysis.schema.json +19 -0
- package/certification/geospatial-mcp-schemas/tools/preview_app_package.schema.json +15 -0
- package/certification/geospatial-mcp-schemas/tools/preview_map_package.schema.json +17 -0
- package/certification/geospatial-mcp-schemas/tools/preview_package.schema.json +40 -0
- package/certification/geospatial-mcp-schemas/tools/propose_operation.schema.json +28 -0
- package/certification/geospatial-mcp-schemas/tools/publish_result.schema.json +29 -0
- package/certification/geospatial-mcp-schemas/tools/publish_service.schema.json +59 -0
- package/certification/geospatial-mcp-schemas/tools/query_features.schema.json +74 -0
- package/certification/geospatial-mcp-schemas/tools/refine_map_package.schema.json +22 -0
- package/certification/geospatial-mcp-schemas/tools/render_map.schema.json +60 -0
- package/certification/geospatial-mcp-schemas/tools/reproject_features.schema.json +49 -0
- package/certification/geospatial-mcp-schemas/tools/resolve_entity.schema.json +30 -0
- package/certification/geospatial-mcp-schemas/tools/solve_route.schema.json +39 -0
- package/certification/geospatial-mcp-schemas/tools/summarize_statistics.schema.json +71 -0
- package/certification/geospatial-mcp-schemas/tools/validate_package.schema.json +35 -0
- package/certification/geospatial-mcp-schemas/tools/validate_plan.schema.json +61 -0
- package/dist/src/capability.d.ts +48 -0
- package/dist/src/capability.d.ts.map +1 -0
- package/dist/src/capability.js +44 -0
- package/dist/src/certification/census-data.d.ts +33 -0
- package/dist/src/certification/census-data.d.ts.map +1 -0
- package/dist/src/certification/census-data.js +506 -0
- package/dist/src/certification/census-fixture-client.d.ts +7 -0
- package/dist/src/certification/census-fixture-client.d.ts.map +1 -0
- package/dist/src/certification/census-fixture-client.js +226 -0
- package/dist/src/certification/certifier.d.ts +125 -0
- package/dist/src/certification/certifier.d.ts.map +1 -0
- package/dist/src/certification/certifier.js +455 -0
- package/dist/src/certification/cli.d.ts +3 -0
- package/dist/src/certification/cli.d.ts.map +1 -0
- package/dist/src/certification/cli.js +63 -0
- package/dist/src/certification/contracts.d.ts +103 -0
- package/dist/src/certification/contracts.d.ts.map +1 -0
- package/dist/src/certification/contracts.js +245 -0
- package/dist/src/certification/fixture-client.d.ts +4 -0
- package/dist/src/certification/fixture-client.d.ts.map +1 -0
- package/dist/src/certification/fixture-client.js +113 -0
- package/dist/src/certification/fixtures.d.ts +45 -0
- package/dist/src/certification/fixtures.d.ts.map +1 -0
- package/dist/src/certification/fixtures.js +92 -0
- package/dist/src/certification/json-schema.d.ts +71 -0
- package/dist/src/certification/json-schema.d.ts.map +1 -0
- package/dist/src/certification/json-schema.js +172 -0
- package/dist/src/certification/lifecycle.d.ts +14 -0
- package/dist/src/certification/lifecycle.d.ts.map +1 -0
- package/dist/src/certification/lifecycle.js +326 -0
- package/dist/src/certification/mock-upstream.d.ts +29 -0
- package/dist/src/certification/mock-upstream.d.ts.map +1 -0
- package/dist/src/certification/mock-upstream.js +96 -0
- package/dist/src/certification/operator-catalog.d.ts +90 -0
- package/dist/src/certification/operator-catalog.d.ts.map +1 -0
- package/dist/src/certification/operator-catalog.js +650 -0
- package/dist/src/certification/report.d.ts +4 -0
- package/dist/src/certification/report.d.ts.map +1 -0
- package/dist/src/certification/report.js +103 -0
- package/dist/src/certification/run.d.ts +28 -0
- package/dist/src/certification/run.d.ts.map +1 -0
- package/dist/src/certification/run.js +32 -0
- package/dist/src/certification/schema-index.d.ts +94 -0
- package/dist/src/certification/schema-index.d.ts.map +1 -0
- package/dist/src/certification/schema-index.js +109 -0
- package/dist/src/certification/target.d.ts +48 -0
- package/dist/src/certification/target.d.ts.map +1 -0
- package/dist/src/certification/target.js +219 -0
- package/dist/src/eval/cli.d.ts +3 -0
- package/dist/src/eval/cli.d.ts.map +1 -0
- package/dist/src/eval/cli.js +118 -0
- package/dist/src/eval/corpus.d.ts +34 -0
- package/dist/src/eval/corpus.d.ts.map +1 -0
- package/dist/src/eval/corpus.js +150 -0
- package/dist/src/eval/drivers/anthropic.d.ts +14 -0
- package/dist/src/eval/drivers/anthropic.d.ts.map +1 -0
- package/dist/src/eval/drivers/anthropic.js +120 -0
- package/dist/src/eval/drivers/bedrock.d.ts +54 -0
- package/dist/src/eval/drivers/bedrock.d.ts.map +1 -0
- package/dist/src/eval/drivers/bedrock.js +178 -0
- package/dist/src/eval/drivers/deterministic.d.ts +17 -0
- package/dist/src/eval/drivers/deterministic.d.ts.map +1 -0
- package/dist/src/eval/drivers/deterministic.js +64 -0
- package/dist/src/eval/drivers/index.d.ts +34 -0
- package/dist/src/eval/drivers/index.d.ts.map +1 -0
- package/dist/src/eval/drivers/index.js +74 -0
- package/dist/src/eval/drivers/openai.d.ts +14 -0
- package/dist/src/eval/drivers/openai.d.ts.map +1 -0
- package/dist/src/eval/drivers/openai.js +136 -0
- package/dist/src/eval/grade.d.ts +14 -0
- package/dist/src/eval/grade.d.ts.map +1 -0
- package/dist/src/eval/grade.js +75 -0
- package/dist/src/eval/northstar-corpus.d.ts +3 -0
- package/dist/src/eval/northstar-corpus.d.ts.map +1 -0
- package/dist/src/eval/northstar-corpus.js +194 -0
- package/dist/src/eval/operator-corpus.d.ts +3 -0
- package/dist/src/eval/operator-corpus.d.ts.map +1 -0
- package/dist/src/eval/operator-corpus.js +193 -0
- package/dist/src/eval/report.d.ts +109 -0
- package/dist/src/eval/report.d.ts.map +1 -0
- package/dist/src/eval/report.js +133 -0
- package/dist/src/eval/runner.d.ts +28 -0
- package/dist/src/eval/runner.d.ts.map +1 -0
- package/dist/src/eval/runner.js +130 -0
- package/dist/src/eval/standalone-corpus.d.ts +3 -0
- package/dist/src/eval/standalone-corpus.d.ts.map +1 -0
- package/dist/src/eval/standalone-corpus.js +699 -0
- package/dist/src/eval/types.d.ts +118 -0
- package/dist/src/eval/types.d.ts.map +1 -0
- package/dist/src/eval/types.js +10 -0
- package/dist/src/helpers.d.ts +11 -0
- package/dist/src/helpers.d.ts.map +1 -1
- package/dist/src/helpers.js +22 -0
- package/dist/src/index.d.ts +31 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +50 -4
- package/dist/src/provenance.d.ts +44 -0
- package/dist/src/provenance.d.ts.map +1 -0
- package/dist/src/provenance.js +37 -0
- package/dist/src/proxy.d.ts +46 -0
- package/dist/src/proxy.d.ts.map +1 -0
- package/dist/src/proxy.js +124 -0
- package/dist/src/resources/styles.d.ts +29 -0
- package/dist/src/resources/styles.d.ts.map +1 -0
- package/dist/src/resources/styles.js +76 -0
- package/dist/src/styles.d.ts +112 -0
- package/dist/src/styles.d.ts.map +1 -0
- package/dist/src/styles.js +144 -0
- package/dist/src/tools/apply-style-preset.d.ts +27 -0
- package/dist/src/tools/apply-style-preset.d.ts.map +1 -0
- package/dist/src/tools/apply-style-preset.js +41 -0
- package/dist/src/tools/count-features.d.ts.map +1 -1
- package/dist/src/tools/count-features.js +5 -4
- package/dist/src/tools/get-extent.d.ts.map +1 -1
- package/dist/src/tools/get-extent.js +6 -4
- package/dist/src/tools/get-style.d.ts +24 -0
- package/dist/src/tools/get-style.d.ts.map +1 -0
- package/dist/src/tools/get-style.js +39 -0
- package/dist/src/tools/list-services.d.ts.map +1 -1
- package/dist/src/tools/list-services.js +16 -1
- package/dist/src/tools/query-features.d.ts.map +1 -1
- package/dist/src/tools/query-features.js +4 -1
- package/package.json +28 -4
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude-via-Amazon-Bedrock driver for the cross-model eval (honua-server #1956).
|
|
3
|
+
*
|
|
4
|
+
* This is the "any client LLM" counterpart to the direct-Anthropic driver, but it
|
|
5
|
+
* reaches Claude through Amazon Bedrock's Converse API instead of the Anthropic
|
|
6
|
+
* API. It exists so the AI proof can run on infrastructure that has AWS Bedrock
|
|
7
|
+
* access (default `~/.aws` credential chain) but no ANTHROPIC_API_KEY — exactly
|
|
8
|
+
* the situation on the build host. The SAME corpus and grading are driven over
|
|
9
|
+
* the identical MCP catalog, so the artifact compares success / clarification /
|
|
10
|
+
* edit rates across vendors and transports.
|
|
11
|
+
*
|
|
12
|
+
* Auth uses the standard AWS SDK credential provider chain (env vars, shared
|
|
13
|
+
* config/credentials files, SSO, instance/role credentials) — no key is ever
|
|
14
|
+
* hardcoded. Because that chain is resolved asynchronously and cannot be sniffed
|
|
15
|
+
* synchronously, the driver opts in via the `HONUA_EVAL_BEDROCK` env flag (set by
|
|
16
|
+
* `resolveDrivers`) or by explicit selection (`--driver bedrock`).
|
|
17
|
+
*
|
|
18
|
+
* Model id: defaults to `anthropic.claude-sonnet-4-5-20250929-v1:0`, overridable
|
|
19
|
+
* via `HONUA_EVAL_BEDROCK_MODEL`. NOTE: most current Anthropic models on Bedrock
|
|
20
|
+
* are not enabled for on-demand throughput by the bare id and must be invoked
|
|
21
|
+
* through a cross-region inference profile — i.e. the `us.`-prefixed form
|
|
22
|
+
* `us.anthropic.claude-sonnet-4-5-20250929-v1:0` in us.* regions. Set
|
|
23
|
+
* `HONUA_EVAL_BEDROCK_MODEL` accordingly for live runs.
|
|
24
|
+
*
|
|
25
|
+
* Region: `AWS_REGION` if set, else `us-west-2`.
|
|
26
|
+
*
|
|
27
|
+
* `@aws-sdk/client-bedrock-runtime` is a declared dependency but is imported
|
|
28
|
+
* dynamically (variable specifier) so the offline CI path — which only runs the
|
|
29
|
+
* deterministic control — never needs to load it, and a graceful `driverError`
|
|
30
|
+
* is recorded if it is somehow absent.
|
|
31
|
+
*
|
|
32
|
+
* Live cross-model runs are tracked under honua-io/honua-server#1956.
|
|
33
|
+
*/
|
|
34
|
+
const ASK_USER_TOOL_NAME = "ask_user";
|
|
35
|
+
const ASK_USER_TOOL = {
|
|
36
|
+
toolSpec: {
|
|
37
|
+
name: ASK_USER_TOOL_NAME,
|
|
38
|
+
description: "Call this ONLY if the request is too ambiguous to proceed. Provide the clarifying question you would ask.",
|
|
39
|
+
inputSchema: {
|
|
40
|
+
json: {
|
|
41
|
+
type: "object",
|
|
42
|
+
properties: { question: { type: "string" } },
|
|
43
|
+
required: ["question"],
|
|
44
|
+
additionalProperties: false,
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
const SYSTEM_PROMPT = "You are a GIS analyst working through the Honua MCP tools. Complete the user's task by calling the available tools. " +
|
|
50
|
+
"Ground every step in real tool results — do not invent service names, layers, or fields. " +
|
|
51
|
+
"If, and only if, the request is genuinely ambiguous and you cannot proceed, call ask_user. " +
|
|
52
|
+
"When finished, give a concise final answer that references what the tools returned.";
|
|
53
|
+
const MAX_ITERATIONS = 8;
|
|
54
|
+
const MAX_TOKENS = 4096;
|
|
55
|
+
const DEFAULT_MODEL = "anthropic.claude-sonnet-4-5-20250929-v1:0";
|
|
56
|
+
const DEFAULT_REGION = "us-west-2";
|
|
57
|
+
export class BedrockDriver {
|
|
58
|
+
vendor = "bedrock";
|
|
59
|
+
id;
|
|
60
|
+
region;
|
|
61
|
+
enabled;
|
|
62
|
+
constructor(options = {}) {
|
|
63
|
+
this.id = options.model ?? DEFAULT_MODEL;
|
|
64
|
+
this.region = options.region ?? DEFAULT_REGION;
|
|
65
|
+
const flag = options.enabled ?? process.env.HONUA_EVAL_BEDROCK;
|
|
66
|
+
this.enabled = isTruthy(flag);
|
|
67
|
+
}
|
|
68
|
+
isAvailable() {
|
|
69
|
+
return this.enabled;
|
|
70
|
+
}
|
|
71
|
+
async runWorkflow(scenario, ctx) {
|
|
72
|
+
const steps = [];
|
|
73
|
+
const transcript = {
|
|
74
|
+
scenarioId: scenario.id,
|
|
75
|
+
modelId: this.id,
|
|
76
|
+
steps,
|
|
77
|
+
finalAnswer: "",
|
|
78
|
+
clarificationRequested: false,
|
|
79
|
+
errorCount: 0,
|
|
80
|
+
};
|
|
81
|
+
let runtime;
|
|
82
|
+
try {
|
|
83
|
+
// Variable specifier → not statically resolved; keeps the AWS SDK an
|
|
84
|
+
// optional load for live runs only and the build independent of it.
|
|
85
|
+
const specifier = "@aws-sdk/client-bedrock-runtime";
|
|
86
|
+
runtime = (await import(specifier));
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
transcript.driverError = "@aws-sdk/client-bedrock-runtime is not installed (required for live Bedrock eval runs)";
|
|
90
|
+
return transcript;
|
|
91
|
+
}
|
|
92
|
+
const { BedrockRuntimeClient, ConverseCommand } = runtime;
|
|
93
|
+
const client = new BedrockRuntimeClient({ region: this.region });
|
|
94
|
+
const toolConfig = { tools: [...toBedrockTools(ctx.tools), ASK_USER_TOOL] };
|
|
95
|
+
const messages = [{ role: "user", content: [{ text: scenario.prompt }] }];
|
|
96
|
+
try {
|
|
97
|
+
for (let i = 0; i < MAX_ITERATIONS; i++) {
|
|
98
|
+
const response = await client.send(new ConverseCommand({
|
|
99
|
+
modelId: this.id,
|
|
100
|
+
system: [{ text: SYSTEM_PROMPT }],
|
|
101
|
+
messages,
|
|
102
|
+
toolConfig,
|
|
103
|
+
inferenceConfig: { maxTokens: MAX_TOKENS },
|
|
104
|
+
}));
|
|
105
|
+
const content = response.output?.message?.content ?? [];
|
|
106
|
+
const toolUses = extractToolUses(content);
|
|
107
|
+
const text = extractText(content);
|
|
108
|
+
if (response.stopReason !== "tool_use" || toolUses.length === 0) {
|
|
109
|
+
transcript.finalAnswer = text;
|
|
110
|
+
return transcript;
|
|
111
|
+
}
|
|
112
|
+
messages.push({ role: "assistant", content });
|
|
113
|
+
const toolResults = [];
|
|
114
|
+
for (const use of toolUses) {
|
|
115
|
+
if (use.name === ASK_USER_TOOL_NAME) {
|
|
116
|
+
transcript.clarificationRequested = true;
|
|
117
|
+
transcript.finalAnswer = text;
|
|
118
|
+
return transcript;
|
|
119
|
+
}
|
|
120
|
+
const args = (use.input ?? {});
|
|
121
|
+
const result = await ctx.callTool(use.name, args);
|
|
122
|
+
steps.push({ tool: use.name, args, isError: result.isError });
|
|
123
|
+
if (result.isError) {
|
|
124
|
+
transcript.errorCount++;
|
|
125
|
+
}
|
|
126
|
+
toolResults.push(toToolResultBlock(use.toolUseId, result.text, result.isError));
|
|
127
|
+
}
|
|
128
|
+
messages.push({ role: "user", content: toolResults });
|
|
129
|
+
}
|
|
130
|
+
transcript.driverError = `exceeded ${MAX_ITERATIONS} tool-use iterations without finishing`;
|
|
131
|
+
return transcript;
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
transcript.driverError = err instanceof Error ? err.message : String(err);
|
|
135
|
+
return transcript;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function isTruthy(value) {
|
|
140
|
+
if (typeof value !== "string") {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
const v = value.trim().toLowerCase();
|
|
144
|
+
return v.length > 0 && v !== "0" && v !== "false" && v !== "no" && v !== "off";
|
|
145
|
+
}
|
|
146
|
+
/** Map MCP tool descriptors to Bedrock Converse `toolSpec` entries. */
|
|
147
|
+
export function toBedrockTools(tools) {
|
|
148
|
+
return tools.map((t) => ({
|
|
149
|
+
toolSpec: {
|
|
150
|
+
name: t.name,
|
|
151
|
+
description: t.description,
|
|
152
|
+
inputSchema: { json: (t.inputSchema ?? { type: "object" }) },
|
|
153
|
+
},
|
|
154
|
+
}));
|
|
155
|
+
}
|
|
156
|
+
/** Pull tool-use blocks out of a Converse content array. */
|
|
157
|
+
export function extractToolUses(content) {
|
|
158
|
+
return content
|
|
159
|
+
.map((b) => b.toolUse)
|
|
160
|
+
.filter((u) => typeof u === "object" && u !== null && typeof u.name === "string");
|
|
161
|
+
}
|
|
162
|
+
/** Concatenate text blocks of a Converse content array. */
|
|
163
|
+
export function extractText(content) {
|
|
164
|
+
return content
|
|
165
|
+
.map((b) => b.text)
|
|
166
|
+
.filter((t) => typeof t === "string")
|
|
167
|
+
.join("\n");
|
|
168
|
+
}
|
|
169
|
+
/** Build a Converse `toolResult` content block to feed back to the model. */
|
|
170
|
+
export function toToolResultBlock(toolUseId, text, isError) {
|
|
171
|
+
return {
|
|
172
|
+
toolResult: {
|
|
173
|
+
toolUseId,
|
|
174
|
+
content: [{ text }],
|
|
175
|
+
status: isError ? "error" : "success",
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ModelDriver, Scenario, WorkflowContext, WorkflowTranscript } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Deterministic offline driver — the CI control for the cross-model eval.
|
|
4
|
+
*
|
|
5
|
+
* It does not call any model. Instead it executes the scenario's scripted "ideal
|
|
6
|
+
* client" tool trajectory against the REAL MCP surface (the same `tools/call`
|
|
7
|
+
* round-trips a live LLM would make), then synthesizes a final answer from the
|
|
8
|
+
* concatenated tool results. This makes the offline eval a genuine end-to-end
|
|
9
|
+
* exercise of the MCP surface — not a stub — while staying fully reproducible.
|
|
10
|
+
*/
|
|
11
|
+
export declare class DeterministicDriver implements ModelDriver {
|
|
12
|
+
readonly id = "deterministic";
|
|
13
|
+
readonly vendor: "deterministic";
|
|
14
|
+
isAvailable(): boolean;
|
|
15
|
+
runWorkflow(scenario: Scenario, ctx: WorkflowContext): Promise<WorkflowTranscript>;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=deterministic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deterministic.d.ts","sourceRoot":"","sources":["../../../../src/eval/drivers/deterministic.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAkB,eAAe,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAE9G;;;;;;;;GAQG;AACH,qBAAa,mBAAoB,YAAW,WAAW;IACrD,QAAQ,CAAC,EAAE,mBAAmB;IAC9B,QAAQ,CAAC,MAAM,EAAG,eAAe,CAAU;IAE3C,WAAW,IAAI,OAAO;IAIhB,WAAW,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,kBAAkB,CAAC;CAoDzF"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic offline driver — the CI control for the cross-model eval.
|
|
3
|
+
*
|
|
4
|
+
* It does not call any model. Instead it executes the scenario's scripted "ideal
|
|
5
|
+
* client" tool trajectory against the REAL MCP surface (the same `tools/call`
|
|
6
|
+
* round-trips a live LLM would make), then synthesizes a final answer from the
|
|
7
|
+
* concatenated tool results. This makes the offline eval a genuine end-to-end
|
|
8
|
+
* exercise of the MCP surface — not a stub — while staying fully reproducible.
|
|
9
|
+
*/
|
|
10
|
+
export class DeterministicDriver {
|
|
11
|
+
id = "deterministic";
|
|
12
|
+
vendor = "deterministic";
|
|
13
|
+
isAvailable() {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
async runWorkflow(scenario, ctx) {
|
|
17
|
+
const advertised = new Set(ctx.tools.map((t) => t.name));
|
|
18
|
+
const steps = [];
|
|
19
|
+
const answerParts = [];
|
|
20
|
+
let errorCount = 0;
|
|
21
|
+
// Ambiguity/refusal scenarios: the ideal client asks for clarification rather
|
|
22
|
+
// than guessing. Honoring this deterministically lets the offline control
|
|
23
|
+
// exercise refusal/clarification behavior, not just happy-path trajectories.
|
|
24
|
+
if (scenario.clarify) {
|
|
25
|
+
return {
|
|
26
|
+
scenarioId: scenario.id,
|
|
27
|
+
modelId: this.id,
|
|
28
|
+
steps,
|
|
29
|
+
finalAnswer: scenario.clarify.question,
|
|
30
|
+
clarificationRequested: true,
|
|
31
|
+
errorCount,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
for (const planned of scenario.script) {
|
|
35
|
+
if (!advertised.has(planned.tool)) {
|
|
36
|
+
// The scripted tool is not on the surface — record a driver-visible error
|
|
37
|
+
// (a real catalog regression would surface here, not silently pass).
|
|
38
|
+
return {
|
|
39
|
+
scenarioId: scenario.id,
|
|
40
|
+
modelId: this.id,
|
|
41
|
+
steps,
|
|
42
|
+
finalAnswer: answerParts.join("\n"),
|
|
43
|
+
clarificationRequested: false,
|
|
44
|
+
errorCount,
|
|
45
|
+
driverError: `scripted tool not advertised by the MCP surface: ${planned.tool}`,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const result = await ctx.callTool(planned.tool, planned.args);
|
|
49
|
+
steps.push({ tool: planned.tool, args: planned.args, isError: result.isError });
|
|
50
|
+
if (result.isError) {
|
|
51
|
+
errorCount++;
|
|
52
|
+
}
|
|
53
|
+
answerParts.push(result.text);
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
scenarioId: scenario.id,
|
|
57
|
+
modelId: this.id,
|
|
58
|
+
steps,
|
|
59
|
+
finalAnswer: answerParts.join("\n"),
|
|
60
|
+
clarificationRequested: false,
|
|
61
|
+
errorCount,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ModelDriver } from "../types.js";
|
|
2
|
+
import { AnthropicDriver } from "./anthropic.js";
|
|
3
|
+
import { BedrockDriver } from "./bedrock.js";
|
|
4
|
+
import { DeterministicDriver } from "./deterministic.js";
|
|
5
|
+
import { OpenAiDriver } from "./openai.js";
|
|
6
|
+
export { AnthropicDriver, BedrockDriver, DeterministicDriver, OpenAiDriver };
|
|
7
|
+
/** Names accepted by `selectDrivers` / the CLI `--driver` flag. */
|
|
8
|
+
export type DriverName = "deterministic" | "anthropic" | "openai" | "bedrock";
|
|
9
|
+
export interface ResolveDriversOptions {
|
|
10
|
+
env?: NodeJS.ProcessEnv;
|
|
11
|
+
/** Include live drivers even if their key/opt-in is absent (they record driverError). */
|
|
12
|
+
includeUnavailable?: boolean;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Resolve the set of model drivers to run.
|
|
16
|
+
*
|
|
17
|
+
* The deterministic control is always included. Claude (Anthropic API) and GPT
|
|
18
|
+
* drivers are added when their API keys are present; the Claude-via-Bedrock driver
|
|
19
|
+
* is added when `HONUA_EVAL_BEDROCK` is set (its auth comes from the AWS credential
|
|
20
|
+
* chain, which can't be sniffed from a single env var). This gates live runs behind
|
|
21
|
+
* env without hardcoding any credentials. CI runs the deterministic driver only —
|
|
22
|
+
* a green, reproducible artifact with no model/API calls.
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolveDrivers(options?: ResolveDriversOptions): ModelDriver[];
|
|
25
|
+
/**
|
|
26
|
+
* Build the driver set for an explicit `--driver` selection. The deterministic
|
|
27
|
+
* control is always included (it is the CI gate). Named live drivers are always
|
|
28
|
+
* constructed and run — even if their key/opt-in is absent — so an explicit
|
|
29
|
+
* request surfaces an honest `driverError` rather than being silently dropped.
|
|
30
|
+
*/
|
|
31
|
+
export declare function selectDrivers(names: DriverName[], options?: ResolveDriversOptions): ModelDriver[];
|
|
32
|
+
/** Parse/validate a `--driver` value into a known driver name. */
|
|
33
|
+
export declare function parseDriverName(value: string): DriverName;
|
|
34
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/eval/drivers/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,mBAAmB,EAAE,YAAY,EAAE,CAAC;AAE7E,mEAAmE;AACnE,MAAM,MAAM,UAAU,GAAG,eAAe,GAAG,WAAW,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE9E,MAAM,WAAW,qBAAqB;IACpC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,yFAAyF;IACzF,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,OAAO,GAAE,qBAA0B,GAAG,WAAW,EAAE,CAwBjF;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,OAAO,GAAE,qBAA0B,GAAG,WAAW,EAAE,CAuBrG;AAED,kEAAkE;AAClE,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAMzD"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { AnthropicDriver } from "./anthropic.js";
|
|
2
|
+
import { BedrockDriver } from "./bedrock.js";
|
|
3
|
+
import { DeterministicDriver } from "./deterministic.js";
|
|
4
|
+
import { OpenAiDriver } from "./openai.js";
|
|
5
|
+
export { AnthropicDriver, BedrockDriver, DeterministicDriver, OpenAiDriver };
|
|
6
|
+
/**
|
|
7
|
+
* Resolve the set of model drivers to run.
|
|
8
|
+
*
|
|
9
|
+
* The deterministic control is always included. Claude (Anthropic API) and GPT
|
|
10
|
+
* drivers are added when their API keys are present; the Claude-via-Bedrock driver
|
|
11
|
+
* is added when `HONUA_EVAL_BEDROCK` is set (its auth comes from the AWS credential
|
|
12
|
+
* chain, which can't be sniffed from a single env var). This gates live runs behind
|
|
13
|
+
* env without hardcoding any credentials. CI runs the deterministic driver only —
|
|
14
|
+
* a green, reproducible artifact with no model/API calls.
|
|
15
|
+
*/
|
|
16
|
+
export function resolveDrivers(options = {}) {
|
|
17
|
+
const env = options.env ?? process.env;
|
|
18
|
+
const drivers = [new DeterministicDriver()];
|
|
19
|
+
const anthropic = new AnthropicDriver({ apiKey: env.ANTHROPIC_API_KEY, model: env.HONUA_EVAL_ANTHROPIC_MODEL });
|
|
20
|
+
if (options.includeUnavailable || anthropic.isAvailable()) {
|
|
21
|
+
drivers.push(anthropic);
|
|
22
|
+
}
|
|
23
|
+
const openai = new OpenAiDriver({ apiKey: env.OPENAI_API_KEY, model: env.OPENAI_MODEL });
|
|
24
|
+
if (options.includeUnavailable || openai.isAvailable()) {
|
|
25
|
+
drivers.push(openai);
|
|
26
|
+
}
|
|
27
|
+
const bedrock = new BedrockDriver({
|
|
28
|
+
enabled: env.HONUA_EVAL_BEDROCK,
|
|
29
|
+
model: env.HONUA_EVAL_BEDROCK_MODEL,
|
|
30
|
+
region: env.AWS_REGION,
|
|
31
|
+
});
|
|
32
|
+
if (options.includeUnavailable || bedrock.isAvailable()) {
|
|
33
|
+
drivers.push(bedrock);
|
|
34
|
+
}
|
|
35
|
+
return drivers;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Build the driver set for an explicit `--driver` selection. The deterministic
|
|
39
|
+
* control is always included (it is the CI gate). Named live drivers are always
|
|
40
|
+
* constructed and run — even if their key/opt-in is absent — so an explicit
|
|
41
|
+
* request surfaces an honest `driverError` rather than being silently dropped.
|
|
42
|
+
*/
|
|
43
|
+
export function selectDrivers(names, options = {}) {
|
|
44
|
+
const env = options.env ?? process.env;
|
|
45
|
+
const drivers = [new DeterministicDriver()];
|
|
46
|
+
for (const name of names) {
|
|
47
|
+
switch (name) {
|
|
48
|
+
case "deterministic":
|
|
49
|
+
break; // already included
|
|
50
|
+
case "anthropic":
|
|
51
|
+
drivers.push(new AnthropicDriver({ apiKey: env.ANTHROPIC_API_KEY, model: env.HONUA_EVAL_ANTHROPIC_MODEL }));
|
|
52
|
+
break;
|
|
53
|
+
case "openai":
|
|
54
|
+
drivers.push(new OpenAiDriver({ apiKey: env.OPENAI_API_KEY, model: env.OPENAI_MODEL }));
|
|
55
|
+
break;
|
|
56
|
+
case "bedrock":
|
|
57
|
+
drivers.push(new BedrockDriver({ enabled: "1", model: env.HONUA_EVAL_BEDROCK_MODEL, region: env.AWS_REGION }));
|
|
58
|
+
break;
|
|
59
|
+
default: {
|
|
60
|
+
const exhaustive = name;
|
|
61
|
+
throw new Error(`unknown driver: ${String(exhaustive)}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return drivers;
|
|
66
|
+
}
|
|
67
|
+
/** Parse/validate a `--driver` value into a known driver name. */
|
|
68
|
+
export function parseDriverName(value) {
|
|
69
|
+
const known = ["deterministic", "anthropic", "openai", "bedrock"];
|
|
70
|
+
if (known.includes(value)) {
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
throw new Error(`unknown --driver "${value}" (expected one of: ${known.join(", ")})`);
|
|
74
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ModelDriver, Scenario, WorkflowContext, WorkflowTranscript } from "../types.js";
|
|
2
|
+
export interface OpenAiDriverOptions {
|
|
3
|
+
apiKey?: string | undefined;
|
|
4
|
+
model?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare class OpenAiDriver implements ModelDriver {
|
|
7
|
+
readonly vendor: "openai";
|
|
8
|
+
readonly id: string;
|
|
9
|
+
private readonly apiKey;
|
|
10
|
+
constructor(options?: OpenAiDriverOptions);
|
|
11
|
+
isAvailable(): boolean;
|
|
12
|
+
runWorkflow(scenario: Scenario, ctx: WorkflowContext): Promise<WorkflowTranscript>;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=openai.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../../../../src/eval/drivers/openai.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAkB,eAAe,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAkD9G,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,YAAa,YAAW,WAAW;IAC9C,QAAQ,CAAC,MAAM,EAAG,QAAQ,CAAU;IACpC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;gBAEhC,OAAO,GAAE,mBAAwB;IAK7C,WAAW,IAAI,OAAO;IAIhB,WAAW,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,kBAAkB,CAAC;CA4EzF"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GPT client driver for the cross-model eval (honua-server #1956).
|
|
3
|
+
*
|
|
4
|
+
* Gated behind OPENAI_API_KEY and a dynamically-imported `openai` SDK — neither
|
|
5
|
+
* is a dependency of this package, so the offline CI path never needs them. This
|
|
6
|
+
* is the "any client" counterpart to the Claude driver: the SAME corpus and the
|
|
7
|
+
* SAME grading are driven by a GPT model over the identical MCP catalog, so the
|
|
8
|
+
* artifact can compare success / clarification / edit rates across vendors.
|
|
9
|
+
*
|
|
10
|
+
* Live cross-model runs are tracked under honua-io/honua-server#1956.
|
|
11
|
+
*/
|
|
12
|
+
const ASK_USER_FUNCTION = {
|
|
13
|
+
type: "function",
|
|
14
|
+
function: {
|
|
15
|
+
name: "ask_user",
|
|
16
|
+
description: "Call this ONLY if the request is too ambiguous to proceed. Provide the clarifying question you would ask.",
|
|
17
|
+
parameters: {
|
|
18
|
+
type: "object",
|
|
19
|
+
properties: { question: { type: "string" } },
|
|
20
|
+
required: ["question"],
|
|
21
|
+
additionalProperties: false,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
const SYSTEM_PROMPT = "You are a GIS analyst working through the Honua MCP tools. Complete the user's task by calling the available tools. " +
|
|
26
|
+
"Ground every step in real tool results — do not invent service names, layers, or fields. " +
|
|
27
|
+
"If, and only if, the request is genuinely ambiguous and you cannot proceed, call ask_user. " +
|
|
28
|
+
"When finished, give a concise final answer that references what the tools returned.";
|
|
29
|
+
const MAX_ITERATIONS = 8;
|
|
30
|
+
/**
|
|
31
|
+
* Default GPT model for live cross-model runs.
|
|
32
|
+
*
|
|
33
|
+
* `gpt-5.5` is OpenAI's generally-available flagship reasoning model as of
|
|
34
|
+
* July 2026 (the apex of the public Chat Completions catalog; see
|
|
35
|
+
* developers.openai.com/api/docs/models/all). The newer GPT-5.6 "Sol" family was
|
|
36
|
+
* still preview-gated to a handful of orgs at that date and not callable by an
|
|
37
|
+
* ordinary API key, so it is NOT the default — set `OPENAI_MODEL=gpt-5.6-sol`
|
|
38
|
+
* (or any other id) to override once you have access. The resolved id is recorded
|
|
39
|
+
* per-driver in the results artifact (`models[].model`) so published comparisons
|
|
40
|
+
* are attributable to an exact model.
|
|
41
|
+
*/
|
|
42
|
+
const DEFAULT_MODEL = "gpt-5.5";
|
|
43
|
+
export class OpenAiDriver {
|
|
44
|
+
vendor = "openai";
|
|
45
|
+
id;
|
|
46
|
+
apiKey;
|
|
47
|
+
constructor(options = {}) {
|
|
48
|
+
this.id = options.model ?? process.env.OPENAI_MODEL ?? DEFAULT_MODEL;
|
|
49
|
+
this.apiKey = options.apiKey ?? process.env.OPENAI_API_KEY;
|
|
50
|
+
}
|
|
51
|
+
isAvailable() {
|
|
52
|
+
return typeof this.apiKey === "string" && this.apiKey.length > 0;
|
|
53
|
+
}
|
|
54
|
+
async runWorkflow(scenario, ctx) {
|
|
55
|
+
const steps = [];
|
|
56
|
+
const transcript = {
|
|
57
|
+
scenarioId: scenario.id,
|
|
58
|
+
modelId: this.id,
|
|
59
|
+
steps,
|
|
60
|
+
finalAnswer: "",
|
|
61
|
+
clarificationRequested: false,
|
|
62
|
+
errorCount: 0,
|
|
63
|
+
};
|
|
64
|
+
let OpenAI;
|
|
65
|
+
try {
|
|
66
|
+
const specifier = "openai";
|
|
67
|
+
const mod = (await import(specifier));
|
|
68
|
+
OpenAI = mod.default;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
transcript.driverError = "openai SDK is not installed (required for live GPT eval runs)";
|
|
72
|
+
return transcript;
|
|
73
|
+
}
|
|
74
|
+
if (!this.apiKey) {
|
|
75
|
+
transcript.driverError = "OPENAI_API_KEY is not set";
|
|
76
|
+
return transcript;
|
|
77
|
+
}
|
|
78
|
+
const client = new OpenAI({ apiKey: this.apiKey });
|
|
79
|
+
const tools = [
|
|
80
|
+
...ctx.tools.map((t) => ({
|
|
81
|
+
type: "function",
|
|
82
|
+
function: { name: t.name, description: t.description, parameters: t.inputSchema },
|
|
83
|
+
})),
|
|
84
|
+
ASK_USER_FUNCTION,
|
|
85
|
+
];
|
|
86
|
+
const messages = [
|
|
87
|
+
{ role: "system", content: SYSTEM_PROMPT },
|
|
88
|
+
{ role: "user", content: scenario.prompt },
|
|
89
|
+
];
|
|
90
|
+
try {
|
|
91
|
+
for (let i = 0; i < MAX_ITERATIONS; i++) {
|
|
92
|
+
const response = await client.chat.completions.create({ model: this.id, messages, tools });
|
|
93
|
+
const message = response.choices[0]?.message;
|
|
94
|
+
if (!message) {
|
|
95
|
+
transcript.driverError = "no choice returned by the model";
|
|
96
|
+
return transcript;
|
|
97
|
+
}
|
|
98
|
+
const toolCalls = message.tool_calls ?? [];
|
|
99
|
+
if (toolCalls.length === 0) {
|
|
100
|
+
transcript.finalAnswer = message.content ?? "";
|
|
101
|
+
return transcript;
|
|
102
|
+
}
|
|
103
|
+
messages.push({ role: "assistant", content: message.content ?? null, tool_calls: toolCalls });
|
|
104
|
+
for (const call of toolCalls) {
|
|
105
|
+
const args = safeParse(call.function.arguments);
|
|
106
|
+
if (call.function.name === "ask_user") {
|
|
107
|
+
transcript.clarificationRequested = true;
|
|
108
|
+
transcript.finalAnswer = message.content ?? "";
|
|
109
|
+
return transcript;
|
|
110
|
+
}
|
|
111
|
+
const result = await ctx.callTool(call.function.name, args);
|
|
112
|
+
steps.push({ tool: call.function.name, args, isError: result.isError });
|
|
113
|
+
if (result.isError) {
|
|
114
|
+
transcript.errorCount++;
|
|
115
|
+
}
|
|
116
|
+
messages.push({ role: "tool", tool_call_id: call.id, content: result.text });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
transcript.driverError = `exceeded ${MAX_ITERATIONS} tool-use iterations without finishing`;
|
|
120
|
+
return transcript;
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
transcript.driverError = err instanceof Error ? err.message : String(err);
|
|
124
|
+
return transcript;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function safeParse(raw) {
|
|
129
|
+
try {
|
|
130
|
+
const parsed = JSON.parse(raw);
|
|
131
|
+
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return {};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Scenario, ScenarioGrade, WorkflowTranscript } from "./types.js";
|
|
2
|
+
/** True if `sequence` appears as an ordered (not necessarily contiguous) subsequence of `steps`. */
|
|
3
|
+
export declare function isOrderedSubsequence(steps: string[], sequence: string[]): boolean;
|
|
4
|
+
/**
|
|
5
|
+
* Grade a transcript against a scenario's declarative success criteria.
|
|
6
|
+
*
|
|
7
|
+
* Outcomes:
|
|
8
|
+
* - `error`: the driver failed to run (unavailable model, exception).
|
|
9
|
+
* - `clarified`: the driver asked a clarifying question instead of completing.
|
|
10
|
+
* - `pass`: all criteria satisfied.
|
|
11
|
+
* - `fail`: completed but violated one or more criteria.
|
|
12
|
+
*/
|
|
13
|
+
export declare function grade(scenario: Scenario, transcript: WorkflowTranscript): ScenarioGrade;
|
|
14
|
+
//# sourceMappingURL=grade.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grade.d.ts","sourceRoot":"","sources":["../../../src/eval/grade.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAE9E,oGAAoG;AACpG,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAQjF;AAED;;;;;;;;GAQG;AACH,wBAAgB,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,kBAAkB,GAAG,aAAa,CAgEvF"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/** True if `sequence` appears as an ordered (not necessarily contiguous) subsequence of `steps`. */
|
|
2
|
+
export function isOrderedSubsequence(steps, sequence) {
|
|
3
|
+
let i = 0;
|
|
4
|
+
for (const step of steps) {
|
|
5
|
+
if (i < sequence.length && step === sequence[i]) {
|
|
6
|
+
i++;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
return i === sequence.length;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Grade a transcript against a scenario's declarative success criteria.
|
|
13
|
+
*
|
|
14
|
+
* Outcomes:
|
|
15
|
+
* - `error`: the driver failed to run (unavailable model, exception).
|
|
16
|
+
* - `clarified`: the driver asked a clarifying question instead of completing.
|
|
17
|
+
* - `pass`: all criteria satisfied.
|
|
18
|
+
* - `fail`: completed but violated one or more criteria.
|
|
19
|
+
*/
|
|
20
|
+
export function grade(scenario, transcript) {
|
|
21
|
+
const base = {
|
|
22
|
+
scenarioId: scenario.id,
|
|
23
|
+
modelId: transcript.modelId,
|
|
24
|
+
errorCount: transcript.errorCount,
|
|
25
|
+
};
|
|
26
|
+
const { criteria } = scenario;
|
|
27
|
+
if (transcript.driverError) {
|
|
28
|
+
return { ...base, outcome: "error", violations: [transcript.driverError] };
|
|
29
|
+
}
|
|
30
|
+
if (transcript.clarificationRequested) {
|
|
31
|
+
// For an ambiguity/refusal scenario, clarifying IS the correct behavior.
|
|
32
|
+
if (criteria.expectClarification) {
|
|
33
|
+
return { ...base, outcome: "pass", violations: [] };
|
|
34
|
+
}
|
|
35
|
+
return { ...base, outcome: "clarified", violations: ["driver requested clarification"] };
|
|
36
|
+
}
|
|
37
|
+
if (criteria.expectClarification) {
|
|
38
|
+
// The prompt was ambiguous/unsupported but the driver answered anyway.
|
|
39
|
+
return { ...base, outcome: "fail", violations: ["expected a clarifying question but the driver completed"] };
|
|
40
|
+
}
|
|
41
|
+
const violations = [];
|
|
42
|
+
const calledTools = transcript.steps.map((s) => s.tool);
|
|
43
|
+
const calledSet = new Set(calledTools);
|
|
44
|
+
for (const required of criteria.requiredTools) {
|
|
45
|
+
if (!calledSet.has(required)) {
|
|
46
|
+
violations.push(`required tool not called: ${required}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (criteria.expectedToolSequence && !isOrderedSubsequence(calledTools, criteria.expectedToolSequence)) {
|
|
50
|
+
violations.push(`expected tool sequence not observed: ${criteria.expectedToolSequence.join(" -> ")}`);
|
|
51
|
+
}
|
|
52
|
+
for (const forbidden of criteria.forbiddenTools ?? []) {
|
|
53
|
+
if (calledSet.has(forbidden)) {
|
|
54
|
+
violations.push(`forbidden tool called: ${forbidden}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const answer = transcript.finalAnswer.toLowerCase();
|
|
58
|
+
for (const fragment of criteria.answerMustInclude ?? []) {
|
|
59
|
+
if (!answer.includes(fragment.toLowerCase())) {
|
|
60
|
+
violations.push(`answer missing expected content: "${fragment}"`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Semantic assertions (issue #369): meaning, not just trajectory.
|
|
64
|
+
for (const fragment of criteria.answerMustNotInclude ?? []) {
|
|
65
|
+
if (answer.includes(fragment.toLowerCase())) {
|
|
66
|
+
violations.push(`answer contains forbidden content: "${fragment}"`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
for (const pattern of criteria.answerMustMatch ?? []) {
|
|
70
|
+
if (!new RegExp(pattern, "i").test(transcript.finalAnswer)) {
|
|
71
|
+
violations.push(`answer did not match required pattern: /${pattern}/i`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return { ...base, outcome: violations.length === 0 ? "pass" : "fail", violations };
|
|
75
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"northstar-corpus.d.ts","sourceRoot":"","sources":["../../../src/eval/northstar-corpus.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAiF3C,eAAO,MAAM,gBAAgB,EAAE,QAAQ,EAyHtC,CAAC"}
|