agent-tune-kit 2.1.1__tar.gz → 2.2.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.
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/.codex-plugin/plugin.json +1 -1
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/.gitignore +2 -1
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/PKG-INFO +29 -2
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/README.en.md +28 -1
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/README.md +28 -2
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/agent_tune_kit/__init__.py +1 -1
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/agent_tune_kit/cli/dataset.py +4 -1
- agent_tune_kit-2.2.0/agent_tune_kit/cli/results.py +99 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/agent_tune_kit/cli/visualize.py +4 -1
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/agent_tune_kit/datasets/prepare.py +4 -1
- agent_tune_kit-2.2.0/agent_tune_kit/datasets/truth.py +179 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/agent_tune_kit/datasets/updates.py +53 -12
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/agent_tune_kit/datasets/validation.py +3 -0
- agent_tune_kit-2.2.0/agent_tune_kit/execution/calibration.py +135 -0
- agent_tune_kit-2.2.0/agent_tune_kit/execution/rules.py +78 -0
- agent_tune_kit-2.2.0/agent_tune_kit/execution/runner.py +696 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/agent_tune_kit/execution/runtime.py +27 -6
- agent_tune_kit-2.2.0/agent_tune_kit/execution/tracing.py +216 -0
- agent_tune_kit-2.2.0/agent_tune_kit/results/assessment.py +162 -0
- agent_tune_kit-2.2.0/agent_tune_kit/results/evidence.py +815 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/agent_tune_kit/results/judgments.py +23 -7
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/agent_tune_kit/results/summary.py +43 -7
- agent_tune_kit-2.2.0/agent_tune_kit/results/versions.py +140 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/agent_tune_kit/visualize/assets/dataset/app.js +77 -28
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/agent_tune_kit/visualize/assets/dataset/page.html +9 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/agent_tune_kit/visualize/assets/dataset/styles.css +4 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/agent_tune_kit/visualize/dataset.py +24 -2
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/agent_tune_kit/visualize/failures.py +43 -5
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/agent_tune_kit/visualize/history.py +7 -1
- agent_tune_kit-2.2.0/docs/execution-evidence-and-tracing-plan.md +902 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/docs/ground-truth-and-grading-assessment-2026-10-25.md +107 -3
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/docs/shared-versioning-and-confirmation.md +61 -67
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/docs/skill-template-pack-usage.md +22 -1
- agent_tune_kit-2.2.0/docs/truth-and-assessment.md +106 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/pyproject.toml +6 -2
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/scripts/check-release.py +41 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/scripts/validate_skill_pack.py +10 -1
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/skills/atk-dataset/SKILL.md +13 -2
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/skills/atk-diagnose/SKILL.md +26 -5
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/skills/atk-eval/SKILL.md +15 -6
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/skills/atk-init/SKILL.md +10 -6
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/skills/atk-optimize/SKILL.md +13 -3
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/skills/atk-report/SKILL.md +19 -2
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/skills/atk-visualize-dataset/SKILL.md +6 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/skills/atk-visualize-failures/SKILL.md +4 -2
- agent_tune_kit-2.2.0/templates/.atk/runner/eval_runner.py.md +89 -0
- agent_tune_kit-2.2.0/templates/.atk/runner/import_evidence.py.md +42 -0
- agent_tune_kit-2.2.0/tests/test_assessment_contract.py +183 -0
- agent_tune_kit-2.2.0/tests/test_evidence_import.py +160 -0
- agent_tune_kit-2.2.0/tests/test_evidence_judgments.py +63 -0
- agent_tune_kit-2.2.0/tests/test_evidence_packaging.py +11 -0
- agent_tune_kit-2.2.0/tests/test_evidence_versions.py +79 -0
- agent_tune_kit-2.2.0/tests/test_execution_evidence.py +196 -0
- agent_tune_kit-2.2.0/tests/test_execution_tracing.py +403 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/tests/test_generate_dataset_browser.py +1 -1
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/tests/test_install_plugin.py +1 -1
- agent_tune_kit-2.2.0/tests/test_judge_calibration.py +81 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/tests/test_release_scripts.py +1 -1
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/tests/test_runner_template_row_logging.py +1 -1
- agent_tune_kit-2.2.0/tests/test_truth_browser.py +63 -0
- agent_tune_kit-2.2.0/tests/test_truth_cli.py +65 -0
- agent_tune_kit-2.2.0/tests/test_truth_contract.py +151 -0
- agent_tune_kit-2.2.0/tests/test_truth_runner.py +21 -0
- agent_tune_kit-2.1.1/agent_tune_kit/cli/results.py +0 -32
- agent_tune_kit-2.1.1/agent_tune_kit/execution/rules.py +0 -40
- agent_tune_kit-2.1.1/agent_tune_kit/execution/runner.py +0 -379
- agent_tune_kit-2.1.1/agent_tune_kit/results/versions.py +0 -51
- agent_tune_kit-2.1.1/docs/execution-evidence-and-tracing-plan.md +0 -434
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/agent_tune_kit/cli/__init__.py +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/agent_tune_kit/cli/__main__.py +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/agent_tune_kit/cli/evaluation.py +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/agent_tune_kit/cli/main.py +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/agent_tune_kit/cli/output.py +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/agent_tune_kit/datasets/__init__.py +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/agent_tune_kit/errors.py +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/agent_tune_kit/execution/__init__.py +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/agent_tune_kit/execution/logs.py +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/agent_tune_kit/installer.py +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/agent_tune_kit/results/__init__.py +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/agent_tune_kit/utils/__init__.py +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/agent_tune_kit/utils/csv_io.py +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/agent_tune_kit/utils/files.py +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/agent_tune_kit/visualize/__init__.py +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/agent_tune_kit/visualize/assets/dataset/vendor/echarts.min.js +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/agent_tune_kit/visualize/assets/failures/app.js +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/agent_tune_kit/visualize/assets/failures/page.html +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/agent_tune_kit/visualize/assets/failures/styles.css +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/agent_tune_kit/visualize/fields.py +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/agent_tune_kit/visualize/render.py +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/agent_tune_kit/workspace.py +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/docs/assets/arch-en.png +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/docs/assets/arch.png +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/docs/assets/architecture.en.svg +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/docs/assets/architecture.svg +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/docs/cli-skill-modularization-plan.md +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/docs/codex_agent_tuning_prd.md +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/docs/local-context.md +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/scripts/dev-refresh-install.sh +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/scripts/install_plugin.py +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/scripts/publish-pypi.sh +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/scripts/publish-release.py +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/scripts/release-version.py +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/scripts/release-version.sh +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/skills/atk-dataset/scripts/update_dataset.py +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/skills/atk-visualize-dataset/scripts/generate_dataset_browser.py +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/skills/atk-visualize-failures/scripts/generate_failure_browser.py +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/templates/.atk/runner/failure_rule.py.md +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/templates/.gitkeep +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/tests/__init__.py +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/tests/conftest.py +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/tests/fixtures/legacy_eval_runner.py.txt +0 -0
- /agent_tune_kit-2.1.1/templates/.atk/runner/eval_runner.py.md → /agent_tune_kit-2.2.0/tests/fixtures/managed_v1.py.md +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/tests/test_atk_init_contract.py +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/tests/test_cli_workflow.py +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/tests/test_dataset_updates.py +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/tests/test_diagnosis.py +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/tests/test_generate_failure_browser.py +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/tests/test_local_context.py +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/tests/test_plugin_cache.py +0 -0
- {agent_tune_kit-2.1.1 → agent_tune_kit-2.2.0}/tests/test_runtime_contracts.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: agent-tune-kit
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Local Codex plugin for iterative Agent tuning with guided Skills, reusable runner templates, versioned results, and static validation.
|
|
5
5
|
Project-URL: Homepage, https://github.com/hustyichi/agent-tune-kit
|
|
6
6
|
Project-URL: Repository, https://github.com/hustyichi/agent-tune-kit
|
|
@@ -14,6 +14,12 @@ Description-Content-Type: text/markdown
|
|
|
14
14
|
|
|
15
15
|
# Agent Tune Kit
|
|
16
16
|
|
|
17
|
+
Ground-truth reviews, judge calibration and assessment provenance are documented in
|
|
18
|
+
[the trust contract](docs/truth-and-assessment.md). Existing datasets remain readable, not automatically certified.
|
|
19
|
+
`atk dataset inspect/update` supports content-bound confirmations; `atk results calibrate` checks reviewed outputs
|
|
20
|
+
without executing the Agent or allocating an evaluation version. Rejudgments preserve history and comparisons expose
|
|
21
|
+
changes to their criteria and judge.
|
|
22
|
+
|
|
17
23
|
English | [简体中文](README.md)
|
|
18
24
|
|
|
19
25
|
[](https://pypi.org/project/agent-tune-kit/)
|
|
@@ -207,4 +213,25 @@ atk visualize dataset --no-open
|
|
|
207
213
|
atk visualize failures --no-open
|
|
208
214
|
```
|
|
209
215
|
|
|
210
|
-
These commands run from the Agent project. New project adapters delegate to the shared engine;
|
|
216
|
+
These commands run from the Agent project. New project adapters delegate to the shared engine; legacy full runners require migration before writing alongside new evidence Runs. Dataset fields, IDs, result paths, unknown verdicts, logs and offline review pages retain their contracts. Use `--help` on each command for its options.
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
## 本地执行证据 / Local execution evidence
|
|
220
|
+
|
|
221
|
+
V2 adapters declare `ATK_ADAPTER_VERSION = 2` and `EVIDENCE.retain(role, value)`.
|
|
222
|
+
Each execution saves independent input/output, context, structured events and artifacts beneath
|
|
223
|
+
`.atk/results/vN/executions/<id>/`; shared Cases map multiple selected samples to one directory.
|
|
224
|
+
Retention runs before persistence. Existing V1 adapters remain explicit; no historical migration is automatic.
|
|
225
|
+
|
|
226
|
+
```sh
|
|
227
|
+
atk results inspect --json
|
|
228
|
+
atk results evidence --version v1 --atk-id 1 --json
|
|
229
|
+
atk results evidence --execution-dir ./copied-execution --json
|
|
230
|
+
atk results import-evidence --source ./native-run --adapter ./importer.py --json
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
`inspect` separates latest Run from the evaluation target. Evidence-only imports do not displace the latter.
|
|
234
|
+
Diagnosis and reports bind `--version`; changed evidence invalidates dependent judgments. Import adapters are
|
|
235
|
+
trusted Python code, not a sandbox. No Agent call, remote fetch, automatic cleanup or post-seal supplementation
|
|
236
|
+
is performed. Declared capture coverage is distinct from successful persistence and business verdicts.
|
|
237
|
+
See [execution evidence plan](docs/execution-evidence-and-tracing-plan.md) for interfaces, source limitations, recovery and validation.
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Agent Tune Kit
|
|
2
2
|
|
|
3
|
+
Ground-truth reviews, judge calibration and assessment provenance are documented in
|
|
4
|
+
[the trust contract](docs/truth-and-assessment.md). Existing datasets remain readable, not automatically certified.
|
|
5
|
+
`atk dataset inspect/update` supports content-bound confirmations; `atk results calibrate` checks reviewed outputs
|
|
6
|
+
without executing the Agent or allocating an evaluation version. Rejudgments preserve history and comparisons expose
|
|
7
|
+
changes to their criteria and judge.
|
|
8
|
+
|
|
3
9
|
English | [简体中文](README.md)
|
|
4
10
|
|
|
5
11
|
[](https://pypi.org/project/agent-tune-kit/)
|
|
@@ -193,4 +199,25 @@ atk visualize dataset --no-open
|
|
|
193
199
|
atk visualize failures --no-open
|
|
194
200
|
```
|
|
195
201
|
|
|
196
|
-
These commands run from the Agent project. New project adapters delegate to the shared engine;
|
|
202
|
+
These commands run from the Agent project. New project adapters delegate to the shared engine; legacy full runners require migration before writing alongside new evidence Runs. Dataset fields, IDs, result paths, unknown verdicts, logs and offline review pages retain their contracts. Use `--help` on each command for its options.
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
## 本地执行证据 / Local execution evidence
|
|
206
|
+
|
|
207
|
+
V2 adapters declare `ATK_ADAPTER_VERSION = 2` and `EVIDENCE.retain(role, value)`.
|
|
208
|
+
Each execution saves independent input/output, context, structured events and artifacts beneath
|
|
209
|
+
`.atk/results/vN/executions/<id>/`; shared Cases map multiple selected samples to one directory.
|
|
210
|
+
Retention runs before persistence. Existing V1 adapters remain explicit; no historical migration is automatic.
|
|
211
|
+
|
|
212
|
+
```sh
|
|
213
|
+
atk results inspect --json
|
|
214
|
+
atk results evidence --version v1 --atk-id 1 --json
|
|
215
|
+
atk results evidence --execution-dir ./copied-execution --json
|
|
216
|
+
atk results import-evidence --source ./native-run --adapter ./importer.py --json
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
`inspect` separates latest Run from the evaluation target. Evidence-only imports do not displace the latter.
|
|
220
|
+
Diagnosis and reports bind `--version`; changed evidence invalidates dependent judgments. Import adapters are
|
|
221
|
+
trusted Python code, not a sandbox. No Agent call, remote fetch, automatic cleanup or post-seal supplementation
|
|
222
|
+
is performed. Declared capture coverage is distinct from successful persistence and business verdicts.
|
|
223
|
+
See [execution evidence plan](docs/execution-evidence-and-tracing-plan.md) for interfaces, source limitations, recovery and validation.
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Agent Tune Kit
|
|
2
2
|
|
|
3
|
+
真值复核、裁判校准和判定追溯见[可信度契约](docs/truth-and-assessment.md):数据集可按答案字段记录来源和
|
|
4
|
+
有效确认,输入/答案/标准变化会使确认过期;`atk dataset update --review-path dataset_review.csv` 可只导入
|
|
5
|
+
确认而不改答案。`atk results calibrate` 用已复核输出检查裁判,不调用 Agent 或创建评测轮次。
|
|
6
|
+
判定可绑定标准与裁判快照,重判保存历史,比较显式列出可比性差异。旧数据可读,但不自动标为已确认。
|
|
7
|
+
|
|
3
8
|
简体中文 | [English](README.en.md)
|
|
4
9
|
|
|
5
10
|
[](https://pypi.org/project/agent-tune-kit/)
|
|
@@ -176,7 +181,7 @@ ATK 可选读取本地私有的 `.atk/context.md`,用于保存用户沟通后
|
|
|
176
181
|
## 能力评估
|
|
177
182
|
|
|
178
183
|
- [Ground truth 与评分方案支持评估(2026-10-25)](docs/ground-truth-and-grading-assessment-2026-10-25.md):基于当前源码与 44 项相关测试,梳理数据准备、人工校准和判定支持,区分小规模调优可用性与可信评分体系的缺口;未重新验收业务调优效果。
|
|
179
|
-
- [
|
|
184
|
+
- [执行项现场与调用轨迹改造实现方案(2026-10-26)](docs/execution-evidence-and-tracing-plan.md):明确每项独立现场、V2 托管采集及外部 runner 本地证据导入,结合 Magic 双 Case 实践补齐来源身份、文件变更与验收;首版不做封存后补采,按 P0–P5 实施,详见方案中的验收状态。
|
|
180
185
|
|
|
181
186
|
兼容一次性安装命令 `uvx --from agent-tune-kit atk install`;日常使用 Skill 需要 PATH 中持续可用的 `atk` 命令。
|
|
182
187
|
|
|
@@ -198,4 +203,25 @@ atk visualize dataset --no-open
|
|
|
198
203
|
atk visualize failures --no-open
|
|
199
204
|
```
|
|
200
205
|
|
|
201
|
-
|
|
206
|
+
新生成的项目适配文件使用统一执行引擎,旧完整 runner 与新证据现场共存时需先满足写入保护,否则启动前提示迁移。数据字段、ID、结果目录、未判定状态、日志和离线浏览页遵循原有约定。各命令参数见对应 `--help`。
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
## 本地执行证据 / Local execution evidence
|
|
210
|
+
|
|
211
|
+
V2 adapters declare `ATK_ADAPTER_VERSION = 2` and `EVIDENCE.retain(role, value)`.
|
|
212
|
+
Each execution saves independent input/output, context, structured events and artifacts beneath
|
|
213
|
+
`.atk/results/vN/executions/<id>/`; shared Cases map multiple selected samples to one directory.
|
|
214
|
+
Retention runs before persistence. Existing V1 adapters remain explicit; no historical migration is automatic.
|
|
215
|
+
|
|
216
|
+
```sh
|
|
217
|
+
atk results inspect --json
|
|
218
|
+
atk results evidence --version v1 --atk-id 1 --json
|
|
219
|
+
atk results evidence --execution-dir ./copied-execution --json
|
|
220
|
+
atk results import-evidence --source ./native-run --adapter ./importer.py --json
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
`inspect` separates latest Run from the evaluation target. Evidence-only imports do not displace the latter.
|
|
224
|
+
Diagnosis and reports bind `--version`; changed evidence invalidates dependent judgments. Import adapters are
|
|
225
|
+
trusted Python code, not a sandbox. No Agent call, remote fetch, automatic cleanup or post-seal supplementation
|
|
226
|
+
is performed. Declared capture coverage is distinct from successful persistence and business verdicts.
|
|
227
|
+
See [execution evidence plan](docs/execution-evidence-and-tracing-plan.md) for interfaces, source limitations, recovery and validation.
|
|
@@ -6,6 +6,7 @@ from agent_tune_kit.workspace import DATASET_PATH
|
|
|
6
6
|
def add_options(parser):
|
|
7
7
|
parser.add_argument("--dataset-path", default=str(DATASET_PATH))
|
|
8
8
|
parser.add_argument("--field", default="ground_truth")
|
|
9
|
+
parser.add_argument("--input-fields", nargs="+")
|
|
9
10
|
parser.add_argument("--home-dir")
|
|
10
11
|
mode = parser.add_mutually_exclusive_group()
|
|
11
12
|
mode.add_argument("--review-path")
|
|
@@ -27,7 +28,7 @@ def register(subparsers):
|
|
|
27
28
|
add_options(commands.add_parser("inspect"))
|
|
28
29
|
update = commands.add_parser("update")
|
|
29
30
|
add_options(update)
|
|
30
|
-
update.add_argument("--updates-path"
|
|
31
|
+
update.add_argument("--updates-path")
|
|
31
32
|
|
|
32
33
|
|
|
33
34
|
def execute(args):
|
|
@@ -39,6 +40,7 @@ def execute(args):
|
|
|
39
40
|
review_path=args.review_path,
|
|
40
41
|
field=args.field,
|
|
41
42
|
home_dir=args.home_dir,
|
|
43
|
+
input_fields=args.input_fields,
|
|
42
44
|
)
|
|
43
45
|
if args.action == "inspect":
|
|
44
46
|
return datasets.inspect_dataset(**options)
|
|
@@ -61,6 +63,7 @@ def legacy_main(argv=None):
|
|
|
61
63
|
review_path=args.review_path,
|
|
62
64
|
field=args.field,
|
|
63
65
|
home_dir=args.home_dir,
|
|
66
|
+
input_fields=args.input_fields,
|
|
64
67
|
)
|
|
65
68
|
try:
|
|
66
69
|
if args.dump_context or not args.updates_path:
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
from agent_tune_kit import results
|
|
2
|
+
from agent_tune_kit.execution import run_rule
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def register(subparsers):
|
|
6
|
+
commands = subparsers.add_parser("results", help="inspect and judge versioned results").add_subparsers(
|
|
7
|
+
dest="action", required=True
|
|
8
|
+
)
|
|
9
|
+
calibration = commands.add_parser(
|
|
10
|
+
"calibrate", help="check a judge against reviewed outputs without running the Agent"
|
|
11
|
+
)
|
|
12
|
+
calibration.add_argument("--samples-path", required=True)
|
|
13
|
+
calibration.add_argument("--output-path", required=True)
|
|
14
|
+
mode = calibration.add_mutually_exclusive_group(required=True)
|
|
15
|
+
mode.add_argument("--rule-path")
|
|
16
|
+
mode.add_argument("--judgments-path")
|
|
17
|
+
calibration.add_argument("--judge-id")
|
|
18
|
+
calibration.add_argument("--instructions")
|
|
19
|
+
calibration.add_argument("--python")
|
|
20
|
+
calibration.add_argument("--json", action="store_true")
|
|
21
|
+
for name in ["inspect", "run-rule", "apply-judgments", "compare", "evidence", "import-evidence"]:
|
|
22
|
+
parser = commands.add_parser(name)
|
|
23
|
+
parser.add_argument("--results-dir", default=".atk/results")
|
|
24
|
+
parser.add_argument("--json", action="store_true")
|
|
25
|
+
if name != "import-evidence":
|
|
26
|
+
parser.add_argument("--version")
|
|
27
|
+
if name == "run-rule":
|
|
28
|
+
parser.add_argument("--rule-path", default=".atk/runner/failure_rule.py")
|
|
29
|
+
parser.add_argument("--python")
|
|
30
|
+
elif name == "apply-judgments":
|
|
31
|
+
parser.add_argument("--judgments-path", required=True)
|
|
32
|
+
parser.add_argument("--source-results")
|
|
33
|
+
elif name == "compare":
|
|
34
|
+
parser.add_argument("--ids", nargs="+", required=True)
|
|
35
|
+
elif name == "import-evidence":
|
|
36
|
+
parser.add_argument("--source", required=True)
|
|
37
|
+
parser.add_argument("--adapter", required=True, help="explicit trusted Python adapter (not sandboxed)")
|
|
38
|
+
elif name == "evidence":
|
|
39
|
+
parser.add_argument("--execution-dir")
|
|
40
|
+
parser.add_argument("--atk-id")
|
|
41
|
+
parser.add_argument("--execution-id")
|
|
42
|
+
parser.add_argument("--event-id")
|
|
43
|
+
parser.add_argument("--artifact-id")
|
|
44
|
+
parser.add_argument("--after-seq", type=int)
|
|
45
|
+
parser.add_argument("--offset", type=int, default=0)
|
|
46
|
+
parser.add_argument("--limit", type=int)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def execute(args):
|
|
50
|
+
if args.action == "calibrate":
|
|
51
|
+
from agent_tune_kit.execution.calibration import launch_calibration
|
|
52
|
+
|
|
53
|
+
return launch_calibration(
|
|
54
|
+
**{
|
|
55
|
+
key: getattr(args, key)
|
|
56
|
+
for key in (
|
|
57
|
+
"samples_path",
|
|
58
|
+
"output_path",
|
|
59
|
+
"rule_path",
|
|
60
|
+
"judgments_path",
|
|
61
|
+
"judge_id",
|
|
62
|
+
"instructions",
|
|
63
|
+
"python",
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
)
|
|
67
|
+
if args.action == "import-evidence":
|
|
68
|
+
from agent_tune_kit.results.evidence import import_evidence
|
|
69
|
+
|
|
70
|
+
return import_evidence(args.source, args.adapter, args.results_dir)
|
|
71
|
+
if args.action == "evidence":
|
|
72
|
+
from agent_tune_kit.results.evidence import query_evidence
|
|
73
|
+
|
|
74
|
+
return query_evidence(
|
|
75
|
+
**{
|
|
76
|
+
key: getattr(args, key)
|
|
77
|
+
for key in [
|
|
78
|
+
"results_dir",
|
|
79
|
+
"version",
|
|
80
|
+
"execution_dir",
|
|
81
|
+
"atk_id",
|
|
82
|
+
"execution_id",
|
|
83
|
+
"event_id",
|
|
84
|
+
"artifact_id",
|
|
85
|
+
"after_seq",
|
|
86
|
+
"offset",
|
|
87
|
+
"limit",
|
|
88
|
+
]
|
|
89
|
+
}
|
|
90
|
+
)
|
|
91
|
+
if args.action == "inspect":
|
|
92
|
+
return results.inspect_results(args.results_dir, version=args.version)
|
|
93
|
+
if args.action == "compare":
|
|
94
|
+
return results.compare_targets(args.ids, args.results_dir, version=args.version)
|
|
95
|
+
if args.action == "apply-judgments":
|
|
96
|
+
return results.apply_judgments(
|
|
97
|
+
args.judgments_path, source_results=args.source_results, results_dir=args.results_dir, version=args.version
|
|
98
|
+
)
|
|
99
|
+
return run_rule(rule_path=args.rule_path, results_dir=args.results_dir, python=args.python, version=args.version)
|
|
@@ -16,6 +16,7 @@ def add_options(parser, kind):
|
|
|
16
16
|
parser.add_argument("--dataset-path", default=".atk/datasets/dataset.csv")
|
|
17
17
|
else:
|
|
18
18
|
parser.add_argument("--results-dir", default=".atk/results")
|
|
19
|
+
parser.add_argument("--version")
|
|
19
20
|
parser.add_argument("--no-report", action="store_true")
|
|
20
21
|
parser.add_argument("--no-history", action="store_true")
|
|
21
22
|
|
|
@@ -32,7 +33,9 @@ def execute(args):
|
|
|
32
33
|
options = dict(overwrite=args.overwrite, open_browser=args.open_browser)
|
|
33
34
|
if args.action == "dataset":
|
|
34
35
|
return generate_dataset(args.dataset_path, **options)
|
|
35
|
-
return generate_failures(
|
|
36
|
+
return generate_failures(
|
|
37
|
+
args.results_dir, no_report=args.no_report, no_history=args.no_history, version=args.version, **options
|
|
38
|
+
)
|
|
36
39
|
|
|
37
40
|
|
|
38
41
|
def legacy_main(kind, argv=None):
|
|
@@ -4,6 +4,7 @@ from agent_tune_kit.errors import UserActionRequired
|
|
|
4
4
|
from agent_tune_kit.utils.csv_io import parse_csv, write_csv_atomic
|
|
5
5
|
from agent_tune_kit.workspace import DATASET_PATH
|
|
6
6
|
|
|
7
|
+
from .truth import TRUTH_FIELD
|
|
7
8
|
from .validation import validate_dataset_rows
|
|
8
9
|
|
|
9
10
|
|
|
@@ -15,8 +16,10 @@ def prepare_dataset(source, dataset_path=DATASET_PATH, *, append=False, overwrit
|
|
|
15
16
|
raise UserActionRequired("Append requires an existing canonical dataset.")
|
|
16
17
|
if append:
|
|
17
18
|
validate_dataset_rows(old_fields, old_rows)
|
|
18
|
-
if set(fields) - {"atk_id"} != set(old_fields) - {"atk_id"}:
|
|
19
|
+
if set(fields) - {"atk_id", TRUTH_FIELD} != set(old_fields) - {"atk_id", TRUTH_FIELD}:
|
|
19
20
|
raise UserActionRequired("Append fields differ; confirm the field mapping before appending.")
|
|
21
|
+
if TRUTH_FIELD in fields and TRUTH_FIELD not in old_fields:
|
|
22
|
+
old_fields = old_fields + [TRUTH_FIELD]
|
|
20
23
|
if "atk_id" not in fields:
|
|
21
24
|
fields = fields + ["atk_id"]
|
|
22
25
|
start = max((int(row["atk_id"]) for row in old_rows), default=0) + 1 if append else 1
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""Content-bound review declarations; integrity never implies domain correctness."""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
from datetime import UTC, datetime
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from agent_tune_kit.errors import UserActionRequired
|
|
9
|
+
|
|
10
|
+
TRUTH_FIELD = "atk_truth"
|
|
11
|
+
REVIEW_STATES = {"pending", "confirmed", "disputed"}
|
|
12
|
+
STANDARD_SECTIONS = {"Agent Behavior Standard", "Ground Truth Standard"}
|
|
13
|
+
INPUT_CANDIDATES = ("input", "query", "question", "prompt", "task", "user_input", "instruction")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def digest(value):
|
|
17
|
+
return hashlib.sha256(
|
|
18
|
+
json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode()
|
|
19
|
+
).hexdigest()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def standard_snapshot(path):
|
|
23
|
+
path = Path(path)
|
|
24
|
+
text = path.read_text(encoding="utf-8") if path.is_file() else ""
|
|
25
|
+
if path.name == "context.md":
|
|
26
|
+
lines, active = [], False
|
|
27
|
+
for line in text.splitlines():
|
|
28
|
+
if line.startswith("## "):
|
|
29
|
+
active = line[3:].strip() in STANDARD_SECTIONS
|
|
30
|
+
if active:
|
|
31
|
+
lines.append(line.rstrip())
|
|
32
|
+
text = "\n".join(lines).strip()
|
|
33
|
+
return {"text": text, "sha256": digest(text)}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def validate_inputs(row, field, inputs):
|
|
37
|
+
if field not in row or field in {TRUTH_FIELD, "atk_id"}:
|
|
38
|
+
raise UserActionRequired(f"Unknown or reserved answer field: {field}")
|
|
39
|
+
if (
|
|
40
|
+
not isinstance(inputs, list)
|
|
41
|
+
or not inputs
|
|
42
|
+
or any(not isinstance(k, str) for k in inputs)
|
|
43
|
+
or len(set(inputs)) != len(inputs)
|
|
44
|
+
or any(k not in row or k in {field, TRUTH_FIELD, "atk_id"} for k in inputs)
|
|
45
|
+
):
|
|
46
|
+
raise UserActionRequired(
|
|
47
|
+
"Review requires explicit, unique business input fields, excluding answer and ATK metadata."
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def subject_digest(row, field, inputs, standard, *, source="", evidence=""):
|
|
52
|
+
validate_inputs(row, field, inputs)
|
|
53
|
+
return digest(
|
|
54
|
+
{
|
|
55
|
+
"atk_id": row.get("atk_id"),
|
|
56
|
+
"field": field,
|
|
57
|
+
"answer": row[field],
|
|
58
|
+
"inputs": {k: row[k] for k in inputs},
|
|
59
|
+
"standard": standard["sha256"],
|
|
60
|
+
"source": source,
|
|
61
|
+
"evidence": evidence,
|
|
62
|
+
}
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def truth_records(row):
|
|
67
|
+
raw = row.get(TRUTH_FIELD, "")
|
|
68
|
+
if not raw:
|
|
69
|
+
return {}
|
|
70
|
+
try:
|
|
71
|
+
data = json.loads(raw)
|
|
72
|
+
if data.get("schema") != 1 or not isinstance(data.get("fields"), dict):
|
|
73
|
+
raise ValueError("Unknown truth schema")
|
|
74
|
+
for field, record in data["fields"].items():
|
|
75
|
+
validate_inputs(row, field, record.get("input_fields"))
|
|
76
|
+
standard = record["standard"]
|
|
77
|
+
if (
|
|
78
|
+
record["status"] not in REVIEW_STATES
|
|
79
|
+
or not isinstance(standard["text"], str)
|
|
80
|
+
or standard["sha256"] != digest(standard["text"])
|
|
81
|
+
or not isinstance(record["subject_sha256"], str)
|
|
82
|
+
or len(record["subject_sha256"]) != 64
|
|
83
|
+
):
|
|
84
|
+
raise ValueError("Invalid review binding")
|
|
85
|
+
if record["status"] == "confirmed" and any(
|
|
86
|
+
not isinstance(record.get(k), str) or not record[k].strip()
|
|
87
|
+
for k in ("source", "evidence", "reviewer", "reviewed_at")
|
|
88
|
+
):
|
|
89
|
+
raise ValueError("Confirmed reviews require source, evidence, reviewer and time")
|
|
90
|
+
return data["fields"]
|
|
91
|
+
except (ValueError, TypeError, AttributeError, KeyError) as exc:
|
|
92
|
+
raise UserActionRequired(f"Invalid atk_truth for {row.get('atk_id')}: {exc}") from exc
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def truth_status(row, field, *, standard=None):
|
|
96
|
+
record = truth_records(row).get(field)
|
|
97
|
+
if record is None:
|
|
98
|
+
return {"field": field, "status": "unreviewed", "reason": "No review recorded"}
|
|
99
|
+
current = standard if standard is not None else record["standard"]
|
|
100
|
+
stale = (
|
|
101
|
+
subject_digest(
|
|
102
|
+
row,
|
|
103
|
+
field,
|
|
104
|
+
record["input_fields"],
|
|
105
|
+
current,
|
|
106
|
+
source=record.get("source", ""),
|
|
107
|
+
evidence=record.get("evidence", ""),
|
|
108
|
+
)
|
|
109
|
+
!= record["subject_sha256"]
|
|
110
|
+
)
|
|
111
|
+
return {
|
|
112
|
+
"field": field,
|
|
113
|
+
"status": "stale" if stale else record["status"],
|
|
114
|
+
"reason": "Input, answer or relevant standard changed" if stale else "Recorded review declaration",
|
|
115
|
+
"record": record,
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def review_context(path, rows, field, input_fields=None):
|
|
120
|
+
path = Path(path)
|
|
121
|
+
standard = standard_snapshot(path.parent.parent / "context.md")
|
|
122
|
+
inputs = input_fields or next(([k] for k in INPUT_CANDIDATES if rows and k in rows[0]), [])
|
|
123
|
+
truth = []
|
|
124
|
+
for row in rows:
|
|
125
|
+
state = truth_status(row, field, standard=standard)
|
|
126
|
+
state["atk_id"] = row.get("atk_id", "")
|
|
127
|
+
state["input_fields"] = input_fields or state.get("record", {}).get("input_fields", inputs)
|
|
128
|
+
truth.append(state)
|
|
129
|
+
return {
|
|
130
|
+
"dataset_sha256": hashlib.sha256(path.read_bytes()).hexdigest(),
|
|
131
|
+
"dataset_scope": digest(str(path.resolve())),
|
|
132
|
+
"standard": standard,
|
|
133
|
+
"truth": truth,
|
|
134
|
+
"truth_counts": {
|
|
135
|
+
s: sum(r["status"] == s for r in truth) for s in ("unreviewed", "pending", "confirmed", "disputed", "stale")
|
|
136
|
+
},
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def validate_review_binding(review, context):
|
|
141
|
+
for key in ("dataset_sha256", "dataset_scope"):
|
|
142
|
+
if review.get(key) != context[key]:
|
|
143
|
+
raise UserActionRequired("Review is stale or belongs to another dataset; inspect the current data again.")
|
|
144
|
+
if review.get("standard_sha256") != context["standard"]["sha256"]:
|
|
145
|
+
raise UserActionRequired("Review standard changed; inspect the current standard again.")
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def apply_review(row, review, standard, *, answer_changed=False):
|
|
149
|
+
field = review.get("field", "")
|
|
150
|
+
try:
|
|
151
|
+
inputs = json.loads(review.get("input_fields", "[]"))
|
|
152
|
+
except (ValueError, TypeError) as exc:
|
|
153
|
+
raise UserActionRequired("input_fields must be a JSON array.") from exc
|
|
154
|
+
validate_inputs(row, field, inputs)
|
|
155
|
+
status = review.get("review_status")
|
|
156
|
+
if status not in REVIEW_STATES:
|
|
157
|
+
raise UserActionRequired("review_status must be pending, confirmed or disputed.")
|
|
158
|
+
if status == "confirmed" and (
|
|
159
|
+
not row[field].strip() or any(not review.get(k, "").strip() for k in ("source", "evidence", "reviewer"))
|
|
160
|
+
):
|
|
161
|
+
raise UserActionRequired("Confirmation requires a non-empty answer, source, evidence and reviewer.")
|
|
162
|
+
records = truth_records(row)
|
|
163
|
+
record = {
|
|
164
|
+
"status": "pending" if answer_changed else status,
|
|
165
|
+
"input_fields": inputs,
|
|
166
|
+
"source": review.get("source", ""),
|
|
167
|
+
"evidence": review.get("evidence", ""),
|
|
168
|
+
"reviewer": review.get("reviewer", ""),
|
|
169
|
+
"standard": standard,
|
|
170
|
+
"subject_sha256": subject_digest(
|
|
171
|
+
row, field, inputs, standard, source=review.get("source", ""), evidence=review.get("evidence", "")
|
|
172
|
+
),
|
|
173
|
+
}
|
|
174
|
+
old = records.get(field, {})
|
|
175
|
+
# Reapplying the same declaration does not manufacture a new review event.
|
|
176
|
+
record["reviewed_at"] = old.get("reviewed_at") if all(old.get(k) == v for k, v in record.items()) else None
|
|
177
|
+
record["reviewed_at"] = record["reviewed_at"] or datetime.now(UTC).isoformat()
|
|
178
|
+
records[field] = record
|
|
179
|
+
row[TRUTH_FIELD] = json.dumps({"schema": 1, "fields": records}, ensure_ascii=False, sort_keys=True)
|
|
@@ -6,6 +6,7 @@ from agent_tune_kit.utils.csv_io import parse_csv
|
|
|
6
6
|
from agent_tune_kit.utils.csv_io import write_csv_atomic as write_dataset_atomic
|
|
7
7
|
from agent_tune_kit.workspace import DATASET_PATH
|
|
8
8
|
|
|
9
|
+
from .truth import TRUTH_FIELD, apply_review, review_context, validate_review_binding
|
|
9
10
|
from .validation import find_duplicates, parse_dataset_csv
|
|
10
11
|
|
|
11
12
|
REVIEW_FILENAME = "dataset_review.csv"
|
|
@@ -18,7 +19,7 @@ def parse_review_csv(path: Path, dataset_rows: list[dict[str, str]]) -> tuple[li
|
|
|
18
19
|
missing = [field for field in REVIEW_REQUIRED_FIELDS if field not in fieldnames]
|
|
19
20
|
if missing:
|
|
20
21
|
raise UserActionRequired(f"{path.as_posix()} is missing required review fields: {', '.join(missing)}")
|
|
21
|
-
review_rows = [row for row in rows if (row.get("review_feedback") or "").strip()]
|
|
22
|
+
review_rows = [row for row in rows if (row.get("review_feedback") or "").strip() or row.get("review_status")]
|
|
22
23
|
if not review_rows:
|
|
23
24
|
raise UserActionRequired(f"{path.as_posix()} contains no non-empty review_feedback values.")
|
|
24
25
|
dataset_ids = {row["atk_id"] for row in dataset_rows}
|
|
@@ -27,9 +28,9 @@ def parse_review_csv(path: Path, dataset_rows: list[dict[str, str]]) -> tuple[li
|
|
|
27
28
|
raise UserActionRequired(
|
|
28
29
|
f"{path.as_posix()} contains review atk_id values absent from dataset.csv: {', '.join(unknown)}"
|
|
29
30
|
)
|
|
30
|
-
duplicates = find_duplicates((row.get("atk_id"
|
|
31
|
+
duplicates = find_duplicates((row.get("atk_id", ""), row.get("field", "")) for row in review_rows)
|
|
31
32
|
if duplicates:
|
|
32
|
-
raise UserActionRequired(f"{path.as_posix()} contains duplicate review
|
|
33
|
+
raise UserActionRequired(f"{path.as_posix()} contains duplicate review targets: {duplicates}")
|
|
33
34
|
return fieldnames, review_rows
|
|
34
35
|
|
|
35
36
|
|
|
@@ -82,7 +83,11 @@ def parse_updates_csv(
|
|
|
82
83
|
if not updates:
|
|
83
84
|
raise UserActionRequired(f"{path.as_posix()} contains no non-empty {field} updates.")
|
|
84
85
|
dataset_ids = {row["atk_id"] for row in dataset_rows}
|
|
85
|
-
review_ids =
|
|
86
|
+
review_ids = (
|
|
87
|
+
{row["atk_id"] for row in review_rows if not row.get("field") or row["field"] == field}
|
|
88
|
+
if review_rows is not None
|
|
89
|
+
else dataset_ids
|
|
90
|
+
)
|
|
86
91
|
unknown = sorted({row["atk_id"] for row in updates if row["atk_id"] not in dataset_ids})
|
|
87
92
|
if unknown:
|
|
88
93
|
raise UserActionRequired(f"{path.as_posix()} contains unknown atk_id values: {', '.join(unknown)}")
|
|
@@ -144,15 +149,18 @@ def build_context(
|
|
|
144
149
|
}
|
|
145
150
|
|
|
146
151
|
|
|
147
|
-
def inspect_dataset(
|
|
152
|
+
def inspect_dataset(
|
|
153
|
+
dataset_path=DATASET_PATH, *, direct=False, review_path=None, home_dir=None, field="ground_truth", input_fields=None
|
|
154
|
+
):
|
|
148
155
|
dataset_path = Path(dataset_path)
|
|
149
|
-
if not field.strip() or field
|
|
156
|
+
if not field.strip() or field in {"atk_id", TRUTH_FIELD}:
|
|
150
157
|
raise UserActionRequired("Answer field must be non-empty; cannot update atk_id.")
|
|
151
158
|
if not dataset_path.is_file():
|
|
152
159
|
raise UserActionRequired(f"Dataset {dataset_path} is missing; run atk-dataset or atk-init first.")
|
|
153
160
|
_, rows = parse_dataset_csv(dataset_path)
|
|
161
|
+
trust = review_context(dataset_path, rows, field, input_fields)
|
|
154
162
|
if direct:
|
|
155
|
-
return {"dataset_path": str(dataset_path), "field": field, "rows": rows}
|
|
163
|
+
return {"dataset_path": str(dataset_path), "field": field, "rows": rows, **trust}
|
|
156
164
|
review = resolve_review_path(
|
|
157
165
|
str(review_path) if review_path else None,
|
|
158
166
|
dataset_path.parent,
|
|
@@ -160,24 +168,57 @@ def inspect_dataset(dataset_path=DATASET_PATH, *, direct=False, review_path=None
|
|
|
160
168
|
rows,
|
|
161
169
|
)
|
|
162
170
|
_, feedback = parse_review_csv(review, rows)
|
|
163
|
-
|
|
171
|
+
for row in feedback:
|
|
172
|
+
if row.get("review_status") or row.get("dataset_sha256"):
|
|
173
|
+
validate_review_binding(row, trust)
|
|
174
|
+
return {**build_context(dataset_path, review, rows, feedback), **trust}
|
|
164
175
|
|
|
165
176
|
|
|
166
177
|
def update_dataset(
|
|
167
|
-
updates_path
|
|
178
|
+
updates_path=None,
|
|
179
|
+
dataset_path=DATASET_PATH,
|
|
180
|
+
*,
|
|
181
|
+
direct=False,
|
|
182
|
+
review_path=None,
|
|
183
|
+
home_dir=None,
|
|
184
|
+
field="ground_truth",
|
|
185
|
+
input_fields=None,
|
|
168
186
|
):
|
|
169
|
-
context = inspect_dataset(
|
|
187
|
+
context = inspect_dataset(
|
|
188
|
+
dataset_path, direct=direct, review_path=review_path, home_dir=home_dir, field=field, input_fields=input_fields
|
|
189
|
+
)
|
|
170
190
|
fields, rows = parse_dataset_csv(Path(dataset_path))
|
|
191
|
+
before = [dict(row) for row in rows]
|
|
171
192
|
feedback = None
|
|
172
193
|
if not direct:
|
|
173
194
|
_, feedback = parse_review_csv(Path(context["review_path"]), rows)
|
|
174
|
-
updates = parse_updates_csv(Path(updates_path), rows, feedback, field)
|
|
195
|
+
updates = parse_updates_csv(Path(updates_path), rows, feedback, field) if updates_path else []
|
|
196
|
+
if not updates and not any(r.get("review_status") for r in feedback or []):
|
|
197
|
+
raise UserActionRequired("Provide answer updates or explicit content-bound review decisions.")
|
|
175
198
|
output_fields, output_rows, count = apply_updates(fields, rows, updates, field)
|
|
176
|
-
|
|
199
|
+
original = {row["atk_id"]: row for row in before}
|
|
200
|
+
by_id = {row["atk_id"]: row for row in output_rows}
|
|
201
|
+
for review in feedback or []:
|
|
202
|
+
if review.get("review_status"):
|
|
203
|
+
row = by_id[review["atk_id"]]
|
|
204
|
+
apply_review(
|
|
205
|
+
row,
|
|
206
|
+
review,
|
|
207
|
+
context["standard"],
|
|
208
|
+
answer_changed=original[row["atk_id"]].get(review.get("field")) != row.get(review.get("field")),
|
|
209
|
+
)
|
|
210
|
+
if TRUTH_FIELD not in output_fields:
|
|
211
|
+
output_fields.append(TRUTH_FIELD)
|
|
212
|
+
reviewed = sum(a.get(TRUTH_FIELD, "") != b.get(TRUTH_FIELD, "") for a, b in zip(before, output_rows, strict=True))
|
|
213
|
+
current = review_context(Path(dataset_path), before, field)
|
|
214
|
+
if any(current[k] != context[k] for k in ("dataset_sha256", "standard")):
|
|
215
|
+
raise UserActionRequired("Dataset or standard changed during review; nothing was written.")
|
|
216
|
+
if count or reviewed:
|
|
177
217
|
write_dataset_atomic(Path(dataset_path), output_fields, output_rows)
|
|
178
218
|
result = {
|
|
179
219
|
"dataset": str(dataset_path),
|
|
180
220
|
"updated_rows": count,
|
|
221
|
+
"reviewed_rows": reviewed,
|
|
181
222
|
f"{field}_column": "existing" if field in fields else "appended",
|
|
182
223
|
}
|
|
183
224
|
if not direct:
|
|
@@ -4,6 +4,8 @@ from typing import Any
|
|
|
4
4
|
from agent_tune_kit.errors import UserActionRequired
|
|
5
5
|
from agent_tune_kit.utils.csv_io import parse_csv
|
|
6
6
|
|
|
7
|
+
from .truth import truth_records
|
|
8
|
+
|
|
7
9
|
|
|
8
10
|
def validate_dataset_rows(fieldnames: list[str], rows: list[dict[str, str]]) -> None:
|
|
9
11
|
if "atk_id" not in fieldnames:
|
|
@@ -11,6 +13,7 @@ def validate_dataset_rows(fieldnames: list[str], rows: list[dict[str, str]]) ->
|
|
|
11
13
|
seen: set[str] = set()
|
|
12
14
|
invalid: list[str] = []
|
|
13
15
|
for row in rows:
|
|
16
|
+
truth_records(row)
|
|
14
17
|
atk_id = (row.get("atk_id") or "").strip()
|
|
15
18
|
if not atk_id or not atk_id.isdigit() or int(atk_id) <= 0 or atk_id in seen:
|
|
16
19
|
invalid.append(atk_id or "<empty>")
|