Document audit policy and add full kept export
This commit is contained in:
238
docs/resolved_and_audit_policy.md
Normal file
238
docs/resolved_and_audit_policy.md
Normal 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`。
|
||||
|
||||
### 模式 B:5k+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
|
||||
```
|
||||
Reference in New Issue
Block a user