Spoken Function Calling 是最近在语音大模型方向里越来越值得关注的一个话题。它的目标很直接:让大型音频语言模型(Large Audio Language Model,LALM)直接根据用户的语音输入判断意图,并产出结构化的函数调用结果,而不是先转写文字、再做文本意图分析、最后决定调用哪个工具。这个方向解决的是语音助手、智能客服、车载控制、智能家居这类场景里最实际的问题:怎么让“听、懂、动”在同一个模型里完成。
这个视角对做语音 Agent 的人来说尤其值得看。传统语音理解把“听懂语音”和“决定动作”分成了两个阶段,中间隔着一个文本转写层。语音函数调用则把两者打通了,模型不一定要输出完整文字,而是可以直接输出函数名、参数和调用逻辑。从研究角度看,它是 Spoken Language Understanding(SLU)任务的延展;从工程角度看,它是把语音大模型接入业务系统的一种标准接口。下面按实际做实验时的顺序拆一遍:先理解概念,再准备数据,再选模型和跑通推理,接着做微调和评测,最后聊常见坑和落地边界。
1. 先理解 Spoken Function Calling:它是 SLU 任务的延伸,不是 ASR 后处理
1.1 传统思路为什么不够用
传统的语音助手链路大家都很熟悉:ASR 先转文字,文本送入 NLU 或 LLM,LLM 判断意图和参数,再触发函数调用。这套链路最大的问题是错误传染: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 python=3.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(audio=audio, text=system_prompt, return_tensors="pt") output = model.generate(**inputs, max_new_tokens=128) print(processor.decode(output[0], skip_special_tokens=True))这一步要看的不是函数调用多准确,而是三个现象:能不能跑通、输出是不是可解析的 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 换成音频模型”这么简单,真正决定成败的是数据配方、输出格式约束、评测体系和兜底机制。等这几个部分都能稳定复现,再慢慢扩大工具范围,整个系统的可控性会好很多。