@tangle-network/agent-eval 0.21.0 → 0.23.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/CHANGELOG.md +236 -1
- package/README.md +17 -3
- package/dist/benchmarks/index.d.ts +2 -2
- package/dist/{chunk-WOK2RTWG.js → chunk-4W4NCYM2.js} +134 -109
- package/dist/chunk-4W4NCYM2.js.map +1 -0
- package/dist/{chunk-WOPGKVN4.js → chunk-6KQG5HAH.js} +2 -2
- package/dist/chunk-6M774GY6.js +53 -0
- package/dist/chunk-6M774GY6.js.map +1 -0
- package/dist/chunk-7EAUOUQS.js +495 -0
- package/dist/chunk-7EAUOUQS.js.map +1 -0
- package/dist/chunk-AXHNWLIX.js +246 -0
- package/dist/chunk-AXHNWLIX.js.map +1 -0
- package/dist/chunk-EXGR4XEM.js +283 -0
- package/dist/chunk-EXGR4XEM.js.map +1 -0
- package/dist/{chunk-3IX6QTB7.js → chunk-IOXMGMHQ.js} +418 -541
- package/dist/chunk-IOXMGMHQ.js.map +1 -0
- package/dist/{chunk-3GN6U53I.js → chunk-KAO3Q65R.js} +2 -2
- package/dist/chunk-LZKIOBG2.js +2026 -0
- package/dist/chunk-LZKIOBG2.js.map +1 -0
- package/dist/{chunk-YUFXO3TU.js → chunk-QBW3YBTR.js} +1 -1
- package/dist/chunk-QBW3YBTR.js.map +1 -0
- package/dist/chunk-QUKKGHTZ.js +121 -0
- package/dist/chunk-QUKKGHTZ.js.map +1 -0
- package/dist/{chunk-SNUHRBDL.js → chunk-SQQLHODJ.js} +10 -1
- package/dist/{chunk-SNUHRBDL.js.map → chunk-SQQLHODJ.js.map} +1 -1
- package/dist/{chunk-ARZ6BEV6.js → chunk-V5QSWN7L.js} +2 -2
- package/dist/{chunk-HRZELXCR.js → chunk-VQQSPGSM.js} +3 -3
- package/dist/cli.js +3 -3
- package/dist/{control-cxwMOAsy.d.ts → control-DvkH87qJ.d.ts} +2 -2
- package/dist/control.d.ts +3 -3
- package/dist/control.js +2 -2
- package/dist/eval-campaign-Ds5QljIh.d.ts +573 -0
- package/dist/{feedback-trajectory-CB0A32o3.d.ts → feedback-trajectory-c43WGtTX.d.ts} +1 -1
- package/dist/{index-c5saLbKD.d.ts → index-DDTlbHEK.d.ts} +1 -1
- package/dist/index-ekBXweiQ.d.ts +1894 -0
- package/dist/index.d.ts +20 -430
- package/dist/index.js +154 -34
- package/dist/index.js.map +1 -1
- package/dist/integrity-Cr5YodSY.d.ts +210 -0
- package/dist/openapi.json +1 -1
- package/dist/optimization.d.ts +7 -145
- package/dist/optimization.js +12 -3
- package/dist/reporting.d.ts +294 -4
- package/dist/reporting.js +18 -9
- package/dist/rl.d.ts +8 -0
- package/dist/rl.js +113 -0
- package/dist/rl.js.map +1 -0
- package/dist/{run-record-CX_jcAyr.d.ts → run-record-DNiOMBrZ.d.ts} +10 -1
- package/dist/sequential-DgU2mFsE.d.ts +304 -0
- package/dist/{multi-shot-optimization-Bvtz294B.d.ts → summary-report-Ce1r4EYo.d.ts} +382 -2
- package/dist/traces.d.ts +101 -181
- package/dist/traces.js +19 -8
- package/dist/wire/index.js +3 -3
- package/docs/auto-research-loop-end-to-end.md +186 -0
- package/docs/research-report-methodology.md +19 -4
- package/docs/three-package-architecture.md +180 -0
- package/docs/wire-protocol.md +1 -1
- package/package.json +7 -2
- package/dist/chunk-3IX6QTB7.js.map +0 -1
- package/dist/chunk-KRR4VMH7.js +0 -423
- package/dist/chunk-KRR4VMH7.js.map +0 -1
- package/dist/chunk-WOK2RTWG.js.map +0 -1
- package/dist/chunk-YUFXO3TU.js.map +0 -1
- package/dist/reporting-Da2ihlcM.d.ts +0 -672
- /package/dist/{chunk-WOPGKVN4.js.map → chunk-6KQG5HAH.js.map} +0 -0
- /package/dist/{chunk-3GN6U53I.js.map → chunk-KAO3Q65R.js.map} +0 -0
- /package/dist/{chunk-ARZ6BEV6.js.map → chunk-V5QSWN7L.js.map} +0 -0
- /package/dist/{chunk-HRZELXCR.js.map → chunk-VQQSPGSM.js.map} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,238 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.23.0 — RL primitives + auto-research worked example
|
|
4
|
+
|
|
5
|
+
In addition to the RL bridge primitives below, this release ships the
|
|
6
|
+
canonical worked example of the auto-research loop end-to-end against
|
|
7
|
+
agent-builder, plus a concrete prime-rl SFT integration. The auto-research
|
|
8
|
+
thesis — capture → score → preferences → mutate → improved candidate —
|
|
9
|
+
is now demonstrably real, not aspirational.
|
|
10
|
+
|
|
11
|
+
### Added (worked examples)
|
|
12
|
+
|
|
13
|
+
- **`examples/auto-research-with-agent-builder/`** — runnable demo of the
|
|
14
|
+
closed loop: a synthetic agent-builder driver iterates 4 generations
|
|
15
|
+
of prompt variants, with each generation's runs feeding
|
|
16
|
+
`analyzeOptimizationResult` for preferences + reward-hacking + sequential
|
|
17
|
+
verdict, and the next generation proposed via a deterministic mutator.
|
|
18
|
+
The demo shows score climbing from 0.739 → 0.973 over 4 iterations on
|
|
19
|
+
the synthetic environment. Real-driver mode (replace the synthetic
|
|
20
|
+
runner with `runForgeBuilderSim` from `agent-builder`) is documented
|
|
21
|
+
inline.
|
|
22
|
+
- **`examples/fine-tune-with-prime-rl/`** — concrete integration with
|
|
23
|
+
Prime Intellect's prime-rl SFT trainer. Reads `RunRecord[]` (NDJSON),
|
|
24
|
+
filters to high-quality runs, projects via `toSftRows` to messages-list
|
|
25
|
+
JSONL, writes a 15-line prime-rl SFT TOML config, prints the runnable
|
|
26
|
+
command. ~150 LoC of glue. SFT was chosen as the first integration
|
|
27
|
+
because it's the cleanest fit between agent-eval's exporters and
|
|
28
|
+
prime-rl's entrypoints (DPO/PRM go to TRL; offline GRPO requires a
|
|
29
|
+
custom verifiers env — both called out in the README).
|
|
30
|
+
- **`docs/three-package-architecture.md`** — the contracts between
|
|
31
|
+
agent-eval, agent-knowledge, agent-runtime. Dependency direction (both
|
|
32
|
+
consume agent-eval; agent-eval imports neither), shared data
|
|
33
|
+
interchange (RunRecord, Scenario, KnowledgeBundle), and known
|
|
34
|
+
contract gaps tracked as follow-ups.
|
|
35
|
+
- **`docs/auto-research-loop-end-to-end.md`** — the runnable composition
|
|
36
|
+
pattern with the explicit invariants every iteration must preserve
|
|
37
|
+
(canonical RunRecord with scenarioId, capture wired by construction,
|
|
38
|
+
stable comparator, deterministic mutator).
|
|
39
|
+
|
|
40
|
+
### Added (RL primitives)
|
|
41
|
+
|
|
42
|
+
0.22 made eval rigorous and integrated; 0.23 closes the loop back to RL training. The package now ships the canonical primitives a working RL-on-LLM-agents team needs — verifiable rewards, preference extraction, off-policy evaluation, process reward scaffolding, contamination probing, Bradley-Terry / Elo tournaments, adversarial scenario search, and test-time compute scaling — all designed to consume the standardised `RunRecord` artifact 0.22 produced. The auto-research loop is now coherent end-to-end.
|
|
43
|
+
|
|
44
|
+
#### RL barrel — `@tangle-network/agent-eval/rl` (new subpath)
|
|
45
|
+
|
|
46
|
+
A single subpath for every RL-shaped primitive, importable as a unit. The 9 modules:
|
|
47
|
+
|
|
48
|
+
1. **`run-record-adapters.ts`** — convert `TrialResult[]` (from `runPromptEvolution` / `runMultiShotOptimization`), `VerificationReport` (from `MultiLayerVerifier`), and `VariantAggregate` into canonical `RunRecord[]`. Closes the integration gap between the pre-0.22 optimization stack and the post-0.22 campaign artifact. Existing optimization runs become `replayCache`-able and `rubricPredictiveValidity`-scorable for free.
|
|
49
|
+
|
|
50
|
+
2. **`verifiable-reward.ts`** — extract a clean `VerifiableReward` from `VerificationReport` or `RunRecord`. Distinguishes `'deterministic'` (compile, test, schema, sandbox) from `'probabilistic'` (judge) reward sources. The seam every credible 2025-2026 frontier RL result on coding agents leans on (DeepSeek-R1 GRPO on test pass-rate, AlphaProof on Lean kernel checking).
|
|
51
|
+
|
|
52
|
+
3. **`preferences.ts`** — `extractPreferences(runRecords)` produces DPO/PPO/KTO-shape `(chosen, rejected)` triples with three documented strategies (`paired-by-scenario-and-seed`, `paired-by-scenario`, `top-vs-bottom`). Bridge from campaign artifact to RL training. Includes `toTRLFormat` and `toAnthropicFormat` adapters.
|
|
53
|
+
|
|
54
|
+
4. **`off-policy.ts`** — IPS, SNIPS, doubly-robust off-policy estimators (Dudík–Langford–Li 2011 for DR, Owen 2013 for SNIPS SE). Caller supplies behavior + target propensity scores (typically from token log-probs). All three return matched-shape `OffPolicyEstimate` with effective-sample-size and max-importance-weight diagnostics. `offPolicyEstimateAll` runs all three side-by-side — agreement across estimators is a much stronger signal than any one alone.
|
|
55
|
+
|
|
56
|
+
5. **`process-reward.ts`** — step-level credit assignment from trace spans. `extractStepRewards(store, runId, scorers)` produces `StepReward[]`; `prmTrainingPairs(stepRewardsByRun)` produces `(prefix, chosen_step, rejected_step)` triples in the canonical Lightman et al. / DeepSeek-R1 process supervision shape. We ship the data extraction, not the trainer — gradient descent over a transformer is out of scope for a TS package.
|
|
57
|
+
|
|
58
|
+
6. **`contamination.ts`** — held-out perturbation contamination probe. `runContaminationProbe({ originals, perturbation, scoreFn })` runs the policy against original + perturbed scenarios, computes paired Wilcoxon on the deltas, and flags suspected contamination when median drop ≥ 5pp at p < 0.05. Stock perturbations: `renameVariables`, `shuffleOrder`, `injectIrrelevantClause`. Catches the SWE-Bench → SWE-Bench-Verified failure mode upstream.
|
|
59
|
+
|
|
60
|
+
7. **`tournament.ts`** — `fitBradleyTerry(outcomes)` uses Hunter's MM algorithm to recover candidate strengths from pairwise outcomes; `applyEloUpdate(ratings, outcome)` for online updates with FIDE-style K-factor. `buildPairwiseFromCampaign` extracts pairwise outcomes from per-scenario campaign runs. Sample-efficient ranking for many-candidate sweeps; the methodology Chatbot Arena and AlpacaEval converged on.
|
|
61
|
+
|
|
62
|
+
8. **`adversarial.ts`** — `adversarialScenarioSearch({ seeds, mutations, scoreFn })` actively searches for inputs the policy fails on. Hill-climb-against-failure-indicator loop (the simplest version of AdA / POET / auto-jailbreak rigs). Caller supplies mutation strategies; the harness deduplicates, budgets, and reports per-generation statistics.
|
|
63
|
+
|
|
64
|
+
9. **`compute-curves.ts`** — characterize a candidate as a *curve* across compute budgets, not a point. `runComputeCurve` produces `(cost, score)` points + log-slope. `bestOfN`, `selfConsistency` are the canonical test-time-scaling primitives (Snell et al. 2024). `paretoFrontier` removes dominated (candidate, compute) combinations. Required for honest cost-quality reporting in the o1-era.
|
|
65
|
+
|
|
66
|
+
#### RL barrel — additional experimental modules
|
|
67
|
+
|
|
68
|
+
The 9 modules above are stable and tested. The following modules are also shipped under `@tangle-network/agent-eval/rl` as **experimental** — interfaces are reasonable but may evolve based on real production consumer feedback. Marked clearly in the barrel docstring; flagged here so consumers know the contract may shift.
|
|
69
|
+
|
|
70
|
+
10. **`active-curriculum.ts`** — adaptive scenario allocation. `varianceBasedCurriculum` (Neyman 1934 optimal allocation: weight ∝ √variance + 1/√n for under-sampled-cell tie-break) and `thompsonCurriculum` (Beta-Bernoulli posterior + decision-threshold-weighted sampling) reallocate next-round budget toward cells whose outcome is uncertain.
|
|
71
|
+
|
|
72
|
+
11. **`reward-hacking.ts`** — `detectRewardHacking({ runs, truthOf })` watches four signature signals (proxy-vs-truth divergence, distributional shift, reward disagreement between independent rewards, judge drift relative to deterministic reward) and returns a structured `'clean' | 'suspect' | 'gaming'` verdict with per-signal severity. Krakovna et al. + Skalse et al. 2022 + Kim et al. 2023 lineage.
|
|
73
|
+
|
|
74
|
+
12. **`adaptation-eval.ts`** — `runAdaptationCurve` and `compareAdaptationCurves` for sample-efficient adaptation evaluation. The metric a foundation-model-based agent should be measured on isn't end-state performance but the curve of score vs k (k=0, 1, 2, 4, 8, 16 demonstrations). Returns area-under-curve summary + per-k bootstrap CIs.
|
|
75
|
+
|
|
76
|
+
13. **`exporters.ts`** — trainer-format export functions. `toDpoRows` (HuggingFace TRL DPO/IPO/KTO format), `toGrpoRows` (offline GRPO `{prompt, completions[], rewards[]}`), `toSftRows` (TRL/prime-rl SFT messages list), `toPrmRows` (Lightman-style PRM training shape), `stepRewardsToJsonl` (step-level rewards for value-function regression). **Honest scope:** `toSftRows` is the only one that maps directly onto a prime-rl entrypoint; the others target TRL or custom trainers — see `examples/fine-tune-with-prime-rl/README.md` for the explicit fit table.
|
|
77
|
+
|
|
78
|
+
14. **`rl-campaign.ts`** — `runRLCampaign(opts)` wraps `runEvalCampaign` and runs the full RL bridge (verifiable rewards + preferences + sequential interim verdict + reward-hacking + optional predictive validity + optional trainer export) in one call. The single top-level orchestrator the pre-0.23 audit panel called out as missing.
|
|
79
|
+
|
|
80
|
+
15. **`auto-research.ts`** — `analyzeOptimizationResult({ result, ctx, comparator })` takes a `PromptEvolutionResult` or `MultiShotOptimizationResult` (the existing GEPA/AxRLM stack outputs) and runs the same RL bridge on top, producing a unified artifact. Closes the architectural fragmentation between the optimization primitives and the RL bridge.
|
|
81
|
+
|
|
82
|
+
16. **`predictive-validity-researcher.ts`** — `PredictiveValidityResearcher` is a concrete `Researcher` interface implementation (the interface had been a placeholder + `NoopResearcher` until now). Drives steering changes from outcome-anchored predictive validity: rubrics that don't predict deployment outcomes get down-weighted; load-bearing rubrics get up-weighted.
|
|
83
|
+
|
|
84
|
+
17. **`run-record.ts`** — `RunRecord.scenarioId` is now an optional canonical field (was previously inferred from `outcome.raw.scenario_id`). Populated automatically by `runEvalCampaign` and the optimization adapters; legacy `RunRecord[]` arrays without it fall back to the `outcome.raw.scenario_id` convention. Closes the fragility called out by the 0.23 audit.
|
|
85
|
+
|
|
86
|
+
#### Build / surface
|
|
87
|
+
|
|
88
|
+
- New build entry: `dist/rl.{js,d.ts}` exposed via the `@tangle-network/agent-eval/rl` package subpath.
|
|
89
|
+
- All RL primitives also re-exported from the root barrel for ergonomic single-import use.
|
|
90
|
+
- Default `BradleyTerry` smoothing raised from 0 to 0.1 — Hunter's MM degenerates when a candidate has zero wins; 0.1 keeps the iteration well-conditioned without meaningfully biasing real win counts.
|
|
91
|
+
|
|
92
|
+
### Why
|
|
93
|
+
|
|
94
|
+
The previous release shipped EvalCampaign + replay + sequential + outcome calibration as parallel infrastructure to the existing optimization primitives. That left a real gap: `runMultiShotOptimization` and `runPromptEvolution` produced their own trial shapes that didn't compose with the new artifacts. 0.23 closes that gap with the adapter layer, and ships the eight downstream primitives that turn the unified artifact into RL training data, OPE estimates, contamination probes, tournament rankings, adversarial scenarios, and compute curves.
|
|
95
|
+
|
|
96
|
+
After 0.23, the auto-research loop is coherent end-to-end:
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
mutate (existing primitives)
|
|
100
|
+
→ trial outcomes (TrialResult)
|
|
101
|
+
→ adapter (run-record-adapters)
|
|
102
|
+
→ RunRecord[] (canonical artifact)
|
|
103
|
+
→ preferences / verifiable rewards / OPE / step rewards
|
|
104
|
+
→ policy update (consumer's choice of TRL / GRPO / PPO / DPO)
|
|
105
|
+
→ next sweep
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### References
|
|
109
|
+
|
|
110
|
+
- Dudík, M., Langford, J., Li, L. (2011). Doubly Robust Policy Evaluation and Learning. *ICML*.
|
|
111
|
+
- Owen, A. B. (2013). *Monte Carlo Theory, Methods and Examples*. Ch. 9 — Importance Sampling.
|
|
112
|
+
- Hunter, D. R. (2004). MM algorithms for generalized Bradley-Terry models. *Annals of Statistics*, 32(1), 384–406.
|
|
113
|
+
- Bradley, R. A., Terry, M. E. (1952). Rank analysis of incomplete block designs. *Biometrika*, 39(3/4).
|
|
114
|
+
- Lightman, H. et al. (2023). Let's Verify Step by Step. *arXiv:2305.20050*.
|
|
115
|
+
- Snell, C. et al. (2024). Scaling LLM Test-Time Compute Optimally. *arXiv:2408.03314*.
|
|
116
|
+
- Plus the foundational citations from 0.21 / 0.22.
|
|
117
|
+
|
|
118
|
+
### Migration
|
|
119
|
+
|
|
120
|
+
All 0.23 primitives are additive. Existing consumers don't need to change. Recommended adoption sequence:
|
|
121
|
+
|
|
122
|
+
1. Add `trialsToRunRecords(trials, ctx)` after every existing optimization sweep — every old run becomes replay-able and predictive-validity-scorable for free.
|
|
123
|
+
2. Wire `extractVerifiableReward` into your scoring pipeline; route deterministic and probabilistic rewards into separate training batches.
|
|
124
|
+
3. Use `extractPreferences` to produce DPO/PPO triples for any RL training the consumer runs.
|
|
125
|
+
4. Run `rubricPredictiveValidity` quarterly + `runContaminationProbe` per release to keep the rubric weights honest.
|
|
126
|
+
5. Replace fixed-comparator HeldOutGate with `fitBradleyTerry` once you have ≥ 5 candidates running on shared scenarios.
|
|
127
|
+
6. Replace single-budget evaluation with `runComputeCurve` for any candidate where compute scaling is a question.
|
|
128
|
+
|
|
129
|
+
### Caveats and out-of-scope
|
|
130
|
+
|
|
131
|
+
- The DR estimator's Q-function is caller-supplied. We don't ship a learned Q-function trainer — that's a regression problem with too many domain-specific choices to ship a default.
|
|
132
|
+
- PRM training itself (gradient descent over a transformer) is out of scope; we ship the data extraction shape.
|
|
133
|
+
- The contamination probe's per-scenario q-values use a heuristic pseudo-p (the load-bearing test is the global Wilcoxon).
|
|
134
|
+
- `prmTrainingPairs` matches trajectories by step name + kind; production use should replace this with a token-level prefix hash.
|
|
135
|
+
- Adversarial scenario search is a simple hill-climb; novel scenario synthesis (compositional, language-model-driven) is future work.
|
|
136
|
+
|
|
137
|
+
## 0.22.0 — EvalCampaign + replay + always-valid + outcome calibration
|
|
138
|
+
|
|
139
|
+
0.21 shipped the four capture-integrity primitives as opt-in. Every consumer still had to wire them by hand, and the bug class blueprint-agent reported (forgotten wiring → silent partial-capture) reappears the moment a new consumer adopts agent-eval cold. **0.22 makes the right thing the default path** — and adds three primitives that compound on top of standardized capture: replay-from-raw-events, anytime-valid sequential evaluation, and rubric predictive validity. The four primitives together turn agent-eval from a TS framework into research-grade evaluation infrastructure.
|
|
140
|
+
|
|
141
|
+
### Added
|
|
142
|
+
|
|
143
|
+
#### `runEvalCampaign` — capture integrity by construction
|
|
144
|
+
|
|
145
|
+
Opinionated matrix runner that wires the four directives by construction. Inputs: variants, scenarios, seeds, an `LlmClientOptions`, factories for `TraceStore` and `RawProviderSink`, and a `runner(ctx)` callback. Outputs: per-cell `RunRecord[]`, `RunIntegrityReport[]`, optional `researchReport`, and a campaign fingerprint.
|
|
146
|
+
|
|
147
|
+
- **Preflight:** `assertLlmRoute` is called once before any work, with `{ requireExplicitBaseUrl: true, requireAuth: true }` defaults. Misconfigured routes never burn a run.
|
|
148
|
+
- **Per run:** the campaign constructs the `TraceStore`, `RawProviderSink`, and `TraceEmitter` (with `onRunComplete` hooks attached), then hands the runner an `LlmClientOptions` already pre-wired with `rawSink` + `traceContext`. The runner cannot accidentally call an LLM without capture.
|
|
149
|
+
- **Run-completion:** `assertRunCaptured` runs after every `endRun` with `{ llmSpansMin: 1, requireRawCoverageOfLlmSpans: true, requireOutcome: true }` defaults. Failures are routed via `onIntegrityFailure: 'throw' | 'mark_failed' | 'log'` (default `'mark_failed'`).
|
|
150
|
+
- **End of campaign:** if `report.comparator` is set, computes `researchReport` over the collected `RunRecord`s and embeds the campaign fingerprint + `preregistrationHash`.
|
|
151
|
+
- **Concurrency:** local async worker pool, default 1, configurable via `concurrency`.
|
|
152
|
+
- **Determinism:** the default `runId` generator is a stable hash of `(campaignId, variantId, scenarioId, seed)`, so re-running the same campaign produces the same ids; override `runId` for non-deterministic generation.
|
|
153
|
+
|
|
154
|
+
Exported from the root barrel and the `@tangle-network/agent-eval/optimization` subpath: `runEvalCampaign`, `CampaignRunner`, `CampaignRunContext`, `CampaignRunOutcome`, `CampaignVariant`, `CampaignScenario`, `EvalCampaignOptions`, `EvalCampaignResult`, `FailedRun`, `CampaignIntegrityPolicy`, `CampaignFactoryParams`.
|
|
155
|
+
|
|
156
|
+
#### Replay-from-raw-events
|
|
157
|
+
|
|
158
|
+
Every campaign run is now a re-runnable artifact. `ReplayCache.fromSink(sink)` turns a populated `RawProviderSink` into a deterministic `(canonicalised request → cached response)` map; `createReplayFetch(cache)` returns a `fetch`-shaped function that satisfies `/chat/completions` calls out of the cache and passes other URLs through.
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
const cache = await ReplayCache.fromSink(yesterdayRawSink)
|
|
162
|
+
const replayFetch = createReplayFetch(cache, { onMiss: 'fail-closed' })
|
|
163
|
+
await callLlm(req, { ...llmOpts, fetch: replayFetch }) // zero LLM cost
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Use cases:
|
|
167
|
+
|
|
168
|
+
- Post-hoc judging — apply a new judge or scorer to last week's runs without burning a single token.
|
|
169
|
+
- Determinism audits — replay a campaign and verify the responses match byte-for-byte.
|
|
170
|
+
- Free judge calibration — run two judges on identical responses and measure agreement.
|
|
171
|
+
|
|
172
|
+
`onMiss` is `'throw' | 'fallback' | 'fail-closed'`. The cache hashes a canonical projection (`model + messages + temperature + max_tokens|max_completion_tokens + response_format`) so insertion-order quirks don't cause spurious misses.
|
|
173
|
+
|
|
174
|
+
Exported from root and `@tangle-network/agent-eval/traces`: `ReplayCache`, `createReplayFetch`, `iterateRawCalls`, `ReplayCacheEntry`, `ReplayCacheStats`, `ReplayFetchOptions`, `ReplayCacheMissError`.
|
|
175
|
+
|
|
176
|
+
#### Always-valid sequential evaluation
|
|
177
|
+
|
|
178
|
+
`pairedEvalueSequence(deltas, opts)` and `evaluateInterimReleaseConfidence({ deltaSeries })` ship the predictable plug-in betting martingale of Waudby-Smith & Ramdas (2024) for paired bounded outcomes, plus the empirical Bernstein confidence sequence of Howard et al. (2021) for the running mean. Both are *anytime-valid* — type-I error is bounded by α at every stopping time, no peeking penalty.
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
const verdict = evaluateInterimReleaseConfidence({
|
|
182
|
+
deltaSeries: [{ candidateId: 'cand', deltas }],
|
|
183
|
+
alpha: 0.05,
|
|
184
|
+
rope: { low: -0.02, high: 0.02 },
|
|
185
|
+
})
|
|
186
|
+
// → { recommendation: { decision: 'promote_now' | 'continue' | 'reject_now' | 'equivalent', candidateId } }
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
This closes the methodological hole flagged in the 0.21 methodology doc as out-of-scope. Consumers running rolling campaigns can now ship the moment evidence is decisive, stop-early on dead-on-arrival variants, and accumulate evidence across partial runs without spending the FDR budget. Tested under-the-null at α=0.05 on 100 synthetic series; false-rejection rate stays below the bound.
|
|
190
|
+
|
|
191
|
+
Exported from root and `@tangle-network/agent-eval/reporting`: `pairedEvalueSequence`, `evaluateInterimReleaseConfidence`, `PairedEvalueOptions`, `PairedEvalueSequence`, `PairedEvalueStep`, `InterimReleaseConfidence`, `InterimReleaseConfidenceInput`, `SequentialDecision`.
|
|
192
|
+
|
|
193
|
+
#### Rubric predictive validity
|
|
194
|
+
|
|
195
|
+
`rubricPredictiveValidity({ runs, outcomes, outcomeMetrics })` joins canonical campaign `RunRecord`s to a `DeploymentOutcomeStore` and reports per-rubric Pearson + Spearman + bootstrap CI against each outcome metric. Verdict bucketing: `'load_bearing' | 'informative' | 'decorative'` based on `|spearman|`. **Without this loop every rubric is faith-based;** with it, you know which rubrics earn their promotion power and which are decoration.
|
|
196
|
+
|
|
197
|
+
```ts
|
|
198
|
+
const validity = await rubricPredictiveValidity({
|
|
199
|
+
runs: lastQuarterRuns,
|
|
200
|
+
outcomes: shipFlagOutcomeStore,
|
|
201
|
+
outcomeMetrics: ['revenue_lift', 'retention_30d', 'csat'],
|
|
202
|
+
})
|
|
203
|
+
for (const r of validity.ranked) {
|
|
204
|
+
console.log(`${r.rubric} → ${r.bestOutcome}: ρ=${r.spearman.toFixed(2)} (${r.verdict})`)
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Builds on the existing `correlationStudy` primitive but works directly off `RunRecord` (the canonical campaign artifact) rather than `Run` from a `TraceStore`, so it composes cleanly with `runEvalCampaign`'s output. Returns a per-rubric ranking + every (rubric, outcome) pair tested + a list of rubrics that produced no usable data.
|
|
209
|
+
|
|
210
|
+
Exported from root and `@tangle-network/agent-eval/reporting`: `rubricPredictiveValidity`, `RubricOutcomePair`, `RubricRanking`, `RubricPredictiveValidityInput`, `RubricPredictiveValidityReport`. The existing `correlationStudy`, `OutcomeStore`, `InMemoryOutcomeStore`, `FileSystemOutcomeStore` continue to work unchanged.
|
|
211
|
+
|
|
212
|
+
#### `NoopRawProviderSink.list()` returns `[]`
|
|
213
|
+
|
|
214
|
+
Explicit opt-out from capture is no longer flagged by `assertRunCaptured` as `no_raw_sink`. Opt-out remains a deliberate choice; the campaign still requires the matching integrity overrides.
|
|
215
|
+
|
|
216
|
+
### Why
|
|
217
|
+
|
|
218
|
+
Every consumer that adopted agent-eval before 0.22 wrote their own matrix runner, and every one of them re-introduced the same forgettable wiring (raw sink, route guard, integrity assertion, analyst hook). 0.21 documented the pattern; 0.22 owns it. The four new primitives compound:
|
|
219
|
+
|
|
220
|
+
- `runEvalCampaign` standardises the artifact (`RunRecord` + raw events + fingerprint).
|
|
221
|
+
- Replay turns every past run into free training/validation data for new judges.
|
|
222
|
+
- Sequential evaluation makes "ship-when-evidence-says-so" mathematically defensible.
|
|
223
|
+
- Predictive validity converts evals from belief-based to outcome-anchored.
|
|
224
|
+
|
|
225
|
+
`runMultiShotOptimization` remains the right primitive for trajectory-shaped GEPA optimization sweeps; `runPromptEvolution` for prompt + code evolution loops with sandbox pools; `runEvalCampaign` for the "compare N variants on M scenarios with K seeds and tell me which to ship" case that makes up the bulk of consumer evals.
|
|
226
|
+
|
|
227
|
+
### References
|
|
228
|
+
|
|
229
|
+
- Howard, S. R., Ramdas, A., McAuliffe, J., Sekhon, J. (2021). Time-uniform, nonparametric, nonasymptotic confidence sequences. *Annals of Statistics*, 49(2), 1055–1080.
|
|
230
|
+
- Waudby-Smith, I., Ramdas, A. (2024). Estimating means of bounded random variables by betting. *JRSS B*, 86(1), 1–27.
|
|
231
|
+
|
|
232
|
+
### Migration
|
|
233
|
+
|
|
234
|
+
Existing consumers do not need to change. All four primitives are additive. Recommended path: on the next eval-runner refactor, replace hand-rolled matrix loops with `runEvalCampaign`. Use `evaluateInterimReleaseConfidence` for any campaign you run on a recurring cadence. Wire `rubricPredictiveValidity` once you have ≥ 30 deployment outcomes joinable by `runId`. Replay is a free win — once campaigns are running, every eval R&D loop drops to CPU-bound.
|
|
235
|
+
|
|
3
236
|
## 0.21.0 — capture integrity + launch-grade reporting
|
|
4
237
|
|
|
5
238
|
This release closes the layer-1 gap a downstream consumer surfaced: better
|
|
@@ -74,7 +307,9 @@ surface.
|
|
|
74
307
|
|
|
75
308
|
### Python client
|
|
76
309
|
|
|
77
|
-
|
|
310
|
+
The PyPI distribution renamed from `tangle-agent-eval` to **`agent-eval-rpc`**, and the import path from `tangle_agent_eval` to `agent_eval_rpc`. The new name accurately describes the package — it is a thin RPC client over the Node runtime, not a Python re-implementation of the eval logic — and the npm scope (`@tangle-network/agent-eval`) already provides the namespacing the `tangle-` prefix was substituting for. No prior PyPI version ever shipped under the old name (Trusted Publisher misconfiguration; see issue #40), so this rename is a clean first publish rather than a migration.
|
|
311
|
+
|
|
312
|
+
Locked at `agent-eval-rpc==0.21.0` to match the npm package.
|
|
78
313
|
|
|
79
314
|
## 0.20.10 — hardening audit follow-up
|
|
80
315
|
|
package/README.md
CHANGED
|
@@ -96,9 +96,10 @@ import { renderReleaseReport } from '@tangle-network/agent-eval/reporting'
|
|
|
96
96
|
| Subpath | Use for |
|
|
97
97
|
| --- | --- |
|
|
98
98
|
| `@tangle-network/agent-eval/control` | `observe -> validate -> decide -> act`, action policy, propose/review loops |
|
|
99
|
-
| `@tangle-network/agent-eval/traces` | trace stores, emitters, TraceAnalyst |
|
|
100
|
-
| `@tangle-network/agent-eval/optimization` | feedback trajectories, multi-shot optimization, prompt evolution |
|
|
101
|
-
| `@tangle-network/agent-eval/reporting` | release confidence, paired stats, report/table/chart specs |
|
|
99
|
+
| `@tangle-network/agent-eval/traces` | trace stores, emitters, TraceAnalyst, replay |
|
|
100
|
+
| `@tangle-network/agent-eval/optimization` | feedback trajectories, multi-shot optimization, prompt evolution, EvalCampaign |
|
|
101
|
+
| `@tangle-network/agent-eval/reporting` | release confidence, paired stats, sequential e-values, report/table/chart specs, predictive validity |
|
|
102
|
+
| `@tangle-network/agent-eval/rl` | RL bridge: adapters, verifiable rewards, preferences, OPE, PRM, contamination, tournaments, adversarial, compute curves |
|
|
102
103
|
| `@tangle-network/agent-eval/wire` | HTTP/RPC judge server and schemas |
|
|
103
104
|
| `@tangle-network/agent-eval/benchmarks` | benchmark adapter contracts and reference wrappers |
|
|
104
105
|
|
|
@@ -116,6 +117,19 @@ import { renderReleaseReport } from '@tangle-network/agent-eval/reporting'
|
|
|
116
117
|
| Fail loud if an eval would silently use the wrong route | `assertLlmRoute` |
|
|
117
118
|
| Assert at run-end that the artifact is complete | `assertRunCaptured`, `throwIfRunIncomplete` |
|
|
118
119
|
| Auto-execute the trace analyst on every run | `traceAnalystOnRunComplete` + `TraceEmitterOptions.onRunComplete` |
|
|
120
|
+
| Run a matrix of variants × scenarios × seeds with capture integrity by construction | `runEvalCampaign` |
|
|
121
|
+
| Re-judge / determinism-audit a past campaign for free | `ReplayCache`, `createReplayFetch` |
|
|
122
|
+
| Ship-when-decisive with anytime-valid α across rolling looks | `pairedEvalueSequence`, `evaluateInterimReleaseConfidence` |
|
|
123
|
+
| Tell load-bearing rubrics from decorative ones using deployment outcomes | `rubricPredictiveValidity` |
|
|
124
|
+
| Bridge legacy optimization output to canonical `RunRecord[]` | `trialsToRunRecords`, `verificationReportToRunRecord` |
|
|
125
|
+
| Extract a clean reward signal for RL training (compile/test/schema vs judge) | `extractVerifiableReward`, `filterDeterministicallyRewarded` |
|
|
126
|
+
| Produce DPO / PPO / KTO `(chosen, rejected)` triples | `extractPreferences` |
|
|
127
|
+
| Estimate a new policy's value on old trajectories without re-running | `offPolicyEstimateAll` (IPS + SNIPS + DR) |
|
|
128
|
+
| Step-level credit assignment / PRM training data | `extractStepRewards`, `prmTrainingPairs` |
|
|
129
|
+
| Detect benchmark contamination via held-out perturbations | `runContaminationProbe` |
|
|
130
|
+
| Pairwise tournament ratings for many-candidate sweeps | `fitBradleyTerry`, `applyEloUpdate` |
|
|
131
|
+
| Active search for inputs the policy fails on | `adversarialScenarioSearch` |
|
|
132
|
+
| Characterise a candidate across compute budgets | `runComputeCurve`, `bestOfN`, `selfConsistency`, `paretoFrontier` |
|
|
119
133
|
| Model missing context separately from bad reasoning | `KnowledgeRequirement`, `KnowledgeBundle` |
|
|
120
134
|
|
|
121
135
|
### Capture integrity (0.21+)
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { B as BENCHMARK_SPLIT_SEED, a as BenchmarkAdapter, b as BenchmarkDatasetItem, c as BenchmarkEvaluation, d as deterministicSplit, e as routing } from '../index-
|
|
2
|
-
import '../run-record-
|
|
1
|
+
export { B as BENCHMARK_SPLIT_SEED, a as BenchmarkAdapter, b as BenchmarkDatasetItem, c as BenchmarkEvaluation, d as deterministicSplit, e as routing } from '../index-DDTlbHEK.js';
|
|
2
|
+
import '../run-record-DNiOMBrZ.js';
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
canonicalize,
|
|
3
|
+
hashJson
|
|
4
|
+
} from "./chunk-6M774GY6.js";
|
|
5
|
+
|
|
1
6
|
// src/trace/store.ts
|
|
2
7
|
var InMemoryTraceStore = class {
|
|
3
8
|
runs = /* @__PURE__ */ new Map();
|
|
@@ -497,119 +502,138 @@ function runToTraceId(run) {
|
|
|
497
502
|
return cleaned.slice(0, 32).padEnd(32, "0");
|
|
498
503
|
}
|
|
499
504
|
|
|
500
|
-
// src/
|
|
501
|
-
var
|
|
502
|
-
constructor(
|
|
503
|
-
super(
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
this.
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
505
|
+
// src/replay.ts
|
|
506
|
+
var ReplayCacheMissError = class extends Error {
|
|
507
|
+
constructor(url, requestKey2, message) {
|
|
508
|
+
super(message ?? `replay cache miss for ${url} (key=${requestKey2})`);
|
|
509
|
+
this.url = url;
|
|
510
|
+
this.requestKey = requestKey2;
|
|
511
|
+
this.name = "ReplayCacheMissError";
|
|
512
|
+
}
|
|
513
|
+
url;
|
|
514
|
+
requestKey;
|
|
510
515
|
};
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
516
|
+
var ReplayCache = class _ReplayCache {
|
|
517
|
+
byKey = /* @__PURE__ */ new Map();
|
|
518
|
+
orphans = 0;
|
|
519
|
+
byProvider = {};
|
|
520
|
+
byModel = {};
|
|
521
|
+
/**
|
|
522
|
+
* Build a cache from a sink's events. The sink must implement `list()`.
|
|
523
|
+
* Filter by `runId` / `spanId` to scope to a specific replay.
|
|
524
|
+
*/
|
|
525
|
+
static async fromSink(sink, filter = {}) {
|
|
526
|
+
if (!sink.list) {
|
|
527
|
+
throw new Error("ReplayCache.fromSink: sink must implement list() to be replayable.");
|
|
528
|
+
}
|
|
529
|
+
const events = await sink.list(filter);
|
|
530
|
+
return _ReplayCache.fromEvents(events);
|
|
531
|
+
}
|
|
532
|
+
/** Build a cache from an in-memory event list. */
|
|
533
|
+
static async fromEvents(events) {
|
|
534
|
+
const cache = new _ReplayCache();
|
|
535
|
+
const groups = /* @__PURE__ */ new Map();
|
|
536
|
+
for (const e of events) {
|
|
537
|
+
const k = `${e.runId ?? ""}::${e.spanId ?? ""}::${e.attemptIndex}`;
|
|
538
|
+
const g = groups.get(k) ?? {};
|
|
539
|
+
if (e.direction === "request") g.req = e;
|
|
540
|
+
else g.res = e;
|
|
541
|
+
groups.set(k, g);
|
|
542
|
+
}
|
|
543
|
+
for (const g of groups.values()) {
|
|
544
|
+
if (!g.req) continue;
|
|
545
|
+
if (!g.res) {
|
|
546
|
+
cache.orphans += 1;
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
const key = await requestKey(g.req);
|
|
550
|
+
cache.byKey.set(key, { request: g.req, response: g.res });
|
|
551
|
+
cache.byProvider[g.req.provider] = (cache.byProvider[g.req.provider] ?? 0) + 1;
|
|
552
|
+
cache.byModel[g.req.model] = (cache.byModel[g.req.model] ?? 0) + 1;
|
|
553
|
+
}
|
|
554
|
+
return cache;
|
|
525
555
|
}
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
const toolSpans2 = spans.filter((s) => s.kind === "tool");
|
|
530
|
-
const llmMin = expectations.llmSpansMin ?? 0;
|
|
531
|
-
const judgeMin = expectations.judgeSpansMin ?? 0;
|
|
532
|
-
const toolMin = expectations.toolSpansMin ?? 0;
|
|
533
|
-
if (llmSpans2.length < llmMin) {
|
|
534
|
-
issues.push({
|
|
535
|
-
code: "missing_llm_spans",
|
|
536
|
-
message: `Expected \u2265 ${llmMin} LLM spans, found ${llmSpans2.length}.`,
|
|
537
|
-
detail: { expected: llmMin, found: llmSpans2.length }
|
|
538
|
-
});
|
|
556
|
+
/** Number of cacheable (request, response) pairs in the cache. */
|
|
557
|
+
size() {
|
|
558
|
+
return this.byKey.size;
|
|
539
559
|
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
560
|
+
stats() {
|
|
561
|
+
return {
|
|
562
|
+
total: this.byKey.size,
|
|
563
|
+
byProvider: { ...this.byProvider },
|
|
564
|
+
byModel: { ...this.byModel },
|
|
565
|
+
orphanRequests: this.orphans
|
|
566
|
+
};
|
|
546
567
|
}
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
568
|
+
/**
|
|
569
|
+
* Look up a cached response by hashing the (model, messages, temperature,
|
|
570
|
+
* maxTokens, response_format) shape. Returns `undefined` on miss; the
|
|
571
|
+
* caller decides whether to throw, fall back to the network, or skip.
|
|
572
|
+
*/
|
|
573
|
+
async lookup(requestBody) {
|
|
574
|
+
const key = await keyFromBody(requestBody);
|
|
575
|
+
return this.byKey.get(key);
|
|
553
576
|
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
});
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
message: `Expected \u2265 ${rawMin} raw provider events, found ${rawEventCount}.`,
|
|
570
|
-
detail: { expected: rawMin, found: rawEventCount }
|
|
571
|
-
});
|
|
572
|
-
}
|
|
573
|
-
if (expectations.requireRawCoverageOfLlmSpans) {
|
|
574
|
-
const requestEventsBySpan = new Set(
|
|
575
|
-
events.filter((e) => e.direction === "request" && e.spanId).map((e) => e.spanId)
|
|
576
|
-
);
|
|
577
|
-
const orphaned = llmSpans2.filter((s) => !requestEventsBySpan.has(s.spanId));
|
|
578
|
-
coverage = { covered: llmSpans2.length - orphaned.length, total: llmSpans2.length };
|
|
579
|
-
if (orphaned.length > 0) {
|
|
580
|
-
issues.push({
|
|
581
|
-
code: "orphan_llm_span",
|
|
582
|
-
message: `${orphaned.length} LLM span(s) have no matching raw provider request event.`,
|
|
583
|
-
detail: { orphanedSpanIds: orphaned.map((s) => s.spanId) }
|
|
584
|
-
});
|
|
585
|
-
}
|
|
577
|
+
};
|
|
578
|
+
function createReplayFetch(cache, opts = {}) {
|
|
579
|
+
const onMiss = opts.onMiss ?? "throw";
|
|
580
|
+
const fallback = opts.fallbackFetch ?? globalThis.fetch?.bind(globalThis);
|
|
581
|
+
return (async (input, init) => {
|
|
582
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
|
583
|
+
if (!/\/chat\/completions(?:[?#].*)?$/.test(url)) {
|
|
584
|
+
if (!fallback) throw new Error(`replay fetch: non-completions URL ${url} but no fallbackFetch configured`);
|
|
585
|
+
return fallback(input, init);
|
|
586
|
+
}
|
|
587
|
+
let bodyParsed;
|
|
588
|
+
if (init?.body && typeof init.body === "string") {
|
|
589
|
+
try {
|
|
590
|
+
bodyParsed = JSON.parse(init.body);
|
|
591
|
+
} catch {
|
|
586
592
|
}
|
|
587
593
|
}
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
rawSpanCoverage: coverage,
|
|
608
|
-
issues
|
|
609
|
-
};
|
|
594
|
+
const hit = bodyParsed === void 0 ? void 0 : await cache.lookup(bodyParsed);
|
|
595
|
+
if (hit) {
|
|
596
|
+
opts.onHit?.({ url, provider: hit.request.provider, model: hit.request.model });
|
|
597
|
+
const status = hit.response.statusCode ?? 200;
|
|
598
|
+
const headers = new Headers(Object.entries(hit.response.responseHeaders ?? { "Content-Type": "application/json" }));
|
|
599
|
+
const bodyText = typeof hit.response.responseBody === "string" ? hit.response.responseBody : JSON.stringify(hit.response.responseBody ?? {});
|
|
600
|
+
return new Response(bodyText, { status, headers });
|
|
601
|
+
}
|
|
602
|
+
opts.onMissNotify?.({ url, requestBody: bodyParsed });
|
|
603
|
+
if (onMiss === "throw") {
|
|
604
|
+
const key = bodyParsed === void 0 ? "<unparseable>" : await keyFromBody(bodyParsed);
|
|
605
|
+
throw new ReplayCacheMissError(url, key);
|
|
606
|
+
}
|
|
607
|
+
if (onMiss === "fail-closed") {
|
|
608
|
+
return new Response(JSON.stringify({ error: "replay_cache_miss" }), { status: 599 });
|
|
609
|
+
}
|
|
610
|
+
if (!fallback) throw new Error("replay fetch: onMiss=fallback but no fallbackFetch configured");
|
|
611
|
+
return fallback(input, init);
|
|
612
|
+
});
|
|
610
613
|
}
|
|
611
|
-
function
|
|
612
|
-
if (!
|
|
614
|
+
async function* iterateRawCalls(sink, filter = {}) {
|
|
615
|
+
if (!sink.list) {
|
|
616
|
+
throw new Error("iterateRawCalls: sink must implement list().");
|
|
617
|
+
}
|
|
618
|
+
const events = await sink.list(filter);
|
|
619
|
+
const cache = await ReplayCache.fromEvents(events);
|
|
620
|
+
for (const entry of cache["byKey"].values()) yield entry;
|
|
621
|
+
}
|
|
622
|
+
async function requestKey(event) {
|
|
623
|
+
return keyFromBody(event.requestBody);
|
|
624
|
+
}
|
|
625
|
+
async function keyFromBody(body) {
|
|
626
|
+
if (body == null || typeof body !== "object") return hashJson({ raw: String(body) });
|
|
627
|
+
const b = body;
|
|
628
|
+
const reduced = canonicalize({
|
|
629
|
+
model: b.model ?? null,
|
|
630
|
+
messages: b.messages ?? null,
|
|
631
|
+
temperature: b.temperature ?? null,
|
|
632
|
+
max_tokens: b.max_tokens ?? null,
|
|
633
|
+
max_completion_tokens: b.max_completion_tokens ?? null,
|
|
634
|
+
response_format: b.response_format ?? null
|
|
635
|
+
});
|
|
636
|
+
return hashJson(reduced);
|
|
613
637
|
}
|
|
614
638
|
|
|
615
639
|
// src/trace-analyst/types.ts
|
|
@@ -1891,9 +1915,10 @@ export {
|
|
|
1891
1915
|
redactValue,
|
|
1892
1916
|
OTEL_AGENT_EVAL_SCOPE,
|
|
1893
1917
|
exportRunAsOtlp,
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1918
|
+
ReplayCacheMissError,
|
|
1919
|
+
ReplayCache,
|
|
1920
|
+
createReplayFetch,
|
|
1921
|
+
iterateRawCalls,
|
|
1897
1922
|
DEFAULT_TRACE_ANALYST_BUDGETS,
|
|
1898
1923
|
TRACE_ANALYST_TRUNCATION_MARKER_PREFIX,
|
|
1899
1924
|
OtlpFileTraceStore,
|
|
@@ -1917,4 +1942,4 @@ export {
|
|
|
1917
1942
|
defaultTraceInsightPanel,
|
|
1918
1943
|
buildTraceInsightPrompt
|
|
1919
1944
|
};
|
|
1920
|
-
//# sourceMappingURL=chunk-
|
|
1945
|
+
//# sourceMappingURL=chunk-4W4NCYM2.js.map
|