aeval-framework 0.3.0__tar.gz → 0.3.1__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.
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/PKG-INFO +1 -1
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/pyproject.toml +6 -1
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/__init__.py +1 -1
- aeval_framework-0.3.0/src/agent_eval/cli.py → aeval_framework-0.3.1/src/agent_eval/_cli_app.py +5 -42
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/api/app.py +1 -1
- aeval_framework-0.3.1/src/agent_eval/cli.py +103 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/tests/test_cli.py +3 -3
- aeval_framework-0.3.1/tests/test_cli_entry_guard.py +193 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/tests/test_discovery.py +2 -2
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/tests/test_holdout_and_canary.py +1 -1
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/tests/test_packaging.py +2 -2
- aeval_framework-0.3.1/tests/test_version_single_source.py +131 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/.gitignore +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/README.md +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/api/__init__.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/api/events.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/api/routes/__init__.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/api/routes/datasets.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/api/routes/graders.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/api/routes/metrics.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/api/routes/runs.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/api/routes/suites.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/api/routes/tasks.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/api/standalone.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/core/__init__.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/core/comparison.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/core/contract.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/core/discovery.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/core/metrics.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/core/packaging.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/core/pricing.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/core/redaction.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/core/runner.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/core/simulator.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/core/suite.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/core/types.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/dataset/__init__.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/dataset/models.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/dataset/quality.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/dataset/sources/__init__.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/dataset/sources/llm_generator.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/dataset/sources/manual.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/dataset/sources/regression.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/dataset/sources/trace_mining.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/dataset/storage.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/dataset/version.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/examples/__init__.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/examples/basic_usage.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/examples/mock_runner.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/graders/__init__.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/graders/_evidence.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/graders/_verdicts.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/graders/artifact_check.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/graders/code_based.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/graders/human.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/graders/metric.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/graders/model_based.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/graders/presentation_probes.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/graders/state_check.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/graders/step_level.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/graders/tool_calls.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/graders/transcript.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/metrics/__init__.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/metrics/answer_relevancy.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/metrics/base.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/metrics/batch_evaluation.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/metrics/context_precision.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/metrics/context_recall.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/metrics/faithfulness.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/metrics/llm_judge.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/metrics/prompt_metric.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/metrics/pytest_plugin.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/metrics/report.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/metrics/synthetic_data.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/packs/__init__.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/packs/starter/README.md +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/packs/starter/manifest.json +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/packs/starter/suite.yaml +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/storage/__init__.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/storage/memory.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/storage/sqlite.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/trace/__init__.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/trace/mapping.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/trace/normalize.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/trace/observations.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/trace/phoenix.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/tests/conftest.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/tests/smoke_achat_runner.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/tests/test_agent_metric_catalog.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/tests/test_api.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/tests/test_baseline_comparison.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/tests/test_builtin_graders.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/tests/test_collection_phases.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/tests/test_conversation.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/tests/test_e2e.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/tests/test_environment_identity.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/tests/test_eval_batch_evaluation.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/tests/test_eval_dataset_api.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/tests/test_eval_dataset_sources.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/tests/test_eval_dataset_storage.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/tests/test_eval_metric_pipeline.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/tests/test_eval_metrics_api.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/tests/test_eval_metrics_module.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/tests/test_eval_prompt_metric.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/tests/test_eval_pytest_plugin.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/tests/test_eval_report.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/tests/test_eval_task_history.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/tests/test_evidence_capture_and_storage.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/tests/test_graders.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/tests/test_import_isolation.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/tests/test_metric_roles_gates_agreement.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/tests/test_metrics.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/tests/test_power_analysis.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/tests/test_presentation_probes.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/tests/test_prompt_determinism.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/tests/test_regrade_and_archive.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/tests/test_run_storage_fields.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/tests/test_runner.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/tests/test_sse.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/tests/test_standalone_api.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/tests/test_suite.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/tests/test_termination_and_budgets.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/tests/test_trace_normalization.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/tests/test_trial_evidence.py +0 -0
- {aeval_framework-0.3.0 → aeval_framework-0.3.1}/tests/test_vocabulary_isolation.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: aeval-framework
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Aeval — open-source agent evaluation framework driven by OTel traces (suites, graders, metrics, storage, API, CLI)
|
|
5
5
|
Project-URL: Homepage, https://github.com/QiYuyyds/Aeval
|
|
6
6
|
Project-URL: Repository, https://github.com/QiYuyyds/Aeval
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
# Distribution name: aeval-framework (PyPI). Python module: agent_eval, CLI: eval-suite.
|
|
3
3
|
# (`agent-eval` on PyPI belongs to an unrelated project — UK AISI agenteval.)
|
|
4
4
|
name = "aeval-framework"
|
|
5
|
-
|
|
5
|
+
# 版本只有一个真相源: src/agent_eval/__init__.py 的 __version__ (design D1)。
|
|
6
|
+
# 手工写第二份 (旧的 pyproject `version =`) 才是缺陷 —— dynamic 让它无法被写出来。
|
|
7
|
+
dynamic = ["version"]
|
|
6
8
|
description = "Aeval — open-source agent evaluation framework driven by OTel traces (suites, graders, metrics, storage, API, CLI)"
|
|
7
9
|
readme = "README.md"
|
|
8
10
|
requires-python = ">=3.11"
|
|
@@ -58,6 +60,9 @@ Changelog = "https://github.com/QiYuyyds/Aeval/blob/main/CHANGELOG.md"
|
|
|
58
60
|
requires = ["hatchling"]
|
|
59
61
|
build-backend = "hatchling.build"
|
|
60
62
|
|
|
63
|
+
[tool.hatch.version]
|
|
64
|
+
path = "src/agent_eval/__init__.py"
|
|
65
|
+
|
|
61
66
|
[tool.hatch.build.targets.wheel]
|
|
62
67
|
packages = ["src/agent_eval"]
|
|
63
68
|
# src/agent_eval/packs/** (含 starter pack 的 suite.yaml/manifest.json) 作为
|
aeval_framework-0.3.0/src/agent_eval/cli.py → aeval_framework-0.3.1/src/agent_eval/_cli_app.py
RENAMED
|
@@ -1,46 +1,9 @@
|
|
|
1
1
|
"""
|
|
2
|
-
eval-suite
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
3 evaluation untrustworthy. --baseline adds a relative
|
|
8
|
-
regression gate against a stored run
|
|
9
|
-
validate Validate a suite without running it
|
|
10
|
-
list List runs or suites from the storage DB
|
|
11
|
-
show Show one run's details (--task drills into a single task);
|
|
12
|
-
reports valid/invalid/pending counts, pass@1 95% CI and
|
|
13
|
-
extrapolation markers
|
|
14
|
-
compare A/B compare two runs; overlapping 95% intervals read as
|
|
15
|
-
"not significant" with no directional verdict, and differing
|
|
16
|
-
statistics versions or evidence boundaries read as not comparable
|
|
17
|
-
power Sample-size planning: trials needed for a target resolution
|
|
18
|
-
(--delta with an assumed or measured baseline pass rate)
|
|
19
|
-
serve Serve the standalone API (/v1) via uvicorn
|
|
20
|
-
|
|
21
|
-
Source forms (run/validate, spec: suite-distribution): a single suite.yaml
|
|
22
|
-
file (status quo); a pack directory or .tar.gz/.zip archive (integrity-checked
|
|
23
|
-
against manifest.json); a git URL (shallow clone into a temp dir — needs the
|
|
24
|
-
git executable); or the literal "demo" (the built-in starter pack shipped
|
|
25
|
-
inside the wheel; a local file/dir named demo wins with a hint). Temp dirs are
|
|
26
|
-
cleaned up when the command ends.
|
|
27
|
-
|
|
28
|
-
Holdout (run/validate): tasks marked holdout: true are excluded by default
|
|
29
|
-
and the skip count is reported; --include-holdout runs them. validate reports
|
|
30
|
-
the holdout count without running anything.
|
|
31
|
-
|
|
32
|
-
Runner selection (run): --runner option > AEVAL_RUNNER env var > "mock"
|
|
33
|
-
(forced "mock" for the built-in demo pack unless --runner is explicit).
|
|
34
|
-
Custom runners register via the "agent_eval.runners" entry-point group
|
|
35
|
-
(name → zero-arg factory returning an AgentRunner).
|
|
36
|
-
|
|
37
|
-
Trace vocabulary (run): --vocabulary option > AEVAL_TRACE_VOCABULARY env var >
|
|
38
|
-
"otel-genai". The value selects a built-in public-convention preset
|
|
39
|
-
(agent_eval.trace.known_vocabularies()); host-private attribute names still go
|
|
40
|
-
in through default_mapping(extra_entries) in library code, never through the CLI.
|
|
41
|
-
|
|
42
|
-
Storage (list/show/compare and run persistence): SQLite, ./aeval.db by
|
|
43
|
-
default; override with --db or the AEVAL_DB environment variable.
|
|
2
|
+
eval-suite 的实现体 —— 依赖 typer (发行包 `cli` 可选依赖组里的命令行依赖)。
|
|
3
|
+
|
|
4
|
+
本模块**不是**入口点: 入口是 `agent_eval/cli.py:main()`, 它必须能在缺少命令行
|
|
5
|
+
依赖时被正常导入并给出指引 (spec: cli)。所以本模块只在入口点确认依赖齐备之后
|
|
6
|
+
才被导入。命令语义与退出码见 `agent_eval.cli` 的模块文档。
|
|
44
7
|
"""
|
|
45
8
|
|
|
46
9
|
from __future__ import annotations
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""
|
|
2
|
+
eval-suite — the Aeval command line.
|
|
3
|
+
|
|
4
|
+
本模块是命令行入口点 (`pyproject [project.scripts]`),**不得在模块期导入
|
|
5
|
+
typer**:发行包无条件生成 `eval-suite` 可执行文件(PEP 621 的 `[project.scripts]`
|
|
6
|
+
是项目级表,无法按可选依赖声明,见 change 的 design D2),所以只装了 core 的人
|
|
7
|
+
手上也有这条命令。它必须在这里被说清楚缺什么,而不是抛一个指向内部依赖的
|
|
8
|
+
traceback (spec: cli)。真正的实现体在 `agent_eval/_cli_app.py`。
|
|
9
|
+
|
|
10
|
+
Commands:
|
|
11
|
+
run Execute a suite (default runner: built-in MockAgentRunner).
|
|
12
|
+
Exit 0 all passed / 1 failed tasks or baseline regression /
|
|
13
|
+
3 evaluation untrustworthy. --baseline adds a relative
|
|
14
|
+
regression gate against a stored run
|
|
15
|
+
validate Validate a suite without running it
|
|
16
|
+
list List runs or suites from the storage DB
|
|
17
|
+
show Show one run's details (--task drills into a single task);
|
|
18
|
+
reports valid/invalid/pending counts, pass@1 95% CI and
|
|
19
|
+
extrapolation markers
|
|
20
|
+
compare A/B compare two runs; overlapping 95% intervals read as
|
|
21
|
+
"not significant" with no directional verdict, and differing
|
|
22
|
+
statistics versions or evidence boundaries read as not comparable
|
|
23
|
+
power Sample-size planning: trials needed for a target resolution
|
|
24
|
+
(--delta with an assumed or measured baseline pass rate)
|
|
25
|
+
serve Serve the standalone API (/v1) via uvicorn
|
|
26
|
+
|
|
27
|
+
Exit codes: 0 放行 / 1 agent 表现未达标 / 2 用法错误 / 3 评测本身不可信 /
|
|
28
|
+
4 命令行依赖缺失(安装形态不对,见 `MISSING_CLI_DEPENDENCY_EXIT_CODE`)
|
|
29
|
+
|
|
30
|
+
Source forms (run/validate, spec: suite-distribution): a single suite.yaml
|
|
31
|
+
file (status quo); a pack directory or .tar.gz/.zip archive (integrity-checked
|
|
32
|
+
against manifest.json); a git URL (shallow clone into a temp dir — needs the
|
|
33
|
+
git executable); or the literal "demo" (the built-in starter pack shipped
|
|
34
|
+
inside the wheel; a local file/dir named demo wins with a hint). Temp dirs are
|
|
35
|
+
cleaned up when the command ends.
|
|
36
|
+
|
|
37
|
+
Holdout (run/validate): tasks marked holdout: true are excluded by default
|
|
38
|
+
and the skip count is reported; --include-holdout runs them. validate reports
|
|
39
|
+
the holdout count without running anything.
|
|
40
|
+
|
|
41
|
+
Runner selection (run): --runner option > AEVAL_RUNNER env var > "mock"
|
|
42
|
+
(forced "mock" for the built-in demo pack unless --runner is explicit).
|
|
43
|
+
Custom runners register via the "agent_eval.runners" entry-point group
|
|
44
|
+
(name → zero-arg factory returning an AgentRunner).
|
|
45
|
+
|
|
46
|
+
Trace vocabulary (run): --vocabulary option > AEVAL_TRACE_VOCABULARY env var >
|
|
47
|
+
"otel-genai". The value selects a built-in public-convention preset
|
|
48
|
+
(agent_eval.trace.known_vocabularies()); host-private attribute names still go
|
|
49
|
+
in through default_mapping(extra_entries) in library code, never through the CLI.
|
|
50
|
+
|
|
51
|
+
Storage (list/show/compare and run persistence): SQLite, ./aeval.db by
|
|
52
|
+
default; override with --db or the AEVAL_DB environment variable.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
from __future__ import annotations
|
|
56
|
+
|
|
57
|
+
import sys
|
|
58
|
+
|
|
59
|
+
# 与评测结果 (1/3) 和用法错误 (2) 都区分开,脚本可以只凭退出码判定"安装形态不对"
|
|
60
|
+
MISSING_CLI_DEPENDENCY_EXIT_CODE = 4
|
|
61
|
+
# 缺依赖指引的稳定标记:文本可判别,不需要去匹配 traceback
|
|
62
|
+
MISSING_CLI_DEPENDENCY_TOKEN = "error: missing-cli-dependency"
|
|
63
|
+
|
|
64
|
+
# `cli` 可选依赖组实际提供的顶层模块 —— 只有缺这些才给安装指引
|
|
65
|
+
_CLI_EXTRA_MODULES = frozenset({"typer", "rich"})
|
|
66
|
+
|
|
67
|
+
_DISTRIBUTION_NAME = "aeval-framework"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def missing_dependency_guidance() -> str:
|
|
71
|
+
"""缺命令行依赖时的可行动指引 (spec: cli「未安装 CLI 依赖时命令行入口必须给出可行动指引」)。"""
|
|
72
|
+
return (
|
|
73
|
+
f"{MISSING_CLI_DEPENDENCY_TOKEN}\n"
|
|
74
|
+
"eval-suite needs the command-line dependencies, which are not installed\n"
|
|
75
|
+
f"in the {_DISTRIBUTION_NAME} environment. The library itself is fine — only\n"
|
|
76
|
+
"this command is missing its extras.\n"
|
|
77
|
+
"\n"
|
|
78
|
+
f" pip install \"{_DISTRIBUTION_NAME}[cli]\"\n"
|
|
79
|
+
"\n"
|
|
80
|
+
f"(from a source checkout: pip install -e \".[cli]\")\n"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _is_missing_cli_dependency(exc: BaseException) -> bool:
|
|
85
|
+
"""区分"命令行依赖没装"与"安装坏了/代码本身有 bug"——后者不能被指引用盖住。"""
|
|
86
|
+
missing = getattr(exc, "name", None) or ""
|
|
87
|
+
return missing.partition(".")[0] in _CLI_EXTRA_MODULES
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def main() -> int | None:
|
|
91
|
+
"""Console-script entry point (pyproject [project.scripts])."""
|
|
92
|
+
try:
|
|
93
|
+
from agent_eval._cli_app import main as _cli_main
|
|
94
|
+
except ImportError as exc:
|
|
95
|
+
if not _is_missing_cli_dependency(exc):
|
|
96
|
+
raise
|
|
97
|
+
sys.stderr.write(missing_dependency_guidance())
|
|
98
|
+
return MISSING_CLI_DEPENDENCY_EXIT_CODE
|
|
99
|
+
return _cli_main()
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
if __name__ == "__main__":
|
|
103
|
+
sys.exit(main())
|
|
@@ -10,7 +10,7 @@ import re
|
|
|
10
10
|
|
|
11
11
|
from typer.testing import CliRunner
|
|
12
12
|
|
|
13
|
-
from agent_eval.
|
|
13
|
+
from agent_eval._cli_app import app
|
|
14
14
|
|
|
15
15
|
runner = CliRunner()
|
|
16
16
|
|
|
@@ -699,8 +699,8 @@ class TestServeSmoke:
|
|
|
699
699
|
def test_serve_defaults_are_loopback(self):
|
|
700
700
|
import inspect
|
|
701
701
|
|
|
702
|
-
from agent_eval import
|
|
702
|
+
from agent_eval import _cli_app
|
|
703
703
|
|
|
704
|
-
sig = inspect.signature(
|
|
704
|
+
sig = inspect.signature(_cli_app.serve)
|
|
705
705
|
default = sig.parameters["host"].default
|
|
706
706
|
assert getattr(default, "default", default) == "127.0.0.1"
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""core-only 安装形态下命令行入口的指引 (change fix-version-drift-and-cli-entry-guard,
|
|
2
|
+
组 4 / spec: cli「未安装 CLI 依赖时命令行入口必须给出可行动指引」).
|
|
3
|
+
|
|
4
|
+
全部用子进程跑真入口点 —— 引导路径的成立与否取决于"模块导入期碰不到 typer",
|
|
5
|
+
进程内 import 早就把模块缓存好了,测不到那一层。
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
import subprocess
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
import pytest
|
|
15
|
+
|
|
16
|
+
import agent_eval
|
|
17
|
+
from agent_eval.cli import (
|
|
18
|
+
_CLI_EXTRA_MODULES,
|
|
19
|
+
MISSING_CLI_DEPENDENCY_EXIT_CODE,
|
|
20
|
+
MISSING_CLI_DEPENDENCY_TOKEN,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
PACKAGE_ROOT = Path(agent_eval.__file__).parent
|
|
24
|
+
CHILD_ENV = {**os.environ, "PYTHONIOENCODING": "utf-8", "PYTHONPATH": str(PACKAGE_ROOT.parent)}
|
|
25
|
+
|
|
26
|
+
# 屏蔽指定顶层模块后再走真入口点,模拟"该依赖没装"
|
|
27
|
+
GUARD_PROBE = """
|
|
28
|
+
import sys
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class _Blocker:
|
|
32
|
+
def __init__(self, names):
|
|
33
|
+
self.names = set(names)
|
|
34
|
+
|
|
35
|
+
def find_spec(self, name, path=None, target=None):
|
|
36
|
+
if name in self.names or name.partition(".")[0] in self.names:
|
|
37
|
+
raise ModuleNotFoundError(f"No module named {{name!r}}", name=name)
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
sys.meta_path.insert(0, _Blocker([{blocked!r}]))
|
|
42
|
+
sys.argv = ["eval-suite", *{argv!r}]
|
|
43
|
+
from agent_eval.cli import main
|
|
44
|
+
|
|
45
|
+
sys.exit(main())
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
CLI_ARGS = ["--help", "run --help", "validate --help", "list --help", "show --help",
|
|
49
|
+
"compare --help", "power --help", "serve --help", "extensions"]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _run_python(body: str, extra_env: dict | None = None, **fmt) -> subprocess.CompletedProcess:
|
|
53
|
+
env = {**CHILD_ENV, **(extra_env or {})}
|
|
54
|
+
return subprocess.run(
|
|
55
|
+
[sys.executable, "-c", body.format(**fmt)],
|
|
56
|
+
capture_output=True, text=True, encoding="utf-8", env=env,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _module_entry(module: str, args: str, extra_env: dict | None = None) -> subprocess.CompletedProcess:
|
|
61
|
+
env = {**CHILD_ENV, **(extra_env or {})}
|
|
62
|
+
return subprocess.run(
|
|
63
|
+
[sys.executable, "-m", module, *args.split()],
|
|
64
|
+
capture_output=True, text=True, encoding="utf-8", env=env,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# 两条路径都在 -c 子进程里跑并把 argv[0] 钉成 eval-suite —— 与真 console script 同形,
|
|
69
|
+
# click 由此算出同一个 prog_name,剩下的字节差只可能来自引导层。
|
|
70
|
+
RUN_ENTRY = """
|
|
71
|
+
import sys
|
|
72
|
+
sys.argv = ["eval-suite", *{argv!r}]
|
|
73
|
+
from agent_eval.cli import main
|
|
74
|
+
|
|
75
|
+
sys.exit(main())
|
|
76
|
+
"""
|
|
77
|
+
RUN_IMPL = """
|
|
78
|
+
import sys
|
|
79
|
+
sys.argv = ["eval-suite", *{argv!r}]
|
|
80
|
+
from agent_eval._cli_app import app
|
|
81
|
+
|
|
82
|
+
app()
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class TestNormalPathIsUntouched:
|
|
87
|
+
"""Scenario: 已装齐依赖时零影响 —— 引导路径完全不介入。"""
|
|
88
|
+
|
|
89
|
+
def test_entry_module_import_alone_never_touches_typer(self):
|
|
90
|
+
"""改动前 `import agent_eval.cli` 就会 ModuleNotFoundError; 入口模块必须与依赖解耦。"""
|
|
91
|
+
body = """
|
|
92
|
+
import sys
|
|
93
|
+
import agent_eval.cli
|
|
94
|
+
print("typer" in sys.modules, "agent_eval._cli_app" in sys.modules)
|
|
95
|
+
"""
|
|
96
|
+
resp = _run_python(body)
|
|
97
|
+
assert resp.returncode == 0, resp.stderr
|
|
98
|
+
assert resp.stdout.strip() == "False False", resp.stdout
|
|
99
|
+
|
|
100
|
+
def test_bootstrap_adds_zero_bytes_and_zero_exit_code_shift(self):
|
|
101
|
+
"""主闸: 走入口点的输出与退出码, 和直接跑实现体逐字节相同。"""
|
|
102
|
+
for args in CLI_ARGS:
|
|
103
|
+
argv = args.split()
|
|
104
|
+
through_entry = _run_python(RUN_ENTRY, argv=argv)
|
|
105
|
+
direct = _run_python(RUN_IMPL, argv=argv)
|
|
106
|
+
label = f"[{args}]"
|
|
107
|
+
assert through_entry.stdout == direct.stdout, f"{label} stdout 变了"
|
|
108
|
+
assert through_entry.stderr == direct.stderr, f"{label} stderr 变了"
|
|
109
|
+
assert through_entry.returncode == direct.returncode, f"{label} 退出码变了"
|
|
110
|
+
|
|
111
|
+
def test_guidance_text_is_absent_when_dependencies_are_installed(self):
|
|
112
|
+
from agent_eval.cli import missing_dependency_guidance
|
|
113
|
+
|
|
114
|
+
guidance_word = "pip install"
|
|
115
|
+
for args in CLI_ARGS:
|
|
116
|
+
resp = _module_entry("agent_eval.cli", args)
|
|
117
|
+
assert guidance_word not in resp.stdout + resp.stderr, args
|
|
118
|
+
assert guidance_word in missing_dependency_guidance()
|
|
119
|
+
|
|
120
|
+
def test_run_demo_still_exits_zero_through_the_entry(self, tmp_path):
|
|
121
|
+
"""正常路径不止 help: 真跑一次内置套件仍按既有语义给码 (0=放行)。"""
|
|
122
|
+
resp = _module_entry(
|
|
123
|
+
"agent_eval.cli", "run demo", extra_env={"AEVAL_DB": str(tmp_path / "demo.db")}
|
|
124
|
+
)
|
|
125
|
+
assert resp.returncode == 0, resp.stdout + resp.stderr
|
|
126
|
+
assert "Status: completed" in resp.stdout
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class TestMissingCliDependencyGuidance:
|
|
130
|
+
"""Scenario: 裸装后执行命令行 —— 得到的是指引,不是指向内部依赖的 traceback。"""
|
|
131
|
+
|
|
132
|
+
def test_missing_typer_yields_actionable_guidance(self, tmp_path):
|
|
133
|
+
resp = _run_python(GUARD_PROBE, blocked="typer", argv=["--help"])
|
|
134
|
+
out = resp.stdout + resp.stderr
|
|
135
|
+
assert resp.returncode != 0
|
|
136
|
+
assert "pip install" in out and "[cli]" in out, out
|
|
137
|
+
assert "eval-suite" in out
|
|
138
|
+
|
|
139
|
+
def test_output_is_not_a_typer_traceback(self):
|
|
140
|
+
resp = _run_python(GUARD_PROBE, blocked="typer", argv=["--help"])
|
|
141
|
+
out = resp.stdout + resp.stderr
|
|
142
|
+
assert "Traceback" not in out, out
|
|
143
|
+
assert "No module named 'typer'" not in out, out
|
|
144
|
+
assert "agent_eval/_cli_app.py" not in out.replace("\\", "/"), out
|
|
145
|
+
|
|
146
|
+
def test_handled_module_set_matches_the_declared_cli_extra(self):
|
|
147
|
+
"""守护集合不许和发行声明漂移 —— `cli` 组改了这里就得改。"""
|
|
148
|
+
tomllib = pytest.importorskip("tomllib")
|
|
149
|
+
pyproject = PACKAGE_ROOT.parent.parent / "pyproject.toml"
|
|
150
|
+
if not pyproject.is_file():
|
|
151
|
+
pytest.skip("发行环境不随包带 pyproject.toml")
|
|
152
|
+
declared = tomllib.loads(pyproject.read_text(encoding="utf-8"))["project"][
|
|
153
|
+
"optional-dependencies"
|
|
154
|
+
]["cli"]
|
|
155
|
+
tops = {re.match(r"\s*([A-Za-z0-9._-]+)", r).group(1) for r in declared}
|
|
156
|
+
assert tops == set(_CLI_EXTRA_MODULES), (tops, set(_CLI_EXTRA_MODULES))
|
|
157
|
+
|
|
158
|
+
def test_rich_is_not_an_import_time_dependency_of_the_entry(self):
|
|
159
|
+
"""记录实测边界: 缺 typer 才是裸装形态的真实故障点 (rich 由 typer 在渲染期拉入)。"""
|
|
160
|
+
resp = _run_python(GUARD_PROBE, blocked="rich", argv=["--help"])
|
|
161
|
+
assert "No module named 'rich'" in resp.stderr or resp.returncode != 0
|
|
162
|
+
assert resp.returncode != MISSING_CLI_DEPENDENCY_EXIT_CODE
|
|
163
|
+
|
|
164
|
+
def test_unrelated_import_failure_is_not_masked_by_the_guidance(self):
|
|
165
|
+
"""安装坏了/代码自身有 bug 时不许用"请装 [cli]"盖住 —— 那会把人引到错路上。"""
|
|
166
|
+
resp = _run_python(GUARD_PROBE, blocked="agent_eval.core.types", argv=["--help"])
|
|
167
|
+
out = resp.stdout + resp.stderr
|
|
168
|
+
assert resp.returncode != 0
|
|
169
|
+
assert "Traceback" in out, "非依赖缺失的导入失败必须照原样抛出来"
|
|
170
|
+
assert "pip install" not in out, out
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class TestExitCodeIsScriptDiscriminable:
|
|
174
|
+
def test_missing_dependency_code_is_distinct_from_every_outcome(self):
|
|
175
|
+
"""4.4: 成功与两种失败不共用一个码 (0 放行 / 1 表现 / 2 用法 / 3 不可信 / 4 缺依赖)。"""
|
|
176
|
+
assert MISSING_CLI_DEPENDENCY_EXIT_CODE == 4
|
|
177
|
+
assert len({MISSING_CLI_DEPENDENCY_EXIT_CODE, 0, 1, 2, 3}) == 5
|
|
178
|
+
|
|
179
|
+
def test_missing_dependency_run_reports_the_documented_code(self):
|
|
180
|
+
resp = _run_python(GUARD_PROBE, blocked="typer", argv=["--help"])
|
|
181
|
+
assert resp.returncode == MISSING_CLI_DEPENDENCY_EXIT_CODE
|
|
182
|
+
|
|
183
|
+
def test_guidance_carries_a_stable_machine_readable_token(self):
|
|
184
|
+
resp = _run_python(GUARD_PROBE, blocked="typer", argv=["--help"])
|
|
185
|
+
out = resp.stdout + resp.stderr
|
|
186
|
+
assert MISSING_CLI_DEPENDENCY_TOKEN in out
|
|
187
|
+
# 脚本只需匹配这一行,不需要读安装表达式的具体写法
|
|
188
|
+
assert out.strip().splitlines()[0].startswith(MISSING_CLI_DEPENDENCY_TOKEN)
|
|
189
|
+
|
|
190
|
+
def test_normal_path_never_emits_the_token(self):
|
|
191
|
+
for args in CLI_ARGS:
|
|
192
|
+
resp = _module_entry("agent_eval.cli", args)
|
|
193
|
+
assert MISSING_CLI_DEPENDENCY_TOKEN not in resp.stdout + resp.stderr, args
|
|
@@ -221,7 +221,7 @@ def test_same_name_conflict_raises_listing_both_sources(monkeypatch):
|
|
|
221
221
|
|
|
222
222
|
def test_cli_extensions_listing_shows_builtin_and_discovered(monkeypatch):
|
|
223
223
|
"""eval-suite extensions: 内置 + 被发现的 (标注来源包)。"""
|
|
224
|
-
from agent_eval.
|
|
224
|
+
from agent_eval._cli_app import app
|
|
225
225
|
|
|
226
226
|
patch_entry_points(
|
|
227
227
|
monkeypatch,
|
|
@@ -240,8 +240,8 @@ def test_cli_extensions_listing_shows_builtin_and_discovered(monkeypatch):
|
|
|
240
240
|
|
|
241
241
|
def test_cli_and_rest_meta_list_same_names(monkeypatch):
|
|
242
242
|
"""CLI 清单与 /meta 能力清单给出的自定义判据名字集合相同。"""
|
|
243
|
+
from agent_eval._cli_app import app
|
|
243
244
|
from agent_eval.api.app import meta_payload
|
|
244
|
-
from agent_eval.cli import app
|
|
245
245
|
|
|
246
246
|
patch_entry_points(
|
|
247
247
|
monkeypatch,
|
|
@@ -12,7 +12,7 @@ from pathlib import Path
|
|
|
12
12
|
import pytest
|
|
13
13
|
from typer.testing import CliRunner
|
|
14
14
|
|
|
15
|
-
from agent_eval.
|
|
15
|
+
from agent_eval._cli_app import app
|
|
16
16
|
from agent_eval.core.runner import EvalRunner, NoRunnableTasksError
|
|
17
17
|
from agent_eval.core.suite import SuiteLoadError, load_suite
|
|
18
18
|
from agent_eval.core.types import RunResult
|
|
@@ -401,7 +401,7 @@ class TestCliSourceForms:
|
|
|
401
401
|
def test_run_tar_gz_source_completes(self, tmp_path):
|
|
402
402
|
from typer.testing import CliRunner
|
|
403
403
|
|
|
404
|
-
from agent_eval.
|
|
404
|
+
from agent_eval._cli_app import app
|
|
405
405
|
|
|
406
406
|
root = _make_pack(tmp_path)
|
|
407
407
|
archive = _tar_pack(tmp_path, root, name="suite-pack.tar.gz")
|
|
@@ -417,7 +417,7 @@ class TestCliSourceForms:
|
|
|
417
417
|
|
|
418
418
|
from typer.testing import CliRunner
|
|
419
419
|
|
|
420
|
-
from agent_eval.
|
|
420
|
+
from agent_eval._cli_app import app
|
|
421
421
|
|
|
422
422
|
repo = tmp_path / "suite-repo"
|
|
423
423
|
repo.mkdir()
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""版本出口同源 (change fix-version-drift-and-cli-entry-guard, 组 2 / spec: rest-api).
|
|
2
|
+
|
|
3
|
+
钉住三件事:
|
|
4
|
+
1. 同一进程内接口文档版本 / 能力清单版本 / 版本响应头三处相等;
|
|
5
|
+
2. 寄宿挂载形态与独立部署形态读到的接口文档版本相同;
|
|
6
|
+
3. `api/` 下不存在第二处写死的版本号字面量 —— 静态扫描, 防的是"新增第二处"。
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import ast
|
|
10
|
+
import re
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from starlette.testclient import TestClient
|
|
14
|
+
|
|
15
|
+
import agent_eval.api
|
|
16
|
+
from agent_eval.api.app import create_app, meta_payload, package_version
|
|
17
|
+
from agent_eval.api.standalone import create_standalone_app
|
|
18
|
+
|
|
19
|
+
VERSION_LITERAL = re.compile(r"\d+\.\d+\.\d+[\w.+-]*")
|
|
20
|
+
API_ROOT = Path(next(iter(agent_eval.api.__path__)))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _doc_string_nodes(tree: ast.AST) -> set[int]:
|
|
24
|
+
"""docstring 常量节点 id —— 叙述历史版本的文字不是版本声明。"""
|
|
25
|
+
out = set()
|
|
26
|
+
for node in ast.walk(tree):
|
|
27
|
+
if isinstance(node, ast.Module | ast.ClassDef | ast.FunctionDef | ast.AsyncFunctionDef):
|
|
28
|
+
first = node.body[0] if node.body else None
|
|
29
|
+
if (
|
|
30
|
+
isinstance(first, ast.Expr)
|
|
31
|
+
and isinstance(first.value, ast.Constant)
|
|
32
|
+
and isinstance(first.value.value, str)
|
|
33
|
+
):
|
|
34
|
+
out.add(id(first.value))
|
|
35
|
+
return out
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _version_literals_in(source: str, path: Path) -> list[str]:
|
|
39
|
+
tree = ast.parse(source)
|
|
40
|
+
docstrings = _doc_string_nodes(tree)
|
|
41
|
+
offenders = []
|
|
42
|
+
for node in ast.walk(tree):
|
|
43
|
+
if not isinstance(node, ast.Constant) or id(node) in docstrings:
|
|
44
|
+
continue
|
|
45
|
+
if isinstance(node.value, str) and VERSION_LITERAL.fullmatch(node.value):
|
|
46
|
+
loc = path.relative_to(API_ROOT.parent).as_posix()
|
|
47
|
+
offenders.append(f"{loc}:{node.lineno} = {node.value!r}")
|
|
48
|
+
return offenders
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class TestVersionOutletsSameSource:
|
|
52
|
+
def test_openapi_meta_and_header_agree_in_one_process(self):
|
|
53
|
+
"""Scenario: 同一进程对外声明的版本只有一个来源 (独立部署形态三出口齐全)。"""
|
|
54
|
+
app = create_standalone_app()
|
|
55
|
+
doc_version = app.openapi()["info"]["version"]
|
|
56
|
+
with TestClient(app) as client:
|
|
57
|
+
meta_version = client.get("/v1/meta").json()["version"]
|
|
58
|
+
header = client.get("/v1/health").headers["X-Aeval-Version"]
|
|
59
|
+
|
|
60
|
+
resolved = package_version()
|
|
61
|
+
assert doc_version == resolved, doc_version
|
|
62
|
+
assert meta_version == resolved, meta_version
|
|
63
|
+
assert header == resolved, header
|
|
64
|
+
|
|
65
|
+
def test_hosted_openapi_and_meta_agree(self):
|
|
66
|
+
"""寄宿形态自己那两处出口同样同源 (它没有版本头, 那是独立部署专属)。"""
|
|
67
|
+
app = create_app()
|
|
68
|
+
resolved = package_version()
|
|
69
|
+
assert app.openapi()["info"]["version"] == resolved
|
|
70
|
+
assert meta_payload()["version"] == resolved
|
|
71
|
+
|
|
72
|
+
def test_mounted_openapi_served_under_prefix_matches(self):
|
|
73
|
+
"""独立部署里内层 app 被挂到 /v1, 它自己的接口文档也是同一版本。"""
|
|
74
|
+
with TestClient(create_standalone_app()) as client:
|
|
75
|
+
resp = client.get("/v1/openapi.json")
|
|
76
|
+
assert resp.status_code == 200
|
|
77
|
+
assert resp.json()["info"]["version"] == package_version()
|
|
78
|
+
|
|
79
|
+
def test_bumping_the_package_version_moves_every_outlet(self):
|
|
80
|
+
"""Scenario: 升级包版本后所有出口同步 —— 不改任何接口层代码。"""
|
|
81
|
+
import agent_eval.api.app as app_module
|
|
82
|
+
import agent_eval.api.standalone as standalone_module
|
|
83
|
+
|
|
84
|
+
bumped = "9.9.9"
|
|
85
|
+
original = app_module.package_version
|
|
86
|
+
assert package_version() != bumped
|
|
87
|
+
app_module.package_version = lambda: bumped
|
|
88
|
+
standalone_module.package_version = lambda: bumped
|
|
89
|
+
try:
|
|
90
|
+
app = create_standalone_app()
|
|
91
|
+
with TestClient(app) as client:
|
|
92
|
+
assert app.openapi()["info"]["version"] == bumped
|
|
93
|
+
assert client.get("/v1/meta").json()["version"] == bumped
|
|
94
|
+
assert client.get("/v1/health").headers["X-Aeval-Version"] == bumped
|
|
95
|
+
assert create_app().openapi()["info"]["version"] == bumped
|
|
96
|
+
assert meta_payload()["version"] == bumped
|
|
97
|
+
finally:
|
|
98
|
+
app_module.package_version = original
|
|
99
|
+
standalone_module.package_version = original
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class TestDeploymentFormsAgree:
|
|
103
|
+
def test_mounted_and_standalone_read_the_same_doc_version(self):
|
|
104
|
+
"""Scenario: 两种部署形态给出同一版本 —— 挂载形态不得成为例外。"""
|
|
105
|
+
hosted = create_app().openapi()["info"]["version"]
|
|
106
|
+
standalone = create_standalone_app().openapi()["info"]["version"]
|
|
107
|
+
assert hosted == standalone == package_version()
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class TestNoSecondVersionLiteral:
|
|
111
|
+
def test_api_source_is_found_by_the_scan(self):
|
|
112
|
+
"""一条永远绿的守护测试比没有更糟 —— 先确认扫描真的覆盖了 api/ 源码。"""
|
|
113
|
+
scanned = sorted(p.name for p in API_ROOT.rglob("*.py"))
|
|
114
|
+
assert {"app.py", "standalone.py"} <= set(scanned), scanned
|
|
115
|
+
|
|
116
|
+
def test_guard_actually_catches_a_planted_literal(self):
|
|
117
|
+
"""守护测试自己得是活的: 手工植入一处字面量必须被抓到并报出位置。"""
|
|
118
|
+
planted = 'app = FastAPI(title="Aeval API", version="0.1.0")\n'
|
|
119
|
+
offenders = _version_literals_in(planted, API_ROOT / "planted.py")
|
|
120
|
+
assert len(offenders) == 1, offenders
|
|
121
|
+
assert offenders[0].endswith("planted.py:1 = '0.1.0'"), offenders
|
|
122
|
+
|
|
123
|
+
def test_api_layer_has_no_hardcoded_version_string(self):
|
|
124
|
+
"""Scenario: 出现第二处版本字面量时测试失败并报出该位置。"""
|
|
125
|
+
offenders = []
|
|
126
|
+
for path in sorted(API_ROOT.rglob("*.py")):
|
|
127
|
+
offenders += _version_literals_in(path.read_text(encoding="utf-8"), path)
|
|
128
|
+
|
|
129
|
+
assert not offenders, (
|
|
130
|
+
"api/ 下出现写死的版本号字面量, 必须改走 package_version(): " + "; ".join(offenders)
|
|
131
|
+
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/dataset/sources/llm_generator.py
RENAMED
|
File without changes
|
|
File without changes
|
{aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/dataset/sources/regression.py
RENAMED
|
File without changes
|
{aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/dataset/sources/trace_mining.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aeval_framework-0.3.0 → aeval_framework-0.3.1}/src/agent_eval/graders/presentation_probes.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|