唯品会

NLP 算法工程师一面面经

完整信息

10 条问法 · 10 道题

本篇目录
1. 请做一下自我介绍,并重点说明你在大模型后训练、Agent 或工具调用方向上的技术积累 2. 轨迹数据的最长长度、最短长度与任务复杂度之间是什么关系?训练时如何调用这类数据 轨迹长度本身不是任务复杂度的充分条件。一个任务可能调用十几个工具,却只是重复翻页;另一个任务只有两次调用,但需要精确处理跨工具依赖。因此更合理的复杂度指标应同时考虑: 有效工具调用数量; 依赖图深度与分支数; 参数约束数量; 环境状态变化次数; 错误恢复次数; 最终答案对中间结果的依赖程度。 可以定义一个近似复杂度分数: 最短轨迹一般用于学习基本格式、工具选择和参数填充;中等长度轨迹负责学习多步规划;最长轨迹则用于训练状态维护、错误恢复和长程 credit assignment。 训练时不能简单按照数据原始分布随机采样,否则短轨迹会占据大多数 batch。通常采用长度桶和复杂度桶联合采样: import random from collections import defaultdict buckets = defaultdict(list) for sample in dataset: length_bin = min(sample["num_calls"] // 3, 5) complexity_bin = min(int(sample["complexity_score"] // 2), 5) buckets[(length_bin, complexity_bin)].append(sample) def sample_batch(batch_size): keys = list(buckets.keys()) batch = [] for _ in range(batch_size): key = random.choice(keys) batch.append(random.choice(buckets[key])) return batch 还可以在训练前期提高短轨迹比例,保证结构化输出稳定;中后期逐步增加长轨迹和失败恢复轨迹,形成 curriculum learning。 3. Tool-use SFT 的训练目标是什么?基座模型已经具备工具调用能力时,SFT 还需要学习什么 Tool-use SFT 的目标不是让模型“知道存在工具”,而是把通用能力约束到特定工具协议和业务环境中。即使基座模型已经具备较强的工具调用能力,也不代表它能直接完成下面这些事情: 从大量候选工具中选择正确工具; 遵循私有 schema 和字段约束; 按照正确顺序完成多工具调用; 将上一步工具结果准确传入下一步; 在工具报错、超时或返回空结果时恢复; 判断何时停止调用并输出最终答案。 训练数据通常写成: 其中T是工具定义,ai是工具调用,oi是环境返回,z 是最终答案。 需要特别注意 loss mask。工具返回结果来自外部环境,通常不应该作为模型预测目标;真正需要计算 loss 的部分是 assistant 的推理、工具选择、参数和最终回答。 labels = input_ids.clone() for span in environment_output_spans: labels[span.start:span.end] = -100 for span in system_prompt_spans: labels[span.start:span.end] = -100 loss = model( input_ids=input_ids, attention_mask=attention_mask, labels=labels ).loss 如果把工具返回也纳入 loss,模型可能学习“伪造观察结果”,而不是等待环境真正执行工具。 4. Chat Template、System Prompt 和模型训练阶段写入的行为先验分别是什么关系 三者属于不同层次。 Chat Template 是序列化协议,负责把 system、user、assistant、tool 等结构转换成 token 序列,例如插入角色标记、结束符和工具调用边界。它解决的是“消息如何编码”。 System Prompt 是运行时上下文的一部分,用于声明身份、规则、工具和输出要求。它是显式条件,不是模型结构中天然存在的一段固定文本。 训练形成的行为先验 存在于模型参数中。模型在预训练和后训练过程中学习到:看到某类 system 指令后应当怎样回答、什么时候调用工具、如何结束生成。它不是可直接读取的“隐藏 system prompt”。 可以将最终输入近似表示为: 模型输出则为: P(Y∣X;θ) 其中 (\theta) 包含训练阶段形成的行为先验。更换模板但不重新适配,可能导致角色边界错乱、工具调用格式失效,甚至使模型无法正确停止。 5. 将 MCP 工具的 JSON Schema 注册给模型时,模型究竟是如何获得结构化工具信息的 MCP 解决的是客户端与工具服务之间的发现、描述和调用协议,它不会把工具“写进模型参数”。一次完整流程通常是: 客户端连接 MCP Server; 通过 tools/list 获取工具名称、描述和输入 schema; 将工具 schema 转换成模型 API 或模板支持的格式; 把工具定义注入当前上下文; 模型生成结构化 tool call; 客户端解析并校验参数; 调用 MCP Server; 将工具结果作为 tool message 回填上下文; 模型继续生成。 工具定义可能被序列化为类似下面的内容: { "type": "function", "function": { "name": "query_order", "description": "根据订单编号查询订单状态", "parameters": { "type": "object", "properties": { "order_id": { "type": "string" } }, "required": ["order_id"], "additionalProperties": false } } } 模型并不真正“执行函数”,它只是生成一个满足协议的调用意图: { "name": "query_order", "arguments": { "order_id": "A1024" } } 真正的参数校验、权限控制、函数执行、重试和结果回填都由 Agent Runtime 完成。生产环境不能仅依赖模型生成合法 JSON,还需要在执行前进行 schema validation。 6. 工具数量达到数千个时,为什么不能把全部 schema 直接塞进上下文?如何设计工具路由 全部注入会引发三个问题:上下文成本过高、相似工具之间发生注意力干扰、模型选择错误率随候选集合扩大而上升。 更合理的是分层路由: 第一层根据任务选择工具域,例如数据库、代码执行、搜索、办公系统;第二层在域内做向量召回或学习式 rerank;第三层只向大模型暴露 Top-K 工具。 候选召回不能只看用户问题,还应结合对话状态和上一步工具结果: 其中权限不满足的工具应在进入模型上下文之前就被过滤,而不是通过 prompt 告诉模型“不要调用”。 对召回阶段的评测也不能只看工具选择准确率,应重点观察 Recall@K。因为正确工具没有被召回时,后续模型无论多强都无法恢复。 7. 主流 Agent Harness 的核心差异体现在哪里 Agent Harness 本质上是模型与外部环境之间的运行时。不同框架的核心差异通常不在“能不能调用工具”,而在下面几个方面: 控制循环:固定 ReAct 循环、状态机、事件驱动或图执行; 上下文管理:完整保留、滑动窗口、摘要压缩或分层记忆; 工具执行模型:串行、并行、异步、事务式执行; 状态持久化:是否支持 checkpoint、恢复和跨会话状态; 权限与隔离:文件系统、网络、Shell 和密钥访问是否沙箱化; 可观测性:是否记录 token、工具参数、环境状态和错误链路; 人机协同:高风险操作是否支持审批、回滚和中断; 协议适配:是否兼容 MCP、函数调用协议及不同模型模板。 代码型 Agent 还必须处理仓库索引、补丁生成、测试执行、终端状态和上下文压缩。真正决定上限的是环境建模与状态管理,而不仅是 prompt。 8. 为什么 Step-level SFT 之后再进行 GRPO,通常比直接从基座模型开始做 GRPO 稳定 GRPO 属于 on-policy 或近似 on-policy 优化。它要求当前策略能够采样出一定比例的有效轨迹,组内奖励才有可比较性。 如果直接从基座模型开始: 大量输出不符合工具协议; 同组样本可能全部奖励为零; 奖励方差过低,归一化优势接近零; 探索空间被无效格式占据; KL 和熵正则难以同时维持稳定。 GRPO 的组内优势通常写成: 当组内所有轨迹都失败时: 此时几乎没有有效学习信号。Step-level SFT 先让模型学会基本动作空间,例如合法 JSON、工具边界、调用顺序和停止条件,把策略带到奖励非零区域;之后 GRPO 才能优化多种合法策略之间的质量差异。 9. GRPO 中相对奖励是如何计算的?同一组奖励方差接近零时如何处理 GRPO 对同一问题采样 (G) 条回答,并使用组内相对表现估计优势: 优化目标可写为: 其中: 当组内方差接近零时,强行除以很小的标准差会放大噪声。可采用以下处理: 跳过该组更新; 使用标准差下限; 混合全局 advantage statistics; 增大采样组大小或采样温度; 改善任务难度分布,减少全对或全错样本; 增加过程奖励,提高组内区分度。 def group_advantage(rewards, eps=1e-6, min_std=0.05): mean = rewards.mean() std = rewards.std(unbiased=False) if std < eps: return None # 跳过无区分度的 group std = std.clamp_min(min_std) return (rewards - mean) / std 长期大量出现零方差组,通常说明题目难度、采样策略或奖励函数存在问题,而不只是数值稳定问题。 10. 长轨迹做 SFT 时,采用截断、切分还是掩码?如何避免破坏工具依赖关系 直接从 token 中间截断最危险,因为可能截掉工具调用参数、工具返回结果或关键依赖节点,导致训练样本语义不闭合。 更合理的方法是按照语义边界切分: 每个 chunk 从完整消息边界开始; tool call 与对应 tool result 不得分离; 下游调用所依赖的关键观察
本篇目录
  1. 01
  2. 02
  3. 03
  4. 04
  5. 05
  6. 06
  7. 07
  8. 08
  9. 09
  10. 10

相关公司