金融语音 Agent 后训练实战:从 SFT 到 GRPO 的踩坑全记录
这篇文章是基于知乎原文的深度扩写。我以第一视角重新梳理了一个金融实时语音 Agent 从架构设计、模型选型、SFT 到 GRPO 微调、上线部署的完整后训练过程——把每一个踩过的坑和最终有效的解法摊开来拆解清楚。
这篇文章说了什么
我为一个金融客户构建了一套实时语音聊天机器人,从架构搭建、模型选型,到 SFT 与 GRPO 微调、上线验证,完整走过了 LLM 后训练的全过程。技术栈是 ASR → LLM Agent → TTS 的端到端语音对话,最终做到了 p99 延迟 1.5s。
这篇文章围绕三个核心挑战展开:如何在延迟和智能之间平衡(不得不分层)、如何构造高质量训练数据(真实录音的噪声处理)、如何让模型真的"能落地"(不要做脱离实际生产的调参)。重点是 Orchestrator 的 GRPO 微调——路由准确率从 87.1% 拉到 98.4%。
两个回合下来,有效的实验就这几招
| 方法 | 做了什么 | 效果 |
|---|---|---|
| 双层 Agent 架构 | Orchestrator(Qwen2.5-7B)做调度 + 复杂 Sub-Agent(Mixtral-8x22B)做推理,"先响应后执行" | 用户感知延迟压缩到 1.3s 内,复杂任务不卡交互 |
| 跨语言分层处理 | 小模型负责 Hindi/Hinglish 的语言理解与生成,大模型只处理英文推理 | 大模型专注推理不背多语言负担,系统又快又好 |
| ASR 噪声注入训练 | 构建 ASR 错误模拟器,分析 Whisper 典型错误后注入 10% 噪声到训练数据 | 路由 micro-F1 从 81.2% 提升到 90.9% |
| 复合 reward 函数 | 路由准确性(0.3) + 响应自然度(0.4) + 延迟惩罚(0.3) 三项加权 | GRPO 后路由准确率 87.1%→98.4% |
| 实体识别预训练 | 5,000 条 NER 数据先训练日期/金额/账号提取,再训练工具调用 | API 参数准确率 85%→96% |
| 模型选型全面对比 | Llama3-8B vs Gemma2-9B vs Qwen2.5-7B vs Mistral-7B 逐个测试 | Qwen2.5-7B 综合最优(格式错误率低至 3%,多语言稳定) |
试了但翻车的方法:学习率 5e-6(Hindi 能力退化)、LoRA rank=16(容量不够路由 88%)、单一大模型方案(延迟+成本双高)。
一、先搞懂这些概念
1.1 Agent 架构是什么
这是一个 ASR → LLM Agent → TTS 的端到端语音对话系统。五个环节:
用户说话 → WebRTC → ASR(Whisper) → Agent(LLM) → TTS(Polly) → WebRTC → 用户听到
看起来简单,但最大的挑战是延迟与准确率的 trade-off——用户不能等,但大模型推理本身就慢。最终 VAD 后 p99 延迟 1.5s:ASR 0.25s + LLM 0.35s + TTS 0.8s。
1.2 双层 Agent 架构是什么
一开始考虑过用单一大模型处理所有问题,很快发现不行:
| 问题 | 具体表现 | 后果 |
|---|---|---|
| 响应速度 | 大模型推理慢 | 用户等待时间 >2s,体验差 |
| 成本 | 所有请求都走大模型 | token 成本不可持续 |
| 灵活性 | 简单查询和复杂诊断用同一模型 | 资源浪费 |
所以我设计了一个双层 Agent 架构——Orchestrator + Sub-Agent。打个比方你就明白了:
Orchestrator 是银行大堂经理。 你进门说「我要取钱」,他立刻回复「请跟我来」然后带你到 ATM——响应快,不需要行长出面。你说「我昨天的转账出问题了」,他先说「我让后台查一下,大概需要半分钟」然后走到后台找风控专员——先给你安心,再去做重活。
用一段实际对话走一遍,你就能看懂这套架构怎么运转:
用户:「I didn't receive my withdrawal yesterday, what happened?」(我昨天取的钱没到账)
VAD 后 0~1.3s:
Orchestrator 立刻回复:
"I understand you haven't received your withdrawal yesterday.
Let me run a quick check; it may take up to half a minute, please hold on."
VAD 后 1.3s~15s:
Orchestrator 调用 investigate_payment_issue(复杂 Sub-Agent)
→ Sub-Agent 多步推理:
Step 1: 查交易历史 → 找到未到账记录
Step 2: 查交易详情 → 付款已发出
Step 3: 查支付网关 → 网关正常
Step 4: 查银行状态 → 银行 API 挂了
Step 5: 输出根因:银行端故障
VAD 后 15s:
Orchestrator 组织语言回复:
"我查到您的转账已从我们这边成功发出,但收款银行目前系统
在处理延迟,预计 2 小时内恢复,您的钱会自动到账。"
关键设计点:Orchestrator 永远先给即时反馈,再执行任务。用户不会觉得系统在发呆。
1.3 跨语言处理策略
这个项目要支持三种语言:English / Hindi / Hinglish(Hindi + English 混合)。关键设计是大小模型语言分工:
- Orchestrator & 简单 Sub-Agent:需要支持 Hindi/Hinglish,因为直接跟用户对话
- 复杂 Sub-Agent:只需要 English,因为做的是推理和 API 调用
为什么不让大模型也支持多语言?因为 prompt 里适配多语言会让推理难度更高、速度更慢,而且 post-train 大模型支持多语言通常要更多的训练数据、质量还不稳定。纯英文的大模型在推理上往往更强。
实际运转是:用户说 Hindi → 小模型翻成 English 给大模型 → 大模型用 English 推理 → 小模型翻回 Hindi 回给用户。
二、模型选型:五个候选的全面对比
2.1 Orchestrator 选型:为什么是 Qwen2.5-7B
五个候选全测了:
| 模型 | 问题 | 为什么不能用 |
|---|---|---|
| Gemma-2-9B | 对英文倾向性过高,Hindi 敬语不稳定,经常语言切换 | 金融场景混语是刚需 |
| Mistral-7B | 语言漂移严重,需要复杂 prompt 压制 | prompt 越长推理越慢,得不偿失 |
| Llama3-8B | 指令遵从弱,tool call 格式错误率高 | 格式错误 = 工具调用失败 = 用户体验一票否决 |
| BLOOMZ-7B1 | 混合语言能力最强,但翻译腔严重 | few-shot 也压不住翻译腔 |
| Qwen2.5-7B | tool call 时偶尔把字段名翻成 Hindi | 用 prompt 就能有效避免 |
Qwen 的两个关键指标:
- Valid Tool Call Rate 低至 3%:Orchestrator 调用 MCP tools 时需严格按格式输出,Qwen 的指令遵从在我们测试中最好。以 JSON Schema + 约束解码 + 校验修复链保障,格式类错误自动修复 ≤ 2 次。
- 微平均 F1 比 Llama3-8B 高 8%:意图识别是 Orchestrator 的核心,这个差距就是上线和不能上线的差距。
2.2 复杂 Sub-Agent 选型:为什么是 Mixtral-8x22B
| 维度 | Mixtral-8x22B | Llama3-70B |
|---|---|---|
| 复杂诊断多步推理 Exact Match@K | 高 10% | — |
| 未 tune 格式正确率 | >95% | ~80% |
| p95 推理延迟 | 快 20% | — |
| 总推理成本 | 低 30% | — |
Mixtral 的 MoE 架构在复杂推理里表现出色——不同的 expert 负责不同的推理步骤,不会像 Llama3 那样在多步推理中间步骤"跳步"。而且虽然标称 22B×8 experts,但实际激活参数只有 44B+shard,成本比 70B 模型低 30%。
三、SFT 阶段:数据构建和两个清洗 trick
3.1 数据构建流程
数据来源:400+ 小时脱敏客户客服录音 → Whisper 转写 → AI+人工标注
| 标注步骤 | 做什么 |
|---|---|
| Step 1 | Whisper 转写录音为文本 |
| Step 2 | 标注意图类别 |
| Step 3 | 标注应该调用的 MCP tool / Sub-Agent |
| Step 4 | 插入 Orchestrator 初始响应文本 |
额外用闭源大模型生成了 2,000+ 条 Hinglish code-switching 样本做数据增强。最终数据集:训练 6,000 / 验证 700 / 测试 300。
3.2 Trick 1:ASR 错误噪声注入
坑:Whisper 在 Hindi/Hinglish 上准确率约 90%,典型错误如 withdrawal 常被识别成 wish。如果直接用含错数据训练,模型会学到错误模式。
解法:构建了一个"ASR 错误模拟器",分析 Whisper 典型错误类型(口音/同音/混语各 2,000),构建噪声仿真集。在训练数据的用户输入部分注入 10% 的错误。
Trick 的逻辑是:不是去修 ASR,而是让 LLM 见过这些错误。 模型训练时就见过 wish 代表 withdrawal,推理时再遇到就不会被带偏。
效果:Orchestrator 路由 micro-F1 从 81.2% → 90.9%。
3.3 Trick 2:意图标注一致性
坑:同一个 prompt「我的 withdrawal 怎么还没到」可能被标为 withdrawal_status_query(简单查询)或 withdrawal_issue_diagnosis(复杂诊断)。标注不一致让模型困惑。
解法:
- 建立决策树规则库:问"状态"= 简单查询,问"出什么问题/为什么"= 复杂诊断
- 多轮专家审核不一致样本
- 主动学习:用已训练模型预测新数据,找出概率低于 0.7 的样本优先标注
四、GRPO 微调:Orchestrator 的路由准确率 87.1%→98.4%
4.1 为什么需要 GRPO 而不是只做 SFT
SFT 只能教模型「给你一个输入,输出应该是什么」,但 Orchestrator 的任务不是简单的输入→输出。同一个 prompt,可能多个 MCP tool 都合理,需要模型选最优的那个。SFT 教不了「在这些选项中选最好的」。
GRPO 恰好适合:不需要昂贵的人工偏好标注,只需要定义 reward 函数,让模型自己试、自己比较、自己优化。
4.2 Reward 函数设计
这是 GRPO 的灵魂。我设计了一个复合 reward:
R = 0.3 × R_routing + 0.4 × R_fluency + 0.3 × R_latency
| 维度 | 权重 | 怎么算 | 为什么 |
|---|---|---|---|
| 路由准确性 | 0.3 | 模型选的 Sub-Agent 跟标注一致 → +1,否则 → -1 | 这是核心目标,但权重太高会过拟合 |
| 响应自然度 | 0.4 | 闭源大模型打分 0-1,评估初始响应是否自然、友好 | 金融场景客户体感最重要 |
| 延迟惩罚 | 0.3 | response token 低于 50 → +1,50-100 → 0,超过 100 → -1 | 强制 Orchestrator 简洁,超时扣分 |
路由权重不高反而让准确率更高——因为过度追求路由正确会牺牲自然度,用户听到的是"生硬但正确"的回复,体验反而差。
4.3 训练超参数和调参教训
学习率:3e-6(不是常见的 5e-6)
Batch size:32
LoRA rank:32(不是 16)
LoRA alpha:64
Epochs:4
优化器:AdamW
三个关键调参决定:
| 参数 | 初始值 | 现象 | 最终值 | 原因 |
|---|---|---|---|---|
| 学习率 | 5e-6 | 微调初期 Hindi 能力退化,只能说 English | 3e-6 | 训练数据 Hindi 占比不高,高学习率导致灾难性遗忘多语言知识,冻结 embedding 层保持多语言稳定 |
| LoRA rank | 16 | 路由准确率只有 88%,容量不够 | 32 | 增加到 32 拉升到 94%;试过 64 没进一步提升但训练时间 +50%,没必要 |
| Epochs | — | 第 3 个 epoch 后验证 reward 收敛,第 4 个 +1.2% | 4 | 边际收益递减,不做更多 epoch 防止过拟合 |
4.4 最终效果
| 指标 | Base Model | GRPO 后 | 提升 |
|---|---|---|---|
| 路由准确率 | 78% | 94% | +16% |
| 响应自然度 | 3.2/5 | 4.5/5 | +1.3 |
| 平均响应长度 | 85 tokens | 42 tokens | -50% |
| 线上用户满意度 | — | +18% | — |
| 线上响应速度 | — | +32% | — |
五、简单 Sub-Agent 微调:工具调用的两个坑
5.1 坑一:API 参数提取不准确
日期、金额等实体提取频繁出错:
用户:「show me transactions from 1st January to 31st March」
模型应该输出:start_date="2025-01-01", end_date="2025-03-31"
模型经常输出:start_date="January 1", end_date="March 31"(缺少年份+格式错)
解法:在训练工具调用之前,先用 5,000 条 NER 数据做实体识别预训练,让模型先学会提日期、金额、账号。然后结合约束解码 + 参数校验器,参数准确率 85% → 96%。
5.2 坑二:生产日志中的脏数据
生产日志里很多 API 调用缺少必需参数:
{
"api": "get_transaction_history",
"params": {
"start_date": "2025-01-01"
// 缺少 end_date
}
}
不完整的样本会污染训练数据。解法:训练前先跑一遍参数完整性校验,筛掉缺必需参数的样本。额外用闭源大模型补齐 2,000+ 条覆盖边界情况的合成样本(缺失参数、格式错误等)。
六、Insight 提炼
6.1 延迟是语音 Agent 的一票否决项——架构设计优先于模型选择
用户对语音交互的等待容忍度是秒级。不管模型多聪明,超过 2 秒不回应就是失败。所以「先响应后执行」不是锦上添花,是架构必须项。双层 Agent 的核心逻辑不是「大模型做难事小模型做简单事」,而是「用瞬时反馈买推理时间」。
6.2 跨语言策略的核心原则:让模型各司其职
不要妄想一个大模型同时搞定多语言理解和复杂推理。让大模型专注做它最擅长的(英文推理),让经过多语言微调的小模型做语言理解和翻译。这样做的代价很小(小模型翻译开销可忽略),收益很大(大模型不背多语言负担,推理又快又准)。
6.3 训练数据噪声不是坏事——关键是让模型见过噪声
ASR 错误是语音系统的常态。不应该花无限精力去提升 ASR 准确率,而应该在训练数据里注入噪声,让 LLM 学会在噪声下理解意图。效果上,10% 的噪声注入就让路由 micro-F1 从 81.2% 跳到 90.9%,远超继续优化 ASR 的边际收益。
6.4 GRPO reward 设计:复合权重比单一指标更稳
不要只 reward 准确率。对生产系统来说,自然度和延迟跟准确率同等重要。0.3/0.4/0.3 的权重分配是因为:自然度 > 路由准确 > 延迟控制(自然度权重最高,因为语音场景用户感知最直接)。单一维度的 reward 会导致模型在一个指标上过拟合,牺牲用户体验。
6.5 模型选型不要只看 benchmark
五个模型全测的结果是 Qwen2.5-7B 胜出——不是因为它的 benchmark 最高,而是因为它在实际场景的指令遵从和多语言稳定性上最好。Llama3-8B 在通用 benchmark 上分数不低,但在 tool call 格式上频繁出错,这种事情 benchmark 测不出来。