第 22 章 篇三:后训练数据工程

多轮 Agent 交互数据

多轮工具调用、Agent 轨迹数据的采集、回放与质量筛选(由原书独立章升级整合)。

Agent 多轮 工具调用

单轮 SFT 教模型说话,多轮 SFT 教模型对话。

Agent 数据是后训练的”深水区”——工具幻觉、参数错误、失败恢复,每一个都能毁掉上线。

环境即数据:决定 Agent 上限的,往往不是你囤了多少轨迹,而是你能造出多少世界。

单轮指令数据回答的是”模型该说什么”;多轮对话数据回答的是”模型该如何接住上下文”;而 Agent 数据回答的是一个更难的问题——“模型该如何把一件事做完”。本章把这两类递进的数据放进同一个工程体系:多轮与 Agent 数据工程。它们共享同一套底层表示(messages 结构),共享同一套质量命门(一致性、正确性、可验证性),也共享同一个演化终点——当轨迹不够用时,你需要的是环境。

本章是《AI 数据工程》第 18 章”多轮对话与 Agent 数据”的体系化升级版。在保留原章全部核心内容(多轮数据构造、Agent 轨迹、工具调用数据、轨迹质量评估)的基础上,重组为一条完整的逻辑链:多轮对话数据 → Agent 轨迹数据 → 工具调用/函数调用数据 → 轨迹质量与验证 → 环境即数据,并补上正在重塑这个领域的两块新拼图——Environment Factory 与环境 Scaling Law(EnvScaler、AgentScaler),以及 Agentic Data Engineering(LLM 作为 Agent 参与数据准备全流程)这一前沿方向。

22.1 总论:从”教模型说话”到”教模型做事”

22.1.1 数据形态的五级跃迁

后训练数据的复杂度沿着一条清晰的阶梯上升:

层级数据单元教模型什么质量核心典型风险
L1 单轮指令(instruction, response)说话响应质量幻觉、格式漂移
L2 多轮对话messages 列表对话上下文一致性上下文丢失、自相矛盾
L3 工具调用messages + tool_calls调用接口接口契约正确工具幻觉、参数错误
L4 Agent 轨迹状态-动作-观察序列完成任务任务成功且过程合理失败恢复、意图波动
L5 环境环境定义 + 验证器在交互中学习可验证、多样性环境成为瓶颈

这五级不是替代关系,而是包含关系:L4 的轨迹里嵌着 L3 的工具调用与 L2 的多轮结构;L5 的环境是批量生产 L4 轨迹的机器。理解了这条阶梯,就理解了本章的结构——22.2 到 22.4 讲 L2 到 L3 的数据构造,22.5 讲 L3/L4 的质量验证,22.6 讲 L5(环境即数据),22.7 讲这个体系自身的未来(Agent 造数据)。

22.1.2 为什么 Agent 数据是”深水区”

有三个结构性原因,决定了 Agent 数据工程的难度远高于单轮指令数据:

第一,数据不是文本,是交互过程。 单轮数据的质量在生成那一刻就冻结了;而一条 Agent 轨迹的质量,取决于模型与环境”共舞”的全过程——同一个模型在不同环境实例里会走出完全不同的轨迹。你不能脱离环境单独评价一条轨迹”好不好”,这使得数据构造与数据评估必须耦合设计。

第二,正确性不是”像不像”,而是”成没成”。 单轮数据可以用偏好模型打分;轨迹数据的金标准是任务是否真正完成——这要求可执行、可验证的环境与断言。评审一条轨迹,最终要回到”最终状态是否正确”,而不是”过程是否像专家”。

第三,失败模式是长尾的。 工具幻觉、参数错误、失败恢复、超长轨迹、工具偏好、意图波动——每一个失效模式都需要专门的数据注入与专门的验证手段,任何一处短板都可能毁掉上线(本章 22.5 将逐一展开这六大难点)。

22.1.3 本章路线图

  • 22.2 多轮对话数据工程:messages 结构、多轮 loss mask、上下文一致性、三大来源、UltraChat 自玩合成、System Prompt 数据。
  • 22.3 Agent 轨迹数据工程:轨迹的形式化表示、四种来源、采集与回放、ReAct 合成范式、技能使用(SKT)第四维度。
  • 22.4 工具调用与函数调用数据工程:tool_calls 结构化 JSON、tool role、mock 执行、多步嵌套与并行调用、数据集生态。
  • 22.5 轨迹质量与验证:六大难点、JSON Schema 静态校验、执行验证、多轮一致性评估基准(MT-Bench / MINT / τ-Bench / BFCL)、六维质量评估(含依赖/顺序满足度)、轨迹安全、部分成功轨迹回收。
  • 22.6 环境即数据:Environment Factory、EnvScaler、AgentScaler 环境Scaling Law,以及环境数据工程的实践清单。
  • 22.7 前沿方向:Agentic Data Engineering:LLM 作为 Agent 参与数据准备全流程,从”为 Agent 造数据”到”用 Agent 造数据”。
  • 22.8 多轮/Agent 数据工业 SOP:把前面所有环节串成一条可落地的流水线。

22.2 多轮对话数据工程

22.2.1 messages 结构:多轮数据的通用表示

多轮对话的标准结构是 messages 列表,这是从 L2 到 L4 全部数据的底座:

{
  "messages": [
    {"role": "system", "content": "你是一个有用的助手。"},
    {"role": "user", "content": "今天天气怎么样?"},
    {"role": "assistant", "content": "我需要查询天气数据。请问你在哪个城市?"},
    {"role": "user", "content": "北京"},
    {"role": "assistant", "content": "北京今天晴,最高温度 25°C。"},
    {"role": "user", "content": "明天呢?"},
    {"role": "assistant", "content": "明天多云,最高温度 23°C。"}
  ]
}

这个结构看似简单,工业落地时却有一整套隐含规范,任何一条不满足都会在训练时产生脏信号:

  • 角色交替约束:标准多轮数据应严格满足 user/assistant 交替(system 只出现在开头)。连续两条 user、连续两条 assistant、assistant 开场(无前置 user)都是常见脏数据。
  • 轮次切分:一个”轮次”(turn)指一次 user→assistant 往返。评估数据规模时通常统计轮数分布,而非消息条数。
  • 元数据外挂:messages 之外,工业数据通常附带数据卡字段——来源(真实日志/人工/合成)、轮数、任务类型、质量分、一致性标记、脱敏标记。这些字段不进训练,但决定采样与配比。

一个实用的入库前结构校验:

def validate_messages(messages):
    """多轮数据的结构合法性校验"""
    errors = []
    roles = [m['role'] for m in messages]
    # 1. system 只能在开头
    for i, r in enumerate(roles):
        if r == 'system' and i != 0:
            errors.append(f"system at position {i}")
    # 2. user/assistant 严格交替
    body = [r for r in roles if r != 'system']
    for i in range(0, len(body) - 1):
        if body[i] == body[i + 1]:
            errors.append(f"consecutive {body[i]} at {i}")
    # 3. 不允许 assistant 开场或 user 收尾
    if body and body[0] != 'user':
        errors.append("must start with user")
    if body and body[-1] != 'assistant':
        errors.append("must end with assistant")
    return errors

22.2.2 多轮损失计算:loss 只算 assistant

多轮训练的关键机制:loss 只在 assistant 部分计算,user 与 system 部分只作为条件(上下文),不作为预测目标。

def compute_multi_turn_loss(input_ids, messages, template):
    losses = []
    for i, msg in enumerate(messages):
        if msg['role'] != 'assistant':
            continue  # user/system 不算 loss
        
        # 找到该 assistant 响应的 token 范围
        start, end = find_assistant_region(i, input_ids, template)
        
        # 只对这部分计算 loss
        loss = cross_entropy(model(input_ids[start:end]), input_ids[start+1:end+1])
        losses.append(loss)
    
    return sum(losses) / len(losses)

为什么必须这样做?如果对整个序列无差别计算 loss,模型会同时学习”生成 user 的话”——训练目标被污染为”模仿整个对话的双方”,推理时模型可能学会自问自答、抢答用户。loss mask 把监督信号精确限制在”在给定上下文时如何接话”这一件事上。

工程上有三个高频出错的细节:

  • mask 与 chat template 必须对齐。不同模型的模板(Llama-3 的 <|start_header_id|>、Qwen 的 <|im_start|>)切分方式不同,mask 的起止 token 要与模板渲染结果逐 token 对齐。模板换版本而 mask 逻辑没换,是训练事故的常见来源。
  • 轮级平均 vs token 级平均。上面代码按 assistant 轮数平均,另一种做法是对所有 assistant token 做全局平均。前者让每一轮话语权相等,后者让长回复权重更大。工业实践多采用 token 级平均并配合长度截断,避免超长回复主导梯度。
  • mask 泄漏检查。上线前用一个零模型(输出恒定)跑一遍 loss:如果 loss 不为常数,说明有非 assistant token 混入了监督区间。

第三层 mask:assistant 输出内部,教师的推理标注要不要计 loss。上面三条的粒度停在”assistant vs 非 assistant”,22.6.6 会把它细化到 turn 级(按信息量选哪些 turn 参与)。但还有第三层,而且它的方向与直觉相反:在 agent 轨迹里,assistant 输出内部还要再分——教师给出的推理标注(Thought / rationale)算不算 loss,本身就是一个影响效果的选择。

SyntheticAgentTraceQA(论文标题 Execution-First Synthetic Tool-Use Trace Generation for LLM Agents,arXiv:2607.29175,6 位作者,2026-07-31;International Workshop on Agentic AI for Next-Generation Software Development,2026-10 Munich)在四个工具生态上报告:masked supervision(把教师推理标注排除在训练目标之外)提升最终答案指标;而 full supervision(对包含推理 token 在内的完整 assistant 输出计 loss)在答案质量上反而更差,且在参考轨迹一致性上也没有一贯提升——在 9B 规模上尤其明显。

⚠️ 这是一条反直觉且证据较弱的观察,按”待验证的前沿结论”对待。三条边界:① workshop 论文,未见正式期刊/会议收录;② 结论来自 9B 单一规模、四个工具生态;③ 它与 22.6.6 的 turn 级 mask 是不同粒度的两件事——turn 级 mask 按信息量选 turn,这一层按token 类别选内容,不要混为一谈。实践建议是默认先按 masked 试,再按你的目标(要过程能力还是要答案质量)决定是否打开。

“Thought 可监督”不等于”Thought 应该进 loss”。这与 22.3.4 的判断形成张力但并非矛盾——那里说 ReAct 的显式 Thought “让推理过程可监督,可以单独对 Thought 部分做质量评审”,说的是可评审、可归因(支撑过程奖励与错误定位),这与它是否参与梯度是两件事。Thought 留在上下文里、但不进 loss,可以同时拿到”可归因”与”答案质量”两头的收益。

22.2.3 上下文一致性:多轮数据的质量核心

多轮数据的”上下文一致性”是质量核心——每轮对话必须与前面轮次逻辑一致。这是多轮数据区别于”一堆独立单轮数据拼接”的本质属性。

典型不一致:

  • 用户第 2 轮问”明天呢”,assistant 回答”今天是晴天”——上下文丢失
  • assistant 第 1 轮说”我在北京”,第 3 轮说”我在上海”——自相矛盾
  • 用户在第 5 轮称呼变了,assistant 没跟上——指代不一致

检测:

def check_consistency(messages):
    # 用 LLM 检查上下文一致性
    prompt = f"""
    检查以下多轮对话的上下文一致性。是否存在:
    1. 自相矛盾
    2. 上下文丢失
    3. 指代不一致
    
    对话:{format_messages(messages)}
    
    输出:{{"consistent": true/false, "issues": [...]}}
    """
    return llm.generate(prompt)

一致性检测的成本分层是规模化落地的关键——不可能对上亿条数据全部跑大模型评审,工业上通常采用三层金字塔:

  1. 规则层(近零成本):实体冲突检测(抽取每轮的城市/日期/数字,检查与历史轮次矛盾)、人称代词统计、回复与问题相关性打分。过滤掉最明显的 30-50% 不一致。
  2. 小模型层(低成本):训练一个轻量一致性分类器(如 1.5B 级别),对规则层存疑的数据二次筛。
  3. 大模型层(高成本):只对通过前两层、且将被采入训练集的数据做最终评审。

一致性可以进一步细分为四个维度,分别对应不同的数据修复策略:

一致性维度含义典型修复手段
事实一致前后陈述的事实不矛盾冲突实体检测 + 丢弃或改写
指代一致代词、称呼、省略主语可正确解析指代链抽取 + 校验
目标一致对话始终围绕用户的任务目标任务类型标注 + 偏题检测
风格一致语气、格式、语言保持稳定风格分类器过滤

22.2.4 多轮数据三大来源

来源对比

来源真实度规模多样性成本
真实日志极高10万-1亿中0
人工对话高1万-10万高5-50 美元/条
模型多轮自玩中10万-1000万高0.001-0.1 美元/条

三种来源没有优劣之分,只有配比问题。工业默认起点大致是:真实日志打底(真实分布)、人工金标拉高上限(质量锚点)、自玩合成撑规模(数量主力)。

真实日志

从生产环境拉取的多轮对话,最真实但有隐私与采样偏差问题。

处理流程:

1. 隐私脱敏(Presidio + 规则)
2. 用户去重(每用户最多 N 轮)
3. 质量过滤(RM 评分 > 0.5)
4. 长度过滤(≥ 3 轮)
5. 多样性采样(任务聚类)

两个容易被低估的坑:

  • 采样偏差:真实日志反映的是”当前模型的行为分布”,用它们训练下一代模型会形成自我强化循环——模型不擅长的任务在日志中天然稀少。对冲手段是统计日志的任务聚类分布,对欠表达簇用合成数据补齐。
  • “差评偏差”:能进入日志的多轮对话,很多是因为用户没得到满意答案才持续追问。直接把整段对话当正样本,等于把”失败的挣扎过程”教给模型。需要结合用户反馈信号(重述率、放弃率)做负向加权或改写。

人工对话

人工编写的多轮对话,质量最高但成本高。

典型流程:

  • 标注员扮演用户,给 prompt
  • 另一个标注员扮演 assistant,给响应
  • 双盲审核,确保上下文一致

人工数据的核心价值不是数量,而是锚定质量坐标:它可以作为合成数据的评审标准源(few-shot 示例、评审 rubric 的校准集),也可以直接进入高质量训练子集。每一条人工数据都应该被复用多次——作为合成模板、作为评审参照、作为回归测试集。

模型多轮自玩

让两个 LLM 模型互相扮演 user/assistant,自动生成多轮对话。代表工作:UltraChat(见下节)。自玩把成本压到了每条几厘到一毛钱,是唯一能上千万级规模的多轮来源,代价是真实度折损——用户不像是真用户,更像”另一个模型”。

22.2.5 UltraChat:多轮自玩合成的代表作

UltraChat 算法

UltraChat(Ding et al., 2023)用两个 GPT-4 模型多轮自玩:

def ultrachat_generate(user_model, assistant_model, n_conversations=50000):
    conversations = []
    for _ in range(n_conversations):
        # 1. 随机选一个种子话题
        topic = sample_topic()
        
        # 2. 多轮对话
        messages = [{"role": "system", "content": "You are a helpful assistant."}]
        for turn in range(5):  # 最多 5 轮
            # user 模型生成问题
            user_msg = user_model.generate(messages, temperature=0.7)
            if is_ending(user_msg):  # 检测到结束意图
                break
            messages.append({"role": "user", "content": user_msg})
            
            # assistant 模型生成回答
            assistant_msg = assistant_model.generate(messages, temperature=0.7)
            messages.append({"role": "assistant", "content": assistant_msg})
        
        conversations.append(messages)
    return conversations

关键设计

  • 5 轮截断:超过 5 轮话题容易发散,质量下降
  • 双 GPT-4:用同一个模型会导致”同质化”,用两个独立实例更好
  • 种子话题:从 30 个大类(科学、技术、艺术…)× 200 个小类采样
  • 结束检测:user 模型可以输出 <|end|> 主动结束

UltraChat 的局限

  1. 话题偏向”GPT-4 喜欢的”:哲学、文学多,实用任务少
  2. 多轮逻辑深度有限:第 3 轮后经常”为了聊天而聊天”
  3. 缺乏指令遵循:用户更像在”请教”而非”指令”

这三条局限在后续工程迭代中都有了对策,可作为自玩合成的通用改进清单:

  • 话题分布控制:不依赖模型自发选话题,而是从业务任务分类法(参照真实日志的聚类分布)反向指定种子话题,把”模型喜欢的”矫正为”用户需要的”。
  • 用户角色注入:给 user 模型注入 persona(急躁的运维、谨慎的法务、话痨的学生)与目标(砍价、报障、改签),让追问带策略性,而不是礼貌地换话题。
  • 对抗性引导:让 user 模型在指令中制造省略、歧义、中途变更(与 22.5.6 的意图波动呼应),使多轮数据覆盖真实对话的”不完美指令”。

22.2.6 System Prompt 数据

多轮数据的第一个 message 往往不是 user,而是 system。System Prompt 数据是独立于对话内容的一个构造维度:同一段对话,配上不同 system prompt,就是不同的训练样本。

System Prompt 的 5 类型

工业级 System Prompt 数据按类型配比:

类型占比示例
通用助手30%“你是一个有用的助手”
角色扮演20%“你是一个 19 世纪英国侦探”
专家领域20%“你是 Python 专家”
工具使用20%“你有以下工具:…”
安全约束10%“你不能讨论…”

System Prompt 数据的构造

def generate_system_prompted_data(system_prompts, base_data):
    data = []
    for sys_prompt, ratio in system_prompts:
        # 按比例采样基础数据
        n_samples = int(len(base_data) * ratio)
        sampled = random.sample(base_data, n_samples)
        
        for d in sampled:
            # 在 messages 前加 system message
            d['messages'] = [
                {"role": "system", "content": sys_prompt},
                *d['messages']
            ]
            data.append(d)
    
    return data

注意一个隐性要求:system prompt 与对话内容必须语义相容。“你是 Python 专家”的 system 配上一段美食闲聊,是比缺 system 更糟的脏数据——模型学到的是”system prompt 可以被无视”。所以配对之后必须过一遍相容性检查(可用规则 + 轻量分类器),不相容的组合要么丢弃、要么让强模型改写对话使其符合人设。

System Prompt 的反例数据

加入”违反 System Prompt”的反例,教模型严格遵守:

{
  "messages": [
    {"role": "system", "content": "你只能说中文"},
    {"role": "user", "content": "Tell me about AI in English"},
    {"role": "assistant", "content": "抱歉,我只能用中文回答。AI 是人工智能..."}
  ]
}

反例数据的意义在于覆盖”诱导违反”场景:用户明确要求模型突破 system 约束(换语言、破人设、弃工具)。没有这类数据,模型对 system prompt 的遵循是脆弱的——一次礼貌的英文提问就可能让它忘记”只说中文”的约束。这与后文 Agent harness 的可靠性直接相关:工具使用类的 system prompt 同样需要”用户要求走捷径、模型仍坚持先调工具查证”的反例。


22.3 Agent 轨迹数据工程

多轮对话数据教模型”接话”;Agent 轨迹数据教模型”接事”。轨迹(trajectory)是 Agent 数据的基本单元,也是本节到 22.6 的主角。

22.3.1 轨迹:Agent 数据的基本单元

一条轨迹记录了 Agent 从初始状态到任务终止的完整交互历史,可以形式化为:

τ = (s₀, a₁, o₁, s₁, a₂, o₂, ..., aₙ, oₙ, sₙ)

其中:

  • s:环境状态(对 LLM Agent 而言,通常是”messages 上下文 + 环境内部状态”)
  • a:Agent 的动作(生成文本、发起工具调用、给出最终答案)
  • o:观察(环境对动作的反馈——工具返回、报错、页面内容、状态变更通知)

轨迹与多轮对话数据的血缘关系一目了然:messages 结构里的 assistant.tool_calls 就是动作 a,tool 角色的返回就是观察 o。轨迹是对话数据的推广——把角色集合从 {user, assistant} 扩展到 {user, assistant, tool, environment},把”谁来接话”的规则从”轮流”改为”环境驱动”。这个统一意味着:22.2 的全部基础设施(messages 校验、loss mask、一致性检测)在轨迹数据上原样适用,只需针对 tool 角色扩展。

轨迹数据区别于对话数据的关键增量是任务级标签。一条轨迹必须携带:

  • 任务描述:Agent 要达成什么目标
  • 成功判定:环境终态断言(或人工判定)
  • 步数与成本:动作次数、token 消耗、工具调用次数
  • 环境引用:在哪套工具/哪版环境里产生(这条在 22.6 会成为一等公民)

22.3.2 轨迹的四种来源

来源真实度可扩展性验证难度典型用途
真实部署采集极高低(受流量限制)中分布对齐、发现盲区
专家示范高低(贵)低SFT 金标、few-shot 锚点
强模型合成(teacher rollout)中高高中SFT 规模主力
自玩/RL 探索(self-play)视验证器而定极高高(依赖验证器)RL 训练、超越 teacher

四者的工程定位:

  • 真实部署采集:从线上 Agent 的执行日志提取。最贴近真实分布,但有两个先天缺陷——失败的轨迹居多(成功就不用人工再看了),且蕴含隐私。它最大的价值不是直接做 SFT 正样本,而是发现失效模式:把线上失败模式分类统计,反哺合成管线的场景清单(这正是 22.5 六大难点的主要来源)。
  • 专家示范:人工操作员在环境里一步步完成任务,录下全过程。质量最高,但也最贵,且”人会走的路径”不一定是”模型能走的路径”——专家习惯性的跳步、隐含知识,模型学不动。示范数据适合做评审基准与少样本锚点,不适合大规模直接模仿。
  • 强模型合成:让 teacher 模型在环境里 rollout,收集成功轨迹。这是当前 SFT 轨迹的规模主力,成本随 teacher API 价格线性增长。质量命门在 teacher 的失败率——必须配备 22.5 的全链路验证,否则”老师错的学生跟着错”。
  • 自玩/RL 探索:被训练的模型自己在环境里探索,用验证器筛选或奖励引导。可扩展性最高(推理成本随集群线性扩展),且能产生超越 teacher 的轨迹——代价是对环境与奖励设计的要求极高,这正是 22.6”环境即数据”的入口。

22.3.3 轨迹的采集与回放

轨迹数据工程的独有工序:回放(replay)——在采集之后、训练之前,把轨迹在环境里重新执行一遍,验证其可复现性。

为什么要回放?因为合成管线中的任何一环(模型输出漂移、mock 数据陈旧、环境版本变更)都可能让”当时成功的轨迹”变成”现在跑不通的轨迹”。回放是轨迹数据版本的回归测试。

工业回放流程:

1. 逐条加载轨迹(含任务描述、环境配置版本号)
2. 按原顺序重放 assistant 动作,向环境发起真实工具调用
3. 比对每步观察与原始观察(工具返回是否一致)
4. 终态断言:任务成功判定是否仍然成立
5. 全部通过 → 保留;任一步漂移 → 标记重生成或丢弃

存储格式建议(JSONL,逐行一条轨迹):

{
  "trajectory_id": "traj_20260830_00042",
  "task": "帮用户取消航班 UA123,用户有 credit",
  "env_config": {"env_id": "airline-sim", "version": "1.4.2", "seed": 42},
  "steps": [
    {"action": {"tool": "get_user_credit", "args": {"user_id": "U123"}},
     "observation": {"credit": 320}},
    {"action": {"tool": "cancel_reservation", "args": {"flight": "UA123"}},
     "observation": {"status": "cancelled"}}
  ],
  "final_answer": "已为您取消航班 UA123...",
  "success": true,
  "metrics": {"n_steps": 4, "tokens": 1820, "latency_ms": 4380}
}

回放的三个坑:

  • 环境非确定性:搜索、推荐、时间类工具每次返回都不同。对策:这类工具在回放时改用录制模式(直接返回原始观察),只有确定性工具才真执行。
  • 环境演进:API 升级(参数改名、返回结构变更)会让历史轨迹整体失效。环境配置必须版本化(见 22.6.5),回放是发现”环境升级毁掉旧数据”的唯一手段。
  • 回放成本:全量回放开销大,工业上按批次抽样回放(例如每批 5-10%),环境大版本升级时全量回放。

22.3.4 ReAct 合成范式

ReAct 结构

ReAct(Yao et al., 2022)是 Agent 的经典范式,把推理与行动交织成文本循环,结构为:

Thought: 用户问北京天气,我需要查询
Action: get_weather(city="北京")
Observation: {"temp": 25, "weather": "sunny"}
Thought: 查到了,可以回答
Action: finish("北京今天晴 25°C")

ReAct 数据合成

def generate_react_trajectory(prompt, tools):
    trajectory = []
    for step in range(10):  # 最多 10 步
        # 1. 生成 Thought + Action
        react_msg = model.generate(prompt + trajectory, tools=tools)
        trajectory.append(react_msg)
        
        # 2. 解析 Action
        action = parse_action(react_msg)
        if action['name'] == 'finish':
            break
        
        # 3. 执行 Action,得到 Observation
        observation = execute_tool(action)
        trajectory.append(f"Observation: {observation}")
    
    return trajectory

ReAct 与 Function Calling:两种格式的取舍

ReAct 把工具调用编码为纯文本约定(Action: ... 行),Function Calling 把工具调用编码为结构化字段(tool_calls JSON,见 22.4)。两者教的是同一种能力,但数据形态与失效模式不同:

ReAct 文本格式Function Calling 结构化格式
解析方式正则/约定解析JSON 解析(chat template 原生支持)
典型失效格式漂移(Action 行写错)JSON 非法、字段缺失
Thought 显式性显式(每步必须写 Thought)可选(content 可为空)
训练自由度高(纯文本,模型可发挥)受 schema 约束
适用场景研究复现、无原生 tool 支持的老模型生产环境主流

工业主流是 Function Calling 格式,但 ReAct 数据仍有独特价值:显式的 Thought 让推理过程可监督——可以单独对 Thought 部分做质量评审(“这一步想对了吗”),这在过程奖励(process reward)与错误归因(哪一步开始走错)时非常顺手。一个务实的混合策略:训练数据以 tool_calls 结构为主,但保留一定比例的 ReAct 风格轨迹,让模型同时具备”显式思考”的能力出口。

⚠️ 但要区分”可监督”与”进 loss”。Thought 的价值在于可评审、可归因,这与它是否参与梯度是两件事。22.2.2 末尾引的那条观察提示,把教师推理标注放进 loss(full supervision)在答案质量上反而更差,9B 规模尤其明显(证据强度见该处的三条边界)。因此建议的默认动作是:Thought 保留在上下文里、但不进 loss——同时拿到”可归因”与”答案质量”两头的收益。

另一种合成顺序:execution-first。本章迄今的合成顺序基本是 query-first(先有 prompt/任务,再让模型 rollout 生成轨迹)。SyntheticAgentTraceQA(arXiv:2607.29175)提出的替代顺序值得记一笔:先构造高层工作流结构 → 依赖感知地把工作流映射到可用工具 → 在受控环境中执行并验证轨迹 → 最后才反推合成自然语言任务、教师推理标注与参考答案。

动机正是 query-first 的失败模式:“看起来合理的用户请求”未必对应任何合法的工具序列、兼容参数或可用数据——这类 query 会系统性地产出无法执行的”伪轨迹”,而且这种伪轨迹在文本层面往往看不出破绽,只有真正去执行才会暴露。execution-first 与 22.5.3 的 L3 执行验证同源,区别只在于把验证从”事后筛”提到了”事前约束”。

22.3.5 技能使用:与工具调用不同的第四个数据维度

前面讨论的都是工具调用(tool invocation)数据——教模型正确地调用一个 API。但 Agent 数据还有第四个维度,常被混在工具调用里:技能使用(skill use)。

两者的区别在于对象层次:

工具调用数据技能使用数据
教的内容如何正确调用单个 API(参数、格式、时机)如何识别、应用并协调多个能力
知识形态接口契约可复用的程序性知识
单条数据跨度一次或几次调用跨多步的完整任务
典型失败工具幻觉、参数错误有技能但不知道该用哪个、不会组合

SKT(Skill-Use Training at Scale via Verified Synthetic Data Generation,Zelin Tan 等 14 人)把这个维度做成了一条可规模化的合成管线。它的问题陈述很直接:仅仅提供 skills,并不保证当前模型能有效识别、应用与协调它们——这正是不少团队”接了一堆 skills 但效果没提升”的原因。

管线三步:

  1. 技能配置选择:挑选合适的单技能与多技能组合
  2. 任务合成 + 反馈修复:通过 rule-based 与 agent-based verification 合成任务,并用 feedback-guided repair 修复失败样本
  3. 只保留合格轨迹:仅保留实质性使用了每一个必需技能的成功轨迹

规模(论文披露的完整链路):

2,000 个公开 skills
  → 4,000 个 task packages
  → 27,164 条经过验证的轨迹
  → 另有 SkillEval(基于同一管线 + 不重叠测试池构建的留出可执行基准)

三条可复用的工程结论:

  1. 验证是增益的来源,不是可选优化。论文的消融显示,增益依赖于高质量监督——去掉验证环节,规模化反而放大噪声。这与第 16 章”质量优先于数量”的结论一致。
  2. 增益能跨 agent harness 迁移。跨 harness 评估表明效果不限于单一 agent interface,说明学到的是技能使用能力本身,不是对某个框架提示词模板的过拟合。
  3. 增益随技能覆盖广度增长。scaling 实验显示覆盖的 skills 越多效果越好——这意味着技能使用数据是可以持续投入的方向,不会很快撞到边际收益递减。

与既有数据配比的关系:如果你当前的 Agent 数据全是工具调用格式,建议单独评估”模型有技能但不用”的失败占比。SKT 给出的判据是把”用到每个必需技能”作为轨迹的准入条件——这个思路也可以反向用于质检:统计你的现有轨迹中,实质性使用了多技能的占比是多少。

⚠️ 命名更正:部分转述把 SKT 展开为 “Skill-usage Knowledge Transfer”。论文标题为 SKT: Skill-Use Training at Scale via Verified Synthetic Data Generation,缩写 SKT 对应 Skill-Use Training。引用时请使用后者。

从体系视角看,SKT 的位置在 L3 与 L4 之间:它以工具调用为原子能力,训练的是能力编排(orchestration)这一更高层的程序性知识。这也预示了轨迹数据的评价标准要往上再走一层——单步调用对了不够,还要看多技能是否被合理组织。


22.4 工具调用与函数调用数据工程

工具调用(tool use)与函数调用(function calling)在数据工程语境下基本同义:把”调对 API”做成可训练的数据。这一节讲它的表示、执行与生态。

22.4.1 tool_calls 结构化 JSON

工业级 Tool-Use 数据用结构化 JSON 表示工具调用:

{
  "messages": [
    {"role": "user", "content": "北京明天天气"},
    {"role": "assistant", "content": null, "tool_calls": [
      {
        "id": "call_001",
        "type": "function",
        "function": {
          "name": "get_weather",
          "arguments": "{\"city\": \"北京\", \"date\": \"tomorrow\"}"
        }
      }
    ]},
    {"role": "tool", "name": "get_weather", "content": "{\"temp\": 23, \"weather\": \"cloudy\"}"},
    {"role": "assistant", "content": "北京明天多云,最高温度 23°C。"}
  ]
}

三个容易踩坑的字段细节:

  • content: null 是合法值。发起工具调用的 assistant 消息可以没有正文(也可以同时有”我先查一下”之类的说明文字)。训练数据里两种形态都要有,否则模型会学出”必须先说话才能调工具”或”调工具时绝不能说话”的死板模式。
  • arguments 是字符串化的 JSON,不是嵌套对象。序列化空格、键序、引号风格都会影响训练分布——合成时应统一规范化,避免模型学到碎片化的序列化习惯。
  • id 与 tool_call_id 必须严格配对。并行调用多个工具时(见 22.4.4),配对错乱会让模型学坏”把哪个结果对应到哪次调用”。

22.4.2 tool role 的特殊性

tool 角色的 content 是工具返回的 JSON,不是自然语言。在 chat-template 中需要特殊处理:

# Llama-3 的 tool role 处理
def format_tool_message(msg):
    return f"<|start_header_id|>tool({msg['name']})<|end_header_id|>\n{msg['content']}<|eot_id|>"

tool role 的特殊性在于:它进入上下文,但(通常)不进入 loss。模型要学习的是”读观察、做反应”,而不是”生成观察”。这与 22.2.2 的多轮 loss mask 一脉相承——mask 的角色集合是 {assistant},其余角色(user/system/tool)全部只做条件。

训练数据层面的另一个特殊性问题:工具返回的长度分布。真实 API 返回可能是 50 字符的天气结果,也可能是 5000 行的表格 JSON。如果合成数据全是短小干净的返回,模型上线后遇到超长、嵌套、含噪声的返回会当场”失明”(忽略观察或复读观察)。对策:mock 返回的长度与脏度要按真实 API 的分布构造(见下节)。

22.4.3 mock 执行与半真实环境

合成 Tool-Use 数据时,工具不真实执行——用 mock 返回:

def generate_tool_trajectory(prompt, available_tools):
    messages = [{"role": "user", "content": prompt}]
    
    for step in range(5):  # 最多 5 步
        # 1. 模型决定调用哪个工具
        assistant_msg = model.generate(messages, tools=available_tools)
        messages.append(assistant_msg)
        
        if not assistant_msg.get('tool_calls'):
            # 模型不再调用工具,结束
            break
        
        # 2. mock 执行每个工具调用
        for call in assistant_msg['tool_calls']:
            mock_result = mock_tool(call['function']['name'], 
                                   json.loads(call['function']['arguments']))
            messages.append({
                "role": "tool",
                "name": call['function']['name'],
                "content": json.dumps(mock_result)
            })
    
    # 3. 最终回答
    final_msg = model.generate(messages)
    messages.append(final_msg)
    return messages

mock 的质量决定数据质量——这是 Tool-Use 合成最容易低估的一环。mock 返回按真实度分三档:

mock 档位做法数据风险
静态模板固定/随机填充字段模型学到”返回总是干净短小”
统计仿真按真实 API 的字段分布、长度分布、脏度采样中等,缺状态连续性
模拟器(半真实环境)实现带状态的轻量模拟(数据库快照、状态机)最低,成本最高

经验法则:SFT 数据至少用统计仿真档;涉及多步状态依赖的任务(订票、改单、库存)必须用模拟器档——因为第 2 步的返回依赖第 1 步造成的状态变更,无状态 mock 会合成出”逻辑上不可能成立”的轨迹(比如两次查询之间库存变了但返回没变)。模拟器档的 mock,本质上就是 22.6 要讲的”环境”的雏形。

此外,mock 返回应覆盖错误与边界:HTTP 429 限流、参数越界报错、空结果、权限不足。只喂成功返回的模型,遇到第一个报错就会卡死(参见 22.5.3 失败恢复)。

怎么加错误才不会毁掉奖励:上面这句话容易被理解成”往返回里随机塞错误”,那样做通常得到一个致命后果——扰动之后这条样本的奖励判不了。这在做 SFT 时还不明显,一旦进 RL(需要逐条 rollout 算奖励)就会直接卡住。可执行的做法是 COVERT(Controllable and Verifiable Tool-Use Data Synthesis for Agentic Reinforcement Learning,arXiv:2604.09813,2026-04-10)提出的 oracle-preserving 增强,分两步、顺序不能颠倒:

  1. 先造一条干净的 base 轨迹(简单 query + 单工具),每条形变都要过三级校验:确定性 Format Checker、确定性 Rule Checker(schema 约束)、LLM Semantic Checker。这一步保证奖励本来就可判定。
  2. 再注入扰动,且保证注入后 oracle 仍然可判定——ground-truth 的正确调用 c\*c^\* 与最终答案 a\*a^\* 在增强前后保持一致。注入项与本章已有难点可以直接对应:加入语义相近的干扰工具(→ 22.5.1 难点一工具幻觉)、把 query 改写成间接指称(→ 难点六意图波动的简化版)、把工具输出埋进噪声系统日志(→ 22.4.2 工具返回长度与脏度分布)、注入 API 连接错误(→ 难点三失败恢复)、给出缺参数的病态 query。

增强后的样本按 RL 时可否判定分两类:Exact-Verifiable(正确调用与最终答案不变,奖励用精确参考匹配确定性计算)与 Judge-Assisted(期望行为被改变了,例如”应当报告 API 错误而不是幻觉出一个答案”,用轻量 LLM judge 判定具体行为目标)。

⚠️ 顺序提醒:先干净、后扰动。反过来做——先加扰动再想怎么判奖励——通常会得到判不了的样本。这与 22.5.3 的”L3 终态断言”是同一条原则在数据构造侧的镜像:奖励可判定性是设计约束,不是事后补丁。另外,COVERT 的合成工具与评测基准在 schema 和域上刻意完全不重叠,测的是泛化;真实 API 分布下的迁移幅度本书未核。

22.4.4 多步嵌套与并行调用

复杂任务需要多步工具调用:

用户: "对比北京和上海今天的天气"
Step 1: assistant 调 get_weather(city="北京") → tool 返回
Step 2: assistant 调 get_weather(city="上海") → tool 返回
Step 3: assistant 综合两个结果回答
{
  "messages": [
    {"role": "user", "content": "对比北京和上海今天的天气"},
    {"role": "assistant", "tool_calls": [
      {"id": "call_1", "function": {"name": "get_weather", "arguments": "{\"city\": \"北京\"}"}},
      {"id": "call_2", "function": {"name": "get_weather", "arguments": "{\"city\": \"上海\"}"}}
    ]},
    {"role": "tool", "tool_call_id": "call_1", "content": "{\"temp\": 25, \"weather\": \"sunny\"}"},
    {"role": "tool", "tool_call_id": "call_2", "content": "{\"temp\": 28, \"weather\": \"cloudy\"}"},
    {"role": "assistant", "content": "北京今天晴 25°C,上海多云 28°C。上海比北京高 3°C。"}
  ]
}

多步嵌套数据的三个构造要点:

  • 串行与并行都要有:上例是并行(一次消息里两个 tool_calls,无依赖);串行依赖(第 2 步的参数取自第 1 步的返回,如先查用户 ID 再查订单)是另一种必须覆盖的模式。配比建议参考真实场景的依赖结构,通常串行任务占比更高。
  • 深度的长尾:简单任务 1-3 步,复杂任务 10+ 步。深度分布不能只有浅的——但深轨迹有 22.5.4 的长上下文难题,需要与训练侧的长度外推配合。
  • 终止行为:数据必须教会模型”什么时候不再调工具”。全部由”达到最大步数被截断”结束的轨迹是危险的——模型学不到自然终止,上线后会死循环或反复重调。合成时要显式包含”信息已足够→直接回答”与”信息不足→向用户澄清”两种终止样本。

22.4.5 数据集生态:xLAM、Glaive 与 APIGen

xLAM(Salesforce, 2024):

  • 60K Tool-Use 数据
  • 覆盖 4000+ 工具
  • 支持 ReAct 与 OpenAI Function Calling 两种格式

Glaive:

  • 100K Tool-Use 数据
  • 单工具调用为主
  • 工具集较小(< 100)

工业选择:xLAM 适合复杂 Agent 场景,Glaive 适合简单工具调用。

生态层面的选型判据可以概括为三条,适用于一切现成 Tool-Use 数据集的评估:

  1. 工具覆盖广度:工具种类越多,模型泛化到没见过的工具的能力越强(对齐 SKT 的”增益随技能覆盖广度增长”结论)。
  2. 调用深度分布:数据集以单步为主还是多步为主,要与你目标场景的任务深度匹配。
  3. 可验证性:数据集是否附带可执行环境/验证器——只有”问题+理想轨迹”而无环境的数据集,无法做 22.5 的执行验证,质量上限低一个档次。

以 APIGen 为代表的管线化生产思路(先大规模合成、再可执行验证、只留合格轨迹)已成为事实标准——它把 Tool-Use 数据从”手工采集”变成了”工厂流水线”,其精神与 SKT 的”验证是增益来源”完全一致。


22.5 轨迹质量与验证

轨迹数据的价值上限由构造决定,下限由验证决定。这一节先盘点 Agent 数据的六大失效模式(每一条都对应一类必须专门构造的数据),再讲静态校验、执行验证与评估基准三道质量防线。

22.5.1 Agent 数据六大难点

难点一:工具幻觉

模型”想象”出根本不存在的工具。

prompt: "查北京天气"
assistant: {"name": "weather_api_v2", "arguments": "..."}  # 这个工具不存在

应对:

  • 训练时严格限制在 available_tools 内
  • 用 JSON Schema 静态校验工具名

工具幻觉在多工具场景下指数级恶化:给模型 200 个工具时幻觉率远高于给 20 个。对策是在数据构造时做工具干扰设计——可用工具列表里故意混入名称相近、功能重叠的工具(get_weather vs query_weather_forecast),迫使模型学会精确匹配而非模糊联想。

难点二:参数错误

模型调用工具时参数错。

prompt: "查北京天气"
assistant: {"name": "get_weather", "arguments": "{\"location\": \"北京\"}"}  # 参数名错

应对:

  • JSON Schema 校验参数名与类型
  • 在训练数据中加入”错误参数→修正”的负样本

负样本的设计有讲究:不只是”错了→罚”,而是要展示修正过程——模型发出错误参数、收到工具报错(“unknown parameter: location”)、下一步改对。这种”错误-反馈-修正”的三段式样本,与下面要讲的失败恢复共用一套数据构造逻辑。

难点三:失败恢复

工具调用失败时,模型应该重试或换工具。

Step 1: 调 get_weather(city="北京") → 失败
Step 2 (错误): 模型卡住,输出空
Step 2 (正确): 模型换一个工具,或告知用户失败

应对:

  • 在训练数据中加入”工具失败→恢复”的 trajectory
  • 用 Constitutional 风格生成”失败案例 + 修正”

HardGen 系统化方法:Constitutional 风格适合少量失败案例,但大规模生产需更系统化方案。HardGen(三阶段框架)专门合成”难”样本:

  1. 基础 trajectory:正常生成工具调用的成功路径
  2. 错误注入:刻意构造错误的环境响应(error_tool_response=true),模拟真实故障场景
  3. RL 优化:在 HardGen 合成数据上进行 RL 训练,学习从失败中恢复

效果:Qwen3-4B-Instruct 经 HardGen SFT+RL 后 BFCL Multi-Turn 从 22.13% 提升到 63.13%,超越 Gemini-3-Pro-Preview(60.75%)和 GPT-5.2(28.13%)。这揭示了一个重要叙事:工具调用能力的主要瓶颈不在模型大小,而在训练数据和方法——4B 模型经优质失败恢复训练后可超越未专门优化的顶级闭源模型。

✅ 本段数字已于 2026-10-11 逐条核验(arXiv:2601.01498 HTML 正文表 2 与 §4.2)。补三条此前缺失的口径信息:① 63.13% 是经过 SFT 与 RL 两个阶段后的终值,中间值是 SFT 后的 49.65%(+27.52),两步增益量级相当,不是一蹴而就;② 对照模型在原文中的完整代号是 GPT-5.2-2025-12-11,这是一个带日期的版本号,引用时应保留;③ 评测基准是 BFCLv3,“Multi-Turn”是该基准的一个子集列。由于这类榜值会随榜单更新而变动,建议按本书 Ch05 已确立的”榜值四元组”规范补记抓取日期与榜单版本。

失败注入的常见错误类型清单(可直接作为合成管线的故障场景枚举):

错误类型示例期望的恢复行为
瞬时失败超时、429 限流等待后重试
参数拒绝日期格式非法改正参数重试
权限不足403换有权限的工具或告知用户
资源不存在404 航班号无效向用户确认输入
结果为空查询无结果换查询策略或告知用户

难点四:长 trajectory

复杂任务需要 10+ 步工具调用,trajectory 超长(>8K tokens)。

挑战:

  • 显存爆炸
  • 上下文丢失
  • 训练不稳定

应对:

  • 用 YaRN 等长度外推技术
  • 训练时用梯度累积
  • 长 trajectory 数据用 Pack + document mask

数据侧还有一个常被忽略的配合项:长轨迹的”中段质量”。步数越多,中段某一步出错且后续”将错就错”的概率越高——验证必须逐步(见 22.5.3),而不能只看终点。此外,长轨迹在 loss 计算时如果按轨迹平均,一条 10 步轨迹与一条 2 步轨迹权重相同,但前者 token 数是后者数倍,token 级归一化的选择会实质改变数据分布权重。

难点五:工具偏好

模型过度偏好某个工具,不用其他工具。

用户: "今天几号"
assistant: 调用 search_engine("今天日期")  # 偏好搜索引擎
正确: 调用 get_current_date()  # 应该用专用工具

应对:

  • 训练数据中均衡各工具使用频率
  • 在 System Prompt 中明确工具选择优先级

工具偏好本质上是数据频率偏置的直接投影:训练数据里 search_engine 出现 5000 次、get_current_date 出现 50 次,模型自然学会”遇事不决先搜索”。治理手段是在数据审计时统计每个工具的调用频次分布,对过热工具降采样、对欠表达工具定向补数据。专用工具优先(能用 get_current_date 就不用 search_engine)的原则应写进合成任务的生成 rubric,而不是只依赖 System Prompt 的运行时提示。

难点六:意图波动(多轮特有的第六大难点)

前五个难点在单轮场景下同样存在。但有一个失效模式只在多轮中出现,而且不是”上下文不够长”能解释的:用户意图在对话中途变了。

IACM-RL(Dingwei Zhu 等 25 人)把它作为独立问题研究,给出的失效链条值得单独记住:

用户意图在后续轮次中漂移
  → 早期轮次的约束并未被删除,仍然留在上下文里
  → 过时约束稀释模型注意力
  → 灾难性意图偏移 + 无限 API 循环

为什么它不等于难点四的”上下文丢失”:长 trajectory 的问题是信息超出了窗口,解法是长度外推与压缩;意图波动的问题是信息还在窗口里、但已经过期了,模型却仍然被它支配——压缩与外推都救不了,甚至会把过时约束一起保下来。这是两类不同的失效,需要不同的解法。

现有方法的盲区:论文指出,既有方案(隐式历史扫描、文本压缩)大多假设简单场景下的完美指令——即用户一句话说清楚、中途不改。真实对话不满足这个假设,所以这些方法在意图波动下会失效。

IACM-RL 的三段式方案:

  1. DynamicIntent 管线:跨 13 种细粒度波动场景合成训练轨迹,并配套五维诊断指标套件用于量化失效
  2. 基于 BeliefState 的自生成上下文管理器:主动追踪不断漂移的目标,并用结构化的 stale flags 把被覆盖的参数隔离出去
  3. RL 优化:让模型自主内化上述管理能力

对数据工程的直接含义:

  • 合成数据必须包含意图漂移场景。如果你的多轮训练数据全是”一路顺风”的对话(用户第一句说清需求,后面只补充细节),模型在真实对话中遇到意图变更时没有对应的训练信号。DynamicIntent 的 13 种场景分类可以直接作为合成管线的场景清单参考。
  • “删除过时信息”比”保留全部信息”更重要。第 2 点的 stale flag 机制给出一个通用设计:与其让模型自己判断哪句还算数,不如在上下文结构上把已失效的参数显式标记出来。
  • 需要配套的度量,而不只是配套的指标。五维诊断指标套件的价值在于——它把”意图漂移导致失败”从一种主观感受变成了可定位的具体维度。做多轮 Agent 数据质检时,建议建立类似的分维度失败归因。

⚠️ 引用边界:IACM-RL 的场景设定是复杂工具调用(complex tool invocation)下的长程执行。其 13 种波动场景与五维指标针对该设定设计;若你的 Agent 场景不涉及多步工具调用,场景分类需要重新梳理,但”过时约束仍在上下文中”这一失效机理仍然成立。

22.5.2 JSON Schema 静态校验

六大难点的前两个(工具幻觉、参数错误)可以由静态校验直接拦截——这是轨迹数据的第一道质量防线。

校验流程

from jsonschema import validate

def validate_tool_call(tool_call, tool_definitions):
    """静态校验工具调用是否符合定义"""
    errors = []
    
    # 1. 工具存在
    tool_name = tool_call['function']['name']
    if tool_name not in tool_definitions:
        errors.append(f"Unknown tool: {tool_name}")
        return errors
    
    # 2. 参数符合 Schema
    tool_def = tool_definitions[tool_name]
    try:
        args = json.loads(tool_call['function']['arguments'])
        validate(args, tool_def['parameters'])
    except Exception as e:
        errors.append(f"Invalid arguments: {e}")
    
    # 3. 必填参数齐全
    for required in tool_def['parameters'].get('required', []):
        if required not in args:
            errors.append(f"Missing required argument: {required}")
    
    return errors

校验在训练流水线中的位置

1. 合成 trajectory
2. JSON Schema 校验每一步工具调用
3. 不合格的 trajectory 整条丢弃
4. 通过的进入训练集

经验:合成 trajectory 的不合格率约 15-25%,必须严格校验。

“整条丢弃”看似浪费,实则是止损:一条含一个非法调用的轨迹,其余步骤再漂亮也是毒药——模型会同时学到正确步骤与错误步骤。如果丢弃率过高(>40%),问题在合成 prompt 或 teacher 模型,应该修上游而不是放宽校验。

22.5.3 动态验证:从静态校验到执行验证

静态校验只能保证”格式合法”,保证不了”行为正确”。合法的调用完全可能是错的:

用户: "取消我明天上午的会"  # 用户日历上有两个上午的会
模型: cancel_meeting(date="明天", time="09:00")  # Schema 全对,但可能删错了会

这类问题只能靠执行验证(动态验证)发现——让轨迹在可执行环境里真跑一遍:

验证层级检查什么成本拦截能力
L1 格式层(JSON Schema)工具存在、参数合法极低工具幻觉、参数错误
L2 语义层(LLM 评审)调用是否服务于任务、顺序是否合理中工具偏好、逻辑混乱
L3 行为层(执行断言)终态是否满足任务要求高(需要环境)一切”过程对但结果错”
L4 过程层(逐步归因)每一步是否为当时最优极高冗余步骤、次优路径

L3 的核心是终态断言:任务描述之外,每条训练轨迹应附带机器可检查的成功判定(“预订记录存在且总价等于报价”、“航班已取消且 credit 到账”)。这与 τ-Bench 的设计思想同源(见 22.5.4)。L4 通常只在 RL 的过程奖励或小规模质检抽样中开展。

一个务实的分层部署:L1 全量、L2 抽样 10-20%、L3 对进入训练集的数据全量、L4 只对失败案例做归因分析。

22.5.4 多轮一致性评估

数据质量的最终裁判是评估。多轮与 Agent 领域的主流评测集:

评测集维度规模备注
MT-Bench多轮对话80GPT-4 评分
MINT多轮工具调用800LLM-as-Judge
τ-BenchAgent 工具调用165工具执行模拟
BFCLFunction Calling2000+工具调用准确率

MT-Bench 评分

MT-Bench 用 GPT-4 当 judge,对多轮对话评分:

def mt_bench_score(model, conversations):
    scores = []
    for conv in conversations:
        # 模型生成多轮响应
        model_responses = []
        for msg in conv:
            if msg['role'] == 'user':
                response = model.generate(msg['content'])
                model_responses.append(response)
        
        # GPT-4 评分(1-10)
        score = gpt4_judge(conv, model_responses)
        scores.append(score)
    
    return np.mean(scores)

MT-Bench 的定位是多轮对话的”通用质量”标尺,但它测不出工具调用的对错——judge 只能评”答得好不好”,不能评”API 调得对不对”。

τ-Bench 详解

τ-Bench(Yao et al., 2024)是 Agent 工具调用的硬核评测:

  • 模拟真实 API 环境(如 airline/retail API)
  • 测试多步工具调用 + 失败恢复
  • 用规则检查最终状态是否正确
任务: "帮用户取消航班 UA123,用户有 credit"
Step 1: 查用户 credit
Step 2: 查航班状态
Step 3: 调 cancel_reservation
Step 4: 确认取消

τ-Bench 评测的是”最终任务是否成功”,不是”过程是否像”。

τ-Bench 对数据工程的启示比”又多了一个榜单”深得多:它示范了可执行环境 + 终态断言这一整套评估基建。你的内部评估完全可以照此自建——为你的业务场景(工单系统、BI 查询、运维变更)各建一个”迷你 τ-Bench”,把线上高频任务做成带终态断言的环境实例。这既是最可信的评估,也是 22.6 环境工厂的第一批种子。

BFCL

BFCL(Berkeley Function Calling Leaderboard)专测工具调用准确率:

子任务指标
工具选择准确率选对工具的比例
参数准确率参数名/值正确的比例
多工具调用并行调用多个工具的准确率
失败恢复工具失败时的恢复率

BFCL 的价值在于分维度拆解:一个总分 70% 的模型,可能是”选择 95% + 参数 60%“(数据问题:参数负样本不足),也可能是”单工具 95% + 多工具 40%“(数据问题:并行调用样本不足)。分维度看分,失败模式直接映射回数据配比的修复动作——这正是数据飞轮(参见全书第 3 章)在 Agent 数据上的运转方式。

22.5.5 轨迹质量六维评估

综合以上防线,一条轨迹的入库质量可以用六个维度刻画(这六个维度同时也是 RL 多维奖励的标准设计):

维度检查内容主要手段
validity(合法性)每步调用格式合法、工具真实存在JSON Schema 静态校验
coverage(覆盖度)任务要求的能力/信息都被使用终态断言 + 必需技能检查(SKT 判据)
efficiency(效率)无冗余步骤、无死循环步数分布 + 环路检测
name accuracy(选择准确)每步选的工具是对的执行验证 / LLM 评审
argument accuracy(参数准确)参数名与值正确Schema + 语义校验
dependency / order satisfaction(依赖与顺序满足度)有依赖关系的调用顺序正确、该并行的并行轨迹级诊断(见下)

为什么必须单独列出第六维:前五维都是”逐步正确”,它们全绿仍可能拼出一条错轨迹——最终答案对,不等于过程对。TRAJECT-Bench(arXiv:2510.04550)用生产风格 API 配高保真可执行工具,合成宽度(并行调用)与深度(依赖链)各异的轨迹,除最终准确率外专门报告轨迹级诊断:工具选择正确性、参数正确性、依赖/顺序满足度。它揭示的两类失效模式值得直接抄进质检清单:

  • 相似工具混淆(similar tool confusion):工具池里存在语义接近的候选时,模型选错但参数填对,前五维里的 name accuracy 才抓得到;
  • 参数盲选(parameter-blind selection):工具选对了,参数值却不是从上下文推出的,而是按先验填的。

还有一条可直接转成数据构造建议的结论:从短轨迹到中等长度轨迹的跃迁是瓶颈(模型在长轨迹上的表现并非随长度平滑下降,卡点在短→中这一段)。合成轨迹时要专门补中等长度样本,只堆长轨迹补不上这个缺口。

六维齐备的轨迹,才有资格谈”质量优先于数量”;六维里任何一维的系统性短板,都指向一类待补的数据——这正是 22.8 工业 SOP 里配比决策的依据。

22.5.6 被漏掉的一维:轨迹安全

上面六维里没有任何一维是安全。对一个已经写了”环境即数据""Agent 会调用上千个工具”的章节,缺”工具反馈被投毒 / 间接提示注入”这一族风险是结构性的空位——Agent 的风险常常不是出现在单条 prompt 或最终回复上,而是在多步交互中逐步浮现。

ATBench(arXiv:2604.02022)给出了现成的风险清单骨架。它把 agent 风险沿三条正交轴组织:风险来源 × 失效模式 × 现实危害,叶层共 8 类风险来源、14 类失效模式、10 类现实危害,每条不安全轨迹带唯一三元组标签。风险来源轴分为四个超类族:用户输入、环境观测、外部实体、内部逻辑。基准本体为 1,000 条轨迹(503 安全 / 497 不安全),平均 9.01 turns、3.95k tokens,涉及 1,954 个被调用工具,取自 2,084 个可用工具的池子;质量控制由规则过滤 + LLM 过滤 + 全量人工审计三道组成。

其中“外部实体”这一类是本章最该补进去的:工具描述注入、恶意工具执行、被污染的工具返回——它们都不是用户输入,传统的输入侧安全过滤拿不到。

落到 22.8 工业 SOP 的两条动作:

  1. 轨迹入库前增加一次安全标注环节,按上述三轴打标签,而不是只看最终回复是否有害;
  2. 对工具定义做训练/评测集隔离——ATBench 的做法是评测用的工具池与训练池不重叠。若评测工具在训练中出现过,你测到的是记忆而非泛化。

22.5.7 反向杠杆:把失败轨迹改写成有效数据

22.5.1 把”失败恢复”与”长 trajectory”当作问题陈述,本章给出的手段偏向过滤——HardGen 造难的、L1–L4 卡掉坏的。还有一条相反方向的杠杆,成本极低:把部分成功的轨迹回收成正样本。

做法是两步:

  1. 约束满足率(CSR):把任务拆成一组可验证的约束,逐时步计算 CSR,取达到最大 CSR 的最短前缀;
  2. 截断并重写任务描述:在该点截断轨迹,并改写任务 prompt,使之匹配 agent 实际达成的结果。

消融证据(arXiv:2602.12544,Mistral 3 24B + LoRA):只用”完全成功”轨迹训练,任务成功率 25.0%;加入重标定后的部分成功轨迹,升到 29.9%(对应 CSR 55.0% → 60.2%)。配套基准 BookingArena 覆盖 20 个网站的复杂预订任务。

与 HardGen 是互补而非替代:HardGen 提高难题的供给,hindsight relabeling 提高已有轨迹的利用率。二值化评估(整条丢弃或整条保留)在复杂多约束任务上的浪费最大——agent 完成了 90% 的步骤,却因一个小约束失败被整条丢掉。

📌 该工作的”重写任务描述”一步是本章按其原文”extract the smallest prefix that reaches maximum CSR”与”leverage partially successful trajectories”归纳的操作化表述;论文自身的术语是 constraint-based evaluation,未使用 hindsight relabeling 一词。


22.6 环境即数据:Environment Factory 与环境 Scaling Law

22.6.1 轨迹的天花板是环境

把 22.3 到 22.5 连起来看,会发现一个反复出现的限制条件:

  • 22.3 说,自玩/RL 探索是可扩展性最高的轨迹来源——但”视验证器而定”;
  • 22.4 说,模拟器档的 mock 才能合成状态依赖的多步任务——而模拟器就是一种环境;
  • 22.5 说,执行验证(L3)与终态断言是质量的最终防线——而执行验证需要环境。

所有道路都通向同一个结论:轨迹不是独立存在的数据,它是”模型 × 环境”交互的产物。你囤了多少轨迹只是存量;你能生产多少新的、多样的、可验证的交互,才是增量。当轨迹用尽、模型把已有轨迹”吃干榨净”之后,瓶颈必然转移到环境上——轨迹的天花板,是环境撑起来的。

这就是”环境即数据”(Environment as Data)的含义:把环境本身当作一类一等公民的数据资产来生产、管理、配比与扩展。轨迹是环境上结出的果子;固化下来的轨迹是静态资产,环境是持续产出轨迹的动态资产。

22.6.2 Environment Factory:环境工厂

一个用于 Agent 训练的环境,完整形态包含六个要素:

要素内容数据工程动作
工具集(动作空间)环境暴露的 API/工具定义从真实工具图谱采样 + 合成变体
状态空间数据库、文件系统、会话内存快照构造 + 种子化
转移函数动作如何改变状态状态机/模拟器实现
初始状态分布任务开始时世界长什么样按任务难度参数化生成
任务与终止条件目标描述、成功/失败判定任务模板 × 参数实例化
验证器终态断言、过程约束与任务模板配套自动生成

Environment Factory(环境工厂)就是把这六要素的生成流水线化:给定一个领域(差旅、电商、运维),工厂能程序化地产出成百上千个结构多样、难度可控、自带验证器的环境实例,每个实例都能持续产生可判分的轨迹。

注意它与 22.4.3 的关系:mock 是退化的环境(无状态、无转移);模拟器是单一环境;环境工厂是批量生产模拟器的工厂。演化路线是:mock → 模拟器 → 环境工厂。

环境工厂让前面各节的数据问题获得系统性解法:

  • 失败恢复数据(22.5.1 难点三):不再靠错误注入脚本,而是让环境自带故障率参数,天然产生含失败的轨迹;
  • 意图波动数据(难点六):用户模拟器本身就是环境的一个组件,可以按 13 类波动场景参数化生成;
  • 执行验证(22.5.3):验证器与环境一体生成,L3 验证从”为每条轨迹手工写断言”变成”工厂批量产出断言”;
  • 长尾任务覆盖(22.2.4 采样偏差):任务模板的组合爆炸天然覆盖日志中稀少的任务类型。

22.6.3 EnvScaler:环境规模化生成框架

EnvScaler(arXiv:2601.05808)把”环境工厂”从工程隐喻推进为系统化框架,核心主张是:环境可以被像数据一样地规模化生产、质量评估与配比调度。其框架要点可概括为四条:

  1. 环境的程序化合成。以领域规范(工具 schema、数据模型、业务规则)为输入,用 LLM 程序化生成环境实例——包括工具集组合、初始状态、任务描述与配套验证器。环境不再是手工搭建的一次性资产,而是可按需量产的合成产物。
  2. 环境级质量验证。就像轨迹要过 JSON Schema 与执行验证,环境本身也要过质量门禁:工具集是否自洽(无死循环组合)、任务是否可解(存在至少一条成功路径)、验证器是否可判定(终态断言机器可查)。不合格的环境在进入训练前被拦截——“验证是增益来源”的原则从轨迹层上移到了环境层。
  3. 多样性度量与去重。环境实例之间要拉开距离(工具组合、状态结构、任务类型、难度),防止”一万个换皮环境”撑起虚假的规模。EnvScaler 用结构化特征刻画环境相似度,在生成端做多样性引导。
  4. 难度分层与课程化。环境按任务成功率/所需步数自动标定难度档位,为训练侧提供从易到难的课程(curriculum)素材——这与 SKT 的技能组合选择、HardGen 的错误率注入共同构成”难度工程”工具箱。

对数据团队的直接含义:你的环境也需要数据卡——领域、工具数、任务类型分布、难度分布、验证器覆盖率、版本号。轨迹数据卡里的 env_config 字段(22.3.3)从此有了完整的对应物。

一个跑通的实例,附全链路漏斗比。上面四条是框架,“批量造环境”在章内此前仍停留在工程隐喻。TerminalTraj(Large-Scale Terminal Agentic Trajectory Generation from Dockerized Environments,Siwei Wu 等 11 人,ICML 2026 Spotlight;代码 github.com/multimodal-art-projection/TerminalTraj)给出的是一个真跑通的终端 agent 数据管线,五步:

  1. 筛源用 ScoreModel,不按 star 数。用一个学到的 ScoreModel 评估 GitHub 仓库的质量与可 executability。这一条值得单独强调:按 star 数筛仓库正是 SWE-bench 式数据集规模天花板的来源——star 数衡量的是流行度,与”能不能在 Docker 里跑起来并按状态验证”几乎无关。
  2. 从仓库里找 Dockerfile 并自动构建镜像,按需注入领域依赖。
  3. 任务实例与可执行验证代码(如 pytest 套件)同批生成,用状态验证而非文本匹配。这正是 22.6.5 第 2 条”验证器先行”的落地形态——终态断言不是事后补写,而是与任务同批产出。
  4. 覆盖八个专门领域(Web Service / SQL / Security / QEMU 等)。
  5. 部署 agent 生成轨迹,用上述可执行验证代码逐条验证。

漏斗比(章内此前完全缺失的成本感知):

899,741 个初始仓库
  → 196,051 个高质量仓库(ScoreModel 筛)      ≈ 21.8%
  → 32,325 个 Docker 镜像                      ≈ 16.5%
  → 1,030,695 个任务实例
  → 50,733 条经可执行验证的轨迹                ≈ 4.9%(对实例)

两个比值各有用途:1.6 条轨迹/镜像(50,733 / 32,325)回答”我要 5 万条轨迹,得先造多少个环境”;而 4.9% 的实例→轨迹转化率回答的是更痛的那个问题——“造出来的任务里,只有不到二十分之一能产出一个通过验证的轨迹”。环境 Scaling Law(22.6.4)讲的是”环境越多越好”,但这两条比值才决定这件事的成本量级。

效果:Qwen2.5-Coder backbone 训练后在 TerminalBench 1.0 最高 +20%、2.0 +10%(论文表述为 absolute gains);TerminalTraj-32B 在 100B 以下模型中表现突出(TB 1.0 35.30% / TB 2.0 22.00%),并改善了 test-time scaling 行为。消融显示加入专门领域数据显著提升。

22.6.4 AgentScaler 与环境 Scaling Law

如果说 EnvScaler 回答”怎么造环境”,AgentScaler 关心的则是更根本的经验规律:Agent 能力如何随环境的规模增长——即环境 Scaling Law。

其核心发现可以表述为:在轨迹合成与 RL 训练的典型设置下,Agent 任务性能随环境数量/多样性的增长呈现近似幂律(power-law)提升,且这一规律在跨越数量级的环境扩展中保持稳定。这组规律与预训练的数据 Scaling Law 形成有意味的对仗:

预训练数据 Scaling Law环境 Scaling Law(AgentScaler)
自变量token 数量环境数量 / 多样性(难度、任务类型、工具组合)
因变量语言建模 loss / 下游能力Agent 任务成功率
增长形态幂律幂律
关键条件数据质量稳定环境可验证性稳定(验证器质量是前提)
边际收益递减来源数据重复、质量长尾环境同质化、难度饱和

但环境维度比 token 维度更丰富——这既是机会也是陷阱:

  • 多样性比数量更本质。一万同质环境的收益远低于一千个拉开结构距离的环境;“换皮式”扩展会让幂律在早期就掉头向下。
  • 难度阶梯是第二自变量。全是简单环境很快饱和,全是困难环境训练信号稀疏(成功率趋零,无梯度可用);难度分布的合理配比本身就是环境数据工程的超参。
  • 验证密度是幂律成立的前提。不可验证的环境再多也不产生有效训练信号——这与 SKT”去掉验证,规模化反而放大噪声”的消融结论在环境层面互为印证。

环境 Scaling Law 的实践含义可以压缩成一句:给 Agent 训练加预算时,先问环境曲线还有没有余量,再问模型还要不要加参数。4B 模型经 HardGen 训练超越顶级闭源模型的故事(22.5.1)已经说明瓶颈常在数据/方法侧;环境 Scaling Law 进一步把”数据侧”的杠杆明确指向环境数量、多样性与可验证性这三件事。

22.6.5 环境数据工程实践清单

把 22.6 收敛为可执行的工程动作:

1. 环境注册表:每个环境有唯一 ID、版本号、领域标签、难度档位、验证器清单
2. 验证器先行:没有终态断言的环境不允许进入训练管线
3. 多样性审计:定期对新环境做结构特征聚类,监测同质化
4. 难度校准:用当前策略模型跑抽样,按成功率重标难度档位(难度是相对模型的)
5. 环境版本化 + 轨迹回放:环境升级触发存量轨迹抽样回放(22.3.3)
6. 故障参数化:错误率、延迟、部分失败作为环境的可配置参数
7. 环境配比:按任务类型 × 难度 × 领域的矩阵管理环境供给,对齐业务分布

其中第 4 条值得强调:环境的”难度”不是绝对属性,是相对于当前模型能力的属性。随着模型变强,昨天的困难环境会变成今天的简单环境——环境工厂必须持续校准难度阶梯,把训练火力保持在”成功率 30-80%“的增益最丰区间。

22.6.6 下一格:世界模型即环境

22.6 到此解决的是”怎么造出足够多的环境”(Environment Factory → EnvScaler → AgentScaler)。同一条线还有下一格:能不能干脆用模型来当环境。

Qwen-AgentWorld(arXiv:2606.24597)是首个在单一语言模型内覆盖 7 类交互环境(MCP / Search / Terminal / SWE / Web / OS / Android)的世界模型,基于超过 1000 万条真实环境交互轨迹,走 CPT → SFT → RL 三阶段训练:CPT 注入通用世界建模能力,SFT 激活下一状态预测推理,RL 用混合 rubric 与规则奖励打磨仿真保真度。开源规模为 35B-A3B 与 397B-A17B 两档,配套 AgentWorldBench(由 5 个前沿模型在 9 个既有基准上的真实交互构建)。

对数据工程最有价值的是它作为解耦环境模拟器的用法:世界模型与 agent 解耦后,turn 级可控性(部分观测、难度调制)使得 Sim RL 达到或超过 Real RL。这把 22.6.1”环境的天花板”换了个解法——不必真的造出成千上万个可执行环境,可以让一个模型按需生成环境响应,且难度可以随手调。

它同时补上了 22.2.2 的 loss mask 规则。本书目前只写了”只在 assistant 轮计 loss”这一条(轨迹数据下扩展为”tool role 进上下文但不进 loss”)。轨迹数据其实还有更细的判据:可按每对 (action, observation) 是否真正携带环境信息做 turn 级 mask,被 mask 的 turn 仍然保留在上下文里,只是不计 loss。这与 22.2.2 的规则不冲突而是更细一层——mask 的粒度从”角色”下沉到了”turn”。

📌 来源与边界:七个领域、1000 万+ 轨迹、CPT→SFT→RL 管线、“Sim RL 匹配或超过 Real RL”(原文 §6.1:Controllable Sim RL exceeds Real RL trained using a live search)均出自论文;数字以官方论文与博客为准,非同行评议结论。

上文”turn 级 mask 仍保留上下文”一条,编辑轮次因 HTML 版全文检索零命中而标为”未定位到逐字表述”,终审按 PDF 全文已坐实(§3.2 Turn-Level Information-Theoretic Loss Masking):原文逐字 “Masked turns are excluded from the loss computation while their tokens are retained as context for subsequent turns”,判据为每条 (action, observation) 对计算四项统计量——Overlap (OL)、Novelty (Nov)、Jaccard (Jac)、长度比 (R)——并据此把 turn 归入七类语义类别、按类设保留比例;分类是统计驱动而非按工具名,故与工具无关。


22.7 前沿方向:Agentic Data Engineering

22.7.1 从”为 Agent 造数据”到”用 Agent 造数据”

本章到此为止的叙事都是”为 Agent 造数据”:构造多轮对话、合成轨迹、搭建环境。而正在发生的另一场转向是它的镜像——用 Agent 造数据:让 LLM 作为 Agent 深度参与数据准备的全流程。

这不是简单的”用 GPT 打标”。Agentic Data Engineering 与传统”LLM 辅助标注”的分野在于:

传统 LLM 辅助数据准备Agentic Data Engineering
LLM 的角色一次性调用的打分器/标注器有规划、有工具、有反馈闭环的执行者
任务粒度单条记录级(打分、分类)流程级(端到端完成一个数据工程任务)
工具使用无或极少数据库、ETL、质检工具、调度系统
质量保障靠事后人工抽检Agent 自带执行-验证循环 + 人在回路

22.7.2 LLM 作为数据工程师

Agentic Data Engineering(arXiv:2601.17058,综述)系统梳理了这一方向:LLM 不只是被处理的对象,而是作为 Agent 参与数据准备全流程——从需求理解到交付验收。综述覆盖的典型环节包括:

  • 数据发现与理解:Agent 主动探查数据源(schema 推断、语义 profiling、质量摸底),而不只是被动接收表结构说明;
  • 数据清洗与转换:识别并修复脏数据(实体消解、去重、缺失值处理),清洗规则由 Agent 制定、执行、自检;
  • 标注与增强:Agent 设计标注方案、预标注、仲裁分歧样本,人工只处理 Agent 标记的低置信长尾;
  • 合成与扩充:以 Agent 管线合成训练数据(本章 22.2-22.4 的全部内容,本身就是 Agentic DE 的最大应用场景);
  • 质量监控与溯源:对数据资产持续体检,自动归因质量回退。

支撑这一切的,恰恰是本章前几节建设的那些能力:Agent 要操作数据库与 ETL 工具(22.4 工具调用)、要跨多轮推进长任务(22.2 多轮一致性)、要在失败时重试换路(22.5 失败恢复)。Agent 数据工程与 Agentic 数据工程是同一枚硬币的两面——你为 Agent 造的数据,正在被用来制造数据。

22.7.3 数据准备全流程的 Agent 化改造

把传统数据工程任务映射到 Agent 化后的形态,可以看清改造的幅度:

传统任务人工/脚本做法Agent 化后
Schema 映射人工比对字段含义Agent 读源/目标 schema,提出映射并生成验证用例
实体消解规则 + 人工复核Agent 综合语义与上下文聚类,边界案例带理由送人工
数据清洗写死规则的 ETLAgent 诊断脏数据模式,生成、执行、测试清洗规则
数据标注标注员逐条标注Agent 预标注 + 不确定性采样,人工只看分歧
数据资产盘点季度性人工梳理Agent 持续巡检 + 增量报告

三类任务收益最大:语义密集(实体消解、schema 理解——LLM 的天然主场)、长尾多样(清洗规则写不过来的脏数据花样)、文档生成(数据卡、血缘、质量报告)。

22.7.4 风险与边界

Agentic DE 不是免费的午餐,至少有四条边界需要守住:

  1. 错误传播。Agent 的清洗决策会改写下游数据,一个系统性偏见会被放大到全库。对策:所有 Agent 产生的数据变更必须可回滚、可溯源(变更日志 + 置信度标注)。
  2. 验证者悖论。用 Agent 验证 Agent 的产出,谁来看守看守者?对策:关键环节保留确定性验证器与人工抽检,Agent 的自检不能是唯一防线——这与 22.5”验证是增益来源”的原则一脉相承。
  3. 成本结构反转。Agent 化流程的边际成本从”人工小时”变成”推理 token”,任务设计不合理时 token 成本可能超过人工。对策:分级路由(简单记录走规则/小模型,复杂记录才动用 Agent)。
  4. 能力边界漂移。底层模型升级可能改变 Agent 的失败模式,上周验证过的管线这周可能悄悄劣化。对策:为数据管线建立回归测试集——数据流水线也需要 CI。

多模态特化:GUI / 视觉 Agent 的轨迹数据

22.3 讲的轨迹形态是”观察 → 思考 → 动作 → 观察”,其中观察是文本(工具返回的 JSON / 日志)。GUI Agent 把观察换成了像素:状态是一张截图,动作是坐标级操作(点击 x/y、输入文本、滑动、长按),思考则要从截图里读出元素位置与页面语义。仅这一处替换,就把轨迹数据的工程难度抬了一档。

轨迹必须带坐标与元素定位。一条完整的 GUI 轨迹至少包含:截图(或截图序列)、动作类型、动作参数(坐标或元素 id)、动作前后的状态。只记”点击了提交按钮”而没有坐标,模型学不到定位;只有坐标而没有元素语义,模型泛化不到新排版。坐标与元素的映射才是 GUI 数据的核心价值,做法是把截图与 DOM / a11y tree 对齐:Web 侧可直接取元素框与语义,移动端走 Android 的 Accessibility Service,桌面端则退回 Grounding DINO + SAM 一类开放集检测与分割来补框。

采集三路:真机或模拟器录制(AndroidWorld、OSWorld 一类环境提供可复现的镜像)、强模型回放标注(让强 VLM 看截图序列反推动作)、Web 侧用 DOM 直接合成。第二路是当下扩量的主力,但必须做执行验证:回放的动作要真的在环境里跑通并被判定为任务成功,否则标注噪声率高到不可用。

失败轨迹比成功轨迹更稀缺,也更值钱。这承接 22.6”环境即数据”的观点:GUI 环境本身就是一台数据生产机器,而环境的可复现性直接决定数据的可验证性——同一镜像、同一 seed、同一初始状态,才能重放一条失败轨迹并定位是哪一步走岔。WebArena、Mind2Web 这类环境之所以成为事实标准,正是因为它们把”可复现”做成了基础设施。成功轨迹只能教模仿,失败轨迹才能教纠错与恢复。

隐私是硬约束:用真实账号录制,截图里全是个人信息(头像、聊天记录、订单、余额)。脱敏必须前置,走第 7 章视觉 PII 的路子——检测 + 模糊,而不是正则替换。

(详见第 26 章多模态数据工程)


22.8 多轮/Agent 数据工业 SOP

把全章收敛为一条可落地的流水线:

1. 任务定义:多轮对话 / 工具调用 / 复杂 Agent
2. 来源配比:
   - 30% UltraChat 风格多轮
   - 30% xLAM 风格工具调用
   - 20% 真实日志
   - 20% 人工金标
3. 合成:双模型自玩 + mock 执行
4. JSON Schema 校验:不合格丢弃
5. 上下文一致性检查
6. 失败恢复 trajectory 补充
7. System Prompt 5 类型配比
8. 长度过滤 + Pack
9. 训练:SFT(多轮 loss mask)→ DPO → RL(可选,GRPO on tool-use)
   - RL 使用多维奖励:validity + coverage + efficiency + name accuracy + argument accuracy
   - 训练数据:多轮 MCP conversation(~13K 条即可见效)
   - 对 BFCL Multi-Turn 有 3-10pp 改善(Qwen3-4B +10.2pp, Qwen2.5-7B +3.7pp)
   - <!-- edit: 2026-07-29 SOP 第9步新增可选 RL 阶段(GRPO for tool-use, arXiv:2606.03892) -->
10. 评估:MT-Bench + τ-Bench + BFCL

对照本章的体系化结构,这份 SOP 可以再往上加两层(升级版 SOP):

11. 环境层:环境注册表 + 验证器先行 + 难度校准(22.6)
    - 轨迹供给从"囤积存量"转向"环境持续产出"
    - 环境扩展预算对标环境 Scaling Law 的三要素:数量、多样性、可验证性
12. 流水线自身 Agent 化(22.7):
    - 合成/校验/配比环节逐步引入 Agent 执行-验证循环
    - 变更可回滚、带置信度、有人工抽检兜底

SOP 里每一步的失效模式与对策,都在前文有对应展开:第 3 步的 mock 质量见 22.4.3;第 4 步的丢弃率红线见 22.5.2;第 5 步的三层金字塔见 22.2.3;第 6 步的故障类型清单见 22.5.1 难点三;第 9 步的多维奖励就是 22.5.5 的六维质量;第 3 步的轨迹入库还应增加 22.5.6 的安全标注环节。

🛠️ 本章衍生工具

🌳

数据工程·多轮对话演化树

《AI 数据全书》第 22 章「多轮 Agent 交互数据」配套——① messages 校验台:§22.2.1 三条结构规则(system 只能在开头 / user-assistant 严格交替 / 必须以 user 开头且 assistant 结尾)在 8 条脏样本上真跑,报错串与原文逐字一致(system at position i / consecutive user at i / must start with user / must end with assistant)+ 错误行红框回指下标;loss mask 逐 token 高亮(Llama-3 <|start_header_id|> ⇄ Qwen <|im_start|> 一键切换),⭐ 打开「mask 逻辑未跟随模板」即刻复现真实训练事故——模板 token 与 user token 被算进 loss,泄漏/漏算数实时计数,配零模型泄漏检查(应有 loss vs 管线实算 loss,Δ 精确到 1e-9)+ 轮级 vs token 级平均与「最长回复占梯度 X%」 · ② 多轮演化树:persona(急躁运维/谨慎法务/话痨学生)× 目标(砍价/报障/改签)× 三种分支概率生成 SVG 分支树,UltraChat 关键设计做成开关(5 轮截断 / 〈|end|〉 结束检测 / 双实例 vs 单实例——切到单实例后多样性剪枝会把树剪到只剩 1 条);⭐ 剪枝策略竞技场(完整保留 / 按质量阈值 / 同簇留代表)输出轮数分布直方图与「只保留主干」对照,讲清「评估数据规模统计轮数而非消息条数」;System Prompt 五类型配比卡片(30/20/20/20/10)+ 相容性检查(「你是 Python 专家」× 非技术主题 → 判脏,原文称之为比缺 system 更糟) · ③ 一致性与意图波动:四维度卡片(事实/指代/目标/风格)规则层真跑,逐轮抽取城市/日期/带单位数字/称呼四类槽位与历史比对,四个注入开关各自触发对应告警;三层成本金字塔标注规则层过滤 30–50%;⭐ stale flag 左右分屏沙盘——同一段「北京明天 → 改上海 → 其实要后天」,无 flag 侧仍按北京回答并陷入无限 API 循环,结构化 stale flag 侧把被覆盖参数显式隔离;IACM-RL 的 13 种波动场景名称/计数/占比全部回源 arXiv:2608.02110 Table 18(N=5,639,逐项相加自洽),附五维诊断指标 JGA/SCRR/RIR/DTCR/ISSR · ④ 五维质量 + 环境工厂:6 条 tool_calls 样本跑 §22.5.2 的 validate_tool_call(Unknown tool / Invalid arguments / Missing required argument)与 id↔tool_call_id 配对检查,配 15–25% 正常区与 >40% 红线;五维雷达(validity/coverage/efficiency/name accuracy/argument accuracy)+ 环境六要素面板 + mock→模拟器→环境工厂演进路线 + 环境 Scaling Law 三自变量曲线(数量/多样性/难度阶梯,标注示意)+ DeepSeek-V4-Pro 12,200 度量自身反例卡 + §22.8 十步 SOP 可展开流水线;页脚三分诚实性标注 📖 文献值 / 🧮 真算 / ⚠ 教学模型·规则生成