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.
- {simpleaudit-0.4.0/simpleaudit.egg-info → simpleaudit-0.4.2}/PKG-INFO +112 -3
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/README.md +111 -2
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/pyproject.toml +1 -1
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/__init__.py +2 -1
- simpleaudit-0.4.2/simpleaudit/_event_loop.py +40 -0
- simpleaudit-0.4.2/simpleaudit/context_attribution.py +384 -0
- simpleaudit-0.4.2/simpleaudit/context_derivations.py +220 -0
- simpleaudit-0.4.2/simpleaudit/context_findings.py +370 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/context_marks.py +88 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/cross_judge.py +2 -1
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/experiment.py +16 -5
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/judges/__init__.py +10 -2
- simpleaudit-0.4.2/simpleaudit/judges/fact_check.py +923 -0
- simpleaudit-0.4.2/simpleaudit/judges/groundedness.py +403 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/model_auditor.py +86 -9
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/reframing.py +2 -1
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/__init__.py +90 -2
- simpleaudit-0.4.2/simpleaudit/scenarios/context_grounding.py +365 -0
- simpleaudit-0.4.2/simpleaudit/scenarios/healthbench_behaviours.py +630 -0
- simpleaudit-0.4.2/simpleaudit/scenarios/healthbench_loader.py +394 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/helfo.py +34 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/lanekassen.py +12 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/nav_aap.py +61 -3
- simpleaudit-0.4.2/simpleaudit/scenarios/nb_rag_kontekst.py +163 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/skatteetaten.py +32 -0
- simpleaudit-0.4.2/simpleaudit/single_turn.py +664 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/targets/decision.py +3 -3
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/targets/http.py +30 -2
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/tracing/context.py +1 -1
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/tracing/selection.py +61 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2/simpleaudit.egg-info}/PKG-INFO +112 -3
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit.egg-info/SOURCES.txt +24 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_basic.py +3 -1
- simpleaudit-0.4.2/tests/test_context_attribution.py +532 -0
- simpleaudit-0.4.2/tests/test_context_derivations.py +336 -0
- simpleaudit-0.4.2/tests/test_context_findings.py +520 -0
- simpleaudit-0.4.2/tests/test_context_grounding_pack.py +263 -0
- simpleaudit-0.4.2/tests/test_context_marks.py +269 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_decision_target.py +1 -1
- simpleaudit-0.4.2/tests/test_event_loop.py +136 -0
- simpleaudit-0.4.2/tests/test_fact_check_judge.py +820 -0
- simpleaudit-0.4.2/tests/test_fact_freshness.py +97 -0
- simpleaudit-0.4.2/tests/test_groundedness_judge.py +809 -0
- simpleaudit-0.4.2/tests/test_healthbench_loader.py +190 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_judge_registry.py +2 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_model_auditor.py +27 -1
- simpleaudit-0.4.2/tests/test_nb_rag_kontekst.py +130 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_scenario_data.py +3 -3
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_scenario_pack_conventions.py +1 -0
- simpleaudit-0.4.2/tests/test_single_turn.py +881 -0
- simpleaudit-0.4.2/tests/test_single_turn_correctness.py +352 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/LICENSE +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/setup.cfg +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/auditor.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/checklist.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/cli.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/decision.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/judges/abstention.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/judges/binary_abstention.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/judges/checklist.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/judges/choice_match.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/judges/compose.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/judges/default.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/judges/factuality.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/judges/harm.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/judges/helpfulness.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/judges/helsedir_sexhealth_no.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/judges/helsedir_sexhealth_no_rag.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/judges/safety.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/perturbations.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/repeated_results.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/results.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/arbeidstilsynet_arbeidstid.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/bullshitbench_health.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/bullshitbench_v1_v2.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/health.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/hei_refusal.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/helpmed.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/human_rights_education.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/human_rights_fair_trial.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/human_rights_water.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/images/blank_chart.png +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/images/illegible_label.png +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/images/injection.png +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/images/make_images.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/images/product_a.png +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/images/product_b.png +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/images/quarterly_revenue.png +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/images/truncated_axis.png +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/nb_kryss_ordning.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/rag.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/safety.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/skatteetaten_legitimasjon.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/system_prompt.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/toll_reisegodskvote.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/ung.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/scenarios/vision_integrity.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/spans.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/stats.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/targets/__init__.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/targets/base.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/targets/callable.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/targets/model.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/tracing/__init__.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/tracing/auth.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/tracing/otlp.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/tracing/provider.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/tracing/shared.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/tracing/store.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit/utils.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit.egg-info/dependency_links.txt +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit.egg-info/entry_points.txt +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit.egg-info/requires.txt +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/simpleaudit.egg-info/top_level.txt +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_anthropic_judge_default_path.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_audit_flow.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_checklist.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_checklist_judge.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_checklist_pipeline.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_checklist_reframing.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_choice_match_judge.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_cross_judge.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_custom_prompts.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_decision.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_document_expansion.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_experiment_streaming.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_fakes.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_file_uri.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_fragility.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_generation_params.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_json_format.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_judge_compose.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_judge_notes.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_judge_output_contract.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_judge_postprocess.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_judge_response_schema.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_judge_robustness_pipeline.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_otlp_auth.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_perturbations.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_reframing.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_rejudge.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_repeated_experiments.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_resumable_experiments.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_spans.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_stats.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_strip_thinking.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_target_api_key.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_targets.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_token_counting.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_tracing.py +0 -0
- {simpleaudit-0.4.0 → simpleaudit-0.4.2}/tests/test_tracing_auth.py +0 -0
- {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.
|
|
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
|
-
| `
|
|
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
|
-
| `
|
|
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.
|
|
@@ -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)
|