evalshift 1.2.2__tar.gz → 1.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {evalshift-1.2.2 → evalshift-1.3.0}/CHANGELOG.md +102 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/PKG-INFO +23 -4
- {evalshift-1.2.2 → evalshift-1.3.0}/README.md +11 -3
- {evalshift-1.2.2 → evalshift-1.3.0}/docs/agents.md +31 -4
- {evalshift-1.2.2 → evalshift-1.3.0}/docs/configuration.md +232 -11
- {evalshift-1.2.2 → evalshift-1.3.0}/docs/evaluators.md +34 -3
- {evalshift-1.2.2 → evalshift-1.3.0}/docs/faq.md +8 -1
- {evalshift-1.2.2 → evalshift-1.3.0}/docs/getting-started.md +8 -2
- {evalshift-1.2.2 → evalshift-1.3.0}/docs/hosted.md +20 -8
- {evalshift-1.2.2 → evalshift-1.3.0}/docs/index.md +2 -1
- {evalshift-1.2.2 → evalshift-1.3.0}/docs/methodology.md +46 -12
- {evalshift-1.2.2 → evalshift-1.3.0}/docs/sdk.md +25 -6
- {evalshift-1.2.2 → evalshift-1.3.0}/docs/traces.md +40 -3
- {evalshift-1.2.2 → evalshift-1.3.0}/pyproject.toml +13 -1
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/analysis/policy.py +151 -5
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/captures/__init__.py +3 -2
- evalshift-1.3.0/src/evalshift_cli/captures/remote.py +432 -0
- evalshift-1.3.0/src/evalshift_cli/captures/store_uri.py +97 -0
- evalshift-1.3.0/src/evalshift_cli/captures/stores/__init__.py +22 -0
- evalshift-1.3.0/src/evalshift_cli/captures/stores/azure.py +60 -0
- evalshift-1.3.0/src/evalshift_cli/captures/stores/gcs.py +53 -0
- evalshift-1.3.0/src/evalshift_cli/captures/stores/s3.py +56 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/cli/commands/_scaffold.py +6 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/cli/commands/capture.py +243 -12
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/cli/commands/doctor.py +48 -3
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/cli/commands/evaluate.py +140 -2
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/config/models.py +214 -2
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/evaluators/failures.py +6 -0
- evalshift-1.3.0/src/evalshift_cli/evaluators/invariants.py +240 -0
- evalshift-1.3.0/src/evalshift_cli/evaluators/trace_invariants.py +236 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/hosted/bundle.py +47 -1
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/hosted/bundle_manifest.schema.json +122 -1
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/hosted/push.py +1 -1
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/insights/facts.py +2 -2
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/reports/html.py +17 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/reports/json.py +151 -16
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/reports/templates/report.css +2 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/reports/templates/report.html.j2 +32 -2
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/integration/test_tool_pipeline.py +93 -0
- evalshift-1.3.0/tests/unit/fake_remote_store.py +110 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_bundle_shape.py +199 -1
- evalshift-1.3.0/tests/unit/test_captures_remote.py +362 -0
- evalshift-1.3.0/tests/unit/test_captures_store_uri.py +106 -0
- evalshift-1.3.0/tests/unit/test_captures_stores.py +219 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_cli_capture.py +123 -1
- evalshift-1.3.0/tests/unit/test_cli_capture_remote.py +344 -0
- evalshift-1.3.0/tests/unit/test_config_captures.py +98 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_config_models.py +108 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_doctor.py +73 -1
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_evaluate_command.py +386 -2
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_init.py +16 -1
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_insight_facts.py +25 -1
- evalshift-1.3.0/tests/unit/test_invariants.py +218 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_policy.py +262 -1
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_reports.py +283 -0
- evalshift-1.3.0/tests/unit/test_trace_invariants.py +200 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/.gitignore +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/CLAUDE.md +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/LICENSE +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/docs/changelog.md +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/docs/conversations.md +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/docs/github-action.md +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/examples/agent/README.md +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/examples/agent/evalshift.yaml +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/examples/agent/golden.jsonl +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/examples/agent/prompts.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/examples/agent/tools.yaml +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/examples/agent/toolsets/ad3f07832877239336d594c7c1626197b39b9eb4c178842304c8754e94323d67.json +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/examples/agent-traces/evalshift.yaml +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/examples/agent-traces/golden.jsonl +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/examples/agent-traces/source-traces.jsonl +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/examples/agent-traces/target-traces.jsonl +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/examples/capture-first/.gitignore +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/examples/capture-first/README.md +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/examples/capture-first/agent.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/examples/capture-first/evalshift.yaml +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/examples/simple/README.md +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/examples/simple/evalshift.yaml +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/examples/simple/golden.jsonl +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/examples/simple/prompts.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/scripts/smoke_live_tools.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/__init__.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/__main__.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/analysis/__init__.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/analysis/slicing.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/analysis/statistics.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/cache/__init__.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/cache/schema.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/cache/store.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/captures/models.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/captures/promote.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/captures/reader.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/captures/toolset.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/cli/__init__.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/cli/commands/__init__.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/cli/commands/_agents.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/cli/commands/_suites.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/cli/commands/analyze.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/cli/commands/bundle.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/cli/commands/cache.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/cli/commands/compare.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/cli/commands/debug_artifacts.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/cli/commands/diff.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/cli/commands/init.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/cli/commands/inspect.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/cli/commands/login.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/cli/commands/logout.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/cli/commands/push.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/cli/commands/replay.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/cli/commands/report.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/cli/commands/run.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/cli/commands/runs.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/cli/commands/test_call.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/cli/commands/traces.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/cli/commands/validate.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/cli/commands/whoami.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/cli/main.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/config/__init__.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/config/loader.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/evaluators/__init__.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/evaluators/agent_trace.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/evaluators/base.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/evaluators/llm_judge.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/evaluators/semantic.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/evaluators/structural.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/evaluators/tool_arguments.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/evaluators/tool_loader.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/evaluators/tool_models.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/evaluators/tool_parser.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/evaluators/tool_rounds.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/evaluators/tool_selection.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/evaluators/tool_trace_structure.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/hosted/__init__.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/hosted/client.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/hosted/credentials.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/hosted/trace_events.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/insights/__init__.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/insights/generator.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/insights/models.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/insights/stage.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/insights/templates.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/models/__init__.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/models/capabilities.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/models/client.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/models/deepseek.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/models/family.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/models/registry.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/parsers/__init__.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/parsers/base.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/parsers/manual.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/parsers/python_string.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/py.typed +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/reports/__init__.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/reports/economics.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/runner/__init__.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/runner/checkpoint.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/runner/generation.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/runner/models.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/runner/orchestrator.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/suite/__init__.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/suite/loader.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/suite/models.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/suite/tags.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/traces/__init__.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/traces/diff.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/traces/loader.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/traces/models.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/utils/__init__.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/utils/ci_pin.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/utils/cost.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/src/evalshift_cli/utils/templating.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/__init__.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/conftest.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/integration/__init__.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/integration/fixtures/validate_missing_var/evalshift.yaml +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/integration/fixtures/validate_missing_var/golden.jsonl +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/integration/fixtures/validate_non_literal/evalshift.yaml +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/integration/fixtures/validate_non_literal/golden.jsonl +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/integration/fixtures/validate_non_literal/prompts.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/integration/fixtures/validate_ok/evalshift.yaml +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/integration/fixtures/validate_ok/golden.jsonl +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/integration/fixtures/validate_ok/prompts.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/integration/replay_client.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/integration/test_agent_trace_pipeline.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/integration/test_capture_to_verdict.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/integration/test_conversation_pipeline.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/integration/test_multi_round_replay.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/integration/test_scoring_semantics.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/integration/test_simple_example.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/integration/test_validate_command.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/scoring_fixtures.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/__init__.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/conftest.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/fixtures/captures/multi_round_tools.json +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/fixtures/scoring/project_insights_pairs.jsonl +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/fixtures/tool_responses/anthropic/parallel_tool_calls.json +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/fixtures/tool_responses/anthropic/refusal.json +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/fixtures/tool_responses/anthropic/single_tool_call.json +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/fixtures/tool_responses/anthropic/text_only.json +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/fixtures/tool_responses/anthropic/tool_call_with_text.json +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/fixtures/tool_responses/deepseek/single_tool_call.json +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/fixtures/tool_responses/gemini/single_tool_call.json +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/fixtures/tool_responses/gemini/text_only.json +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/fixtures/tool_responses/openai/parallel_tool_calls.json +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/fixtures/tool_responses/openai/refusal.json +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/fixtures/tool_responses/openai/single_tool_call.json +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/fixtures/tool_responses/openai/text_only.json +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/insights_factories.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/suite_examples.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_agent_trace_evaluator.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_agents.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_analysis.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_analyze_command.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_cache.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_captures_models.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_captures_promote.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_captures_reader.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_captures_toolset.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_checkpoint.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_ci_pin.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_cli_runs_clean.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_cli_smoke.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_compare_alias.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_compare_command.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_compare_suite_name.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_config_loader.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_config_suites.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_cost.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_debug_commands.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_deepseek.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_deferred_warnings.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_docs_currency.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_dropped_params.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_economics.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_evaluators.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_failures.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_hosted_cli.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_hosted_client.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_insight_generator.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_insights_stage.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_litellm_log_dedupe.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_litellm_log_stream.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_model_capabilities.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_model_client.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_model_family.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_model_registry.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_orchestrator.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_parsers.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_push_server_id.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_push_validation.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_reserved_slice_name.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_run_command.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_run_determinism.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_run_retention.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_run_suite_name.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_runner_generation.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_runner_models.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_suite_loader.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_suite_models.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_suites_helpers.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_templating.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_test_call_command.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_tool_arguments.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_tool_loader.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_tool_models.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_tool_parser.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_tool_rounds.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_tool_selection.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_tool_trace_structure.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_trace_diff.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_trace_events.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_trace_import_command.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_trace_models.py +0 -0
- {evalshift-1.2.2 → evalshift-1.3.0}/tests/unit/test_version.py +0 -0
|
@@ -7,6 +7,108 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.3.0] - 2026-10-09
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `captures.store` in `evalshift.yaml`: name the object store the capture SDK
|
|
15
|
+
ships captures to — `s3://<bucket>/<prefix>`, `gs://<bucket>/<prefix>` or
|
|
16
|
+
`az://<account>/<container>/<prefix>` — and `capture sync` and `capture list`
|
|
17
|
+
mirror new captures and toolset sidecars from it into `.evalshift/` before
|
|
18
|
+
they run, with no flags. Only objects missing locally are downloaded
|
|
19
|
+
(captures are immutable, toolsets content-addressed), captures already
|
|
20
|
+
promoted are skipped so `capture clean` never causes a re-download, and
|
|
21
|
+
every file is written atomically. New `capture fetch` does only that step;
|
|
22
|
+
`--since 30m|24h|7d|<ISO>` limits a fetch to recently written captures and
|
|
23
|
+
`--offline` skips it. Keys from a bucket are validated against the SDK's
|
|
24
|
+
layout and anything else is ignored as malformed — including a suite
|
|
25
|
+
directory containing `:`, `/`, `\`, NUL or `..`, or starting or ending with
|
|
26
|
+
`.` or a space, and a key with a trailing newline — and no destination can
|
|
27
|
+
land outside `.evalshift/`; folder-marker keys ending in `/` (the S3
|
|
28
|
+
console's "Create folder", ADLS directories) are skipped without being
|
|
29
|
+
counted. A list or download failure exits 1 with a hint naming the
|
|
30
|
+
provider's credential chain (`aws sso login` / `AWS_PROFILE` / the CI OIDC
|
|
31
|
+
role, `gcloud auth application-default login`, `az login`); a local write
|
|
32
|
+
failure names the file and asks you to check disk space and permissions on
|
|
33
|
+
its directory instead. Extras:
|
|
34
|
+
`evalshift[s3]`, `[gcs]`, `[azure]` (azure-storage-blob and azure-identity);
|
|
35
|
+
a missing one exits 1 naming the `pip install` to run. Credentials come from
|
|
36
|
+
each provider's default chain and are rejected in the URI: a value with `@`
|
|
37
|
+
or `?` fails to load, naming the accepted forms. No rejected value is echoed
|
|
38
|
+
back (it may be a pasted key or connection string); at most its scheme is
|
|
39
|
+
named.
|
|
40
|
+
`capture list --json` keeps stdout pure JSON (the fetch line and warnings
|
|
41
|
+
go to stderr). `doctor` reports whether the store is installed (`fail`, exit 1,
|
|
42
|
+
when the extra is missing) and reachable (`warn` when it cannot be listed).
|
|
43
|
+
Without `captures.store` nothing changes; a `sync` or `list` whose
|
|
44
|
+
`evalshift.yaml` cannot be read or does not validate skips the fetch,
|
|
45
|
+
warning only when the file has a top-level `captures:` key. Older CLIs
|
|
46
|
+
reject the `captures:` key, so bump the CLI (and a pinned CI action's
|
|
47
|
+
`evalshift-version`) before adding it.
|
|
48
|
+
- `evaluators.trace_invariants`: hand-written rules over tool-call traces —
|
|
49
|
+
`forbidden`, `required`, `order`, `call_count` (`min_calls` and/or
|
|
50
|
+
`max_calls` per tool; equal bounds = an exact count) and
|
|
51
|
+
`arguments` (JSON Schema) — checked on both sides of every pair, on replayed
|
|
52
|
+
traces or (`traces: imported`) on imported ones. A rule the target breaks
|
|
53
|
+
counts whatever the source did. Its `applies_to` is enforced: a prompt
|
|
54
|
+
outside the globs is never checked, and `evaluate` warns, naming the entry
|
|
55
|
+
and its `applies_to`, when the globs matched nothing in the run (so a typo
|
|
56
|
+
is not a silent clean run). A `traces: imported` entry on a run without
|
|
57
|
+
`traces.jsonl` fails `evaluate` with an error naming that family.
|
|
58
|
+
- `migration_policy.max_invariant_violations` (default `0`, per-slice
|
|
59
|
+
overridable): distinct examples on which the target broke a blocking trace
|
|
60
|
+
rule, so an example that broke rules in two entries counts once. A breach
|
|
61
|
+
fails the run even when the suite is too small for statistics, and a
|
|
62
|
+
slice whose own budget breaches reads `fail` however few comparisons it has.
|
|
63
|
+
`analyze` adds a recommendation when the source broke the same rules the
|
|
64
|
+
target did: review the rule with its owner rather than raising the budget.
|
|
65
|
+
Pushed bundles' `decision.policy` now always carries the field; hosted
|
|
66
|
+
EvalShift accepts and stores it since 2026-10-08 (it never re-evaluates it —
|
|
67
|
+
a run that carries its own policy is answered with the CLI's decision).
|
|
68
|
+
- The HTML report lists every broken trace rule in a "Trace rules broken"
|
|
69
|
+
panel, with its owner and whether the source broke it too — advisory
|
|
70
|
+
(`blocking: false`) entries included, though only blocking ones count
|
|
71
|
+
toward the budget; those rows are tagged "advisory". `report.json` carries
|
|
72
|
+
the same rows under a new top-level `invariant_violations` key, each with
|
|
73
|
+
the entry's `blocking` flag. The "Overall, by evaluator" table tags every
|
|
74
|
+
advisory (`blocking: false`) evaluator's row the same way and says what a
|
|
75
|
+
trace-rule row measures, and the "Top
|
|
76
|
+
regression causes" panel counts `INVARIANT_VIOLATION` as the budget does —
|
|
77
|
+
distinct examples with a blocking target violation — so the narrative never
|
|
78
|
+
quotes a figure the verdict did not use.
|
|
79
|
+
- `evalshift init` scaffolds `max_invariant_violations: 0` in every
|
|
80
|
+
`--profile` policy block, beside the other budgets. Older CLIs reject the
|
|
81
|
+
field, so bump the CI action's `evalshift-version` pin to this release
|
|
82
|
+
before committing a freshly scaffolded config.
|
|
83
|
+
- `capture sync` keeps a managed entry's hand-written
|
|
84
|
+
`evaluators.trace_invariants` block when it regenerates the entry. The
|
|
85
|
+
marker comment still says hand edits are overwritten; this block is the one
|
|
86
|
+
exception.
|
|
87
|
+
- `push` now uploads each score row's one-line `explanation` and, for
|
|
88
|
+
`trace_invariants` rows, the rules each side broke (`violations.source` /
|
|
89
|
+
`violations.target`: rule id, type, tool, round, detail, and sample on
|
|
90
|
+
repeated-sampling runs). Hosted EvalShift shows them on the example detail
|
|
91
|
+
and on share links instead of only the violation count. `rules_checked`,
|
|
92
|
+
`owner` and `failure_categories` are not repeated on the score row (they
|
|
93
|
+
already ship in `evaluator_config` / `decision`). Requires a hosted service
|
|
94
|
+
that accepts the two fields (hosted EvalShift was updated before this
|
|
95
|
+
release); nothing else in the bundle changed.
|
|
96
|
+
|
|
97
|
+
### Changed
|
|
98
|
+
|
|
99
|
+
- The report's per-prompt Executive summary — and the header's mean score Δ,
|
|
100
|
+
which averages its rows — now picks each prompt's worst severity with ties
|
|
101
|
+
broken toward the most negative effect size (the order the hero panel and
|
|
102
|
+
the narrative already used) instead of whichever evaluator `analyze` listed
|
|
103
|
+
first, and skips advisory (`blocking: false`) evaluators while the prompt
|
|
104
|
+
has a gating one. An advisory rule the target satisfied by calling no tools
|
|
105
|
+
at all could otherwise read as the prompt's "+9% improved" above a failed
|
|
106
|
+
verdict.
|
|
107
|
+
- Upgrading moves the resume `config_hash`: the new config fields are part of
|
|
108
|
+
every config's canonical dump, so a run left in progress by an older CLI
|
|
109
|
+
cannot be `--resume`d after upgrading (it fails with the usual config-hash
|
|
110
|
+
mismatch). Start a fresh run; cached responses are reused.
|
|
111
|
+
|
|
10
112
|
## [1.2.2] - 2026-10-05
|
|
11
113
|
|
|
12
114
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: evalshift
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Run your prompts on two LLMs and find out, with statistical confidence, what regressed.
|
|
5
5
|
Project-URL: Homepage, https://github.com/evalshift/evalshift-cli
|
|
6
6
|
Project-URL: Documentation, https://github.com/evalshift/evalshift-cli#readme
|
|
@@ -41,7 +41,14 @@ Requires-Dist: scipy>=1.12
|
|
|
41
41
|
Requires-Dist: sqlalchemy>=2.0
|
|
42
42
|
Requires-Dist: tiktoken>=0.7
|
|
43
43
|
Requires-Dist: typer>=0.12
|
|
44
|
+
Provides-Extra: azure
|
|
45
|
+
Requires-Dist: azure-identity>=1.15; extra == 'azure'
|
|
46
|
+
Requires-Dist: azure-storage-blob>=12.19; extra == 'azure'
|
|
44
47
|
Provides-Extra: dev
|
|
48
|
+
Requires-Dist: azure-identity>=1.15; extra == 'dev'
|
|
49
|
+
Requires-Dist: azure-storage-blob>=12.19; extra == 'dev'
|
|
50
|
+
Requires-Dist: boto3>=1.34; extra == 'dev'
|
|
51
|
+
Requires-Dist: google-cloud-storage>=2.14; extra == 'dev'
|
|
45
52
|
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
46
53
|
Requires-Dist: pre-commit>=3.7; extra == 'dev'
|
|
47
54
|
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
@@ -51,6 +58,10 @@ Requires-Dist: respx>=0.21; extra == 'dev'
|
|
|
51
58
|
Requires-Dist: ruff==0.16.4; extra == 'dev'
|
|
52
59
|
Requires-Dist: types-jsonschema>=4.22; extra == 'dev'
|
|
53
60
|
Requires-Dist: types-pyyaml>=6.0; extra == 'dev'
|
|
61
|
+
Provides-Extra: gcs
|
|
62
|
+
Requires-Dist: google-cloud-storage>=2.14; extra == 'gcs'
|
|
63
|
+
Provides-Extra: s3
|
|
64
|
+
Requires-Dist: boto3>=1.34; extra == 's3'
|
|
54
65
|
Description-Content-Type: text/markdown
|
|
55
66
|
|
|
56
67
|
# EvalShift
|
|
@@ -318,7 +329,11 @@ shape, `fail-on` modes, and baseline behavior.
|
|
|
318
329
|
|
|
319
330
|
Migrating an agent (a prompt that uses tools)? EvalShift detects
|
|
320
331
|
regressions in *which* tools the new model calls, *what* arguments it
|
|
321
|
-
passes, and in what order and parallelism within a response.
|
|
332
|
+
passes, and in what order and parallelism within a response. Hand-written
|
|
333
|
+
`trace_invariants` rules (auth before a charge, a tool that must never be
|
|
334
|
+
called, at most one charge, argument bounds) hold *both* models to a
|
|
335
|
+
contract whatever the source did, gated by
|
|
336
|
+
`migration_policy.max_invariant_violations`. By default
|
|
322
337
|
each example is one model call scored against the first recorded round;
|
|
323
338
|
promote with `--rounds all` to replay every recorded round teacher-forced,
|
|
324
339
|
with the recorded tool results fed back and each round scored on its own.
|
|
@@ -344,7 +359,10 @@ assets, works offline) has:
|
|
|
344
359
|
|
|
345
360
|
* **Migration verdict** — the policy decision up top: which budgets failed, the
|
|
346
361
|
top regression causes, and the recommendation.
|
|
347
|
-
* **Executive summary** — one row per prompt with a severity badge
|
|
362
|
+
* **Executive summary** — one row per prompt with a severity badge, drawn from
|
|
363
|
+
blocking evaluators (advisory ones only when nothing else scored the prompt).
|
|
364
|
+
* **Trace rules broken** — every hand-written trace rule the target broke,
|
|
365
|
+
with its owner and whether the source broke it too; advisory rows are tagged.
|
|
348
366
|
* **What changed, in plain language** — the verdict, the economics and the
|
|
349
367
|
behavioural drift explained by `defaults.insights_model`. Every figure in it
|
|
350
368
|
is copied from the computed statistics, never generated. Needs a provider
|
|
@@ -358,7 +376,8 @@ assets, works offline) has:
|
|
|
358
376
|
|
|
359
377
|
Your prompts and suite stay local for `doctor`, `run`, `evaluate`, `analyze`,
|
|
360
378
|
and `report`. The only outbound calls in local mode are to the LLM providers you
|
|
361
|
-
configure — any provider LiteLLM supports, called with your own API keys
|
|
379
|
+
configure — any provider LiteLLM supports, called with your own API keys — and,
|
|
380
|
+
if you set `captures.store`, reads from your own capture bucket.
|
|
362
381
|
Anthropic, DeepSeek, Google and OpenAI ids additionally get a curated pricing and
|
|
363
382
|
capability entry; everything else is passed through with the provider inferred
|
|
364
383
|
from the id.
|
|
@@ -263,7 +263,11 @@ shape, `fail-on` modes, and baseline behavior.
|
|
|
263
263
|
|
|
264
264
|
Migrating an agent (a prompt that uses tools)? EvalShift detects
|
|
265
265
|
regressions in *which* tools the new model calls, *what* arguments it
|
|
266
|
-
passes, and in what order and parallelism within a response.
|
|
266
|
+
passes, and in what order and parallelism within a response. Hand-written
|
|
267
|
+
`trace_invariants` rules (auth before a charge, a tool that must never be
|
|
268
|
+
called, at most one charge, argument bounds) hold *both* models to a
|
|
269
|
+
contract whatever the source did, gated by
|
|
270
|
+
`migration_policy.max_invariant_violations`. By default
|
|
267
271
|
each example is one model call scored against the first recorded round;
|
|
268
272
|
promote with `--rounds all` to replay every recorded round teacher-forced,
|
|
269
273
|
with the recorded tool results fed back and each round scored on its own.
|
|
@@ -289,7 +293,10 @@ assets, works offline) has:
|
|
|
289
293
|
|
|
290
294
|
* **Migration verdict** — the policy decision up top: which budgets failed, the
|
|
291
295
|
top regression causes, and the recommendation.
|
|
292
|
-
* **Executive summary** — one row per prompt with a severity badge
|
|
296
|
+
* **Executive summary** — one row per prompt with a severity badge, drawn from
|
|
297
|
+
blocking evaluators (advisory ones only when nothing else scored the prompt).
|
|
298
|
+
* **Trace rules broken** — every hand-written trace rule the target broke,
|
|
299
|
+
with its owner and whether the source broke it too; advisory rows are tagged.
|
|
293
300
|
* **What changed, in plain language** — the verdict, the economics and the
|
|
294
301
|
behavioural drift explained by `defaults.insights_model`. Every figure in it
|
|
295
302
|
is copied from the computed statistics, never generated. Needs a provider
|
|
@@ -303,7 +310,8 @@ assets, works offline) has:
|
|
|
303
310
|
|
|
304
311
|
Your prompts and suite stay local for `doctor`, `run`, `evaluate`, `analyze`,
|
|
305
312
|
and `report`. The only outbound calls in local mode are to the LLM providers you
|
|
306
|
-
configure — any provider LiteLLM supports, called with your own API keys
|
|
313
|
+
configure — any provider LiteLLM supports, called with your own API keys — and,
|
|
314
|
+
if you set `captures.store`, reads from your own capture bucket.
|
|
307
315
|
Anthropic, DeepSeek, Google and OpenAI ids additionally get a curated pricing and
|
|
308
316
|
capability entry; everything else is passed through with the provider inferred
|
|
309
317
|
from the id.
|
|
@@ -22,8 +22,9 @@ The killer scenario it catches:
|
|
|
22
22
|
|
|
23
23
|
* **`ToolTrace` data model**: provider-agnostic, populated from
|
|
24
24
|
Anthropic / OpenAI / Gemini responses.
|
|
25
|
-
* **
|
|
26
|
-
`tool_trace_structure
|
|
25
|
+
* **Four new evaluators**: `tool_selection`, `tool_arguments`,
|
|
26
|
+
`tool_trace_structure`, and hand-written `trace_invariants` (both sides
|
|
27
|
+
judged against your rules, not against each other).
|
|
27
28
|
* **Suite extension**: optional `expected_tools`, `expected_tool_count`,
|
|
28
29
|
`expected_no_tools`, `expected_parallel` per example, plus a required
|
|
29
30
|
`toolset_ref` or inline `tools` — see [Suite ground truth](#suite-ground-truth).
|
|
@@ -85,7 +86,9 @@ defaults:
|
|
|
85
86
|
|
|
86
87
|
evaluators:
|
|
87
88
|
# Top-level: what every suite is scored with. Tool evaluators do not belong
|
|
88
|
-
# here — `capture sync` writes them per suite (see below).
|
|
89
|
+
# here — `capture sync` writes them per suite (see below). Hand-written
|
|
90
|
+
# `trace_invariants` are the exception: top level is fine, and sync keeps
|
|
91
|
+
# them on the managed entries it rewrites.
|
|
89
92
|
semantic:
|
|
90
93
|
embedding_model: gemini/gemini-embedding-001
|
|
91
94
|
blocking: false
|
|
@@ -123,7 +126,11 @@ inherited. See
|
|
|
123
126
|
Hand edits inside the markers are regenerated away on the next sync. To keep
|
|
124
127
|
them — a `severity_floor: high` on `routing`, a per-field strategy — set
|
|
125
128
|
`managed: false` on that suite's entry; sync then prints what it would have
|
|
126
|
-
written instead of writing it.
|
|
129
|
+
written instead of writing it. The one exception is a hand-written
|
|
130
|
+
`evaluators.trace_invariants` block: no capture can derive a team's rules, so
|
|
131
|
+
sync carries it into the regenerated entry verbatim, even though the marker
|
|
132
|
+
comment says hand edits are overwritten. See
|
|
133
|
+
[Configuration → `managed`](configuration.md#managed).
|
|
127
134
|
|
|
128
135
|
> **Note:** `structural.length` is intentionally **not** in the
|
|
129
136
|
> scaffolded config. Agent runs frequently produce empty `final_text`
|
|
@@ -437,6 +444,7 @@ together and `run` replays each one with its recorded history prefix. See
|
|
|
437
444
|
| `tool_selection` | You care about *which* tools fire (most common). |
|
|
438
445
|
| `tool_arguments` | You care about *what* the model passes to each tool. |
|
|
439
446
|
| `tool_trace_structure` | You care about call counts, parallelism, or refusals. |
|
|
447
|
+
| `trace_invariants` | A rule must hold whatever the source did (auth before a charge, at most one charge, argument bounds). |
|
|
440
448
|
|
|
441
449
|
You can run multiple at once. Each becomes an independent comparison
|
|
442
450
|
in `analysis.json`, with the existing Benjamini-Hochberg correction
|
|
@@ -460,6 +468,18 @@ It scores the same recall, counting duplicates (two expected `archive_project`
|
|
|
460
468
|
calls need two actual ones) and ignoring extra calls, without penalising a
|
|
461
469
|
permutation. Call counts are `tool_trace_structure`'s job, not this one's.
|
|
462
470
|
|
|
471
|
+
When an order genuinely matters — authenticate before charging — don't rely on
|
|
472
|
+
the recording's order to say so. Write it as an `order` rule under
|
|
473
|
+
[`trace_invariants`](configuration.md#evaluatorstrace_invariants), which checks
|
|
474
|
+
both models against the rule rather than against each other, and keep
|
|
475
|
+
`expected_set` for the fan-out.
|
|
476
|
+
|
|
477
|
+
`trace_invariants` is also the rule-based counterpart to `agent_trace`'s
|
|
478
|
+
`dangerous_tools`: that one flags a target that made *more* dangerous calls
|
|
479
|
+
than the source, so a dangerous call both made passes; a `forbidden` or
|
|
480
|
+
`call_count` rule fails the target whatever the source did. It reads replayed
|
|
481
|
+
traces by default and imported ones with `traces: imported`.
|
|
482
|
+
|
|
463
483
|
### Scoring free-text arguments
|
|
464
484
|
|
|
465
485
|
Free-text tool arguments — search queries, titles, descriptions — will never
|
|
@@ -583,6 +603,13 @@ identically is not a ✗ — it is the same pair before and after, and what
|
|
|
583
603
|
is wrong is the ground truth. Look for **Ground truth missed by both**
|
|
584
604
|
in the evaluator table when that happens.
|
|
585
605
|
|
|
606
|
+
A **Trace rules broken** panel appears when the target broke a hand-written
|
|
607
|
+
[`trace_invariants`](configuration.md#evaluatorstrace_invariants) rule: every
|
|
608
|
+
broken rule with its owner and whether the source broke it too, whatever the
|
|
609
|
+
delta, advisory (`blocking: false`) rows tagged **advisory**. `report.json`
|
|
610
|
+
carries the same rows under `invariant_violations`, and the evaluator table
|
|
611
|
+
tags an advisory entry's row the same way.
|
|
612
|
+
|
|
586
613
|
## Troubleshooting
|
|
587
614
|
|
|
588
615
|
* **A suite reports tool evaluator scores you didn't expect on some rows** —
|
|
@@ -21,6 +21,7 @@ evaluators: {...} # optional (but at least one is needed for `evaluate`)
|
|
|
21
21
|
suites: {...} # optional named suites for `run --suite-name`, each with
|
|
22
22
|
# its own optional `evaluators:` block
|
|
23
23
|
retention: {...} # optional run-history pruning policy
|
|
24
|
+
captures: {...} # optional, see below
|
|
24
25
|
```
|
|
25
26
|
|
|
26
27
|
Unknown keys are rejected (`extra: forbid` everywhere) so typos fail
|
|
@@ -126,6 +127,7 @@ migration_policy:
|
|
|
126
127
|
min_equivalence_rate: 0.75
|
|
127
128
|
max_tool_argument_drift: 0.20
|
|
128
129
|
max_tool_divergence: 0.20 # share of pairs where the target routed elsewhere
|
|
130
|
+
max_invariant_violations: 0 # examples where the target broke a blocking trace rule
|
|
129
131
|
tool_argument_drift_floor: 0.9 # below this a call counts as drifted
|
|
130
132
|
max_cost_increase: 0.30 # target may cost up to 30% more
|
|
131
133
|
max_latency_increase: 0.30 # …and be up to 30% slower
|
|
@@ -196,6 +198,25 @@ means the target called a tool the source did not, or skipped one it did.
|
|
|
196
198
|
On a teacher-forced multi-round replay the row's score is the mean over the
|
|
197
199
|
replayed rounds, so an example counts as diverged if **any** round diverged.
|
|
198
200
|
|
|
201
|
+
`max_invariant_violations` is a **count**, not a rate: the number of examples
|
|
202
|
+
on which the target broke at least one rule of a blocking
|
|
203
|
+
[`trace_invariants`](#evaluatorstrace_invariants) evaluator, judged against the
|
|
204
|
+
rules alone — a rule the source broke too still counts against the target. The
|
|
205
|
+
default `0` fails on the first broken rule. Examples are counted distinctly by
|
|
206
|
+
`(prompt_id, example_id)`: every `trace_invariants` entry writes its own row, and
|
|
207
|
+
an example that broke rules in two entries counts once, over a denominator of
|
|
208
|
+
the distinct examples with a blocking row. A row averaged over
|
|
209
|
+
`samples_per_example` repeats counts when any one sample broke a rule. Because
|
|
210
|
+
a rule is an assertion the team wrote down rather than a statistic, a breach is
|
|
211
|
+
always conclusive: no interval softens it, it carries no `1/n` granularity
|
|
212
|
+
warning, and it turns `inconclusive` and `conditional_pass` into `fail` even
|
|
213
|
+
when the suite is too small for the paired tests. The same holds per slice: a
|
|
214
|
+
slice whose own `max_invariant_violations` breaches reads `fail` however few
|
|
215
|
+
comparisons it has. The budget's row appears only in scopes that scored at least
|
|
216
|
+
one blocking `trace_invariants` row, so a project with no trace rules (or only
|
|
217
|
+
advisory ones) sees no new budget row. It is overridable per slice like the
|
|
218
|
+
others.
|
|
219
|
+
|
|
199
220
|
A `tool_selection.conformance` row where **both** models missed the recorded
|
|
200
221
|
ground truth by the same margin is excluded from every policy rate: its zero
|
|
201
222
|
delta is a shared failure, not evidence the migration is safe. Such rows are
|
|
@@ -263,6 +284,16 @@ Two behaviours to know:
|
|
|
263
284
|
`conclusive: false` on a scope that scored zero records — their
|
|
264
285
|
`0/0` default looks clean but measures nothing.
|
|
265
286
|
|
|
287
|
+
* **`max_invariant_violations` is conclusive by construction.** It counts
|
|
288
|
+
distinct examples, so it has no interval and no "too small to be sure": a breach
|
|
289
|
+
fails the run (or the slice) outright, overriding `inconclusive` and
|
|
290
|
+
`conditional_pass`, and the verdict's `reason` names the scope and the
|
|
291
|
+
count. Only blocking `trace_invariants` evaluators count toward it. When the
|
|
292
|
+
source broke the same rule on the same example, `recommendations` adds a
|
|
293
|
+
line saying so — not an exemption, but a hint that either both models break
|
|
294
|
+
the rule or the rule no longer matches the toolset, which its owner should
|
|
295
|
+
review.
|
|
296
|
+
|
|
266
297
|
* **`fail_on_dropped_params` gates constraints, not scores.** Default
|
|
267
298
|
`false`. A promoted capture can pin generation parameters
|
|
268
299
|
(`response_format`, `tool_choice`, `parallel_tool_calls`, `top_p`, a
|
|
@@ -312,6 +343,8 @@ the snapshot each run pushed. `policy` is `null` when no `migration_policy` is
|
|
|
312
343
|
configured, and on a `migration_decision.json` written before this field
|
|
313
344
|
existed. See [What `push` sends](hosted.md#what-push-sends-block-by-block) for
|
|
314
345
|
the upload contract and the notices `push` prints around a policy-less run.
|
|
346
|
+
Because every budget is resolved, the snapshot carries `max_invariant_violations`
|
|
347
|
+
(at its default `0`) even for a config that never mentions trace rules.
|
|
315
348
|
|
|
316
349
|
## `prompts`
|
|
317
350
|
|
|
@@ -404,8 +437,9 @@ bad generations fall back to deterministic templated prose.
|
|
|
404
437
|
|
|
405
438
|
## `evaluators`
|
|
406
439
|
|
|
407
|
-
|
|
408
|
-
`tool_arguments`, `tool_trace_structure`, `agent_trace` and
|
|
440
|
+
Eight sub-keys, all optional — `structural`, `semantic`, `tool_selection`,
|
|
441
|
+
`tool_arguments`, `tool_trace_structure`, `agent_trace`, `trace_invariants` and
|
|
442
|
+
`llm_judge`, each
|
|
409
443
|
documented below. **At least one evaluator must be configured for
|
|
410
444
|
`evalshift evaluate` to do anything.**
|
|
411
445
|
|
|
@@ -471,7 +505,7 @@ A list. Each entry has:
|
|
|
471
505
|
| `name` | string | (required) | Identifier surfaced in reports. |
|
|
472
506
|
| `conformance` | enum | `expected` | Ground-truth axis: `expected` / `expected_set` / `off`. `expected_set` is `expected` made order-insensitive: multiset recall of `example.expected_tools` names. Use it when the expected calls are a parallel fan-out whose order carries no meaning. |
|
|
473
507
|
| `divergence` | enum | `set` | Target-vs-source axis: `set` (Jaccard on tool names) / `exact` (sequence equality) / `first` (first call only) / `off`. |
|
|
474
|
-
| `applies_to` | list | `["*"]` | Glob list of prompt ids. |
|
|
508
|
+
| `applies_to` | list | `["*"]` | Glob list of prompt ids (accepted, not yet enforced — only [`trace_invariants`](#evaluatorstrace_invariants) enforces it). |
|
|
475
509
|
| `severity_floor` | enum | `null` | If set, surfaces in metadata so the analysis layer can floor severity. |
|
|
476
510
|
|
|
477
511
|
The two axes are independent and each writes **its own record**, under
|
|
@@ -502,7 +536,7 @@ deprecated, and a config still carrying one fails validation.
|
|
|
502
536
|
| Field | Type | Default | Description |
|
|
503
537
|
| ----------------------- | ------ | ------- | ----------- |
|
|
504
538
|
| `name` | string | (required) | Identifier. |
|
|
505
|
-
| `applies_to` | list | `["*"]` | Glob list. |
|
|
539
|
+
| `applies_to` | list | `["*"]` | Glob list of prompt ids (accepted, not yet enforced — only [`trace_invariants`](#evaluatorstrace_invariants) enforces it). |
|
|
506
540
|
| `against` | enum | `source` | What arguments are compared to: `source` (drift from the source model) or `expected` (correctness against `expected_tools[].arguments`, scored on both sides). |
|
|
507
541
|
| `strategies` | dict | `{}` | Per-field strategy overrides (`exact`/`subset`/`numeric`/`semantic`/`auto`). |
|
|
508
542
|
| `default_strategy` | enum | `auto` | Strategy for fields `strategies` does not name. `auto` is the ladder below; `exact` restores byte-equality scoring. |
|
|
@@ -578,7 +612,7 @@ unaffected: it counts calls whose *target* score fell below
|
|
|
578
612
|
| Field | Type | Default | Description |
|
|
579
613
|
| --------------------- | ------ | ------- | ----------- |
|
|
580
614
|
| `name` | string | (required) | Identifier. |
|
|
581
|
-
| `applies_to` | list | `["*"]` | Glob list. |
|
|
615
|
+
| `applies_to` | list | `["*"]` | Glob list of prompt ids (accepted, not yet enforced — only [`trace_invariants`](#evaluatorstrace_invariants) enforces it). |
|
|
582
616
|
| `check_call_count` | bool | `true` | Score the number of tool calls. |
|
|
583
617
|
| `check_parallelism` | bool | `true` | Score parallel-vs-sequential alignment. |
|
|
584
618
|
| `check_refusals` | bool | `true` | Score refusal alignment; mismatches force `severity_floor: high`. |
|
|
@@ -593,7 +627,7 @@ the normal `raw.jsonl` model-call artifact.
|
|
|
593
627
|
| Field | Type | Default | Description |
|
|
594
628
|
| ---------------------------- | ------ | ------- | ----------- |
|
|
595
629
|
| `name` | string | required | Identifier surfaced in scores and reports. |
|
|
596
|
-
| `applies_to` | list | `["*"]` | Glob list of prompt ids. |
|
|
630
|
+
| `applies_to` | list | `["*"]` | Glob list of prompt ids (accepted, not yet enforced — only [`trace_invariants`](#evaluatorstrace_invariants) enforces it). |
|
|
597
631
|
| `check_tool_order` | bool | `true` | Compare source and target tool-call order. |
|
|
598
632
|
| `check_arguments` | bool | `true` | Compare arguments for same-name matched tool calls. |
|
|
599
633
|
| `check_missing_verification` | bool | `true` | Check dangerous tools have an earlier verification tool. |
|
|
@@ -610,6 +644,136 @@ evaluators:
|
|
|
610
644
|
dangerous_tools: ["issue_refund"]
|
|
611
645
|
```
|
|
612
646
|
|
|
647
|
+
### `evaluators.trace_invariants`
|
|
648
|
+
|
|
649
|
+
A list. Each entry is a set of hand-written rules that every in-scope tool-call
|
|
650
|
+
trace must satisfy, checked on **both** sides of every pair and judged against
|
|
651
|
+
the rules, not against the source: a rule the source also broke still counts
|
|
652
|
+
against the target. See [Evaluators → Trace invariants](evaluators.md#trace-invariants)
|
|
653
|
+
for when to reach for it.
|
|
654
|
+
|
|
655
|
+
Write it in the top-level `evaluators:` block (outside the `capture sync`
|
|
656
|
+
managed region) or in a suite's own `evaluators:` override. `capture sync`
|
|
657
|
+
keeps a managed suite entry's `trace_invariants` when it regenerates that
|
|
658
|
+
entry — see [`managed`](#managed).
|
|
659
|
+
|
|
660
|
+
```yaml
|
|
661
|
+
evaluators:
|
|
662
|
+
trace_invariants:
|
|
663
|
+
- name: payments_contract
|
|
664
|
+
owner: "@payments-team" # optional; echoed onto every record and the report
|
|
665
|
+
applies_to: ["checkout-*"] # prompt-id globs; default ["*"]
|
|
666
|
+
blocking: true # default; false = reported, never gates
|
|
667
|
+
traces: replayed # default; `imported` checks `evalshift traces import` traces
|
|
668
|
+
rules:
|
|
669
|
+
- id: auth-before-charge
|
|
670
|
+
type: order
|
|
671
|
+
before: authenticate
|
|
672
|
+
after: charge_card
|
|
673
|
+
- id: no-legacy-refund
|
|
674
|
+
type: forbidden
|
|
675
|
+
tools: [refund_v1]
|
|
676
|
+
- id: one-charge
|
|
677
|
+
type: call_count
|
|
678
|
+
tool: charge_card
|
|
679
|
+
max_calls: 1 # min_calls, max_calls or both; both equal = exact
|
|
680
|
+
- id: must-book
|
|
681
|
+
type: required
|
|
682
|
+
tools: [create_booking]
|
|
683
|
+
- id: sane-amount
|
|
684
|
+
type: arguments
|
|
685
|
+
tool: charge_card
|
|
686
|
+
json_schema:
|
|
687
|
+
type: object
|
|
688
|
+
required: [amount]
|
|
689
|
+
properties:
|
|
690
|
+
amount: {type: number, minimum: 0.01, maximum: 5000}
|
|
691
|
+
|
|
692
|
+
migration_policy:
|
|
693
|
+
max_invariant_violations: 0 # default; examples where the target broke any blocking rule
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
| Field | Type | Default | Description |
|
|
697
|
+
| ------------ | ------ | ------------ | ----------- |
|
|
698
|
+
| `name` | string | (required) | Identifier surfaced in scores and reports. |
|
|
699
|
+
| `applies_to` | list | `["*"]` | Glob list of prompt ids. **Enforced here**, unlike on the other families: a prompt outside these globs is never checked, gets no row and is never counted. If the globs match no prompt in a run (with `traces: imported`, no imported trace pair), `evaluate` prints a warning naming the entry and its `applies_to`, because none of its rules were checked (`compare` scores quietly and does not print it); matching at least one prompt stays silent. |
|
|
700
|
+
| `blocking` | bool | `true` | `true` counts target violations toward [`max_invariant_violations`](#migration_policy); `false` still checks and reports them, but never gates. |
|
|
701
|
+
| `traces` | string | `replayed` | `replayed` checks the tool calls `evalshift run` replayed (a pair whose replay recorded no tool trace gets no row). `imported` checks the traces brought in with [`evalshift traces import`](traces.md); `evaluate` fails on a run with no `traces.jsonl`, naming `trace_invariants (traces: imported)` in the error. |
|
|
702
|
+
| `owner` | string | `null` | Free text (a team or a handle). Echoed onto every record and into the report, so a violation names who to ask. Nothing enforces it. |
|
|
703
|
+
| `rules` | list | (required) | At least one rule. Every rule has an `id` (unique within the entry; it names violations in reports) and a `type`. |
|
|
704
|
+
|
|
705
|
+
The unit of checking is a **response**: one model turn, plus the tool calls
|
|
706
|
+
already in its **context** before it answered. On a replayed trace there is one
|
|
707
|
+
response per replayed round, and round *k*'s context is the tool calls on the
|
|
708
|
+
example's `history` assistant turns plus the *recorded* calls of the rounds the
|
|
709
|
+
teacher-forced replay fed back before it; the model's own earlier rounds are
|
|
710
|
+
never context, because the replay never feeds them back. An imported trace is
|
|
711
|
+
one response with an empty context — every call in it was the agent's own.
|
|
712
|
+
Context calls can never *be* violations, since the model under test did not
|
|
713
|
+
make them, but they can satisfy or count toward a rule:
|
|
714
|
+
|
|
715
|
+
| `type` | Fields | Violated when | Context role |
|
|
716
|
+
| ------------ | ------ | ------------- | ------------ |
|
|
717
|
+
| `forbidden` | `tools` (list) | the model calls any tool in `tools` (one violation per call) | none |
|
|
718
|
+
| `required` | `tools` (list) | some tool in `tools` is called in no response by the model | none: a call only in the context does not satisfy it. Use `call_count` `min_calls` when the conversation may already contain the call |
|
|
719
|
+
| `order` | `before`, `after` (two different tools) | the model calls `after` and no `before` precedes it, either in the context or earlier in the same response | a context `before` satisfies it |
|
|
720
|
+
| `call_count` | `tool`, `min_calls` and/or `max_calls` (at least one; `max_calls` ≥ `min_calls`) | `max_calls`: a model call to `tool` takes the running count past it, counting context calls first (one violation per excess call). `min_calls`: at the end of the last response, context calls plus the model's calls to `tool` number fewer than it (one violation). Both set and equal is exact cardinality | context calls count, toward both bounds |
|
|
721
|
+
| `arguments` | `tool`, `json_schema` | a model call to `tool` has arguments that fail `json_schema` (Draft 2020-12, itself validated when the config loads) | none |
|
|
722
|
+
|
|
723
|
+
"Earlier in the same response" means emitted earlier; imported traces are
|
|
724
|
+
ordered by `sequence_index`, so the two modes agree.
|
|
725
|
+
|
|
726
|
+
**Scoring.** Each side scores the share of rules it broke zero times —
|
|
727
|
+
`(rules − rules broken) / rules` — so a rule broken three times costs the same
|
|
728
|
+
as one broken once, and `delta = target − source`. The record's kind is
|
|
729
|
+
`trace_invariants`; its metadata carries `rules_checked`, `source_violations`
|
|
730
|
+
and `target_violations` (each a list of `{rule_id, rule_type, tool,
|
|
731
|
+
round_index, detail}`), `owner` when set, and the failure category
|
|
732
|
+
`INVARIANT_VIOLATION` when the target broke anything. The report's **Top
|
|
733
|
+
regression causes** panel (and `failure_categories` in
|
|
734
|
+
`migration_decision.json`) counts that category the way the budget does —
|
|
735
|
+
distinct examples on which the target broke a *blocking* rule — so an advisory
|
|
736
|
+
violation, or a second entry broken on the same example, never inflates it
|
|
737
|
+
past the figure the verdict used. With `samples_per_example` above 1 the
|
|
738
|
+
example's row keeps every scored sample's violations, each tagged with its
|
|
739
|
+
`sample` ordinal. The HTML report's **Trace rules broken** panel and
|
|
740
|
+
`report.json`'s `invariant_violations` list every rule the target broke,
|
|
741
|
+
whatever the delta — including those of advisory (`blocking: false`) entries,
|
|
742
|
+
which never count toward the budget — with the owner and whether the source
|
|
743
|
+
broke it too. Each row carries the entry's `blocking` flag, and the panel tags
|
|
744
|
+
`blocking: false` rows **advisory**. The **Overall, by evaluator** table tags
|
|
745
|
+
every advisory (`blocking: false`) evaluator's row the same way, and every
|
|
746
|
+
`trace_invariants` row there also says what it measures: each side judged
|
|
747
|
+
against the entry's rules, not against the other side. The
|
|
748
|
+
per-prompt **Executive summary** — and the header's mean score Δ, which
|
|
749
|
+
averages its rows — is drawn from blocking evaluators only (worst severity
|
|
750
|
+
first, ties to the most negative effect size), falling back to advisory ones
|
|
751
|
+
on a prompt that has nothing else, so an advisory rule the target satisfied by
|
|
752
|
+
calling no tools cannot read as the prompt's improvement above a failed
|
|
753
|
+
verdict.
|
|
754
|
+
|
|
755
|
+
Four things to know:
|
|
756
|
+
|
|
757
|
+
- **`required` and `min_calls` are not vacuous.** `order`, `arguments` and a
|
|
758
|
+
`call_count` with only `max_calls` say nothing about a trace that never calls
|
|
759
|
+
the tool. `required` and `call_count` with `min_calls` fail on every in-scope
|
|
760
|
+
trace that lacks the calls. A single-shot replay sees only the first
|
|
761
|
+
response, so scope them with `applies_to`, or use them with
|
|
762
|
+
`capture sync --rounds all` or with imported traces.
|
|
763
|
+
- **`required` counts the model's calls; `call_count` counts the
|
|
764
|
+
conversation's.** `required` asks whether the model under test made the
|
|
765
|
+
call. `call_count` asks whether the conversation contains it, so a recorded
|
|
766
|
+
history that already called `charge_card` satisfies `min_calls: 1` and
|
|
767
|
+
leaves `max_calls: 1` no room.
|
|
768
|
+
- **Teacher-forced rounds see recorded context.** Round *k* is judged with the
|
|
769
|
+
recorded rounds before it as context, not the model's own earlier rounds.
|
|
770
|
+
That applies to `min_calls` too: it is judged on the last response's context
|
|
771
|
+
plus that response's own calls.
|
|
772
|
+
- **Malformed arguments fail any schema that requires a field.** Arguments the
|
|
773
|
+
model emitted as unparseable JSON are recorded as
|
|
774
|
+
`{"_raw": …, "_parse_error": true}`, which fails any `json_schema` that
|
|
775
|
+
requires a field.
|
|
776
|
+
|
|
613
777
|
### `evaluators.llm_judge`
|
|
614
778
|
|
|
615
779
|
A list of pairwise judges. Each entry has:
|
|
@@ -769,8 +933,8 @@ suites:
|
|
|
769
933
|
|
|
770
934
|
The block takes the same families as the top-level `evaluators:` — `structural`,
|
|
771
935
|
`semantic`, `llm_judge`, `tool_selection`, `tool_arguments`,
|
|
772
|
-
`tool_trace_structure`, `agent_trace` — and resolution is
|
|
773
|
-
replacement**:
|
|
936
|
+
`tool_trace_structure`, `agent_trace`, `trace_invariants` — and resolution is
|
|
937
|
+
**family-level replacement**:
|
|
774
938
|
|
|
775
939
|
- A family the suite does **not** mention is inherited from the top level.
|
|
776
940
|
- A family it **does** mention replaces the top-level one wholesale. There is no
|
|
@@ -787,9 +951,15 @@ a name that has no `suites:` entry, resolves to the top-level block.
|
|
|
787
951
|
|
|
788
952
|
### `managed`
|
|
789
953
|
|
|
790
|
-
`capture sync` regenerates a managed suite's
|
|
954
|
+
`capture sync` regenerates a managed suite's entry — `path` and
|
|
791
955
|
`evaluators` — from what that suite's captures contain, so hand edits inside the
|
|
792
|
-
marker-delimited region are overwritten.
|
|
956
|
+
marker-delimited region are overwritten. The one exception is
|
|
957
|
+
`evaluators.trace_invariants`: no capture can derive a team's rules, so sync
|
|
958
|
+
copies a managed entry's existing `trace_invariants` block into the entry it
|
|
959
|
+
regenerates, verbatim. The marker comment above the region still says hand edits
|
|
960
|
+
are overwritten; that text cannot change (sync finds the region by matching it
|
|
961
|
+
exactly), so this paragraph is where the exception is written down. Every other
|
|
962
|
+
hand edit is overwritten. Set `managed: false` to freeze an entry:
|
|
793
963
|
|
|
794
964
|
```yaml
|
|
795
965
|
suites:
|
|
@@ -844,6 +1014,50 @@ evalshift runs clean --suite main_chat # restrict to one suite
|
|
|
844
1014
|
`--keep` beats `EVALSHIFT_MAX_RUNS`, which beats the config value. `runs clean` works even without a
|
|
845
1015
|
valid `evalshift.yaml` (it falls back to the defaults), so it's always available for disk cleanup.
|
|
846
1016
|
|
|
1017
|
+
## `captures`
|
|
1018
|
+
|
|
1019
|
+
Where the capture SDK's recordings live when they are not on this machine. Omit the block
|
|
1020
|
+
entirely for the default: captures are read from `.evalshift/captures/` (or `EVALSHIFT_DIR`)
|
|
1021
|
+
on local disk and nothing here is needed.
|
|
1022
|
+
|
|
1023
|
+
Production agents on Fargate, Lambda or Kubernetes lose their disk when they stop, so the
|
|
1024
|
+
SDK can ship captures to an object store you own instead (`EVALSHIFT_SINK=<uri>` in the
|
|
1025
|
+
agent's environment — see [Capture SDK](sdk.md)). Name the same store here and the CLI
|
|
1026
|
+
pulls new captures down before it promotes them, with no flags to remember:
|
|
1027
|
+
|
|
1028
|
+
```yaml
|
|
1029
|
+
captures:
|
|
1030
|
+
store: s3://acme-evals/support-agent
|
|
1031
|
+
```
|
|
1032
|
+
|
|
1033
|
+
| Field | Type | Default | Description |
|
|
1034
|
+
| ------- | ------------- | ------- | ----------- |
|
|
1035
|
+
| `store` | string (URI) | (none) | Object store the SDK writes to. `capture sync` and `capture list` fetch new captures and toolset sidecars from it into the local directory first; `capture fetch` does only that step. |
|
|
1036
|
+
|
|
1037
|
+
CLI versions before this release reject the `captures:` key (the config is strict), so bump
|
|
1038
|
+
the CLI — and a pinned GitHub Action's `evalshift-version` — before adding it.
|
|
1039
|
+
|
|
1040
|
+
Accepted URI forms — the same grammar the SDK uses:
|
|
1041
|
+
|
|
1042
|
+
| Form | Store | Install | Credentials |
|
|
1043
|
+
| --- | --- | --- | --- |
|
|
1044
|
+
| `s3://<bucket>/<prefix>` | Amazon S3; MinIO, Cloudflare R2, Backblaze B2 via boto3's `AWS_ENDPOINT_URL` | `pip install "evalshift[s3]"` | IAM role in CI (OIDC), `aws sso login` locally |
|
|
1045
|
+
| `gs://<bucket>/<prefix>` | Google Cloud Storage | `pip install "evalshift[gcs]"` | Workload Identity in CI, `gcloud auth application-default login` locally |
|
|
1046
|
+
| `az://<account>/<container>/<prefix>` | Azure Blob Storage | `pip install "evalshift[azure]"` | Managed Identity / federated credential in CI, `az login` locally |
|
|
1047
|
+
|
|
1048
|
+
`<prefix>` is optional. Credentials never go in the URI: a value containing `@` or `?` fails
|
|
1049
|
+
to load, naming the accepted forms. A rejected value is never echoed back (it may be a pasted
|
|
1050
|
+
key or connection string); the error names at most its scheme. Under the prefix the layout is exactly the local one —
|
|
1051
|
+
`captures/<suite>/cap_<hex>.json` and `toolsets/<hex>.json` — so the bucket is a mirror of
|
|
1052
|
+
`.evalshift/`, not a different format.
|
|
1053
|
+
|
|
1054
|
+
What the fetch does: it lists the bucket, downloads only objects missing locally (captures
|
|
1055
|
+
are immutable and toolsets content-addressed, so "exists" means "current"), skips captures
|
|
1056
|
+
already promoted into a suite (so `capture clean` never causes a re-download), and writes
|
|
1057
|
+
each file atomically. `--since 24h` (or `7d`, `30m`, an ISO date) limits a fetch to recently
|
|
1058
|
+
written captures; `--offline` skips it and works with what is already local. Retention in the bucket
|
|
1059
|
+
is yours to set with a lifecycle rule; the SDK's `max_captures` does not apply there.
|
|
1060
|
+
|
|
847
1061
|
## Capture lifecycle
|
|
848
1062
|
|
|
849
1063
|
The companion `evalshift-sdk` package records real agent runs to
|
|
@@ -852,11 +1066,16 @@ the base). The CLI consumes them via the `capture` commands:
|
|
|
852
1066
|
|
|
853
1067
|
```shell
|
|
854
1068
|
evalshift capture list # see what the SDK recorded
|
|
1069
|
+
evalshift capture fetch # (captures.store only) mirror new captures locally
|
|
855
1070
|
evalshift capture sync --input-var query # promote every capture + wire suites:
|
|
856
1071
|
evalshift run --suite-name support_agent --yes # score a candidate model against it
|
|
857
1072
|
evalshift capture clean # prune already-promoted captures
|
|
858
1073
|
```
|
|
859
1074
|
|
|
1075
|
+
With [`captures.store`](#captures) configured, `list` and `sync` run the same fetch first
|
|
1076
|
+
and print one summary line (`fetched 12 capture(s) and 1 toolset(s) from s3://…`); pass
|
|
1077
|
+
`--offline` to skip it. `promote`, `diff` and `clean` operate on the local mirror only.
|
|
1078
|
+
|
|
860
1079
|
`capture clean [<suite>]` deletes promoted captures by default (`--promoted`);
|
|
861
1080
|
`--all` deletes every capture, promoted or not. It never touches promoted
|
|
862
1081
|
suites, then offers to sweep toolset sidecars no surviving capture or suite
|
|
@@ -883,7 +1102,9 @@ key on evaluator names across runs, so regenerating a suite must not rename what
|
|
|
883
1102
|
it already wired. `structural` is deliberately not derived: nothing in a capture
|
|
884
1103
|
says what shape an answer must have. Sync regenerates only the suites it just
|
|
885
1104
|
promoted and carries every other entry in the region forward verbatim, and
|
|
886
|
-
`managed: false` freezes an entry entirely (see [`suites`](#suites)).
|
|
1105
|
+
`managed: false` freezes an entry entirely (see [`suites`](#suites)). A
|
|
1106
|
+
regenerated entry keeps its hand-written `evaluators.trace_invariants` (see
|
|
1107
|
+
[`managed`](#managed)).
|
|
887
1108
|
Captures with no recorded events are skipped, and captures whose replayed
|
|
888
1109
|
content duplicates an already-promoted case (or an earlier capture in the
|
|
889
1110
|
same run) are skipped too — duplicate examples inflate *n* and corrupt the
|