@ggui-ai/negotiator 0.28.0 → 0.29.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/package.json +11 -5
- package/dist/rerank-eval/run-probe-cli.d.ts +0 -3
- package/dist/rerank-eval/run-probe-cli.d.ts.map +0 -1
- package/dist/rerank-eval/run-probe-cli.js +0 -80
- package/dist/synth-bench/cache-key-probe.d.ts +0 -3
- package/dist/synth-bench/cache-key-probe.d.ts.map +0 -1
- package/dist/synth-bench/cache-key-probe.js +0 -177
- package/dist/synth-bench/cli-llm.d.ts +0 -25
- package/dist/synth-bench/cli-llm.d.ts.map +0 -1
- package/dist/synth-bench/cli-llm.js +0 -124
- package/dist/synth-bench/run-bench-cli.d.ts +0 -3
- package/dist/synth-bench/run-bench-cli.d.ts.map +0 -1
- package/dist/synth-bench/run-bench-cli.js +0 -105
- package/dist/synth-bench/run-repair-bench-cli.d.ts +0 -3
- package/dist/synth-bench/run-repair-bench-cli.d.ts.map +0 -1
- package/dist/synth-bench/run-repair-bench-cli.js +0 -86
- package/src/rerank-eval/run-probe-cli.ts +0 -103
- package/src/synth-bench/cache-key-probe.ts +0 -213
- package/src/synth-bench/cli-llm.ts +0 -168
- package/src/synth-bench/run-bench-cli.ts +0 -135
- package/src/synth-bench/run-repair-bench-cli.ts +0 -119
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ggui-ai/negotiator",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.0",
|
|
4
4
|
"description": "Contract-synthesis + match-judge engine for GGUI's handshake. Synthesizes or repairs a conforming DataContract from an agent's draft, judges blueprint-match candidates for reuse, and validates contract structure + novelty — the primitives composed by decideHandshake in @ggui-ai/mcp-server-handlers. Deployment-agnostic: concrete embedding and vector-store bindings plug in via the storage interfaces from @ggui-ai/mcp-server-core.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"keywords": [
|
|
@@ -35,7 +35,12 @@
|
|
|
35
35
|
"!**/*.test-d.d.ts",
|
|
36
36
|
"!**/__tests__/**",
|
|
37
37
|
"!**/*.spec.ts",
|
|
38
|
-
"!**/*.spec.tsx"
|
|
38
|
+
"!**/*.spec.tsx",
|
|
39
|
+
"!src/synth-bench/cli-llm.ts",
|
|
40
|
+
"!src/synth-bench/run-bench-cli.ts",
|
|
41
|
+
"!src/synth-bench/run-repair-bench-cli.ts",
|
|
42
|
+
"!src/synth-bench/cache-key-probe.ts",
|
|
43
|
+
"!src/rerank-eval/run-probe-cli.ts"
|
|
39
44
|
],
|
|
40
45
|
"type": "module",
|
|
41
46
|
"main": "dist/index.js",
|
|
@@ -47,13 +52,14 @@
|
|
|
47
52
|
}
|
|
48
53
|
},
|
|
49
54
|
"dependencies": {
|
|
50
|
-
"@ggui-ai/
|
|
51
|
-
"@ggui-ai/
|
|
55
|
+
"@ggui-ai/protocol": "0.29.0",
|
|
56
|
+
"@ggui-ai/mcp-server-core": "0.29.0"
|
|
52
57
|
},
|
|
53
58
|
"devDependencies": {
|
|
54
59
|
"@types/node": "^24.0.0",
|
|
55
60
|
"typescript": "^5.0.0",
|
|
56
|
-
"vitest": "^
|
|
61
|
+
"vitest": "^4.1.11",
|
|
62
|
+
"@ggui-ai/ui-gen": "0.29.0"
|
|
57
63
|
},
|
|
58
64
|
"bugs": {
|
|
59
65
|
"url": "https://github.com/ggui-ai/ggui/issues"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"run-probe-cli.d.ts","sourceRoot":"","sources":["../../src/rerank-eval/run-probe-cli.ts"],"names":[],"mappings":""}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Rerank quality probe CLI.
|
|
4
|
-
*
|
|
5
|
-
* Reads `~/.ggui/credentials.json` for the Anthropic API key, runs
|
|
6
|
-
* the probe against `claude-haiku-5-5` (ggui#1804), prints the report. Its readings are not comparable to runs made before the switch.
|
|
7
|
-
*
|
|
8
|
-
* Usage:
|
|
9
|
-
* pnpm -F @ggui-ai/negotiator probe-rerank
|
|
10
|
-
* ANTHROPIC_API_KEY=sk-... pnpm -F @ggui-ai/negotiator probe-rerank
|
|
11
|
-
* pnpm -F @ggui-ai/negotiator probe-rerank -- --limit 5
|
|
12
|
-
*
|
|
13
|
-
* Cost: ~$0.025 for the full 25-pair run with Haiku 4.5.
|
|
14
|
-
*
|
|
15
|
-
* Eval-only — not exported from the package index.
|
|
16
|
-
*/
|
|
17
|
-
import { runProbe, formatReport } from './run-probe.js';
|
|
18
|
-
import { buildAnthropicLlmCaller, getTokenUsage, resolveAnthropicKey, } from '../synth-bench/cli-llm.js';
|
|
19
|
-
const DEFAULT_MODEL = 'claude-haiku-5-5';
|
|
20
|
-
function parseArgs(argv) {
|
|
21
|
-
let limit;
|
|
22
|
-
let threshold;
|
|
23
|
-
let model = DEFAULT_MODEL;
|
|
24
|
-
for (let i = 0; i < argv.length; i++) {
|
|
25
|
-
const a = argv[i];
|
|
26
|
-
if (a === '--limit' && argv[i + 1]) {
|
|
27
|
-
limit = Number(argv[++i]);
|
|
28
|
-
}
|
|
29
|
-
else if (a === '--threshold' && argv[i + 1]) {
|
|
30
|
-
threshold = Number(argv[++i]);
|
|
31
|
-
}
|
|
32
|
-
else if (a === '--model' && argv[i + 1]) {
|
|
33
|
-
model = argv[++i];
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
const result = { model };
|
|
37
|
-
if (limit !== undefined)
|
|
38
|
-
result.limit = limit;
|
|
39
|
-
if (threshold !== undefined)
|
|
40
|
-
result.threshold = threshold;
|
|
41
|
-
return result;
|
|
42
|
-
}
|
|
43
|
-
// Approximate Haiku 4.5 pricing as of 2026-05.
|
|
44
|
-
// Input: $1.00 / Mtok, output: $5.00 / Mtok.
|
|
45
|
-
const HAIKU_4_5_PRICE_INPUT_PER_TOKEN = 1.0 / 1_000_000;
|
|
46
|
-
const HAIKU_4_5_PRICE_OUTPUT_PER_TOKEN = 5.0 / 1_000_000;
|
|
47
|
-
async function main() {
|
|
48
|
-
const args = parseArgs(process.argv.slice(2));
|
|
49
|
-
const apiKey = resolveAnthropicKey('probe-rerank');
|
|
50
|
-
const llm = buildAnthropicLlmCaller(apiKey, args.model);
|
|
51
|
-
process.stdout.write(`probe: model=${args.model}\n\n`);
|
|
52
|
-
const report = await runProbe({ llm }, {
|
|
53
|
-
...(args.limit !== undefined ? { limit: args.limit } : {}),
|
|
54
|
-
...(args.threshold !== undefined ? { threshold: args.threshold } : {}),
|
|
55
|
-
onProgress: (outcome, idx, total) => {
|
|
56
|
-
const status = outcome.correct ? 'OK ' : 'NO ';
|
|
57
|
-
const conf = outcome.decision.confidence.toFixed(2);
|
|
58
|
-
const id = outcome.pair.id.padEnd(24);
|
|
59
|
-
process.stdout.write(`[${status}] ${(idx + 1).toString().padStart(2)}/${total} ${id} conf=${conf} pred=${outcome.predictedMatchId ?? 'null'} gold=${outcome.pair.goldMatchId ?? 'null'}\n`);
|
|
60
|
-
},
|
|
61
|
-
});
|
|
62
|
-
process.stdout.write('\n');
|
|
63
|
-
process.stdout.write(formatReport(report));
|
|
64
|
-
process.stdout.write('\n\n');
|
|
65
|
-
const usage = getTokenUsage();
|
|
66
|
-
const totalCost = usage.input * HAIKU_4_5_PRICE_INPUT_PER_TOKEN +
|
|
67
|
-
usage.output * HAIKU_4_5_PRICE_OUTPUT_PER_TOKEN;
|
|
68
|
-
const callsMade = report.outcomes.filter(
|
|
69
|
-
// ggui#1235 — `reason` is optional on the seam; a judge with no prose
|
|
70
|
-
// still made a call.
|
|
71
|
-
(o) => !/short-circuited/.test(o.decision.reason ?? '')).length;
|
|
72
|
-
const costPerCall = callsMade === 0 ? 0 : totalCost / callsMade;
|
|
73
|
-
process.stdout.write(`Tokens: input=${usage.input} · output=${usage.output}\n`);
|
|
74
|
-
process.stdout.write(`Cost: total=$${totalCost.toFixed(4)} · per-call=$${costPerCall.toFixed(4)}\n`);
|
|
75
|
-
process.stdout.write(` G4 cost ≤ $0.002/call → ${costPerCall <= 0.002 ? 'PASS' : 'FAIL'} ($${costPerCall.toFixed(4)})\n`);
|
|
76
|
-
}
|
|
77
|
-
main().catch((err) => {
|
|
78
|
-
process.stderr.write(`probe-rerank failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
79
|
-
process.exit(1);
|
|
80
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cache-key-probe.d.ts","sourceRoot":"","sources":["../../src/synth-bench/cache-key-probe.ts"],"names":[],"mappings":""}
|
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Blueprint cache-key stability probe.
|
|
4
|
-
*
|
|
5
|
-
* The handshake blueprint cache hits ONLY on exact canonical-contract
|
|
6
|
-
* equality: `blueprintKey(contract)` = sha256 of `canonicalizeContracts`
|
|
7
|
-
* (RFC 8785), which preserves spec placement, action/slot/tool NAMES and
|
|
8
|
-
* schema shapes (it only normalizes key order / whitespace and strips
|
|
9
|
-
* `description`/`usage`). The semantic/fuzzy match is disabled whenever a
|
|
10
|
-
* contract is present. So two requests for the SAME intent reuse a cached
|
|
11
|
-
* blueprint only if the LLM authored a byte-identical canonical contract
|
|
12
|
-
* both times.
|
|
13
|
-
*
|
|
14
|
-
* This probe synthesizes one intent N times and reports:
|
|
15
|
-
* 1. how many DISTINCT blueprintKeys result (distinct > 1 ⇒ the
|
|
16
|
-
* exact-key cache structurally cannot hit on re-runs);
|
|
17
|
-
* 2. the KEY-AFFECTING diff — every leaf path where the runs disagree
|
|
18
|
-
* (descriptions/usage stripped, since the key ignores them) — so we
|
|
19
|
-
* can see WHETHER the variance is trivial (normalizable: required
|
|
20
|
-
* order, additionalProperties, optional fields) or semantic
|
|
21
|
-
* (property names / structure → only a semantic match can bridge it).
|
|
22
|
-
*
|
|
23
|
-
* Usage:
|
|
24
|
-
* pnpm -F @ggui-ai/negotiator cache-probe -- --intent "my todo items" --n 5
|
|
25
|
-
* pnpm -F @ggui-ai/negotiator cache-probe -- --n 5 --dump /tmp/cache-probe.json
|
|
26
|
-
*/
|
|
27
|
-
import { writeFileSync } from 'node:fs';
|
|
28
|
-
import { blueprintKey } from '@ggui-ai/protocol/blueprint-key';
|
|
29
|
-
import { synthesizeContract } from '../synthesize-contract.js';
|
|
30
|
-
import { DEFAULT_MODEL, buildAnthropicLlmCaller, resolveAnthropicKey, } from './cli-llm.js';
|
|
31
|
-
function parseArgs(argv) {
|
|
32
|
-
let intent = 'my todo items';
|
|
33
|
-
let n = 3;
|
|
34
|
-
let model = DEFAULT_MODEL;
|
|
35
|
-
let dump;
|
|
36
|
-
for (let i = 0; i < argv.length; i++) {
|
|
37
|
-
const a = argv[i];
|
|
38
|
-
if (a === '--intent' && argv[i + 1])
|
|
39
|
-
intent = argv[++i];
|
|
40
|
-
else if (a === '--n' && argv[i + 1])
|
|
41
|
-
n = Number(argv[++i]);
|
|
42
|
-
else if (a === '--model' && argv[i + 1])
|
|
43
|
-
model = argv[++i];
|
|
44
|
-
else if (a === '--dump' && argv[i + 1])
|
|
45
|
-
dump = argv[++i];
|
|
46
|
-
}
|
|
47
|
-
return { intent, n, model, ...(dump !== undefined ? { dump } : {}) };
|
|
48
|
-
}
|
|
49
|
-
/** One-line canonical-surface summary — the bits blueprintKey is sensitive to. */
|
|
50
|
-
function shapeSummary(c) {
|
|
51
|
-
const parts = [];
|
|
52
|
-
const props = Object.keys(c.propsSpec?.properties ?? {});
|
|
53
|
-
const ctx = Object.keys(c.contextSpec ?? {});
|
|
54
|
-
const act = Object.keys(c.actionSpec ?? {});
|
|
55
|
-
const str = Object.keys(c.streamSpec ?? {});
|
|
56
|
-
const tools = Object.keys(c.agentCapabilities?.tools ?? {});
|
|
57
|
-
if (props.length)
|
|
58
|
-
parts.push(`props{${props.join(',')}}`);
|
|
59
|
-
if (ctx.length)
|
|
60
|
-
parts.push(`context{${ctx.join(',')}}`);
|
|
61
|
-
if (act.length)
|
|
62
|
-
parts.push(`action{${act.join(',')}}`);
|
|
63
|
-
if (str.length)
|
|
64
|
-
parts.push(`stream{${str.join(',')}}`);
|
|
65
|
-
if (tools.length)
|
|
66
|
-
parts.push(`tools{${tools.join(',')}}`);
|
|
67
|
-
return parts.join(' ') || '(empty)';
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Flatten a contract to `path → json-encoded leaf`, applying the same
|
|
71
|
-
* domain rule the cache key does: drop string-valued `description` /
|
|
72
|
-
* `usage` (informational — `canonicalizeContracts` strips them). Objects
|
|
73
|
-
* recurse; arrays are encoded whole (order is key-affecting under JCS,
|
|
74
|
-
* which sorts object keys but NOT array elements).
|
|
75
|
-
*/
|
|
76
|
-
function flattenKeyAffecting(value, prefix, out) {
|
|
77
|
-
if (Array.isArray(value)) {
|
|
78
|
-
out.set(prefix, JSON.stringify(value));
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
if (value !== null && typeof value === 'object') {
|
|
82
|
-
for (const [k, v] of Object.entries(value)) {
|
|
83
|
-
if ((k === 'description' || k === 'usage') && typeof v === 'string') {
|
|
84
|
-
continue; // stripped by the cache key
|
|
85
|
-
}
|
|
86
|
-
flattenKeyAffecting(v, prefix ? `${prefix}.${k}` : k, out);
|
|
87
|
-
}
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
out.set(prefix, JSON.stringify(value));
|
|
91
|
-
}
|
|
92
|
-
/** Every leaf path where the N contracts do not all agree, with the
|
|
93
|
-
* distinct per-run values (or `∅` when a run omits the path). */
|
|
94
|
-
function crossRunDiff(contracts) {
|
|
95
|
-
const flat = contracts.map((c) => {
|
|
96
|
-
const m = new Map();
|
|
97
|
-
flattenKeyAffecting(c, '', m);
|
|
98
|
-
return m;
|
|
99
|
-
});
|
|
100
|
-
const allPaths = new Set();
|
|
101
|
-
for (const m of flat)
|
|
102
|
-
for (const p of m.keys())
|
|
103
|
-
allPaths.add(p);
|
|
104
|
-
const divergent = [];
|
|
105
|
-
for (const path of [...allPaths].sort()) {
|
|
106
|
-
const values = flat.map((m) => m.get(path) ?? '∅');
|
|
107
|
-
if (new Set(values).size > 1)
|
|
108
|
-
divergent.push({ path, values });
|
|
109
|
-
}
|
|
110
|
-
return divergent;
|
|
111
|
-
}
|
|
112
|
-
async function main() {
|
|
113
|
-
const args = parseArgs(process.argv.slice(2));
|
|
114
|
-
const llm = buildAnthropicLlmCaller(resolveAnthropicKey('cache-probe'), args.model);
|
|
115
|
-
process.stdout.write(`cache-probe: intent=${JSON.stringify(args.intent)} n=${args.n} model=${args.model}\n\n`);
|
|
116
|
-
const contracts = [];
|
|
117
|
-
const keys = [];
|
|
118
|
-
for (let i = 0; i < args.n; i++) {
|
|
119
|
-
const synth = await synthesizeContract({ llm }, args.intent);
|
|
120
|
-
if (synth.contract === null) {
|
|
121
|
-
process.stdout.write(` run ${i + 1}: synth declined — ${synth.reason}\n`);
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
|
-
const key = blueprintKey(synth.contract);
|
|
125
|
-
contracts.push(synth.contract);
|
|
126
|
-
keys.push(key);
|
|
127
|
-
process.stdout.write(` run ${i + 1}: ${key} ${shapeSummary(synth.contract)}\n`);
|
|
128
|
-
}
|
|
129
|
-
const distinct = new Set(keys);
|
|
130
|
-
process.stdout.write(`\nDistinct blueprintKeys: ${distinct.size}/${keys.length}\n`);
|
|
131
|
-
if (distinct.size <= 1 && keys.length > 1) {
|
|
132
|
-
process.stdout.write('STABLE → the exact-key cache WOULD hit on a re-run of this intent.\n');
|
|
133
|
-
}
|
|
134
|
-
else if (keys.length > 1) {
|
|
135
|
-
process.stdout.write('UNSTABLE → same intent yields different canonical contracts → the exact-key\n' +
|
|
136
|
-
'cache cannot hit; each request cold-gens a new blueprint.\n');
|
|
137
|
-
}
|
|
138
|
-
// Key-affecting cross-run diff — the "what varies" the user asked for.
|
|
139
|
-
if (contracts.length > 1) {
|
|
140
|
-
const diff = crossRunDiff(contracts);
|
|
141
|
-
process.stdout.write(`\n=== KEY-AFFECTING DIFF (${diff.length} divergent paths; descriptions stripped) ===\n`);
|
|
142
|
-
for (const { path, values } of diff) {
|
|
143
|
-
process.stdout.write(` ${path}\n`);
|
|
144
|
-
values.forEach((v, i) => {
|
|
145
|
-
const capped = v.length > 100 ? `${v.slice(0, 100)}…` : v;
|
|
146
|
-
process.stdout.write(` run${i + 1}: ${capped}\n`);
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
// Heuristic taxonomy hint.
|
|
150
|
-
const isArrayOrderOnly = diff.every(({ values }) => {
|
|
151
|
-
const parsed = values
|
|
152
|
-
.filter((v) => v !== '∅')
|
|
153
|
-
.map((v) => {
|
|
154
|
-
try {
|
|
155
|
-
return JSON.stringify([...JSON.parse(v)].sort());
|
|
156
|
-
}
|
|
157
|
-
catch {
|
|
158
|
-
return v;
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
|
-
return new Set(parsed).size === 1;
|
|
162
|
-
});
|
|
163
|
-
process.stdout.write(`\nHint: ${diff.length === 0
|
|
164
|
-
? 'no key-affecting differences — variance must be elsewhere'
|
|
165
|
-
: isArrayOrderOnly
|
|
166
|
-
? 'ALL divergent paths are arrays differing only in ORDER → a canonical sort in canonicalizeContracts would collapse these keys (cheap fix, no semantic match needed).'
|
|
167
|
-
: 'divergent paths include non-array-order differences (names / types / structure / presence) → a semantic intent-match is needed to bridge them (canonicalization alone cannot).'}\n`);
|
|
168
|
-
}
|
|
169
|
-
if (args.dump !== undefined) {
|
|
170
|
-
writeFileSync(args.dump, JSON.stringify({ intent: args.intent, model: args.model, keys, contracts }, null, 2));
|
|
171
|
-
process.stdout.write(`\nDumped ${contracts.length} contracts → ${args.dump}\n`);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
main().catch((err) => {
|
|
175
|
-
process.stderr.write(`cache-probe failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
176
|
-
process.exit(1);
|
|
177
|
-
});
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import type { LLMCaller } from '../llm-caller.js';
|
|
2
|
-
/** Default bench model — the model the `ui-gen-default` seed generator declares. */
|
|
3
|
-
export declare const DEFAULT_MODEL = "claude-haiku-5-5";
|
|
4
|
-
/** Haiku 4.5 token pricing (USD per token) for the cost report. */
|
|
5
|
-
export declare const HAIKU_4_5_PRICE_INPUT_PER_TOKEN: number;
|
|
6
|
-
export declare const HAIKU_4_5_PRICE_OUTPUT_PER_TOKEN: number;
|
|
7
|
-
/**
|
|
8
|
-
* Resolve the Anthropic API key: `ANTHROPIC_API_KEY` env var first, then
|
|
9
|
-
* `~/.ggui/credentials.json` at `apps.global.anthropic`. `scriptName`
|
|
10
|
-
* prefixes the error hints so a failure names the bench that needs it.
|
|
11
|
-
*/
|
|
12
|
-
export declare function resolveAnthropicKey(scriptName: string): string;
|
|
13
|
-
/** Running token usage accumulated across `callStructured` calls in this
|
|
14
|
-
* process — read after a run for the cost line. */
|
|
15
|
-
export declare function getTokenUsage(): {
|
|
16
|
-
readonly input: number;
|
|
17
|
-
readonly output: number;
|
|
18
|
-
};
|
|
19
|
-
/**
|
|
20
|
-
* The dev caller the synth benches and the rerank probe CLI share
|
|
21
|
-
* (Haiku-pinned by default; every CLI takes `--model`). Text mode is not
|
|
22
|
-
* exercised — every consumer uses `callStructured`.
|
|
23
|
-
*/
|
|
24
|
-
export declare function buildAnthropicLlmCaller(apiKey: string, model: string): LLMCaller;
|
|
25
|
-
//# sourceMappingURL=cli-llm.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cli-llm.d.ts","sourceRoot":"","sources":["../../src/synth-bench/cli-llm.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,SAAS,EAAc,MAAM,kBAAkB,CAAC;AAK9D,oFAAoF;AACpF,eAAO,MAAM,aAAa,qBAAqB,CAAC;AAEhD,mEAAmE;AACnE,eAAO,MAAM,+BAA+B,QAAkB,CAAC;AAC/D,eAAO,MAAM,gCAAgC,QAAkB,CAAC;AAMhE;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAmB9D;AAmBD;oDACoD;AACpD,wBAAgB,aAAa,IAAI;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,CAEA;AAUD;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GACZ,SAAS,CAyEX"}
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared CLI LLM plumbing for the synth-bench family of live probes
|
|
3
|
-
* (bench-synth, bench-repair). Builds an Anthropic-backed
|
|
4
|
-
* {@link LLMCaller} with forced tool-use, resolves the API key from env
|
|
5
|
-
* or `~/.ggui/credentials.json`, and accumulates token usage for the
|
|
6
|
-
* per-run cost line. Bench-only — not exported from the package index.
|
|
7
|
-
*/
|
|
8
|
-
import { readFileSync } from 'node:fs';
|
|
9
|
-
import { homedir } from 'node:os';
|
|
10
|
-
import { resolve as pathResolve } from 'node:path';
|
|
11
|
-
import { anthropicRejectsForcedToolChoice } from '@ggui-ai/protocol';
|
|
12
|
-
const ANTHROPIC_API = 'https://api.anthropic.com/v1/messages';
|
|
13
|
-
/** Default bench model — the model the `ui-gen-default` seed generator declares. */
|
|
14
|
-
export const DEFAULT_MODEL = 'claude-haiku-5-5';
|
|
15
|
-
/** Haiku 4.5 token pricing (USD per token) for the cost report. */
|
|
16
|
-
export const HAIKU_4_5_PRICE_INPUT_PER_TOKEN = 1.0 / 1_000_000;
|
|
17
|
-
export const HAIKU_4_5_PRICE_OUTPUT_PER_TOKEN = 5.0 / 1_000_000;
|
|
18
|
-
/**
|
|
19
|
-
* Resolve the Anthropic API key: `ANTHROPIC_API_KEY` env var first, then
|
|
20
|
-
* `~/.ggui/credentials.json` at `apps.global.anthropic`. `scriptName`
|
|
21
|
-
* prefixes the error hints so a failure names the bench that needs it.
|
|
22
|
-
*/
|
|
23
|
-
export function resolveAnthropicKey(scriptName) {
|
|
24
|
-
const envKey = process.env['ANTHROPIC_API_KEY'];
|
|
25
|
-
if (envKey && envKey.length > 0)
|
|
26
|
-
return envKey;
|
|
27
|
-
const credsPath = pathResolve(homedir(), '.ggui', 'credentials.json');
|
|
28
|
-
let parsed;
|
|
29
|
-
try {
|
|
30
|
-
parsed = JSON.parse(readFileSync(credsPath, 'utf8'));
|
|
31
|
-
}
|
|
32
|
-
catch (err) {
|
|
33
|
-
throw new Error(`${scriptName}: could not read ${credsPath} (${err instanceof Error ? err.message : String(err)}). Set ANTHROPIC_API_KEY env var or run \`ggui auth set anthropic\`.`);
|
|
34
|
-
}
|
|
35
|
-
const key = parsed.apps?.global?.anthropic;
|
|
36
|
-
if (typeof key !== 'string' || key.length === 0) {
|
|
37
|
-
throw new Error(`${scriptName}: no anthropic key found at apps.global.anthropic in ${credsPath}.`);
|
|
38
|
-
}
|
|
39
|
-
return key;
|
|
40
|
-
}
|
|
41
|
-
let totalInputTokens = 0;
|
|
42
|
-
let totalOutputTokens = 0;
|
|
43
|
-
/** Running token usage accumulated across `callStructured` calls in this
|
|
44
|
-
* process — read after a run for the cost line. */
|
|
45
|
-
export function getTokenUsage() {
|
|
46
|
-
return { input: totalInputTokens, output: totalOutputTokens };
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Thinking budget added to the caller's answer budget for a model that
|
|
50
|
-
* refuses a forced tool (ggui#1264). Same value and reason as the
|
|
51
|
-
* negotiator's production caller in `@ggui-ai/mcp-server`; the three
|
|
52
|
-
* copies are consolidated under ggui#1271.
|
|
53
|
-
*/
|
|
54
|
-
const ALWAYS_THINKING_HEADROOM_TOKENS = 16_000;
|
|
55
|
-
/**
|
|
56
|
-
* The dev caller the synth benches and the rerank probe CLI share
|
|
57
|
-
* (Haiku-pinned by default; every CLI takes `--model`). Text mode is not
|
|
58
|
-
* exercised — every consumer uses `callStructured`.
|
|
59
|
-
*/
|
|
60
|
-
export function buildAnthropicLlmCaller(apiKey, model) {
|
|
61
|
-
return {
|
|
62
|
-
async call() {
|
|
63
|
-
throw new Error('negotiator dev caller: text-mode not exercised — use callStructured');
|
|
64
|
-
},
|
|
65
|
-
async callStructured(systemPrompt, userMessage, tool, maxTokens) {
|
|
66
|
-
// `temperature` deprecated on Haiku 4.5+ — Anthropic rejects with
|
|
67
|
-
// HTTP 400. Residual stochasticity stays bounded via canonical-key
|
|
68
|
-
// normalization downstream.
|
|
69
|
-
//
|
|
70
|
-
// ggui#1264 — a forced `tool_choice` only where the model accepts
|
|
71
|
-
// one. The always-thinking models (`@ggui-ai/protocol`
|
|
72
|
-
// `anthropicRejectsForcedToolChoice`) 400 on it, so they get
|
|
73
|
-
// `auto` + at most one call + the tool named in the system prompt,
|
|
74
|
-
// and thinking headroom on top of the answer budget (their thinking
|
|
75
|
-
// counts against `max_tokens` and comes first).
|
|
76
|
-
const refusesForcedTool = anthropicRejectsForcedToolChoice(model);
|
|
77
|
-
const answerBudget = maxTokens ?? 1024;
|
|
78
|
-
const body = {
|
|
79
|
-
model,
|
|
80
|
-
max_tokens: refusesForcedTool
|
|
81
|
-
? answerBudget + ALWAYS_THINKING_HEADROOM_TOKENS
|
|
82
|
-
: answerBudget,
|
|
83
|
-
system: refusesForcedTool
|
|
84
|
-
? `${systemPrompt}\n\nAnswer by calling the \`${tool.name}\` tool exactly once. Do not answer in text.`
|
|
85
|
-
: systemPrompt,
|
|
86
|
-
messages: [{ role: 'user', content: userMessage }],
|
|
87
|
-
tools: [
|
|
88
|
-
{
|
|
89
|
-
name: tool.name,
|
|
90
|
-
description: tool.description,
|
|
91
|
-
input_schema: tool.input_schema,
|
|
92
|
-
},
|
|
93
|
-
],
|
|
94
|
-
tool_choice: refusesForcedTool
|
|
95
|
-
? { type: 'auto', disable_parallel_tool_use: true }
|
|
96
|
-
: { type: 'tool', name: tool.name },
|
|
97
|
-
};
|
|
98
|
-
const res = await fetch(ANTHROPIC_API, {
|
|
99
|
-
method: 'POST',
|
|
100
|
-
headers: {
|
|
101
|
-
'content-type': 'application/json',
|
|
102
|
-
'x-api-key': apiKey,
|
|
103
|
-
'anthropic-version': '2023-06-01',
|
|
104
|
-
},
|
|
105
|
-
body: JSON.stringify(body),
|
|
106
|
-
});
|
|
107
|
-
const json = (await res.json());
|
|
108
|
-
if (!res.ok) {
|
|
109
|
-
const errType = json.error?.type ?? 'unknown';
|
|
110
|
-
const errMsg = json.error?.message ?? `HTTP ${res.status}`;
|
|
111
|
-
throw new Error(`anthropic ${errType}: ${errMsg}`);
|
|
112
|
-
}
|
|
113
|
-
if (json.usage) {
|
|
114
|
-
totalInputTokens += json.usage.input_tokens ?? 0;
|
|
115
|
-
totalOutputTokens += json.usage.output_tokens ?? 0;
|
|
116
|
-
}
|
|
117
|
-
const toolBlock = json.content?.find((b) => b.type === 'tool_use');
|
|
118
|
-
if (!toolBlock || toolBlock.input === undefined) {
|
|
119
|
-
throw new Error(`anthropic: no tool_use block in response (stop_reason=${json.stop_reason ?? 'unknown'})`);
|
|
120
|
-
}
|
|
121
|
-
return toolBlock.input;
|
|
122
|
-
},
|
|
123
|
-
};
|
|
124
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"run-bench-cli.d.ts","sourceRoot":"","sources":["../../src/synth-bench/run-bench-cli.ts"],"names":[],"mappings":""}
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Synthesizer bench CLI — live LLM probe.
|
|
4
|
-
*
|
|
5
|
-
* Reads ~/.ggui/credentials.json for the Anthropic API key, runs
|
|
6
|
-
* synthesizeContract over BENCH_CORPUS, prints per-shape precision
|
|
7
|
-
* + the redundant-action firing count + p50/p95 latency.
|
|
8
|
-
*
|
|
9
|
-
* Costs ~$0.001 per entry on Haiku 4.5 → roughly $0.05 for the full
|
|
10
|
-
* 50+ corpus run. NOT run in CI; opt-in only.
|
|
11
|
-
*
|
|
12
|
-
* Usage:
|
|
13
|
-
* pnpm -F @ggui-ai/negotiator bench-synth
|
|
14
|
-
* pnpm -F @ggui-ai/negotiator bench-synth -- --limit 10
|
|
15
|
-
* pnpm -F @ggui-ai/negotiator bench-synth -- --shape context+action
|
|
16
|
-
* ANTHROPIC_API_KEY=sk-... pnpm -F @ggui-ai/negotiator bench-synth
|
|
17
|
-
* pnpm -F @ggui-ai/negotiator bench-synth -- --json > report.json
|
|
18
|
-
*
|
|
19
|
-
* Bench-only — not exported from the package index.
|
|
20
|
-
*/
|
|
21
|
-
import { evaluateAgainstCorpus, formatBenchReport } from './run-bench.js';
|
|
22
|
-
import { contractShape } from './corpus.js';
|
|
23
|
-
import { DEFAULT_MODEL, HAIKU_4_5_PRICE_INPUT_PER_TOKEN, HAIKU_4_5_PRICE_OUTPUT_PER_TOKEN, buildAnthropicLlmCaller, getTokenUsage, resolveAnthropicKey, } from './cli-llm.js';
|
|
24
|
-
const SHAPE_FILTERS = [
|
|
25
|
-
'props-only',
|
|
26
|
-
'context-only',
|
|
27
|
-
'context+action',
|
|
28
|
-
'stream',
|
|
29
|
-
'with-gadgets',
|
|
30
|
-
'empty',
|
|
31
|
-
];
|
|
32
|
-
function parseArgs(argv) {
|
|
33
|
-
let limit;
|
|
34
|
-
let shapeFilter;
|
|
35
|
-
let model = DEFAULT_MODEL;
|
|
36
|
-
let json = false;
|
|
37
|
-
for (let i = 0; i < argv.length; i++) {
|
|
38
|
-
const a = argv[i];
|
|
39
|
-
if (a === '--limit' && argv[i + 1]) {
|
|
40
|
-
limit = Number(argv[++i]);
|
|
41
|
-
}
|
|
42
|
-
else if (a === '--shape' && argv[i + 1]) {
|
|
43
|
-
const v = argv[++i];
|
|
44
|
-
if (SHAPE_FILTERS.includes(v)) {
|
|
45
|
-
shapeFilter = v;
|
|
46
|
-
}
|
|
47
|
-
else {
|
|
48
|
-
throw new Error(`unknown shape: ${v} (expected one of ${SHAPE_FILTERS.join(', ')})`);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
else if (a === '--model' && argv[i + 1]) {
|
|
52
|
-
model = argv[++i];
|
|
53
|
-
}
|
|
54
|
-
else if (a === '--json') {
|
|
55
|
-
json = true;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
const result = { model, json };
|
|
59
|
-
if (limit !== undefined)
|
|
60
|
-
result.limit = limit;
|
|
61
|
-
if (shapeFilter !== undefined)
|
|
62
|
-
result.shapeFilter = shapeFilter;
|
|
63
|
-
return result;
|
|
64
|
-
}
|
|
65
|
-
async function main() {
|
|
66
|
-
const args = parseArgs(process.argv.slice(2));
|
|
67
|
-
const apiKey = resolveAnthropicKey('bench-synth');
|
|
68
|
-
const llm = buildAnthropicLlmCaller(apiKey, args.model);
|
|
69
|
-
if (!args.json) {
|
|
70
|
-
process.stdout.write(`bench-synth: model=${args.model}\n\n`);
|
|
71
|
-
}
|
|
72
|
-
const report = await evaluateAgainstCorpus({ llm }, {
|
|
73
|
-
...(args.limit !== undefined ? { limit: args.limit } : {}),
|
|
74
|
-
...(args.shapeFilter !== undefined
|
|
75
|
-
? { shapeFilter: args.shapeFilter }
|
|
76
|
-
: {}),
|
|
77
|
-
onProgress: args.json
|
|
78
|
-
? undefined
|
|
79
|
-
: (outcome, idx, total) => {
|
|
80
|
-
const status = outcome.score.pass ? 'OK ' : 'NO ';
|
|
81
|
-
const id = outcome.entry.id.padEnd(28);
|
|
82
|
-
const shape = contractShape(outcome.entry.expected).padEnd(16);
|
|
83
|
-
process.stdout.write(`[${status}] ${(idx + 1).toString().padStart(2)}/${total} ${shape} ${id} ${outcome.latencyMs}ms\n`);
|
|
84
|
-
},
|
|
85
|
-
});
|
|
86
|
-
if (args.json) {
|
|
87
|
-
process.stdout.write(JSON.stringify(report, null, 2));
|
|
88
|
-
process.stdout.write('\n');
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
process.stdout.write('\n');
|
|
92
|
-
process.stdout.write(formatBenchReport(report));
|
|
93
|
-
process.stdout.write('\n\n');
|
|
94
|
-
const usage = getTokenUsage();
|
|
95
|
-
const totalCost = usage.input * HAIKU_4_5_PRICE_INPUT_PER_TOKEN +
|
|
96
|
-
usage.output * HAIKU_4_5_PRICE_OUTPUT_PER_TOKEN;
|
|
97
|
-
const callsMade = report.totals.all - report.totals.synthDeclined;
|
|
98
|
-
const costPerCall = callsMade === 0 ? 0 : totalCost / callsMade;
|
|
99
|
-
process.stdout.write(`Tokens: input=${usage.input} output=${usage.output}\n`);
|
|
100
|
-
process.stdout.write(`Cost: total=$${totalCost.toFixed(4)} per-call=$${costPerCall.toFixed(4)}\n`);
|
|
101
|
-
}
|
|
102
|
-
main().catch((err) => {
|
|
103
|
-
process.stderr.write(`bench-synth failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
104
|
-
process.exit(1);
|
|
105
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"run-repair-bench-cli.d.ts","sourceRoot":"","sources":["../../src/synth-bench/run-repair-bench-cli.ts"],"names":[],"mappings":""}
|