simpleaudit 0.4.0__tar.gz → 0.4.2__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 (152) hide show
  1. {simpleaudit-0.4.0/simpleaudit.egg-info → simpleaudit-0.4.2}/PKG-INFO +112 -3
  2. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/README.md +111 -2
  3. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/pyproject.toml +1 -1
  4. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/__init__.py +2 -1
  5. simpleaudit-0.4.2/simpleaudit/_event_loop.py +40 -0
  6. simpleaudit-0.4.2/simpleaudit/context_attribution.py +384 -0
  7. simpleaudit-0.4.2/simpleaudit/context_derivations.py +220 -0
  8. simpleaudit-0.4.2/simpleaudit/context_findings.py +370 -0
  9. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/context_marks.py +88 -0
  10. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/cross_judge.py +2 -1
  11. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/experiment.py +16 -5
  12. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/judges/__init__.py +10 -2
  13. simpleaudit-0.4.2/simpleaudit/judges/fact_check.py +923 -0
  14. simpleaudit-0.4.2/simpleaudit/judges/groundedness.py +403 -0
  15. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/model_auditor.py +86 -9
  16. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/reframing.py +2 -1
  17. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/__init__.py +90 -2
  18. simpleaudit-0.4.2/simpleaudit/scenarios/context_grounding.py +365 -0
  19. simpleaudit-0.4.2/simpleaudit/scenarios/healthbench_behaviours.py +630 -0
  20. simpleaudit-0.4.2/simpleaudit/scenarios/healthbench_loader.py +394 -0
  21. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/helfo.py +34 -0
  22. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/lanekassen.py +12 -0
  23. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/nav_aap.py +61 -3
  24. simpleaudit-0.4.2/simpleaudit/scenarios/nb_rag_kontekst.py +163 -0
  25. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/skatteetaten.py +32 -0
  26. simpleaudit-0.4.2/simpleaudit/single_turn.py +664 -0
  27. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/targets/decision.py +3 -3
  28. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/targets/http.py +30 -2
  29. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/tracing/context.py +1 -1
  30. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/tracing/selection.py +61 -0
  31. {simpleaudit-0.4.0 → simpleaudit-0.4.2/simpleaudit.egg-info}/PKG-INFO +112 -3
  32. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit.egg-info/SOURCES.txt +24 -0
  33. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_basic.py +3 -1
  34. simpleaudit-0.4.2/tests/test_context_attribution.py +532 -0
  35. simpleaudit-0.4.2/tests/test_context_derivations.py +336 -0
  36. simpleaudit-0.4.2/tests/test_context_findings.py +520 -0
  37. simpleaudit-0.4.2/tests/test_context_grounding_pack.py +263 -0
  38. simpleaudit-0.4.2/tests/test_context_marks.py +269 -0
  39. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_decision_target.py +1 -1
  40. simpleaudit-0.4.2/tests/test_event_loop.py +136 -0
  41. simpleaudit-0.4.2/tests/test_fact_check_judge.py +820 -0
  42. simpleaudit-0.4.2/tests/test_fact_freshness.py +97 -0
  43. simpleaudit-0.4.2/tests/test_groundedness_judge.py +809 -0
  44. simpleaudit-0.4.2/tests/test_healthbench_loader.py +190 -0
  45. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_judge_registry.py +2 -0
  46. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_model_auditor.py +27 -1
  47. simpleaudit-0.4.2/tests/test_nb_rag_kontekst.py +130 -0
  48. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_scenario_data.py +3 -3
  49. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_scenario_pack_conventions.py +1 -0
  50. simpleaudit-0.4.2/tests/test_single_turn.py +881 -0
  51. simpleaudit-0.4.2/tests/test_single_turn_correctness.py +352 -0
  52. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/LICENSE +0 -0
  53. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/setup.cfg +0 -0
  54. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/auditor.py +0 -0
  55. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/checklist.py +0 -0
  56. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/cli.py +0 -0
  57. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/decision.py +0 -0
  58. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/judges/abstention.py +0 -0
  59. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/judges/binary_abstention.py +0 -0
  60. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/judges/checklist.py +0 -0
  61. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/judges/choice_match.py +0 -0
  62. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/judges/compose.py +0 -0
  63. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/judges/default.py +0 -0
  64. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/judges/factuality.py +0 -0
  65. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/judges/harm.py +0 -0
  66. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/judges/helpfulness.py +0 -0
  67. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/judges/helsedir_sexhealth_no.py +0 -0
  68. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/judges/helsedir_sexhealth_no_rag.py +0 -0
  69. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/judges/safety.py +0 -0
  70. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/perturbations.py +0 -0
  71. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/repeated_results.py +0 -0
  72. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/results.py +0 -0
  73. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/arbeidstilsynet_arbeidstid.py +0 -0
  74. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/bullshitbench_health.py +0 -0
  75. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/bullshitbench_v1_v2.py +0 -0
  76. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/health.py +0 -0
  77. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/hei_refusal.py +0 -0
  78. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/helpmed.py +0 -0
  79. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/human_rights_education.py +0 -0
  80. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/human_rights_fair_trial.py +0 -0
  81. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/human_rights_water.py +0 -0
  82. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/images/blank_chart.png +0 -0
  83. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/images/illegible_label.png +0 -0
  84. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/images/injection.png +0 -0
  85. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/images/make_images.py +0 -0
  86. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/images/product_a.png +0 -0
  87. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/images/product_b.png +0 -0
  88. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/images/quarterly_revenue.png +0 -0
  89. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/images/truncated_axis.png +0 -0
  90. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/nb_kryss_ordning.py +0 -0
  91. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/rag.py +0 -0
  92. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/safety.py +0 -0
  93. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/skatteetaten_legitimasjon.py +0 -0
  94. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/system_prompt.py +0 -0
  95. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/toll_reisegodskvote.py +0 -0
  96. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/ung.py +0 -0
  97. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/vision_integrity.py +0 -0
  98. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/spans.py +0 -0
  99. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/stats.py +0 -0
  100. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/targets/__init__.py +0 -0
  101. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/targets/base.py +0 -0
  102. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/targets/callable.py +0 -0
  103. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/targets/model.py +0 -0
  104. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/tracing/__init__.py +0 -0
  105. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/tracing/auth.py +0 -0
  106. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/tracing/otlp.py +0 -0
  107. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/tracing/provider.py +0 -0
  108. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/tracing/shared.py +0 -0
  109. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/tracing/store.py +0 -0
  110. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/utils.py +0 -0
  111. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit.egg-info/dependency_links.txt +0 -0
  112. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit.egg-info/entry_points.txt +0 -0
  113. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit.egg-info/requires.txt +0 -0
  114. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit.egg-info/top_level.txt +0 -0
  115. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_anthropic_judge_default_path.py +0 -0
  116. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_audit_flow.py +0 -0
  117. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_checklist.py +0 -0
  118. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_checklist_judge.py +0 -0
  119. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_checklist_pipeline.py +0 -0
  120. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_checklist_reframing.py +0 -0
  121. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_choice_match_judge.py +0 -0
  122. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_cross_judge.py +0 -0
  123. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_custom_prompts.py +0 -0
  124. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_decision.py +0 -0
  125. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_document_expansion.py +0 -0
  126. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_experiment_streaming.py +0 -0
  127. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_fakes.py +0 -0
  128. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_file_uri.py +0 -0
  129. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_fragility.py +0 -0
  130. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_generation_params.py +0 -0
  131. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_json_format.py +0 -0
  132. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_judge_compose.py +0 -0
  133. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_judge_notes.py +0 -0
  134. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_judge_output_contract.py +0 -0
  135. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_judge_postprocess.py +0 -0
  136. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_judge_response_schema.py +0 -0
  137. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_judge_robustness_pipeline.py +0 -0
  138. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_otlp_auth.py +0 -0
  139. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_perturbations.py +0 -0
  140. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_reframing.py +0 -0
  141. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_rejudge.py +0 -0
  142. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_repeated_experiments.py +0 -0
  143. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_resumable_experiments.py +0 -0
  144. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_spans.py +0 -0
  145. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_stats.py +0 -0
  146. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_strip_thinking.py +0 -0
  147. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_target_api_key.py +0 -0
  148. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_targets.py +0 -0
  149. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_token_counting.py +0 -0
  150. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_tracing.py +0 -0
  151. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_tracing_auth.py +0 -0
  152. {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_vision_integrity_pack.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simpleaudit
3
- Version: 0.4.0
3
+ Version: 0.4.2
4
4
  Summary: Lightweight AI Safety Auditing Framework
5
5
  Author: SimpleAudit Contributors
6
6
  License: MIT
@@ -65,6 +65,17 @@ See the [standards and best practices for creating custom test scenarios](https:
65
65
 
66
66
  <img alt="simpleaudit_example_gemma_model" src="https://github.com/user-attachments/assets/05c45a62-74e7-4aa3-a3cd-41bad0cc8233" />
67
67
 
68
+ ---
69
+
70
+ > [!TIP]
71
+ > **🚀 Prefer a visual interface?** Try [SimpleAudit Studio](https://github.com/SimulaMet/SimpleAuditStudio) — a web UI to build scenarios, run audits, compare results, and track drift.
72
+ >
73
+ > **🔥 [Live demo](https://sushantgautam-simpleaudit-studio.hf.space/)** (no install) · See the [Studio GitHub repository](https://github.com/SimulaMet/SimpleAuditStudio) for more details. **Run locally** (requires [uv](https://docs.astral.sh/uv/getting-started/installation/)):
74
+ >
75
+ > `uvx simpleaudit-studio@latest`
76
+
77
+ ---
78
+
68
79
  ## Installation
69
80
 
70
81
  **Install from PyPI (recommended):**
@@ -126,7 +137,7 @@ results.save("./my_audit_results/audit_results.json")
126
137
 
127
138
  **💡 View results interactively:**
128
139
 
129
- The web visualizer now lives in [SimpleAudit Studio](https://github.com/SimulaMet/SimpleAuditStudio). To browse a folder of results in a local web server (no audit worker), run:
140
+ The web visualizer now lives in [SimpleAudit Studio](https://github.com/SimulaMet/SimpleAuditStudio) (🔥 demo instance running at https://sushantgautam-simpleaudit-studio.hf.space/visualizer). To browse a folder of results in a local web server (no audit worker), run:
130
141
 
131
142
  ```bash
132
143
  uvx simpleaudit-studio --visualize-only --results_dir ./my_audit_results
@@ -485,6 +496,7 @@ auditor = ModelAuditor(
485
496
  | `show_progress` | Show tqdm progress bars | No (default: `True`) |
486
497
  | `max_retries` | Retries per API call for transient failures | No (default: 2) |
487
498
  | `retry_backoff` | Initial retry delay in seconds, doubled per attempt (exponential backoff) | No (default: 0.5) |
499
+ | `on_turn` | Progress callback `on_turn(turn_index, max_turns, role)`, fired after each phase with `role` `"auditor"`, `"target"` or `"judge"`. `turn_index` is 0-based; the judge is reported once, at `max_turns - 1`, and not at all if a target or judge call raised. Also accepted per call by `run` / `run_async`, where it overrides this one. Full contract: `simpleaudit.model_auditor.OnTurn` | No |
488
500
 
489
501
  ## Why SimpleAudit?
490
502
 
@@ -553,10 +565,20 @@ SimpleAudit includes pre-built scenario packs:
553
565
  | `human_rights_fair_trial` | 14 | International human rights law, liberty and fair trial (ICCPR 9, 14): pre-trial detention, minimum guarantees, independence, military courts, appeal, derogation |
554
566
  | `vision_integrity` | 8 | Chart-reading integrity for vision models — **requires vision-capable models**, not included in `all` |
555
567
  | `nb_kryss_ordning` | 13 | National Library cross-scheme transfer: ISBN/ISSN/ISMN format rules, ISBN series thresholds, legal-deposit copy counts, unchanged-reprint consequences, jurisdiction. Six matched pairs — each outlier probe has a majority twin with character-identical wording |
556
- | `all` | 1386 | All scenarios combined |
568
+ | `healthbench_behaviours` | 17 | One scenario per HealthBench consensus category: emergency referrals (including over-referral), context seeking, hedging, communication with clinicians and laypeople, response depth, global health, health data tasks |
569
+ | `context_grounding` | 3 | Retrieval-context grounding: does the target use the supplied documents and ignore a planted superseded/low-authority chunk? **Requires `SingleTurnAuditor`** (one fixed question over a fixed document set), so it is **not** part of `all` |
570
+ | `all` | 1403 | All scenarios combined |
557
571
 
558
572
  </div>
559
573
 
574
+ > **Comparability note:** since the context-grounding release, `all` includes the
575
+ > 17 `healthbench_behaviours` scenarios (the count rose from 1386 to 1403).
576
+ > Aggregate scores from `auditor.run("all")` taken against a pre-context-grounding
577
+ > SimpleAudit are therefore not directly comparable to those from this version —
578
+ > the same model audited by both will score over 17 additional scenarios. Use a
579
+ > specific named pack when you need like-for-like historical comparison.
580
+
581
+
560
582
  ```python
561
583
  # List available packs
562
584
  from simpleaudit import list_scenario_packs
@@ -576,6 +598,91 @@ Contributing a pack: follow the
576
598
  `python scripts/check_scenario_pack.py <pack>` before opening the PR, and expect the review to
577
599
  follow the [pack review checklist](simpleaudit/scenarios/PACK_REVIEW_CHECKLIST.md).
578
600
 
601
+ ### Dated facts
602
+
603
+ Rates and thresholds go out of date. A scenario can list the dated facts it relies on in
604
+ `metadata.facts`, recording when each figure was checked and when it needs checking again:
605
+
606
+ ```python
607
+ "facts": [{
608
+ "claim": "Grunnbeløpet (G), NOK",
609
+ "anchors": ["grunnbeløp", "G"], # optional; read by the fact_check judge only
610
+ "value": 136549,
611
+ "valid_from": "2026-05-01", # None when the source gives no date
612
+ "verified_at": "2026-10-07",
613
+ "review_by": "2027-05-01", # set by the rule's own rhythm; None for a figure fixed in statute
614
+ "source_url": "https://www.nav.no/grunnbelopet",
615
+ "source_quote": "Grunnbeløpet (G) per 1. mai 2026 er 136 549 kroner.",
616
+ }]
617
+ ```
618
+
619
+ `stale_facts(packs, as_of)` returns the facts whose `review_by` is before `as_of`. It reads no
620
+ clock, so pass the date yourself:
621
+
622
+ ```python
623
+ from datetime import date
624
+ from simpleaudit.scenarios import SCENARIO_PACKS, stale_facts
625
+
626
+ for f in stale_facts(SCENARIO_PACKS, date.today()):
627
+ print(f["pack"], f["scenario"], f["claim"], f["review_by"])
628
+ ```
629
+
630
+ `anchors` lists the words an answer uses when it talks about that quantity. The experimental
631
+ `fact_check` judge weighs only the figures in sentences that carry one of them; without the
632
+ key it falls back to the longer words of `claim`, which misses more when the claim is written
633
+ in English and the answer in Norwegian.
634
+
635
+ The field is optional and, like the rest of `metadata`, never reaches the models.
636
+
637
+ ### HealthBench (loaded at run time)
638
+
639
+ [HealthBench](https://openai.com/index/healthbench/) and
640
+ [HealthBench Professional](https://arxiv.org/abs/2604.27470) are OpenAI's MIT-licensed health
641
+ benchmarks with physician-written rubrics. OpenAI asks that their examples are not posted online in
642
+ plain text, so they are not a built-in pack. `load_healthbench_scenarios` downloads a subset from
643
+ OpenAI into `~/.cache/simpleaudit/healthbench`, checks it against a pinned SHA-256 and builds v2
644
+ scenarios in memory:
645
+
646
+ ```python
647
+ from simpleaudit import ModelAuditor, load_healthbench_scenarios
648
+
649
+ auditor = ModelAuditor(model="gpt-4o-mini", provider="openai",
650
+ judge_model="gpt-4o", judge_provider="openai", judge="checklist")
651
+ scenarios = load_healthbench_scenarios("hard", themes=["emergency_referrals"], limit=20, seed=0)
652
+ results = auditor.run(scenarios, max_turns=1)
653
+ ```
654
+
655
+ | Subset | Examples | Single-turn scenarios |
656
+ |--------|----------|-----------------------|
657
+ | `main` | 5,000 | 2,915 |
658
+ | `hard` | 1,000 | 523 |
659
+ | `consensus` | 3,671 | 2,201 |
660
+ | `professional` | 525 | 410 |
661
+
662
+ Each scenario's `test_prompt` is the user's message and its `expected_behavior` is the example's
663
+ rubric, heaviest criteria first, with negative-point criteria phrased as "Should NOT". Themes map to
664
+ categories, and severity is `critical` for emergencies; `high` for possible emergencies, red teaming,
665
+ or a rubric that gives the maximum penalty of −10; `medium` otherwise. Rubric points, prompt ids and
666
+ the HealthBench canary string are in `metadata`.
667
+
668
+ Limits:
669
+
670
+ - **Multi-turn examples are skipped:** 42% of the main set and 22% of Professional. A scenario has no
671
+ field for earlier turns, and pasting them into one message would show the target earlier
672
+ "assistant" answers as user text.
673
+ - **Rubrics are written for one reply,** so use `max_turns=1` for HealthBench-style grading. Later
674
+ turns go beyond what the rubric covers.
675
+ - **Use `judge="checklist"`.** Some criteria were written while grading another reply and describe
676
+ it ("references a YouTube source, 'Hypertension by Mike'"). In a test run the default judge reported
677
+ such criteria as faults of a reply that mentioned neither. The checklist judge has to quote the
678
+ reply for every violation, and the quote is checked, so those criteria came out as met.
679
+ - **The judge returns a severity verdict, not a HealthBench score.**
680
+ - **Rubrics have 2–48 criteria (Professional: 1–5),** so many scenarios fall outside the 3–7 the
681
+ scenario guideline asks for. Pass `min_criteria` and `max_criteria` to filter.
682
+ - **Language is detected only if `langdetect` is installed;** otherwise it is `"und"`.
683
+
684
+ Audit results and HTML exports contain the test prompts, so don't publish them for these runs.
685
+
579
686
  ### Vision Integrity
580
687
 
581
688
  `vision_integrity` is the first pack that attaches images (via `file_uri`). It tests the same
@@ -761,6 +868,8 @@ Without `scenario_severities` the ceiling defaults to `medium` and `rejudge` war
761
868
 
762
869
  **The designed severity is a ceiling.** The checklist judge can never report a severity above the scenario's designed `severity`: a `medium` scenario can at most come out `medium`, however badly the model did on it. That follows the scenario guidelines, where the designed severity is the potential harm if the model fails. The holistic judge is not bound this way, so on scenarios where it reported `high` for a `medium` scenario the two judges will disagree by construction. Expect lower agreement with old holistic verdicts on such packs, and read it as a difference in what is being measured, not as an error in either judge.
763
870
 
871
+ **Pairing with the groundedness judge.** `SingleTurnAuditor` (the runner for the `context_grounding` pack, whose scenarios attach marked documents) uses the groundedness judge to say what the answer relied on. That judge is blind to `expected_behavior` by design, so its severity measures provenance, not correctness. When a marked scenario also carries `expected_behavior`, the runner makes a second judge call with the checklist judge on the same exchange and combines the two: the stricter severity wins, and the result keeps both halves under `provenance` and `correctness` with a `severity_components` summary. Pass `correctness_judge=None` to `SingleTurnAuditor` for the provenance judgment alone, or another registry name to grade correctness differently.
872
+
764
873
  **Limits worth knowing.** A quote can prove presence, not absence, so omissions are anchored to the nearest passage and labelled `omission`. A scenario whose `expected_behavior` is a single compound sentence (the `ung` pack) gets a binary verdict: `pass` or S. Scenario `judge_notes` are shown to the judge to help it decide met or violated; a note that names a severity has no effect on this judge, because the severity is derived. Unverified violations count by default and are flagged; `functools.partial(postprocess_checklist, unverified_policy="exclude")` passed as `judge_postprocess=` (or `postprocess=` on the judge-only paths) drops them from the score instead, which cannot be gamed by a confabulated violation but reads unverifiable violations as passes.
765
874
 
766
875
  **Measured against the holistic judge.** `examples/checklist_judge_comparison.py` runs the checklist judge through the same fixed-transcript checks as the holistic baseline and writes `results/checklist_judge_*.json`. The numbers from one run are in the Judge Robustness section below.
@@ -20,6 +20,17 @@ See the [standards and best practices for creating custom test scenarios](https:
20
20
 
21
21
  <img alt="simpleaudit_example_gemma_model" src="https://github.com/user-attachments/assets/05c45a62-74e7-4aa3-a3cd-41bad0cc8233" />
22
22
 
23
+ ---
24
+
25
+ > [!TIP]
26
+ > **🚀 Prefer a visual interface?** Try [SimpleAudit Studio](https://github.com/SimulaMet/SimpleAuditStudio) — a web UI to build scenarios, run audits, compare results, and track drift.
27
+ >
28
+ > **🔥 [Live demo](https://sushantgautam-simpleaudit-studio.hf.space/)** (no install) · See the [Studio GitHub repository](https://github.com/SimulaMet/SimpleAuditStudio) for more details. **Run locally** (requires [uv](https://docs.astral.sh/uv/getting-started/installation/)):
29
+ >
30
+ > `uvx simpleaudit-studio@latest`
31
+
32
+ ---
33
+
23
34
  ## Installation
24
35
 
25
36
  **Install from PyPI (recommended):**
@@ -81,7 +92,7 @@ results.save("./my_audit_results/audit_results.json")
81
92
 
82
93
  **💡 View results interactively:**
83
94
 
84
- The web visualizer now lives in [SimpleAudit Studio](https://github.com/SimulaMet/SimpleAuditStudio). To browse a folder of results in a local web server (no audit worker), run:
95
+ The web visualizer now lives in [SimpleAudit Studio](https://github.com/SimulaMet/SimpleAuditStudio) (🔥 demo instance running at https://sushantgautam-simpleaudit-studio.hf.space/visualizer). To browse a folder of results in a local web server (no audit worker), run:
85
96
 
86
97
  ```bash
87
98
  uvx simpleaudit-studio --visualize-only --results_dir ./my_audit_results
@@ -440,6 +451,7 @@ auditor = ModelAuditor(
440
451
  | `show_progress` | Show tqdm progress bars | No (default: `True`) |
441
452
  | `max_retries` | Retries per API call for transient failures | No (default: 2) |
442
453
  | `retry_backoff` | Initial retry delay in seconds, doubled per attempt (exponential backoff) | No (default: 0.5) |
454
+ | `on_turn` | Progress callback `on_turn(turn_index, max_turns, role)`, fired after each phase with `role` `"auditor"`, `"target"` or `"judge"`. `turn_index` is 0-based; the judge is reported once, at `max_turns - 1`, and not at all if a target or judge call raised. Also accepted per call by `run` / `run_async`, where it overrides this one. Full contract: `simpleaudit.model_auditor.OnTurn` | No |
443
455
 
444
456
  ## Why SimpleAudit?
445
457
 
@@ -508,10 +520,20 @@ SimpleAudit includes pre-built scenario packs:
508
520
  | `human_rights_fair_trial` | 14 | International human rights law, liberty and fair trial (ICCPR 9, 14): pre-trial detention, minimum guarantees, independence, military courts, appeal, derogation |
509
521
  | `vision_integrity` | 8 | Chart-reading integrity for vision models — **requires vision-capable models**, not included in `all` |
510
522
  | `nb_kryss_ordning` | 13 | National Library cross-scheme transfer: ISBN/ISSN/ISMN format rules, ISBN series thresholds, legal-deposit copy counts, unchanged-reprint consequences, jurisdiction. Six matched pairs — each outlier probe has a majority twin with character-identical wording |
511
- | `all` | 1386 | All scenarios combined |
523
+ | `healthbench_behaviours` | 17 | One scenario per HealthBench consensus category: emergency referrals (including over-referral), context seeking, hedging, communication with clinicians and laypeople, response depth, global health, health data tasks |
524
+ | `context_grounding` | 3 | Retrieval-context grounding: does the target use the supplied documents and ignore a planted superseded/low-authority chunk? **Requires `SingleTurnAuditor`** (one fixed question over a fixed document set), so it is **not** part of `all` |
525
+ | `all` | 1403 | All scenarios combined |
512
526
 
513
527
  </div>
514
528
 
529
+ > **Comparability note:** since the context-grounding release, `all` includes the
530
+ > 17 `healthbench_behaviours` scenarios (the count rose from 1386 to 1403).
531
+ > Aggregate scores from `auditor.run("all")` taken against a pre-context-grounding
532
+ > SimpleAudit are therefore not directly comparable to those from this version —
533
+ > the same model audited by both will score over 17 additional scenarios. Use a
534
+ > specific named pack when you need like-for-like historical comparison.
535
+
536
+
515
537
  ```python
516
538
  # List available packs
517
539
  from simpleaudit import list_scenario_packs
@@ -531,6 +553,91 @@ Contributing a pack: follow the
531
553
  `python scripts/check_scenario_pack.py <pack>` before opening the PR, and expect the review to
532
554
  follow the [pack review checklist](simpleaudit/scenarios/PACK_REVIEW_CHECKLIST.md).
533
555
 
556
+ ### Dated facts
557
+
558
+ Rates and thresholds go out of date. A scenario can list the dated facts it relies on in
559
+ `metadata.facts`, recording when each figure was checked and when it needs checking again:
560
+
561
+ ```python
562
+ "facts": [{
563
+ "claim": "Grunnbeløpet (G), NOK",
564
+ "anchors": ["grunnbeløp", "G"], # optional; read by the fact_check judge only
565
+ "value": 136549,
566
+ "valid_from": "2026-05-01", # None when the source gives no date
567
+ "verified_at": "2026-10-07",
568
+ "review_by": "2027-05-01", # set by the rule's own rhythm; None for a figure fixed in statute
569
+ "source_url": "https://www.nav.no/grunnbelopet",
570
+ "source_quote": "Grunnbeløpet (G) per 1. mai 2026 er 136 549 kroner.",
571
+ }]
572
+ ```
573
+
574
+ `stale_facts(packs, as_of)` returns the facts whose `review_by` is before `as_of`. It reads no
575
+ clock, so pass the date yourself:
576
+
577
+ ```python
578
+ from datetime import date
579
+ from simpleaudit.scenarios import SCENARIO_PACKS, stale_facts
580
+
581
+ for f in stale_facts(SCENARIO_PACKS, date.today()):
582
+ print(f["pack"], f["scenario"], f["claim"], f["review_by"])
583
+ ```
584
+
585
+ `anchors` lists the words an answer uses when it talks about that quantity. The experimental
586
+ `fact_check` judge weighs only the figures in sentences that carry one of them; without the
587
+ key it falls back to the longer words of `claim`, which misses more when the claim is written
588
+ in English and the answer in Norwegian.
589
+
590
+ The field is optional and, like the rest of `metadata`, never reaches the models.
591
+
592
+ ### HealthBench (loaded at run time)
593
+
594
+ [HealthBench](https://openai.com/index/healthbench/) and
595
+ [HealthBench Professional](https://arxiv.org/abs/2604.27470) are OpenAI's MIT-licensed health
596
+ benchmarks with physician-written rubrics. OpenAI asks that their examples are not posted online in
597
+ plain text, so they are not a built-in pack. `load_healthbench_scenarios` downloads a subset from
598
+ OpenAI into `~/.cache/simpleaudit/healthbench`, checks it against a pinned SHA-256 and builds v2
599
+ scenarios in memory:
600
+
601
+ ```python
602
+ from simpleaudit import ModelAuditor, load_healthbench_scenarios
603
+
604
+ auditor = ModelAuditor(model="gpt-4o-mini", provider="openai",
605
+ judge_model="gpt-4o", judge_provider="openai", judge="checklist")
606
+ scenarios = load_healthbench_scenarios("hard", themes=["emergency_referrals"], limit=20, seed=0)
607
+ results = auditor.run(scenarios, max_turns=1)
608
+ ```
609
+
610
+ | Subset | Examples | Single-turn scenarios |
611
+ |--------|----------|-----------------------|
612
+ | `main` | 5,000 | 2,915 |
613
+ | `hard` | 1,000 | 523 |
614
+ | `consensus` | 3,671 | 2,201 |
615
+ | `professional` | 525 | 410 |
616
+
617
+ Each scenario's `test_prompt` is the user's message and its `expected_behavior` is the example's
618
+ rubric, heaviest criteria first, with negative-point criteria phrased as "Should NOT". Themes map to
619
+ categories, and severity is `critical` for emergencies; `high` for possible emergencies, red teaming,
620
+ or a rubric that gives the maximum penalty of −10; `medium` otherwise. Rubric points, prompt ids and
621
+ the HealthBench canary string are in `metadata`.
622
+
623
+ Limits:
624
+
625
+ - **Multi-turn examples are skipped:** 42% of the main set and 22% of Professional. A scenario has no
626
+ field for earlier turns, and pasting them into one message would show the target earlier
627
+ "assistant" answers as user text.
628
+ - **Rubrics are written for one reply,** so use `max_turns=1` for HealthBench-style grading. Later
629
+ turns go beyond what the rubric covers.
630
+ - **Use `judge="checklist"`.** Some criteria were written while grading another reply and describe
631
+ it ("references a YouTube source, 'Hypertension by Mike'"). In a test run the default judge reported
632
+ such criteria as faults of a reply that mentioned neither. The checklist judge has to quote the
633
+ reply for every violation, and the quote is checked, so those criteria came out as met.
634
+ - **The judge returns a severity verdict, not a HealthBench score.**
635
+ - **Rubrics have 2–48 criteria (Professional: 1–5),** so many scenarios fall outside the 3–7 the
636
+ scenario guideline asks for. Pass `min_criteria` and `max_criteria` to filter.
637
+ - **Language is detected only if `langdetect` is installed;** otherwise it is `"und"`.
638
+
639
+ Audit results and HTML exports contain the test prompts, so don't publish them for these runs.
640
+
534
641
  ### Vision Integrity
535
642
 
536
643
  `vision_integrity` is the first pack that attaches images (via `file_uri`). It tests the same
@@ -716,6 +823,8 @@ Without `scenario_severities` the ceiling defaults to `medium` and `rejudge` war
716
823
 
717
824
  **The designed severity is a ceiling.** The checklist judge can never report a severity above the scenario's designed `severity`: a `medium` scenario can at most come out `medium`, however badly the model did on it. That follows the scenario guidelines, where the designed severity is the potential harm if the model fails. The holistic judge is not bound this way, so on scenarios where it reported `high` for a `medium` scenario the two judges will disagree by construction. Expect lower agreement with old holistic verdicts on such packs, and read it as a difference in what is being measured, not as an error in either judge.
718
825
 
826
+ **Pairing with the groundedness judge.** `SingleTurnAuditor` (the runner for the `context_grounding` pack, whose scenarios attach marked documents) uses the groundedness judge to say what the answer relied on. That judge is blind to `expected_behavior` by design, so its severity measures provenance, not correctness. When a marked scenario also carries `expected_behavior`, the runner makes a second judge call with the checklist judge on the same exchange and combines the two: the stricter severity wins, and the result keeps both halves under `provenance` and `correctness` with a `severity_components` summary. Pass `correctness_judge=None` to `SingleTurnAuditor` for the provenance judgment alone, or another registry name to grade correctness differently.
827
+
719
828
  **Limits worth knowing.** A quote can prove presence, not absence, so omissions are anchored to the nearest passage and labelled `omission`. A scenario whose `expected_behavior` is a single compound sentence (the `ung` pack) gets a binary verdict: `pass` or S. Scenario `judge_notes` are shown to the judge to help it decide met or violated; a note that names a severity has no effect on this judge, because the severity is derived. Unverified violations count by default and are flagged; `functools.partial(postprocess_checklist, unverified_policy="exclude")` passed as `judge_postprocess=` (or `postprocess=` on the judge-only paths) drops them from the score instead, which cannot be gamed by a confabulated violation but reads unverifiable violations as passes.
720
829
 
721
830
  **Measured against the holistic judge.** `examples/checklist_judge_comparison.py` runs the checklist judge through the same fixed-transcript checks as the holistic baseline and writes `results/checklist_judge_*.json`. The numbers from one run are in the Judge Robustness section below.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "simpleaudit"
7
- version = "0.4.0"
7
+ version = "0.4.2"
8
8
  description = "Lightweight AI Safety Auditing Framework"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -46,7 +46,7 @@ from .targets import (
46
46
  TargetResponse,
47
47
  )
48
48
  from .results import AuditResults, AuditResult
49
- from .scenarios import get_scenarios, list_scenario_packs
49
+ from .scenarios import get_scenarios, list_scenario_packs, load_healthbench_scenarios
50
50
  from .judges import build_judge, customize_judge, get_judge, list_judge_configs
51
51
  from .experiment import AuditExperiment, ExperimentEvent
52
52
  from .repeated_results import (
@@ -100,6 +100,7 @@ __all__ = [
100
100
  "AuditResult",
101
101
  "get_scenarios",
102
102
  "list_scenario_packs",
103
+ "load_healthbench_scenarios",
103
104
  "get_judge",
104
105
  "build_judge",
105
106
  "customize_judge",
@@ -0,0 +1,40 @@
1
+ """Running SimpleAudit's coroutines from synchronous code."""
2
+
3
+ import asyncio
4
+ from typing import Any, Coroutine, Dict
5
+
6
+
7
+ def _is_stale_client_close(context: Dict[str, Any]) -> bool:
8
+ """Is this an HTTP client from an earlier run, closed on a loop that is gone?
9
+
10
+ Each sync ``run()`` gets its own event loop. The OpenAI SDK's async client sits
11
+ in a reference cycle, so one from an earlier run can be garbage-collected while
12
+ a later run's loop is running. Its ``__del__`` then schedules ``aclose()`` on
13
+ that loop, for connections that were opened on the earlier, closed one, and the
14
+ task fails with "Event loop is closed". Nothing is lost: those connections ended
15
+ with their loop. But asyncio prints the traceback as "Task exception was never
16
+ retrieved", which reads like a failed audit.
17
+
18
+ any-llm has no public way to close a provider's client, so the client cannot be
19
+ closed before its loop ends; until it does, the message is dropped here.
20
+ """
21
+ exception = context.get("exception")
22
+ if not isinstance(exception, RuntimeError) or str(exception) != "Event loop is closed":
23
+ return False
24
+ task = context.get("future") or context.get("task")
25
+ get_coro = getattr(task, "get_coro", None)
26
+ coro = get_coro() if get_coro is not None else None
27
+ return getattr(coro, "__qualname__", "").endswith("AsyncClient.aclose")
28
+
29
+
30
+ def _exception_handler(loop: asyncio.AbstractEventLoop, context: Dict[str, Any]) -> None:
31
+ if _is_stale_client_close(context):
32
+ return
33
+ loop.default_exception_handler(context)
34
+
35
+
36
+ def run_sync(coro: Coroutine[Any, Any, Any]) -> Any:
37
+ """``asyncio.run(coro)``, minus the stale-client message described above."""
38
+ with asyncio.Runner() as runner:
39
+ runner.get_loop().set_exception_handler(_exception_handler)
40
+ return runner.run(coro)