agent-comparability 0.5.2__tar.gz → 0.5.4__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.
- {agent_comparability-0.5.2/src/agent_comparability.egg-info → agent_comparability-0.5.4}/PKG-INFO +43 -6
- {agent_comparability-0.5.2 → agent_comparability-0.5.4}/README.md +38 -5
- {agent_comparability-0.5.2 → agent_comparability-0.5.4}/pyproject.toml +29 -23
- agent_comparability-0.5.4/src/agent_comparability/__init__.py +3 -0
- agent_comparability-0.5.4/src/agent_comparability/__main__.py +131 -0
- {agent_comparability-0.5.2 → agent_comparability-0.5.4}/src/agent_comparability/core.py +10 -5
- {agent_comparability-0.5.2 → agent_comparability-0.5.4}/src/agent_comparability/data/SPEC.md +7 -4
- agent_comparability-0.5.4/src/agent_comparability/data/cases-0.4.json +16 -0
- {agent_comparability-0.5.2 → agent_comparability-0.5.4}/src/agent_comparability/data/cdr.schema.json +1 -1
- agent_comparability-0.5.4/src/agent_comparability/evidence.py +109 -0
- agent_comparability-0.5.4/src/agent_comparability/inspect_log.py +283 -0
- {agent_comparability-0.5.2 → agent_comparability-0.5.4/src/agent_comparability.egg-info}/PKG-INFO +43 -6
- {agent_comparability-0.5.2 → agent_comparability-0.5.4}/src/agent_comparability.egg-info/SOURCES.txt +8 -0
- agent_comparability-0.5.4/tests/test_cli.py +152 -0
- agent_comparability-0.5.4/tests/test_evidence.py +126 -0
- {agent_comparability-0.5.2 → agent_comparability-0.5.4}/tests/test_gate_regressions.py +7 -7
- agent_comparability-0.5.4/tests/test_inspect_log.py +352 -0
- agent_comparability-0.5.4/tests/test_scenarios_001.py +54 -0
- agent_comparability-0.5.2/src/agent_comparability/__init__.py +0 -3
- {agent_comparability-0.5.2 → agent_comparability-0.5.4}/LICENSE +0 -0
- {agent_comparability-0.5.2 → agent_comparability-0.5.4}/NOTICE +0 -0
- {agent_comparability-0.5.2 → agent_comparability-0.5.4}/setup.cfg +0 -0
- {agent_comparability-0.5.2 → agent_comparability-0.5.4}/src/agent_comparability/data/boundary-cases.json +0 -0
- {agent_comparability-0.5.2 → agent_comparability-0.5.4}/src/agent_comparability/data/frozen-cases-a.json +0 -0
- {agent_comparability-0.5.2 → agent_comparability-0.5.4}/src/agent_comparability/py.typed +0 -0
- {agent_comparability-0.5.2 → agent_comparability-0.5.4}/src/agent_comparability.egg-info/dependency_links.txt +0 -0
- {agent_comparability-0.5.2 → agent_comparability-0.5.4}/src/agent_comparability.egg-info/requires.txt +0 -0
- {agent_comparability-0.5.2 → agent_comparability-0.5.4}/src/agent_comparability.egg-info/top_level.txt +0 -0
- {agent_comparability-0.5.2 → agent_comparability-0.5.4}/tests/test_properties.py +0 -0
- {agent_comparability-0.5.2 → agent_comparability-0.5.4}/tests/test_smoke.py +0 -0
{agent_comparability-0.5.2/src/agent_comparability.egg-info → agent_comparability-0.5.4}/PKG-INFO
RENAMED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent-comparability
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.4
|
|
4
4
|
Summary: Agent evaluation comparability semantics: spec + conformance suite + thin reference implementation
|
|
5
5
|
License: Apache-2.0
|
|
6
|
+
Project-URL: Homepage, https://github.com/Rethymus/agent-comparability
|
|
7
|
+
Project-URL: Repository, https://github.com/Rethymus/agent-comparability
|
|
8
|
+
Project-URL: Issues, https://github.com/Rethymus/agent-comparability/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/Rethymus/agent-comparability/blob/main/CHANGELOG.md
|
|
6
10
|
Requires-Python: >=3.10
|
|
7
11
|
Description-Content-Type: text/markdown
|
|
8
12
|
License-File: LICENSE
|
|
@@ -25,6 +29,7 @@ Dynamic: license-file
|
|
|
25
29
|
[](https://github.com/Rethymus/agent-comparability/actions/workflows/ci.yml)
|
|
26
30
|
[](LICENSE)
|
|
27
31
|
[](pyproject.toml)
|
|
32
|
+
[](https://pypi.org/project/agent-comparability/)
|
|
28
33
|
|
|
29
34
|
**English** | [简体中文](README.zh-CN.md)
|
|
30
35
|
|
|
@@ -74,6 +79,12 @@ Agent Comparability is a framework-independent **specification** ([spec/SPEC.md]
|
|
|
74
79
|
a frozen **conformance suite**, and a thin **reference implementation** that adjudicates
|
|
75
80
|
*comparability* of agent evaluation records — never the scores themselves.
|
|
76
81
|
|
|
82
|
+
**Does it actually refuse unsupported claims?** See
|
|
83
|
+
[experiments/scenarios_001/RECORD.md](experiments/scenarios_001/RECORD.md): four real-pipeline
|
|
84
|
+
scenarios (real `.eval` logs → converter → core) covering all four verdicts — including the
|
|
85
|
+
confounded comparison and the missing-trajectory case, both correctly **refused** rather than
|
|
86
|
+
packaged as supported conclusions.
|
|
87
|
+
|
|
77
88
|
**Who is this for**: developers comparing agent/prompt/harness evaluation results,
|
|
78
89
|
benchmark authors documenting what conclusions their data supports, and teams building
|
|
79
90
|
reliability pipelines on top of existing eval frameworks.
|
|
@@ -97,6 +108,8 @@ Three failure modes silently corrupt agent-evaluation conclusions:
|
|
|
97
108
|
| **Evidence gaps** | Missing traces quietly inflate success rates; rankings reverse across harnesses |
|
|
98
109
|
|
|
99
110
|
These boundaries should be **machine-checkable**, not vibes. That is what this project ships.
|
|
111
|
+
How this relates to benchmark-rigor and transcript-audit work (verified literature anchors):
|
|
112
|
+
see [docs/positioning.md](docs/positioning.md).
|
|
100
113
|
|
|
101
114
|
## 3. What Agent Comparability Does
|
|
102
115
|
|
|
@@ -153,10 +166,30 @@ pip install -e .[suite] # + suite basics (pyyaml, jsonschema)
|
|
|
153
166
|
pip install -e .[suite,inspect,pydantic] # FULL conformance run — required by run_suite.py
|
|
154
167
|
```
|
|
155
168
|
|
|
156
|
-
Requires Python ≥ 3.10. No Docker, no model keys.
|
|
169
|
+
Requires Python ≥ 3.10. No Docker, no model keys. Note: the core package supports 3.10+;
|
|
170
|
+
the optional `inspect`/`pydantic` extras pull dependencies that require Python ≥ 3.11 —
|
|
171
|
+
on 3.10 pip resolves older versions of them.
|
|
157
172
|
|
|
158
173
|
## 7. Quickstart
|
|
159
174
|
|
|
175
|
+
**Command line** (offline, zero extra dependencies — input contract below):
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
python -m agent_comparability examples/demo-case.json --claim score_comparison
|
|
179
|
+
# → CDR JSON on stdout (exit 0). INADMISSIBLE / PARTIAL / INCONCLUSIVE are verdicts,
|
|
180
|
+
# not program failures. Invalid input (bad JSON / missing or mistyped fields) →
|
|
181
|
+
# exit 2 with a path-precise diagnostic — never silently converted into a verdict.
|
|
182
|
+
# -o cdr.json writes to a file instead. The demo case is SYNTHETIC/illustrative.
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Input contract (`{"id": str, "records": [{"task_id", "harness", "model", "reward":
|
|
186
|
+
number, "reward_status", ...optional}]}`); unknown record fields are ignored and
|
|
187
|
+
recorded in `unknown_conditions` (SPEC §5). Add `--manifest out.manifest.json` to
|
|
188
|
+
also emit an Evidence Manifest documenting artifact digests and evidence trust
|
|
189
|
+
states (never auto-upgraded; see [docs/evidence-manifest.md](docs/evidence-manifest.md)).
|
|
190
|
+
|
|
191
|
+
**Python API**:
|
|
192
|
+
|
|
160
193
|
```python
|
|
161
194
|
from agent_comparability import adjudicate
|
|
162
195
|
|
|
@@ -222,7 +255,7 @@ CDR — actual output:
|
|
|
222
255
|
"rec:t1/m1/h1",
|
|
223
256
|
"rec:t1/m1/h2"
|
|
224
257
|
],
|
|
225
|
-
"policy_version": "cmp/0.
|
|
258
|
+
"policy_version": "cmp/0.4.0",
|
|
226
259
|
"limitations": [
|
|
227
260
|
"摘要一致仅证明未同步修改,不构成抗恶意篡改(单写信任域)"
|
|
228
261
|
]
|
|
@@ -281,10 +314,14 @@ Both wrap the *same* core (no semantic duplication). Inspect scorer uses
|
|
|
281
314
|
The Pydantic evaluator checks the **task's real output** (`ctx.output`) against
|
|
282
315
|
frozen expectations — it never re-adjudicates inside the evaluator — and the
|
|
283
316
|
adapter's exit code derives from the full 22-pair `report` (cases + failures).
|
|
317
|
+
For **real Inspect EvalLog files** (not frozen cases) use the converter:
|
|
318
|
+
`python -m agent_comparability.inspect_log run.eval --claim … --status …`
|
|
319
|
+
(official APIs, explicit mapping policy, no silent guessing — see
|
|
320
|
+
[docs/inspect-log.md](docs/inspect-log.md)).
|
|
284
321
|
|
|
285
322
|
## 12. Specification & Versioning
|
|
286
323
|
|
|
287
|
-
Three distinct lines: software tag (e.g. `v0.5.0`), specification `cmp/0.
|
|
324
|
+
Three distinct lines: software tag (e.g. `v0.5.0`), specification `cmp/0.4.0`
|
|
288
325
|
(inside SPEC), and `agent_comparability.__version__`. Judgment-table changes
|
|
289
326
|
require a spec major bump (SPEC §5). See [CHANGELOG](CHANGELOG.md) for the full tag lineage
|
|
290
327
|
(v0.3.1–v0.3.6 are deprecated candidates kept immutably for the record).
|
|
@@ -318,13 +355,13 @@ No — no network, no credentials, no Docker. It only ever sees the records you
|
|
|
318
355
|
|
|
319
356
|
**Which versions should I depend on?**
|
|
320
357
|
Package `v0.5.0+` (version lines unified and CI-enforced since then); specification
|
|
321
|
-
`cmp/0.
|
|
358
|
+
`cmp/0.4.0` evolves independently — see [§12](#12-specification--versioning).
|
|
322
359
|
|
|
323
360
|
## Project status
|
|
324
361
|
|
|
325
362
|
| Component | Status |
|
|
326
363
|
|---|---|
|
|
327
|
-
| Specification `cmp/0.
|
|
364
|
+
| Specification `cmp/0.4.0` | Stable — frozen conformance set anchors it |
|
|
328
365
|
| Claim types | `score_comparison`, `causal`, `descriptive_registration` (+ unknown → `INCONCLUSIVE`) |
|
|
329
366
|
| Conformance cases | 22 frozen pairs (A + B sets), three separated metrics, all green in CI |
|
|
330
367
|
| Adapters | Inspect AI ✓ · Pydantic Evals ✓ |
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
[](https://github.com/Rethymus/agent-comparability/actions/workflows/ci.yml)
|
|
8
8
|
[](LICENSE)
|
|
9
9
|
[](pyproject.toml)
|
|
10
|
+
[](https://pypi.org/project/agent-comparability/)
|
|
10
11
|
|
|
11
12
|
**English** | [简体中文](README.zh-CN.md)
|
|
12
13
|
|
|
@@ -56,6 +57,12 @@ Agent Comparability is a framework-independent **specification** ([spec/SPEC.md]
|
|
|
56
57
|
a frozen **conformance suite**, and a thin **reference implementation** that adjudicates
|
|
57
58
|
*comparability* of agent evaluation records — never the scores themselves.
|
|
58
59
|
|
|
60
|
+
**Does it actually refuse unsupported claims?** See
|
|
61
|
+
[experiments/scenarios_001/RECORD.md](experiments/scenarios_001/RECORD.md): four real-pipeline
|
|
62
|
+
scenarios (real `.eval` logs → converter → core) covering all four verdicts — including the
|
|
63
|
+
confounded comparison and the missing-trajectory case, both correctly **refused** rather than
|
|
64
|
+
packaged as supported conclusions.
|
|
65
|
+
|
|
59
66
|
**Who is this for**: developers comparing agent/prompt/harness evaluation results,
|
|
60
67
|
benchmark authors documenting what conclusions their data supports, and teams building
|
|
61
68
|
reliability pipelines on top of existing eval frameworks.
|
|
@@ -79,6 +86,8 @@ Three failure modes silently corrupt agent-evaluation conclusions:
|
|
|
79
86
|
| **Evidence gaps** | Missing traces quietly inflate success rates; rankings reverse across harnesses |
|
|
80
87
|
|
|
81
88
|
These boundaries should be **machine-checkable**, not vibes. That is what this project ships.
|
|
89
|
+
How this relates to benchmark-rigor and transcript-audit work (verified literature anchors):
|
|
90
|
+
see [docs/positioning.md](docs/positioning.md).
|
|
82
91
|
|
|
83
92
|
## 3. What Agent Comparability Does
|
|
84
93
|
|
|
@@ -135,10 +144,30 @@ pip install -e .[suite] # + suite basics (pyyaml, jsonschema)
|
|
|
135
144
|
pip install -e .[suite,inspect,pydantic] # FULL conformance run — required by run_suite.py
|
|
136
145
|
```
|
|
137
146
|
|
|
138
|
-
Requires Python ≥ 3.10. No Docker, no model keys.
|
|
147
|
+
Requires Python ≥ 3.10. No Docker, no model keys. Note: the core package supports 3.10+;
|
|
148
|
+
the optional `inspect`/`pydantic` extras pull dependencies that require Python ≥ 3.11 —
|
|
149
|
+
on 3.10 pip resolves older versions of them.
|
|
139
150
|
|
|
140
151
|
## 7. Quickstart
|
|
141
152
|
|
|
153
|
+
**Command line** (offline, zero extra dependencies — input contract below):
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
python -m agent_comparability examples/demo-case.json --claim score_comparison
|
|
157
|
+
# → CDR JSON on stdout (exit 0). INADMISSIBLE / PARTIAL / INCONCLUSIVE are verdicts,
|
|
158
|
+
# not program failures. Invalid input (bad JSON / missing or mistyped fields) →
|
|
159
|
+
# exit 2 with a path-precise diagnostic — never silently converted into a verdict.
|
|
160
|
+
# -o cdr.json writes to a file instead. The demo case is SYNTHETIC/illustrative.
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Input contract (`{"id": str, "records": [{"task_id", "harness", "model", "reward":
|
|
164
|
+
number, "reward_status", ...optional}]}`); unknown record fields are ignored and
|
|
165
|
+
recorded in `unknown_conditions` (SPEC §5). Add `--manifest out.manifest.json` to
|
|
166
|
+
also emit an Evidence Manifest documenting artifact digests and evidence trust
|
|
167
|
+
states (never auto-upgraded; see [docs/evidence-manifest.md](docs/evidence-manifest.md)).
|
|
168
|
+
|
|
169
|
+
**Python API**:
|
|
170
|
+
|
|
142
171
|
```python
|
|
143
172
|
from agent_comparability import adjudicate
|
|
144
173
|
|
|
@@ -204,7 +233,7 @@ CDR — actual output:
|
|
|
204
233
|
"rec:t1/m1/h1",
|
|
205
234
|
"rec:t1/m1/h2"
|
|
206
235
|
],
|
|
207
|
-
"policy_version": "cmp/0.
|
|
236
|
+
"policy_version": "cmp/0.4.0",
|
|
208
237
|
"limitations": [
|
|
209
238
|
"摘要一致仅证明未同步修改,不构成抗恶意篡改(单写信任域)"
|
|
210
239
|
]
|
|
@@ -263,10 +292,14 @@ Both wrap the *same* core (no semantic duplication). Inspect scorer uses
|
|
|
263
292
|
The Pydantic evaluator checks the **task's real output** (`ctx.output`) against
|
|
264
293
|
frozen expectations — it never re-adjudicates inside the evaluator — and the
|
|
265
294
|
adapter's exit code derives from the full 22-pair `report` (cases + failures).
|
|
295
|
+
For **real Inspect EvalLog files** (not frozen cases) use the converter:
|
|
296
|
+
`python -m agent_comparability.inspect_log run.eval --claim … --status …`
|
|
297
|
+
(official APIs, explicit mapping policy, no silent guessing — see
|
|
298
|
+
[docs/inspect-log.md](docs/inspect-log.md)).
|
|
266
299
|
|
|
267
300
|
## 12. Specification & Versioning
|
|
268
301
|
|
|
269
|
-
Three distinct lines: software tag (e.g. `v0.5.0`), specification `cmp/0.
|
|
302
|
+
Three distinct lines: software tag (e.g. `v0.5.0`), specification `cmp/0.4.0`
|
|
270
303
|
(inside SPEC), and `agent_comparability.__version__`. Judgment-table changes
|
|
271
304
|
require a spec major bump (SPEC §5). See [CHANGELOG](CHANGELOG.md) for the full tag lineage
|
|
272
305
|
(v0.3.1–v0.3.6 are deprecated candidates kept immutably for the record).
|
|
@@ -300,13 +333,13 @@ No — no network, no credentials, no Docker. It only ever sees the records you
|
|
|
300
333
|
|
|
301
334
|
**Which versions should I depend on?**
|
|
302
335
|
Package `v0.5.0+` (version lines unified and CI-enforced since then); specification
|
|
303
|
-
`cmp/0.
|
|
336
|
+
`cmp/0.4.0` evolves independently — see [§12](#12-specification--versioning).
|
|
304
337
|
|
|
305
338
|
## Project status
|
|
306
339
|
|
|
307
340
|
| Component | Status |
|
|
308
341
|
|---|---|
|
|
309
|
-
| Specification `cmp/0.
|
|
342
|
+
| Specification `cmp/0.4.0` | Stable — frozen conformance set anchors it |
|
|
310
343
|
| Claim types | `score_comparison`, `causal`, `descriptive_registration` (+ unknown → `INCONCLUSIVE`) |
|
|
311
344
|
| Conformance cases | 22 frozen pairs (A + B sets), three separated metrics, all green in CI |
|
|
312
345
|
| Adapters | Inspect AI ✓ · Pydantic Evals ✓ |
|
|
@@ -1,23 +1,29 @@
|
|
|
1
|
-
[build-system]
|
|
2
|
-
requires = ["setuptools>=68"]
|
|
3
|
-
build-backend = "setuptools.build_meta"
|
|
4
|
-
|
|
5
|
-
[project]
|
|
6
|
-
name = "agent-comparability"
|
|
7
|
-
version = "0.5.
|
|
8
|
-
description = "Agent evaluation comparability semantics: spec + conformance suite + thin reference implementation"
|
|
9
|
-
readme = "README.md"
|
|
10
|
-
requires-python = ">=3.10"
|
|
11
|
-
license = {text = "Apache-2.0"}
|
|
12
|
-
dependencies = []
|
|
13
|
-
|
|
14
|
-
[project.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
[
|
|
23
|
-
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "agent-comparability"
|
|
7
|
+
version = "0.5.4"
|
|
8
|
+
description = "Agent evaluation comparability semantics: spec + conformance suite + thin reference implementation"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = {text = "Apache-2.0"}
|
|
12
|
+
dependencies = []
|
|
13
|
+
|
|
14
|
+
[project.urls]
|
|
15
|
+
Homepage = "https://github.com/Rethymus/agent-comparability"
|
|
16
|
+
Repository = "https://github.com/Rethymus/agent-comparability"
|
|
17
|
+
Issues = "https://github.com/Rethymus/agent-comparability/issues"
|
|
18
|
+
Changelog = "https://github.com/Rethymus/agent-comparability/blob/main/CHANGELOG.md"
|
|
19
|
+
|
|
20
|
+
[project.optional-dependencies]
|
|
21
|
+
suite = ["pyyaml", "jsonschema"]
|
|
22
|
+
inspect = ["inspect-ai"]
|
|
23
|
+
pydantic = ["pydantic-evals"]
|
|
24
|
+
|
|
25
|
+
[tool.setuptools.packages.find]
|
|
26
|
+
where = ["src"]
|
|
27
|
+
|
|
28
|
+
[tool.setuptools.package-data]
|
|
29
|
+
agent_comparability = ["data/*"]
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""离线 JSON → CDR 命令行闭环(#16,零额外运行时依赖)。
|
|
2
|
+
|
|
3
|
+
用法:
|
|
4
|
+
python -m agent_comparability input.json --claim score_comparison [-o cdr.json]
|
|
5
|
+
|
|
6
|
+
输入契约(= adjudicate 既有输入形状):
|
|
7
|
+
{"id": str,
|
|
8
|
+
"records": [{"task_id": str, "harness": str, "model": str,
|
|
9
|
+
"reward": number, "reward_status": str,
|
|
10
|
+
...可选 trajectory_available/score_scale/repetition_count}]}
|
|
11
|
+
|
|
12
|
+
退出码契约:
|
|
13
|
+
0 = 产出 CDR(ADMISSIBLE/INADMISSIBLE/PARTIAL/INCONCLUSIVE 均为裁决成功,非程序失败)
|
|
14
|
+
2 = 输入错误(文件不可读/JSON 语法/契约字段缺失或类型错)——诊断含出错路径;
|
|
15
|
+
无效输入绝不允许被转换为貌似成功的裁决
|
|
16
|
+
|
|
17
|
+
本模块只做契约存在性与类型检查;一切判定语义由 adjudicate() 负责(不重新实现判定表,
|
|
18
|
+
不改规范语义)。未知字段按 SPEC §5 忽略并记入 CDR 的 unknown_conditions,不在此报错。
|
|
19
|
+
"""
|
|
20
|
+
import argparse
|
|
21
|
+
import json
|
|
22
|
+
import sys
|
|
23
|
+
|
|
24
|
+
from . import adjudicate
|
|
25
|
+
|
|
26
|
+
REQUIRED_STR = ("task_id", "harness", "model", "reward_status")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ContractError(Exception):
|
|
30
|
+
"""输入违反契约:消息含出错路径,如 records[2].reward。"""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _is_number(v):
|
|
34
|
+
return isinstance(v, (int, float)) and not isinstance(v, bool)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _is_int(v):
|
|
38
|
+
return isinstance(v, int) and not isinstance(v, bool)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def validate_case(case):
|
|
42
|
+
if not isinstance(case, dict):
|
|
43
|
+
raise ContractError("输入顶层必须是 JSON 对象,实际是 " + type(case).__name__)
|
|
44
|
+
if "id" not in case:
|
|
45
|
+
raise ContractError("缺少必填字段 id")
|
|
46
|
+
if not isinstance(case["id"], str):
|
|
47
|
+
raise ContractError("id 必须是字符串,实际是 " + type(case["id"]).__name__)
|
|
48
|
+
if "records" not in case:
|
|
49
|
+
raise ContractError("缺少必填字段 records")
|
|
50
|
+
if not isinstance(case["records"], list) or not case["records"]:
|
|
51
|
+
raise ContractError("records 必须是非空数组")
|
|
52
|
+
for i, r in enumerate(case["records"]):
|
|
53
|
+
where = f"records[{i}]"
|
|
54
|
+
if not isinstance(r, dict):
|
|
55
|
+
raise ContractError(f"{where} 必须是 JSON 对象,实际是 " + type(r).__name__)
|
|
56
|
+
for k in REQUIRED_STR:
|
|
57
|
+
if k not in r:
|
|
58
|
+
raise ContractError(f"{where}.{k} 缺失(必填字段)")
|
|
59
|
+
if not isinstance(r[k], str):
|
|
60
|
+
raise ContractError(f"{where}.{k} 必须是字符串,实际是 " + type(r[k]).__name__)
|
|
61
|
+
if "reward" not in r:
|
|
62
|
+
raise ContractError(f"{where}.reward 缺失(必填字段)")
|
|
63
|
+
if not _is_number(r["reward"]):
|
|
64
|
+
raise ContractError(f"{where}.reward 必须是数值,实际是 " + type(r["reward"]).__name__)
|
|
65
|
+
if "trajectory_available" in r and not isinstance(r["trajectory_available"], bool):
|
|
66
|
+
raise ContractError(f"{where}.trajectory_available 必须是布尔值")
|
|
67
|
+
if "score_scale" in r and not isinstance(r["score_scale"], dict):
|
|
68
|
+
raise ContractError(f"{where}.score_scale 必须是对象 {{range, unit, verifier}}")
|
|
69
|
+
if "repetition_count" in r and (not _is_int(r["repetition_count"]) or r["repetition_count"] < 1):
|
|
70
|
+
raise ContractError(f"{where}.repetition_count 必须是 >=1 的整数")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def main(argv=None) -> int:
|
|
74
|
+
p = argparse.ArgumentParser(
|
|
75
|
+
prog="python -m agent_comparability",
|
|
76
|
+
description="离线评测记录 JSON → 结构化 CDR(Comparison Decision Record)")
|
|
77
|
+
p.add_argument("input", help="输入 JSON 文件路径(契约见模块文档)")
|
|
78
|
+
p.add_argument("--claim", required=True,
|
|
79
|
+
help="请求结论类型:score_comparison / causal / descriptive_registration"
|
|
80
|
+
"(未支持类型 → INCONCLUSIVE,不猜测)")
|
|
81
|
+
p.add_argument("-o", "--output", help="CDR 输出文件路径(缺省打印到 stdout)")
|
|
82
|
+
p.add_argument("--manifest", help="同时输出 Evidence Manifest(#18):输入文件作为证据制品"
|
|
83
|
+
"实算 sha256,全部记录 artifact_observed,不含任何独立验证声明")
|
|
84
|
+
args = p.parse_args(argv)
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
raw = open(args.input, encoding="utf-8").read()
|
|
88
|
+
except OSError as e:
|
|
89
|
+
print(f"error: 无法读取输入文件 {args.input}: {e.strerror or e}", file=sys.stderr)
|
|
90
|
+
return 2
|
|
91
|
+
try:
|
|
92
|
+
case = json.loads(raw)
|
|
93
|
+
except json.JSONDecodeError as e:
|
|
94
|
+
print(f"error: JSON 解析错误 {args.input}(第 {e.lineno} 行第 {e.colno} 列): {e.msg}", file=sys.stderr)
|
|
95
|
+
return 2
|
|
96
|
+
try:
|
|
97
|
+
validate_case(case)
|
|
98
|
+
except ContractError as e:
|
|
99
|
+
print(f"error: 输入违反契约 {args.input}: {e}", file=sys.stderr)
|
|
100
|
+
return 2
|
|
101
|
+
|
|
102
|
+
cdr = adjudicate(case, args.claim).to_dict()
|
|
103
|
+
out = json.dumps(cdr, ensure_ascii=False, indent=2)
|
|
104
|
+
if args.output:
|
|
105
|
+
try:
|
|
106
|
+
with open(args.output, "w", encoding="utf-8") as f:
|
|
107
|
+
f.write(out + "\n")
|
|
108
|
+
except OSError as e:
|
|
109
|
+
print(f"error: 无法写入输出文件 {args.output}: {e.strerror or e}", file=sys.stderr)
|
|
110
|
+
return 2
|
|
111
|
+
else:
|
|
112
|
+
print(out)
|
|
113
|
+
if args.manifest:
|
|
114
|
+
from .evidence import build_manifest
|
|
115
|
+
# 离线 JSON 的诚实默认:输入文件即证据制品(读后实算摘要);无来源端验证能力,
|
|
116
|
+
# 无独立验证声明——绝不把 source_claimed 冒充为已验证。
|
|
117
|
+
m = build_manifest(case, source_kind="offline_json",
|
|
118
|
+
artifacts={i: args.input for i in range(len(case["records"]))},
|
|
119
|
+
requested_claim=args.claim,
|
|
120
|
+
unsupported_capabilities=["source_side_verification_unavailable"])
|
|
121
|
+
try:
|
|
122
|
+
with open(args.manifest, "w", encoding="utf-8") as f:
|
|
123
|
+
f.write(json.dumps(m, ensure_ascii=False, indent=2) + "\n")
|
|
124
|
+
except OSError as e:
|
|
125
|
+
print(f"error: 无法写入 manifest 文件 {args.manifest}: {e.strerror or e}", file=sys.stderr)
|
|
126
|
+
return 2
|
|
127
|
+
return 0
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
if __name__ == "__main__":
|
|
131
|
+
sys.exit(main())
|
|
@@ -1,16 +1,20 @@
|
|
|
1
|
-
"""比较判定核心库 cmp/core(cmp/0.
|
|
1
|
+
"""比较判定核心库 cmp/core(cmp/0.4.0 规范参考实现)。
|
|
2
2
|
|
|
3
|
+
相对 0.3.1 的判定表分支修正(#27 裁决方向 B,SPEC §5 允许 minor 新增分支):
|
|
4
|
+
⑤score_comparison 的 INADMISSIBLE 触发移除 same_model——单变量对称性
|
|
5
|
+
(§4 行 2:仅模型不同 → ADMISSIBLE 并排);模型-only 变化由
|
|
6
|
+
score_scale_consistent / zero_semantics_uniform 独立把关(D2 冻结对证明)。
|
|
3
7
|
相对 0.2 的规范符合性修复(依据 SPEC 0.3.1;B 集冻结预期先于本修改存在):
|
|
4
8
|
①同任务性检查入 score 判定优先级 2(B3 暴露 0.2 缺失)
|
|
5
9
|
②评分尺度一致性检查(含 0.3.1 隐式默认规则)(B4/B5 暴露 0.2 缺失)
|
|
6
10
|
③claim 路由补 registration/descriptive 关键词(B2-B5 暴露误判)
|
|
7
11
|
④policy_version 升 cmp/0.3.1(消费方按 SPEC §5 重判旧 CDR)
|
|
8
|
-
判定优先级(SPEC §3.3):INCONCLUSIVE(缺trace) > INADMISSIBLE(
|
|
12
|
+
判定优先级(SPEC §3.3):INCONCLUSIVE(缺trace) > INADMISSIBLE(任务/混淆/尺度) > PARTIAL(零语义) > ADMISSIBLE。
|
|
9
13
|
"""
|
|
10
14
|
|
|
11
15
|
from dataclasses import dataclass, field, asdict
|
|
12
16
|
|
|
13
|
-
POLICY_VERSION = "cmp/0.
|
|
17
|
+
POLICY_VERSION = "cmp/0.4.0"
|
|
14
18
|
TAMPER_LIMITATION = "摘要一致仅证明未同步修改,不构成抗恶意篡改(单写信任域)"
|
|
15
19
|
DEFAULT_SCALE = {"range": "0..1", "unit": "fraction", "verifier": "source"}
|
|
16
20
|
KNOWN_FIELDS = {"task_id", "harness", "model", "reward", "reward_status",
|
|
@@ -92,9 +96,10 @@ def adjudicate(case: dict, requested_claim: str) -> CDR:
|
|
|
92
96
|
cdr.admissibility = "INCONCLUSIVE"
|
|
93
97
|
cdr.supported_claims.append("score_transcription(source_verifier_attested)")
|
|
94
98
|
cdr.unsupported_claims.append("success_rate(含证据缺失记录)")
|
|
95
|
-
elif len(tasks) > 1 or
|
|
99
|
+
elif len(tasks) > 1 or confounded or "score_scale_consistent" in cdr.violated_conditions:
|
|
100
|
+
# 0.4:same_model 移出硬触发(#27 单变量对称性);尺度/零语义条件独立把关
|
|
96
101
|
cdr.admissibility = "INADMISSIBLE"
|
|
97
|
-
cdr.unsupported_claims.append("score_comparison(
|
|
102
|
+
cdr.unsupported_claims.append("score_comparison(任务/混淆/尺度条件冲突)")
|
|
98
103
|
elif any(v.startswith("zero_semantics_uniform") for v in cdr.violated_conditions):
|
|
99
104
|
cdr.admissibility = "PARTIAL"
|
|
100
105
|
cdr.supported_claims.append("per_status_stratified_rate(verified_zero/unresolved_zero 分列)")
|
{agent_comparability-0.5.2 → agent_comparability-0.5.4}/src/agent_comparability/data/SPEC.md
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Agent Evaluation Comparability Specification — cmp/0.
|
|
1
|
+
# Agent Evaluation Comparability Specification — cmp/0.4.0 (Release Candidate)
|
|
2
2
|
|
|
3
3
|
规范状态:候选规范(非行业标准、无第三方认证)。本文是判定语义的单一事实来源;参考实现(`scripts/cmp/core.py`)与一切第三方实现均以本文为准,冲突时以本文裁决。可脱离任何源码阅读。
|
|
4
4
|
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
| 类型 | 语义 |
|
|
33
33
|
|---|---|
|
|
34
34
|
| `causal` | 断言某变量(模型/Harness)**导致**了分数差异 |
|
|
35
|
-
| `score_comparison` |
|
|
35
|
+
| `score_comparison` | 请求对连续分数做(并排或汇总)比较。历史冻结案例集使用早期写法 `score_compare`;参考实现通过下划线归一化与关键词匹配接受两者(实现细节,非规范语义),新代码应一律使用 `score_comparison`。 |
|
|
36
36
|
| `descriptive_registration` | 仅登记条件与分数,不作比较结论 |
|
|
37
37
|
| 其他 | **未支持类型** |
|
|
38
38
|
|
|
@@ -67,7 +67,10 @@
|
|
|
67
67
|
|
|
68
68
|
**`score_comparison`**(按优先级,先命中先裁决):
|
|
69
69
|
1. 违反 `trace_available` → `INCONCLUSIVE`(来源评分转录保留于 `unknown_conditions`,禁止删除)
|
|
70
|
-
2. 违反 `single_variable_isolation` **或** `same_task_id`
|
|
70
|
+
2. 违反 `single_variable_isolation` **或** `same_task_id` → `INADMISSIBLE`
|
|
71
|
+
(**cmp/0.4 修正(#27)**:`same_model` 不再单独触发 INADMISSIBLE——单变量对称性,与
|
|
72
|
+
§4 行 1/行 2 一致;模型-only 变化由 `score_scale_consistent`/`zero_semantics_uniform`
|
|
73
|
+
独立把关。0.3.x 行为见版本记录。)
|
|
71
74
|
3. 违反 `score_scale_consistent` → `INADMISSIBLE`
|
|
72
75
|
4. 违反 `zero_semantics_uniform` → `PARTIAL`(支持按 verified/unresolved 分列的率;禁止合并 fail 率)
|
|
73
76
|
5. 其余 → `ADMISSIBLE`(同尺度连续分数并排)
|
|
@@ -95,7 +98,7 @@
|
|
|
95
98
|
|
|
96
99
|
- **未知字段**:Record 中规范未定义的字段**必须忽略**(不得报错、不得猜测),并在 `unknown_conditions` 记 `ignored_fields:[...]`。
|
|
97
100
|
- **未支持 Claim**:见 §2 —— INCONCLUSIVE + `unrecognized_claim_type`,禁止近似匹配。
|
|
98
|
-
- **Policy 版本**:CDR 必须携带 `policy_version`。消费方遇到 `policy_version` 不认识时**必须丢弃该 CDR 并重新判定**,不得部分信任。本规范为 `cmp/0.3.1
|
|
101
|
+
- **Policy 版本**:CDR 必须携带 `policy_version`。消费方遇到 `policy_version` 不认识时**必须丢弃该 CDR 并重新判定**,不得部分信任。本规范为 `cmp/0.4.0`(0.3→0.4:判定表分支修正——score_comparison 的 INADMISSIBLE 触发移除 `same_model`,实现单变量对称性,裁决记录见 #27;D 冻结对(D1/D2)先于实现冻结)。0.3.1 为 0.3 的澄清版(仅补隐式默认尺度规则)。
|
|
99
102
|
- **规范不兼容变更**:升 minor(0.3→0.4)可新增判定分支;改判定表必须升 major 并迁移案例。
|
|
100
103
|
|
|
101
104
|
---
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"policy_version": "cmp/0.4.0",
|
|
3
|
+
"frozen_note": "D 集(#27 裁决方向 B,先于实现冻结):单变量对称性——模型-only 变化遵循 SPEC §4 行 2。A/B 集与既有预期零改动;D2 证明尺度条件独立把关未被放松。",
|
|
4
|
+
"cases": [
|
|
5
|
+
{"id": "D1", "category": "model-only-variation",
|
|
6
|
+
"records": [
|
|
7
|
+
{"task_id": "t-model-only", "harness": "h1", "model": "model-a", "reward": 0.8, "reward_status": "verified"},
|
|
8
|
+
{"task_id": "t-model-only", "harness": "h1", "model": "model-b", "reward": 0.6, "reward_status": "verified"}],
|
|
9
|
+
"claims": {"score_comparison": "ADMISSIBLE", "causal": "INADMISSIBLE"}},
|
|
10
|
+
{"id": "D2", "category": "model-plus-scale-violation",
|
|
11
|
+
"records": [
|
|
12
|
+
{"task_id": "t-model-scale", "harness": "h1", "model": "model-a", "reward": 0.8, "reward_status": "verified"},
|
|
13
|
+
{"task_id": "t-model-scale", "harness": "h1", "model": "model-b", "reward": 62.0, "reward_status": "verified"}],
|
|
14
|
+
"claims": {"score_comparison": "INADMISSIBLE", "descriptive_registration": "ADMISSIBLE"}}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Evidence Manifest(#18)——CDR 外部的来源与证据可信边界。
|
|
2
|
+
|
|
3
|
+
三态(**证据制品级**,与 SPEC §1 的记录级评分信任四层是不同维度,绝不进入
|
|
4
|
+
adjudicate、不改变任何 CDR 字段或判定结果):
|
|
5
|
+
|
|
6
|
+
source_claimed < artifact_observed < independently_verified
|
|
7
|
+
|
|
8
|
+
升级只能由显式证据触发:
|
|
9
|
+
- artifact_observed ⟺ 制品路径存在且被实际读取且 sha256 已实算记录;
|
|
10
|
+
- independently_verified ⟺ 在 artifact_observed 之上存在显式 verification 条目
|
|
11
|
+
(method/operator/at 三者齐备);
|
|
12
|
+
- 无制品 → 地板 source_claimed + 警告;有 verification 无制品 → 不一致,不升级。
|
|
13
|
+
不存在任何隐式跃迁(防"证据不足被包装成可信结论")。
|
|
14
|
+
"""
|
|
15
|
+
import hashlib
|
|
16
|
+
|
|
17
|
+
from . import POLICY_VERSION
|
|
18
|
+
|
|
19
|
+
MANIFEST_VERSION = "evidence/0.1"
|
|
20
|
+
STATES = ("source_claimed", "artifact_observed", "independently_verified")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _digest(path):
|
|
24
|
+
h = hashlib.sha256()
|
|
25
|
+
try:
|
|
26
|
+
with open(path, "rb") as f:
|
|
27
|
+
for chunk in iter(lambda: f.read(65536), b""):
|
|
28
|
+
h.update(chunk)
|
|
29
|
+
except OSError:
|
|
30
|
+
return None
|
|
31
|
+
return "sha256:" + h.hexdigest()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def build_manifest(case, source_kind, source_version=None, artifacts=None,
|
|
35
|
+
verifications=None, requested_claim=None,
|
|
36
|
+
unsupported_capabilities=None):
|
|
37
|
+
"""构建 manifest。artifacts: {record_index: 制品路径};verifications: {record_index:
|
|
38
|
+
{method, operator, at}}。状态只由提供的证据决定——本函数绝不主动升级。"""
|
|
39
|
+
artifacts = artifacts or {}
|
|
40
|
+
verifications = verifications or {}
|
|
41
|
+
warnings = []
|
|
42
|
+
records = []
|
|
43
|
+
for i, r in enumerate(case["records"]):
|
|
44
|
+
state, digest, art_ref, reason = "source_claimed", None, None, None
|
|
45
|
+
if i in artifacts:
|
|
46
|
+
art_ref = artifacts[i]
|
|
47
|
+
digest = _digest(art_ref)
|
|
48
|
+
if digest is None:
|
|
49
|
+
warnings.append(f"records[{i}]:artifact_missing({art_ref} 不可读——保持 source_claimed)")
|
|
50
|
+
reason = f"artifact_unreadable:{art_ref}"
|
|
51
|
+
else:
|
|
52
|
+
state = "artifact_observed"
|
|
53
|
+
else:
|
|
54
|
+
warnings.append(f"records[{i}]:artifact_missing(未提供制品——保持 source_claimed)")
|
|
55
|
+
reason = "no_artifact_provided"
|
|
56
|
+
verification = None
|
|
57
|
+
if i in verifications:
|
|
58
|
+
v = verifications[i]
|
|
59
|
+
valid = isinstance(v, dict) and {"method", "operator", "at"} <= set(v)
|
|
60
|
+
if state != "artifact_observed":
|
|
61
|
+
warnings.append(f"records[{i}]:verification_without_artifact("
|
|
62
|
+
f"无法独立验证从未观察过的制品——不升级)")
|
|
63
|
+
elif not valid:
|
|
64
|
+
warnings.append(f"records[{i}]:verification_entry_invalid(需 method/operator/at)")
|
|
65
|
+
else:
|
|
66
|
+
state = "independently_verified"
|
|
67
|
+
verification = {"method": v["method"], "operator": v["operator"], "at": v["at"]}
|
|
68
|
+
records.append({
|
|
69
|
+
"record_id": f"records[{i}]",
|
|
70
|
+
"task_identity": {"task_id": r["task_id"], "model": r["model"], "harness": r["harness"]},
|
|
71
|
+
"artifact_ref": art_ref,
|
|
72
|
+
"file_digest": digest,
|
|
73
|
+
"verification_state": state,
|
|
74
|
+
"verification": verification,
|
|
75
|
+
"unverified_reason": reason,
|
|
76
|
+
"provenance_note": (f"state={state}; artifact="
|
|
77
|
+
f"{'read+sha256' if digest else 'none'}; source={source_kind}"),
|
|
78
|
+
})
|
|
79
|
+
return {
|
|
80
|
+
"manifest_version": MANIFEST_VERSION,
|
|
81
|
+
"policy_version": POLICY_VERSION, # 引用锚定:标识判定所用规范版本,不参与判定
|
|
82
|
+
"source_kind": source_kind,
|
|
83
|
+
"source_version": source_version, # None = 不可取得(诚实记录,不猜测)
|
|
84
|
+
"case_id": case["id"],
|
|
85
|
+
"requested_claim": requested_claim,
|
|
86
|
+
"records": records,
|
|
87
|
+
"mapping_warnings": warnings,
|
|
88
|
+
"unsupported_capabilities": unsupported_capabilities or [],
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def verify_manifest(manifest):
|
|
93
|
+
"""重算校验(供审计与后续转换器复用):重读 artifact 实算摘要比对记录值。
|
|
94
|
+
返回问题列表;空列表 = 干净。只验证已声明观察过的制品——不产生新状态。"""
|
|
95
|
+
problems = []
|
|
96
|
+
for e in manifest.get("records", []):
|
|
97
|
+
rid, ref, dig = e.get("record_id"), e.get("artifact_ref"), e.get("file_digest")
|
|
98
|
+
if e.get("verification_state") in ("artifact_observed", "independently_verified"):
|
|
99
|
+
if not ref or not dig:
|
|
100
|
+
problems.append(f"{rid}:structural(声明已观察但缺 artifact_ref/file_digest)")
|
|
101
|
+
continue
|
|
102
|
+
now = _digest(ref)
|
|
103
|
+
if now is None:
|
|
104
|
+
problems.append(f"{rid}:artifact_missing_on_reverify({ref})")
|
|
105
|
+
elif now != dig:
|
|
106
|
+
problems.append(f"{rid}:digest_mismatch({ref})")
|
|
107
|
+
if e.get("verification_state") == "independently_verified" and not e.get("verification"):
|
|
108
|
+
problems.append(f"{rid}:structural(verified 态缺 verification 条目)")
|
|
109
|
+
return problems
|