evalgate-sdk 3.5.0__tar.gz → 3.6.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.
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/CHANGELOG.md +14 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/PKG-INFO +32 -2
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/README.md +29 -1
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/pyproject.toml +5 -1
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/__init__.py +10 -0
- evalgate_sdk-3.6.0/src/evalgate_sdk/_version.py +3 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/__init__.py +46 -0
- evalgate_sdk-3.6.0/src/evalgate_sdk/cli/agent_evidence_utils.py +87 -0
- evalgate_sdk-3.6.0/src/evalgate_sdk/cli/check_runner.py +291 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/cli_constants.py +5 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/commands.py +443 -154
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/config.py +31 -0
- evalgate_sdk-3.6.0/src/evalgate_sdk/cli/evidence_persistence.py +391 -0
- evalgate_sdk-3.6.0/src/evalgate_sdk/cli/failure_modes.py +131 -0
- evalgate_sdk-3.6.0/src/evalgate_sdk/cli/gate_evaluator.py +291 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/judge_commands.py +2 -2
- evalgate_sdk-3.6.0/src/evalgate_sdk/cli/mcp.py +820 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/new_commands.py +141 -6
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/regression_gate.py +111 -12
- evalgate_sdk-3.6.0/src/evalgate_sdk/cli/run_cli.py +718 -0
- evalgate_sdk-3.6.0/src/evalgate_sdk/cli/sprint4_commands.py +38 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/client.py +388 -24
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/runtime/executor.py +2 -2
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/types.py +52 -6
- evalgate_sdk-3.6.0/tests/test_cli_hardening_351.py +357 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/tests/test_cli_new_commands.py +4 -0
- evalgate_sdk-3.6.0/tests/test_cli_sprint4.py +277 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/tests/test_client.py +195 -0
- evalgate_sdk-3.6.0/tests/test_provider_keys.py +150 -0
- evalgate_sdk-3.5.0/src/evalgate_sdk/_version.py +0 -3
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/.gitignore +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/assertions.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/auto.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/auto_experiment_runner.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/auto_failure_analyzer.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/auto_mutation_engine.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/batch.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cache.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/ci_context.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/api.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/api_command_config.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/env.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/formatters/types.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/golden_commands.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/manifest.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/policy_packs.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/profiles.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/render/__init__.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/render/snippet.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/render/sort.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/report/__init__.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/report/build_check_report.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/traces.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/workflow_commands.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/workspace.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cluster.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/collector.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/constants.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/context.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/errors.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/export.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/formatters/__init__.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/formatters/github.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/formatters/human.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/formatters/json_fmt.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/formatters/pr_comment.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/golden.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/integrations/__init__.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/integrations/anthropic.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/integrations/autogen.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/integrations/crewai.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/integrations/langchain.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/integrations/openai.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/integrations/openai_eval.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/knowledge.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/local.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/logger.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/matchers.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/otel.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/pagination.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/py.typed +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/pytest_plugin.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/reason_codes.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/regression.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/replay_decision.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/runtime/__init__.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/runtime/adapters/__init__.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/runtime/adapters/config_to_dsl.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/runtime/adapters/testsuite_to_dsl.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/runtime/context.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/runtime/eval.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/runtime/execution_mode.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/runtime/registry.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/runtime/run_report.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/runtime/types.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/snapshot.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/streaming.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/synthesize.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/testing.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/utils/__init__.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/utils/input_hash.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/src/evalgate_sdk/workflows.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/tests/__init__.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/tests/test_assertions.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/tests/test_assertions_async.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/tests/test_auto_mutation_engine_parity.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/tests/test_auto_session_methods.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/tests/test_batch_collector.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/tests/test_ci_context.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/tests/test_cli_golden_commands.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/tests/test_collector.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/tests/test_contract_payloads.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/tests/test_decorators_and_integrations.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/tests/test_errors.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/tests/test_execution_mode.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/tests/test_formatters.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/tests/test_knowledge_methods.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/tests/test_local_storage.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/tests/test_new_modules.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/tests/test_openapi_contracts.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/tests/test_otel.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/tests/test_parity_gap_modules.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/tests/test_parity_gaps.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/tests/test_prod_hardening.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/tests/test_pytest_plugin.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/tests/test_run_report.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/tests/test_runtime_dsl.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/tests/test_sdk_contract_scaffold.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/tests/test_testing.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/tests/test_types.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/tests/test_workflows.py +0 -0
- {evalgate_sdk-3.5.0 → evalgate_sdk-3.6.0}/tests/test_workflows_parity.py +0 -0
|
@@ -9,6 +9,20 @@ Version numbering is aligned with the TypeScript SDK (`@evalgate/sdk`) and the p
|
|
|
9
9
|
|
|
10
10
|
**Version history note:** The Python SDK jumped from 1.0.0 → 1.9.x → 2.0.0 to stay in sync with the TypeScript SDK. The TypeScript SDK had many releases (1.1–1.9) before the Python SDK existed. We now align both SDKs on the same major.minor version.
|
|
11
11
|
|
|
12
|
+
## Unreleased
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- **Production-loop scaffold parity** — `evalgate init --template production-loop`, `evalgate verify --local`, configured `discover`, JSON `validate`, and JSON/dry-run `gate` now work together from a clean Python SDK install.
|
|
16
|
+
|
|
17
|
+
## [3.5.1] - 2026-06-26
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- **CLI JSON contracts** — `check --format json` now emits one parseable JSON document.
|
|
21
|
+
- **Zero-config scaffolds** — `start --template` writes config that `run` and `doctor` discover consistently.
|
|
22
|
+
- **Runtime scoring defaults** — boolean/dict executor fallbacks now use the SDK's 0-100 score scale.
|
|
23
|
+
- **Sync safety** — missing labeled datasets surface an explicit error, and prompt pulls write UTF-8 content on Windows.
|
|
24
|
+
- **Default CLI install** — `pip install evalgate-sdk` now includes the runtime dependencies needed for the `evalgate` command.
|
|
25
|
+
|
|
12
26
|
## [3.5.0] - 2026-04-09
|
|
13
27
|
|
|
14
28
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: evalgate-sdk
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.6.0
|
|
4
4
|
Summary: EvalGate Python SDK — CI for AI behavior. Traces, evaluations, assertions, and regression gates for LLM apps.
|
|
5
5
|
Project-URL: Homepage, https://evalgate.com
|
|
6
6
|
Project-URL: Documentation, https://github.com/evalgate/ai-evaluation-platform#readme
|
|
@@ -24,6 +24,8 @@ Classifier: Typing :: Typed
|
|
|
24
24
|
Requires-Python: >=3.10
|
|
25
25
|
Requires-Dist: httpx<1,>=0.27
|
|
26
26
|
Requires-Dist: pydantic<3,>=2.0
|
|
27
|
+
Requires-Dist: rich>=13
|
|
28
|
+
Requires-Dist: typer>=0.12
|
|
27
29
|
Provides-Extra: all
|
|
28
30
|
Requires-Dist: anthropic>=0.20; extra == 'all'
|
|
29
31
|
Requires-Dist: langchain-core>=0.2; extra == 'all'
|
|
@@ -65,7 +67,6 @@ Use it to run assertions, trace agent workflows, inspect platform runs, orchestr
|
|
|
65
67
|
|
|
66
68
|
```bash
|
|
67
69
|
pip install evalgate-sdk
|
|
68
|
-
pip install "evalgate-sdk[cli]"
|
|
69
70
|
```
|
|
70
71
|
|
|
71
72
|
Optional extras:
|
|
@@ -125,6 +126,35 @@ evalgate judge registry
|
|
|
125
126
|
evalgate judge presets
|
|
126
127
|
```
|
|
127
128
|
|
|
129
|
+
## Web App Sync
|
|
130
|
+
|
|
131
|
+
The Python CLI uses the same sync contracts as the TypeScript SDK. Local labeled
|
|
132
|
+
JSONL can be pushed into the web app, web-labeled cases can be merged back into
|
|
133
|
+
the same file, and prompt files can round-trip with stale active-version
|
|
134
|
+
protection.
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
# Push local labels into the web app and pull web labels back into JSONL
|
|
138
|
+
evalgate sync \
|
|
139
|
+
--project-key support-agent \
|
|
140
|
+
--labeled-dataset .evalgate/golden/labeled.jsonl \
|
|
141
|
+
--pull-labels
|
|
142
|
+
|
|
143
|
+
# Pull the active web prompt into a local file
|
|
144
|
+
evalgate sync \
|
|
145
|
+
--project-key support-agent \
|
|
146
|
+
--prompt-id prompt_support \
|
|
147
|
+
--target-path prompts/support.md \
|
|
148
|
+
--pull-prompt
|
|
149
|
+
|
|
150
|
+
# Push an edited local prompt as a new web prompt version
|
|
151
|
+
evalgate sync \
|
|
152
|
+
--project-key support-agent \
|
|
153
|
+
--prompt-id prompt_support \
|
|
154
|
+
--target-path prompts/support.md \
|
|
155
|
+
--push-prompt
|
|
156
|
+
```
|
|
157
|
+
|
|
128
158
|
## Judge Orchestration
|
|
129
159
|
|
|
130
160
|
The Python SDK supports the same core judge model as the platform:
|
|
@@ -13,7 +13,6 @@ Use it to run assertions, trace agent workflows, inspect platform runs, orchestr
|
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
15
|
pip install evalgate-sdk
|
|
16
|
-
pip install "evalgate-sdk[cli]"
|
|
17
16
|
```
|
|
18
17
|
|
|
19
18
|
Optional extras:
|
|
@@ -73,6 +72,35 @@ evalgate judge registry
|
|
|
73
72
|
evalgate judge presets
|
|
74
73
|
```
|
|
75
74
|
|
|
75
|
+
## Web App Sync
|
|
76
|
+
|
|
77
|
+
The Python CLI uses the same sync contracts as the TypeScript SDK. Local labeled
|
|
78
|
+
JSONL can be pushed into the web app, web-labeled cases can be merged back into
|
|
79
|
+
the same file, and prompt files can round-trip with stale active-version
|
|
80
|
+
protection.
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# Push local labels into the web app and pull web labels back into JSONL
|
|
84
|
+
evalgate sync \
|
|
85
|
+
--project-key support-agent \
|
|
86
|
+
--labeled-dataset .evalgate/golden/labeled.jsonl \
|
|
87
|
+
--pull-labels
|
|
88
|
+
|
|
89
|
+
# Pull the active web prompt into a local file
|
|
90
|
+
evalgate sync \
|
|
91
|
+
--project-key support-agent \
|
|
92
|
+
--prompt-id prompt_support \
|
|
93
|
+
--target-path prompts/support.md \
|
|
94
|
+
--pull-prompt
|
|
95
|
+
|
|
96
|
+
# Push an edited local prompt as a new web prompt version
|
|
97
|
+
evalgate sync \
|
|
98
|
+
--project-key support-agent \
|
|
99
|
+
--prompt-id prompt_support \
|
|
100
|
+
--target-path prompts/support.md \
|
|
101
|
+
--push-prompt
|
|
102
|
+
```
|
|
103
|
+
|
|
76
104
|
## Judge Orchestration
|
|
77
105
|
|
|
78
106
|
The Python SDK supports the same core judge model as the platform:
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "evalgate-sdk"
|
|
7
|
-
version = "3.
|
|
7
|
+
version = "3.6.0"
|
|
8
8
|
description = "EvalGate Python SDK — CI for AI behavior. Traces, evaluations, assertions, and regression gates for LLM apps."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -31,6 +31,8 @@ classifiers = [
|
|
|
31
31
|
dependencies = [
|
|
32
32
|
"httpx>=0.27,<1",
|
|
33
33
|
"pydantic>=2.0,<3",
|
|
34
|
+
"rich>=13",
|
|
35
|
+
"typer>=0.12",
|
|
34
36
|
]
|
|
35
37
|
|
|
36
38
|
[project.optional-dependencies]
|
|
@@ -66,6 +68,8 @@ packages = ["src/evalgate_sdk"]
|
|
|
66
68
|
[tool.pytest.ini_options]
|
|
67
69
|
testpaths = ["tests"]
|
|
68
70
|
asyncio_mode = "auto"
|
|
71
|
+
asyncio_default_fixture_loop_scope = "function"
|
|
72
|
+
pythonpath = ["src"]
|
|
69
73
|
|
|
70
74
|
[tool.ruff]
|
|
71
75
|
target-version = "py310"
|
|
@@ -392,12 +392,17 @@ from evalgate_sdk.types import (
|
|
|
392
392
|
AutoPlanPreviewResponse,
|
|
393
393
|
AutoSessionWorkflowProfile,
|
|
394
394
|
CamelModel,
|
|
395
|
+
CreateProviderKeyParams,
|
|
395
396
|
EvaluationIntegritySnapshot,
|
|
396
397
|
EvaluationRunDetail,
|
|
397
398
|
KnowledgeHintSummary,
|
|
398
399
|
KnowledgeResponse,
|
|
400
|
+
ListProviderKeysParams,
|
|
401
|
+
ProviderKey,
|
|
399
402
|
QualityBreakdown,
|
|
400
403
|
QualityScore,
|
|
404
|
+
RotateProviderKeyResult,
|
|
405
|
+
UpdateProviderKeyParams,
|
|
401
406
|
)
|
|
402
407
|
from evalgate_sdk.utils.input_hash import normalize_input, sha256_input
|
|
403
408
|
from evalgate_sdk.workflows import WorkflowTracer, create_workflow_tracer, trace_workflow_step
|
|
@@ -700,6 +705,11 @@ __all__ = [
|
|
|
700
705
|
"CamelModel",
|
|
701
706
|
"QualityScore",
|
|
702
707
|
"QualityBreakdown",
|
|
708
|
+
"CreateProviderKeyParams",
|
|
709
|
+
"ListProviderKeysParams",
|
|
710
|
+
"ProviderKey",
|
|
711
|
+
"RotateProviderKeyResult",
|
|
712
|
+
"UpdateProviderKeyParams",
|
|
703
713
|
# Knowledge API
|
|
704
714
|
"KnowledgeAPI",
|
|
705
715
|
# Collector (T2)
|
|
@@ -6,10 +6,34 @@ Submodules like config.py, api.py, etc. can be imported without typer.
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
+
import sys
|
|
10
|
+
|
|
9
11
|
# Lazy initialization - only set up CLI when typer is available
|
|
10
12
|
app = None # Will be initialized on first access via get_app()
|
|
11
13
|
|
|
12
14
|
|
|
15
|
+
def _stream_needs_replacement_errors(stream: object) -> bool:
|
|
16
|
+
"""Return whether the stream encoding cannot represent common CLI glyphs."""
|
|
17
|
+
encoding = getattr(stream, "encoding", None)
|
|
18
|
+
if not encoding:
|
|
19
|
+
return False
|
|
20
|
+
try:
|
|
21
|
+
"✓ → ─".encode(encoding)
|
|
22
|
+
return False
|
|
23
|
+
except (LookupError, UnicodeEncodeError):
|
|
24
|
+
return True
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _make_output_streams_encoding_safe() -> None:
|
|
28
|
+
"""Prevent Windows legacy code pages from crashing Rich/Typer help output."""
|
|
29
|
+
for stream in (sys.stdout, sys.stderr):
|
|
30
|
+
if not _stream_needs_replacement_errors(stream):
|
|
31
|
+
continue
|
|
32
|
+
reconfigure = getattr(stream, "reconfigure", None)
|
|
33
|
+
if reconfigure is not None:
|
|
34
|
+
reconfigure(errors="replace")
|
|
35
|
+
|
|
36
|
+
|
|
13
37
|
def _ensure_typer() -> None:
|
|
14
38
|
"""Check that typer is installed, raise helpful error if not."""
|
|
15
39
|
try:
|
|
@@ -50,7 +74,10 @@ def get_app():
|
|
|
50
74
|
print_config,
|
|
51
75
|
run,
|
|
52
76
|
share,
|
|
77
|
+
sync,
|
|
53
78
|
upgrade,
|
|
79
|
+
verify,
|
|
80
|
+
version,
|
|
54
81
|
)
|
|
55
82
|
from evalgate_sdk.cli.judge_commands import judge
|
|
56
83
|
from evalgate_sdk.cli.workflow_commands import controls, integrity, knowledge, plan, trace, workflow
|
|
@@ -74,8 +101,11 @@ def get_app():
|
|
|
74
101
|
app.command("baseline")(baseline)
|
|
75
102
|
app.command("print-config")(print_config)
|
|
76
103
|
app.command("share")(share)
|
|
104
|
+
app.command("sync")(sync)
|
|
77
105
|
app.command("configure")(configure)
|
|
78
106
|
app.command("upgrade")(upgrade)
|
|
107
|
+
app.command("verify")(verify)
|
|
108
|
+
app.command("version")(version)
|
|
79
109
|
app.command("impact-analysis")(impact_analysis)
|
|
80
110
|
app.command("migrate")(migrate)
|
|
81
111
|
|
|
@@ -104,6 +134,21 @@ def get_app():
|
|
|
104
134
|
app.command("replay-decision")(replay_decision)
|
|
105
135
|
app.add_typer(auto_app, name="auto")
|
|
106
136
|
|
|
137
|
+
from evalgate_sdk.cli.sprint4_commands import failure_modes_command, mcp_command, run_cli_command
|
|
138
|
+
|
|
139
|
+
app.command(
|
|
140
|
+
"run-cli",
|
|
141
|
+
context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
|
|
142
|
+
)(run_cli_command)
|
|
143
|
+
app.command(
|
|
144
|
+
"mcp",
|
|
145
|
+
context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
|
|
146
|
+
)(mcp_command)
|
|
147
|
+
app.command(
|
|
148
|
+
"failure-modes",
|
|
149
|
+
context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
|
|
150
|
+
)(failure_modes_command)
|
|
151
|
+
|
|
107
152
|
return app
|
|
108
153
|
|
|
109
154
|
|
|
@@ -116,5 +161,6 @@ def __getattr__(name: str):
|
|
|
116
161
|
|
|
117
162
|
def main() -> None:
|
|
118
163
|
"""CLI entry point."""
|
|
164
|
+
_make_output_streams_encoding_safe()
|
|
119
165
|
cli_app = get_app()
|
|
120
166
|
cli_app()
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""Utilities for agent evidence capture (hashing, redaction, JSON I/O).
|
|
2
|
+
|
|
3
|
+
Port of ``cli/agent-evidence-utils.ts``.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import hashlib
|
|
9
|
+
import json
|
|
10
|
+
import re
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
SECRET_PATTERNS: list[re.Pattern[str]] = [
|
|
15
|
+
re.compile(
|
|
16
|
+
r"\b((?:api[_-]?key|access[_-]?token|auth[_-]?token|token|secret|password|client[_-]?secret)\s*[:=]\s*)([\"']?)([^\s\"',;]+)",
|
|
17
|
+
re.I,
|
|
18
|
+
),
|
|
19
|
+
re.compile(r"(Bearer\s+)[A-Za-z0-9._~+/=-]+", re.I),
|
|
20
|
+
re.compile(r"\b(sk-[A-Za-z0-9_-]{20,})\b"),
|
|
21
|
+
re.compile(r"\b(gh[pousr]_[A-Za-z0-9_]{20,})\b"),
|
|
22
|
+
re.compile(r"\b(xox[baprs]-[A-Za-z0-9-]{20,})\b"),
|
|
23
|
+
re.compile(r"\b(npm_[A-Za-z0-9]{20,})\b"),
|
|
24
|
+
re.compile(r"\b(AKIA[0-9A-Z]{16})\b"),
|
|
25
|
+
re.compile(r"\b(eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,})\b"),
|
|
26
|
+
re.compile(r"(postgres(?:ql)?:\/\/[^:\s/@]+:)([^@\s]+)(@[^\s]+)", re.I),
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def sha256_hex(value: str) -> str:
|
|
31
|
+
return hashlib.sha256(value.encode("utf-8")).hexdigest()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _sort_value(value: Any) -> Any:
|
|
35
|
+
if isinstance(value, list):
|
|
36
|
+
return [_sort_value(item) for item in value]
|
|
37
|
+
if isinstance(value, dict):
|
|
38
|
+
return {key: _sort_value(value[key]) for key in sorted(value)}
|
|
39
|
+
return value
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def canonicalize(value: Any) -> str:
|
|
43
|
+
return json.dumps(_sort_value(value), separators=(",", ":"), default=str)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def stable_hash(value: Any) -> str:
|
|
47
|
+
return sha256_hex(canonicalize(value))
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def redact_text(value: str) -> dict[str, Any]:
|
|
51
|
+
text = value
|
|
52
|
+
secrets_found = 0
|
|
53
|
+
for pattern in SECRET_PATTERNS:
|
|
54
|
+
|
|
55
|
+
def _replace(match: re.Match[str]) -> str:
|
|
56
|
+
nonlocal secrets_found
|
|
57
|
+
secrets_found += 1
|
|
58
|
+
groups = match.groups()
|
|
59
|
+
first = groups[0] if groups else ""
|
|
60
|
+
if first.lower().startswith("bearer"):
|
|
61
|
+
return f"{first}[REDACTED]"
|
|
62
|
+
if first.lower().startswith("postgres"):
|
|
63
|
+
suffix = groups[2] if len(groups) > 2 else ""
|
|
64
|
+
return f"{first}[REDACTED]{suffix}"
|
|
65
|
+
if len(groups) >= 3 and first:
|
|
66
|
+
quote = groups[1] if len(groups) > 1 else ""
|
|
67
|
+
return f"{first}{quote}[REDACTED]"
|
|
68
|
+
return "[REDACTED]"
|
|
69
|
+
|
|
70
|
+
text = pattern.sub(_replace, text)
|
|
71
|
+
return {"text": text, "secretsFound": secrets_found}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def bounded(value: str, max_bytes: int = 64 * 1024) -> str:
|
|
75
|
+
encoded = value.encode("utf-8")
|
|
76
|
+
if len(encoded) <= max_bytes:
|
|
77
|
+
return value
|
|
78
|
+
truncated = encoded[:max_bytes].decode("utf-8", errors="ignore")
|
|
79
|
+
return f"{truncated}\n[TRUNCATED]"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def read_json_file(file_path: str) -> dict[str, Any]:
|
|
83
|
+
raw = Path(file_path).read_text(encoding="utf-8-sig")
|
|
84
|
+
parsed = json.loads(raw)
|
|
85
|
+
if not isinstance(parsed, dict):
|
|
86
|
+
raise ValueError(f"{file_path} must contain a JSON object")
|
|
87
|
+
return parsed
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"""evalgate check runner — CI/CD evaluation gate.
|
|
2
|
+
|
|
3
|
+
Port of ``cli/check.ts`` (core path: fetch quality, evaluate gate, build report).
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import json
|
|
10
|
+
from dataclasses import asdict, dataclass, is_dataclass
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, Literal
|
|
13
|
+
|
|
14
|
+
from evalgate_sdk.cli.api import fetch_quality_latest, fetch_run_details
|
|
15
|
+
from evalgate_sdk.cli.cli_constants import EXIT
|
|
16
|
+
from evalgate_sdk.cli.config import load_config, merge_config_with_args
|
|
17
|
+
from evalgate_sdk.cli.gate_evaluator import CheckGateArgs, evaluate_gate, gate_result_to_dict
|
|
18
|
+
from evalgate_sdk.cli.report.build_check_report import build_check_report
|
|
19
|
+
from evalgate_sdk.formatters.human import format_human
|
|
20
|
+
from evalgate_sdk.formatters.json_fmt import format_json
|
|
21
|
+
|
|
22
|
+
FormatType = Literal["human", "json", "github"]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class CheckArgs:
|
|
27
|
+
base_url: str
|
|
28
|
+
api_key: str
|
|
29
|
+
min_score: float
|
|
30
|
+
max_drop: float | None = None
|
|
31
|
+
warn_drop: float | None = None
|
|
32
|
+
min_n: int | None = None
|
|
33
|
+
allow_weak_evidence: bool = False
|
|
34
|
+
evaluation_id: str = ""
|
|
35
|
+
policy: str | None = None
|
|
36
|
+
baseline: str = "published"
|
|
37
|
+
format: FormatType = "human"
|
|
38
|
+
explain: bool = False
|
|
39
|
+
dry_run: bool = False
|
|
40
|
+
allow_empty: bool = False
|
|
41
|
+
max_cost_usd: float | None = None
|
|
42
|
+
max_latency_ms: float | None = None
|
|
43
|
+
max_cost_delta_usd: float | None = None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _to_camel(key: str) -> str:
|
|
47
|
+
parts = key.split("_")
|
|
48
|
+
return parts[0] + "".join(part[:1].upper() + part[1:] for part in parts[1:])
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _serialize(value: Any) -> Any:
|
|
52
|
+
if is_dataclass(value) and not isinstance(value, type):
|
|
53
|
+
return {_to_camel(k): _serialize(v) for k, v in asdict(value).items() if v is not None}
|
|
54
|
+
if isinstance(value, list):
|
|
55
|
+
return [_serialize(item) for item in value]
|
|
56
|
+
if isinstance(value, dict):
|
|
57
|
+
return {_to_camel(k): _serialize(v) for k, v in value.items() if v is not None}
|
|
58
|
+
return value
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def parse_check_args(argv: list[str]) -> tuple[CheckArgs | None, int, str | None]:
|
|
62
|
+
args: dict[str, str] = {}
|
|
63
|
+
index = 0
|
|
64
|
+
while index < len(argv):
|
|
65
|
+
arg = argv[index]
|
|
66
|
+
if arg.startswith("--"):
|
|
67
|
+
key = arg[2:]
|
|
68
|
+
next_value = argv[index + 1] if index + 1 < len(argv) else None
|
|
69
|
+
if next_value is not None and not next_value.startswith("--"):
|
|
70
|
+
args[key] = next_value
|
|
71
|
+
index += 2
|
|
72
|
+
else:
|
|
73
|
+
args[key] = "true"
|
|
74
|
+
index += 1
|
|
75
|
+
else:
|
|
76
|
+
index += 1
|
|
77
|
+
|
|
78
|
+
base_url = args.get("baseUrl") or args.get("base-url") or ""
|
|
79
|
+
api_key = args.get("apiKey") or args.get("api-key") or ""
|
|
80
|
+
min_score = int(args.get("minScore") or args.get("min-score") or "0")
|
|
81
|
+
max_drop = (
|
|
82
|
+
int(args["maxDrop"]) if args.get("maxDrop") else (int(args["max-drop"]) if args.get("max-drop") else None)
|
|
83
|
+
)
|
|
84
|
+
warn_drop = (
|
|
85
|
+
int(args["warnDrop"]) if args.get("warnDrop") else (int(args["warn-drop"]) if args.get("warn-drop") else None)
|
|
86
|
+
)
|
|
87
|
+
min_n = int(args["minN"]) if args.get("minN") else (int(args["min-n"]) if args.get("min-n") else None)
|
|
88
|
+
allow_weak_evidence = args.get("allowWeakEvidence") in {"true", "1"} or args.get("allow-weak-evidence") in {
|
|
89
|
+
"true",
|
|
90
|
+
"1",
|
|
91
|
+
}
|
|
92
|
+
evaluation_id = args.get("evaluationId") or args.get("evaluation-id") or ""
|
|
93
|
+
policy = args.get("policy")
|
|
94
|
+
format_raw = args.get("format") or "human"
|
|
95
|
+
fmt: FormatType = "json" if format_raw == "json" else ("github" if format_raw == "github" else "human")
|
|
96
|
+
explain = args.get("explain") in {"true", "1"}
|
|
97
|
+
dry_run = args.get("dry-run") == "true" or args.get("dryRun") == "true"
|
|
98
|
+
allow_empty = args.get("allow-empty") == "true" or args.get("allowEmpty") == "true"
|
|
99
|
+
profile = args.get("profile")
|
|
100
|
+
baseline = args.get("baseline") or "published"
|
|
101
|
+
if baseline not in {"published", "previous", "production", "auto"}:
|
|
102
|
+
baseline = "published"
|
|
103
|
+
max_cost_usd = (
|
|
104
|
+
float(args.get("max-cost-usd") or args.get("maxCostUsd") or "nan")
|
|
105
|
+
if (args.get("max-cost-usd") or args.get("maxCostUsd"))
|
|
106
|
+
else None
|
|
107
|
+
)
|
|
108
|
+
max_latency_ms = (
|
|
109
|
+
int(args.get("max-latency-ms") or args.get("maxLatencyMs") or "0")
|
|
110
|
+
if (args.get("max-latency-ms") or args.get("maxLatencyMs"))
|
|
111
|
+
else None
|
|
112
|
+
)
|
|
113
|
+
max_cost_delta_usd = (
|
|
114
|
+
float(args.get("max-cost-delta-usd") or args.get("maxCostDeltaUsd") or "nan")
|
|
115
|
+
if (args.get("max-cost-delta-usd") or args.get("maxCostDeltaUsd"))
|
|
116
|
+
else None
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
config = load_config()
|
|
120
|
+
merged = merge_config_with_args(
|
|
121
|
+
config,
|
|
122
|
+
{
|
|
123
|
+
"evaluation_id": evaluation_id or None,
|
|
124
|
+
"base_url": base_url or None,
|
|
125
|
+
"min_score": str(min_score) if args.get("minScore") or args.get("min-score") else None,
|
|
126
|
+
"max_drop": str(max_drop) if max_drop is not None else None,
|
|
127
|
+
"warn_drop": str(warn_drop) if warn_drop is not None else None,
|
|
128
|
+
"min_n": str(min_n) if min_n is not None else None,
|
|
129
|
+
"allow_weak_evidence": "true" if allow_weak_evidence else None,
|
|
130
|
+
"baseline": baseline,
|
|
131
|
+
"profile": profile,
|
|
132
|
+
},
|
|
133
|
+
)
|
|
134
|
+
if not evaluation_id and merged.get("evaluation_id"):
|
|
135
|
+
evaluation_id = str(merged["evaluation_id"])
|
|
136
|
+
if merged.get("base_url"):
|
|
137
|
+
base_url = str(merged["base_url"])
|
|
138
|
+
if merged.get("min_score") is not None and not (args.get("minScore") or args.get("min-score")):
|
|
139
|
+
min_score = int(merged["min_score"])
|
|
140
|
+
if merged.get("max_drop") is not None and max_drop is None:
|
|
141
|
+
max_drop = int(merged["max_drop"])
|
|
142
|
+
if merged.get("warn_drop") is not None and warn_drop is None:
|
|
143
|
+
warn_drop = int(merged["warn_drop"])
|
|
144
|
+
if merged.get("min_n") is not None and min_n is None:
|
|
145
|
+
min_n = int(merged["min_n"])
|
|
146
|
+
if merged.get("allow_weak_evidence") is not None and not (
|
|
147
|
+
args.get("allowWeakEvidence") or args.get("allow-weak-evidence")
|
|
148
|
+
):
|
|
149
|
+
allow_weak_evidence = bool(merged["allow_weak_evidence"])
|
|
150
|
+
if merged.get("baseline") and not args.get("baseline"):
|
|
151
|
+
baseline = str(merged["baseline"])
|
|
152
|
+
|
|
153
|
+
import os
|
|
154
|
+
|
|
155
|
+
if not base_url:
|
|
156
|
+
from evalgate_sdk.constants import DEFAULT_BASE_URL
|
|
157
|
+
|
|
158
|
+
base_url = os.environ.get("EVALGATE_BASE_URL") or DEFAULT_BASE_URL
|
|
159
|
+
if not api_key:
|
|
160
|
+
api_key = os.environ.get("EVALGATE_API_KEY") or ""
|
|
161
|
+
|
|
162
|
+
if not api_key:
|
|
163
|
+
return None, EXIT.BAD_ARGS, "Error: --apiKey or EVALGATE_API_KEY is required"
|
|
164
|
+
if not evaluation_id:
|
|
165
|
+
return None, EXIT.BAD_ARGS, "Run evalgate init and paste your evaluationId, or pass --evaluationId."
|
|
166
|
+
if min_score < 0 or min_score > 100:
|
|
167
|
+
return None, EXIT.BAD_ARGS, "Error: --minScore must be 0-100"
|
|
168
|
+
if min_n is not None and min_n < 1:
|
|
169
|
+
return None, EXIT.BAD_ARGS, "Error: --minN must be a positive number"
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
CheckArgs(
|
|
173
|
+
base_url=base_url,
|
|
174
|
+
api_key=api_key,
|
|
175
|
+
min_score=min_score,
|
|
176
|
+
max_drop=max_drop,
|
|
177
|
+
warn_drop=warn_drop,
|
|
178
|
+
min_n=min_n,
|
|
179
|
+
allow_weak_evidence=allow_weak_evidence,
|
|
180
|
+
evaluation_id=evaluation_id,
|
|
181
|
+
policy=policy,
|
|
182
|
+
baseline=baseline,
|
|
183
|
+
format=fmt,
|
|
184
|
+
explain=explain,
|
|
185
|
+
dry_run=dry_run,
|
|
186
|
+
allow_empty=allow_empty,
|
|
187
|
+
max_cost_usd=max_cost_usd,
|
|
188
|
+
max_latency_ms=max_latency_ms,
|
|
189
|
+
max_cost_delta_usd=max_cost_delta_usd,
|
|
190
|
+
),
|
|
191
|
+
EXIT.PASS,
|
|
192
|
+
None,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
async def run_check(args: CheckArgs) -> int:
|
|
197
|
+
quality_result = await fetch_quality_latest(args.base_url, args.api_key, args.evaluation_id, args.baseline)
|
|
198
|
+
if not quality_result.get("ok"):
|
|
199
|
+
status = quality_result.get("status", 0)
|
|
200
|
+
body = quality_result.get("body", "")
|
|
201
|
+
if status == 0:
|
|
202
|
+
print(f"EvalGate gate ERROR: Network failure — {body}")
|
|
203
|
+
else:
|
|
204
|
+
print(f"EvalGate gate ERROR: API returned {status} — {body}")
|
|
205
|
+
return EXIT.API_ERROR
|
|
206
|
+
|
|
207
|
+
quality = quality_result["data"]
|
|
208
|
+
request_id = quality_result.get("request_id")
|
|
209
|
+
evaluation_run_id = quality.get("evaluationRunId") or quality.get("evaluation_run_id")
|
|
210
|
+
|
|
211
|
+
run_details = None
|
|
212
|
+
if evaluation_run_id is not None:
|
|
213
|
+
run_res = await fetch_run_details(args.base_url, args.api_key, args.evaluation_id, int(evaluation_run_id))
|
|
214
|
+
if run_res.get("ok"):
|
|
215
|
+
run_details = run_res.get("data")
|
|
216
|
+
|
|
217
|
+
gate_args = CheckGateArgs(
|
|
218
|
+
min_score=args.min_score,
|
|
219
|
+
max_drop=args.max_drop,
|
|
220
|
+
warn_drop=args.warn_drop,
|
|
221
|
+
min_n=args.min_n,
|
|
222
|
+
allow_weak_evidence=args.allow_weak_evidence,
|
|
223
|
+
policy=args.policy,
|
|
224
|
+
baseline=args.baseline,
|
|
225
|
+
allow_empty=args.allow_empty,
|
|
226
|
+
max_cost_usd=args.max_cost_usd,
|
|
227
|
+
max_latency_ms=args.max_latency_ms,
|
|
228
|
+
max_cost_delta_usd=args.max_cost_delta_usd,
|
|
229
|
+
)
|
|
230
|
+
gate_result = evaluate_gate(gate_args, quality)
|
|
231
|
+
gate_dict = gate_result_to_dict(gate_result)
|
|
232
|
+
|
|
233
|
+
report = build_check_report(
|
|
234
|
+
evaluation_id=args.evaluation_id,
|
|
235
|
+
quality=quality,
|
|
236
|
+
gate_result=gate_dict,
|
|
237
|
+
base_url=args.base_url,
|
|
238
|
+
run_details=run_details,
|
|
239
|
+
request_id=request_id,
|
|
240
|
+
policy=args.policy,
|
|
241
|
+
min_score=args.min_score,
|
|
242
|
+
max_drop=args.max_drop,
|
|
243
|
+
warn_drop=args.warn_drop,
|
|
244
|
+
min_n=args.min_n,
|
|
245
|
+
allow_weak_evidence=args.allow_weak_evidence,
|
|
246
|
+
baseline=args.baseline,
|
|
247
|
+
max_cost_usd=args.max_cost_usd,
|
|
248
|
+
max_latency_ms=args.max_latency_ms,
|
|
249
|
+
max_cost_delta_usd=args.max_cost_delta_usd,
|
|
250
|
+
explain=args.explain,
|
|
251
|
+
)
|
|
252
|
+
report_dict = _serialize(report)
|
|
253
|
+
|
|
254
|
+
report_dir = Path.cwd() / ".evalgate"
|
|
255
|
+
try:
|
|
256
|
+
report_dir.mkdir(parents=True, exist_ok=True)
|
|
257
|
+
(report_dir / "last-report.json").write_text(json.dumps(report_dict, indent=2) + "\n", encoding="utf-8")
|
|
258
|
+
except OSError as exc:
|
|
259
|
+
print(f"EvalGate: failed to write .evalgate/last-report.json: {exc}")
|
|
260
|
+
return EXIT.API_ERROR
|
|
261
|
+
|
|
262
|
+
if args.format == "json":
|
|
263
|
+
print(format_json(report_dict))
|
|
264
|
+
elif args.format == "github":
|
|
265
|
+
from evalgate_sdk.formatters.github import format_github
|
|
266
|
+
|
|
267
|
+
print(format_github(report_dict))
|
|
268
|
+
else:
|
|
269
|
+
print(format_human(report_dict))
|
|
270
|
+
|
|
271
|
+
if not gate_result.passed:
|
|
272
|
+
print("\nNext: evalgate explain")
|
|
273
|
+
|
|
274
|
+
if args.dry_run:
|
|
275
|
+
print(f"\n[dry-run] Gate exits with code {gate_result.exit_code} (severity={gate_result.severity})")
|
|
276
|
+
return gate_result.exit_code
|
|
277
|
+
|
|
278
|
+
return gate_result.exit_code
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def run_check_sync(argv: list[str]) -> int:
|
|
282
|
+
parsed, exit_code, message = parse_check_args(argv)
|
|
283
|
+
if parsed is None:
|
|
284
|
+
if message:
|
|
285
|
+
print(message)
|
|
286
|
+
return exit_code
|
|
287
|
+
try:
|
|
288
|
+
loop = asyncio.get_running_loop()
|
|
289
|
+
except RuntimeError:
|
|
290
|
+
return asyncio.run(run_check(parsed))
|
|
291
|
+
return loop.run_until_complete(run_check(parsed))
|