跳到主要内容

第 23 章:从 Trial 生成 SFT 数据集

一个评测 Job 跑完后,团队通常很快就会看到第二种用途:把成功轨迹整理成监督微调(Supervised Fine-Tuning,SFT)数据。此时最危险的误解是把“能够导出”当成“可以训练”。Trial 目录可能同时保存任务指令、模型原始响应、工具参数、终端输出、Verifier 日志、内部主机名乃至临时凭据;同一任务的多次尝试和同一轨迹的多个前缀样本,还可能跨越训练集与验证集。若直接上传,这些问题会从评测产物变成长期数据资产。

本章把贯穿全书的“系统配置与故障诊断 Agent Benchmark”作为数据源,建立一条可审计的路径:先从 Harbor v0.18.0 的 Trial 提取对话,再执行 Reward 门槛、失败排除、脱敏、精确去重、按任务分组切分和质量抽检,最后只把通过门禁的 JSONL 交给训练团队。所有示例数据均为教学性构造;本章没有调用真实模型、没有上传数据,也没有训练或声称提升任何模型。

读完本章,读者应当能够:

  • 解释 ATIF Trial 如何变成一行或多行对话样本;
  • 正确处理原始内容、上下文压缩、continuation 和 subagent 轨迹;
  • 区分 Harbor 内置导出能力与需要自行实现的数据治理步骤;
  • 用确定性脚本构建并验收一个不包含已知测试凭据的 SFT 数据集。

23.1 先划清边界:导出器不是训练流水线

本书继续锁定 Harbor v0.18.0,提交 527d50deb63a5d279e8c20593c18a2cbc7f61f9e;该版本把 Hugging Face datasets>=4.4.1 放在 huggingface 可选依赖中。1 Harbor 随附的 SFT 文档把 traces_utils、独立 CLI、Job 结束后导出和 sweeps 分流列为入口;这些入口最终复用同一组 trace helper。2 本章所说的“Harbor 导出”只包括以下能力:发现 Trial、读取 ATIF 轨迹、构造 OpenAI 风格的 conversations 列、可选增加 ShareGPT 列、按正负 Reward 过滤、选择全部或最后一个 episode、处理特定的 continuation/subagent 文件,并返回 datasets.Dataset;还可显式推送到 Hugging Face Hub。3

下面这些事情 不是 v0.18.0 导出器替你完成的:

  • 任意 Reward 阈值,例如 reward >= 0.8
  • PII、凭据、内部地址、客户数据或 Verifier 答案的脱敏;
  • 精确或语义去重;
  • 防泄露的 train/validation/test 分组切分;
  • 许可证、授权、保留期限与删除请求管理;
  • SFT 模板适配、tokenization、训练、模型评测或收益归因。

因此,工程边界应当画成下面这条单向流水线:

只读 Trial 区
-> ATIF/元数据校验
-> Harbor 对话导出(隔离暂存区)
-> Reward 与异常门禁
-> 脱敏和推理内容策略
-> 去重
-> 按任务组切分
-> 静态校验 + 受控人工抽检
-> 可发布数据区
-> 训练系统(不在本章范围内)

每一步都产生计数和拒绝原因。原始 Trial 不应因转换成功而删除;转换结果也不应覆盖原始证据。这样,后续发现脱敏规则缺陷时,可以修订策略并重新构建,而不是在一个来历不明的 JSONL 上继续打补丁。

23.2 Trial 到样本:v0.18.0 实际做了什么

23.2.1 输入不是任意日志,而是带元数据的已注册 ATIF Agent

ATIF(Agent Trajectory Interchange Format)是 Harbor 的 JSON 轨迹规范。根对象保存 agent、顺序 steps、可选指标、continuation 引用和 subagent 轨迹;step 的来源只能是 systemuseragent,并可携带工具调用与观察。4 但“文件看起来像 ATIF”仍不足以让 v0.18.0 的 SFT 导出器接受它。

源码中的发现逻辑把“存在 agent/ 子目录”作为 Trial 候选条件;随后 load_run_metadata() 必须向上找到 result.json,否则该 Trial 会被跳过。主轨迹还必须位于 agent/trajectory.json5 高层导出函数会把元数据里的 Agent 名称转成 AgentName,再通过工厂取得类并检查 SUPPORTS_ATIF。换言之,本版本的实现要求名称能被 Harbor 注册表识别,未知的外部名称即使写出了 ATIF,也不能仅凭文件格式保证被该入口导出。6

注意:v0.18.0 随附 SFT 文档把能力概括为“任何产生 ATIF 的 Agent”。锁定源码还多了一层 AgentName/factory 门禁。本书按源码描述;接入 Custom Agent 时,应先做一个最小导出回归测试,不要只验证 JSON schema。

23.2.2 一条 ATIF 轨迹会产生累积前缀样本

导出器把每个未标记 is_copied_contextsource="agent" step 当作一个 episode 的结束点。第一个样本包含到第一个 Agent step 为止的历史,第二个包含到第二个 Agent step 为止的历史,以此类推。设置 episodes="last" 时,只保留该 Trial 最后一个完成的前缀。被复制的 Agent step 不产生新的边界,但仍可能作为后续对话的上下文出现。7

这一区别直接影响数据集:all 适合研究每个中间决策,却会生成大量相互重叠的前缀;last 更接近“每个 Trial 一条完整主对话”。本章的治理示例选择 last,避免同一条长轨迹按长度被重复加权。若训练目标是单步工具决策,可以改用 all,但必须在采样权重和切分策略中显式处理前缀相关性。

ATIF system step 在导出对话时被映射成 user role;Agent step 变成 assistant。若存在 reasoning_content,导出器会把它放进 <think>...</think>,再附加消息和序列化后的 <tool_call>;上一轮 Agent 的 observation 会作为下一轮之前的 user 消息。工具定义默认既可作为列保存,也会嵌入第一个 system-derived 消息的 <tools> 块。8 这是面向训练的转换,不是 ATIF 的无损副本。调试、审计和重建仍应以原始 trajectory.json 为准。

23.2.3 success 只是“主 Reward 大于零”

导出结果的 result 来自 Job 聚合统计或 Trial 的 result.json。对于 Trial 级文件,源码优先返回 exception_info.exception_type;否则只读取 verifier_result.rewards["reward"]。多维 Reward 中其他键不会参与这一过滤。9 _trial_is_success() 尝试把结果转成浮点数,并以 > 0.0 判定成功;异常字符串判为失败,使用过滤器时缺失结果的 Trial 会被跳过。10

于是:

  • --filter success 不等于 reward == 1.0
  • 0.01 在内置过滤器看来也是成功;
  • reward = 0 属于失败,而不是“可保留的零分样本”;
  • correctness=1, safety=0 若没有名为 reward 的主维度,不能据此完成所需筛选;
  • 任意阈值和多维门禁必须在导出后自行实现。

这也是本章脚本不传 success_filter="success" 的原因:它先保留可读取的行,再用明确的 MIN_REWARD 做二次门禁,并拒绝非数值结果。

23.2.4 把导出行当作有版本的数据契约

主数据行至少包含 conversationsagentmodelmodel_providerdatetaskepisoderun_idtrial_nameresult;工具定义、instruction、Verifier 输出、ShareGPT 列与 trace_source 取决于参数和输入轨迹。3 不要让训练代码直接依赖“当前碰巧出现的所有列”。更稳妥的做法是先定义允许列,再显式重命名和丢弃:conversations 是训练负载,task_group 与 hash 是治理元数据,Reward 只用于筛选或离线分析;时间、明文 Trial 名和运行 ID 通常不进入公开数据。

还要验证“一个样本”的含义。导出器生成的是对话前缀,不是原始 ATIF step,也不是自动套好目标模型 chat template 的 token 序列。不同模型可能要求不同的 system role、工具调用编码、结束标记和 loss mask。应在数据治理之后、训练之前再做模型专用适配,并保留适配器版本;否则同一份 JSONL 换一个 tokenizer 就可能得到不同监督目标。这个阶段属于训练系统,不是 Harbor v0.18.0 导出器的保证。

23.3 在运行时保存可训练、可解释的轨迹

数据质量从 Trial 运行时开始,而不是从导出命令开始。Terminus-2 的 TrajectoryConfig 有两个与 SFT 直接相关的布尔项:raw_content=True 保存 LLM 的原始响应,而不是只保存解析后的 analysis/plan 与合成工具调用;linear_history=True 在上下文总结发生时把轨迹切成 continuation 文件,使每个文件代表一段实际送给模型的连续历史。两者默认均为 False11

在 Job 配置的 Agent kwargs 中可以这样表达:

agents:
- name: terminus-2
model_name: provider/model-placeholder
kwargs:
trajectory_config:
raw_content: true
linear_history: true

这里的模型名是教学占位符,不可直接运行;配置形状已按 v0.18.0 的 AgentConfig.kwargs 与 Terminus-2 构造参数核验。12 raw_content=True 并不意味着“更安全”或“更准确”,只意味着更忠实地保留模型输出,因此更可能保存意外回显的凭据、受保护文本或显式推理内容。采集目的若不需要某类字段,就不要仅为“以后也许有用”而保留。NIST SP 800-122 把最小化 PII 的使用、收集和保留视为基本隐私原则;对轨迹数据也应采用同样的最小必要思路。13

linear_history=True 且发生总结时,Terminus-2 在前一段写入 continued_trajectory_ref,随后生成 trajectory.cont-1.json 等文件;复制到新段的上下文会被标记为 is_copied_context14 v0.18.0 的导出器从 trajectory.json 沿这条引用链读取 continuation,并为 episode 重新编号。它不会把名字碰巧类似 continuation 的文件盲目拼接。

Subagent 需要单独决策。Python 入口默认 export_subagents=Truemerge_subagents=True,会把文件型 subagent 对话与主数据合并,并增加 trace_source。文件扫描针对 trajectory.*.json,排除 .cont-;每个 subagent 文件按一条完整对话导出。15 ATIF v1.7 本身还允许根对象内的 subagent_trajectories 嵌入形式,但 v0.18.0 这一 SFT 收集路径扫描的是外部文件,并没有遍历嵌入数组。由此可以推断:不要把 ATIF schema 的所有表达能力等同于当前 SFT 导出器的消费能力。

本章先使用 --no-subagents 建立主 Agent 数据集。总结 subagent 的“摘要”“提问”“回答”是不同训练目标,若确实要用,应单独导出、单独抽检并单独记录来源,不能因为默认合并就与主任务回答混为一谈。

23.4 做一次确定性原始导出

23.4.1 安装与命令

从锁定源码目录或已固定到 v0.18.0 的项目环境运行:

uv sync --extra huggingface
uv run harbor traces export \
--path jobs/system-diagnosis-sft \
--recursive \
--episodes last \
--filter success \
--no-subagents \
--no-sharegpt \
--no-push \
--verbose

harbor traces 在 v0.18.0 的主 CLI 中被注册为 hidden command,但子命令和上述选项都存在;--push 还必须配合 --repo16 这里刻意使用 --no-pushpush_dataset() 会读取 HUGGINGFACE_TOKENHF_TOKEN 并把数据发送到指定 Hub 仓库;这应当发生在脱敏和审批之后,而不是原始导出阶段。17

命令行入口只打印行数。若要保存本地文件或继续治理,应使用 Python API:

from pathlib import Path
from harbor.utils.traces_utils import export_traces

raw = export_traces(
root=Path("jobs/system-diagnosis-sft"),
recursive=True,
episodes="last",
success_filter=None,
export_subagents=False,
include_instruction=False,
include_verifier_output=False,
embed_tools_in_conversation=False,
use_rich_progress=False,
)

print(len(raw), raw.column_names)
# 仅在隔离暂存区需要快照时:raw.to_parquet("quarantine/raw.parquet")

Dataset.to_parquet() 是 Hugging Face Datasets 提供的本地导出方法,不是 Harbor 自定义文件格式。18

include_instructioninclude_verifier_output 默认关闭。后者会拼接 verifier/test-stdout.txttest-stderr.txt;测试输出可能包含标准答案、测试路径或内部诊断信息,不应为了“元数据更完整”而默认加入训练样本。19 同样,文本导出器检测到 ATIF 消息或 observation 中的图片时会抛出 MultimodalExportError,避免静默丢图;本章脚本不处理多模态轨迹。20

本章验证时构造了两个无模型参与的 Trial:一个 reward=1.0,一个 reward=0.0,二者各有三个 ATIF step。对锁定源码调用导出器后,得到如下确定性测试结果

success_rows=1
failure_rows=1
success_episode=episode-1

之所以最后一个 episode 是 episode-1,是因为轨迹中有两个 Agent step。这个输出不是模型评测成绩,只验证了 discovery、Reward 过滤、last 选择和 ShareGPT 转换的代码路径。

23.5 构建带泄露防护的治理脚本

下面的 build_sft.py 是本书提供的教学实现,不是 Harbor v0.18.0 内置命令。它实现八条保守策略:只取主 Agent 最后一个 episode;使用任意数值阈值;拒绝异常或无 Reward 行;移除显式 <think> 内容;替换若干已知敏感模式;只保留允许的元数据;在脱敏后做精确去重;最后按 task 分组做稳定切分。

from __future__ import annotations

import argparse
import hashlib
import json
import math
import re
from pathlib import Path
from typing import Any

from harbor.utils.traces_utils import export_traces

MIN_REWARD = 0.8 # 教学策略,不是 Harbor 默认值
POLICY_VERSION = "book-sft-policy-v1"

THINK_BLOCK = re.compile(r"<think>.*?</think>", re.DOTALL | re.IGNORECASE)
SENSITIVE_PATTERNS = [
re.compile(r"\bsk-[A-Za-z0-9_-]{8,}\b"),
re.compile(r"(?i)\bBearer\s+[A-Za-z0-9._~+/=-]+"),
re.compile(r"(?i)\b(password|passwd|token|secret)\s*[:=]\s*[^\s,;]+"),
re.compile(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b"),
re.compile(
r"-----BEGIN [A-Z ]*PRIVATE KEY-----.*?"
r"-----END [A-Z ]*PRIVATE KEY-----",
re.DOTALL,
),
]


def sha256_text(value: str) -> str:
return hashlib.sha256(value.encode("utf-8")).hexdigest()


def redact_text(value: str) -> str:
value = THINK_BLOCK.sub("[REDACTED_REASONING]", value)
for pattern in SENSITIVE_PATTERNS:
value = pattern.sub("[REDACTED]", value)
return value


def redact(value: Any) -> Any:
if isinstance(value, str):
return redact_text(value)
if isinstance(value, list):
return [redact(item) for item in value]
if isinstance(value, dict):
return {key: redact(item) for key, item in value.items()}
return value


def numeric_reward(row: dict[str, Any]) -> float | None:
try:
reward = float(row.get("result"))
except (TypeError, ValueError):
return None
return reward if math.isfinite(reward) else None


def validate_conversation(messages: Any) -> list[dict[str, str]]:
if not isinstance(messages, list) or not messages:
raise ValueError("empty conversation")
clean: list[dict[str, str]] = []
for message in messages:
if not isinstance(message, dict):
raise ValueError("message is not an object")
role, content = message.get("role"), message.get("content")
if role not in {"user", "assistant"}:
raise ValueError(f"unsupported role: {role!r}")
if not isinstance(content, str) or not content.strip():
raise ValueError("empty message content")
clean.append({"role": role, "content": content})
if clean[-1]["role"] != "assistant":
raise ValueError("conversation does not end with assistant")
return clean


def split_for_task(task_group: str) -> str:
# 教学用 80/10/10 稳定切分;生产比例应由实验方案决定。
bucket = int(sha256_text(task_group)[:8], 16) % 100
return "train" if bucket < 80 else "validation" if bucket < 90 else "test"


def build(root: Path, output: Path) -> dict[str, int]:
dataset = export_traces(
root=root,
recursive=True,
episodes="last",
success_filter=None, # 在下方应用明确阈值
export_subagents=False,
include_instruction=False,
include_verifier_output=False,
embed_tools_in_conversation=False,
use_rich_progress=False,
)

counts = {
"exported": len(dataset), "below_threshold": 0,
"invalid": 0, "duplicate": 0, "accepted": 0,
}
accepted: list[dict[str, Any]] = []
seen: set[str] = set()

for source in dataset:
row = dict(source)
reward = numeric_reward(row)
if reward is None or reward < MIN_REWARD:
counts["below_threshold"] += 1
continue

task = row.get("task")
if not isinstance(task, str) or not task or task == "unknown-task":
counts["invalid"] += 1
continue

try:
messages = validate_conversation(redact(row.get("conversations")))
except ValueError:
counts["invalid"] += 1
continue

canonical = json.dumps(messages, ensure_ascii=False, sort_keys=True)
fingerprint = sha256_text(canonical)
if fingerprint in seen:
counts["duplicate"] += 1
continue
seen.add(fingerprint)

provenance = "|".join(
str(row.get(key, ""))
for key in ("run_id", "task", "trial_name", "episode")
)
task_group = sha256_text(task)
accepted.append({
"conversations": messages,
"agent": str(row.get("agent", "unknown-agent")),
"model": str(row.get("model", "unknown-model")),
"reward": reward,
"task_group": task_group[:16],
"source_hash": sha256_text(provenance),
"conversation_hash": fingerprint,
"data_label": "teaching-sft-example",
})
counts["accepted"] += 1

output.mkdir(parents=True, exist_ok=True)
splits = {name: [] for name in ("train", "validation", "test")}
for row in accepted:
splits[split_for_task(row["task_group"])].append(row)

for name, rows in splits.items():
with (output / f"{name}.jsonl").open("w", encoding="utf-8") as handle:
for row in rows:
handle.write(json.dumps(row, ensure_ascii=False) + "\n")
counts[f"split_{name}"] = len(rows)

manifest = {
"policy_version": POLICY_VERSION,
"min_reward": MIN_REWARD,
"counts": counts,
"note": "Teaching data only; no model was trained.",
}
(output / "manifest.json").write_text(
json.dumps(manifest, ensure_ascii=False, indent=2), encoding="utf-8"
)
return counts


if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("trial_root", type=Path)
parser.add_argument("output", type=Path)
args = parser.parse_args()
print(json.dumps(build(args.trial_root, args.output), sort_keys=True))

运行方式如下:

uv run python build_sft.py \
jobs/system-diagnosis-sft \
build/sft-v1

wc -l build/sft-v1/*.jsonl
python -m json.tool build/sft-v1/manifest.json

这个脚本故意不在发布行中保留 run_id、明文 trial_name、时间、provider、instruction、Verifier 输出或工具定义。source_hash 纳入 run_id、task、Trial 名与 episode;只有保留只读原始 Trial,或另存受控的 hash→来源映射表时,它才支持回溯,哈希本身不可逆。先脱敏再去重也很重要:两个只在密钥值上不同的样本,脱敏后可能完全相同,应当只保留一次。

警告:示例正则只能识别已列出的模式,不构成匿名化保证,也检测不了所有准标识符、业务机密、编码后的凭据或可从上下文重识别的信息。NIST SP 800-188 明确区分简单遮罩与完整的去标识治理,并要求在发布模型、风险目标和重识别风险之间作系统评估。21 生产流程应叠加组织专用检测器、数据所有者审批、受控人工复核和删除/重建机制。

示例也直接写入 output,因此它不是原子发布器。生产构建应拒绝复用已有版本目录,先在同一文件系统的临时同级目录生成三份 JSONL、私有来源映射和 manifest;校验“导出数 = 阈值拒绝 + 无效 + 重复 + 接收”以及“接收数 = 三个 split 之和”后,再原子切换目录名。manifest 还应记录输入 Trial 清单摘要、构建代码提交、Harbor/Agent 版本和审批状态。远端 Hub 提交是另一条发布事务,不能因为本地 rename 成功就推断上传、权限或历史删除也已完成。

23.6 去重、切分与抽检:最容易出现的三类泄露

23.6.1 行级随机切分会泄露任务

同一 task 的多次 attempt 通常共享 instruction、文件结构和 Verifier 目标;episodes="all" 还会产生同一对话的累积前缀。由这些源码行为可以推断:按行随机切分,很容易把近乎相同的输入分到训练集和验证集。本章以 task 明文的 SHA-256 作为 task_group,同一任务的所有 Trial 必然进入同一 split。若多个 task 来自同一模板或同一底层仓库,还应使用更高层的 template_id、仓库 commit 或案例族作为分组键。

哈希切分提供稳定性,不提供随机性的统计魔法。数据量很小时,某个 split 为空是正常的门禁信号,而不是把几行复制过去的理由。应当先增加独立任务,再冻结分组清单;不能为了得到“好看”的验证分数而移动已看过结果的任务。

23.6.2 精确去重抓不到语义重复

示例的 conversation_hash 只识别脱敏后字节级一致的对话。它抓不到变量名变化、日志时间戳变化、同一模板换一组端口、或同一解法的自然语言改写。更完整的流程至少分三层:规范化后的精确哈希;按 task/template 的结构去重;最后在隔离环境中做近邻或聚类抽检。近邻模型本身也可能把敏感文本发送给外部服务,因此应优先使用本地、经过审批的实现。

去重时不要只保留“第一行”而丢失来源关系。清单至少应记录策略版本、输入 Job/Trial 集合摘要、保留行的 source_hash、剔除原因和构建时间。若同一对话来自多个独立 Trial,可以把来源集合保存在受控 side table,而不是全部写入公开训练文件。

23.6.3 Verifier 与工具输出可能泄露答案

成功轨迹天然倾向于包含正确结果,这正是 SFT 的价值;但它不应包含 Agent 本来不可见的测试实现、Solution 文件或 Verifier 私有日志。如果 Agent 通过越权读取测试得分,reward=1 也不能把这条轨迹洗成高质量数据。Reward 门槛之前,应复用第 13 章的 gaming 审计:检查是否读取 /tests、写 Reward 文件、访问 Solution,或利用了任务包中的答案泄露。

人工抽检不要只抽“看起来正常”的训练行。建议从每个数据来源、Reward 区间、Agent/模型组合、长度区间、含工具调用与不含工具调用、被脱敏与未触发脱敏的样本中分别抽取;失败队列也要抽检,以判断规则是在拦截真实风险,还是误删有效数据。抽检记录只保存样本 hash、结论、问题类别和审阅者,不把敏感正文复制进工单或聊天系统。

23.7 失败模式与排查顺序

导出为零行

先用 --verbose 看 Trial 是否被发现,再依次确认 result.jsonagent/trajectory.json、Agent 名称注册和 SUPPORTS_ATIF。不要先放宽 Reward 门槛;输入结构缺失与样本质量不足是两类问题。episodes="last" 也要求轨迹至少有一个未复制的 Agent step。

success 数量与预期不符

检查 result.jsonverifier_result.rewards.reward,而不是只看 Viewer 中其他维度。记住内置 success 是 >0。若需要 >=0.8、多个维度同时达标或排除特定异常,请在治理层显式编码并把规则写入 manifest。

continuation 丢失或重复

检查每段的 continued_trajectory_ref 是否指向存在的文件,以及新段复制的 step 是否标记 is_copied_context。不要用文件名排序替代引用链。对含总结的真实 Agent,在升级 Harbor 或 Agent 版本时,应把固定 golden trajectory 导出结果纳入回归测试。

多模态轨迹抛错

这是文本导出器的保护行为。把多模态 Trial 分流到保留图片引用和媒体文件的专用训练格式;不要捕获异常后直接删除图片 part,因为那会改变模型当时实际看到的输入。

脱敏后仍发现凭据

立即阻断发布并轮换仍然有效的凭据;定位该值从 instruction、工具参数、observation、原始响应还是 Verifier 输出进入。添加规则后必须从原始 Trial 重建全部 split,因为只修补一个发布文件会留下缓存、Parquet、Hub commit 或下游副本。若数据已推送远端,还要按平台和组织流程处理历史版本,而不是只删除最新文件。

23.8 验收清单

一个 SFT 数据版本至少通过以下门禁:

  1. Harbor 版本、commit、Agent 版本、模型标识和构建策略版本可追溯;
  2. 输入 Trial 均有可读 result.jsonagent/trajectory.json,ATIF 生产者已做导出回归;
  3. Reward 规则写明主维度、阈值、异常处理和缺失值处理;
  4. instruction、Verifier 输出、工具定义、显式推理是否保留都有字段级决策;
  5. 已知凭据扫描、组织专用敏感信息扫描和人工抽检均通过;
  6. 去重发生在脱敏之后,并记录精确重复与结构/语义重复策略;
  7. split 按 task 或更高层案例族分组,跨 split 精确 hash 交集为空;
  8. 每行对话非空、role 合法、最后一条为 assistant,来源 hash 可回溯;
  9. manifest 的输入数、拒绝数、重复数、接收数和各 split 数量守恒;
  10. 推送远端前完成权限、许可证、保留期限和删除流程审批。

只有这些条件满足后,数据才进入“可交付给训练系统”的状态。它仍不等于模型已经训练,更不等于训练会提高独立测试集表现。

23.9 本章小结

  • Harbor v0.18.0 从已注册且声明 ATIF 支持的 Agent Trial 构造 Hugging Face Dataset,但不负责完整数据治理。
  • episodes="all" 生成累积前缀;主 Agent SFT 通常先从 last 开始,并单独处理 subagent 数据。
  • 内置 success 语义是主 reward > 0,任意阈值和多维门禁必须自行实现。
  • raw_contentlinear_history 分别保留原始生成和线性调用历史,却也扩大敏感信息暴露面。
  • 脱敏、去重、分组切分、gaming 审计和人工抽检共同组成发布门禁。
  • 导出成功只说明转换完成;本章没有训练模型,也没有产生训练收益结论。

23.10 练习

  1. 用两个教学 Trial 复现 episodes="all""last",画出每行包含的 step 前缀,并解释为什么行数不同。
  2. 把治理脚本的门禁改为 reward >= 0.8safety >= 1.0。由于当前导出行只保留主 result,说明你需要从何处补充并校验多维 Reward,而不是假装列已存在。
  3. 构造同一对话仅凭测试 API Key 不同的两行,验证“先脱敏、后去重”只保留一行;再构造一次自然语言改写,说明精确 hash 为何抓不到它。
  4. 为 continuation 断链、未知 Agent 名称、多模态内容和缺失 Reward 各写一个最小失败夹具,记录 v0.18.0 的实际错误或跳过行为。
  5. 设计一份人工抽检表,覆盖来源、Reward、长度、工具调用、脱敏命中和 gaming 风险;表中只允许保存 hash 与结论,不复制正文。

参考资料

Footnotes

  1. Harbor Framework Team,pyproject.toml,Harbor v0.18.0,https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/pyproject.toml#L1-L9https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/pyproject.toml#L57-L60,访问于 2026-07-16。

  2. Harbor Framework Team,SFT 工作流文档,Harbor v0.18.0,https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/docs/content/docs/training-workflows/sft.mdx#L1-L93,访问于 2026-07-16。

  3. Harbor Framework Team,traces_utils.pyexport_traces() 参数、返回类型、Dataset 构造与合并逻辑,Harbor v0.18.0,https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/utils/traces_utils.py#L1117-L1194https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/utils/traces_utils.py#L1335-L1423,访问于 2026-07-16。 2

  4. Harbor Framework Team,RFC 0001 Agent Trajectory Interchange Format (ATIF) Specification,ATIF v1.7,https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/rfcs/0001-trajectory-format.md#L12-L20https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/rfcs/0001-trajectory-format.md#L76-L104https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/rfcs/0001-trajectory-format.md#L120-L134,访问于 2026-07-16。

  5. Harbor Framework Team,traces_utils.py,Trial discovery、metadata loading 与主轨迹检查,Harbor v0.18.0,https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/utils/traces_utils.py#L189-L209https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/utils/traces_utils.py#L460-L465https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/utils/traces_utils.py#L792-L805,访问于 2026-07-16。

  6. Harbor Framework Team,traces_utils.py,AgentName/factory/SUPPORTS_ATIF 门禁,Harbor v0.18.0,https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/utils/traces_utils.py#L1230-L1249,访问于 2026-07-16。

  7. Harbor Framework Team,traces_utils.py,episode 边界、复制上下文与 last 选择,Harbor v0.18.0,https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/utils/traces_utils.py#L468-L555https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/utils/traces_utils.py#L834-L865,访问于 2026-07-16。

  8. Harbor Framework Team,traces_utils.py,system/user/agent 映射、reasoning、tool call、observation 与工具定义嵌入,Harbor v0.18.0,https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/utils/traces_utils.py#L583-L711,访问于 2026-07-16。

  9. Harbor Framework Team,traces_utils.py,Job/Trial 结果提取及主 Reward 键,Harbor v0.18.0,https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/utils/traces_utils.py#L398-L457,访问于 2026-07-16。

  10. Harbor Framework Team,traces_utils.py,success/failure 过滤和 > 0.0 判定,Harbor v0.18.0,https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/utils/traces_utils.py#L1251-L1263https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/utils/traces_utils.py#L1426-L1449,访问于 2026-07-16。

  11. Harbor Framework Team,trajectory_config.py 与 Terminus-2 实现,raw_content/linear_history 语义及默认值,Harbor v0.18.0,https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/models/agent/trajectory_config.py#L1-L17https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/agents/terminus_2/terminus_2.py#L335-L340https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/agents/terminus_2/terminus_2.py#L1337-L1348,访问于 2026-07-16。

  12. Harbor Framework Team,AgentConfigkwargs 字段,Harbor v0.18.0,https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/models/trial/config.py#L61-L124,访问于 2026-07-16。

  13. National Institute of Standards and Technology,Guide to Protecting the Confidentiality of Personally Identifiable Information (PII),NIST SP 800-122,§4.2.1,https://doi.org/10.6028/NIST.SP.800-122,访问于 2026-07-16。

  14. Harbor Framework Team,Terminus-2 continuation 生成与导出器引用链处理,Harbor v0.18.0,https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/agents/terminus_2/terminus_2.py#L1862-L1955https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/utils/traces_utils.py#L794-L849,访问于 2026-07-16。

  15. Harbor Framework Team,traces_utils.py,subagent 文件扫描、单对话导出、合并与 trace_source,Harbor v0.18.0,https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/utils/traces_utils.py#L1020-L1109https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/utils/traces_utils.py#L1387-L1423,访问于 2026-07-16。

  16. Harbor Framework Team,cli/traces.pycli/main.pytraces export 选项及 hidden 注册,Harbor v0.18.0,https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/cli/traces.py#L11-L115https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/cli/main.py#L75-L82,访问于 2026-07-16。

  17. Harbor Framework Team,traces_utils.py,Hugging Face token 读取和 push_to_hub,Harbor v0.18.0,https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/utils/traces_utils.py#L1129-L1138,访问于 2026-07-16。

  18. Hugging Face,Processing data in a Dataset,保存与导出方法,https://huggingface.co/docs/datasets/process#save,访问于 2026-07-16。

  19. Harbor Framework Team,traces_utils.py,instruction 与 Verifier stdout/stderr 读取,Harbor v0.18.0,https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/utils/traces_utils.py#L335-L387https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/utils/traces_utils.py#L780-L874,访问于 2026-07-16。

  20. Harbor Framework Team,traces_utils.py,多模态检测与 MultimodalExportError,Harbor v0.18.0,https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/utils/traces_utils.py#L49-L109https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/utils/traces_utils.py#L490-L507,访问于 2026-07-16。

  21. National Institute of Standards and Technology,De-Identifying Government Datasets: Techniques and Governance,NIST SP 800-188,https://doi.org/10.6028/NIST.SP.800-188,访问于 2026-07-16。