你们的 Function Call 是怎么训练的?训练数据怎么构建的
1️⃣ 考察意图
面试官想看你是否真正落地过 Agent 系统的训练完整流程,而非只调过 API。考察类型是系统设计 + 工程取舍。刁钻点在于:很多人只会背“用合成数据做 SFT”,但一问到负样本怎么构造、难例如何挖掘、多轮上下文如何对齐就露馅。答好了能展示你对 Function Call 整条链路(数据→训练→评估→迭代)的掌控力,以及从“能用”到“好用”的工程优化思维。
2️⃣ 标准答
Function Call 训练的核心目标有三个:工具选择(该调哪个函数)、参数绑定(JSON 格式正确、值合理)、多轮上下文保持(历史调用不干扰当前决策)。训练方法分轻量级和重量级,数据构建是成败关键。
训练数据构建(三类样本 + 来源)
- 正样本:模型必须学会的“标准答案”。
- 来源 1:真实用户日志。从线上 Agent 日志中提取用户 query + 正确调用的函数名 + 参数 JSON。注意清洗:去掉因上游错误导致的“假正例”。
- 来源 2:合成数据。用 GPT-4 / DeepSeek 等强模型,基于 API 文档自动生成 query-call 对。技巧:对每个函数生成 3-5 种不同表述的 query(如“查天气” vs “今天出门要带伞吗”),增加泛化性。
- 来源 3:规则模板。对固定参数(如
get_weather(city, date)),用程序枚举城市名和日期组合,生成海量模板数据。坑:模板数据太死板,模型会过拟合到固定句式,必须混入 30% 以上的自然语言变体。 - 负样本:模型必须学会“不做什么”。
- 类型 1:错误函数选择。用户 query 明显不匹配某函数时,强制模型输出
no_call或调用无关函数。例:用户问“股票行情”,却调用send_email。 - 类型 2:参数错误。参数名拼错(
cityy而非city)、类型错误(字符串传成数组)、必填参数缺失。 - 类型 3:幻觉调用。调用一个不存在的函数名(如
get_weather_forecast而非get_weather)。来源:用规则生成 + 从线上 Badcase 中抽取。 - 难例(Hard Negative):提升模型“抗干扰”能力的关键。
- 场景 1:相似函数混淆。
get_weathervsget_forecast,参数几乎一样,但返回值不同。构造 query 模糊的样本(如“明天天气如何”),让模型必须根据上下文选择正确函数。 - 场景 2:多轮上下文干扰。上一轮调用了
send_email,本轮 query 是“再发一封”,模型必须复用历史参数而非重新生成。解法:在训练数据中插入 20% 的多轮样本,且每轮随机插入 1-2 个无关函数调用作为干扰。 - 场景 3:参数值边界。日期参数传“2025-02-30”(非法日期),模型必须拒绝或修正。构造这类样本时,用规则生成非法值,并标注正确行为(报错 or 自动修正)。
训练方法
- 轻量级(LoRA):适合快速迭代。在基座模型(如 Qwen2.5-7B)上加 LoRA rank=16,只训练 2-3 个 epoch。Trade-off:LoRA 对复杂参数绑定(如嵌套 JSON)效果差,因为低秩矩阵难以捕捉深层语义。解法:对参数绑定部分单独用全参数微调(Full Fine-tune)一个 1B 小模型,然后蒸馏回大模型。
- 重量级(SFT + DPO):适合生产级 Agent。
- SFT 阶段:用上述三类数据混合训练,正负样本比例 3:1(正样本过多会导致模型“过度自信”,总想调用函数)。学习率 1e-5,batch size 128。
- DPO 阶段:对每个 query 生成多个候选调用(通过 temperature 采样),用规则或人工标注偏好对。例:query “北京天气”,候选 A(
get_weather(city="北京"))优于候选 B(get_weather(city="北京市")),因为参数值必须与 API 文档一致。坑:DPO 训练时,偏好对必须保证“正确调用 vs 错误调用”的区分度,否则模型会学到无意义偏好。 - 实际落地的坑 + 解法:
- 坑 1:数据泄露。合成数据中混入了测试集 query,导致评估指标虚高。解法:用 MinHash 去重,确保训练集与测试集 Jaccard 相似度 < 0.3。
- 坑 2:多轮上下文漂移。模型在长对话中忘记之前调用的函数。解法:在训练数据中,对每轮对话随机截断前 3 轮上下文,强制模型依赖最近信息。
- 坑 3:参数值幻觉。模型生成不存在的城市名(如“Beijing City”)。解法:在 DPO 阶段加入“参数值合法性”偏好对,并配合后处理校验(如调用 API 前用正则过滤非法值)。
3️⃣ 答题模板(30 秒电梯版)
“这个问题我从数据构建、训练方法、工程坑三个层面回答。数据层面,分正样本(真实日志+合成+模板)、负样本(错误选择+参数错误+幻觉)、难例(相似函数+多轮干扰+边界值)三类。训练层面,轻量用 LoRA,重量用 SFT+DPO,DPO 阶段必须构造高区分度偏好对。总结一句:Function Call 训练的核心不是让模型‘学会调用’,而是‘学会不调用’和‘在干扰中正确调用’。”
4️⃣ 高频追问 & 应对
追问 1:你们怎么评估 Function Call 的准确率?线上指标是什么?
评估分离线与在线。离线:构建测试集(500-1000 条),计算工具选择准确率(函数名正确率)和参数绑定 F1(参数名+值的精确匹配)。注意:参数值有同义变体(如“北京” vs “北京市”),用归一化后比较。在线:看调用成功率(API 返回 200 且无参数错误)和用户满意度(通过隐式反馈,如用户是否继续追问)。坑:离线准确率 95% 不代表线上好用,因为线上有大量未见过的函数和参数值。
追问 2:如果函数数量从 10 个扩展到 1000 个,训练数据怎么调整?
核心挑战是函数选择空间爆炸。解法:① 引入检索增强,先用 BM25 或 embedding 召回 Top-20 候选函数,再让模型从中选择。训练数据中,每个 query 只标注候选函数内的正负样本,减少模型负担。② 对低频函数做数据增强,用 GPT-4 生成 50-100 条 query-call 对,并加入 30% 的难例(与高频函数混淆)。③ 训练时用课程学习,先训 10 个函数,再逐步增加,避免模型一开始就面对 1000 个函数导致收敛困难。
追问 3:你们怎么处理函数参数有嵌套结构(如 filter{conditions: [{field: "age", op: "gt", value: 18}]})?
嵌套参数是 Function Call 的难点。解法:① 在训练数据中,对嵌套结构做扁平化,将
filter.conditions[0].field视为独立参数,用序列标注方式生成。② 引入结构约束,在模型输出后加一层 JSON Schema 校验,自动修正格式错误(如补全缺失的括号)。③ 训练时,对嵌套参数样本做上采样,比例从 10% 提升到 30%,因为模型天然对扁平结构更敏感。Trade-off:上采样过多会降低简单样本的准确率,需通过验证集调优。
5️⃣ 避坑 · 常见错误答法
- ❌ “只用 GPT-4 合成数据就够了,不需要负样本。” → ✅ “正样本让模型学会‘做什么’,负样本让模型学会‘不做什么’,难例让模型学会‘在干扰中做对’。三者缺一不可,比例建议 3:1:1。”
- ❌ “训练时用全参数微调,效果最好。” → ✅ “全参数微调成本高且容易灾难性遗忘。实际工程中,先用 LoRA 快速验证数据质量,再用 SFT+DPO 做最终训练。对参数绑定部分,可以单独用 1B 小模型全参数微调后蒸馏。”
- ❌ “评估只看函数名准确率。” → ✅ “函数名准确率只是基础,参数绑定 F1 和多轮上下文保持率才是关键。线上还要看调用成功率和用户满意度,因为参数值幻觉(如非法日期)会导致 API 报错。”
6️⃣ 简历呼应
- 如果你有 RAG 项目:从“检索增强”角度切入,说明如何用 BM25 召回候选函数,减少模型选择空间。强调 RAG 中的 query 改写经验可迁移到 Function Call 的 query 归一化。
- 如果你只做过传统 NLP:用“序列标注”类比参数绑定(如 NER 中的实体识别),说明嵌套参数可视为结构化序列。强调传统 NLP 中的数据增强方法(如回译)同样适用于合成数据生成。
- 如果你是校招无项目:聚焦论文复现,如《Toolformer》或《Gorilla》的数据构建方法。说明你理解合成数据 + 负样本 + 难例的体系,并可以手写一个简单的 LoRA 微调 demo(用 Hugging Face PEFT 库)。
- 《Toolformer: Language Models Can Teach Themselves to Use Tools》
- 《Gorilla: Large Language Model Connected with Massive APIs》
- 《Training Language Models to Follow Instructions with Human Feedback》(RLHF 在 Function Call 的变体)
- Hugging Face PEFT 库的 LoRA 微调教程
- 《Self-Instruct: Aligning Language Models with Self-Generated Instructions》(合成数据方法论)