@tangle-network/agent-eval 0.184.0 → 0.185.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.
Files changed (107) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/adapters/http.d.ts +1 -1
  3. package/dist/analyst/index.d.ts +6 -331
  4. package/dist/analyst/index.d.ts.map +1 -1
  5. package/dist/analyst/index.js +7 -6
  6. package/dist/analyst/index.js.map +1 -1
  7. package/dist/analyze-runs-CrYhqXop.d.ts +75 -0
  8. package/dist/analyze-runs-CrYhqXop.d.ts.map +1 -0
  9. package/dist/{define-agent-eval-RTekxhUE.js → analyze-runs-sTygaDyD.js} +4 -811
  10. package/dist/analyze-runs-sTygaDyD.js.map +1 -0
  11. package/dist/{benchmark-command-DlSePljH.js → benchmark-command-kvq7yFY4.js} +9 -47
  12. package/dist/benchmark-command-kvq7yFY4.js.map +1 -0
  13. package/dist/benchmarks/index.js +2 -2
  14. package/dist/campaign/index.js +7 -6
  15. package/dist/{campaign-D6puiGFO.js → campaign-eJgtRs-W.js} +8 -7
  16. package/dist/{campaign-D6puiGFO.js.map → campaign-eJgtRs-W.js.map} +1 -1
  17. package/dist/{campaign-evidence-aWEmfROB.js → campaign-evidence-BsrCmBSE.js} +4 -255
  18. package/dist/campaign-evidence-BsrCmBSE.js.map +1 -0
  19. package/dist/cli.js +1 -1
  20. package/dist/contract/index.d.ts +3 -3
  21. package/dist/contract/index.js +10 -279
  22. package/dist/contract/index.js.map +1 -1
  23. package/dist/{default-registry-DYB4aHVn.d.ts → default-registry-CXv9kHMt.d.ts} +3 -3
  24. package/dist/{default-registry-DYB4aHVn.d.ts.map → default-registry-CXv9kHMt.d.ts.map} +1 -1
  25. package/dist/{default-registry-CpLko4jB.js → default-registry-xiHrldW8.js} +3 -2
  26. package/dist/{default-registry-CpLko4jB.js.map → default-registry-xiHrldW8.js.map} +1 -1
  27. package/dist/define-agent-eval-D4uumjl3.js +813 -0
  28. package/dist/define-agent-eval-D4uumjl3.js.map +1 -0
  29. package/dist/diagnosis/index.d.ts +319 -0
  30. package/dist/diagnosis/index.d.ts.map +1 -0
  31. package/dist/diagnosis/index.js +1328 -0
  32. package/dist/diagnosis/index.js.map +1 -0
  33. package/dist/{dspy-rlm-engine-Da-l3CpD.js → dspy-rlm-engine-BE9lCzzL.js} +2 -2
  34. package/dist/{dspy-rlm-engine-Da-l3CpD.js.map → dspy-rlm-engine-BE9lCzzL.js.map} +1 -1
  35. package/dist/{engine-D1ykrAKR.d.ts → engine-cXUK-WHS.d.ts} +3 -75
  36. package/dist/engine-cXUK-WHS.d.ts.map +1 -0
  37. package/dist/equal-terms-Blx9Zk5I.js +25 -0
  38. package/dist/equal-terms-Blx9Zk5I.js.map +1 -0
  39. package/dist/experiment/index.js +4 -3
  40. package/dist/experiment/index.js.map +1 -1
  41. package/dist/{index-58wex7KV.d.ts → index-BQWfAFeH.d.ts} +3 -3
  42. package/dist/{index-58wex7KV.d.ts.map → index-BQWfAFeH.d.ts.map} +1 -1
  43. package/dist/{index-DVrtLO5j.d.ts → index-C8_iU8KL.d.ts} +3 -3
  44. package/dist/{index-DVrtLO5j.d.ts.map → index-C8_iU8KL.d.ts.map} +1 -1
  45. package/dist/index.d.ts +5 -4
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +12 -11
  48. package/dist/index.js.map +1 -1
  49. package/dist/{kind-factory-DE088Zhv.js → kind-factory-V79Hw0AU.js} +4 -82
  50. package/dist/kind-factory-V79Hw0AU.js.map +1 -0
  51. package/dist/{llm-judge-C5wbADA5.js → llm-judge-FWWhEb31.js} +5 -3
  52. package/dist/{llm-judge-C5wbADA5.js.map → llm-judge-FWWhEb31.js.map} +1 -1
  53. package/dist/openapi.json +1 -1
  54. package/dist/otel-spans-CQY1pNiN.js +275 -0
  55. package/dist/otel-spans-CQY1pNiN.js.map +1 -0
  56. package/dist/otlp-attributes-BpC_blLF.js +83 -0
  57. package/dist/otlp-attributes-BpC_blLF.js.map +1 -0
  58. package/dist/{run-record-BhMVsHR1.js → paired-promotion-decision-ClJh_9nK.js} +2 -215
  59. package/dist/paired-promotion-decision-ClJh_9nK.js.map +1 -0
  60. package/dist/prime-bridge-transport-Z3cpKGoV.js +43 -0
  61. package/dist/prime-bridge-transport-Z3cpKGoV.js.map +1 -0
  62. package/dist/prime-protocol-CDBcL9NM.d.ts +331 -0
  63. package/dist/prime-protocol-CDBcL9NM.d.ts.map +1 -0
  64. package/dist/{prime-protocol-6tZTVsWm.js → prime-protocol-DcAK9pC8.js} +2 -23
  65. package/dist/prime-protocol-DcAK9pC8.js.map +1 -0
  66. package/dist/{produced-state-D3uWsY8t.js → produced-state-CmB02tr2.js} +3 -3
  67. package/dist/{produced-state-D3uWsY8t.js.map → produced-state-CmB02tr2.js.map} +1 -1
  68. package/dist/{promotion-policy-CD-bUeyR.js → promotion-policy-DdrYWHDh.js} +3 -3
  69. package/dist/{promotion-policy-CD-bUeyR.js.map → promotion-policy-DdrYWHDh.js.map} +1 -1
  70. package/dist/rl.js +2 -1
  71. package/dist/rl.js.map +1 -1
  72. package/dist/run-record-CNNzWGpB.js +216 -0
  73. package/dist/run-record-CNNzWGpB.js.map +1 -0
  74. package/dist/{semantic-concept-judge-5r9hDEBA.js → semantic-concept-judge-Cnt0TWp0.js} +3 -3
  75. package/dist/{semantic-concept-judge-5r9hDEBA.js.map → semantic-concept-judge-Cnt0TWp0.js.map} +1 -1
  76. package/dist/{sequential-BfDkCBGo.js → sequential-PgLEqcTJ.js} +2 -2
  77. package/dist/{sequential-BfDkCBGo.js.map → sequential-PgLEqcTJ.js.map} +1 -1
  78. package/dist/{skillopt-optimization-method-B2zxoDk0.js → skillopt-optimization-method-FSk8b5FQ.js} +4 -3
  79. package/dist/{skillopt-optimization-method-B2zxoDk0.js.map → skillopt-optimization-method-FSk8b5FQ.js.map} +1 -1
  80. package/dist/statistical-heldout-CeWT_W7y.js +256 -0
  81. package/dist/statistical-heldout-CeWT_W7y.js.map +1 -0
  82. package/dist/{store-otlp-rU5PFlOB.js → store-otlp-2gYCy-v0.js} +2 -2
  83. package/dist/{store-otlp-rU5PFlOB.js.map → store-otlp-2gYCy-v0.js.map} +1 -1
  84. package/dist/{store-tool-spans-zrtftcRe.js → store-tool-spans-B1e6Auc2.js} +4 -3
  85. package/dist/{store-tool-spans-zrtftcRe.js.map → store-tool-spans-B1e6Auc2.js.map} +1 -1
  86. package/dist/{store-tool-spans-DwdokbHg.d.ts → store-tool-spans-BrdfPaVh.d.ts} +3 -3
  87. package/dist/{store-tool-spans-DwdokbHg.d.ts.map → store-tool-spans-BrdfPaVh.d.ts.map} +1 -1
  88. package/dist/{task-failure-attributes-BOxjfbhr.js → task-failure-attributes-B34TiMBe.js} +2 -2
  89. package/dist/{task-failure-attributes-BOxjfbhr.js.map → task-failure-attributes-B34TiMBe.js.map} +1 -1
  90. package/dist/{tool-groups-qHMydbAH.d.ts → tool-groups-eOOgYGaM.d.ts} +2 -2
  91. package/dist/tool-groups-eOOgYGaM.d.ts.map +1 -0
  92. package/dist/trace-repair/index.d.ts +1 -1
  93. package/dist/trace-repair/index.js +2 -1
  94. package/dist/trace-repair/index.js.map +1 -1
  95. package/dist/traces.d.ts +2 -2
  96. package/dist/traces.js +5 -4
  97. package/dist/traces.js.map +1 -1
  98. package/docs/diagnosis.md +42 -0
  99. package/package.json +7 -1
  100. package/dist/benchmark-command-DlSePljH.js.map +0 -1
  101. package/dist/campaign-evidence-aWEmfROB.js.map +0 -1
  102. package/dist/define-agent-eval-RTekxhUE.js.map +0 -1
  103. package/dist/engine-D1ykrAKR.d.ts.map +0 -1
  104. package/dist/kind-factory-DE088Zhv.js.map +0 -1
  105. package/dist/prime-protocol-6tZTVsWm.js.map +0 -1
  106. package/dist/run-record-BhMVsHR1.js.map +0 -1
  107. package/dist/tool-groups-qHMydbAH.d.ts.map +0 -1
package/dist/openapi.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "openapi": "3.1.0",
3
3
  "info": {
4
4
  "title": "@tangle-network/agent-eval — wire protocol",
5
- "version": "0.184.0",
5
+ "version": "0.185.0",
6
6
  "description": "HTTP and stdio RPC interface to agent-eval. The TypeScript runtime is the source of truth; this spec is the contract that cross-language clients (Python, Rust, Go) generate from.\n\nWire-protocol version: 1.1.0. Bumps on breaking changes to request/response schemas.",
7
7
  "contact": {
8
8
  "name": "Tangle Network",
@@ -0,0 +1,275 @@
1
+ import { s as ValidationError } from "./errors-Dngq5h35.js";
2
+ import { LLM_MODEL_ATTR_KEYS, SPAN_KIND_ATTR_KEYS } from "./trace-attributes.js";
3
+ import { n as classifyOtlpSpanRole, r as isOtlpModelCall } from "./otlp-attributes-BpC_blLF.js";
4
+ import { i as summarizeTraceErrors, n as recordAggregateMeasurements, r as summarizeExecutionMeasurements, t as readTaskFailureLabels } from "./task-failure-attributes-B34TiMBe.js";
5
+ //#region src/contract/intake/otel-spans.ts
6
+ /**
7
+ * # `intake/otel-spans` — OTel `TraceSpanEvent[]` → `RunRecord[]`.
8
+ *
9
+ * Turns an existing observability stream into the substrate-canonical
10
+ * `RunRecord` shape so consumers with logs but no eval discipline can
11
+ * call `analyzeRuns()` against their production traffic immediately.
12
+ *
13
+ * Pivot rule: spans are grouped by `tangle.runId` (the same attribute the
14
+ * hosted-tier wire format uses) or, when absent, by `traceId`. One group
15
+ * becomes one `RunRecord`. The root span (no `parentSpanId`) supplies:
16
+ *
17
+ * - `runId` (the group key)
18
+ * - `wallMs` from `endTimeUnixNano - startTimeUnixNano`
19
+ * - `model` from `gen_ai.request.model` / `llm.model` / `tangle.model`
20
+ * - task failure class and detail from explicit `tangle.task.*` attributes
21
+ * - cost from `cost.usd` / `gen_ai.usage.cost_usd` / `tangle.cost.usd`
22
+ * - token usage from model-call input, output, cache-read, and cache-write
23
+ * attributes without double-counting aggregate parent spans
24
+ * - task quality from an explicit `scoreForRun` callback or a designated
25
+ * evaluation attribute on a root / `EVALUATOR` span; `outcome.raw`
26
+ * collects every numeric attribute without promoting it to task quality.
27
+ *
28
+ * Errored tool, model, and child-agent spans contribute to execution-error
29
+ * counts. Root process, guardrail, evaluator, propagated parent, and unknown
30
+ * errors retain separate counters. Only one failed root can set
31
+ * `RunRecord.terminalOutcome` and `RunRecord.terminalFailureReason`; a child
32
+ * error cannot become a task failure.
33
+ */
34
+ const TASK_SCORE_ATTR_KEYS = [
35
+ "gen_ai.evaluation.score.value",
36
+ "tangle.task.score",
37
+ "eval.score",
38
+ "tangle.score"
39
+ ];
40
+ const MODEL_KEYS = [
41
+ "tangle.model",
42
+ ...LLM_MODEL_ATTR_KEYS,
43
+ "model"
44
+ ];
45
+ const PROMPT_HASH_KEYS = ["tangle.prompt_hash", "prompt.hash"];
46
+ const CONFIG_HASH_KEYS = ["tangle.config_hash", "config.hash"];
47
+ function fromOtelSpans(opts) {
48
+ const { spans, defaultSplit = "holdout", experimentId = "otel-corpus" } = opts;
49
+ const grouped = groupSpans(spans);
50
+ const runs = [];
51
+ for (const [groupKey, groupSpans] of grouped) {
52
+ const root = findRoot(groupSpans);
53
+ if (!root) continue;
54
+ const measurements = summarizeExecutionMeasurements(groupSpans.map((span) => ({
55
+ id: span.spanId,
56
+ ...span.parentSpanId ? { parentId: span.parentSpanId } : {},
57
+ attributes: span.attributes,
58
+ modelCall: isExplicitModelCall(span),
59
+ aggregate: isExplicitAggregate(span)
60
+ })));
61
+ const callSpanIds = new Set(measurements.callSpanIds);
62
+ const callSpans = groupSpans.filter((span) => callSpanIds.has(span.spanId));
63
+ const wallMs = unixNanoDurationMs(root.startTimeUnixNano, root.endTimeUnixNano);
64
+ const model = readAttrString(callSpans, MODEL_KEYS) ?? readAttrString(groupSpans, MODEL_KEYS) ?? "unknown@unknown";
65
+ const capturedCost = (measurements.cost.complete ? measurements.cost.value : void 0) ?? measurements.aggregate?.costUsd;
66
+ const costUsd = capturedCost ?? null;
67
+ const scenarioId = readConsistentScenarioId(groupKey, groupSpans) ?? groupKey;
68
+ const promptHash = readAttrString(groupSpans, PROMPT_HASH_KEYS) ?? "sha256:unknown";
69
+ const configHash = readAttrString(groupSpans, CONFIG_HASH_KEYS) ?? "sha256:unknown";
70
+ const score = resolveTaskScore(groupKey, groupSpans, opts.scoreForRun);
71
+ const taskFailure = readTaskFailureLabels(groupSpans.filter((span) => !span.parentSpanId && isTerminalRootCandidate(span)), `fromOtelSpans: run '${groupKey}'`);
72
+ const rawNumeric = collectNumericAttrs(groupSpans);
73
+ const errorSummary = summarizeTraceErrors(groupSpans.map((span) => ({
74
+ id: spanIdentity(span),
75
+ ...span.parentSpanId ? { parentId: parentIdentity(span) } : {},
76
+ role: errorRoleForSpan(span),
77
+ error: span.status?.code === "ERROR",
78
+ processRoot: !span.parentSpanId && isTerminalRootCandidate(span)
79
+ })));
80
+ rawNumeric.error_span_count = errorSummary.total;
81
+ rawNumeric.execution_error_count = errorSummary.execution;
82
+ rawNumeric.process_error_count = errorSummary.process;
83
+ rawNumeric.guardrail_error_count = errorSummary.guardrail;
84
+ rawNumeric.judge_error_count = errorSummary.evaluation;
85
+ rawNumeric.propagated_error_count = errorSummary.propagated;
86
+ rawNumeric.unclassified_error_count = errorSummary.unclassified;
87
+ rawNumeric.llm_span_count = measurements.modelCallCount;
88
+ if (measurements.cost.value !== void 0 && !measurements.cost.complete) rawNumeric.partial_observed_cost_usd = measurements.cost.value;
89
+ recordAggregateMeasurements(rawNumeric, measurements.aggregate);
90
+ const judgeScores = score !== void 0 ? {
91
+ perJudge: { "otel-derived": { score } },
92
+ perDimMean: { score },
93
+ composite: score
94
+ } : void 0;
95
+ const terminalOutcome = terminalOutcomeFromRoots(groupSpans);
96
+ const failedRoot = terminalOutcome === "failed" ? groupSpans.find((span) => !span.parentSpanId && isTerminalRootCandidate(span) && span.status?.code === "ERROR") : void 0;
97
+ const outcome = {
98
+ raw: rawNumeric,
99
+ ...judgeScores ? { judgeScores } : {}
100
+ };
101
+ if (score !== void 0) if (defaultSplit === "holdout") outcome.holdoutScore = score;
102
+ else outcome.searchScore = score;
103
+ runs.push({
104
+ runId: groupKey,
105
+ experimentId,
106
+ candidateId: root.attributes["tangle.candidateId"] ?? "otel-default",
107
+ seed: 0,
108
+ model,
109
+ promptHash,
110
+ configHash,
111
+ commitSha: root.attributes["tangle.commit_sha"] ?? "unknown",
112
+ wallMs,
113
+ costUsd,
114
+ costProvenance: capturedCost === void 0 ? {
115
+ kind: "uncaptured",
116
+ usd: null
117
+ } : {
118
+ kind: "observed",
119
+ usd: capturedCost
120
+ },
121
+ tokenUsage: measurements.tokenUsage,
122
+ terminalOutcome,
123
+ ...failedRoot ? { terminalFailureReason: failedRoot.status?.message ?? failedRoot.name } : {},
124
+ outcome,
125
+ ...taskFailure,
126
+ splitTag: defaultSplit,
127
+ scenarioId
128
+ });
129
+ }
130
+ return runs;
131
+ }
132
+ function terminalOutcomeFromRoots(spans) {
133
+ const roots = spans.filter((span) => !span.parentSpanId && isTerminalRootCandidate(span));
134
+ if (roots.length !== 1) return "unknown";
135
+ if (roots[0].status?.code === "OK") return "succeeded";
136
+ if (roots[0].status?.code === "ERROR") return "failed";
137
+ return "unknown";
138
+ }
139
+ function isTerminalRootCandidate(span) {
140
+ const role = errorRoleForSpan(span);
141
+ if (role === "LLM" || role === "TOOL" || role === "GUARDRAIL" || role === "EVALUATOR") return false;
142
+ return true;
143
+ }
144
+ function readSpanKind(span) {
145
+ return readAttrString([span], [...SPAN_KIND_ATTR_KEYS, "span.kind"])?.toUpperCase();
146
+ }
147
+ function errorRoleForSpan(span) {
148
+ return classifyOtlpSpanRole({
149
+ kind: readSpanKind(span),
150
+ name: span.name,
151
+ attributes: span.attributes
152
+ });
153
+ }
154
+ function spanIdentity(span) {
155
+ return `${span.traceId}:${span.spanId}`;
156
+ }
157
+ function parentIdentity(span) {
158
+ return `${span.traceId}:${span.parentSpanId}`;
159
+ }
160
+ function isExplicitModelCall(span) {
161
+ return isOtlpModelCall({
162
+ kind: readSpanKind(span),
163
+ name: span.name,
164
+ attributes: span.attributes
165
+ });
166
+ }
167
+ function isExplicitAggregate(span) {
168
+ const kind = readSpanKind(span);
169
+ return kind !== void 0 && kind !== "LLM";
170
+ }
171
+ function groupSpans(spans) {
172
+ const m = /* @__PURE__ */ new Map();
173
+ for (const span of spans) {
174
+ const key = span["tangle.runId"] ?? span.traceId;
175
+ const list = m.get(key) ?? [];
176
+ list.push(span);
177
+ m.set(key, list);
178
+ }
179
+ return m;
180
+ }
181
+ function findRoot(group) {
182
+ const structuralRoots = group.filter((span) => !span.parentSpanId);
183
+ const terminalRoots = structuralRoots.filter(isTerminalRootCandidate);
184
+ return orderSpans(terminalRoots.length > 0 ? terminalRoots : structuralRoots.length > 0 ? structuralRoots : group)[0];
185
+ }
186
+ function readAttrString(spans, keys) {
187
+ for (const span of spans) for (const key of keys) {
188
+ const v = span.attributes[key];
189
+ if (typeof v === "string" && v.length > 0) return v;
190
+ }
191
+ }
192
+ function readConsistentScenarioId(runId, spans) {
193
+ const values = /* @__PURE__ */ new Set();
194
+ for (const span of spans) {
195
+ const topLevel = span["tangle.scenarioId"];
196
+ if (typeof topLevel === "string" && topLevel.length > 0) values.add(topLevel);
197
+ const attribute = span.attributes["tangle.scenarioId"];
198
+ if (typeof attribute === "string" && attribute.length > 0) values.add(attribute);
199
+ }
200
+ if (values.size > 1) throw new ValidationError(`fromOtelSpans: conflicting scenario ids for run '${runId}': ${[...values].sort().join(", ")}`);
201
+ return values.values().next().value;
202
+ }
203
+ function resolveTaskScore(runId, spans, scoreForRun) {
204
+ const orderedSpans = orderSpans(spans);
205
+ const sources = [];
206
+ if (scoreForRun) {
207
+ const supplied = scoreForRun(runId, orderedSpans);
208
+ if (supplied !== void 0) {
209
+ if (typeof supplied !== "number" || !Number.isFinite(supplied)) throw new ValidationError(`fromOtelSpans: scoreForRun returned a non-finite number for run '${runId}'`);
210
+ sources.push({
211
+ label: "scoreForRun",
212
+ value: supplied
213
+ });
214
+ }
215
+ }
216
+ for (const span of orderedSpans) {
217
+ const role = errorRoleForSpan(span);
218
+ if (span.parentSpanId && role !== "EVALUATOR") continue;
219
+ if (role === "EVALUATOR" && span.status?.code === "ERROR") continue;
220
+ for (const key of TASK_SCORE_ATTR_KEYS) {
221
+ if (!Object.hasOwn(span.attributes, key)) continue;
222
+ sources.push({
223
+ label: `span '${span.spanId}' attribute '${key}'`,
224
+ value: parseTaskScoreAttribute(runId, span.spanId, key, span.attributes[key])
225
+ });
226
+ }
227
+ }
228
+ if (sources.length === 0) return void 0;
229
+ sources.sort((left, right) => left.label.localeCompare(right.label));
230
+ const score = sources[0].value;
231
+ if (sources.some((source) => source.value !== score)) throw new ValidationError(`fromOtelSpans: conflicting task-quality scores for run '${runId}': ${sources.map((source) => `${source.label}=${source.value}`).join(", ")}`);
232
+ return score;
233
+ }
234
+ function parseTaskScoreAttribute(runId, spanId, key, value) {
235
+ const source = `span '${spanId}' attribute '${key}'`;
236
+ if (typeof value === "string") {
237
+ if (value.trim().length === 0) throw new ValidationError(`fromOtelSpans: ${source} is blank for run '${runId}'; task quality must be finite`);
238
+ const parsed = Number(value);
239
+ if (Number.isFinite(parsed)) return parsed;
240
+ } else if (typeof value === "number" && Number.isFinite(value)) return value;
241
+ throw new ValidationError(`fromOtelSpans: ${source} is not a finite task-quality score for run '${runId}'`);
242
+ }
243
+ function orderSpans(spans) {
244
+ return [...spans].sort((left, right) => compareUnixNano(left.startTimeUnixNano, right.startTimeUnixNano) || left.spanId.localeCompare(right.spanId));
245
+ }
246
+ function parseUnixNano(value) {
247
+ try {
248
+ return BigInt(value);
249
+ } catch {
250
+ throw new ValidationError(`fromOtelSpans: invalid Unix nanosecond timestamp '${value}'`);
251
+ }
252
+ }
253
+ function compareUnixNano(left, right) {
254
+ const leftValue = parseUnixNano(left);
255
+ const rightValue = parseUnixNano(right);
256
+ return leftValue < rightValue ? -1 : leftValue > rightValue ? 1 : 0;
257
+ }
258
+ function unixNanoDurationMs(start, end) {
259
+ const delta = parseUnixNano(end) - parseUnixNano(start);
260
+ if (delta <= 0n) return 0;
261
+ const wholeMs = delta / 1000000n;
262
+ const fractionalMs = delta % 1000000n;
263
+ const value = Number(wholeMs) + Number(fractionalMs) / 1e6;
264
+ if (!Number.isSafeInteger(Number(wholeMs))) throw new ValidationError("fromOtelSpans: span duration exceeds the safe millisecond range");
265
+ return value;
266
+ }
267
+ function collectNumericAttrs(spans) {
268
+ const raw = {};
269
+ for (const span of spans) for (const [k, v] of Object.entries(span.attributes)) if (typeof v === "number" && Number.isFinite(v)) raw[k] = v;
270
+ return raw;
271
+ }
272
+ //#endregion
273
+ export { fromOtelSpans as t };
274
+
275
+ //# sourceMappingURL=otel-spans-CQY1pNiN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"otel-spans-CQY1pNiN.js","names":[],"sources":["../src/contract/intake/otel-spans.ts"],"sourcesContent":["/**\n * # `intake/otel-spans` — OTel `TraceSpanEvent[]` → `RunRecord[]`.\n *\n * Turns an existing observability stream into the substrate-canonical\n * `RunRecord` shape so consumers with logs but no eval discipline can\n * call `analyzeRuns()` against their production traffic immediately.\n *\n * Pivot rule: spans are grouped by `tangle.runId` (the same attribute the\n * hosted-tier wire format uses) or, when absent, by `traceId`. One group\n * becomes one `RunRecord`. The root span (no `parentSpanId`) supplies:\n *\n * - `runId` (the group key)\n * - `wallMs` from `endTimeUnixNano - startTimeUnixNano`\n * - `model` from `gen_ai.request.model` / `llm.model` / `tangle.model`\n * - task failure class and detail from explicit `tangle.task.*` attributes\n * - cost from `cost.usd` / `gen_ai.usage.cost_usd` / `tangle.cost.usd`\n * - token usage from model-call input, output, cache-read, and cache-write\n * attributes without double-counting aggregate parent spans\n * - task quality from an explicit `scoreForRun` callback or a designated\n * evaluation attribute on a root / `EVALUATOR` span; `outcome.raw`\n * collects every numeric attribute without promoting it to task quality.\n *\n * Errored tool, model, and child-agent spans contribute to execution-error\n * counts. Root process, guardrail, evaluator, propagated parent, and unknown\n * errors retain separate counters. Only one failed root can set\n * `RunRecord.terminalOutcome` and `RunRecord.terminalFailureReason`; a child\n * error cannot become a task failure.\n */\n\nimport { ValidationError } from '../../errors'\nimport type { TraceSpanEvent } from '../../hosted/types'\nimport type {\n JudgeScoresRecord,\n RunOutcome,\n RunRecord,\n RunSplitTag,\n RunTerminalOutcome,\n} from '../../run-record'\nimport { summarizeTraceErrors, type TraceErrorRole } from '../../trace/error-classification'\nimport {\n recordAggregateMeasurements,\n summarizeExecutionMeasurements,\n} from '../../trace/execution-measurements'\nimport {\n classifyOtlpSpanRole,\n isOtlpModelCall,\n LLM_MODEL_ATTR_KEYS,\n SPAN_KIND_ATTR_KEYS,\n} from '../../trace/otlp-attributes'\nimport { readTaskFailureLabels } from '../../trace/task-failure-attributes'\n\nconst TASK_SCORE_ATTR_KEYS = [\n 'gen_ai.evaluation.score.value',\n 'tangle.task.score',\n 'eval.score',\n 'tangle.score',\n] as const\nconst MODEL_KEYS = ['tangle.model', ...LLM_MODEL_ATTR_KEYS, 'model']\nconst PROMPT_HASH_KEYS = ['tangle.prompt_hash', 'prompt.hash']\nconst CONFIG_HASH_KEYS = ['tangle.config_hash', 'config.hash']\n\nexport interface FromOtelSpansOptions {\n spans: TraceSpanEvent[]\n /** Default split tag for synthesized records. Defaults to `'holdout'`. */\n defaultSplit?: RunSplitTag\n /** Default `experimentId` when not present on any span. */\n experimentId?: string\n /**\n * Explicit task-quality score for a logical run. The callback receives\n * spans in deterministic time/id order. Its value must agree with any\n * designated score attributes present on root or `EVALUATOR` spans.\n */\n scoreForRun?: (runId: string, spans: readonly TraceSpanEvent[]) => number | undefined\n}\n\nexport function fromOtelSpans(opts: FromOtelSpansOptions): RunRecord[] {\n const { spans, defaultSplit = 'holdout', experimentId = 'otel-corpus' } = opts\n const grouped = groupSpans(spans)\n\n const runs: RunRecord[] = []\n for (const [groupKey, groupSpans] of grouped) {\n const root = findRoot(groupSpans)\n if (!root) continue\n const measurements = summarizeExecutionMeasurements(\n groupSpans.map((span) => ({\n id: span.spanId,\n ...(span.parentSpanId ? { parentId: span.parentSpanId } : {}),\n attributes: span.attributes,\n modelCall: isExplicitModelCall(span),\n aggregate: isExplicitAggregate(span),\n })),\n )\n const callSpanIds = new Set(measurements.callSpanIds)\n const callSpans = groupSpans.filter((span) => callSpanIds.has(span.spanId))\n const wallMs = unixNanoDurationMs(root.startTimeUnixNano, root.endTimeUnixNano)\n const model =\n readAttrString(callSpans, MODEL_KEYS) ??\n readAttrString(groupSpans, MODEL_KEYS) ??\n 'unknown@unknown'\n const capturedCost =\n (measurements.cost.complete ? measurements.cost.value : undefined) ??\n measurements.aggregate?.costUsd\n const costUsd = capturedCost ?? null\n const scenarioId = readConsistentScenarioId(groupKey, groupSpans) ?? groupKey\n const promptHash = readAttrString(groupSpans, PROMPT_HASH_KEYS) ?? 'sha256:unknown'\n const configHash = readAttrString(groupSpans, CONFIG_HASH_KEYS) ?? 'sha256:unknown'\n const score = resolveTaskScore(groupKey, groupSpans, opts.scoreForRun)\n const taskFailure = readTaskFailureLabels(\n groupSpans.filter((span) => !span.parentSpanId && isTerminalRootCandidate(span)),\n `fromOtelSpans: run '${groupKey}'`,\n )\n\n const rawNumeric = collectNumericAttrs(groupSpans)\n const errorSummary = summarizeTraceErrors(\n groupSpans.map((span) => ({\n id: spanIdentity(span),\n ...(span.parentSpanId ? { parentId: parentIdentity(span) } : {}),\n role: errorRoleForSpan(span),\n error: span.status?.code === 'ERROR',\n processRoot: !span.parentSpanId && isTerminalRootCandidate(span),\n })),\n )\n rawNumeric.error_span_count = errorSummary.total\n rawNumeric.execution_error_count = errorSummary.execution\n rawNumeric.process_error_count = errorSummary.process\n rawNumeric.guardrail_error_count = errorSummary.guardrail\n rawNumeric.judge_error_count = errorSummary.evaluation\n rawNumeric.propagated_error_count = errorSummary.propagated\n rawNumeric.unclassified_error_count = errorSummary.unclassified\n rawNumeric.llm_span_count = measurements.modelCallCount\n if (measurements.cost.value !== undefined && !measurements.cost.complete) {\n rawNumeric.partial_observed_cost_usd = measurements.cost.value\n }\n recordAggregateMeasurements(rawNumeric, measurements.aggregate)\n\n const judgeScores: JudgeScoresRecord | undefined =\n score !== undefined\n ? {\n perJudge: { 'otel-derived': { score } },\n perDimMean: { score },\n composite: score,\n }\n : undefined\n\n const terminalOutcome = terminalOutcomeFromRoots(groupSpans)\n const failedRoot =\n terminalOutcome === 'failed'\n ? groupSpans.find(\n (span) =>\n !span.parentSpanId && isTerminalRootCandidate(span) && span.status?.code === 'ERROR',\n )\n : undefined\n const outcome: RunOutcome = {\n raw: rawNumeric,\n ...(judgeScores ? { judgeScores } : {}),\n }\n if (score !== undefined) {\n if (defaultSplit === 'holdout') outcome.holdoutScore = score\n else outcome.searchScore = score\n }\n\n runs.push({\n runId: groupKey,\n experimentId,\n candidateId: (root.attributes['tangle.candidateId'] as string | undefined) ?? 'otel-default',\n seed: 0,\n model,\n promptHash,\n configHash,\n commitSha: (root.attributes['tangle.commit_sha'] as string | undefined) ?? 'unknown',\n wallMs,\n costUsd,\n costProvenance:\n capturedCost === undefined\n ? { kind: 'uncaptured', usd: null }\n : { kind: 'observed', usd: capturedCost },\n tokenUsage: measurements.tokenUsage,\n terminalOutcome,\n ...(failedRoot\n ? { terminalFailureReason: failedRoot.status?.message ?? failedRoot.name }\n : {}),\n outcome,\n ...taskFailure,\n splitTag: defaultSplit,\n scenarioId,\n })\n }\n return runs\n}\n\nfunction terminalOutcomeFromRoots(spans: TraceSpanEvent[]): RunTerminalOutcome {\n const roots = spans.filter((span) => !span.parentSpanId && isTerminalRootCandidate(span))\n if (roots.length !== 1) return 'unknown'\n if (roots[0]!.status?.code === 'OK') return 'succeeded'\n if (roots[0]!.status?.code === 'ERROR') return 'failed'\n return 'unknown'\n}\n\nfunction isTerminalRootCandidate(span: TraceSpanEvent): boolean {\n const role = errorRoleForSpan(span)\n if (role === 'LLM' || role === 'TOOL' || role === 'GUARDRAIL' || role === 'EVALUATOR') {\n return false\n }\n return true\n}\n\nfunction readSpanKind(span: TraceSpanEvent): string | undefined {\n return readAttrString([span], [...SPAN_KIND_ATTR_KEYS, 'span.kind'])?.toUpperCase()\n}\n\nfunction errorRoleForSpan(span: TraceSpanEvent): TraceErrorRole {\n return classifyOtlpSpanRole({\n kind: readSpanKind(span),\n name: span.name,\n attributes: span.attributes,\n })\n}\n\nfunction spanIdentity(span: TraceSpanEvent): string {\n return `${span.traceId}:${span.spanId}`\n}\n\nfunction parentIdentity(span: TraceSpanEvent): string {\n return `${span.traceId}:${span.parentSpanId}`\n}\n\nfunction isExplicitModelCall(span: TraceSpanEvent): boolean {\n return isOtlpModelCall({\n kind: readSpanKind(span),\n name: span.name,\n attributes: span.attributes,\n })\n}\n\nfunction isExplicitAggregate(span: TraceSpanEvent): boolean {\n const kind = readSpanKind(span)\n return kind !== undefined && kind !== 'LLM'\n}\n\n// ── Internal helpers ────────────────────────────────────────────────\n\nfunction groupSpans(spans: TraceSpanEvent[]): Map<string, TraceSpanEvent[]> {\n const m = new Map<string, TraceSpanEvent[]>()\n for (const span of spans) {\n const key = (span['tangle.runId'] as string | undefined) ?? span.traceId\n const list = m.get(key) ?? []\n list.push(span)\n m.set(key, list)\n }\n return m\n}\n\nfunction findRoot(group: TraceSpanEvent[]): TraceSpanEvent | undefined {\n const structuralRoots = group.filter((span) => !span.parentSpanId)\n const terminalRoots = structuralRoots.filter(isTerminalRootCandidate)\n const pool =\n terminalRoots.length > 0 ? terminalRoots : structuralRoots.length > 0 ? structuralRoots : group\n return orderSpans(pool)[0]\n}\n\nfunction readAttrString(spans: TraceSpanEvent[], keys: string[]): string | undefined {\n for (const span of spans) {\n for (const key of keys) {\n const v = span.attributes[key]\n if (typeof v === 'string' && v.length > 0) return v\n }\n }\n return undefined\n}\n\nfunction readConsistentScenarioId(\n runId: string,\n spans: readonly TraceSpanEvent[],\n): string | undefined {\n const values = new Set<string>()\n for (const span of spans) {\n const topLevel = span['tangle.scenarioId']\n if (typeof topLevel === 'string' && topLevel.length > 0) values.add(topLevel)\n const attribute = span.attributes['tangle.scenarioId']\n if (typeof attribute === 'string' && attribute.length > 0) values.add(attribute)\n }\n if (values.size > 1) {\n throw new ValidationError(\n `fromOtelSpans: conflicting scenario ids for run '${runId}': ${[...values].sort().join(', ')}`,\n )\n }\n return values.values().next().value\n}\n\ninterface TaskScoreSource {\n label: string\n value: number\n}\n\nfunction resolveTaskScore(\n runId: string,\n spans: TraceSpanEvent[],\n scoreForRun: FromOtelSpansOptions['scoreForRun'],\n): number | undefined {\n const orderedSpans = orderSpans(spans)\n const sources: TaskScoreSource[] = []\n\n if (scoreForRun) {\n const supplied: unknown = scoreForRun(runId, orderedSpans)\n if (supplied !== undefined) {\n if (typeof supplied !== 'number' || !Number.isFinite(supplied)) {\n throw new ValidationError(\n `fromOtelSpans: scoreForRun returned a non-finite number for run '${runId}'`,\n )\n }\n sources.push({ label: 'scoreForRun', value: supplied })\n }\n }\n\n for (const span of orderedSpans) {\n const role = errorRoleForSpan(span)\n if (span.parentSpanId && role !== 'EVALUATOR') continue\n if (role === 'EVALUATOR' && span.status?.code === 'ERROR') continue\n for (const key of TASK_SCORE_ATTR_KEYS) {\n if (!Object.hasOwn(span.attributes, key)) continue\n sources.push({\n label: `span '${span.spanId}' attribute '${key}'`,\n value: parseTaskScoreAttribute(runId, span.spanId, key, span.attributes[key]),\n })\n }\n }\n\n if (sources.length === 0) return undefined\n sources.sort((left, right) => left.label.localeCompare(right.label))\n const score = sources[0]!.value\n if (sources.some((source) => source.value !== score)) {\n const details = sources.map((source) => `${source.label}=${source.value}`).join(', ')\n throw new ValidationError(\n `fromOtelSpans: conflicting task-quality scores for run '${runId}': ${details}`,\n )\n }\n return score\n}\n\nfunction parseTaskScoreAttribute(\n runId: string,\n spanId: string,\n key: string,\n value: unknown,\n): number {\n const source = `span '${spanId}' attribute '${key}'`\n if (typeof value === 'string') {\n if (value.trim().length === 0) {\n throw new ValidationError(\n `fromOtelSpans: ${source} is blank for run '${runId}'; task quality must be finite`,\n )\n }\n const parsed = Number(value)\n if (Number.isFinite(parsed)) return parsed\n } else if (typeof value === 'number' && Number.isFinite(value)) {\n return value\n }\n throw new ValidationError(\n `fromOtelSpans: ${source} is not a finite task-quality score for run '${runId}'`,\n )\n}\n\nfunction orderSpans(spans: readonly TraceSpanEvent[]): TraceSpanEvent[] {\n return [...spans].sort(\n (left, right) =>\n compareUnixNano(left.startTimeUnixNano, right.startTimeUnixNano) ||\n left.spanId.localeCompare(right.spanId),\n )\n}\n\nfunction parseUnixNano(value: string): bigint {\n try {\n return BigInt(value)\n } catch {\n throw new ValidationError(`fromOtelSpans: invalid Unix nanosecond timestamp '${value}'`)\n }\n}\n\nfunction compareUnixNano(left: string, right: string): number {\n const leftValue = parseUnixNano(left)\n const rightValue = parseUnixNano(right)\n return leftValue < rightValue ? -1 : leftValue > rightValue ? 1 : 0\n}\n\nfunction unixNanoDurationMs(start: string, end: string): number {\n const delta = parseUnixNano(end) - parseUnixNano(start)\n if (delta <= 0n) return 0\n const wholeMs = delta / 1_000_000n\n const fractionalMs = delta % 1_000_000n\n const value = Number(wholeMs) + Number(fractionalMs) / 1_000_000\n if (!Number.isSafeInteger(Number(wholeMs))) {\n throw new ValidationError('fromOtelSpans: span duration exceeds the safe millisecond range')\n }\n return value\n}\n\nfunction collectNumericAttrs(spans: TraceSpanEvent[]): Record<string, number> {\n const raw: Record<string, number> = {}\n for (const span of spans) {\n for (const [k, v] of Object.entries(span.attributes)) {\n if (typeof v === 'number' && Number.isFinite(v)) raw[k] = v\n }\n }\n return raw\n}\n"],"mappings":"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"}
@@ -0,0 +1,83 @@
1
+ import { INPUT_VALUE, LLM_MODEL_ATTR_KEYS, OUTPUT_VALUE, SPAN_KIND_ATTR_KEYS, TOOL_ARGS_CAPTURED, TOOL_LATENCY_MS, TOOL_NAME, TOOL_NAME_ATTR_KEYS } from "./trace-attributes.js";
2
+ //#region src/trace/otlp-attributes.ts
3
+ /** Canonical OpenInference-over-OTLP attribute vocabulary used at the trace boundary. */
4
+ const TOOL_SPAN_ATTRIBUTE_KEYS = [
5
+ TOOL_NAME,
6
+ TOOL_ARGS_CAPTURED,
7
+ TOOL_LATENCY_MS,
8
+ INPUT_VALUE,
9
+ OUTPUT_VALUE
10
+ ];
11
+ const EXPLICIT_SPAN_ROLES = /* @__PURE__ */ new Set([
12
+ "AGENT",
13
+ "CHAIN",
14
+ "EVALUATOR",
15
+ "GUARDRAIL",
16
+ "LLM",
17
+ "SPAN",
18
+ "TOOL"
19
+ ]);
20
+ /**
21
+ * Classify a span once for both measurement and error accounting.
22
+ * An explicit OpenInference kind wins; untyped spans use the same tool and
23
+ * model signals in online and offline intake.
24
+ */
25
+ function classifyOtlpSpanRole(input) {
26
+ const explicitKind = input.kind ?? firstStringAttribute(input.attributes, SPAN_KIND_ATTR_KEYS);
27
+ if (explicitKind) {
28
+ const normalized = explicitKind.toUpperCase();
29
+ if (EXPLICIT_SPAN_ROLES.has(normalized)) return normalized;
30
+ }
31
+ if (firstStringAttribute(input.attributes, TOOL_NAME_ATTR_KEYS) !== void 0 || /^(?:function|tool)[.:/]/i.test(input.name)) return "TOOL";
32
+ const spanType = input.attributes["span.type"];
33
+ if (typeof spanType === "string" && spanType.toLowerCase() === "llm_request" || /(?:^|[.:/_-])(?:chat[._-]?completions?|llm)(?:$|[.:/_-])/i.test(input.name) || firstStringAttribute(input.attributes, LLM_MODEL_ATTR_KEYS) !== void 0 || typeof input.attributes["gen_ai.operation.name"] === "string") return "LLM";
34
+ return "UNKNOWN";
35
+ }
36
+ function isOtlpModelCall(input) {
37
+ return classifyOtlpSpanRole(input) === "LLM";
38
+ }
39
+ function toolSpanOtlpAttributes(span) {
40
+ const argsCaptured = span.argsCaptured !== false;
41
+ const attributes = {
42
+ [TOOL_NAME]: span.toolName,
43
+ [TOOL_ARGS_CAPTURED]: argsCaptured
44
+ };
45
+ if (span.latencyMs !== void 0) attributes[TOOL_LATENCY_MS] = span.latencyMs;
46
+ if (argsCaptured) attributes[INPUT_VALUE] = stringifyTraceValue(span.args);
47
+ if (span.result !== void 0) attributes[OUTPUT_VALUE] = stringifyTraceValue(span.result);
48
+ return attributes;
49
+ }
50
+ function applyToolSpanOtlpAttributes(attributes, span) {
51
+ for (const key of TOOL_SPAN_ATTRIBUTE_KEYS) delete attributes[key];
52
+ Object.assign(attributes, toolSpanOtlpAttributes(span));
53
+ }
54
+ function traceSpanKindToOpenInferenceKind(kind) {
55
+ switch (kind) {
56
+ case "llm": return "LLM";
57
+ case "tool": return "TOOL";
58
+ case "retrieval": return "CHAIN";
59
+ case "judge": return "EVALUATOR";
60
+ case "sandbox": return "CHAIN";
61
+ case "agent": return "AGENT";
62
+ default: return "SPAN";
63
+ }
64
+ }
65
+ function firstStringAttribute(attributes, keys) {
66
+ for (const key of keys) {
67
+ const value = attributes[key];
68
+ if (typeof value === "string" && value.length > 0) return value;
69
+ }
70
+ }
71
+ function stringifyTraceValue(value) {
72
+ if (value === void 0) return "null";
73
+ if (typeof value === "string") return value;
74
+ try {
75
+ return JSON.stringify(value) ?? String(value);
76
+ } catch {
77
+ return String(value);
78
+ }
79
+ }
80
+ //#endregion
81
+ export { traceSpanKindToOpenInferenceKind as i, classifyOtlpSpanRole as n, isOtlpModelCall as r, applyToolSpanOtlpAttributes as t };
82
+
83
+ //# sourceMappingURL=otlp-attributes-BpC_blLF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"otlp-attributes-BpC_blLF.js","names":[],"sources":["../src/trace/otlp-attributes.ts"],"sourcesContent":["/** Canonical OpenInference-over-OTLP attribute vocabulary used at the trace boundary. */\n\nimport {\n INPUT_VALUE,\n LLM_MODEL_ATTR_KEYS,\n OUTPUT_VALUE,\n SPAN_KIND_ATTR_KEYS,\n TOOL_ARGS_CAPTURED,\n TOOL_LATENCY_MS,\n TOOL_NAME,\n TOOL_NAME_ATTR_KEYS,\n} from './attribute-vocabulary'\nimport type { ToolSpan } from './schema'\n\nexport * from './attribute-vocabulary'\n\nconst TOOL_SPAN_ATTRIBUTE_KEYS = [\n TOOL_NAME,\n TOOL_ARGS_CAPTURED,\n TOOL_LATENCY_MS,\n INPUT_VALUE,\n OUTPUT_VALUE,\n] as const\n\nexport type ToolSpanOtlpInput = Pick<\n ToolSpan,\n 'toolName' | 'args' | 'argsCaptured' | 'result' | 'latencyMs'\n>\n\nexport type OtlpSpanRole =\n | 'AGENT'\n | 'CHAIN'\n | 'EVALUATOR'\n | 'GUARDRAIL'\n | 'LLM'\n | 'SPAN'\n | 'TOOL'\n | 'UNKNOWN'\n\nexport interface OtlpSpanRoleInput {\n name: string\n attributes: Record<string, unknown>\n kind?: string | null\n}\n\nconst EXPLICIT_SPAN_ROLES = new Set<OtlpSpanRole>([\n 'AGENT',\n 'CHAIN',\n 'EVALUATOR',\n 'GUARDRAIL',\n 'LLM',\n 'SPAN',\n 'TOOL',\n])\n\n/**\n * Classify a span once for both measurement and error accounting.\n * An explicit OpenInference kind wins; untyped spans use the same tool and\n * model signals in online and offline intake.\n */\nexport function classifyOtlpSpanRole(input: OtlpSpanRoleInput): OtlpSpanRole {\n const explicitKind = input.kind ?? firstStringAttribute(input.attributes, SPAN_KIND_ATTR_KEYS)\n if (explicitKind) {\n const normalized = explicitKind.toUpperCase() as OtlpSpanRole\n if (EXPLICIT_SPAN_ROLES.has(normalized)) return normalized\n }\n\n if (\n firstStringAttribute(input.attributes, TOOL_NAME_ATTR_KEYS) !== undefined ||\n /^(?:function|tool)[.:/]/i.test(input.name)\n ) {\n return 'TOOL'\n }\n\n const spanType = input.attributes['span.type']\n if (\n (typeof spanType === 'string' && spanType.toLowerCase() === 'llm_request') ||\n /(?:^|[.:/_-])(?:chat[._-]?completions?|llm)(?:$|[.:/_-])/i.test(input.name) ||\n firstStringAttribute(input.attributes, LLM_MODEL_ATTR_KEYS) !== undefined ||\n typeof input.attributes['gen_ai.operation.name'] === 'string'\n ) {\n return 'LLM'\n }\n\n return 'UNKNOWN'\n}\n\nexport function isOtlpModelCall(input: OtlpSpanRoleInput): boolean {\n return classifyOtlpSpanRole(input) === 'LLM'\n}\n\nfunction toolSpanOtlpAttributes(\n span: ToolSpanOtlpInput,\n): Record<string, string | number | boolean> {\n const argsCaptured = span.argsCaptured !== false\n const attributes: Record<string, string | number | boolean> = {\n [TOOL_NAME]: span.toolName,\n [TOOL_ARGS_CAPTURED]: argsCaptured,\n }\n if (span.latencyMs !== undefined) attributes[TOOL_LATENCY_MS] = span.latencyMs\n if (argsCaptured) attributes[INPUT_VALUE] = stringifyTraceValue(span.args)\n if (span.result !== undefined) attributes[OUTPUT_VALUE] = stringifyTraceValue(span.result)\n return attributes\n}\n\nexport function applyToolSpanOtlpAttributes(\n attributes: Record<string, unknown>,\n span: ToolSpanOtlpInput,\n): void {\n for (const key of TOOL_SPAN_ATTRIBUTE_KEYS) delete attributes[key]\n Object.assign(attributes, toolSpanOtlpAttributes(span))\n}\n\nexport function traceSpanKindToOpenInferenceKind(kind: string): string {\n switch (kind) {\n case 'llm':\n return 'LLM'\n case 'tool':\n return 'TOOL'\n case 'retrieval':\n return 'CHAIN'\n case 'judge':\n return 'EVALUATOR'\n case 'sandbox':\n return 'CHAIN'\n case 'agent':\n return 'AGENT'\n default:\n return 'SPAN'\n }\n}\n\nfunction firstStringAttribute(\n attributes: Record<string, unknown>,\n keys: readonly string[],\n): string | undefined {\n for (const key of keys) {\n const value = attributes[key]\n if (typeof value === 'string' && value.length > 0) return value\n }\n return undefined\n}\n\nfunction stringifyTraceValue(value: unknown): string {\n if (value === undefined) return 'null'\n if (typeof value === 'string') return value\n try {\n return JSON.stringify(value) ?? String(value)\n } catch {\n return String(value)\n }\n}\n"],"mappings":";;;AAgBA,MAAM,2BAA2B;CAC/B;CACA;CACA;CACA;CACA;AACF;AAuBA,MAAM,sCAAsB,IAAI,IAAkB;CAChD;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;;;;;;AAOD,SAAgB,qBAAqB,OAAwC;CAC3E,MAAM,eAAe,MAAM,QAAQ,qBAAqB,MAAM,YAAY,mBAAmB;CAC7F,IAAI,cAAc;EAChB,MAAM,aAAa,aAAa,YAAY;EAC5C,IAAI,oBAAoB,IAAI,UAAU,GAAG,OAAO;CAClD;CAEA,IACE,qBAAqB,MAAM,YAAY,mBAAmB,MAAM,KAAA,KAChE,2BAA2B,KAAK,MAAM,IAAI,GAE1C,OAAO;CAGT,MAAM,WAAW,MAAM,WAAW;CAClC,IACG,OAAO,aAAa,YAAY,SAAS,YAAY,MAAM,iBAC5D,4DAA4D,KAAK,MAAM,IAAI,KAC3E,qBAAqB,MAAM,YAAY,mBAAmB,MAAM,KAAA,KAChE,OAAO,MAAM,WAAW,6BAA6B,UAErD,OAAO;CAGT,OAAO;AACT;AAEA,SAAgB,gBAAgB,OAAmC;CACjE,OAAO,qBAAqB,KAAK,MAAM;AACzC;AAEA,SAAS,uBACP,MAC2C;CAC3C,MAAM,eAAe,KAAK,iBAAiB;CAC3C,MAAM,aAAwD;GAC3D,YAAY,KAAK;GACjB,qBAAqB;CACxB;CACA,IAAI,KAAK,cAAc,KAAA,GAAW,WAAW,mBAAmB,KAAK;CACrE,IAAI,cAAc,WAAW,eAAe,oBAAoB,KAAK,IAAI;CACzE,IAAI,KAAK,WAAW,KAAA,GAAW,WAAW,gBAAgB,oBAAoB,KAAK,MAAM;CACzF,OAAO;AACT;AAEA,SAAgB,4BACd,YACA,MACM;CACN,KAAK,MAAM,OAAO,0BAA0B,OAAO,WAAW;CAC9D,OAAO,OAAO,YAAY,uBAAuB,IAAI,CAAC;AACxD;AAEA,SAAgB,iCAAiC,MAAsB;CACrE,QAAQ,MAAR;EACE,KAAK,OACH,OAAO;EACT,KAAK,QACH,OAAO;EACT,KAAK,aACH,OAAO;EACT,KAAK,SACH,OAAO;EACT,KAAK,WACH,OAAO;EACT,KAAK,SACH,OAAO;EACT,SACE,OAAO;CACX;AACF;AAEA,SAAS,qBACP,YACA,MACoB;CACpB,KAAK,MAAM,OAAO,MAAM;EACtB,MAAM,QAAQ,WAAW;EACzB,IAAI,OAAO,UAAU,YAAY,MAAM,SAAS,GAAG,OAAO;CAC5D;AAEF;AAEA,SAAS,oBAAoB,OAAwB;CACnD,IAAI,UAAU,KAAA,GAAW,OAAO;CAChC,IAAI,OAAO,UAAU,UAAU,OAAO;CACtC,IAAI;EACF,OAAO,KAAK,UAAU,KAAK,KAAK,OAAO,KAAK;CAC9C,QAAQ;EACN,OAAO,OAAO,KAAK;CACrB;AACF"}
@@ -1,6 +1,5 @@
1
1
  import { g as pairedRiskDifferenceScore, h as pairedRiskDifferenceExact, p as pairedBinaryScale } from "./paired-arms-JSDcs2zx.js";
2
2
  import { a as pairedSignTest, i as pairedDeltaTieFraction, r as pairedBootstrap } from "./paired-tests-C8iCsioC.js";
3
- import { c as validateRunRecord } from "./run-record-tg_5H2IK.js";
4
3
  //#region src/paired-delta-test.ts
5
4
  /** Smallest all-positive sample that can clear a one-sided exact sign test. */
6
5
  function minimumPairsForPairedDeltaTest(confidence = .95) {
@@ -247,218 +246,6 @@ function fmt(x) {
247
246
  return x.toFixed(4);
248
247
  }
249
248
  //#endregion
250
- //#region src/campaign/run-record.ts
251
- /**
252
- * A campaign cell carried a judge score without a `dimensions` record.
253
- *
254
- * Two exported types share the name `JudgeScore`: the campaign verdict
255
- * (`{ dimensions, composite, notes }` from `@tangle-network/agent-eval/campaign`)
256
- * and the root export's flat per-dimension row (`{ judgeName, dimension, score }`
257
- * from `@tangle-network/agent-eval`). Campaign aggregation accepts only the
258
- * campaign shape; the flat shape previously crashed here with an opaque
259
- * TypeError deep inside aggregation.
260
- */
261
- var CampaignJudgeScoreShapeError = class extends TypeError {
262
- constructor(judgeName) {
263
- super(`campaign cell judge '${judgeName}' carries a score without a 'dimensions' record. Use the campaign JudgeScore ({ dimensions, composite, notes }) from '@tangle-network/agent-eval/campaign'; the root export's JudgeScore ({ judgeName, dimension, score }) is a different type with the same name.`);
264
- this.name = "CampaignJudgeScoreShapeError";
265
- }
266
- };
267
- /**
268
- * Project one campaign cell into the canonical run format.
269
- *
270
- * A dispatch error establishes terminal execution failure. A judge error only
271
- * establishes that quality measurement failed after dispatch completed.
272
- * Failures without a stage remain unknown. No failure becomes a zero-quality
273
- * label.
274
- */
275
- function campaignCellToRunRecord(cell, options) {
276
- const quality = projectCampaignCellQuality(cell);
277
- const execution = campaignCellExecutionEvidence(cell);
278
- const judgeErrorCount = Math.max(quality.raw.judge_error_count ?? 0, execution.judgeErrorCount ?? 0);
279
- const cellCostProvenance = campaignCellCostProvenance(cell);
280
- const costProvenance = cellCostProvenance.kind === "uncaptured" && options.defaultCostUsd !== void 0 ? {
281
- kind: "estimated",
282
- usd: options.defaultCostUsd
283
- } : cellCostProvenance;
284
- const costUsd = costProvenance.kind === "uncaptured" ? null : costProvenance.usd;
285
- const raw = {
286
- ...finiteMetrics(options.raw),
287
- ...quality.raw,
288
- rep: cell.rep,
289
- duration_ms: cell.durationMs,
290
- ...costUsd === null ? {} : { cost_usd: costUsd },
291
- ...cellCostProvenance.kind === "uncaptured" ? { cost_known_subtotal_usd: cell.costUsd } : {},
292
- cost_observed: costProvenance.kind === "observed" ? 1 : 0,
293
- cost_estimated: costProvenance.kind === "estimated" ? 1 : 0,
294
- cost_uncaptured: costProvenance.kind === "uncaptured" ? 1 : 0,
295
- tokens_input: cell.tokenUsage.input,
296
- tokens_output: cell.tokenUsage.output,
297
- tokens_known: cell.tokenUsage.tokensKnown === false ? 0 : 1,
298
- latency_ms: cell.durationMs,
299
- ...execution.executionErrorCount === void 0 ? {} : { execution_error_count: execution.executionErrorCount },
300
- ...judgeErrorCount > 0 ? { judge_error_count: judgeErrorCount } : {},
301
- ...execution.unclassifiedErrorCount === void 0 ? {} : { unclassified_error_count: execution.unclassifiedErrorCount }
302
- };
303
- if (typeof cell.generation === "number") raw.generation = cell.generation;
304
- if (cell.tokenUsage.reasoning !== void 0) raw.tokens_reasoning = cell.tokenUsage.reasoning;
305
- if (cell.tokenUsage.cached !== void 0) raw.tokens_cached = cell.tokenUsage.cached;
306
- if (cell.tokenUsage.cacheWrite !== void 0) raw.tokens_cache_write = cell.tokenUsage.cacheWrite;
307
- if (cell.tokenUsage.tokensKnown !== false && costUsd !== null && costUsd > 0) raw.tokens_per_dollar = (cell.tokenUsage.input + cell.tokenUsage.output) / costUsd;
308
- if (costUsd !== null && quality.score !== void 0 && quality.score > .01) raw.cost_per_quality = costUsd / quality.score;
309
- const outcome = {
310
- raw,
311
- ...quality.judgeScores ? { judgeScores: quality.judgeScores } : {}
312
- };
313
- if (quality.score !== void 0) if (options.splitTag === "holdout") outcome.holdoutScore = quality.score;
314
- else outcome.searchScore = quality.score;
315
- return validateRunRecord({
316
- runId: options.runId,
317
- experimentId: options.experimentId,
318
- candidateId: options.candidateId,
319
- seed: options.seed ?? cell.seed,
320
- model: options.model,
321
- promptHash: options.promptHash,
322
- configHash: options.configHash,
323
- commitSha: options.commitSha,
324
- wallMs: cell.durationMs,
325
- costUsd,
326
- costProvenance,
327
- tokenUsage: { ...cell.tokenUsage },
328
- terminalOutcome: execution.terminalOutcome,
329
- ...execution.terminalFailureReason ? { terminalFailureReason: execution.terminalFailureReason } : {},
330
- outcome,
331
- splitTag: options.splitTag,
332
- scenarioId: options.scenarioId ?? cell.scenarioId,
333
- ...options.agentProfile ? { agentProfile: options.agentProfile } : {}
334
- });
335
- }
336
- /**
337
- * Validate the cost fields that cross campaign cache and RunRecord boundaries.
338
- * `costUsd` is a known subtotal for uncaptured cells, but it must equal the
339
- * authoritative total whenever that total is observed or estimated.
340
- */
341
- function campaignCellCostProvenance(cell) {
342
- if (!Number.isFinite(cell.costUsd) || cell.costUsd < 0) throw new Error(`campaign cell '${cell.cellId}' has invalid costUsd`);
343
- const provenance = cell.costProvenance;
344
- if (!provenance || typeof provenance !== "object") throw new Error(`campaign cell '${cell.cellId}' has no costProvenance`);
345
- if (provenance.kind === "uncaptured") {
346
- if (provenance.usd !== null) throw new Error(`campaign cell '${cell.cellId}' has invalid uncaptured costProvenance`);
347
- return {
348
- kind: "uncaptured",
349
- usd: null
350
- };
351
- }
352
- if (provenance.kind !== "observed" && provenance.kind !== "estimated" || !Number.isFinite(provenance.usd) || provenance.usd < 0) throw new Error(`campaign cell '${cell.cellId}' has invalid costProvenance`);
353
- if (provenance.usd !== cell.costUsd) throw new Error(`campaign cell '${cell.cellId}' has costUsd inconsistent with costProvenance`);
354
- return {
355
- kind: provenance.kind,
356
- usd: provenance.usd
357
- };
358
- }
359
- function campaignCellExecutionEvidence(cell) {
360
- if (cell.errorStage === "dispatch") return {
361
- terminalOutcome: "failed",
362
- executionErrorCount: 1,
363
- ...cell.error ? { terminalFailureReason: cell.error } : {}
364
- };
365
- if (cell.errorStage === "judge") return {
366
- terminalOutcome: "succeeded",
367
- executionErrorCount: 0,
368
- judgeErrorCount: 1
369
- };
370
- if (!cell.error) return {
371
- terminalOutcome: "succeeded",
372
- executionErrorCount: 0
373
- };
374
- return {
375
- terminalOutcome: "unknown",
376
- unclassifiedErrorCount: 1
377
- };
378
- }
379
- /**
380
- * Produce the only task-quality view used by campaign aggregates and exports.
381
- *
382
- * Successful judge results remain available for diagnosis after another judge
383
- * fails, but a task score exists only for an error-free cell whose reported
384
- * judge values are all finite.
385
- */
386
- function projectCampaignCellQuality(cell) {
387
- if (cell.errorStage === "dispatch") return {
388
- successfulJudgeScores: {},
389
- failedJudges: [],
390
- raw: {}
391
- };
392
- const perJudge = {};
393
- const successfulJudgeScores = {};
394
- const dimensionValues = /* @__PURE__ */ new Map();
395
- const composites = [];
396
- const notes = [];
397
- const failedJudges = new Set(cell.errorStage === "judge" ? [cell.errorJudge ?? "unknown-judge"] : []);
398
- const raw = {};
399
- for (const [judgeName, score] of Object.entries(cell.judgeScores)) {
400
- const dimensionsShape = score.dimensions;
401
- if (typeof dimensionsShape !== "object" || dimensionsShape === null || Array.isArray(dimensionsShape)) throw new CampaignJudgeScoreShapeError(judgeName);
402
- const finiteDimensions = Object.values(score.dimensions).every(Number.isFinite);
403
- if (score.failed || !Number.isFinite(score.composite) || !finiteDimensions) {
404
- failedJudges.add(judgeName);
405
- continue;
406
- }
407
- composites.push(score.composite);
408
- successfulJudgeScores[judgeName] = score;
409
- const dimensions = { ...score.dimensions };
410
- perJudge[judgeName] = dimensions;
411
- for (const [dimension, value] of Object.entries(dimensions)) {
412
- raw[`${judgeName}.${dimension}`] = value;
413
- const values = dimensionValues.get(dimension) ?? [];
414
- values.push(value);
415
- dimensionValues.set(dimension, values);
416
- }
417
- if (score.notes) notes.push(`${judgeName}: ${score.notes}`);
418
- for (const failedJudge of score.failedJudges ?? []) failedJudges.add(`${judgeName}/${failedJudge}`);
419
- }
420
- if (failedJudges.size > 0) raw.judge_error_count = failedJudges.size;
421
- const sortedFailedJudges = [...failedJudges].sort();
422
- if (composites.length === 0) return {
423
- successfulJudgeScores,
424
- failedJudges: sortedFailedJudges,
425
- raw
426
- };
427
- const composite = mean(composites);
428
- const perDimMean = Object.fromEntries([...dimensionValues.entries()].map(([dimension, values]) => [dimension, mean(values)]));
429
- const complete = cell.error === void 0 && cell.errorStage === void 0 && failedJudges.size === 0;
430
- if (complete) raw.composite = composite;
431
- return {
432
- ...complete ? { score: composite } : {},
433
- raw,
434
- successfulJudgeScores,
435
- failedJudges: sortedFailedJudges,
436
- judgeScores: {
437
- perJudge,
438
- perDimMean,
439
- composite,
440
- ...sortedFailedJudges.length > 0 ? { failedJudges: sortedFailedJudges } : {},
441
- ...notes.length > 0 ? { notes: notes.join(" | ") } : {}
442
- }
443
- };
444
- }
445
- /** Read the canonical task score without recomputing cell quality. */
446
- function campaignCellTaskScore(cell) {
447
- return projectCampaignCellQuality(cell).score;
448
- }
449
- /** Read canonical successful judge dimensions without recomputing cell quality. */
450
- function campaignCellJudgeDimensions(cell) {
451
- return projectCampaignCellQuality(cell).judgeScores?.perJudge ?? {};
452
- }
453
- function finiteMetrics(metrics) {
454
- const finite = {};
455
- for (const [key, value] of Object.entries(metrics ?? {})) if (Number.isFinite(value)) finite[key] = value;
456
- return finite;
457
- }
458
- function mean(values) {
459
- return values.reduce((sum, value) => sum + value, 0) / values.length;
460
- }
461
- //#endregion
462
- export { campaignCellToRunRecord as a, pairedDecisionShape as c, campaignCellTaskScore as i, minimumPairsForPairedDeltaTest as l, campaignCellExecutionEvidence as n, projectCampaignCellQuality as o, campaignCellJudgeDimensions as r, decidePairedPromotion as s, campaignCellCostProvenance as t, pairedDeltaTest as u };
249
+ export { pairedDeltaTest as i, pairedDecisionShape as n, minimumPairsForPairedDeltaTest as r, decidePairedPromotion as t };
463
250
 
464
- //# sourceMappingURL=run-record-BhMVsHR1.js.map
251
+ //# sourceMappingURL=paired-promotion-decision-ClJh_9nK.js.map