公司动态
语音函数调用全解析:让语音大模型直接产出函数调用
Spoken Function Calling 是最近在语音大模型方向里越来越值得关注的一个话题。它的目标很直接让大型音频语言模型Large Audio Language ModelLALM直接根据用户的语音输入判断意图并产出结构化的函数调用结果而不是先转写文字、再做文本意图分析、最后决定调用哪个工具。这个方向解决的是语音助手、智能客服、车载控制、智能家居这类场景里最实际的问题怎么让“听、懂、动”在同一个模型里完成。这个视角对做语音 Agent 的人来说尤其值得看。传统语音理解把“听懂语音”和“决定动作”分成了两个阶段中间隔着一个文本转写层。语音函数调用则把两者打通了模型不一定要输出完整文字而是可以直接输出函数名、参数和调用逻辑。从研究角度看它是 Spoken Language UnderstandingSLU任务的延展从工程角度看它是把语音大模型接入业务系统的一种标准接口。下面按实际做实验时的顺序拆一遍先理解概念再准备数据再选模型和跑通推理接着做微调和评测最后聊常见坑和落地边界。1. 先理解 Spoken Function Calling它是 SLU 任务的延伸不是 ASR 后处理1.1 传统思路为什么不够用传统的语音助手链路大家都很熟悉ASR 先转文字文本送入 NLU 或 LLMLLM 判断意图和参数再触发函数调用。这套链路最大的问题是错误传染ASR 错一个词后面的意图、槽位、函数参数都会跟着错。尤其遇到“开一下”“那个”“还是上次那家”这种话文本里信息不完整模型只能靠猜而语音里其实有很多线索是文字表达不出来的。另一个问题是语音信息被丢弃。重音、停顿、语气、语速、音高变化都会影响人的真实意图。比如用户说“把空调调到二十六度”和“调到十六度”如果没有上下文光看文字也可能混淆但真人说话时重音和语境往往能把信息补全。纯文本模型拿到的只有字符序列很难把这些信息用起来。不是说一定不能用文本做只是在这种场景里端到端音频输入拥有更大的信息量。从系统复杂度看传统链路也不占便宜。ASR、NLU、对话管理、工具调用四个模块分别维护任何一个模块升级都可能影响下游。遇到多语言、方言、噪声场景ASR 这块就要单独调很久。语音函数调用把一部分工作整合进大模型模块边界简化了维护成本也随之变化。1.2 语音大模型里的 SLU 和函数调用是什么关系SLU 的任务是从语音直接提取语义结构。传统 SLU 输出意图和槽位比如“播放音乐”加“歌曲名晴天”。语音函数调用可以看成在 SLU 之上再加一层输出协议把语义结构表达成函数调用包括函数名、参数、嵌套对象、可选默认值等。在 Large Audio Language Model 的架构里语音和文本通常会被映射到同一个表示空间。模型既能听音频也能读文本再在这个联合空间中做推理。函数调用如果已经在预训练文本模型里有了基础那语音侧只需要把音频表示和“调用工具”这个行为对齐。实现上不同模型细节差异很大有的用音频编码器加投影层接 LLM有的直接把音频 token 和文本 token 拼接。不管哪种共同点是函数调用的核心能力来自语言模型部分语音部分负责把音频语义可靠地送进来。这也解释了为什么训练语音函数调用时不能只准备音频数据函数 schema、系统提示词、输出格式这些文本侧的东西同样重要。这个理解对后续实验安排很关键。如果你选的基座模型本身不具备文本函数调用能力那语音函数调用几乎要从零学如果基座模型已经能在文本侧按 JSON 调用工具那训练重点就变成“音频输入对应什么函数调用”难度会低很多。2. 数据层面语音函数调用最核心的准备工作2.1 数据配方是决定成败的地方做过几轮语音模型微调之后我的感受是模型结构差异不一定带来巨大差距数据配方对结果的影响往往更大。语音函数调用尤其明显因为模型要同时学会三件事听懂语音、理解工具语义、按固定格式输出。少一类数据训练出来就是半个模型。建议至少准备这么几类数据语音到函数调用的直接配对数据。这是核心一条样本通常包含音频、函数定义、期望输出。多轮语音对话数据。用户说完第一句中途可能补充信息或改变参数比如“把空调打开”“不是客厅是卧室”。没有这类数据模型就只会处理单轮。通用语音理解数据。比如语音问答、描述、摘要用来维持模型原有的听力和语言能力防止微调后只会做工具调用。拒绝调用和自然回复数据。用户说“今天天气怎么样”“你叫什么名字”模型不应该强行调用工具应该输出自然回复或 no_call。数据比例没有固定公式。按我的经验核心函数调用数据可以占总量的三到四成多轮和通用数据再多补一些。如果任务复杂、函数数量多核心数据还要增加。最忌讳的是只有一批函数调用数据训练完模型能输出调用结果但用户随便聊一句就会误触发。2.2 如何构造语音函数调用数据样例先给一个训练样本的通用结构作为参考{ audio: samples/turn_on_light.wav, functions: [ { name: control_light, description: 控制屋内灯光, parameters: { type: object, properties: { position: {type: string, enum: [客厅, 卧室, 厨房]}, action: {type: string, enum: [开, 关]} } } } ], conversation: [ {role: user, content: audio 帮我把客厅的灯打开} ], answer: {name: control_light, arguments: {position: 客厅, action: 开}} }构造时要注意几点。第一音频不一定要从真实设备录TTS 可以快速生成大量初始数据但口音、语速、噪声覆盖不够后期要补真人和真实环境录音。第二同一个文本意图要录多遍说话方式不同、停顿位置不同模型才不容易过拟合到某种发音模式。第三音频和文本要严格对齐不要把“客厅的灯”听成其他错误文本放进去除非你故意在做鲁棒性数据。我一般会先用 50 到 100 条样本做小规模验证确认数据格式、训练脚本、评测逻辑都能跑通再扩展到几千条。不要一上来就生成几万条一旦 schema 设计错了返工成本很高。2.3 schema 和对话结构应该如何设计函数定义尽量跟文本 Agent 保持一致。如果项目里本来就有标准的 function/tool schema语音侧可以直接复用不用为语音单独造一套。这样上层业务系统不用关心输入是语音还是文本只需要解析同一个 JSON 输出。schema 设计有几个原则可以记住函数数量控制在几十个以内太多会让函数名选择变得困难。函数名要有区分度避免get_user_info和get_user_profile这种近乎重复的名字。参数尽量用枚举值让模型更容易收敛。比如“开/关”“客厅/卧室/厨房”不要开放自由字符串。嵌套参数要克制。模型输出 JSON 时结构越深越容易出错能用平铺结构解决就平铺。多轮对话的 schema 也要提前想好。用户在第一轮调用函数之后第二轮可能继续补充参数。这时模型上下文里要带上函数返回结果或“已经调用”的状态否则模型不知道前面做了什么。这里常见错误是只给第一轮音频和函数定义第二轮没有历史调用结果模型就瞎猜。3. 模型与运行环境怎么把方案落到可复现的实验里3.1 模型选型思路语音大模型选型先看架构再看规模。目前能直接跑的开源模型主要有几类一类是音频和文本联合预训练的通用音频模型输入可以是语音、声音事件、音乐输出是文本另一类是面向语音助手的语音对话模型通常支持多轮语音对话部分模型还支持流式输入。选型不能只看宣传要看实际接口是否符合需求。几个关键判断点是否原生支持交错音频和文本输入。如果只在开头支持一段音频后续多轮补充语音就无法处理。是否支持系统提示词和工具定义。函数调用需要把 schema 注入上下文不支持会很难做。输出 token 成本。函数调用结果虽然短但模型要对上下文里所有函数定义做处理函数多时开销不小。是否支持结构化输出或解码约束。能约束 JSON 格式会显著降低解析失败率。显存方面如果只是做推理实验16G 到 24G 比较稳妥如果要微调哪怕是 LoRA也建议 24G 以上。具体数值会因为模型参数量和序列长度变化先按这个水平准备跑起来后再看。3.2 运行环境和依赖准备环境准备最容易踩坑。我一般会固定 Python 版本单独建虚拟环境不跟系统 Python 混用。下面这个命令只是最小示例实际依赖以模型仓库要求为准conda create -n spokencall python3.10 -y conda activate spokencall pip install torch torchaudio transformers accelerate peft librosa音频数据要提前统一格式。采样率、声道、编码方式不统一模型输入会异常但往往不报错只是效果变差。我的习惯是先把所有音频统一到一个采样率常见是 16kHz 或 44.1kHz再统一成单声道长度过长就做切片或分段。磁盘空间也要留够。原始音频、提取后的特征、checkpoint 都不小尤其是数据集里音频多时几百 GB 很常见。如果训练日志显示数据加载很慢先看磁盘 IO 和缓存目录不要直接怀疑 GPU。3.3 最小推理流程先跑通一条音频再谈批量和部署。下面是一个通用伪代码用来确认模型能加载、能输出函数调用结果import torch import torchaudio from transformers import AutoProcessor, AutoModelForAudioLanguageModel model_path your-model-ckpt model AutoModelForAudioLanguageModel.from_pretrained(model_path) processor AutoProcessor.from_pretrained(model_path) audio, sr torchaudio.load(test_light.wav) # 确保采样率与模型要求一致必要时重采样 system_prompt 你是语音助手。请根据用户语音输出 JSON 函数调用。函数定义... inputs processor(audioaudio, textsystem_prompt, return_tensorspt) output model.generate(**inputs, max_new_tokens128) print(processor.decode(output[0], skip_special_tokensTrue))这一步要看的不是函数调用多准确而是三个现象能不能跑通、输出是不是可解析的 JSON、卡住或报错时日志在哪里。如果输出是自然语言但没调用函数多半是提示词里函数定义不够明确或模型没有工具调用能力。如果输出是 JSON 但参数错再进入微调环节调数据。注意第一次跑推理时不要直接接入生产先确认一条样例能输出可解析的函数调用结果再看日志和资源占用。4. 训练和微调从通用音频理解到能稳定输出函数调用4.1 训练策略先小后大先单函数再多轮拿到数据后不建议直接全量微调。先用 LoRA 这类参数高效微调方式跑一个小实验好处是显存占用低、训练快、容易回滚。调整参数和样本格式的成本也会小很多。训练顺序可以这样排先让模型学会单轮、单函数调用。目标是格式正确不追求覆盖复杂场景。加入多函数选择。多个函数定义同时存在让模型学会选对函数名。加入多轮补充和修改。用户第二轮改变参数模型要能正确覆盖。加入拒绝调用和自然回复。这是防止误触发的最关键一步。每一步都跑固定评测集确认没有明显回退再进入下一步。我见过不少同学跳过第一步直接拿复杂多轮数据训练最后模型输出格式混乱还不好定位是数据还是训练策略的问题。LoRA 训练时要注意保持通用语音能力。可以在训练数据里混入一部分通用语音指令数据比例不要太低。模型如果只会调用工具用户平时问一句“这句话什么意思”它都不会正常回答就没法落地。4.2 输出格式约束让模型稳定输出可解析结果函数调用结果要能被下游解析格式不稳定会直接导致调用失败。训练阶段就要强制样本统一。如果是 JSON 输出统一用{name: ..., arguments: {...}}这种结构不要一会儿加说明文字一会儿换字段名。系统提示词里要把函数定义清楚写出来必要时把“如果不需要调用请直接回复”也写进去你是语音助手。根据用户语音判断是否调用工具。 工具列表... 如果需要调用只输出 JSON{name: 函数名, arguments: {参数名: 值}} 如果不需要调用直接输出自然语言回复。推理阶段可以配合结构化生成工具限制输出只能走 JSON 格式。此时模型就不能自由输出自然语言所以拒绝调用场景要单独处理比如模型先输出no_call标记再走另一条自然语言分支。不要过度依赖后处理硬凑 JSON。后处理能解决一部分解析问题但模型本身没有学会格式救不回来。数据、提示词、解码约束这三层都做好成功率才会稳定。4.3 评测指标和验证方法语音函数调用的评测不能只看“模型听懂了吗”。我会拆成几个指标每个指标都指向一个可排查的方向评测指标计算方式低分时优先排查函数名正确率目标函数名命中比例函数定义区分度、意图理解数据参数抽取准确率每个参数是否与预期一致参数枚举设计、上下文信息JSON 可解析率输出能被解析的比例训练数据格式、解码约束端到端任务成功率执行结果是否满足用户意图整体链路、确认机制误调用率无需调用却触发调用的比例拒绝样本数量、阈值设计多轮修改成功率第二轮新参数是否覆盖旧参数历史调用结果是否进入上下文固定评测集至少准备 100 条以上覆盖不同说话人、不同口音、不同噪声条件。每次训练后跑一遍记录指标变化。不要只看总量平均分要按类别看坏 case。模型在标准普通话上准确率很高在方言和噪声下掉点严重这是非常常见的情况需要单独补数据。5. 常见问题与排查格式错、真实环境掉点、延迟并发5.1 语音能听懂但函数调用格式错了这个现象经常出现模型把用户意图理解对了但输出不是标准 JSON或者 JSON 里多了解释文字下游解析失败。原因通常在三个地方。一是训练数据格式不干净。数据里混了“你是要打开客厅的灯吗”这样的解析过程模型学着学着就把思考过程也输出出来了。检查训练样本只保留最终输出格式。二是提示词没有把函数定义给够。模型不知道有哪些函数可用就只能猜。检查系统提示词里是否包含完整工具列表和输出要求。三是推理时缺少解码约束。如果模型本身具备工具调用能力可以用结构化输出限制输出必须是 JSON。这样至少能解决格式可解析的问题。排查顺序建议先看训练样本再看提示词最后看解码约束。不要一步跳到调模型权重。注意出现解析失败时先查训练数据里是不是混入了思考过程或解释文字再动模型。5.2 真实环境噪声、口音、多轮语境导致失败训练集效果好一到真实环境就掉点这是语音模型的常态。真实场景里有回声、背景音乐、多人说话、突然打断还有各种口音和语速。模型在干净录音上听清的内容在真实环境里不一定听清。处理思路分成两类。一类是数据侧增强把噪声、混响、变声加入训练集。另一类是系统侧兜底比如对关键参数增加确认用户说“打开”系统先回“确定打开客厅的灯吗”确认后再调用。任务越重要越建议加确认环节。多轮语境下最常见的问题不是听不懂而是模型忘了之前的函数调用状态。用户先说“帮我查账户余额”模型调用查余额函数接着说“再查一下昨天消费记录”模型如果只看新音频不知道账户是哪个就取不到参数。解决方法是把前一轮的函数调用结果拼进上下文让模型基于上一轮状态决策。5.3 延迟和并发问题语音助手对延迟要求很高。端到端语音函数调用比文本多一个音频编码阶段自回归生成文本 token 的时间也不短。单条推理还能接受并发一上来GPU 显存和批处理就是个坎。排查延迟先分开测三个部分音频编码时间、提示词编码时间、生成 token 时间。哪一段高就处理哪一段。音频编码高可以换更轻量的编码器或降低输入采样率生成 token 高可以限制输出长度减少上下文里的函数定义数量显存不够可以量化模型或减小 batch。生产环境不一定每轮都让大模型跑完整函数调用。可以先用唤醒词、简单规则或小模型做粗过滤把明显不需要调用工具的请求拦下来再把可能的调用请求送进大模型。这样能显著降低整体负载。6. 落地建议和边界哪些场景适合端到端哪些需要保留 ASR 兜底6.1 哪些场景更适合端到端语音函数调用从经验看工具数量少、参数枚举清晰、错误可纠正的场景最适合。比如智能家居控制动作只有开/关、设备位置固定车载娱乐播放某个歌手的歌会议助手创建日程、发送提醒。这些场景函数定义稳定用户语音相对简短即使误调用也可以通过确认或撤销挽回。还有一类场景适合先试点内部工具、低风险动作。例如公司内部的设备查询、会议室预约、待办添加。风险低出错不造成业务损失方便收集真实语音数据再逐步扩大功能范围。6.2 哪些场景建议保留 ASR 兜底金融、医疗、法律这类对准确率要求极高的场景不建议把唯一链路押在端到端模型上。领域里有大量专业名词和人名语音模型就算总体准确率高个别关键参数错了后果比流程慢更严重。可以做成双路结构一路是语音函数调用模型直接出结果另一路是 ASR 转写文本后用标准函数调用做判断。两边结果一致时直接执行不一致时转人工确认或让用户重说。这样既享受端到端的便利又保留文本链路的可审计性。链路优点缺点适合场景纯端到端语音函数调用延迟低信息保留好可解释性弱误调用成本高低风险、固定工具集ASR 文本函数调用链路成熟可审计错误传导延迟更高专业名词多、高准确率场景双路融合稳定性高实现复杂、资源开销大金融、医疗等关键场景双路设计也解决了一个现实问题语音模型推理偶尔不稳定时系统仍然有降级路径。不要把所有功能压在一个模型上。6.3 下一步可以优化的方向如果手里已经有可用的语音函数调用 Demo接下来值得关注几个方向。第一是流式输入和增量调用用户还没说完就能判断是否需要调用工具减少等待。第二是工具返回结果的语音反馈模型调用函数后把结果再转成语音或直接生成语音回复形成完整闭环。第三是多模态上下文比如屏幕截图、摄像头画面一起参与决策用户只说“把那个关掉”模型结合屏幕内容才能知道“那个”是哪个。评测方面也需要持续迭代。固定一个简单评测集只能说明模型没退化不能说明真实场景好。尽量收集真实用户语音按意图类型、口音、噪声、时长几个维度分层评测每次调整都记录下来。长期看数据质量比一次训练技巧更能决定这个系统能不能用。我个人更建议先从小范围场景和固定工具集开始验证不急着把全部业务接进来。语音函数调用不是“把 ASR 换成音频模型”这么简单真正决定成败的是数据配方、输出格式约束、评测体系和兜底机制。等这几个部分都能稳定复现再慢慢扩大工具范围整个系统的可控性会好很多。