evalsuite-python 0.3.1__tar.gz → 0.4.0__tar.gz

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 (91) hide show
  1. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/CHANGELOG.md +39 -0
  2. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/PKG-INFO +59 -22
  3. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/README.md +46 -21
  4. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/pyproject.toml +17 -1
  5. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/src/evalsuite/__init__.py +5 -1
  6. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/src/evalsuite/benchmarks.py +157 -6
  7. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/src/evalsuite/cli/main.py +31 -1
  8. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/src/evalsuite/plot.py +146 -0
  9. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/src/evalsuite/stats/_resolve.py +23 -3
  10. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/src/evalsuite/stats/compare.py +7 -3
  11. evalsuite_python-0.4.0/src/evalsuite/text/__init__.py +152 -0
  12. evalsuite_python-0.4.0/src/evalsuite/text/_common.py +94 -0
  13. evalsuite_python-0.4.0/src/evalsuite/text/factuality.py +469 -0
  14. evalsuite_python-0.4.0/src/evalsuite/text/generation.py +1126 -0
  15. evalsuite_python-0.4.0/src/evalsuite/text/judge.py +532 -0
  16. evalsuite_python-0.4.0/src/evalsuite/text/qa.py +108 -0
  17. evalsuite_python-0.4.0/src/evalsuite/text/rag.py +235 -0
  18. evalsuite_python-0.4.0/src/evalsuite/text/reasoning.py +215 -0
  19. evalsuite_python-0.4.0/src/evalsuite/text/report.py +158 -0
  20. evalsuite_python-0.4.0/src/evalsuite/text/retrieval.py +238 -0
  21. evalsuite_python-0.4.0/src/evalsuite/text/semantic.py +465 -0
  22. evalsuite_python-0.4.0/src/evalsuite/text/structured.py +789 -0
  23. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/src/evalsuite/version.py +1 -1
  24. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/tests/integration/test_readme_examples.py +25 -0
  25. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/tests/output/test_benchmarks.py +14 -2
  26. evalsuite_python-0.4.0/tests/text/test_against_references.py +256 -0
  27. evalsuite_python-0.4.0/tests/text/test_text_edges.py +175 -0
  28. evalsuite_python-0.4.0/tests/text/test_v040.py +835 -0
  29. evalsuite_python-0.4.0/tests/vision/__init__.py +0 -0
  30. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/.gitignore +0 -0
  31. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/CONTRIBUTING.md +0 -0
  32. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/LICENSE +0 -0
  33. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/src/evalsuite/__main__.py +0 -0
  34. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/src/evalsuite/api.py +0 -0
  35. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/src/evalsuite/calibration.py +0 -0
  36. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/src/evalsuite/classification/__init__.py +0 -0
  37. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/src/evalsuite/classification/_common.py +0 -0
  38. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/src/evalsuite/classification/metrics.py +0 -0
  39. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/src/evalsuite/cli/__init__.py +0 -0
  40. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/src/evalsuite/clinical/__init__.py +0 -0
  41. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/src/evalsuite/clinical/metrics.py +0 -0
  42. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/src/evalsuite/clinical/report.py +0 -0
  43. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/src/evalsuite/core/__init__.py +0 -0
  44. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/src/evalsuite/core/context.py +0 -0
  45. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/src/evalsuite/core/exceptions.py +0 -0
  46. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/src/evalsuite/core/export.py +0 -0
  47. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/src/evalsuite/core/registry.py +0 -0
  48. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/src/evalsuite/core/result.py +0 -0
  49. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/src/evalsuite/core/types.py +0 -0
  50. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/src/evalsuite/core/validation.py +0 -0
  51. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/src/evalsuite/py.typed +0 -0
  52. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/src/evalsuite/regression/__init__.py +0 -0
  53. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/src/evalsuite/regression/metrics.py +0 -0
  54. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/src/evalsuite/reporting.py +0 -0
  55. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/src/evalsuite/stats/__init__.py +0 -0
  56. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/src/evalsuite/stats/effect.py +0 -0
  57. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/src/evalsuite/stats/hypothesis.py +0 -0
  58. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/src/evalsuite/stats/intervals.py +0 -0
  59. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/src/evalsuite/stats/paired.py +0 -0
  60. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/src/evalsuite/stats/results.py +0 -0
  61. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/src/evalsuite/vision/__init__.py +0 -0
  62. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/src/evalsuite/vision/detection.py +0 -0
  63. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/src/evalsuite/vision/segmentation.py +0 -0
  64. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/tests/__init__.py +0 -0
  65. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/tests/classification/__init__.py +0 -0
  66. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/tests/classification/test_against_sklearn.py +0 -0
  67. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/tests/clinical/__init__.py +0 -0
  68. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/tests/clinical/test_outputs.py +0 -0
  69. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/tests/clinical/test_v020.py +0 -0
  70. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/tests/conftest.py +0 -0
  71. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/tests/integration/__init__.py +0 -0
  72. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/tests/integration/test_qa.py +0 -0
  73. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/tests/integration/test_repo_hygiene.py +0 -0
  74. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/tests/output/__init__.py +0 -0
  75. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/tests/output/test_cli.py +0 -0
  76. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/tests/output/test_plot.py +0 -0
  77. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/tests/output/test_reporting.py +0 -0
  78. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/tests/regression/__init__.py +0 -0
  79. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/tests/regression/test_against_sklearn.py +0 -0
  80. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/tests/stats/__init__.py +0 -0
  81. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/tests/stats/test_branches.py +0 -0
  82. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/tests/stats/test_compare.py +0 -0
  83. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/tests/stats/test_reference.py +0 -0
  84. {evalsuite_python-0.3.1/tests/unit → evalsuite_python-0.4.0/tests/text}/__init__.py +0 -0
  85. {evalsuite_python-0.3.1/tests/vision → evalsuite_python-0.4.0/tests/unit}/__init__.py +0 -0
  86. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/tests/unit/test_core.py +0 -0
  87. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/tests/unit/test_edges.py +0 -0
  88. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/tests/vision/test_detection.py +0 -0
  89. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/tests/vision/test_edges.py +0 -0
  90. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/tests/vision/test_outputs.py +0 -0
  91. {evalsuite_python-0.3.1 → evalsuite_python-0.4.0}/tests/vision/test_segmentation.py +0 -0
@@ -6,6 +6,45 @@ All notable changes to this project are documented here. The format follows
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.4.0] - 2026-10-09
10
+
11
+ LLM evaluation (the v0.4.0 roadmap). No existing API changed.
12
+
13
+ ### Added
14
+ - `es.text` and top-level functions for language-model evaluation; 69 new registered metrics, each checked
15
+ against a reference implementation where one exists:
16
+ - Text generation: BLEU, sentence BLEU, chrF/chrF++, TER (sacreBLEU), ROUGE-1/2/L/Lsum (rouge-score),
17
+ METEOR (NLTK, synonyms pluggable), CIDEr-D (pycocoevalcap), perplexity, cross-entropy, Distinct-n,
18
+ Self-BLEU, MAUVE (mauve-text divergence frontier, NumPy k-means).
19
+ - Semantic: BERTScore, embedding cosine / Euclidean / Manhattan, MoverScore (exact transport, = POT), and
20
+ `model_score` to run COMET, BLEURT, BARTScore, AlignScore or any scorer with intervals and comparison.
21
+ - Factuality: faithfulness, hallucination (unsupported-claim) rate, groundedness, citation precision and
22
+ recall (ALCE), claim-verification accuracy, knowledge consistency, answer correctness and relevance
23
+ (RAGAS), abstention accuracy, exact match and token F1 (SQuAD).
24
+ - LLM-as-a-judge: win rate, Bradley–Terry (= choix), Elo, Krippendorff's alpha (= krippendorff), Fleiss'
25
+ kappa (= statsmodels), judge–human agreement, position consistency, verbosity and self-preference bias,
26
+ rubric scores.
27
+ - Reasoning: pass@k, majority-vote accuracy, answer extraction and benchmark accuracy (GSM8K, MATH,
28
+ multiple choice).
29
+ - RAG: Precision/Recall/Hit rate@k, MRR, MAP, NDCG (= ranx), context precision, recall and relevance,
30
+ latency summary, task success rate, failure attribution.
31
+ - Structured output: JSON validity, JSON Schema compliance (built-in validator, agrees with jsonschema),
32
+ XML validity, required-field accuracy, tool selection / argument accuracy and tool-call F1, API-call
33
+ success, instruction compliance, constraint satisfaction, format compliance, multi-turn retention,
34
+ extra-content rate.
35
+ - `es.text_report` and `evalsuite text` (CLI) for the reference-based text metrics in one report.
36
+ - `bootstrap_ci`, `compare` and the paired tests resample whole items (texts, reference lists, rankings) for
37
+ these metrics, so corpus metrics such as BLEU get proper intervals.
38
+ - Benchmark suite `llm` against sacreBLEU, rouge-score, NLTK, ranx, krippendorff and jsonschema.
39
+ - `llm` extra (`nltk`) for METEOR's default Porter stemmer.
40
+ - Plots: `es.plot.ratings` (Bradley–Terry or Elo leaderboard with bootstrap intervals), `es.plot.win_matrix`
41
+ (pairwise win rates) and `es.plot.text_scores` (per-example score distributions by system).
42
+ - Benchmark overall summary counts cases without their size suffixes.
43
+
44
+ ### Not included
45
+ - SPICE (needs a Java scene-graph parser); BLEURT, COMET, BARTScore and AlignScore run through
46
+ `es.model_score` with your model rather than being bundled.
47
+
9
48
  ## [0.3.1] - 2026-10-09
10
49
 
11
50
  Quality-assurance release: release pipeline, packaging checks, edge-case fixes and an overall benchmark.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: evalsuite-python
3
- Version: 0.3.1
3
+ Version: 0.4.0
4
4
  Summary: A unified Python framework for machine-learning, clinical, statistical, segmentation, object-detection, uncertainty, and model evaluation.
5
5
  Project-URL: Homepage, https://evalsuite-nine.vercel.app
6
6
  Project-URL: Documentation, https://evalsuite-nine.vercel.app/docs
@@ -36,22 +36,34 @@ Requires-Dist: pandas>=1.4
36
36
  Requires-Dist: scipy>=1.8
37
37
  Provides-Extra: all
38
38
  Requires-Dist: matplotlib>=3.5; extra == 'all'
39
+ Requires-Dist: nltk>=3.8; extra == 'all'
39
40
  Requires-Dist: pillow>=9; extra == 'all'
40
41
  Provides-Extra: dev
41
42
  Requires-Dist: build; extra == 'dev'
43
+ Requires-Dist: choix>=0.3; extra == 'dev'
42
44
  Requires-Dist: hypothesis>=6.80; extra == 'dev'
45
+ Requires-Dist: jsonschema>=4.18; extra == 'dev'
46
+ Requires-Dist: krippendorff>=0.6; extra == 'dev'
43
47
  Requires-Dist: matplotlib>=3.5; extra == 'dev'
44
48
  Requires-Dist: mypy>=1.10; extra == 'dev'
49
+ Requires-Dist: nltk>=3.8; extra == 'dev'
45
50
  Requires-Dist: pandas-stubs; extra == 'dev'
46
51
  Requires-Dist: pillow>=9; extra == 'dev'
47
52
  Requires-Dist: pip-audit; extra == 'dev'
53
+ Requires-Dist: pot>=0.9; (python_version < '3.14') and extra == 'dev'
54
+ Requires-Dist: pycocoevalcap>=1.2; extra == 'dev'
48
55
  Requires-Dist: pycocotools>=2.0.7; extra == 'dev'
49
56
  Requires-Dist: pytest-cov>=4; extra == 'dev'
50
57
  Requires-Dist: pytest>=7; extra == 'dev'
58
+ Requires-Dist: ranx>=0.3.19; (python_version < '3.14') and extra == 'dev'
59
+ Requires-Dist: rouge-score>=0.1.2; extra == 'dev'
51
60
  Requires-Dist: ruff>=0.6; extra == 'dev'
61
+ Requires-Dist: sacrebleu>=2.4; extra == 'dev'
52
62
  Requires-Dist: scikit-learn>=1.4; extra == 'dev'
53
63
  Requires-Dist: statsmodels>=0.13; extra == 'dev'
54
64
  Requires-Dist: twine; extra == 'dev'
65
+ Provides-Extra: llm
66
+ Requires-Dist: nltk>=3.8; extra == 'llm'
55
67
  Provides-Extra: plot
56
68
  Requires-Dist: matplotlib>=3.5; extra == 'plot'
57
69
  Provides-Extra: vision
@@ -65,12 +77,14 @@ Description-Content-Type: text/markdown
65
77
  [![Python](https://img.shields.io/pypi/pyversions/evalsuite-python)](https://pypi.org/project/evalsuite-python/)
66
78
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
67
79
 
68
- **Unified, reproducible evaluation for machine learning and research.**
80
+ **Unified, reproducible evaluation for machine learning, LLM and research.**
69
81
 
70
82
  EvalSuite brings classification, regression, clinical, statistical, segmentation and object-detection
71
83
  evaluation into one consistent, validated, documented framework.
72
84
 
73
- > **Status: stable (0.3.1).** Every item on the 0.1.0, 0.2.0 and 0.3.0 roadmaps is implemented and verified.
85
+ > **Status: stable (0.4.0).** Every item on the 0.1.0, 0.2.0, 0.3.0 and 0.4.0 roadmaps is implemented and verified
86
+ > (except SPICE, which needs a Java scene-graph parser).
87
+ > **LLM evaluation is available from 0.4.0**; LLM systems, safety and operations are planned for 0.5.0.
74
88
 
75
89
  ## Installation
76
90
 
@@ -226,6 +240,22 @@ Models can be compared over the same images with intervals and paired tests, as
226
240
  `es.compare(y_true, {"unet": masks_a, "deeplab": masks_b})` resamples images; for detection it compares
227
241
  mAP.
228
242
 
243
+ ## LLM evaluation
244
+
245
+ ```python
246
+ es.bleu(references, predictions) # sacreBLEU-identical; also chrf, ter, rouge_l, meteor, cider
247
+ es.text_report(references, predictions) # BLEU, chrF(++), TER, ROUGE, METEOR, EM, token F1 at once
248
+ es.bertscore(ref_token_embs, pred_token_embs) # from your encoder's embeddings; also mauve, moverscore
249
+ es.model_score(refs, preds, scorer=comet_fn) # any learned metric or judge, with intervals and compare
250
+ es.faithfulness(claim_verdicts) # also hallucination_rate, citation_recall, answer_correctness
251
+ es.bradley_terry(comparisons, scale="elo") # also win_rate, elo_ratings, krippendorff_alpha
252
+ es.ndcg_at_k(relevant, retrieved, k=10) # also mrr, context_precision, context_recall
253
+ es.json_schema_compliance(outputs, schema) # also tool_call_f1, instruction_compliance_rate
254
+ es.pass_at_k(n_samples, n_correct, k=10) # also benchmark_accuracy(..., style="gsm8k")
255
+ es.compare(references, {"a": preds_a, "b": preds_b}, metrics=["bleu", "rouge_l"]) # paired over examples
256
+ es.plot.ratings(comparisons) # Bradley–Terry leaderboard with intervals; also win_matrix, text_scores
257
+ ```
258
+
229
259
  ## Classification report
230
260
 
231
261
  ```python
@@ -283,28 +313,35 @@ Input files can be CSV, TSV, Parquet or JSON. Output format follows `--format` o
283
313
  ## Performance
284
314
 
285
315
  Benchmarked against reference implementations on the same data (fastest of 5 runs; Python 3.12, NumPy 2.5,
286
- Linux x86_64). **Overall: 45 of 45 rows agree with the reference** (largest
287
- difference 2.8e-14), 32 are faster, and the geometric-mean speed-up across all
288
- 15 cases and three sizes is **2.22×**. At 1,000,000 samples (pixels for
289
- segmentation; 1,000 images for detection and 50 for Hausdorff), in alphabetical order:
316
+ Linux x86_64). **Overall: 63 of 63 rows agree with the reference** (largest
317
+ difference 1.8e-14), 42 are faster, and the geometric-mean speed-up across all
318
+ 21 cases and three sizes is **2.12×**. At 1,000,000 samples (pixels for
319
+ segmentation; 1,000 images for detection and 50 for Hausdorff; 10,000 examples for the LLM cases), in
320
+ alphabetical order:
290
321
 
291
322
  | Case | Reference | EvalSuite (ms) | Reference (ms) | Speed-up |
292
323
  | --- | --- | ---: | ---: | ---: |
293
- | 10 classes: macro F1 | scikit-learn | 29.3 | 130.8 | **4.5×** |
294
- | binary: 8 label metrics via evaluate() | scikit-learn | 39.5 | 1084.1 | **27.4×** |
295
- | binary: ROC AUC | scikit-learn | 195.0 | 348.3 | **1.8×** |
296
- | calibration: slope and intercept | statsmodels | 193.0 | 1068.0 | **5.5×** |
297
- | clinical: diagnostic report (7 CIs) | statsmodels | 23.5 | 4.7 | 0.20× |
298
- | clinical: sensitivity, specificity, LR+, LR− | scikit-learn | 69.1 | 414.0 | **6.0×** |
299
- | decision curve: 99 thresholds | NumPy loop | 156.3 | 181.6 | **1.2×** |
300
- | detection: COCO evaluation (1000 images) | pycocotools | 817.3 | 983.7 | **1.2×** |
301
- | multiple testing: Hochberg (n p-values) | statsmodels | 82.0 | 108.0 | **1.3×** |
302
- | regression: MAE, MSE, RMSE, R² via evaluate() | scikit-learn | 23.1 | 10.8 | 0.47× |
303
- | segmentation: Dice and IoU per class (n = pixels) | scikit-learn | 43.0 | 290.0 | **6.8×** |
304
- | segmentation: Hausdorff distance (50 images) | SciPy | 27.0 | 20.2 | 0.75× |
305
- | statistics: Cramér's V (5×5 table) | SciPy | 0.5 | 0.4 | 0.88× |
306
- | statistics: Mann–Whitney U | SciPy | 349.7 | 350.2 | **1.0×** |
307
- | statistics: Welch t-test | SciPy | 14.5 | 7.2 | 0.49× |
324
+ | 10 classes: macro F1 | scikit-learn | 23.8 | 135.5 | **5.7×** |
325
+ | agreement: Krippendorff's alpha, interval (4 raters × 10000 items) | krippendorff | 3.5 | 3.4 | 0.96× |
326
+ | binary: 8 label metrics via evaluate() | scikit-learn | 28.5 | 1023.8 | **36.0×** |
327
+ | binary: ROC AUC | scikit-learn | 187.4 | 327.3 | **1.7×** |
328
+ | calibration: slope and intercept | statsmodels | 205.7 | 1173.7 | **5.7×** |
329
+ | clinical: diagnostic report (7 CIs) | statsmodels | 19.6 | 4.9 | 0.25× |
330
+ | clinical: sensitivity, specificity, LR+, LR− | scikit-learn | 75.2 | 411.5 | **5.5×** |
331
+ | decision curve: 99 thresholds | NumPy loop | 165.0 | 201.9 | **1.2×** |
332
+ | detection: COCO evaluation (1000 images) | pycocotools | 838.7 | 848.6 | **1.0×** |
333
+ | multiple testing: Hochberg (n p-values) | statsmodels | 72.1 | 79.0 | **1.1×** |
334
+ | regression: MAE, MSE, RMSE, R² via evaluate() | scikit-learn | 22.9 | 10.6 | 0.47× |
335
+ | retrieval: MRR, MAP@20, NDCG@10 (10000 queries) | ranx | 170.4 | 861.0 | **5.1×** |
336
+ | segmentation: Dice and IoU per class (n = pixels) | scikit-learn | 34.3 | 256.0 | **7.5×** |
337
+ | segmentation: Hausdorff distance (50 images) | SciPy | 24.2 | 19.8 | 0.82× |
338
+ | statistics: Cramér's V (5×5 table) | SciPy | 0.2 | 0.2 | **1.0×** |
339
+ | statistics: Mann–Whitney U | SciPy | 353.5 | 340.6 | 0.96× |
340
+ | statistics: Welch t-test | SciPy | 16.1 | 8.3 | 0.52× |
341
+ | structured: JSON Schema compliance (10000 documents) | jsonschema | 144.6 | 330.5 | **2.3×** |
342
+ | text: corpus BLEU and chrF (10000 sentences) | sacreBLEU | 2789.8 | 3051.8 | **1.1×** |
343
+ | text: METEOR, exact and stem matches (10000 sentences) | NLTK | 488.1 | 626.7 | **1.3×** |
344
+ | text: ROUGE-1, ROUGE-2, ROUGE-L (10000 sentences) | rouge-score | 1290.1 | 1095.6 | 0.85× |
308
345
 
309
346
  `evaluate()` validates inputs once and builds the confusion matrix once for all metrics, which is where most
310
347
  of the speed-up comes from. Hypothesis tests use SciPy underneath, so they match its speed at best; rows
@@ -5,12 +5,14 @@
5
5
  [![Python](https://img.shields.io/pypi/pyversions/evalsuite-python)](https://pypi.org/project/evalsuite-python/)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
7
7
 
8
- **Unified, reproducible evaluation for machine learning and research.**
8
+ **Unified, reproducible evaluation for machine learning, LLM and research.**
9
9
 
10
10
  EvalSuite brings classification, regression, clinical, statistical, segmentation and object-detection
11
11
  evaluation into one consistent, validated, documented framework.
12
12
 
13
- > **Status: stable (0.3.1).** Every item on the 0.1.0, 0.2.0 and 0.3.0 roadmaps is implemented and verified.
13
+ > **Status: stable (0.4.0).** Every item on the 0.1.0, 0.2.0, 0.3.0 and 0.4.0 roadmaps is implemented and verified
14
+ > (except SPICE, which needs a Java scene-graph parser).
15
+ > **LLM evaluation is available from 0.4.0**; LLM systems, safety and operations are planned for 0.5.0.
14
16
 
15
17
  ## Installation
16
18
 
@@ -166,6 +168,22 @@ Models can be compared over the same images with intervals and paired tests, as
166
168
  `es.compare(y_true, {"unet": masks_a, "deeplab": masks_b})` resamples images; for detection it compares
167
169
  mAP.
168
170
 
171
+ ## LLM evaluation
172
+
173
+ ```python
174
+ es.bleu(references, predictions) # sacreBLEU-identical; also chrf, ter, rouge_l, meteor, cider
175
+ es.text_report(references, predictions) # BLEU, chrF(++), TER, ROUGE, METEOR, EM, token F1 at once
176
+ es.bertscore(ref_token_embs, pred_token_embs) # from your encoder's embeddings; also mauve, moverscore
177
+ es.model_score(refs, preds, scorer=comet_fn) # any learned metric or judge, with intervals and compare
178
+ es.faithfulness(claim_verdicts) # also hallucination_rate, citation_recall, answer_correctness
179
+ es.bradley_terry(comparisons, scale="elo") # also win_rate, elo_ratings, krippendorff_alpha
180
+ es.ndcg_at_k(relevant, retrieved, k=10) # also mrr, context_precision, context_recall
181
+ es.json_schema_compliance(outputs, schema) # also tool_call_f1, instruction_compliance_rate
182
+ es.pass_at_k(n_samples, n_correct, k=10) # also benchmark_accuracy(..., style="gsm8k")
183
+ es.compare(references, {"a": preds_a, "b": preds_b}, metrics=["bleu", "rouge_l"]) # paired over examples
184
+ es.plot.ratings(comparisons) # Bradley–Terry leaderboard with intervals; also win_matrix, text_scores
185
+ ```
186
+
169
187
  ## Classification report
170
188
 
171
189
  ```python
@@ -223,28 +241,35 @@ Input files can be CSV, TSV, Parquet or JSON. Output format follows `--format` o
223
241
  ## Performance
224
242
 
225
243
  Benchmarked against reference implementations on the same data (fastest of 5 runs; Python 3.12, NumPy 2.5,
226
- Linux x86_64). **Overall: 45 of 45 rows agree with the reference** (largest
227
- difference 2.8e-14), 32 are faster, and the geometric-mean speed-up across all
228
- 15 cases and three sizes is **2.22×**. At 1,000,000 samples (pixels for
229
- segmentation; 1,000 images for detection and 50 for Hausdorff), in alphabetical order:
244
+ Linux x86_64). **Overall: 63 of 63 rows agree with the reference** (largest
245
+ difference 1.8e-14), 42 are faster, and the geometric-mean speed-up across all
246
+ 21 cases and three sizes is **2.12×**. At 1,000,000 samples (pixels for
247
+ segmentation; 1,000 images for detection and 50 for Hausdorff; 10,000 examples for the LLM cases), in
248
+ alphabetical order:
230
249
 
231
250
  | Case | Reference | EvalSuite (ms) | Reference (ms) | Speed-up |
232
251
  | --- | --- | ---: | ---: | ---: |
233
- | 10 classes: macro F1 | scikit-learn | 29.3 | 130.8 | **4.5×** |
234
- | binary: 8 label metrics via evaluate() | scikit-learn | 39.5 | 1084.1 | **27.4×** |
235
- | binary: ROC AUC | scikit-learn | 195.0 | 348.3 | **1.8×** |
236
- | calibration: slope and intercept | statsmodels | 193.0 | 1068.0 | **5.5×** |
237
- | clinical: diagnostic report (7 CIs) | statsmodels | 23.5 | 4.7 | 0.20× |
238
- | clinical: sensitivity, specificity, LR+, LR− | scikit-learn | 69.1 | 414.0 | **6.0×** |
239
- | decision curve: 99 thresholds | NumPy loop | 156.3 | 181.6 | **1.2×** |
240
- | detection: COCO evaluation (1000 images) | pycocotools | 817.3 | 983.7 | **1.2×** |
241
- | multiple testing: Hochberg (n p-values) | statsmodels | 82.0 | 108.0 | **1.3×** |
242
- | regression: MAE, MSE, RMSE, R² via evaluate() | scikit-learn | 23.1 | 10.8 | 0.47× |
243
- | segmentation: Dice and IoU per class (n = pixels) | scikit-learn | 43.0 | 290.0 | **6.8×** |
244
- | segmentation: Hausdorff distance (50 images) | SciPy | 27.0 | 20.2 | 0.75× |
245
- | statistics: Cramér's V (5×5 table) | SciPy | 0.5 | 0.4 | 0.88× |
246
- | statistics: Mann–Whitney U | SciPy | 349.7 | 350.2 | **1.0×** |
247
- | statistics: Welch t-test | SciPy | 14.5 | 7.2 | 0.49× |
252
+ | 10 classes: macro F1 | scikit-learn | 23.8 | 135.5 | **5.7×** |
253
+ | agreement: Krippendorff's alpha, interval (4 raters × 10000 items) | krippendorff | 3.5 | 3.4 | 0.96× |
254
+ | binary: 8 label metrics via evaluate() | scikit-learn | 28.5 | 1023.8 | **36.0×** |
255
+ | binary: ROC AUC | scikit-learn | 187.4 | 327.3 | **1.7×** |
256
+ | calibration: slope and intercept | statsmodels | 205.7 | 1173.7 | **5.7×** |
257
+ | clinical: diagnostic report (7 CIs) | statsmodels | 19.6 | 4.9 | 0.25× |
258
+ | clinical: sensitivity, specificity, LR+, LR− | scikit-learn | 75.2 | 411.5 | **5.5×** |
259
+ | decision curve: 99 thresholds | NumPy loop | 165.0 | 201.9 | **1.2×** |
260
+ | detection: COCO evaluation (1000 images) | pycocotools | 838.7 | 848.6 | **1.0×** |
261
+ | multiple testing: Hochberg (n p-values) | statsmodels | 72.1 | 79.0 | **1.1×** |
262
+ | regression: MAE, MSE, RMSE, R² via evaluate() | scikit-learn | 22.9 | 10.6 | 0.47× |
263
+ | retrieval: MRR, MAP@20, NDCG@10 (10000 queries) | ranx | 170.4 | 861.0 | **5.1×** |
264
+ | segmentation: Dice and IoU per class (n = pixels) | scikit-learn | 34.3 | 256.0 | **7.5×** |
265
+ | segmentation: Hausdorff distance (50 images) | SciPy | 24.2 | 19.8 | 0.82× |
266
+ | statistics: Cramér's V (5×5 table) | SciPy | 0.2 | 0.2 | **1.0×** |
267
+ | statistics: Mann–Whitney U | SciPy | 353.5 | 340.6 | 0.96× |
268
+ | statistics: Welch t-test | SciPy | 16.1 | 8.3 | 0.52× |
269
+ | structured: JSON Schema compliance (10000 documents) | jsonschema | 144.6 | 330.5 | **2.3×** |
270
+ | text: corpus BLEU and chrF (10000 sentences) | sacreBLEU | 2789.8 | 3051.8 | **1.1×** |
271
+ | text: METEOR, exact and stem matches (10000 sentences) | NLTK | 488.1 | 626.7 | **1.3×** |
272
+ | text: ROUGE-1, ROUGE-2, ROUGE-L (10000 sentences) | rouge-score | 1290.1 | 1095.6 | 0.85× |
248
273
 
249
274
  `evaluate()` validates inputs once and builds the confusion matrix once for all metrics, which is where most
250
275
  of the speed-up comes from. Hypothesis tests use SciPy underneath, so they match its speed at best; rows
@@ -38,7 +38,8 @@ dependencies = ["numpy>=1.22", "scipy>=1.8", "pandas>=1.4"]
38
38
  [project.optional-dependencies]
39
39
  plot = ["matplotlib>=3.5"]
40
40
  vision = ["pillow>=9"]
41
- all = ["matplotlib>=3.5", "pillow>=9"]
41
+ llm = ["nltk>=3.8"]
42
+ all = ["matplotlib>=3.5", "pillow>=9", "nltk>=3.8"]
42
43
  dev = [
43
44
  "pytest>=7",
44
45
  "pytest-cov>=4",
@@ -48,6 +49,15 @@ dev = [
48
49
  "matplotlib>=3.5",
49
50
  "pillow>=9",
50
51
  "pycocotools>=2.0.7",
52
+ "nltk>=3.8",
53
+ "sacrebleu>=2.4",
54
+ "rouge-score>=0.1.2",
55
+ "pycocoevalcap>=1.2",
56
+ "jsonschema>=4.18",
57
+ "choix>=0.3",
58
+ "krippendorff>=0.6",
59
+ "pot>=0.9; python_version < '3.14'",
60
+ "ranx>=0.3.19; python_version < '3.14'",
51
61
  "ruff>=0.6",
52
62
  "mypy>=1.10",
53
63
  "pandas-stubs",
@@ -99,6 +109,12 @@ files = ["src/evalsuite"]
99
109
  module = ["scipy.*", "matplotlib.*"]
100
110
  ignore_missing_imports = true
101
111
 
112
+ [[tool.mypy.overrides]]
113
+ # optional reference libraries used only by the benchmarks and METEOR's default stemmer
114
+ module = ["sacrebleu.*", "rouge_score.*", "nltk.*", "ranx.*", "krippendorff.*", "jsonschema.*", "pycocotools.*"]
115
+ ignore_missing_imports = true
116
+ follow_untyped_imports = false
117
+
102
118
  [tool.pytest.ini_options]
103
119
  testpaths = ["tests"]
104
120
  addopts = "-q --strict-markers"
@@ -5,7 +5,7 @@
5
5
  >>> print(result.summary()) # doctest: +SKIP
6
6
  """
7
7
 
8
- from . import calibration, classification, clinical, plot, regression, stats, vision
8
+ from . import calibration, classification, clinical, plot, regression, stats, text, vision
9
9
  from .api import evaluate
10
10
  from .calibration import (
11
11
  CalibrationReport,
@@ -110,6 +110,8 @@ from .stats import (
110
110
  t_test,
111
111
  wilcoxon_test,
112
112
  )
113
+ from .text import * # noqa: F403 (every LLM-evaluation function, listed in text.__all__)
114
+ from .text import __all__ as _text_all
113
115
  from .version import __version__
114
116
  from .vision import (
115
117
  DetectionReport,
@@ -134,6 +136,7 @@ from .vision import (
134
136
  )
135
137
 
136
138
  __all__ = [
139
+ "text",
137
140
  "SegmentationReport",
138
141
  "segmentation_report",
139
142
  "detection_pr_curve",
@@ -258,3 +261,4 @@ __all__ = [
258
261
  "specificity",
259
262
  "top_k_accuracy",
260
263
  ]
264
+ __all__ += list(_text_all)
@@ -213,8 +213,8 @@ def _vision_cases(n: int, rng: np.random.Generator) -> list[Case]:
213
213
  import contextlib as _ctx
214
214
  import io
215
215
 
216
- from pycocotools.coco import COCO # type: ignore[import-untyped]
217
- from pycocotools.cocoeval import COCOeval # type: ignore[import-untyped]
216
+ from pycocotools.coco import COCO
217
+ from pycocotools.cocoeval import COCOeval
218
218
 
219
219
  def coco_map() -> list[float]:
220
220
  images, anns, dets, aid = [], [], [], 1
@@ -262,6 +262,155 @@ def _vision_cases(n: int, rng: np.random.Generator) -> list[Case]:
262
262
  return [(name, ref, es_fn, refs.get(name)) for name, ref, es_fn, _ in cases]
263
263
 
264
264
 
265
+ _LLM_VOCAB = ( # noqa: SIM905
266
+ "the a cat dog sat on mat quickly model models data run running ran 2.5 3,000 well-known state-of-the-art "
267
+ "( ) , . ! ? don't U.S. e-mail Hello WORLD naive cafe results show improves baseline evaluation"
268
+ ).split()
269
+
270
+
271
+ def _llm_cases(n: int, rng: np.random.Generator) -> list[Case]:
272
+ """v0.4.0: text generation, retrieval, rater agreement and structured output (n / 100 examples)."""
273
+ import evalsuite as es
274
+
275
+ m = max(10, n // 100)
276
+ preds, refs_ = [], []
277
+ for _ in range(m):
278
+ base = list(rng.choice(_LLM_VOCAB, int(rng.integers(5, 25))))
279
+ keep = [w for w in base if rng.random() > 0.2] + list(rng.choice(_LLM_VOCAB, int(rng.integers(0, 4))))
280
+ preds.append(" ".join(keep))
281
+ refs_.append(" ".join(base))
282
+ queries = [
283
+ (set(rng.choice(200, int(rng.integers(1, 6)), replace=False).tolist()), rng.permutation(200)[:20].tolist())
284
+ for _ in range(m)
285
+ ]
286
+ rel, ret = [q[0] for q in queries], [q[1] for q in queries]
287
+ ratings = rng.integers(1, 6, (4, m)).astype(float)
288
+ ratings[rng.random(ratings.shape) < 0.1] = np.nan
289
+ schema = {
290
+ "type": "object",
291
+ "properties": {
292
+ "name": {"type": "string", "minLength": 1},
293
+ "age": {"type": "integer", "minimum": 0},
294
+ "tags": {"type": "array", "items": {"type": "string"}, "uniqueItems": True},
295
+ },
296
+ "required": ["name", "age"],
297
+ "additionalProperties": False,
298
+ }
299
+ docs = []
300
+ for i in range(m):
301
+ d: dict[str, Any] = {"name": f"n{i}", "age": int(rng.integers(-2, 90)), "tags": ["a", "b"]}
302
+ if rng.random() < 0.1:
303
+ d["extra"] = 1
304
+ if rng.random() < 0.1:
305
+ del d["name"]
306
+ docs.append(d)
307
+ import json as _json
308
+
309
+ docs_text = [_json.dumps(d) for d in docs]
310
+
311
+ cases: list[Case] = [
312
+ (
313
+ f"text: corpus BLEU and chrF ({m} sentences)",
314
+ "sacreBLEU",
315
+ lambda: [float(es.bleu(refs_, preds)), float(es.chrf(refs_, preds))],
316
+ None,
317
+ ),
318
+ (
319
+ f"text: ROUGE-1, ROUGE-2, ROUGE-L ({m} sentences)",
320
+ "rouge-score",
321
+ lambda: [
322
+ float(es.rouge_1(refs_, preds)),
323
+ float(es.rouge_2(refs_, preds)),
324
+ float(es.rouge_l(refs_, preds)),
325
+ ],
326
+ None,
327
+ ),
328
+ (
329
+ f"text: METEOR, exact and stem matches ({m} sentences)",
330
+ "NLTK",
331
+ lambda: [float(es.meteor(refs_, preds))],
332
+ None,
333
+ ),
334
+ (
335
+ f"retrieval: MRR, MAP@20, NDCG@10 ({m} queries)",
336
+ "ranx",
337
+ lambda: [
338
+ float(es.mrr(rel, ret)),
339
+ float(es.mean_average_precision_at_k(rel, ret, k=20)),
340
+ float(es.ndcg_at_k(rel, ret, k=10)),
341
+ ],
342
+ None,
343
+ ),
344
+ (
345
+ f"agreement: Krippendorff's alpha, interval (4 raters × {m} items)",
346
+ "krippendorff",
347
+ lambda: [float(es.krippendorff_alpha(ratings, level="interval"))],
348
+ None,
349
+ ),
350
+ (
351
+ f"structured: JSON Schema compliance ({m} documents)",
352
+ "jsonschema",
353
+ lambda: [float(es.json_schema_compliance(docs_text, schema))],
354
+ None,
355
+ ),
356
+ ]
357
+ refs: dict[str, Callable[[], Any]] = {}
358
+ with contextlib.suppress(ImportError):
359
+ import sacrebleu
360
+
361
+ refs[cases[0][0]] = lambda: [
362
+ sacrebleu.corpus_bleu(preds, [refs_], force=True).score,
363
+ sacrebleu.corpus_chrf(preds, [refs_]).score,
364
+ ]
365
+ with contextlib.suppress(ImportError):
366
+ from rouge_score import rouge_scorer
367
+
368
+ def rouge_ref() -> list[float]:
369
+ sc = rouge_scorer.RougeScorer(["rouge1", "rouge2", "rougeL"])
370
+ out = [sc.score(r, p) for r, p in zip(refs_, preds)]
371
+ return [float(np.mean([o[k].fmeasure for o in out])) for k in ("rouge1", "rouge2", "rougeL")]
372
+
373
+ refs[cases[1][0]] = rouge_ref
374
+ with contextlib.suppress(ImportError):
375
+ from nltk.translate.meteor_score import meteor_score
376
+
377
+ class _NoWordNet:
378
+ def synsets(self, _w: str) -> list[Any]:
379
+ return []
380
+
381
+ refs[cases[2][0]] = lambda: [
382
+ float(
383
+ np.mean([meteor_score([r.split()], p.split(), wordnet=_NoWordNet()) for r, p in zip(refs_, preds)])
384
+ )
385
+ ]
386
+ with contextlib.suppress(ImportError):
387
+ from ranx import Qrels, Run
388
+ from ranx import evaluate as ranx_evaluate
389
+
390
+ def ranx_ref() -> list[float]:
391
+ qrels = Qrels({f"q{i}": {f"d{d}": 1 for d in r} for i, r in enumerate(rel)})
392
+ run = Run({f"q{i}": {f"d{d}": float(len(r) - j) for j, d in enumerate(r)} for i, r in enumerate(ret)})
393
+ res = ranx_evaluate(qrels, run, ["mrr", "map@20", "ndcg@10"])
394
+ return [float(res["mrr"]), float(res["map@20"]), float(res["ndcg@10"])]
395
+
396
+ refs[cases[3][0]] = ranx_ref
397
+ with contextlib.suppress(ImportError):
398
+ import krippendorff
399
+
400
+ refs[cases[4][0]] = lambda: [
401
+ float(krippendorff.alpha(reliability_data=ratings, level_of_measurement="interval"))
402
+ ]
403
+ with contextlib.suppress(ImportError):
404
+ import jsonschema
405
+
406
+ def jsonschema_ref() -> list[float]:
407
+ v = jsonschema.Draft202012Validator(schema)
408
+ return [float(np.mean([v.is_valid(_json.loads(t)) for t in docs_text]))]
409
+
410
+ refs[cases[5][0]] = jsonschema_ref
411
+ return [(name, ref, es_fn, refs.get(name)) for name, ref, es_fn, _ in cases]
412
+
413
+
265
414
  def _clinical_cases(n: int, rng: np.random.Generator) -> list[Case]:
266
415
  """v0.2.0: diagnostic accuracy, calibration, decision curves and statistical tests."""
267
416
  import evalsuite as es
@@ -377,6 +526,7 @@ _GROUPS = (
377
526
  ("Classification and regression", ("binary", "10 classes", "regression")),
378
527
  ("Clinical, calibration and statistics", ("clinical", "calibration", "decision", "statistics", "multiple")),
379
528
  ("Segmentation and object detection", ("segmentation", "detection")),
529
+ ("LLM evaluation", ("text", "retrieval", "agreement", "structured")),
380
530
  )
381
531
  MATCH_TOLERANCE = 1e-9
382
532
 
@@ -419,7 +569,7 @@ class BenchmarkResult:
419
569
  speedups = [r["speedup"] for r in compared]
420
570
  return {
421
571
  "group": name,
422
- "cases": len({re.sub(r"\s*\(\d+ images?\)", "", r["case"]) for r in rows}),
572
+ "cases": len({re.sub(r"\s*\((?=[^)]*\d)[^)]*\)$", "", r["case"]) for r in rows}),
423
573
  "rows": len(rows),
424
574
  "compared": len(compared),
425
575
  "matching": sum(
@@ -597,8 +747,8 @@ def run_benchmarks(
597
747
 
598
748
  if repeat < 1:
599
749
  raise ValueError("repeat must be at least 1.")
600
- if suite not in ("all", "core", "clinical", "vision"):
601
- raise ValueError("suite must be 'all', 'core', 'clinical' or 'vision'.")
750
+ if suite not in ("all", "core", "clinical", "vision", "llm"):
751
+ raise ValueError("suite must be 'all', 'core', 'clinical', 'vision' or 'llm'.")
602
752
  rng = np.random.default_rng(random_state)
603
753
  rows: list[dict[str, Any]] = []
604
754
  versions: dict[str, Optional[str]] = {"sklearn": None, "statsmodels": None, "pycocotools": None}
@@ -613,7 +763,8 @@ def run_benchmarks(
613
763
  "core": [_core_cases],
614
764
  "clinical": [_clinical_cases],
615
765
  "vision": [_vision_cases],
616
- "all": [_core_cases, _clinical_cases, _vision_cases],
766
+ "llm": [_llm_cases],
767
+ "all": [_core_cases, _clinical_cases, _vision_cases, _llm_cases],
617
768
  }[suite]
618
769
  for n in sizes:
619
770
  for build in builders:
@@ -297,6 +297,24 @@ def cmd_detection(args: argparse.Namespace) -> int:
297
297
  return 0
298
298
 
299
299
 
300
+ def cmd_text(args: argparse.Namespace) -> int:
301
+ import evalsuite as es
302
+
303
+ df = read_table(args.file)
304
+ preds = [str(v) for v in column(df, args.prediction, "--prediction")]
305
+ ref_cols = [column(df, c, "--reference") for c in args.reference]
306
+ refs = [[str(col[i]) for col in ref_cols if not _missing(col[i])] for i in range(len(preds))]
307
+ if any(not r for r in refs):
308
+ raise CLIError("Every row needs at least one non-empty reference.")
309
+ report = es.text_report(refs, preds, metrics=metric_list(args.metrics))
310
+ emit(report, args)
311
+ return 0
312
+
313
+
314
+ def _missing(v: Any) -> bool:
315
+ return v is None or (isinstance(v, float) and v != v) or (isinstance(v, str) and not v.strip())
316
+
317
+
300
318
  def cmd_compare(args: argparse.Namespace) -> int:
301
319
  import evalsuite as es
302
320
 
@@ -506,6 +524,18 @@ def build_parser() -> argparse.ArgumentParser:
506
524
  add_output(p, digits=3)
507
525
  p.set_defaults(func=cmd_detection)
508
526
 
527
+ p = sub.add_parser(
528
+ "text", help="text generation: BLEU, chrF, chrF++, TER, ROUGE, METEOR, exact match, token F1"
529
+ )
530
+ p.add_argument("file", help="CSV, TSV, Parquet or JSON-lines file with one example per row ('-' for stdin)")
531
+ p.add_argument("--prediction", required=True, help="column with the model output")
532
+ p.add_argument(
533
+ "--reference", required=True, action="append", help="column with a reference (repeat for several)"
534
+ )
535
+ p.add_argument("--metrics", help="comma-separated subset, e.g. bleu,chrf,rouge_l")
536
+ add_output(p)
537
+ p.set_defaults(func=cmd_text)
538
+
509
539
  p = sub.add_parser(
510
540
  "plot", help="save a ROC, PR, calibration, decision-curve, confusion-matrix or residual plot"
511
541
  )
@@ -529,7 +559,7 @@ def build_parser() -> argparse.ArgumentParser:
529
559
  p.add_argument("--repeat", type=int, default=5, help="timed repetitions per case; the fastest is reported")
530
560
  p.add_argument(
531
561
  "--suite",
532
- choices=("all", "core", "clinical", "vision"),
562
+ choices=("all", "core", "clinical", "vision", "llm"),
533
563
  default="all",
534
564
  help="core: classification/regression; clinical: clinical, calibration, tests; vision: segmentation, "
535
565
  "detection (default all)",