vis-agent 0.3.4.dev8047__tar.gz → 0.3.4.dev8055__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.
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/PKG-INFO +1 -1
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/contracts/schema/evals.json +24 -2
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/pyproject.toml +1 -1
- vis_agent-0.3.4.dev8055/release-tag +1 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/src/blockether/vis/evals/_gateway.py +10 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/src/blockether/vis/evals/compare.py +99 -25
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/src/blockether/vis/evals/stats.py +7 -5
- vis_agent-0.3.4.dev8055/tests/test_evals_compare.py +196 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_evals_gateway.py +5 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_evals_stats.py +121 -1
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_evals_tasks.py +2 -1
- vis_agent-0.3.4.dev8047/release-tag +0 -1
- vis_agent-0.3.4.dev8047/tests/test_evals_compare.py +0 -53
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/.gitignore +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/LICENSE +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/README.md +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/contracts/fixtures/activity-arguments.json +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/contracts/fixtures/activity-cases.json +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/contracts/fixtures/activity-copy.json +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/contracts/fixtures/activity-execution.json +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/contracts/fixtures/activity-groups.json +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/contracts/fixtures/activity-read-session.json +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/contracts/fixtures/activity-reads.json +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/contracts/fixtures/activity-results.json +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/contracts/fixtures/activity-stream.json +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/contracts/fixtures/activity-tables.json +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/contracts/fixtures/activity.json +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/contracts/fixtures/diff.json +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/contracts/fixtures/live-primitives.json +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/contracts/fixtures/session-health.json +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/contracts/fixtures/view.json +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/contracts/schema/activity.json +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/contracts/schema/agents.json +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/contracts/schema/automations.json +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/contracts/schema/common.json +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/contracts/schema/config.json +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/contracts/schema/content.json +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/contracts/schema/council.json +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/contracts/schema/diff.json +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/contracts/schema/gateway.json +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/contracts/schema/provider.json +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/contracts/schema/rooms.json +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/contracts/schema/symbol.json +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/contracts/schema/toggle.json +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/contracts/schema/view.json +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/examples/greeter/tests/test_greeter.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/hatch_build.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/src/blockether/vis/__init__.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/src/blockether/vis/_contracts.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/src/blockether/vis/_outside.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/src/blockether/vis/_wire.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/src/blockether/vis/activity.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/src/blockether/vis/engine/__init__.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/src/blockether/vis/engine/_agent.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/src/blockether/vis/engine/_agents.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/src/blockether/vis/engine/_client.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/src/blockether/vis/engine/_council.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/src/blockether/vis/engine/_extensions.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/src/blockether/vis/engine/_host.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/src/blockether/vis/engine/_install.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/src/blockether/vis/engine/_local.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/src/blockether/vis/engine/_models.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/src/blockether/vis/evals/__init__.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/src/blockether/vis/evals/agent.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/src/blockether/vis/evals/bench.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/src/blockether/vis/evals/check.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/src/blockether/vis/evals/environment.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/src/blockether/vis/evals/grade.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/src/blockether/vis/evals/harbor.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/src/blockether/vis/evals/judge-rubric.toml +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/src/blockether/vis/evals/judge.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/src/blockether/vis/evals/labels.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/src/blockether/vis/evals/leaderboard.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/src/blockether/vis/evals/lic.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/src/blockether/vis/evals/longmemeval.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/src/blockether/vis/evals/mrcr.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/src/blockether/vis/evals/repeats.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/src/blockether/vis/evals/report.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/src/blockether/vis/evals/reports.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/src/blockether/vis/evals/rounds.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/src/blockether/vis/evals/stages.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/src/blockether/vis/evals/trajectory.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/src/blockether/vis/extension.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/src/blockether/vis/extension_package.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/src/blockether/vis/py.typed +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/src/blockether/vis/rooms.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/src/blockether/vis/views.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/conftest.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/evals_atif.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_activity.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_agent.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_agent_extension_lifecycle.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_agent_extensions.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_agents.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_api_guides.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_attachments.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_authoring.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_catalog.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_catalog_recipe.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_ci_report_recipe.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_client.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_client_extensions_engine.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_code_quality_recipe.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_contracts.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_council.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_declarations.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_engine.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_engine_docstrings.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_evals_agent.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_evals_bench.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_evals_check.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_evals_environment.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_evals_grade.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_evals_guards.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_evals_harbor.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_evals_judge.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_evals_labels.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_evals_lic.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_evals_longmemeval.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_evals_mrcr.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_evals_repeats.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_evals_report.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_evals_rounds.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_evals_stages.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_evals_trajectory.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_extension_package.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_extension_reference.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_gateway_performance.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_goals.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_install.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_live_primitives.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_live_wait.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_local.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_method_typing.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_models.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_monitor_recipe.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_namespace.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_outside.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_providers.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_read_session.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_record_arguments.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_registration.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_repair_hooks.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_result_sequence.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_rooms.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_scoped_settings.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_sdk_bootstrap.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_sdk_guide.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_sdk_typing.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_shortcuts_live.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_structured_live.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_symbol_contract.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_testing.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_text_list_arguments.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_tools.py +0 -0
- {vis_agent-0.3.4.dev8047 → vis_agent-0.3.4.dev8055}/tests/test_views.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: vis-agent
|
|
3
|
-
Version: 0.3.4.
|
|
3
|
+
Version: 0.3.4.dev8055
|
|
4
4
|
Summary: The Vis Python SDK — author extensions and control local or remote engines.
|
|
5
5
|
Project-URL: Homepage, https://vis.blockether.com
|
|
6
6
|
Project-URL: Documentation, https://vis.blockether.com
|
|
@@ -149,6 +149,12 @@
|
|
|
149
149
|
},
|
|
150
150
|
"candidate_name": {
|
|
151
151
|
"type": "string"
|
|
152
|
+
},
|
|
153
|
+
"margin_points": {
|
|
154
|
+
"description": "The smallest change in points that matters. It splits a result without a clear change into equivalent, not worse, not better or inconclusive.",
|
|
155
|
+
"type": "number",
|
|
156
|
+
"exclusiveMinimum": 0,
|
|
157
|
+
"maximum": 100
|
|
152
158
|
}
|
|
153
159
|
},
|
|
154
160
|
"required": [
|
|
@@ -649,7 +655,16 @@
|
|
|
649
655
|
"maxItems": 2
|
|
650
656
|
},
|
|
651
657
|
"verdict": {
|
|
652
|
-
"type": "string"
|
|
658
|
+
"type": "string",
|
|
659
|
+
"enum": [
|
|
660
|
+
"better",
|
|
661
|
+
"worse",
|
|
662
|
+
"no measurable difference",
|
|
663
|
+
"equivalent",
|
|
664
|
+
"not worse",
|
|
665
|
+
"not better",
|
|
666
|
+
"inconclusive"
|
|
667
|
+
]
|
|
653
668
|
},
|
|
654
669
|
"detectable_change_points": {
|
|
655
670
|
"type": "number"
|
|
@@ -683,6 +698,12 @@
|
|
|
683
698
|
"items": {
|
|
684
699
|
"type": "string"
|
|
685
700
|
}
|
|
701
|
+
},
|
|
702
|
+
"margin_points": {
|
|
703
|
+
"type": [
|
|
704
|
+
"number",
|
|
705
|
+
"null"
|
|
706
|
+
]
|
|
686
707
|
}
|
|
687
708
|
},
|
|
688
709
|
"required": [
|
|
@@ -698,7 +719,8 @@
|
|
|
698
719
|
"worse",
|
|
699
720
|
"better",
|
|
700
721
|
"unpaired",
|
|
701
|
-
"caveats"
|
|
722
|
+
"caveats",
|
|
723
|
+
"margin_points"
|
|
702
724
|
],
|
|
703
725
|
"additionalProperties": false
|
|
704
726
|
},
|
|
@@ -8,7 +8,7 @@ build-backend = "hatchling.build"
|
|
|
8
8
|
|
|
9
9
|
[project]
|
|
10
10
|
name = "vis-agent"
|
|
11
|
-
version = "0.3.4.
|
|
11
|
+
version = "0.3.4.dev8055"
|
|
12
12
|
description = "The Vis Python SDK — author extensions and control local or remote engines."
|
|
13
13
|
readme = "README.md"
|
|
14
14
|
requires-python = ">=3.12"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
beta-6e5ce3880c1ae076ac0f06479a3186f14709b26e
|
|
@@ -68,6 +68,15 @@ def _required_text(body: Mapping, key: str) -> str:
|
|
|
68
68
|
return value
|
|
69
69
|
|
|
70
70
|
|
|
71
|
+
def _number(body: Mapping, key: str) -> float | None:
|
|
72
|
+
value = body.get(key)
|
|
73
|
+
if value is None:
|
|
74
|
+
return None
|
|
75
|
+
if isinstance(value, bool) or not isinstance(value, (int, float)):
|
|
76
|
+
raise ValueError(f"`{key}` must be a number.")
|
|
77
|
+
return float(value)
|
|
78
|
+
|
|
79
|
+
|
|
71
80
|
def _cases(body: Mapping, key: str) -> dict[tuple[str, ...], tuple[int, int]]:
|
|
72
81
|
rows = body.get(key)
|
|
73
82
|
if not isinstance(rows, list):
|
|
@@ -114,6 +123,7 @@ def _compare(body: Mapping):
|
|
|
114
123
|
_cases(body, "candidate"),
|
|
115
124
|
baseline_name=_word(body, "baseline_name", "baseline"),
|
|
116
125
|
candidate_name=_word(body, "candidate_name", "candidate"),
|
|
126
|
+
margin_points=_number(body, "margin_points"),
|
|
117
127
|
)
|
|
118
128
|
|
|
119
129
|
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
A case is a key such as `(task, agent, model)` from `case_results`, with its
|
|
4
4
|
`(passed, runs)` count. A route is the agent with the model of a case. Call
|
|
5
5
|
`compare_cases` with the cases of a baseline and a candidate. It pairs the cases,
|
|
6
|
-
gives the paired difference with its 95% interval and tests each route.
|
|
6
|
+
gives the paired difference with its 95% interval and tests each route. Give
|
|
7
|
+
`margin_points`, the smallest change that matters to you, to tell an equivalent
|
|
8
|
+
result from a result that needs more cases.
|
|
7
9
|
"""
|
|
8
10
|
|
|
9
11
|
from __future__ import annotations
|
|
@@ -51,13 +53,25 @@ class CaseComparison:
|
|
|
51
53
|
|
|
52
54
|
The rates are the mean pass rates of the paired cases, in percent.
|
|
53
55
|
`difference_ci95` is the Agresti-Min 95% interval of the paired difference, in
|
|
54
|
-
points. `verdict` is `better` or `worse` only when that interval excludes zero
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
points. `verdict` is `better` or `worse` only when that interval excludes zero.
|
|
57
|
+
Without `margin_points`, every other result is `no measurable difference`. With
|
|
58
|
+
it, the position of the interval against the margin gives the verdict:
|
|
59
|
+
|
|
60
|
+
- `equivalent`: the interval is inside the margin on both sides.
|
|
61
|
+
- `not worse`: the interval is above the negative margin.
|
|
62
|
+
- `not better`: the interval is below the margin, but reaches the negative margin.
|
|
63
|
+
- `inconclusive`: the interval reaches both sides of the margin.
|
|
64
|
+
|
|
65
|
+
The verdict uses the 95% interval before rounding. With a margin, a `better` or
|
|
66
|
+
`worse` verdict gets a caveat when the change is, or can be, smaller than the margin.
|
|
67
|
+
|
|
68
|
+
At this case count, a true change of `detectable_change_points` or more shows with
|
|
69
|
+
about 80% power, by a normal approximation. `p_value` is the two-sided exact McNemar
|
|
70
|
+
p-value of the changed cases. With repeats, it is the exact sign test of the case
|
|
71
|
+
changes. `routes` holds this test for each route. `worse` and `better` list the
|
|
72
|
+
changed cases, largest change first. `unpaired` counts cases on one side only; the
|
|
73
|
+
comparison leaves them out. Caveats name unpaired cases, different routes, a change
|
|
74
|
+
smaller than the margin and a verdict that the exact test does not confirm.
|
|
61
75
|
"""
|
|
62
76
|
|
|
63
77
|
paired_cases: int
|
|
@@ -73,6 +87,7 @@ class CaseComparison:
|
|
|
73
87
|
better: tuple[CaseChange, ...]
|
|
74
88
|
unpaired: int
|
|
75
89
|
caveats: tuple[str, ...]
|
|
90
|
+
margin_points: float | None = None
|
|
76
91
|
|
|
77
92
|
|
|
78
93
|
def _route(key: tuple[str, ...]) -> str:
|
|
@@ -87,6 +102,59 @@ def _points(change: float) -> float:
|
|
|
87
102
|
return round(change * 100, 1)
|
|
88
103
|
|
|
89
104
|
|
|
105
|
+
def _crossed_caveat(base: tuple[str, ...], other: tuple[str, ...]) -> str:
|
|
106
|
+
if base[1] != other[1] and base[2] != other[2]:
|
|
107
|
+
return (
|
|
108
|
+
f"The sides used different agents and models, {_route(base)} and "
|
|
109
|
+
f"{_route(other)}. The change can come from either one."
|
|
110
|
+
)
|
|
111
|
+
if base[1] != other[1]:
|
|
112
|
+
return (
|
|
113
|
+
f"The sides used different agents, {base[1]} and {other[1]}, "
|
|
114
|
+
f"with the same model {base[2]}."
|
|
115
|
+
)
|
|
116
|
+
return (
|
|
117
|
+
f"The sides used different models, {base[2]} and {other[2]}, "
|
|
118
|
+
f"with the same agent {base[1]}."
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _margin_caveat(
|
|
123
|
+
verdict: str, interval: tuple[float, float], margin: float | None
|
|
124
|
+
) -> str | None:
|
|
125
|
+
if not margin or verdict not in ("better", "worse"):
|
|
126
|
+
return None
|
|
127
|
+
low, high = interval if verdict == "better" else (-interval[1], -interval[0])
|
|
128
|
+
change = "gain" if verdict == "better" else "loss"
|
|
129
|
+
if high < margin:
|
|
130
|
+
return (
|
|
131
|
+
f"The {change} is real, but smaller than the margin of {margin:g} points."
|
|
132
|
+
)
|
|
133
|
+
if low < margin:
|
|
134
|
+
return (
|
|
135
|
+
f"The {change} is real, but it can be smaller than the margin of "
|
|
136
|
+
f"{margin:g} points."
|
|
137
|
+
)
|
|
138
|
+
return None
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _verdict(interval: tuple[float, float], margin: float | None) -> str:
|
|
142
|
+
low, high = interval
|
|
143
|
+
if low > 0:
|
|
144
|
+
return "better"
|
|
145
|
+
if high < 0:
|
|
146
|
+
return "worse"
|
|
147
|
+
if not margin:
|
|
148
|
+
return "no measurable difference"
|
|
149
|
+
if -margin < low and high < margin:
|
|
150
|
+
return "equivalent"
|
|
151
|
+
if low > -margin:
|
|
152
|
+
return "not worse"
|
|
153
|
+
if high < margin:
|
|
154
|
+
return "not better"
|
|
155
|
+
return "inconclusive"
|
|
156
|
+
|
|
157
|
+
|
|
90
158
|
def _route_tests(
|
|
91
159
|
changes: Mapping[tuple[str, ...], float], crossed: str | None
|
|
92
160
|
) -> tuple[RouteTest, ...]:
|
|
@@ -117,13 +185,18 @@ def compare_cases(
|
|
|
117
185
|
*,
|
|
118
186
|
baseline_name: str = "baseline",
|
|
119
187
|
candidate_name: str = "candidate",
|
|
188
|
+
margin_points: float | None = None,
|
|
120
189
|
) -> CaseComparison:
|
|
121
190
|
"""Compare the `(passed, runs)` cases of a baseline and a candidate.
|
|
122
191
|
|
|
123
192
|
Each key is `(task, agent, model)`. When the sides share no key and each side has
|
|
124
193
|
one route, the comparison pairs the cases by task and adds a caveat. The names
|
|
125
|
-
appear in error messages.
|
|
194
|
+
appear in error messages. `margin_points` is the smallest change in points that
|
|
195
|
+
matters, above 0 and at most 100. Raise ValueError when no case pairs, or for a
|
|
196
|
+
margin outside that range.
|
|
126
197
|
"""
|
|
198
|
+
if margin_points is not None and not 0 < margin_points <= 100:
|
|
199
|
+
raise ValueError("margin_points must be above 0 and at most 100")
|
|
127
200
|
caveats = []
|
|
128
201
|
crossed = None
|
|
129
202
|
if not baseline.keys() & candidate.keys():
|
|
@@ -135,12 +208,10 @@ def compare_cases(
|
|
|
135
208
|
"has more than one route. Compare results with the same agent and model."
|
|
136
209
|
)
|
|
137
210
|
crossed = f"{min(base_routes)} against {min(other_routes)}"
|
|
211
|
+
if base_routes != other_routes:
|
|
212
|
+
caveats.append(_crossed_caveat(next(iter(baseline)), next(iter(candidate))))
|
|
138
213
|
baseline = {key[:1]: value for key, value in baseline.items()}
|
|
139
214
|
candidate = {key[:1]: value for key, value in candidate.items()}
|
|
140
|
-
caveats.append(
|
|
141
|
-
f"The sides used different routes, {min(base_routes)} and "
|
|
142
|
-
f"{min(other_routes)}. Model and harness differ together."
|
|
143
|
-
)
|
|
144
215
|
paired = sorted(baseline.keys() & candidate.keys())
|
|
145
216
|
if not paired:
|
|
146
217
|
raise ValueError(f"{baseline_name} and {candidate_name} share no case")
|
|
@@ -156,20 +227,24 @@ def compare_cases(
|
|
|
156
227
|
)
|
|
157
228
|
changed.append((change, row))
|
|
158
229
|
(low, high), floor = paired_change(list(changes.values()))
|
|
159
|
-
|
|
160
|
-
verdict = (
|
|
161
|
-
"better"
|
|
162
|
-
if interval[0] > 0
|
|
163
|
-
else "worse"
|
|
164
|
-
if interval[1] < 0
|
|
165
|
-
else "no measurable difference"
|
|
166
|
-
)
|
|
230
|
+
verdict = _verdict((low * 100, high * 100), margin_points)
|
|
167
231
|
unpaired = len(baseline.keys() ^ candidate.keys())
|
|
168
232
|
if unpaired:
|
|
169
233
|
cases = "1 case" if unpaired == 1 else f"{unpaired} cases"
|
|
170
234
|
caveats.append(f"{cases} ran on one side only. The comparison leaves them out.")
|
|
171
235
|
count = len(paired)
|
|
172
236
|
values = list(changes.values())
|
|
237
|
+
p_value = mcnemar_exact(
|
|
238
|
+
sum(value < 0 for value in values), sum(value > 0 for value in values)
|
|
239
|
+
)
|
|
240
|
+
if verdict in ("better", "worse") and p_value >= 0.05:
|
|
241
|
+
caveats.append(
|
|
242
|
+
f"The interval says {verdict}, but the exact test gives p = "
|
|
243
|
+
f"{p_value:.3f}. Confirm the change with more cases."
|
|
244
|
+
)
|
|
245
|
+
margin_caveat = _margin_caveat(verdict, (low * 100, high * 100), margin_points)
|
|
246
|
+
if margin_caveat:
|
|
247
|
+
caveats.append(margin_caveat)
|
|
173
248
|
|
|
174
249
|
def rate(cases: Cases) -> float:
|
|
175
250
|
return round(
|
|
@@ -181,12 +256,10 @@ def compare_cases(
|
|
|
181
256
|
baseline_rate_percent=rate(baseline),
|
|
182
257
|
candidate_rate_percent=rate(candidate),
|
|
183
258
|
difference_points=round(100 * sum(values) / count, 1),
|
|
184
|
-
difference_ci95=
|
|
259
|
+
difference_ci95=(_points(low), _points(high)),
|
|
185
260
|
verdict=verdict,
|
|
186
261
|
detectable_change_points=_points(floor),
|
|
187
|
-
p_value=
|
|
188
|
-
sum(value < 0 for value in values), sum(value > 0 for value in values)
|
|
189
|
-
),
|
|
262
|
+
p_value=p_value,
|
|
190
263
|
routes=_route_tests(changes, crossed),
|
|
191
264
|
worse=tuple(
|
|
192
265
|
row for change, row in sorted(changed, key=lambda i: i[0]) if change < 0
|
|
@@ -196,4 +269,5 @@ def compare_cases(
|
|
|
196
269
|
),
|
|
197
270
|
unpaired=unpaired,
|
|
198
271
|
caveats=tuple(caveats),
|
|
272
|
+
margin_points=margin_points,
|
|
199
273
|
)
|
|
@@ -94,8 +94,10 @@ def detectable_change(units: Sequence[tuple[int, int]]) -> float | None:
|
|
|
94
94
|
"""Return the smallest change of the pass rate that a second run of this size detects.
|
|
95
95
|
|
|
96
96
|
The test compares two independent runs of the same size, two-sided at the 5% level,
|
|
97
|
-
with 80% power. The change
|
|
98
|
-
|
|
97
|
+
with 80% power. The change holds for a rise and for a fall, so the larger of the two
|
|
98
|
+
applies. A direction without a detectable change before 0 or 1 does not count. The
|
|
99
|
+
change has steps of 0.001. None means that there are no runs, or that no change
|
|
100
|
+
from 0 to 1 is detectable.
|
|
99
101
|
"""
|
|
100
102
|
total = sum(runs for _, runs in units)
|
|
101
103
|
if not total:
|
|
@@ -109,16 +111,16 @@ def detectable_change(units: Sequence[tuple[int, int]]) -> float | None:
|
|
|
109
111
|
power = Z_POWER * math.sqrt((rate * (1 - rate) + other * (1 - other)) / size)
|
|
110
112
|
return abs(other - rate) >= noise + power
|
|
111
113
|
|
|
112
|
-
|
|
114
|
+
steps = []
|
|
113
115
|
for sign in (1, -1):
|
|
114
116
|
for step in range(1, 1001):
|
|
115
117
|
other = rate + sign * step / 1000
|
|
116
118
|
if not 0 <= other <= 1:
|
|
117
119
|
break
|
|
118
120
|
if detects(other):
|
|
119
|
-
|
|
121
|
+
steps.append(step)
|
|
120
122
|
break
|
|
121
|
-
return
|
|
123
|
+
return max(steps) / 1000 if steps else None
|
|
122
124
|
|
|
123
125
|
|
|
124
126
|
def paired_change(changes: Sequence[float]) -> tuple[tuple[float, float], float]:
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"""Tests for the paired comparison of `blockether.vis.evals`.
|
|
2
|
+
|
|
3
|
+
The calibration tests run seeded simulations. They prove the claims of the
|
|
4
|
+
evaluation guide on false verdicts and power.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import random
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
from blockether.vis.evals import CaseChange, RouteTest, compare_cases
|
|
11
|
+
|
|
12
|
+
ROUTE = ("vis", "zai/glm")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _cases(passes):
|
|
16
|
+
return {
|
|
17
|
+
(f"case-{index:02}", *ROUTE): (passed, 2) for index, passed in enumerate(passes)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_compare_cases_reports_a_measurable_drop():
|
|
22
|
+
found = compare_cases(_cases([2] * 12), _cases([0] * 8 + [2] * 4))
|
|
23
|
+
assert found.verdict == "worse"
|
|
24
|
+
assert (found.paired_cases, found.baseline_rate_percent) == (12, 100.0)
|
|
25
|
+
assert found.candidate_rate_percent == 33.3
|
|
26
|
+
assert found.worse[0] == CaseChange("case-00 (vis with zai/glm)", "2/2", "0/2")
|
|
27
|
+
assert found.better == ()
|
|
28
|
+
assert found.routes == (
|
|
29
|
+
RouteTest("vis with zai/glm", 12, 8, 0, 2 / 2**8, 2 / 2**8),
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_compare_cases_without_change_has_no_verdict():
|
|
34
|
+
found = compare_cases(_cases([1, 2]), _cases([1, 2]))
|
|
35
|
+
assert found.verdict == "no measurable difference"
|
|
36
|
+
assert found.difference_points == 0.0
|
|
37
|
+
assert found.p_value == 1.0
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_compare_cases_leaves_out_unpaired_cases():
|
|
41
|
+
found = compare_cases(_cases([2, 2]), _cases([2]))
|
|
42
|
+
assert (found.paired_cases, found.unpaired) == (1, 1)
|
|
43
|
+
assert found.caveats == (
|
|
44
|
+
"1 case ran on one side only. The comparison leaves them out.",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_compare_cases_pairs_two_single_routes_by_task():
|
|
49
|
+
other = {("case-00", "vis", "zai/glm-next"): (0, 1)}
|
|
50
|
+
found = compare_cases({("case-00", *ROUTE): (1, 1)}, other)
|
|
51
|
+
assert found.worse == (CaseChange("case-00", "1/1", "0/1"),)
|
|
52
|
+
assert found.routes[0].route == "vis with zai/glm against vis with zai/glm-next"
|
|
53
|
+
assert found.caveats == (
|
|
54
|
+
"The sides used different models, zai/glm and zai/glm-next, "
|
|
55
|
+
"with the same agent vis.",
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@pytest.mark.parametrize(
|
|
60
|
+
("other", "caveat"),
|
|
61
|
+
[
|
|
62
|
+
(
|
|
63
|
+
("codex", "zai/glm"),
|
|
64
|
+
"The sides used different agents, vis and codex, with the same model "
|
|
65
|
+
"zai/glm.",
|
|
66
|
+
),
|
|
67
|
+
(
|
|
68
|
+
("codex", "openai/gpt"),
|
|
69
|
+
"The sides used different agents and models, vis with zai/glm and codex "
|
|
70
|
+
"with openai/gpt. The change can come from either one.",
|
|
71
|
+
),
|
|
72
|
+
],
|
|
73
|
+
)
|
|
74
|
+
def test_the_route_caveat_names_only_the_parts_that_differ(other, caveat):
|
|
75
|
+
found = compare_cases({("case-00", *ROUTE): (1, 1)}, {("case-00", *other): (1, 1)})
|
|
76
|
+
assert found.caveats == (caveat,)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_compare_cases_needs_a_shared_case():
|
|
80
|
+
two_routes = {("a", *ROUTE): (1, 1), ("a", "vis", "other"): (1, 1)}
|
|
81
|
+
with pytest.raises(ValueError, match="more than one route"):
|
|
82
|
+
compare_cases(two_routes, {("b", "x", "y"): (1, 1)}, candidate_name="run-2")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _changes(changes):
|
|
86
|
+
baseline, candidate = {}, {}
|
|
87
|
+
for index, change in enumerate(changes):
|
|
88
|
+
key = (f"case-{index:03}", *ROUTE)
|
|
89
|
+
baseline[key] = (1 if change < 0 else 0, 1)
|
|
90
|
+
candidate[key] = (1 if change > 0 else 0, 1)
|
|
91
|
+
return baseline, candidate
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@pytest.mark.parametrize(
|
|
95
|
+
("changes", "verdict"),
|
|
96
|
+
[
|
|
97
|
+
([0] * 100, "equivalent"),
|
|
98
|
+
([0] * 4, "inconclusive"),
|
|
99
|
+
([1] * 3 + [0] * 57, "not worse"),
|
|
100
|
+
([-1] * 3 + [0] * 57, "not better"),
|
|
101
|
+
],
|
|
102
|
+
)
|
|
103
|
+
def test_a_margin_tells_an_equivalent_result_from_too_few_cases(changes, verdict):
|
|
104
|
+
found = compare_cases(*_changes(changes), margin_points=10)
|
|
105
|
+
assert (found.verdict, found.margin_points) == (verdict, 10)
|
|
106
|
+
assert compare_cases(*_changes(changes)).verdict == "no measurable difference"
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@pytest.mark.parametrize("margin", [-1, 0, 101])
|
|
110
|
+
def test_compare_cases_refuses_a_margin_outside_0_to_100(margin):
|
|
111
|
+
with pytest.raises(ValueError, match="margin_points"):
|
|
112
|
+
compare_cases(*_changes([0]), margin_points=margin)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@pytest.mark.parametrize(
|
|
116
|
+
("change", "margin", "caveat"),
|
|
117
|
+
[
|
|
118
|
+
(1, 15, "The gain is real, but smaller than the margin of 15 points."),
|
|
119
|
+
(
|
|
120
|
+
1,
|
|
121
|
+
10,
|
|
122
|
+
"The gain is real, but it can be smaller than the margin of 10 points.",
|
|
123
|
+
),
|
|
124
|
+
(-1, 15, "The loss is real, but smaller than the margin of 15 points."),
|
|
125
|
+
],
|
|
126
|
+
)
|
|
127
|
+
def test_a_verdict_inside_the_margin_gets_a_caveat(change, margin, caveat):
|
|
128
|
+
# The 95% interval of 40 changed cases in 400 is 7.0 to 12.9 points.
|
|
129
|
+
found = compare_cases(*_changes([change] * 40 + [0] * 360), margin_points=margin)
|
|
130
|
+
verdict = "better" if change > 0 else "worse"
|
|
131
|
+
assert (found.verdict, found.caveats) == (verdict, (caveat,))
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def test_a_change_beyond_the_margin_gets_no_margin_caveat():
|
|
135
|
+
found = compare_cases(*_changes([1] * 120 + [0] * 280), margin_points=10)
|
|
136
|
+
assert (found.verdict, found.caveats) == ("better", ())
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def test_compare_cases_names_a_verdict_that_the_exact_test_does_not_confirm():
|
|
140
|
+
# 12 tasks: 6 tasks became better and 1 worse, as in a small agent comparison.
|
|
141
|
+
found = compare_cases(*_changes([1] * 6 + [-1] + [0] * 5))
|
|
142
|
+
assert (found.verdict, found.p_value) == ("better", 0.125)
|
|
143
|
+
assert found.caveats == (
|
|
144
|
+
"The interval says better, but the exact test gives p = 0.125. "
|
|
145
|
+
"Confirm the change with more cases.",
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _simulate(rng, tasks, change, margin=None, runs=1):
|
|
150
|
+
rates = [rng.uniform(0.3, 0.7) for _ in range(tasks)]
|
|
151
|
+
|
|
152
|
+
def side(shift):
|
|
153
|
+
return {
|
|
154
|
+
(f"t{index}", *ROUTE): (
|
|
155
|
+
sum(rng.random() < rate + shift for _ in range(runs)),
|
|
156
|
+
runs,
|
|
157
|
+
)
|
|
158
|
+
for index, rate in enumerate(rates)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return compare_cases(side(0.0), side(change), margin_points=margin)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@pytest.mark.parametrize(
|
|
165
|
+
("tasks", "runs"), [(12, 1), (16, 1), (30, 1), (30, 3), (80, 1)]
|
|
166
|
+
)
|
|
167
|
+
def test_without_a_change_a_verdict_is_rare(tasks, runs):
|
|
168
|
+
rng = random.Random(tasks * runs)
|
|
169
|
+
trials = 1000
|
|
170
|
+
verdicts = sum(
|
|
171
|
+
_simulate(rng, tasks, 0.0, runs=runs).verdict != "no measurable difference"
|
|
172
|
+
for _ in range(trials)
|
|
173
|
+
)
|
|
174
|
+
assert verdicts / trials <= 0.06
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def test_the_detectable_change_shows_with_80_percent_power():
|
|
178
|
+
rng = random.Random(3)
|
|
179
|
+
floor = _simulate(random.Random(1), 40, 0.0).detectable_change_points
|
|
180
|
+
trials = 600
|
|
181
|
+
better = sum(
|
|
182
|
+
_simulate(rng, 40, floor / 100).verdict == "better" for _ in range(trials)
|
|
183
|
+
)
|
|
184
|
+
assert better / trials >= 0.80
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@pytest.mark.parametrize("tasks", [60, 200])
|
|
188
|
+
def test_a_candidate_worse_by_the_margin_is_rarely_called_not_worse(tasks):
|
|
189
|
+
rng = random.Random(5)
|
|
190
|
+
trials = 600
|
|
191
|
+
wrong = sum(
|
|
192
|
+
_simulate(rng, tasks, -0.10, margin=10).verdict
|
|
193
|
+
in ("better", "equivalent", "not worse")
|
|
194
|
+
for _ in range(trials)
|
|
195
|
+
)
|
|
196
|
+
assert wrong / trials <= 0.04
|
|
@@ -50,6 +50,10 @@ def test_compare_pairs_the_cases():
|
|
|
50
50
|
)["result"]
|
|
51
51
|
assert result["paired_cases"] == 20
|
|
52
52
|
assert result["verdict"] == "better"
|
|
53
|
+
same = side([1] * 60)
|
|
54
|
+
body = {"baseline": same, "candidate": same, "margin_points": 5}
|
|
55
|
+
result = roundtrip("compare", body)["result"]
|
|
56
|
+
assert (result["verdict"], result["margin_points"]) == ("equivalent", 5.0)
|
|
53
57
|
|
|
54
58
|
|
|
55
59
|
def test_agreement_gives_the_kappa_advice():
|
|
@@ -83,6 +87,7 @@ def test_leaderboard_reads_every_page(monkeypatch):
|
|
|
83
87
|
("report", [], "must be a JSON object"),
|
|
84
88
|
("report", {}, "`summary` must be an object"),
|
|
85
89
|
("compare", {"baseline": [{}], "candidate": []}, "needs task, agent"),
|
|
90
|
+
("compare", {"baseline": [], "candidate": [], "margin_points": "5"}, "number"),
|
|
86
91
|
("agreement", {"items": [{"name": "x"}]}, "needs name, automatic"),
|
|
87
92
|
("leaderboard", {"name": "1-0-0"}, "`package` is required"),
|
|
88
93
|
("leaderboard", {"package": "org/bench"}, "`name` is required"),
|