Document audit policy and add full kept export

This commit is contained in:
Codex
2026-06-24 22:43:04 +08:00
parent f06e573b04
commit 28b839eff0
4 changed files with 454 additions and 4 deletions

View File

@@ -0,0 +1,238 @@
# Open-SWE-Traces resolved 标签与 audit 评价体系
本文说明 `nvidia/Open-SWE-Traces``resolved` 字段的含义,以及本仓库在
`scripts/filtering/audit_native_traces.py` 中实现的 trajectory 质量审计规则。
## 全量 resolved 分布
统计时间2026-06-24
统计数据位置:`data/Open-SWE-Traces/data/*_trajectories/*.parquet`
| split | resolved=1 | resolved=0 | resolved=-1 | total |
| --- | ---: | ---: | ---: | ---: |
| `minimax_m25_openhands_trajectories` | 15,941 | 22,911 | 11,096 | 49,948 |
| `minimax_m25_sweagent_trajectories` | 19,551 | 24,554 | 13,163 | 57,268 |
| `qwen35_openhands_trajectories` | 13,657 | 29,147 | 12,684 | 55,488 |
| `qwen35_sweagent_trajectories` | 16,095 | 18,875 | 9,815 | 44,785 |
| **total** | **65,244** | **95,487** | **46,758** | **207,489** |
整体比例:
| 状态 | 数量 | 占比 |
| --- | ---: | ---: |
| resolved明确解决 | 65,244 | 31.44% |
| unresolved明确未解决 | 95,487 | 46.02% |
| unknown/uncertain不确定或非标准成功状态 | 46,758 | 22.54% |
## resolved 是什么
`resolved` 是 Open-SWE-Traces 数据集自带的上游评测结果标签,不是本仓库的
audit 规则计算出来的字段。
当前数据中可观察到三个取值:
- `resolved == 1`:上游认为该 trajectory 的最终 patch 解决了对应任务。
- `resolved == 0`:上游认为未解决。
- `resolved == -1`:不确定、异常或非标准成功状态。当前脚本不把它解释为成功。
在本仓库的 filtering 逻辑中,只有 `resolved == 1` 被视为明确 solved。代码在
`scripts/filtering/audit_native_traces.py:213-219` 将所有 `resolved != 1` 的样本标记为
`unresolved`
```python
resolved = int(row.get("resolved") or 0)
if resolved != 1:
issues.append("unresolved")
```
注意:`unresolved` 本身只是 audit flag不是 hard filter。也就是说`resolved=0`
`resolved=-1` 不会因为这个字段单独被丢弃。原因是失败轨迹中仍可能存在可学习的
定位、诊断、部分修复或验证行为。
## audit flag 是什么
`audit flag` 是本仓库对每条 trajectory 做规则审计后生成的质量标签,主要用于判断
样本是否适合作为 SFT 数据。
入口函数是 `audit_row`,位置是:
- `scripts/filtering/audit_native_traces.py:140-233`
`audit_row` 会检查以下维度:
- tool call JSON 是否能解析:`parse_tool_arguments`,见 `scripts/filtering/audit_native_traces.py:71-84`
- tool 名是否在已知集合中:`KNOWN_TOOLS`,见 `scripts/filtering/audit_native_traces.py:21-31`
- `str_replace_editor` command 是否在已知集合中:`SOURCE_TOOL_COMMANDS`,见 `scripts/filtering/audit_native_traces.py:35-43`
- tool call 和 tool result 是否匹配:见 `scripts/filtering/audit_native_traces.py:144-205`
- trajectory 是否过长:见 `scripts/filtering/audit_native_traces.py:60-63``208-209`
- 是否存在重复、无推进的工具调用 loop`detect_unproductive_loop`,见 `scripts/filtering/audit_native_traces.py:125-137``210-211`
- 未解决样本是否有明显走偏信号:见 `scripts/filtering/audit_native_traces.py:213-219`
- final patch 是否为空、是否是 unified diff、patch 文件是否在 trajectory 中出现过:`patch_empty_or_inconsistent`,见 `scripts/filtering/audit_native_traces.py:104-122`
`audit_row` 返回两类信息:
- `issues`:该样本的全部 audit flags。
- `details`:统计型信息,例如 tool 调用数量、assistant turn 数、总字符数、编辑次数、验证次数和 trajectory 长度。见 `scripts/filtering/audit_native_traces.py:221-233`
## audit flag 与 hard filter 的区别
不是所有 audit flag 都会导致样本被过滤。真正决定过滤的是
`hard_filter_issues(issues)`,位置是:
- `scripts/filtering/audit_native_traces.py:287-312`
`hard_filter_issues` 会从全部 `issues` 中挑出严重问题。只要 hard issues 非空,
`audit_file` 就会把该样本计入 `filtered_rows`,并把样本写入坏例子采样。对应逻辑见:
- `scripts/filtering/audit_native_traces.py:255-276`
因此:
- `audit_flags` 是全量质量诊断。
- `hard_filter_issues` 是实际过滤依据。
- `resolved != 1` 只会生成 `unresolved` 软标记;单独的 `unresolved` 不会触发 hard filter。
## 当前 hard filter 规则
`hard_filter_issues` 当前包含两类规则:精确匹配和前缀匹配。
前缀匹配,见 `scripts/filtering/audit_native_traces.py:289-292`
- `unknown_tool_name:*`
- `unknown_str_replace_editor_command:*`
精确匹配,见 `scripts/filtering/audit_native_traces.py:293-308`
| hard issue | 含义 |
| --- | --- |
| `malformed_tool_call_json` | tool arguments 是字符串但无法解析为 JSON |
| `tool_arguments_not_string_or_object` | tool arguments 既不是字符串也不是对象 |
| `tool_arguments_json_not_object` | tool arguments JSON 能解析,但不是对象 |
| `tool_arguments_polluted_by_markup_or_text` | tool arguments 中混入明显 markup 或自然语言污染 |
| `tool_name_missing_or_invalid` | tool 名缺失或类型不合法 |
| `tool_result_without_pending_tool_call` | 出现 tool result但没有等待中的 tool call |
| `tool_call_without_tool_result` | 存在未闭合的 tool call |
| `final_patch_empty` | final patch 为空 |
| `resolved_but_patch_empty` | 上游标记 solved但 final patch 为空 |
| `final_patch_not_unified_diff` | final patch 不像 unified diff |
| `patch_files_not_mentioned_in_trajectory` | patch 涉及文件在 trajectory 文本中完全没出现 |
| `trajectory_too_long` | trajectory turn 数或字符数超过阈值 |
| `repeated_tool_call_loop` | 短窗口内重复相同 tool call疑似无推进循环 |
| `unresolved_likely_off_track` | 未解决,同时出现过长或重复 loop 等明显走偏信号 |
## 为什么不直接只保留 resolved=1
如果只保留 `resolved=1`,全量数据会从 207,489 条降到 65,244 条,只剩 31.44%。
这对训练会丢掉大量可能有价值的行为轨迹,包括:
- 如何理解任务;
- 如何定位相关文件;
- 如何读取和诊断代码;
- 如何尝试复现问题;
- 如何做部分修复;
- 如何发现验证失败。
因此当前策略更保守:
1. 保留 `resolved` 作为上游结果标签。
2. 用 audit/hard filter 去掉结构错误、工具链错误、patch 不一致、超长 loop、明显走偏等坏样本。
3. 在后续训练或采样阶段,再根据任务目标决定是否偏向 solved 样本,或保留部分 unresolved 轨迹用于过程学习。
## 运行相关统计的命令
## 两种数据导出模式
当前仓库把“全量训练数据筛选”和“小规模 probe/测试数据构建”拆成两个模式,避免把
pipeline smoke test 和正式训练数据混在一起。
### 模式 A全量 hard-filter-kept 数据
入口:
```bash
python scripts/repurposing/build_swift_full_kept.py \
--input-root data/Open-SWE-Traces \
--output-dir runs/training_full_kept_swift
```
用途:
- 用于正式 SFT 数据准备。
- 扫描四个 source config 的所有 trajectory。
- 对每条样本调用 `audit_row``hard_filter_issues`
- 丢弃所有 hard filter 非空的样本。
- 将剩余样本按 ModelScope-SWIFT messages 格式流式写入 `train.jsonl`
为什么使用流式 JSONL
- 全量 kept 规模约 190k 条。
- trajectory 很长,不能像 5k probe 那样把所有样本先放入内存再统一写出。
- 默认只写 `train.jsonl``metadata.json`;如需 gzip可加 `--write-gzip`
### 模式 B5k+500 probe/测试数据
入口:
```bash
python scripts/repurposing/build_swift_training_probe_5k.py
python scripts/repurposing/build_swift_validation_500.py
```
用途:
- 用于训练链路 smoke test、格式验证、上传测试和小规模 probing。
- `build_swift_training_probe_5k.py` 从四个 source config 各取 1,250 条 hard-filter-kept 样本,总计 5,000 条。
- `build_swift_validation_500.py` 从四个 source config 各随机取 125 条,总计 500 条,并排除 train 中已有的 `trajectory_id`
- 该模式会额外写 parquet/gzip 等方便测试和上传的小规模产物。
关键区别:
| 维度 | 全量 hard-filter-kept 模式 | 5k+500 probe 模式 |
| --- | --- | --- |
| 入口脚本 | `build_swift_full_kept.py` | `build_swift_training_probe_5k.py` + `build_swift_validation_500.py` |
| 目标 | 正式 SFT 数据准备 | 快速测试和 probing |
| 样本规模 | 约 190k kept rows | 5,000 train + 500 validation |
| 采样方式 | 不采样,扫描全量并过滤 | 按四个 source config 均衡采样 |
| 写出方式 | 流式 JSONL避免爆内存 | 内存聚合后写 JSONL/GZIP/Parquet |
| 是否应代表全量分布 | 是,过滤后保留全量 | 否,只是小规模测试集 |
统计 `resolved` 分布:
```bash
cd /ssd/workspace/yi/ti_coding_agent_data_prep
.venv/bin/python - <<'PY'
from pathlib import Path
from collections import Counter
import json
import pyarrow.parquet as pq
configs = [
"minimax_m25_openhands_trajectories",
"minimax_m25_sweagent_trajectories",
"qwen35_openhands_trajectories",
"qwen35_sweagent_trajectories",
]
root = Path("data/Open-SWE-Traces/data")
out = {}
total = Counter()
for cfg in configs:
c = Counter()
for f in sorted((root / cfg).glob("*.parquet")):
table = pq.read_table(f, columns=["resolved"])
for row in table.to_pylist():
c[str(row.get("resolved"))] += 1
out[cfg] = dict(c)
total.update(c)
out["TOTAL"] = dict(total)
print(json.dumps(out, indent=2, ensure_ascii=False))
PY
```
运行 audit
```bash
cd /ssd/workspace/yi/ti_coding_agent_data_prep
.venv/bin/python scripts/filtering/audit_native_traces.py \
--input-root data/Open-SWE-Traces \
--output-dir runs/native_trace_audit
```