repeated-run stability analysis, token tracking, resumable experiments, and auditor model separation#20
Conversation
…umable experiment runs
# Conflicts: # simpleaudit/model_auditor.py
… mock server to also work with parallel requests
There was a problem hiding this comment.
Pull request overview
This PR expands SimpleAudit’s experiment and auditing pipeline to support (1) per-role token usage tracking, (2) repeated/resumable experiments with stability analysis across runs, and (3) separating the “auditor” (probe generator) model from the judge model, with accompanying documentation and test infrastructure.
Changes:
- Add token usage propagation through
ModelAuditorintoAuditResult/AuditResults, including summary display and JSON serialization. - Introduce
n_repetitions,save_dircaching/resume, andRepeatedExperimentResults+ModelStabilityReportfor repeated-run stability statistics and serialization. - Add a comprehensive fake-client test harness and expand/modernize the test suite and examples (mock server + notebook + offline demo).
Reviewed changes
Copilot reviewed 24 out of 24 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
simpleaudit/model_auditor.py |
Adds auditor-model separation, token-returning _call_async, and token accounting through scenario runs. |
simpleaudit/results.py |
Extends result objects with token fields and adds aggregated token usage reporting/serialization. |
simpleaudit/experiment.py |
Adds repeated runs, disk caching via save_dir, and returns RepeatedExperimentResults. |
simpleaudit/repeated_results.py |
New repeated-run container + stability stats + JSON save/load. |
simpleaudit/__init__.py |
Exports RepeatedExperimentResults and ModelStabilityReport. |
tests/fakes.py |
New fake AnyLLM-compatible client utilities and make_auditor() helper for tests. |
tests/test_fakes.py |
Sanity tests for the fake client harness. |
tests/test_token_counting.py |
Tests for per-role token tracking and aggregation + end-to-end fake-client path. |
tests/test_json_format.py |
Tests that json_format is stored and forwarded into judge calls. |
tests/test_repeated_experiments.py |
Tests repeated-run behavior, stability calculations, and save/load round-trips. |
tests/test_resumable_experiments.py |
Tests save_dir run caching and resume/skip behavior. |
tests/test_judge_registry.py |
Tests judge registry/listing and named-judge prompt resolution. |
tests/test_strip_thinking.py |
Updates for _call_async returning (content, in_tokens, out_tokens) and adds history semantics tests. |
tests/test_custom_prompts.py |
Updates mocks to new _call_async return type and expands expected-behavior edge case testing. |
tests/test_audit_flow.py |
Migrates to fake clients and adds coverage for system prompt forwarding, language, max_workers, etc. |
tests/test_basic.py |
Adds severity distribution test and makes API-key error expectation explicit. |
tests/test_model_auditor.py |
Adds tests for separate auditor client vs reusing judge client. |
tests/test_scenario_data.py |
Adds assertions for BullshitBench test_prompt presence/shape. |
tests/test_local_providers.py |
Removed obsolete/skip-only local provider tests. |
tests/test_expected_behavior.py |
Removed obsolete minimal unittest-based coverage. |
README.md |
Documents new auditor-model params, repeated runs/stability analysis, and updated experiment usage. |
examples/mock_server.py |
Fixes mock server to return usage, support judge behavior, and simulate latency. |
examples/quickstart.ipynb |
Removes duplicated server code and updates to async API + new mock server behavior. |
examples/fake_audit.py |
New offline demo script using fake clients; supports repeated runs for stability reporting. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # Auditor model: falls back to judge config if not separately specified | ||
| self.auditor_model = auditor_model or judge_model | ||
| self._auditor_client_config = { | ||
| "api_key": auditor_api_key or judge_api_key, | ||
| "base_url": auditor_base_url or judge_base_url, | ||
| "provider": auditor_provider or judge_provider, | ||
| } | ||
| if self._auditor_client_config == self._judge_client_config and self.auditor_model == self.judge_model: | ||
| self.auditor_client = self.judge_client | ||
| else: | ||
| self.auditor_client = self._create_anyllm_client(**self._auditor_client_config) |
There was a problem hiding this comment.
dont see this as a real issue. no overhead or unintended behavior is introduced
| def _run_path(self, label: str, index: int) -> Path: | ||
| # Replace characters that are unsafe in directory names | ||
| safe_label = label.replace("/", "_").replace(":", "_").replace(" ", "_") | ||
| return self.save_dir / safe_label / f"run_{index}.json" |
| """ | ||
| Tests for the json_format parameter on ModelAuditor. | ||
|
|
||
| json_format=True (default) tells _judge_conversation_async to use OpenAI-style | ||
| JSON mode by passing response_format={"type": "json_object"} to _call_async. | ||
| json_format=False omits it — required for providers that don't support it (e.g. Ollama). | ||
|
|
||
| The flag must also be stored on ModelAuditor and forwarded through run_scenario. | ||
| """ |
| Usage in normal SA operations (no API keys needed):: | ||
|
|
||
| import asyncio | ||
| from tests.fakes import ( | ||
| make_auditor, | ||
| random_length_target, | ||
| cycling_severity_judge, | ||
| fixed_probe_auditor, | ||
| ) | ||
|
|
||
| auditor = make_auditor( | ||
| target=random_length_target(200, 500), | ||
| judge=cycling_severity_judge(["critical", "pass"]), | ||
| auditor=fixed_probe_auditor("Tell me more about this."), | ||
| max_turns=2, | ||
| ) | ||
| results = asyncio.run(auditor.run_async(scenarios=[ | ||
| {"name": "Test", "description": "A test scenario."}, | ||
| ])) | ||
| results.summary() | ||
|
|
||
| You can also swap individual clients on an existing ModelAuditor instance | ||
| (e.g. to stub only the judge while keeping a real target):: | ||
|
|
||
| auditor.judge_client = fixed_severity_judge("high") | ||
| """ |
There was a problem hiding this comment.
dont see as relevant as this feature is only for SA contributors that clone the repo anyways
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
|
@copilot resolve the merge conflicts in this pull request |
Overview
This branch contains the code used to generate the experimental data for the "When No Benchmark Exists" paper.
Major Contributions
🪙 Token Usage Tracking
_call_asyncnow returns(content, input_tokens, output_tokens); token counts are propagated through probe generation, judging, and target calls inrun_scenarioAuditResultgains six token count fields (auditor/judge/target × input/output)AuditResultsgains atoken_usageproperty (per-role and total) and prints a Token Usage table at the bottom of.summary()🔁 Repeated Experiments & Stability Analysis
AuditExperimentgainsn_repetitions(default1) to run each model N times and measure judge non-determinismRepeatedExperimentResultsclass wraps all runs with a backward-compatible dict interface (old code iterating over results continues to work).stability(model_name)returns aModelStabilityReportwith mean score, std, CV%, min/max, and per-scenario pass rates and agreement rates.summary()prints stability reports for all models.save()/.load()save_dirparameter: each run is saved to disk immediately after completion ({save_dir}/{label}/run_{i}.json). On re-run, completed runs are loaded from disk and skipped — crash recovery with no repeated workModelStabilityReportandRepeatedExperimentResultsexported from the top-level package🤖 Separate Auditor Model
ModelAuditorandAuditExperimentnow acceptauditor_model,auditor_provider,auditor_api_key,auditor_base_url— a third LLM role for probe/attack generation, separate from the judgeMinor Changes
AuditExperimentvalidates for duplicate model labels at construction time and raises a clearValueError"Sequential"whenmax_workers == 1examples/fake_audit.pyadded: a self-contained runnable demo using fake LLM clients, no API keys required. Supports--repeat Nto exercise the stability analysis pathBug Fixes
mock_server.pywas broken onmain: the/v1/chat/completionsendpoint returned nousagefield (required by the token tracking update), and had no way to simulate judge responses. Fixed by making the endpointasync, adding ausagefield, and adding a separate judge response path whenmodel == "judge". Also adds--min-delay/--max-delayCLI flags to simulate realistic latencyquickstart.ipynbwas broken onmain: it contained an inline copy of the mock server that was out of date and used a deprecatedModelAuditorinstantiation style. Fixed by removing the duplicated server code, updating to the currentModelAuditorAPI, and switching demo cells fromauditor.run()toawait auditor.run_async()Tests
test_fakes.py,test_token_counting.py,test_repeated_experiments.py,test_resumable_experiments.py,test_json_format.py,test_judge_registry.py, and a sharedfakes.pytest harnesstest_expected_behavior.py,test_local_providers.py