slmeval-cli 0.1.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 (218) hide show
  1. slmeval_cli-0.1.0/.gitattributes +15 -0
  2. slmeval_cli-0.1.0/.github/ISSUE_TEMPLATE/benchmark_result.yml +38 -0
  3. slmeval_cli-0.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +37 -0
  4. slmeval_cli-0.1.0/.github/ISSUE_TEMPLATE/config.yml +5 -0
  5. slmeval_cli-0.1.0/.github/ISSUE_TEMPLATE/feature_request.yml +19 -0
  6. slmeval_cli-0.1.0/.github/PULL_REQUEST_TEMPLATE.md +14 -0
  7. slmeval_cli-0.1.0/.github/workflows/ci.yml +163 -0
  8. slmeval_cli-0.1.0/.github/workflows/gate-demo.yml +107 -0
  9. slmeval_cli-0.1.0/.github/workflows/release.yml +114 -0
  10. slmeval_cli-0.1.0/.gitignore +12 -0
  11. slmeval_cli-0.1.0/CHANGELOG.md +59 -0
  12. slmeval_cli-0.1.0/CODE_OF_CONDUCT.md +7 -0
  13. slmeval_cli-0.1.0/CONTRIBUTING.md +96 -0
  14. slmeval_cli-0.1.0/LICENSE +201 -0
  15. slmeval_cli-0.1.0/PKG-INFO +484 -0
  16. slmeval_cli-0.1.0/README.md +245 -0
  17. slmeval_cli-0.1.0/RELEASING.md +77 -0
  18. slmeval_cli-0.1.0/SECURITY.md +24 -0
  19. slmeval_cli-0.1.0/docs/architecture.md +106 -0
  20. slmeval_cli-0.1.0/docs/assets/fault-injection.svg +92 -0
  21. slmeval_cli-0.1.0/docs/assets/how-it-works.svg +100 -0
  22. slmeval_cli-0.1.0/docs/assets/pass-rates.svg +94 -0
  23. slmeval_cli-0.1.0/docs/assets/scorecard.svg +150 -0
  24. slmeval_cli-0.1.0/docs/assets/where-models-failed.svg +100 -0
  25. slmeval_cli-0.1.0/docs/ci.md +98 -0
  26. slmeval_cli-0.1.0/docs/configuration.md +174 -0
  27. slmeval_cli-0.1.0/docs/getting-started.md +194 -0
  28. slmeval_cli-0.1.0/docs/reading-results.md +114 -0
  29. slmeval_cli-0.1.0/examples/github-action/README.md +108 -0
  30. slmeval_cli-0.1.0/examples/github-action/action.yml +361 -0
  31. slmeval_cli-0.1.0/examples/github-action/slmeval.yml +62 -0
  32. slmeval_cli-0.1.0/examples/judge/README.md +104 -0
  33. slmeval_cli-0.1.0/examples/judge/cases.jsonl +1 -0
  34. slmeval_cli-0.1.0/examples/judge/eval.toml +42 -0
  35. slmeval_cli-0.1.0/examples/judge/labels.example.jsonl +31 -0
  36. slmeval_cli-0.1.0/examples/judge/stub_target.py +3 -0
  37. slmeval_cli-0.1.0/examples/support_agent/README.md +89 -0
  38. slmeval_cli-0.1.0/examples/support_agent/__init__.py +42 -0
  39. slmeval_cli-0.1.0/examples/support_agent/agent.py +179 -0
  40. slmeval_cli-0.1.0/examples/support_agent/erp.py +108 -0
  41. slmeval_cli-0.1.0/examples/support_agent/eval.toml +213 -0
  42. slmeval_cli-0.1.0/examples/support_agent/golden_cases.jsonl +4 -0
  43. slmeval_cli-0.1.0/examples/support_agent/graph.py +197 -0
  44. slmeval_cli-0.1.0/examples/support_agent/tools.py +142 -0
  45. slmeval_cli-0.1.0/pyproject.toml +98 -0
  46. slmeval_cli-0.1.0/scripts/install_smoke.py +350 -0
  47. slmeval_cli-0.1.0/scripts/readme_visuals.py +565 -0
  48. slmeval_cli-0.1.0/scripts/teardown.py +347 -0
  49. slmeval_cli-0.1.0/src/slmeval/__init__.py +71 -0
  50. slmeval_cli-0.1.0/src/slmeval/__main__.py +14 -0
  51. slmeval_cli-0.1.0/src/slmeval/adapters/__init__.py +24 -0
  52. slmeval_cli-0.1.0/src/slmeval/adapters/instrument.py +135 -0
  53. slmeval_cli-0.1.0/src/slmeval/adapters/langgraph.py +237 -0
  54. slmeval_cli-0.1.0/src/slmeval/cli/__init__.py +15 -0
  55. slmeval_cli-0.1.0/src/slmeval/cli/commands/__init__.py +35 -0
  56. slmeval_cli-0.1.0/src/slmeval/cli/commands/baseline_cmd.py +201 -0
  57. slmeval_cli-0.1.0/src/slmeval/cli/commands/cache_cmd.py +249 -0
  58. slmeval_cli-0.1.0/src/slmeval/cli/commands/dataset_cmd.py +52 -0
  59. slmeval_cli-0.1.0/src/slmeval/cli/commands/doctor_cmd.py +68 -0
  60. slmeval_cli-0.1.0/src/slmeval/cli/commands/gate_cmd.py +602 -0
  61. slmeval_cli-0.1.0/src/slmeval/cli/commands/init_cmd.py +144 -0
  62. slmeval_cli-0.1.0/src/slmeval/cli/commands/judge_cmd.py +284 -0
  63. slmeval_cli-0.1.0/src/slmeval/cli/commands/record_cmd.py +154 -0
  64. slmeval_cli-0.1.0/src/slmeval/cli/commands/report_cmd.py +98 -0
  65. slmeval_cli-0.1.0/src/slmeval/cli/commands/run_cmd.py +247 -0
  66. slmeval_cli-0.1.0/src/slmeval/cli/commands/show_cmd.py +482 -0
  67. slmeval_cli-0.1.0/src/slmeval/cli/commands/stubs.py +57 -0
  68. slmeval_cli-0.1.0/src/slmeval/cli/commands/validate_cmd.py +296 -0
  69. slmeval_cli-0.1.0/src/slmeval/cli/loading.py +149 -0
  70. slmeval_cli-0.1.0/src/slmeval/cli/main.py +91 -0
  71. slmeval_cli-0.1.0/src/slmeval/cli/render.py +251 -0
  72. slmeval_cli-0.1.0/src/slmeval/cli/wrap.py +31 -0
  73. slmeval_cli-0.1.0/src/slmeval/config/__init__.py +47 -0
  74. slmeval_cli-0.1.0/src/slmeval/config/loader.py +363 -0
  75. slmeval_cli-0.1.0/src/slmeval/config/locate.py +97 -0
  76. slmeval_cli-0.1.0/src/slmeval/config/models.py +430 -0
  77. slmeval_cli-0.1.0/src/slmeval/config/templates.py +246 -0
  78. slmeval_cli-0.1.0/src/slmeval/core/__init__.py +78 -0
  79. slmeval_cli-0.1.0/src/slmeval/core/adapter.py +253 -0
  80. slmeval_cli-0.1.0/src/slmeval/core/context.py +418 -0
  81. slmeval_cli-0.1.0/src/slmeval/core/outcome.py +260 -0
  82. slmeval_cli-0.1.0/src/slmeval/core/router.py +319 -0
  83. slmeval_cli-0.1.0/src/slmeval/core/runner.py +1209 -0
  84. slmeval_cli-0.1.0/src/slmeval/core/trace.py +188 -0
  85. slmeval_cli-0.1.0/src/slmeval/core/verdict.py +127 -0
  86. slmeval_cli-0.1.0/src/slmeval/dataset/__init__.py +8 -0
  87. slmeval_cli-0.1.0/src/slmeval/dataset/loader.py +130 -0
  88. slmeval_cli-0.1.0/src/slmeval/dataset/models.py +108 -0
  89. slmeval_cli-0.1.0/src/slmeval/doctor/__init__.py +7 -0
  90. slmeval_cli-0.1.0/src/slmeval/doctor/checks.py +380 -0
  91. slmeval_cli-0.1.0/src/slmeval/errors.py +76 -0
  92. slmeval_cli-0.1.0/src/slmeval/eval/__init__.py +64 -0
  93. slmeval_cli-0.1.0/src/slmeval/eval/assertions.py +356 -0
  94. slmeval_cli-0.1.0/src/slmeval/eval/calibration.py +364 -0
  95. slmeval_cli-0.1.0/src/slmeval/eval/cost.py +291 -0
  96. slmeval_cli-0.1.0/src/slmeval/eval/pipeline.py +273 -0
  97. slmeval_cli-0.1.0/src/slmeval/eval/stats.py +376 -0
  98. slmeval_cli-0.1.0/src/slmeval/eval/tier1.py +510 -0
  99. slmeval_cli-0.1.0/src/slmeval/eval/tier2.py +582 -0
  100. slmeval_cli-0.1.0/src/slmeval/eval/tier3.py +415 -0
  101. slmeval_cli-0.1.0/src/slmeval/exit_codes.py +28 -0
  102. slmeval_cli-0.1.0/src/slmeval/gate.py +722 -0
  103. slmeval_cli-0.1.0/src/slmeval/providers/__init__.py +69 -0
  104. slmeval_cli-0.1.0/src/slmeval/providers/base.py +426 -0
  105. slmeval_cli-0.1.0/src/slmeval/providers/fake.py +760 -0
  106. slmeval_cli-0.1.0/src/slmeval/providers/jsonx.py +239 -0
  107. slmeval_cli-0.1.0/src/slmeval/providers/ladder.py +339 -0
  108. slmeval_cli-0.1.0/src/slmeval/providers/openai_compat.py +392 -0
  109. slmeval_cli-0.1.0/src/slmeval/providers/registry.py +185 -0
  110. slmeval_cli-0.1.0/src/slmeval/providers/retry.py +81 -0
  111. slmeval_cli-0.1.0/src/slmeval/providers/schema.py +136 -0
  112. slmeval_cli-0.1.0/src/slmeval/providers/tokens.py +150 -0
  113. slmeval_cli-0.1.0/src/slmeval/py.typed +0 -0
  114. slmeval_cli-0.1.0/src/slmeval/report/__init__.py +115 -0
  115. slmeval_cli-0.1.0/src/slmeval/report/attribution.py +197 -0
  116. slmeval_cli-0.1.0/src/slmeval/report/baseline.py +353 -0
  117. slmeval_cli-0.1.0/src/slmeval/report/drilldown.py +1395 -0
  118. slmeval_cli-0.1.0/src/slmeval/report/export.py +450 -0
  119. slmeval_cli-0.1.0/src/slmeval/report/model.py +562 -0
  120. slmeval_cli-0.1.0/src/slmeval/report/progress.py +257 -0
  121. slmeval_cli-0.1.0/src/slmeval/report/scorecard.py +367 -0
  122. slmeval_cli-0.1.0/src/slmeval/sandbox/__init__.py +145 -0
  123. slmeval_cli-0.1.0/src/slmeval/sandbox/diagnosis.py +690 -0
  124. slmeval_cli-0.1.0/src/slmeval/sandbox/envelope.py +486 -0
  125. slmeval_cli-0.1.0/src/slmeval/sandbox/faults.py +794 -0
  126. slmeval_cli-0.1.0/src/slmeval/sandbox/keys.py +458 -0
  127. slmeval_cli-0.1.0/src/slmeval/sandbox/normalizers.py +333 -0
  128. slmeval_cli-0.1.0/src/slmeval/sandbox/store.py +525 -0
  129. slmeval_cli-0.1.0/src/slmeval/sandbox/tool.py +685 -0
  130. slmeval_cli-0.1.0/src/slmeval/storage/__init__.py +73 -0
  131. slmeval_cli-0.1.0/src/slmeval/storage/blobs.py +308 -0
  132. slmeval_cli-0.1.0/src/slmeval/storage/codec.py +93 -0
  133. slmeval_cli-0.1.0/src/slmeval/storage/db.py +1334 -0
  134. slmeval_cli-0.1.0/src/slmeval/storage/ingest.py +346 -0
  135. slmeval_cli-0.1.0/src/slmeval/storage/migrations.py +358 -0
  136. slmeval_cli-0.1.0/src/slmeval/storage/models.py +554 -0
  137. slmeval_cli-0.1.0/src/slmeval/storage/provenance.py +201 -0
  138. slmeval_cli-0.1.0/teardown/README.md +231 -0
  139. slmeval_cli-0.1.0/teardown/artifacts/diagnosis-cassette-miss.txt +42 -0
  140. slmeval_cli-0.1.0/teardown/artifacts/failures-faults.txt +42 -0
  141. slmeval_cli-0.1.0/teardown/artifacts/failures.txt +27 -0
  142. slmeval_cli-0.1.0/teardown/artifacts/gate.txt +22 -0
  143. slmeval_cli-0.1.0/teardown/artifacts/report-faults.json +519 -0
  144. slmeval_cli-0.1.0/teardown/artifacts/report.json +534 -0
  145. slmeval_cli-0.1.0/teardown/artifacts/scorecard-faults.md +24 -0
  146. slmeval_cli-0.1.0/teardown/artifacts/scorecard.md +24 -0
  147. slmeval_cli-0.1.0/teardown/artifacts/scorecard.txt +39 -0
  148. slmeval_cli-0.1.0/teardown/artifacts/trace-forbidden-refund.txt +41 -0
  149. slmeval_cli-0.1.0/teardown/artifacts/trace-repaired-json.txt +61 -0
  150. slmeval_cli-0.1.0/teardown/artifacts/uncovered.txt +19 -0
  151. slmeval_cli-0.1.0/teardown/eval.toml +228 -0
  152. slmeval_cli-0.1.0/tests/__init__.py +1 -0
  153. slmeval_cli-0.1.0/tests/conftest.py +144 -0
  154. slmeval_cli-0.1.0/tests/offline.py +355 -0
  155. slmeval_cli-0.1.0/tests/snapshots/gate_failed.txt +31 -0
  156. slmeval_cli-0.1.0/tests/snapshots/gate_markdown.txt +23 -0
  157. slmeval_cli-0.1.0/tests/snapshots/miss_diagnosis.txt +8 -0
  158. slmeval_cli-0.1.0/tests/snapshots/miss_unknown_tool.txt +7 -0
  159. slmeval_cli-0.1.0/tests/snapshots/nodes.txt +7 -0
  160. slmeval_cli-0.1.0/tests/snapshots/per_case.txt +3 -0
  161. slmeval_cli-0.1.0/tests/snapshots/scorecard_full.txt +28 -0
  162. slmeval_cli-0.1.0/tests/snapshots/scorecard_table.txt +5 -0
  163. slmeval_cli-0.1.0/tests/snapshots/show_diff.txt +29 -0
  164. slmeval_cli-0.1.0/tests/snapshots/show_exec.txt +42 -0
  165. slmeval_cli-0.1.0/tests/snapshots/show_run.txt +17 -0
  166. slmeval_cli-0.1.0/tests/snapshots/show_uncovered.txt +7 -0
  167. slmeval_cli-0.1.0/tests/snapshots/summary_line.txt +1 -0
  168. slmeval_cli-0.1.0/tests/snapshots/warnings.txt +5 -0
  169. slmeval_cli-0.1.0/tests/test_adapter.py +219 -0
  170. slmeval_cli-0.1.0/tests/test_assertions.py +242 -0
  171. slmeval_cli-0.1.0/tests/test_blobs.py +208 -0
  172. slmeval_cli-0.1.0/tests/test_cache_cmd.py +229 -0
  173. slmeval_cli-0.1.0/tests/test_calibration.py +423 -0
  174. slmeval_cli-0.1.0/tests/test_cli.py +325 -0
  175. slmeval_cli-0.1.0/tests/test_config.py +247 -0
  176. slmeval_cli-0.1.0/tests/test_context.py +241 -0
  177. slmeval_cli-0.1.0/tests/test_cost.py +240 -0
  178. slmeval_cli-0.1.0/tests/test_dataset.py +125 -0
  179. slmeval_cli-0.1.0/tests/test_determinism.py +248 -0
  180. slmeval_cli-0.1.0/tests/test_diagnosis.py +420 -0
  181. slmeval_cli-0.1.0/tests/test_doctor.py +174 -0
  182. slmeval_cli-0.1.0/tests/test_drilldown.py +360 -0
  183. slmeval_cli-0.1.0/tests/test_envelope.py +349 -0
  184. slmeval_cli-0.1.0/tests/test_example_agent.py +278 -0
  185. slmeval_cli-0.1.0/tests/test_example_benchmark.py +294 -0
  186. slmeval_cli-0.1.0/tests/test_failure_matrix.py +214 -0
  187. slmeval_cli-0.1.0/tests/test_fake.py +646 -0
  188. slmeval_cli-0.1.0/tests/test_faults.py +434 -0
  189. slmeval_cli-0.1.0/tests/test_gate.py +562 -0
  190. slmeval_cli-0.1.0/tests/test_gate_cmd.py +398 -0
  191. slmeval_cli-0.1.0/tests/test_github_action.py +358 -0
  192. slmeval_cli-0.1.0/tests/test_jsonx.py +103 -0
  193. slmeval_cli-0.1.0/tests/test_keys.py +326 -0
  194. slmeval_cli-0.1.0/tests/test_ladder.py +215 -0
  195. slmeval_cli-0.1.0/tests/test_langgraph_adapter.py +480 -0
  196. slmeval_cli-0.1.0/tests/test_live_providers.py +148 -0
  197. slmeval_cli-0.1.0/tests/test_locate.py +72 -0
  198. slmeval_cli-0.1.0/tests/test_normalizers.py +197 -0
  199. slmeval_cli-0.1.0/tests/test_openai_compat.py +466 -0
  200. slmeval_cli-0.1.0/tests/test_outcome.py +286 -0
  201. slmeval_cli-0.1.0/tests/test_pipeline.py +322 -0
  202. slmeval_cli-0.1.0/tests/test_render.py +159 -0
  203. slmeval_cli-0.1.0/tests/test_report.py +480 -0
  204. slmeval_cli-0.1.0/tests/test_router.py +283 -0
  205. slmeval_cli-0.1.0/tests/test_run_cmd.py +348 -0
  206. slmeval_cli-0.1.0/tests/test_runner.py +840 -0
  207. slmeval_cli-0.1.0/tests/test_sandbox_tool.py +979 -0
  208. slmeval_cli-0.1.0/tests/test_show_cmd.py +288 -0
  209. slmeval_cli-0.1.0/tests/test_snapshots.py +340 -0
  210. slmeval_cli-0.1.0/tests/test_stats.py +275 -0
  211. slmeval_cli-0.1.0/tests/test_storage.py +941 -0
  212. slmeval_cli-0.1.0/tests/test_store.py +311 -0
  213. slmeval_cli-0.1.0/tests/test_teardown.py +305 -0
  214. slmeval_cli-0.1.0/tests/test_tier1.py +307 -0
  215. slmeval_cli-0.1.0/tests/test_tier2.py +310 -0
  216. slmeval_cli-0.1.0/tests/test_tier3.py +389 -0
  217. slmeval_cli-0.1.0/tests/test_tokens.py +141 -0
  218. slmeval_cli-0.1.0/tests/test_visuals.py +101 -0
@@ -0,0 +1,15 @@
1
+ # Line endings are a property of this repository, not of each contributor's
2
+ # git config. The harness hashes generated files to decide whether two runs are
3
+ # comparable, so a CRLF committed from Windows must not read as a different
4
+ # dataset in CI on Linux.
5
+ * text=auto eol=lf
6
+
7
+ # Golden datasets are compared byte-for-byte; never let git touch them.
8
+ *.jsonl text eol=lf
9
+
10
+ # Rendered goldens, compared byte-for-byte against what the tool prints.
11
+ tests/snapshots/*.txt text eol=lf
12
+
13
+ # Published teardown artifacts, diffed against a fresh run by
14
+ # `scripts/teardown.py --check`, which CI runs on Windows and Linux.
15
+ teardown/artifacts/* text eol=lf
@@ -0,0 +1,38 @@
1
+ name: Benchmark result
2
+ description: Share results from running slmeval on real models.
3
+ labels: [benchmark-result]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: Please remove anything private first. Cassettes and traces can contain real data.
8
+ - type: input
9
+ id: models
10
+ attributes:
11
+ label: Models and how they were served
12
+ placeholder: gemma3:4b on Ollama, baseline gpt-4o-mini
13
+ validations:
14
+ required: true
15
+ - type: textarea
16
+ id: report
17
+ attributes:
18
+ label: Output of `slmeval report --format md`
19
+ validations:
20
+ required: true
21
+ - type: textarea
22
+ id: config
23
+ attributes:
24
+ label: Your eval.toml
25
+ render: toml
26
+ validations:
27
+ required: true
28
+ - type: input
29
+ id: dataset
30
+ attributes:
31
+ label: Dataset
32
+ description: A link, or how many cases and what kind if it can't be shared.
33
+ validations:
34
+ required: true
35
+ - type: textarea
36
+ id: hardware
37
+ attributes:
38
+ label: Hardware, if you measured latency
@@ -0,0 +1,37 @@
1
+ name: Bug report
2
+ description: Something doesn't work as documented.
3
+ labels: [bug]
4
+ body:
5
+ - type: textarea
6
+ id: what
7
+ attributes:
8
+ label: What happened?
9
+ description: Include what you expected and the full output, with any private data removed.
10
+ validations:
11
+ required: true
12
+ - type: textarea
13
+ id: reproduce
14
+ attributes:
15
+ label: How can we reproduce it?
16
+ description: The commands you ran and the relevant parts of eval.toml.
17
+ validations:
18
+ required: true
19
+ - type: textarea
20
+ id: doctor
21
+ attributes:
22
+ label: Output of `slmeval doctor`
23
+ render: text
24
+ validations:
25
+ required: true
26
+ - type: dropdown
27
+ id: exit-code
28
+ attributes:
29
+ label: Exit code
30
+ options:
31
+ - "0"
32
+ - "1"
33
+ - "2"
34
+ - "3"
35
+ - "4"
36
+ - Other, or a traceback
37
+ - Not applicable
@@ -0,0 +1,5 @@
1
+ blank_issues_enabled: false
2
+ contact_links:
3
+ - name: Security vulnerability
4
+ url: https://github.com/mohitraj3697/slmeval/security/advisories/new
5
+ about: Please report these privately.
@@ -0,0 +1,19 @@
1
+ name: Feature request
2
+ description: Suggest a change or a new capability.
3
+ labels: [enhancement]
4
+ body:
5
+ - type: textarea
6
+ id: problem
7
+ attributes:
8
+ label: What are you trying to do?
9
+ description: Describe the problem before the solution.
10
+ validations:
11
+ required: true
12
+ - type: textarea
13
+ id: proposal
14
+ attributes:
15
+ label: What would you like to see?
16
+ - type: textarea
17
+ id: alternatives
18
+ attributes:
19
+ label: What have you tried instead?
@@ -0,0 +1,14 @@
1
+ ## What and why
2
+
3
+ <!-- What changes, and what problem it solves. Link any related issue. -->
4
+
5
+ ## Testing
6
+
7
+ <!-- How you checked it. Put new failure modes under the `fake` provider. -->
8
+
9
+ ## Checklist
10
+
11
+ - [ ] `ruff`, `mypy` and `pytest` pass
12
+ - [ ] Docs are updated if a command or flag changed
13
+ - [ ] Snapshots, teardown artifacts and README images are regenerated if the output changed
14
+ - [ ] The changelog mentions any change to exit codes or the report `schema`
@@ -0,0 +1,163 @@
1
+ # The offline suite, on both platforms it has to be identical on.
2
+ #
3
+ # Determinism bugs in a harness like this are nearly all platform bugs: a hash
4
+ # that iterates a dict, a path separator inside a cassette key, a CRLF
5
+ # checkout, a console that cannot encode a box-drawing character. None of them
6
+ # reproduce on the machine they were written on, which is why the matrix is
7
+ # Windows *and* Linux rather than one of them.
8
+ #
9
+ # Nothing here needs a GPU, a model server or an API key. If a job in this
10
+ # file ever needs one, the test that made it necessary belongs behind
11
+ # `-m live` instead.
12
+
13
+ name: ci
14
+
15
+ on:
16
+ push:
17
+ branches: [main]
18
+ pull_request:
19
+ workflow_dispatch:
20
+
21
+ concurrency:
22
+ group: ci-${{ github.ref }}
23
+ cancel-in-progress: true
24
+
25
+ env:
26
+ PYTHONUTF8: "1"
27
+ # The developer-loop target is under 30 seconds; a cold CI runner is slower
28
+ # than a laptop, so the gate here only has to catch a regression in kind -
29
+ # someone reintroducing a real sleep, a retry with a real backoff, or a test
30
+ # that opens a socket.
31
+ SUITE_BUDGET_S: "120"
32
+
33
+ jobs:
34
+ offline:
35
+ name: offline suite (${{ matrix.os }}, py${{ matrix.python }})
36
+ runs-on: ${{ matrix.os }}
37
+ strategy:
38
+ fail-fast: false
39
+ matrix:
40
+ os: [ubuntu-latest, windows-latest]
41
+ python: ["3.11", "3.13"]
42
+ steps:
43
+ - uses: actions/checkout@v4
44
+
45
+ - uses: actions/setup-python@v5
46
+ with:
47
+ python-version: ${{ matrix.python }}
48
+ cache: pip
49
+ cache-dependency-path: pyproject.toml
50
+
51
+ - name: Install
52
+ run: python -m pip install --upgrade pip && python -m pip install -e ".[dev]"
53
+
54
+ - name: Lint
55
+ run: python -m ruff check src tests scripts
56
+
57
+ - name: Type-check
58
+ run: python -m mypy src
59
+
60
+ - name: Offline suite, within its time budget
61
+ shell: bash
62
+ run: |
63
+ start=$(date +%s)
64
+ python -m pytest --durations=10
65
+ elapsed=$(( $(date +%s) - start ))
66
+ echo "offline suite: ${elapsed}s (budget ${SUITE_BUDGET_S}s)"
67
+ if [ "$elapsed" -gt "$SUITE_BUDGET_S" ]; then
68
+ echo "::error::the offline suite took ${elapsed}s, over the ${SUITE_BUDGET_S}s budget."
69
+ echo "A suite people stop running protects nothing - see the slowest tests above."
70
+ exit 1
71
+ fi
72
+
73
+ # The one job that installs a model framework. The core must stay installable
74
+ # in seconds without one, so `langgraph` is an extra and the adapter's tests
75
+ # skip in every other job here - which means that without this job they would
76
+ # never run anywhere, and a skipped test reads as a passing one.
77
+ langgraph:
78
+ name: langgraph adapter
79
+ runs-on: ubuntu-latest
80
+ steps:
81
+ - uses: actions/checkout@v4
82
+
83
+ - uses: actions/setup-python@v5
84
+ with:
85
+ python-version: "3.11"
86
+ cache: pip
87
+ cache-dependency-path: pyproject.toml
88
+
89
+ - name: Install, with the extra
90
+ run: python -m pip install --upgrade pip && python -m pip install -e ".[dev,langgraph]"
91
+
92
+ - name: The adapter, and the example under both adapters
93
+ run: python -m pytest tests/test_langgraph_adapter.py -v
94
+
95
+ - name: Nothing was skipped
96
+ shell: bash
97
+ run: |
98
+ # A green job that skipped the only two tests it exists to run would
99
+ # be worse than no job: it would read as coverage we do not have.
100
+ python -m pytest tests/test_langgraph_adapter.py -q -rs 2>&1 | tee out.txt
101
+ if grep -q "skipped" out.txt; then
102
+ echo "::error::a langgraph test skipped in the job that installs langgraph."
103
+ exit 1
104
+ fi
105
+
106
+ # The published teardown, re-run. `tests/test_teardown.py` checks that the
107
+ # write-up still agrees with the artifacts next to it; only re-running the
108
+ # benchmark can check that the artifacts still agree with the tool. Without
109
+ # this, the first change that moved a number would leave a document on the
110
+ # front page of the repository quoting figures nothing produces any more.
111
+ #
112
+ # Both platforms, because the artifacts are committed from one of them and a
113
+ # path separator or a console codepage leaking into a published file is
114
+ # exactly the class of bug this repository's CI matrix exists to catch.
115
+ teardown:
116
+ name: the published teardown still reproduces (${{ matrix.os }})
117
+ runs-on: ${{ matrix.os }}
118
+ strategy:
119
+ fail-fast: false
120
+ matrix:
121
+ os: [ubuntu-latest, windows-latest]
122
+ steps:
123
+ - uses: actions/checkout@v4
124
+
125
+ - uses: actions/setup-python@v5
126
+ with:
127
+ python-version: "3.11"
128
+ cache: pip
129
+ cache-dependency-path: pyproject.toml
130
+
131
+ - name: Install
132
+ run: python -m pip install --upgrade pip && python -m pip install -e .
133
+
134
+ - name: Record, run, and diff against what is published
135
+ run: python scripts/teardown.py --check
136
+
137
+ install-smoke:
138
+ name: install smoke (${{ matrix.os }})
139
+ runs-on: ${{ matrix.os }}
140
+ strategy:
141
+ fail-fast: false
142
+ matrix:
143
+ os: [ubuntu-latest, windows-latest]
144
+ steps:
145
+ - uses: actions/checkout@v4
146
+
147
+ - uses: actions/setup-python@v5
148
+ with:
149
+ python-version: "3.11"
150
+
151
+ # `pytest` runs against the working tree and so proves nothing about what
152
+ # `pip install slmeval-cli` ships. This builds the wheel, installs it into a
153
+ # throwaway venv with nothing else in it, and runs init / validate /
154
+ # doctor / record / run / report as a new user would.
155
+ - name: Build and use the wheel
156
+ run: python scripts/install_smoke.py
157
+
158
+ - uses: actions/upload-artifact@v4
159
+ if: always()
160
+ with:
161
+ name: wheel-${{ matrix.os }}
162
+ path: dist/*.whl
163
+ if-no-files-found: error
@@ -0,0 +1,107 @@
1
+ # The GitHub Action, proved on the bundled example.
2
+ #
3
+ # This is the "demo repo" check: it exercises `examples/github-action` end to
4
+ # end against `examples/support_agent`, on a stock runner, with **no GPU, no
5
+ # API key and no secret of any kind**. The baseline and both candidates are the
6
+ # scripted `fake` provider and every tool is a local Python function, so the
7
+ # whole thing - record, replay, gate - runs offline.
8
+ #
9
+ # Two jobs, because an action that can only go green is not a gate:
10
+ #
11
+ # `green` gates on a floor the example clears and expects exit 0.
12
+ # `red` gates on a floor nothing can clear and expects exit 1 - a regression,
13
+ # not a crash, not a usage error, and not exit 3. That distinction is the whole
14
+ # contract CI branches on, and it is the one thing worth asserting on every
15
+ # push.
16
+ #
17
+ # The job summary and the pull-request comment this produces are also the
18
+ # artefacts to screenshot for the README: this workflow is where the output in
19
+ # the docs comes from.
20
+
21
+ name: gate-demo
22
+
23
+ on:
24
+ pull_request:
25
+ push:
26
+ branches: [main]
27
+ workflow_dispatch:
28
+
29
+ permissions:
30
+ contents: read
31
+ pull-requests: write
32
+
33
+ concurrency:
34
+ group: gate-demo-${{ github.ref }}
35
+ cancel-in-progress: true
36
+
37
+ jobs:
38
+ green:
39
+ name: the action passes a healthy run
40
+ runs-on: ubuntu-latest
41
+ steps:
42
+ - uses: actions/checkout@v4
43
+
44
+ - id: slmeval
45
+ uses: ./examples/github-action
46
+ with:
47
+ working-directory: examples/support_agent
48
+ # The checkout itself, not a release: this workflow is what proves
49
+ # the action works against the code in the pull request.
50
+ version: .
51
+ # Safe here, and only here: every tool in the example is a local
52
+ # function and the provider is `fake`, so recording touches nothing
53
+ # outside the runner.
54
+ record: missing
55
+ # The candidate whose story is "passes, but only with help": it
56
+ # answers every case correctly and needs a lower rung on every
57
+ # structured call to do it.
58
+ run-args: --candidate fenced-json
59
+ # A floor the example clears, on the estimator that matters, and not
60
+ # a number tuned to pass: 4/4 cases over 3 runs each has a Wilson
61
+ # lower bound of 51%, so this is as high as a floor can honestly go
62
+ # at n=4. Asking for 0.9 here would be asking for a guarantee the
63
+ # sample size cannot give - which is the whole lesson of the tool.
64
+ gate-args: --min-pass-k 0.5 --use-wilson-lower
65
+ artifact-name: slmeval-demo-green
66
+
67
+ - name: The outputs say what happened
68
+ run: |
69
+ test '${{ steps.slmeval.outputs.verdict }}' = passed
70
+ test '${{ steps.slmeval.outputs.exit-code }}' = 0
71
+ test -n '${{ steps.slmeval.outputs.run-id }}'
72
+ test -n '${{ steps.slmeval.outputs.dataset-hash }}'
73
+ echo "run ${{ steps.slmeval.outputs.run-id }} passed"
74
+
75
+ red:
76
+ name: the action fails a regression with exit 1
77
+ runs-on: ubuntu-latest
78
+ steps:
79
+ - uses: actions/checkout@v4
80
+
81
+ - id: slmeval
82
+ # continue-on-error, not a removed threshold: the action *must* fail
83
+ # the step here. A demo that only ever went green would prove the
84
+ # pretty half of a gate and none of the useful half.
85
+ continue-on-error: true
86
+ uses: ./examples/github-action
87
+ with:
88
+ working-directory: examples/support_agent
89
+ version: .
90
+ record: missing
91
+ # Nothing can clear a floor of 100% on a Wilson lower bound, so this
92
+ # is the regression path.
93
+ gate-args: --min-pass-k 1.0 --use-wilson-lower
94
+ comment: "false"
95
+ artifact-name: slmeval-demo-red
96
+
97
+ - name: Exit 1, and labelled as a regression
98
+ run: |
99
+ if [ '${{ steps.slmeval.outputs.exit-code }}' != '1' ]; then
100
+ echo "::error::the gate exited '${{ steps.slmeval.outputs.exit-code }}', not 1."
101
+ echo "1 means a threshold was missed. 2 is a usage error, 3 is an unreachable"
102
+ echo "provider and 4 is thin coverage - if an unclearable floor produces any of"
103
+ echo "those, CI cannot tell a regression from a broken runner."
104
+ exit 1
105
+ fi
106
+ test '${{ steps.slmeval.outputs.verdict }}' = failed
107
+ echo "a missed threshold exits 1, as the contract says"
@@ -0,0 +1,114 @@
1
+ # Publish to PyPI on a tag, with Trusted Publishing.
2
+ #
3
+ # No long-lived token in this repository's secrets. PyPI mints a short-lived
4
+ # credential for this exact workflow in this exact repository, via OIDC, which
5
+ # is why the publish job asks for `id-token: write` and has no `password:`. A
6
+ # token in secrets is a credential that can be exfiltrated by any workflow that
7
+ # can read secrets, and for a measurement tool people are asked to install into
8
+ # their CI, that is the wrong trade.
9
+ #
10
+ # It also refuses to publish a tag that disagrees with the package: a `v0.1.1`
11
+ # tag on a tree whose `__version__` still says `0.1.0` would put a wheel on
12
+ # PyPI that no git tag describes.
13
+ #
14
+ # Setup, once, before the first tag: on PyPI, add a trusted publisher for this
15
+ # repository with workflow `release.yml` and environment `pypi`.
16
+ #
17
+ # git tag v0.1.0 && git push origin v0.1.0
18
+
19
+ name: release
20
+
21
+ on:
22
+ push:
23
+ tags: ["v*"]
24
+ workflow_dispatch:
25
+ inputs:
26
+ dry-run:
27
+ description: Build and check, but do not publish.
28
+ type: boolean
29
+ default: true
30
+
31
+ permissions:
32
+ contents: read
33
+
34
+ jobs:
35
+ # A tag is not a release until the thing it points at actually works. The
36
+ # offline suite and the install smoke both run again here rather than
37
+ # trusting that they ran on the commit: a tag can point anywhere.
38
+ verify:
39
+ name: verify the tagged tree (${{ matrix.os }})
40
+ runs-on: ${{ matrix.os }}
41
+ strategy:
42
+ fail-fast: true
43
+ matrix:
44
+ os: [ubuntu-latest, windows-latest]
45
+ steps:
46
+ - uses: actions/checkout@v4
47
+
48
+ - uses: actions/setup-python@v5
49
+ with:
50
+ python-version: "3.11"
51
+
52
+ - name: Install
53
+ run: python -m pip install --upgrade pip && python -m pip install -e ".[dev]"
54
+
55
+ - name: The tag and the package agree
56
+ if: startsWith(github.ref, 'refs/tags/v')
57
+ shell: bash
58
+ run: |
59
+ set -euo pipefail
60
+ tagged="${GITHUB_REF_NAME#v}"
61
+ packaged=$(python -c "import slmeval; print(slmeval.__version__)")
62
+ echo "tag $tagged, package $packaged"
63
+ if [ "$tagged" != "$packaged" ]; then
64
+ echo "::error::tag $GITHUB_REF_NAME does not match __version__ $packaged."
65
+ echo "Publishing would put a wheel on PyPI that no git tag describes."
66
+ exit 1
67
+ fi
68
+
69
+ - name: Offline suite
70
+ run: python -m pytest
71
+
72
+ - name: The wheel installs and produces a scorecard
73
+ run: python scripts/install_smoke.py
74
+
75
+ build:
76
+ name: build the distributions
77
+ needs: verify
78
+ runs-on: ubuntu-latest
79
+ steps:
80
+ - uses: actions/checkout@v4
81
+
82
+ - uses: actions/setup-python@v5
83
+ with:
84
+ python-version: "3.11"
85
+
86
+ - name: Build
87
+ run: |
88
+ python -m pip install --upgrade pip build twine
89
+ python -m build
90
+ python -m twine check --strict dist/*
91
+
92
+ - uses: actions/upload-artifact@v4
93
+ with:
94
+ name: distributions
95
+ path: dist/*
96
+ if-no-files-found: error
97
+
98
+ publish:
99
+ name: publish to PyPI
100
+ needs: build
101
+ if: startsWith(github.ref, 'refs/tags/v') && !inputs.dry-run
102
+ runs-on: ubuntu-latest
103
+ environment: pypi
104
+ permissions:
105
+ # The whole point: PyPI mints a short-lived credential for this workflow
106
+ # over OIDC, so there is no token in this repository to leak.
107
+ id-token: write
108
+ steps:
109
+ - uses: actions/download-artifact@v4
110
+ with:
111
+ name: distributions
112
+ path: dist
113
+
114
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,12 @@
1
+ .slmeval/
2
+ .venv/
3
+ venv/
4
+ __pycache__/
5
+ *.py[cod]
6
+ *.egg-info/
7
+ dist/
8
+ build/
9
+ .pytest_cache/
10
+ .ruff_cache/
11
+ .mypy_cache/
12
+ .env
@@ -0,0 +1,59 @@
1
+ # Changelog
2
+
3
+ Versions follow [semantic versioning](https://semver.org). While the major version is 0, a
4
+ minor release can change the CLI and the stored schema.
5
+
6
+ The exit codes of `slmeval gate` and the `schema` field of the JSON report are public
7
+ contracts. Any change to them is listed here as breaking.
8
+
9
+ ## [0.1.0] - 2026-11-04
10
+
11
+ First release, published on PyPI as `slmeval-cli`. The command and the Python import are
12
+ both `slmeval`.
13
+
14
+ ### Harness
15
+
16
+ - `slmeval.node()` and `slmeval.chat_model()` mark steps and route model calls per candidate.
17
+ Both do nothing outside a benchmark.
18
+ - `slmeval.instrument_langgraph()` marks every node of a LangGraph builder. Requires the
19
+ `langgraph` extra.
20
+ - `@sandbox_tool` records a tool call once and replays it for every candidate. A miss gets a
21
+ verdict and a diff of the arguments against the nearest recording.
22
+ - Recorded responses keep their types, including `Decimal`, `datetime` and enums. A value that
23
+ can't be stored faithfully raises an error at record time.
24
+ - Seven kinds of fault injection, scheduled per case and seed. Each run records the faults it met.
25
+ - Providers for OpenAI, Ollama, anything serving `/v1/chat/completions`, and a scripted `fake`.
26
+ Reports show which structured-output mechanism each call needed.
27
+
28
+ ### Measurement
29
+
30
+ - Dataset assertions, including forbidden tools as a hard fail.
31
+ - Path distance, tool overlap with the baseline, loop detection and recovery from faults.
32
+ - `pass@k`, `pass^k` and Wilson intervals. The gate reads the lower bound by default.
33
+ - Cost per case. Local inference is reported as modelled from token counts and `--gpu-hourly`.
34
+ - Latency only in `--serial-latency` mode.
35
+ - Bugs in slmeval, unrecorded paths and unreachable providers stay out of pass rates and are
36
+ reported separately.
37
+ - An LLM judge with a calibration harness, off by default. It isn't connected to `slmeval run` yet.
38
+
39
+ ### CLI
40
+
41
+ `init`, `doctor`, `validate`, `record`, `run`, `report`, `show run|exec|case|uncovered`, `gate`,
42
+ `baseline save|show`, `judge calibrate`, `dataset validate` and `cache stats|prune`.
43
+
44
+ Reports render as a table, JSON, Markdown, CSV or JUnit XML.
45
+
46
+ Exit codes: 0 pass, 1 gate failed, 2 usage, 3 environment, 4 coverage.
47
+
48
+ ### Also included
49
+
50
+ - A [GitHub Action](examples/github-action) that runs the benchmark, writes the job summary and
51
+ keeps one pull request comment updated.
52
+ - A [runnable example](examples/support_agent) and a [judge calibration example](examples/judge).
53
+ - [Teardown #1](teardown), a published run with scripted models. CI re-runs it on Linux and Windows.
54
+
55
+ ### Not in this release
56
+
57
+ - `slmeval compare` and `slmeval cassette`. They are hidden from `--help` and exit 2 if called.
58
+ - A native Anthropic provider. Reach Claude through an OpenAI-compatible gateway.
59
+ - Time-to-first-token measurement.
@@ -0,0 +1,7 @@
1
+ # Code of Conduct
2
+
3
+ This project follows the
4
+ [Contributor Covenant, version 2.1](https://www.contributor-covenant.org/version/2/1/code_of_conduct/).
5
+
6
+ To report unacceptable behaviour, contact the maintainer privately through
7
+ [GitHub](https://github.com/mohitraj3697). Reports are kept confidential.