oh-my-knowledge 0.19.0 → 0.20.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 (96) hide show
  1. package/README.md +205 -8
  2. package/README.zh.md +200 -7
  3. package/dist/src/analysis/failure-clusterer.d.ts +96 -0
  4. package/dist/src/analysis/failure-clusterer.d.ts.map +1 -0
  5. package/dist/src/analysis/failure-clusterer.js +298 -0
  6. package/dist/src/analysis/failure-clusterer.js.map +1 -0
  7. package/dist/src/analysis/sample-diagnostics.d.ts +78 -0
  8. package/dist/src/analysis/sample-diagnostics.d.ts.map +1 -0
  9. package/dist/src/analysis/sample-diagnostics.js +259 -0
  10. package/dist/src/analysis/sample-diagnostics.js.map +1 -0
  11. package/dist/src/analysis/saturation.d.ts +85 -0
  12. package/dist/src/analysis/saturation.d.ts.map +1 -0
  13. package/dist/src/analysis/saturation.js +174 -0
  14. package/dist/src/analysis/saturation.js.map +1 -0
  15. package/dist/src/cli.js +751 -5
  16. package/dist/src/cli.js.map +1 -1
  17. package/dist/src/eval-core/bootstrap.d.ts +72 -0
  18. package/dist/src/eval-core/bootstrap.d.ts.map +1 -0
  19. package/dist/src/eval-core/bootstrap.js +174 -0
  20. package/dist/src/eval-core/bootstrap.js.map +1 -0
  21. package/dist/src/eval-core/evaluation-execution.d.ts +15 -1
  22. package/dist/src/eval-core/evaluation-execution.d.ts.map +1 -1
  23. package/dist/src/eval-core/evaluation-execution.js +37 -3
  24. package/dist/src/eval-core/evaluation-execution.js.map +1 -1
  25. package/dist/src/eval-core/evaluation-job.d.ts +8 -2
  26. package/dist/src/eval-core/evaluation-job.d.ts.map +1 -1
  27. package/dist/src/eval-core/evaluation-job.js +7 -1
  28. package/dist/src/eval-core/evaluation-job.js.map +1 -1
  29. package/dist/src/eval-core/evaluation-reporting.d.ts.map +1 -1
  30. package/dist/src/eval-core/evaluation-reporting.js +90 -0
  31. package/dist/src/eval-core/evaluation-reporting.js.map +1 -1
  32. package/dist/src/eval-core/schema.d.ts.map +1 -1
  33. package/dist/src/eval-core/schema.js +69 -0
  34. package/dist/src/eval-core/schema.js.map +1 -1
  35. package/dist/src/eval-core/verdict.d.ts +74 -0
  36. package/dist/src/eval-core/verdict.d.ts.map +1 -0
  37. package/dist/src/eval-core/verdict.js +283 -0
  38. package/dist/src/eval-core/verdict.js.map +1 -0
  39. package/dist/src/eval-workflows/each-evaluation-workflow.d.ts +10 -1
  40. package/dist/src/eval-workflows/each-evaluation-workflow.d.ts.map +1 -1
  41. package/dist/src/eval-workflows/each-evaluation-workflow.js +4 -1
  42. package/dist/src/eval-workflows/each-evaluation-workflow.js.map +1 -1
  43. package/dist/src/eval-workflows/evaluation-pipeline.d.ts +13 -1
  44. package/dist/src/eval-workflows/evaluation-pipeline.d.ts.map +1 -1
  45. package/dist/src/eval-workflows/evaluation-pipeline.js +41 -3
  46. package/dist/src/eval-workflows/evaluation-pipeline.js.map +1 -1
  47. package/dist/src/eval-workflows/run-evaluation.d.ts +17 -2
  48. package/dist/src/eval-workflows/run-evaluation.d.ts.map +1 -1
  49. package/dist/src/eval-workflows/run-evaluation.js +97 -5
  50. package/dist/src/eval-workflows/run-evaluation.js.map +1 -1
  51. package/dist/src/grading/assertions.d.ts +16 -0
  52. package/dist/src/grading/assertions.d.ts.map +1 -1
  53. package/dist/src/grading/assertions.js +385 -111
  54. package/dist/src/grading/assertions.js.map +1 -1
  55. package/dist/src/grading/debias-validate.d.ts +84 -0
  56. package/dist/src/grading/debias-validate.d.ts.map +1 -0
  57. package/dist/src/grading/debias-validate.js +173 -0
  58. package/dist/src/grading/debias-validate.js.map +1 -0
  59. package/dist/src/grading/gold-cli.d.ts +88 -0
  60. package/dist/src/grading/gold-cli.d.ts.map +1 -0
  61. package/dist/src/grading/gold-cli.js +251 -0
  62. package/dist/src/grading/gold-cli.js.map +1 -0
  63. package/dist/src/grading/gold-dataset.d.ts +73 -0
  64. package/dist/src/grading/gold-dataset.d.ts.map +1 -0
  65. package/dist/src/grading/gold-dataset.js +161 -0
  66. package/dist/src/grading/gold-dataset.js.map +1 -0
  67. package/dist/src/grading/human-gold.d.ts +102 -0
  68. package/dist/src/grading/human-gold.d.ts.map +1 -0
  69. package/dist/src/grading/human-gold.js +188 -0
  70. package/dist/src/grading/human-gold.js.map +1 -0
  71. package/dist/src/grading/index.d.ts +27 -2
  72. package/dist/src/grading/index.d.ts.map +1 -1
  73. package/dist/src/grading/index.js +36 -18
  74. package/dist/src/grading/index.js.map +1 -1
  75. package/dist/src/grading/judge.d.ts +65 -2
  76. package/dist/src/grading/judge.d.ts.map +1 -1
  77. package/dist/src/grading/judge.js +280 -23
  78. package/dist/src/grading/judge.js.map +1 -1
  79. package/dist/src/inputs/eval-config.js +19 -0
  80. package/dist/src/inputs/eval-config.js.map +1 -1
  81. package/dist/src/renderer/html-renderer.d.ts.map +1 -1
  82. package/dist/src/renderer/html-renderer.js +16 -4
  83. package/dist/src/renderer/html-renderer.js.map +1 -1
  84. package/dist/src/renderer/layout.d.ts.map +1 -1
  85. package/dist/src/renderer/layout.js +32 -4
  86. package/dist/src/renderer/layout.js.map +1 -1
  87. package/dist/src/renderer/summary.d.ts +28 -1
  88. package/dist/src/renderer/summary.d.ts.map +1 -1
  89. package/dist/src/renderer/summary.js +321 -7
  90. package/dist/src/renderer/summary.js.map +1 -1
  91. package/dist/src/renderer/table.d.ts.map +1 -1
  92. package/dist/src/renderer/table.js +63 -2
  93. package/dist/src/renderer/table.js.map +1 -1
  94. package/dist/src/types.d.ts +241 -0
  95. package/dist/src/types.d.ts.map +1 -1
  96. package/package.json +15 -4
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  **English** | [简体中文](./README.zh.md)
9
9
 
10
- Knowledge-artifact evaluation toolkit measure your artifact's quality with objective data.
10
+ **omk** — LLM evaluation framework with built-in statistical rigor. Bootstrap CI / Krippendorff α / length-debias / saturation curves out of the box. Native support for Claude Code skills, prompts, agents, and RAG.
11
11
 
12
12
  **Fix the model, vary the knowledge artifact, let the data speak.**
13
13
 
@@ -22,6 +22,33 @@ Teams doing knowledge engineering produce lots of knowledge artifacts (skills to
22
22
  - **Production session observability** — parse Claude Code session JSONL traces, measure per-skill failure rate, latency, token cost, and knowledge-gap signals on real user sessions
23
23
  - **Knowledge-gap detection** — severity-weighted signals (explicit markers / failed searches / hedging language / repeated failures) quantify risk exposure instead of claiming completeness
24
24
  - **Pre-merge CI gate** — `omk bench ci` enforces three-layer all-pass (fact + behavior + llm-judge) semantics, catching single-layer regressions a composite score would hide
25
+ - **One-line ship/no-ship verdict** — `omk bench verdict <reportId>` aggregates bootstrap CI / three-layer ci-gate / saturation / human α into a six-tier verdict (PROGRESS / CAUTIOUS / REGRESS / NOISE / UNDERPOWERED / SOLO) plus an action recommendation; the exit code reflects whether to ship
26
+
27
+ ### Statistical rigor
28
+
29
+ The biggest LLM-eval failure mode is "confident bias" — narrow CIs around the wrong answer. omk's statistical layer ships four pieces so conclusions can be externally audited:
30
+
31
+ - **Bootstrap CI** (`--bootstrap`) — distribution-free confidence intervals. The t-test breaks on ordinal LLM scores; bootstrap resamples raw observations and stays valid at small N (< 30) and on skewed data. Pairwise diff CI not crossing 0 = significant.
32
+ - **Human Gold + Krippendorff α** (`--gold-dir`) — bring an external annotation as anchor. CI tells you "is the judge stable", α tells you "is the judge correct" — two complementary axes. omk warns when the gold annotator and the judge are the same model (would inflate α).
33
+ - **Length-controlled judge prompt** (default ON) — research shows LLM judges over-weight verbosity. omk's judge prompt explicitly states "length is not a quality signal"; template hash is `v3-cot-length` so older reports (with the legacy hash) are visibly different. `omk bench debias-validate length <reportId>` re-judges with the opposite setting and reports the score shift.
34
+ - **Saturation curve** — answers "have I run enough samples?". With `--repeat ≥ 5` we accumulate cumulative N → bootstrap CI; when CI shrink rate stays under 5% across 3 windows, more samples buy nothing. The HTML report inlines the SVG curve plus a verdict.
35
+
36
+ ## Why omk over alternatives
37
+
38
+ | | omk | promptfoo | DeepEval | RAGAS | LangSmith |
39
+ |--|--|--|--|--|--|
40
+ | Bootstrap CI | ✓ | ✗ | ✗ | ✗ | ✗ |
41
+ | Krippendorff α (judge ↔ human) | ✓ | ✗ | ✗ | ✗ | ✗ |
42
+ | Length-debias judge prompt | ✓ default | ✗ | ✗ | ✗ | ✗ |
43
+ | Saturation curve | ✓ | ✗ | ✗ | ✗ | ✗ |
44
+ | Three-layer scoring isolation | ✓ | ✗ | partial | ✗ | ✗ |
45
+ | Native Claude Code skill | ✓ | ✗ | ✗ | ✗ | ✗ |
46
+ | Full Chinese docs | ✓ | ✗ | ✗ | ✗ | ✗ |
47
+ | Hosted SaaS dashboard | ✗ | ✗ | ✓ | ✗ | ✓ |
48
+
49
+ omk's moat is **statistical rigor** — every conclusion is auditable by a researcher. If you need a hosted SaaS dashboard, choose LangSmith. If you want quick local prompt iteration without statistics, choose promptfoo. **If you ship to production and someone will ask "why should I trust this number?", choose omk**.
50
+
51
+ Full comparison with 7 tools across 25+ dimensions: [docs/comparison.md](docs/comparison.md)
25
52
 
26
53
  ## Quick start
27
54
 
@@ -61,15 +88,23 @@ You can also just say "compare v1 vs v2 for me" or "improve this artifact" — o
61
88
 
62
89
  | Feature | What it does |
63
90
  |---|---|
64
- | **18 assertion types** | substring, regex, JSON Schema, semantic similarity, custom JS function, and more |
91
+ | **21+ assertion types** | substring, regex, JSON Schema, ROUGE/BLEU/Levenshtein similarity, agent tool-call assertions, semantic similarity, custom JS, and more |
92
+ | **Assertion negation + composition** | universal `not: true` field + `assert-set` (any/all) with arbitrary nesting |
65
93
  | **Six-dim evaluation** | Fact / Behavior / LLM-judge / Cost / Efficiency / Stability shown independently |
94
+ | **Statistical rigor** | Bootstrap CI / Krippendorff α / length-debias / saturation curve |
95
+ | **One-line verdict** | `omk bench verdict <id>` six-tier verdict + ship recommendation + exit-code routing; HTML pill shares the same rules |
96
+ | **RAG metrics** | `faithfulness` / `answer_relevancy` / `context_recall` — anti-hallucination + answer relevance + context coverage; auto-inherits length-debias |
97
+ | **Sample diagnostics** | `omk bench diagnose <id>` — 7 issue kinds (low discrimination / duplicates / ambiguous rubric / cost outliers / etc.) + 0-100 healthScore |
98
+ | **Failure clustering** | `omk bench failures <id>` — single LLM call clusters failed samples and emits per-cluster fixes |
99
+ | **Hard budget caps** | `--budget-usd / --budget-per-sample-usd / --budget-per-sample-ms` — abort on total-cost overrun, flag per-sample overruns; partial report persisted |
66
100
  | **Multi-executor** | Claude CLI / Claude SDK / OpenAI / Gemini / any custom command |
101
+ | **Multi-judge ensemble** | `--judge-models claude:opus,openai:gpt-4o` cross-vendor scoring + agreement metrics |
67
102
  | **MCP URL fetching** | pull content from private-doc URLs via an MCP server (SSO-protected knowledge bases, etc.) |
68
103
  | **Blind A/B** | `--blind` hides variant names; HTML report has a reveal button |
69
104
  | **Parallel execution** | `--concurrency N` runs N tasks at once |
70
105
  | **Multi-run variance** | `--repeat N` repeats the eval and computes mean / SD / CI / t-test |
71
106
  | **Auto analysis** | detects low-discrimination assertions, flat scores, all-pass / all-fail, expensive samples |
72
- | **Traceability** | reports carry CLI version, Node version, artifact version fingerprint |
107
+ | **Traceability** | reports carry CLI version, Node version, artifact version fingerprint, judge prompt hash |
73
108
  | **EN / ZH switch** | one-click language toggle in the HTML report |
74
109
 
75
110
  ## How it works
@@ -112,7 +147,7 @@ flowchart TD
112
147
  end
113
148
 
114
149
  subgraph Report["⑦ Report"]
115
- R["Six dims: Fact / Behavior / LLM-judge / Cost / Efficiency / Stability<br/>JSON + HTML · blind reveal<br/>CLI/Node/version fingerprint traceable"]
150
+ R["Six dims: Fact / Behavior / LLM-judge / Cost / Efficiency / Stability<br/>JSON + HTML · top verdict pill · blind reveal<br/>CLI/Node/version fingerprint traceable"]
116
151
  end
117
152
 
118
153
  S --> U
@@ -246,7 +281,7 @@ The judge model (default `haiku`) scores 1–5 against the rubric. In `dimension
246
281
 
247
282
  ### Assertion types
248
283
 
249
- **Deterministic assertions (18 total):**
284
+ **Deterministic assertions (21+ total):**
250
285
 
251
286
  | Type | Description |
252
287
  |---|---|
@@ -259,9 +294,43 @@ The judge model (default `haiku`) scores 1–5 against the rubric. In `dimension
259
294
  | `word_count_min` / `word_count_max` | word-count bounds |
260
295
  | `contains_all` / `contains_any` | multi-value match |
261
296
  | `cost_max` / `latency_max` | cost / latency caps |
262
- | `semantic_similarity` | LLM-based semantic similarity |
297
+ | `tools_called` / `tools_not_called` / `tools_count_min` / `tools_count_max` | agent tool-call assertions |
298
+ | `tool_output_contains` / `tool_input_contains` | match content of a tool's input or output |
299
+ | `turns_min` / `turns_max` | conversation-turn bounds |
300
+ | `rouge_n_min` | ROUGE-N recall ≥ threshold (`reference` field holds the gold text; `n` defaults to 1; `threshold` defaults to 0.5) |
301
+ | `levenshtein_max` | edit distance ≤ value (for "output should be near-identical to reference") |
302
+ | `bleu_min` | BLEU-4 ≥ threshold (unsmoothed; degenerates to 0 on short text) |
303
+ | `faithfulness` | output stays grounded in `sample.context` (anti-hallucination); LLM judge 1-5; threshold defaults to 3 |
304
+ | `answer_relevancy` | output directly answers `sample.prompt`; catches dodging, topic drift, verbosity; threshold defaults to 3 |
305
+ | `context_recall` | gold facts in `sample.context` are actually used in the output; `reference` may explicitly enumerate gold facts; threshold defaults to 3 |
306
+ | `semantic_similarity` | LLM-based holistic semantic similarity (complementary to the three RAG metrics above) |
263
307
  | `custom` | custom JS function (30 s timeout) |
264
308
 
309
+ **Universal modifier:**
310
+
311
+ Any assertion takes `not: true` to invert (replaces paired `not_contains` / `not_equals` etc; legacy types remain as aliases):
312
+
313
+ ```yaml
314
+ - type: regex
315
+ pattern: "TODO|FIXME"
316
+ not: true # output must NOT contain TODO/FIXME
317
+ ```
318
+
319
+ **Composition (assert-set):**
320
+
321
+ `assert-set` combines child assertions with `any` (OR) or `all` (AND) and supports nesting:
322
+
323
+ ```yaml
324
+ - type: assert-set
325
+ mode: any # at least one child must pass (mode: 'all' = all must pass)
326
+ children:
327
+ - { type: contains, value: "parameterized" }
328
+ - { type: contains, value: "prepared statement" }
329
+ - { type: regex, pattern: "bind\\(.*\\?" }
330
+ ```
331
+
332
+ Children can independently use `not: true`; nested `assert-set`s can express any boolean shape.
333
+
265
334
  ### Custom assertion
266
335
 
267
336
  ```js
@@ -314,14 +383,35 @@ options:
314
383
  --timeout <sec> per-task executor timeout (default: 120)
315
384
  --repeat <n> repeat N times for variance analysis (default: 1)
316
385
  --executor <name> executor (default: claude); supports custom commands
317
- --skip-preflight skip pre-evaluation model reachability check
386
+ --skip-preflight skip evaluation model reachability check
318
387
  --mcp-config <path> MCP config for fetching private-doc URLs via MCP Server
319
388
  (default: .mcp.json in cwd)
320
389
  --no-serve don't auto-start the report server after the run
321
390
  --verbose print per-sample details (duration, tokens, output preview)
322
391
  --each batch mode: evaluate each artifact independently vs baseline
323
392
  requires {name}.eval-samples.json paired with each artifact
324
- ```
393
+ --judge-repeat <n> run the LLM judge N times per (sample × dimension) and report stddev
394
+ --judge-models <list> multi-judge ensemble: "executor1:model1,executor2:model2"
395
+ ≥ 2 judges enables ensemble + inter-judge agreement output
396
+ --bootstrap enable distribution-free CIs: bootstrap CI per variant +
397
+ pairwise diff CI (CI containing 0 = not significant)
398
+ --bootstrap-samples N bootstrap resample count (default 1000)
399
+ --gold-dir <path> after the run, compare scores against the gold dataset
400
+ (Krippendorff α / κ / Pearson). Result is written to
401
+ report.meta.humanAgreement and shown in the HTML report
402
+ --no-debias-length revert to legacy v2-cot judge prompt (no "length is not
403
+ a quality signal" paragraph) — for byte-compat with
404
+ legacy reports whose hash predates v3-cot-length
405
+ --budget-usd <num> total cost cap (USD); on overrun the run aborts and
406
+ a partial report is persisted (`report.meta.budgetExhausted = true`)
407
+ --budget-per-sample-usd <num> per-sample cost cap; offending samples fail individually,
408
+ the run continues
409
+ --budget-per-sample-ms <num> per-sample latency cap (ms); same semantics as cost cap
410
+ ```
411
+
412
+ **eval.yaml budget**: declare `budget: { totalUSD?, perSampleUSD?, perSampleMs? }` (all optional, must be ≥ 0). CLI flags of the same name override the config values.
413
+
414
+ **Difference from `cost_max` / `latency_max` assertions**: assertions are **per-sample scoring rules** (exceeding the cap fails that one assertion, the run continues); budget caps are **workflow-level hard limits** (`totalUSD` overrun aborts the run and persists a partial report; per-sample overruns fail the offending sample but the run continues). Assertions answer "is quality acceptable?"; budgets answer "are cost/time within the envelope?".
325
415
 
326
416
  ### `omk bench run --each` (batch mode)
327
417
 
@@ -423,6 +513,113 @@ omk bench report [options]
423
513
  omk bench init [dir] # scaffold an eval project
424
514
  ```
425
515
 
516
+ ### `omk bench gold` (human gold anchor)
517
+
518
+ Bring a human (or stronger-model proxy) annotation as an external anchor and compute Krippendorff α / weighted κ / Pearson against the LLM judge. Answers "is the judge correct?", complementary to Bootstrap CI's "is the judge stable?".
519
+
520
+ ```bash
521
+ omk bench gold init [--out <dir>] [--annotator <id>] # scaffold a dataset template
522
+ omk bench gold validate <dir> # check schema (annotator / date / version / score range)
523
+ omk bench gold compare <reportId> --gold-dir <dir> # compare against an existing report; prints α/κ/r + verdict
524
+ ```
525
+
526
+ Dataset layout:
527
+
528
+ ```
529
+ gold-dir/
530
+ ├── metadata.yaml # annotator (must NOT match the omk judge model — would trigger contamination warning) + date + version
531
+ └── annotations.yaml # [{ sample_id, score, reason? }] concatenated by sample_id
532
+ ```
533
+
534
+ α thresholds follow Krippendorff (2011): ≥ 0.80 strong agreement; [0.67, 0.80) acceptable; < 0.40 large divergence — investigate rubric / prompt.
535
+
536
+ Full demo: [examples/gold-dataset/](examples/gold-dataset/)
537
+
538
+ ### `omk bench debias-validate length` (judge length-bias check)
539
+
540
+ Re-judges every (sample × variant) of an existing report with the OPPOSITE length-debias setting (v3-cot-length ↔ v2-cot) and bootstraps the CI on the score difference. A significant shift = the judge is sensitive to the length-debias instruction (indirect evidence of length bias).
541
+
542
+ ```bash
543
+ omk bench debias-validate length <reportId> [options]
544
+ --variant <name> check a single variant only
545
+ --judge-model <id> override the report's judge model
546
+ --bootstrap-samples N bootstrap iterations (default 1000)
547
+ --seed N deterministic seed
548
+ ```
549
+
550
+ Verdict bucket: none / weak / medium (|0.2-0.5|) / strong (≥ 0.5). Re-judge cost roughly doubles vs the original judge pass.
551
+
552
+ ### `omk bench saturation` (saturation curve)
553
+
554
+ Answers "have I run enough samples?". Reads the saturation trace from an existing report (no re-run). Verdicts only emit when the original run used `--repeat ≥ 5`; below that, the curve is plotted but no verdict is computed.
555
+
556
+ ```bash
557
+ omk bench saturation <reportId> [options]
558
+ --variant <name> single-variant view
559
+ --method <m> slope | bootstrap-ci-width (default) | plateau-height
560
+ --threshold <num> method-specific cutoff (defaults match the method)
561
+ --window <num> consecutive windows that must satisfy the threshold (default 3)
562
+ ```
563
+
564
+ The HTML report inlines an SVG curve (cumulative N on X, mean ± 95% CI shading on Y, one curve per variant) automatically.
565
+
566
+ ### `omk bench verdict` (one-line ship/no-ship verdict)
567
+
568
+ Aggregates bootstrap CI / three-layer ci-gate / saturation / human α into one of six verdicts: **PROGRESS** (significant improvement, all three layers pass → exit 0), **CAUTIOUS** (real gain but with a warning — broken gate / trivially small / control regressed → exit 1), **REGRESS** (significant negative shift → exit 1), **NOISE** (CI spans 0, undecidable → exit 1), **UNDERPOWERED** (sample size too small → exit 1), **SOLO** (single variant; exit 0 only if its own three-layer gate passes).
569
+
570
+ ```bash
571
+ omk bench verdict <reportId> [options]
572
+ --threshold <num> three-layer gate threshold (default 3.5, matches `omk bench ci`)
573
+ --trivial-diff <num> "practically tiny" cutoff (default 0.1)
574
+ --verbose expand per-pair detail
575
+ ```
576
+
577
+ Shares its rule module with the HTML report's verdict pill — CLI and UI cannot disagree.
578
+
579
+ ### `omk bench diagnose` (sample quality diagnostics)
580
+
581
+ Answers "is the conclusion polluted by bad samples?". Diagnoses 7 sample-quality issues: `flat_scores` (low discrimination), `all_pass` (too easy), `all_fail` (broken — error severity), `near_duplicate` (prompt ROUGE-1 ≥ threshold), `ambiguous_rubric` (high judge stddev across `--judge-repeat ≥ 2`), `cost_outlier` (≥ k× median), `latency_outlier` (≥ k× median), `error_prone` (executor failure).
582
+
583
+ ```bash
584
+ omk bench diagnose <reportId> [options]
585
+ --top <n> show top N per kind (default 10, 0 = all)
586
+ --duplicate-rouge <num> near-duplicate ROUGE-1 threshold (default 0.7)
587
+ --ambiguous-stddev <num> judge-stddev threshold (default 1.0)
588
+ --cost-k <num> cost-outlier multiplier vs median (default 3)
589
+ --latency-k <num> latency-outlier multiplier vs median (default 3)
590
+ --flat <num> flat_scores spread threshold (default 0.5)
591
+ ```
592
+
593
+ Output includes a healthScore (0-100, formula `100 - normalized × 20` where `normalized = (errors×8 + warnings×3 + infos×1) / N`). Exit code is 0 only when `healthScore ≥ 70` AND no error-severity issue — CI-friendly.
594
+
595
+ ### `omk bench failures` (failure case LLM clustering)
596
+
597
+ When 14 of 50 samples failed, reading them one by one is slow. This command sends failed samples to a single LLM call, clusters them into ≤ N groups, and emits per-cluster root cause + fix. "Failed" = `compositeScore < threshold` OR `ok = false`.
598
+
599
+ ```bash
600
+ omk bench failures <reportId> [options]
601
+ --judge-executor <name> executor (default: claude)
602
+ --judge-model <id> clustering model (default: from report.meta.judgeModel)
603
+ --max-clusters <n> maximum clusters (default 5)
604
+ --threshold <num> failure score threshold (default 3)
605
+ --max-feed <n> max failures fed to LLM (default 50; takes the worst)
606
+ ```
607
+
608
+ Tolerant: ```json``` markdown fences, `"sample_id@variant"` string member form, hallucinated members are dropped, single-failure case skips the LLM call, executor errors degrade to unclassified.
609
+
610
+ ### `omk bench diff` (report comparison — single / dual mode)
611
+
612
+ **Single-arg mode** (within-report sample-level): `omk bench diff <reportId>` — within one report, drill down per-sample comparing `variants[0]` against `variants[1]` (or `--variant <name>`).
613
+
614
+ **Dual-arg mode** (cross-report variant-level): `omk bench diff <reportId1> <reportId2>` — compare the same variant across two reports (legacy behavior preserved).
615
+
616
+ ```bash
617
+ omk bench diff <reportId> [--variant <name>] [--regressions-only] [--threshold 0] [--top N]
618
+ omk bench diff <reportId1> <reportId2> [--regressions-only] [--threshold 0]
619
+ ```
620
+
621
+ Single-arg output sorts by |Δ| desc; rows where Δ < threshold are highlighted as regressions. `--top N` caps row count, `--regressions-only` shows only negative Δ samples.
622
+
426
623
  ## `omk analyze` — production observability
427
624
 
428
625
  `omk bench run` is **offline evaluation** (fixed controls, repeatable, scored). Production is different — no control group, no ground truth, no repetition, so scoring isn't valid there. `omk analyze` turns existing Claude Code session traces into **skill-health reports** (coverage, gap signals, execution stability, tokens/latency per skill). It gives you clues about **which skill is worth re-evaluating offline**, not a production score.
package/README.zh.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  [English](./README.md) | **简体中文**
9
9
 
10
- 知识载体评测工具用客观数据衡量你的 artifact 质量。
10
+ **omk**内置统计严谨性的 LLM 评测框架。Bootstrap CI / Krippendorff α / 长度偏差校正 / 饱和曲线开箱即用。原生支持 Claude Code skill、prompt、agent、RAG。
11
11
 
12
12
  **固定模型,只变知识载体,数据说话。**
13
13
 
@@ -22,6 +22,32 @@
22
22
  - **线上 session 观测** — 解析 Claude Code session JSONL,在真实用户会话上测量各 skill 的失败率、耗时、token 成本和知识缺口信号
23
23
  - **知识缺口识别** — 严重度加权的信号(显式标记 / 搜索失败 / hedging 用语 / 反复失败)量化风险敞口,不宣称完备性
24
24
  - **合并前 CI 门** — `omk bench ci` 强制三层 all-pass(fact + behavior + llm-judge),抓复合分掩盖的单层回退
25
+ - **一行 ship/no-ship 结论** — `omk bench verdict <reportId>` 聚合 bootstrap CI / 三层 ci-gate / saturation / human α,给六档 verdict(PROGRESS / CAUTIOUS / REGRESS / NOISE / UNDERPOWERED / SOLO)+ 行动建议;exit code 反映是否可 ship
26
+
27
+ ### 统计严谨性
28
+ LLM 评测最容易踩的坑是"自信的偏差"——CI 很窄但结论错。omk 的统计层做四件事让结论可被外部审计:
29
+
30
+ - **Bootstrap CI** (`--bootstrap`) — 不假设分布的置信区间。t 检验在 LLM 序数评分上失效,bootstrap 直接重采样原始数据,对小 N(< 30)和偏态分布都稳。pairwise diff CI 不含 0 = 显著差异。
31
+ - **Human Gold + Krippendorff α** (`--gold-dir`) — 引入外部标注作为锚点。CI 解决"评委稳不稳",α 解决"评委对不对"——两个维度互补。omk 自动检测污染(gold annotator 与 judge 同模型时警告)。
32
+ - **Length-controlled judge prompt** (默认开启) — 研究证实 LLM 评委隐性偏向更长的回答。omk 的 judge prompt 加显式段落"长度不是质量信号",template hash 为 v3-cot-length,跟旧版本的报告 hash 肉眼可辨。`omk bench debias-validate length <reportId>` 重判检测偏差幅度。
33
+ - **Saturation curve** — 回答"我跑够样本了吗"。`--repeat ≥ 5` 时累积 N → 均值 + bootstrap CI 序列,CI 宽度衰减率 < 5% 持续 3 个窗口判定饱和——再多样本对结论无实质收益。HTML 报告内联 SVG 曲线 + verdict。
34
+
35
+ ## 为什么选 omk
36
+
37
+ | | omk | promptfoo | DeepEval | RAGAS | LangSmith |
38
+ |--|--|--|--|--|--|
39
+ | Bootstrap CI | ✓ | ✗ | ✗ | ✗ | ✗ |
40
+ | Krippendorff α(评委 ↔ 人工锚点) | ✓ | ✗ | ✗ | ✗ | ✗ |
41
+ | Length-debias 评委 prompt | ✓ 默认 | ✗ | ✗ | ✗ | ✗ |
42
+ | 饱和曲线 | ✓ | ✗ | ✗ | ✗ | ✗ |
43
+ | 三层独立评分 | ✓ | ✗ | 部分 | ✗ | ✗ |
44
+ | 原生 Claude Code skill | ✓ | ✗ | ✗ | ✗ | ✗ |
45
+ | 完整中文文档 | ✓ | ✗ | ✗ | ✗ | ✗ |
46
+ | 托管 SaaS 看板 | ✗ | ✗ | ✓ | ✗ | ✓ |
47
+
48
+ omk 的护城河是**统计严谨性** — 每条结论都能被研究者审计。需要托管 SaaS 看板?选 LangSmith。要本地快速 prompt 迭代不要统计层?选 promptfoo。**要 ship 到生产且会被问"为什么应该相信这个数字"?选 omk**。
49
+
50
+ 完整对比(7 个工具 × 25+ 维度): [docs/zh/comparison.md](docs/zh/comparison.md)
25
51
 
26
52
  ## 快速开始
27
53
 
@@ -61,15 +87,23 @@ omk bench run
61
87
 
62
88
  | 特性 | 说明 |
63
89
  |------|------|
64
- | **18 种断言** | 包含子串、正则、JSON Schema、语义相似度、自定义函数等 |
90
+ | **21+ 种断言** | 包含子串、正则、JSON Schema、ROUGE/BLEU/Levenshtein 相似度、Agent 工具调用、语义相似度、自定义函数等 |
91
+ | **断言取反 + 组合** | 通用 `not: true` 字段 + `assert-set` (any/all) 任意嵌套 |
65
92
  | **六维评估** | 事实 / 行为 / LLM 评价 / 成本 / 效率 / 稳定性独立展示 |
93
+ | **统计严谨性** | Bootstrap CI / Krippendorff α / Length-debias / Saturation curve |
94
+ | **Verdict 一行结论** | `omk bench verdict <id>` 六档判定 + ship 建议 + exit code 路由,与 HTML 报告 verdict pill 共享规则 |
95
+ | **RAG metrics** | `faithfulness` / `answer_relevancy` / `context_recall` 三 metric — 反幻觉 + 切题度 + context 覆盖,自动继承 length-debias |
96
+ | **样本质量诊断** | `omk bench diagnose <id>` 7 类 issue(区分度低 / 重复 / 歧义 / 成本异常 / 全 fail 等)+ healthScore 0-100 |
97
+ | **失败聚类 + 根因** | `omk bench failures <id>` 单 LLM 调用聚类失败样本 + 每 cluster 给修复建议 |
98
+ | **预算硬阈值** | `--budget-usd / --budget-per-sample-usd / --budget-per-sample-ms` 总成本 + 单样本成本/耗时上限,超出中止保留 partial report |
66
99
  | **多执行器** | 支持 Claude CLI / Claude SDK / OpenAI / Gemini 及自定义命令 |
100
+ | **多评委 ensemble** | `--judge-models claude:opus,openai:gpt-4o` 跨厂商评分 + agreement 度量 |
67
101
  | **MCP URL 获取** | 通过 MCP Server 获取私有文档 URL 内容(SSO 保护的知识库等) |
68
102
  | **盲测 A/B** | `--blind` 隐藏变体名称,HTML 报告有揭晓按钮 |
69
103
  | **并行执行** | `--concurrency N` 并行 N 个任务 |
70
104
  | **多轮方差分析** | `--repeat N` 重复 N 次,计算均值/标准差/置信区间/t 检验 |
71
105
  | **自动分析** | 检测低区分度断言、均匀分数、全通过/全失败、高成本样本 |
72
- | **可追溯性** | 报告含 CLI 版本、Node 版本、artifact 版本指纹 |
106
+ | **可追溯性** | 报告含 CLI 版本、Node 版本、artifact 版本指纹、judge prompt hash |
73
107
  | **中英切换** | HTML 报告右上角一键切换语言 |
74
108
 
75
109
  ## 工作原理
@@ -112,7 +146,7 @@ flowchart TD
112
146
  end
113
147
 
114
148
  subgraph Report["⑦ 报告"]
115
- R["六维: 事实 / 行为 / LLM 评价 / 成本 / 效率 / 稳定性<br/>JSON + HTML · 盲测揭晓<br/>CLI/Node/版本指纹可追溯"]
149
+ R["六维: 事实 / 行为 / LLM 评价 / 成本 / 效率 / 稳定性<br/>JSON + HTML · 顶部 verdict pill · 盲测揭晓<br/>CLI/Node/版本指纹可追溯"]
116
150
  end
117
151
 
118
152
  S --> U
@@ -246,7 +280,7 @@ flowchart TD
246
280
 
247
281
  ### 断言类型
248
282
 
249
- **确定性断言(18 种):**
283
+ **确定性断言(21+ 种):**
250
284
 
251
285
  | 类型 | 说明 |
252
286
  |------|------|
@@ -259,9 +293,43 @@ flowchart TD
259
293
  | `word_count_min` / `word_count_max` | 词数范围 |
260
294
  | `contains_all` / `contains_any` | 多值匹配 |
261
295
  | `cost_max` / `latency_max` | 成本/延迟限制 |
262
- | `semantic_similarity` | LLM 语义相似度 |
296
+ | `tools_called` / `tools_not_called` / `tools_count_min` / `tools_count_max` | Agent 工具调用断言 |
297
+ | `tool_output_contains` / `tool_input_contains` | 工具输入/输出内容匹配 |
298
+ | `turns_min` / `turns_max` | 多轮对话轮数限制 |
299
+ | `rouge_n_min` | ROUGE-N recall ≥ threshold(`reference` 字段填参考答案,`n` 默认 1,`threshold` 默认 0.5) |
300
+ | `levenshtein_max` | 编辑距离 ≤ value(用于"输出跟参考几乎一致"场景) |
301
+ | `bleu_min` | BLEU-4 ≥ threshold(unsmoothed,短文本会塌陷到 0) |
302
+ | `faithfulness` | 输出是否被 `sample.context` 支持(反幻觉);LLM judge 1-5 评分,threshold 默认 3 |
303
+ | `answer_relevancy` | 输出是否切题回答 `sample.prompt`;能抓住跑题、回避、冗余;threshold 默认 3 |
304
+ | `context_recall` | `sample.context` 关键事实在输出中的覆盖率;`reference` 可显式指定 gold facts;threshold 默认 3 |
305
+ | `semantic_similarity` | LLM 语义相似度(与 reference 的整体相似度,与 RAG 三 metric 互补) |
263
306
  | `custom` | 自定义 JS 函数(30s 超时) |
264
307
 
308
+ **通用修饰:**
309
+
310
+ 任何断言加 `not: true` 即反向(替代 `not_contains` / `not_equals` 等成对类型;老类型保留作 alias):
311
+
312
+ ```yaml
313
+ - type: regex
314
+ pattern: "TODO|FIXME"
315
+ not: true # 必须不含 TODO/FIXME
316
+ ```
317
+
318
+ **断言组合(— assert-set):**
319
+
320
+ `assert-set` 类型让多个断言以 `any`(OR)或 `all`(AND)逻辑组合,可嵌套:
321
+
322
+ ```yaml
323
+ - type: assert-set
324
+ mode: any # 任一通过即过 (mode: 'all' 则需全部通过)
325
+ children:
326
+ - { type: contains, value: "参数化" }
327
+ - { type: contains, value: "prepared statement" }
328
+ - { type: regex, pattern: "bind\\(.*\\?" }
329
+ ```
330
+
331
+ 子断言可独立带 `not: true`;嵌套 assert-set 可表达任意布尔逻辑。
332
+
265
333
  ### 自定义断言
266
334
 
267
335
  ```js
@@ -319,7 +387,25 @@ omk bench run [选项]
319
387
  --verbose 打印每个样本的详细执行结果(耗时、tokens、输出预览)
320
388
  --each 批量评测:每个 artifact 独立和 baseline 对比
321
389
  需要每个 artifact 配对 {name}.eval-samples.json
322
- ```
390
+ --judge-repeat <n> 每条 sample × dimension 跑 LLM 评委 N 次,输出 stddev (评委自一致性)
391
+ --judge-models <list> 多评委 ensemble: "executor1:model1,executor2:model2"
392
+ ≥ 2 个 judge 触发 ensemble + inter-judge agreement 输出
393
+ --bootstrap 启用 distribution-free CI:每个 variant 加 bootstrap CI,
394
+ pairwise diff CI 含 0 = 不显著
395
+ --bootstrap-samples N bootstrap 重采样次数 (默认 1000)
396
+ --gold-dir <路径> 跑完自动对比 human gold 算 Krippendorff α / κ / Pearson,
397
+ 结果写入 report.meta.humanAgreement,HTML 报告显示「人工锚点」
398
+ --no-debias-length 退回 v2-cot 评委 prompt (不含"长度不是质量信号"段落),
399
+ 用于复现旧版本(v3-cot-length 之前)的报告 hash
400
+ --budget-usd <num> 总成本上限 (USD);超出中止评测,partial report 仍持久化
401
+ (`report.meta.budgetExhausted = true`)
402
+ --budget-per-sample-usd <num> 单样本成本上限;超出该样本失败但评测继续
403
+ --budget-per-sample-ms <num> 单样本耗时上限 (ms);超出该样本失败但评测继续
404
+ ```
405
+
406
+ **eval.yaml 预算字段**: `budget: { totalUSD?, perSampleUSD?, perSampleMs? }`,所有字段可选且必须 ≥ 0。CLI 同名 flag 覆盖配置值。
407
+
408
+ **和 `cost_max` / `latency_max` 断言的区别**: 断言是**单样本评分维度**(超出直接打 0 分,run 继续);budget 是**工作流级硬阈值**(`totalUSD` 超出整个 run abort 保留 partial report,per-sample 超出该样本失败但 run 继续)。一个回答"质量是否达标",一个回答"花钱/时间是否在预算内"。
323
409
 
324
410
  ### `omk bench run --each`(批量评测)
325
411
 
@@ -421,6 +507,113 @@ omk bench report [选项]
421
507
  omk bench init [目录] # 生成评测项目脚手架
422
508
  ```
423
509
 
510
+ ### `omk bench gold`(人工锚点)
511
+
512
+ 人工标注(或更强模型代理)作为外部锚点,与 LLM 评委的分数对比 Krippendorff α / 加权 κ / Pearson。回答"评委对不对",与 Bootstrap CI 的"评委稳不稳"互补。
513
+
514
+ ```bash
515
+ omk bench gold init [--out <dir>] [--annotator <id>] # 生成数据集模板
516
+ omk bench gold validate <dir> # 校验 schema (annotator/时间/版本/score 范围)
517
+ omk bench gold compare <reportId> --gold-dir <dir> # 与已有 report 对比,输出 verdict + α/κ/r
518
+ ```
519
+
520
+ dataset 目录结构:
521
+
522
+ ```
523
+ gold-dir/
524
+ ├── metadata.yaml # annotator (注意不要与 omk judge 同模型,会触发污染警告) + 时间 + 版本
525
+ └── annotations.yaml # [{ sample_id, score, reason? }] 按 sample_id 拼接
526
+ ```
527
+
528
+ α 阈值参考 Krippendorff (2011):≥ 0.80 高度一致;[0.67, 0.80) 可接受;< 0.40 偏差大需排查 rubric / prompt。
529
+
530
+ 完整 demo: [examples/gold-dataset/](examples/gold-dataset/)
531
+
532
+ ### `omk bench debias-validate length`(评委长度偏差检测)
533
+
534
+ 重判已有 report 的所有 (sample × variant),用相反的 length-debias 设置(v3-cot-length ↔ v2-cot),bootstrap CI 算两次差值。差异显著 = 评委对长度敏感(length bias 间接证据)。
535
+
536
+ ```bash
537
+ omk bench debias-validate length <reportId> [选项]
538
+ --variant <name> 只测一个 variant
539
+ --judge-model <id> override report 的 judge model
540
+ --bootstrap-samples N bootstrap 迭代数 (默认 1000)
541
+ --seed N 确定性种子
542
+ ```
543
+
544
+ verdict 分四档:未检测 / 弱 / 中(差值 |0.2-0.5|)/ 强(差值 ≥ 0.5)。重判 cost 大致翻倍。
545
+
546
+ ### `omk bench saturation`(饱和曲线)
547
+
548
+ 回答"我跑够样本了吗"。从已有 report 读取 saturation trace 输出判定,无需重跑评测。需要原 run 跑了 `--repeat ≥ 5` 才会有 verdict(低 repeat 只画曲线)。
549
+
550
+ ```bash
551
+ omk bench saturation <reportId> [选项]
552
+ --variant <name> 只看一个 variant
553
+ --method <m> slope | bootstrap-ci-width (默认) | plateau-height
554
+ --threshold <num> 方法相关阈值 (默认随 method)
555
+ --window <num> 连续多少窗口满足才判饱和 (默认 3)
556
+ ```
557
+
558
+ HTML 报告会内联 SVG 饱和曲线(横 N,纵 mean ± 95% CI 阴影带,per-variant 一条),自动渲染。
559
+
560
+ ### `omk bench verdict`(一行 ship/no-ship 结论)
561
+
562
+ 聚合 bootstrap CI / 三层 ci-gate / saturation / human α 给一行结论。Verdict 六档:**PROGRESS**(显著改进 + 三层全过 → exit 0)/ **CAUTIOUS**(改进真实但有警告:gate 破/幅度太小/控制组本身崩 → exit 1)/ **REGRESS**(显著回退 → exit 1)/ **NOISE**(CI 跨 0,无法判定 → exit 1)/ **UNDERPOWERED**(样本不足 → exit 1)/ **SOLO**(单变体,仅自身三层 gate 过才 exit 0)。
563
+
564
+ ```bash
565
+ omk bench verdict <reportId> [选项]
566
+ --threshold <num> 三层 gate 阈值 (默认 3.5,匹配 omk bench ci)
567
+ --trivial-diff <num> "幅度太小"阈值 (默认 0.1)
568
+ --verbose 展开 per-pair 详情
569
+ ```
570
+
571
+ 与 HTML 报告顶部的 verdict pill 共享规则模块,CLI 与 UI 不会矛盾。
572
+
573
+ ### `omk bench diagnose`(样本质量诊断)
574
+
575
+ 回答"测评结论是否被坏样本污染"。诊断 7 类样本质量问题:`flat_scores`(区分度低)/ `all_pass`(太简单)/ `all_fail`(broken,error 级)/ `near_duplicate`(prompt ROUGE-1 ≥ 阈值)/ `ambiguous_rubric`(judge stddev 大,需要 `--judge-repeat ≥ 2`)/ `cost_outlier`(≥ k× median)/ `latency_outlier`(≥ k× median)/ `error_prone`(执行失败)。
576
+
577
+ ```bash
578
+ omk bench diagnose <reportId> [选项]
579
+ --top <n> 每类显示前 N 个 (默认 10,0=全部)
580
+ --duplicate-rouge <num> near-duplicate ROUGE-1 阈值 (默认 0.7)
581
+ --ambiguous-stddev <num> 歧义 judge stddev 阈值 (默认 1.0)
582
+ --cost-k <num> 成本异常倍数 vs median (默认 3)
583
+ --latency-k <num> 耗时异常倍数 vs median (默认 3)
584
+ --flat <num> flat_scores 分差阈值 (默认 0.5)
585
+ ```
586
+
587
+ 输出含 healthScore(0-100,公式 `100 - normalized × 20`,其中 `normalized = (errors×8 + warnings×3 + infos×1) / N`)。exit code 0 仅当 `healthScore ≥ 70` 且无 error 级 issue,适合 CI 链。
588
+
589
+ ### `omk bench failures`(失败 case LLM 聚类)
590
+
591
+ 跑完 14 条失败,逐个看太慢。本命令把失败样本喂给单次 LLM 调用,自动聚到 ≤ N 个 cluster,每个 cluster 给根因 + 修复建议。失败定义:`compositeScore < threshold` 或 `ok = false`。
592
+
593
+ ```bash
594
+ omk bench failures <reportId> [选项]
595
+ --judge-executor <name> 执行器 (默认 claude)
596
+ --judge-model <id> 聚类用 model (默认沿用 report.meta.judgeModel)
597
+ --max-clusters <n> 最多多少 cluster (默认 5)
598
+ --threshold <num> 算失败的分数阈值 (默认 3)
599
+ --max-feed <n> 最多喂给 LLM 多少条 (默认 50,超出取最差)
600
+ ```
601
+
602
+ 容错:tolerate ```json``` markdown fence、`"sample_id@variant"` 字符串成员形式、hallucinated 成员自动剔除、单条失败跳过 LLM 直接列出、executor 错误降级到 unclassified。
603
+
604
+ ### `omk bench diff`(报告对比 — 单参 / 双参双模式)
605
+
606
+ **单参模式**(within-report sample-level 钻取): `omk bench diff <reportId>` — 在同一份报告内对比两个 variant 的逐样本得分,默认对比 `variants[0]` vs `variants[1]`。
607
+
608
+ **双参模式**(cross-report variant-level): `omk bench diff <reportId1> <reportId2>` — 跨报告对比同一 variant 的整体均值漂移(向后兼容旧用法)。
609
+
610
+ ```bash
611
+ omk bench diff <reportId> [--variant <name>] [--regressions-only] [--threshold 0] [--top N]
612
+ omk bench diff <reportId1> <reportId2> [--regressions-only] [--threshold 0]
613
+ ```
614
+
615
+ 单参模式表格按 |Δ| 排序,Δ < threshold 高亮 regression。`--top N` 限制行数,`--regressions-only` 过滤到只看回退。
616
+
424
617
  ## `omk analyze` — 生产观测
425
618
 
426
619
  `omk bench run` 是**离线评测**(固定对照、可复现、可评分)。生产环境不一样 — 没对照组、没标准答案、没重复,所以评分在那里不成立。`omk analyze` 把已有的 Claude Code session trace 转成**skill 健康度报告**(按 skill 维度的覆盖率、缺口信号、执行稳定性、tokens/延迟)。它给的是"哪个 skill 值得拉回离线再测一遍"的线索,不是生产评分。
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Failure clustering — turn a list of "what failed" into "why it failed".
3
+ *
4
+ * Why this exists
5
+ * ---------------
6
+ * Senior-engineer pain point: 50 samples ran, 14 failed. Reading 14 failure
7
+ * cases one by one is tedious; the user wants the tool to say "8 of these
8
+ * are tool-call errors, 4 are refusals, 2 are formatting errors", with a
9
+ * suggested fix per cluster.
10
+ *
11
+ * This module:
12
+ *
13
+ * 1. Selects failed samples from a report (compositeScore < threshold OR
14
+ * ok=false on at least one variant).
15
+ * 2. Builds a compact failure description per sample (failed assertion
16
+ * types + judge reason snippet + tool failure pattern).
17
+ * 3. Sends the full set to a single LLM call asking it to (a) propose
18
+ * N cluster labels, (b) assign each failure to a cluster, (c) suggest
19
+ * a fix per cluster.
20
+ * 4. Returns the structured cluster report. Pure function — no I/O,
21
+ * accepts the executor as an argument.
22
+ *
23
+ * Design choice: single-call LLM clustering rather than embedding-based
24
+ * k-means. Reasons:
25
+ *
26
+ * - Failure descriptions are short (~50-200 tokens each), so 14 of them
27
+ * fit comfortably in one prompt. A 50-failure batch is also fine.
28
+ * - The LLM can produce HUMAN-READABLE cluster labels ("tool call error",
29
+ * "premature refusal") rather than numeric centroids — exactly what
30
+ * the user needs to act on.
31
+ * - One call = one judge cost = predictable. Embedding + clustering
32
+ * needs N+1 calls minimum.
33
+ * - Acceptable inaccuracy: clusters are advisory, not load-bearing. We
34
+ * document this clearly and surface raw failures alongside.
35
+ *
36
+ * The clustering prompt is structured to be JSON-only output for parsing
37
+ * stability. We do NOT include length-debias instructions — clustering is
38
+ * a categorization task, not a quality scoring task, so the length-debias
39
+ * directive is inapplicable.
40
+ */
41
+ import type { ExecutorFn, Report } from '../types.js';
42
+ export interface FailureClusterRequest {
43
+ report: Report;
44
+ /** Executor for the clustering LLM call. */
45
+ executor: ExecutorFn;
46
+ /** Model id to use for clustering. */
47
+ judgeModel: string;
48
+ /** Max number of clusters. Default 5; the LLM may produce fewer. */
49
+ maxClusters?: number;
50
+ /** compositeScore < this OR ok=false counts as failure. Default 3. */
51
+ failureThreshold?: number;
52
+ /** Cap on how many failures to feed the LLM (selects worst by score). Default 50. */
53
+ maxFailuresFed?: number;
54
+ }
55
+ export interface FailureCase {
56
+ sample_id: string;
57
+ variant: string;
58
+ /** Composite score (0 if errored). */
59
+ score: number;
60
+ /** Whether the executor itself errored (ok=false). */
61
+ errored: boolean;
62
+ /** Description summarizing what went wrong, fed to the LLM. */
63
+ description: string;
64
+ }
65
+ export interface FailureCluster {
66
+ /** Human-readable cluster label, in the report's primary language. */
67
+ label: string;
68
+ /** Free-text root cause analysis from the LLM. */
69
+ rootCause: string;
70
+ /** Suggested fix the user can apply. */
71
+ suggestedFix: string;
72
+ /** Sample IDs (with variant) belonging to this cluster. */
73
+ members: Array<{
74
+ sample_id: string;
75
+ variant: string;
76
+ }>;
77
+ }
78
+ export interface FailureClusterReport {
79
+ /** All failures considered (after threshold + cap). */
80
+ failures: FailureCase[];
81
+ /** Clusters returned by the LLM, sorted by member count desc. */
82
+ clusters: FailureCluster[];
83
+ /** Failures the LLM didn't put in any cluster (label = "other"). */
84
+ unclassified: Array<{
85
+ sample_id: string;
86
+ variant: string;
87
+ }>;
88
+ /** USD cost of the clustering call. */
89
+ clusterCostUSD: number;
90
+ /** Truncation flag — set when more failures existed than maxFailuresFed. */
91
+ truncated: boolean;
92
+ totalFailures: number;
93
+ }
94
+ export declare function clusterFailures(req: FailureClusterRequest): Promise<FailureClusterReport>;
95
+ export declare function formatFailureClusterReport(r: FailureClusterReport): string;
96
+ //# sourceMappingURL=failure-clusterer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"failure-clusterer.d.ts","sourceRoot":"","sources":["../../../src/analysis/failure-clusterer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,EAA8B,MAAM,aAAa,CAAC;AAElF,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,4CAA4C;IAC5C,QAAQ,EAAE,UAAU,CAAC;IACrB,sCAAsC;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,oEAAoE;IACpE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sEAAsE;IACtE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,qFAAqF;IACrF,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,sDAAsD;IACtD,OAAO,EAAE,OAAO,CAAC;IACjB,+DAA+D;IAC/D,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,sEAAsE;IACtE,KAAK,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,SAAS,EAAE,MAAM,CAAC;IAClB,wCAAwC;IACxC,YAAY,EAAE,MAAM,CAAC;IACrB,2DAA2D;IAC3D,OAAO,EAAE,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACxD;AAED,MAAM,WAAW,oBAAoB;IACnC,uDAAuD;IACvD,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,iEAAiE;IACjE,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,oEAAoE;IACpE,YAAY,EAAE,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5D,uCAAuC;IACvC,cAAc,EAAE,MAAM,CAAC;IACvB,4EAA4E;IAC5E,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;CACvB;AAeD,wBAAsB,eAAe,CAAC,GAAG,EAAE,qBAAqB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAkF/F;AAwID,wBAAgB,0BAA0B,CAAC,CAAC,EAAE,oBAAoB,GAAG,MAAM,CAsC1E"}