vLLM-Omni 全双工打断(Barge-in)实战:基于 `barge_in_client.py` 的语音对话流程深度解析
2026/9/17 21:35:05 网站建设 项目流程

vLLM-Omni 全双工打断(Barge-in)实战:基于barge_in_client.py的语音对话流程深度解析

【免费下载链接】vllm-omniA framework for efficient model inference with omni-modality models项目地址: https://gitcode.com/GitHub_Trending/vl/vllm-omni

本文基于仓库文档 examples/online_serving/barge_in_client_flow.md 编写。

在真实的全双工语音对话中,打断(barge-in)是衡量系统自然度的关键能力:用户提问后,助手已经开始回答,但用户不等说完就再次开口。模型能否在被"抢话"时得体应对——要么果断掐断当前回答、要么把新问题排在轮次末尾——直接决定了对话体验。vLLM-Omni 仓库中的 barge_in_client.py 正是演示这一场景的可运行示例:它既可以连接远端/v1/realtime?duplex=1WebSocket 服务,也可以借助--inline在进程内驱动DuplexOmni,完整复现"提问 → 中途被打断 → 模型处理重叠 → 回答追问"的全过程,并将每次回答的音频与状态落盘。

读完本文,你将掌握:barge-in 场景的完整时序与两种运行方式、脚本全部命令行参数与输入输出约定、DuplexClient/InlineDuplexClient客户端 API 与模型会话预设(preset)的用法,以及重叠策略(overlap policy)、播放确认(playback ack)等全双工底层机制在源码中的实现依据。

场景概述:一次"日常对话"式的中途打断

barge_in_client.py演示的是一个高度贴近真实生活的对话模式:问一个问题 → 回答中途被"抢话" → 看模型如何应对。脚本对模型完全中立(model-neutral),它不依赖任何特定模型内部实现,而是通过公开的客户端 API 驱动任意全双工(duplex)模型:

  • 默认走网络路径:通过vllm_omni.clients.duplex.DuplexClient连接ws://<host>/v1/realtime?duplex=1
  • 或者使用--inline:通过vllm_omni.clients.inline_duplex.InlineDuplexClient在进程内驱动vllm_omni.entrypoints.duplex_omni.DuplexOmni(无需起服务端,模型直接加载在当前进程)。

两种模式下,对话内容完全一致。完整的高层流程如下(精确的线上事件序列见 barge_in_client.py 的 docstring):

输出结果一目了然:输出目录会明确显示走了哪条分支——被打断掐断的第一个回答保存为response_1_cancelled.wav,完整讲完的保存为response_1_completed.wav,而summary.json逐条记录了每次回答的状态、文本与时长。

运行方式一:连接远端全双工服务

启动服务端

以 MiniCPM-o 4.5 为例,仓库提供了现成的服务端启动脚本 barge_in_serve.sh:

MODEL="${MODEL:-openbmb/MiniCPM-o-4_5}" PORT="${PORT:-8099}" exec vllm-omni serve "$MODEL" \ --omni \ --deploy-config vllm_omni/deploy/minicpmo_4_5.yaml \ --trust-remote-code \ --host 0.0.0.0 --port "$PORT"

要点拆解:

  • --omni启用 vLLM-Omni 的 omni 模式;
  • --deploy-config vllm_omni/deploy/minicpmo_4_5.yaml使用 MiniCPM-o 4.5 的三阶段部署配置(Thinker / Talker / Code2Wav);
  • --trust-remote-code:MiniCPM-o 自带自定义 HF 代码(pipeline),必须信任远端代码才能解析;
  • 该 deploy 配置声明了session_mode: duplex,因此vllm-omni serve会把模型以DuplexOmni方式运行,服务端仅挂载ws://<host>:8099/v1/realtime?duplex=1(别名/v1/duplex)、POST /v1/chat/completions/v1/models/health,其余基于轮次(turn-based)的 HTTP 路由(speech、batch、embeddings、video 等)在 duplex 模型上不提供服务。

关于会话容量的关键事实:vllm_omni/deploy/minicpmo_4_5.yamlduplex_session.max_sessions: 4(与 Stage 0/1 的max_num_seqs对齐),每个请求在其整个生命周期内都占用一个准入名额,因此该上限同时约束 WebSocket 会话与 HTTP 并发;超出后请求以 HTTP 503 被拒绝(WebSocket 侧对应resource_exhausted错误,可重试)。会话的其他运行时默认值包括:空闲 TTL 300 秒、断线宽限(disconnect grace)30 秒、每个会话待处理输入上限 16 MiB。仓库还提供 2-GPU、3-GPU、8×4090 等多卡变体配置(vllm_omni/deploy/minicpmo_4_5_2gpu.yaml 等),可按硬件替换。

运行客户端

服务端就绪后,执行(也可直接cd examples/online_serving后运行):

python examples/online_serving/barge_in_client.py \ --url ws://127.0.0.1:8099/v1/realtime \ --model openbmb/MiniCPM-o-4_5 \ --ref-audio /path/to/reference_voice.wav \ --question-wav question_16k.wav \ --interrupt-wav follow_up_16k.wav \ --output-dir ./duplex_out

脚本会依次完成握手(GREET)、流式上传问题音频并提交轮次(ASK)、等待模型开口、监听约 2 秒后在回答播放中流式上传打断音频并提交(INTERRUPT)、等待针对打断的新回答结束(ANSWER AGAIN)、回报播放进度并优雅关闭会话(HANG UP)。

运行方式二:--inline进程内模式(免服务端)

不需要独立服务端时,使用--inline让模型直接加载在当前进程内:

python barge_in_client.py --inline \ --model /path/to/MiniCPM-o-4_5 \ --ref-audio /path/to/reference_voice.wav \ --question-wav question_16k.wav \ --interrupt-wav follow_up_16k.wav
  • --model此时传本地模型路径而非服务端模型名;
  • --deploy-config可选,默认使用模型自带的 duplex 部署 profile;
  • 该路径下,脚本会惰性导入InlineDuplexClientDuplexOmni,在当前进程创建引擎并加载模型(这正是 WebSocket 路径所不需要的重型依赖),并以trust_remote_code=True解析带自定义 HF 代码的模型 pipeline。

从源码实现看(barge_in_client.py 的_make_client),--inline构造的DuplexOmni(**omni_kwargs)被包装进InlineDuplexClient,且客户端不持有引擎——脚本退出时会通过client.owned_omni.shutdown()关闭进程内引擎,避免遗留 GPU 资源。由于内联客户端没有传输层,它天然不具备重连、会话恢复、心跳与事件确认机制,这是与 WebSocket 客户端的本质差异。

命令行参数全览

脚本通过argparse暴露以下参数(默认值与含义均来自源码 barge_in_client.py 的main()):

参数默认值说明
--urlws://127.0.0.1:8099/v1/realtimeWebSocket 服务地址(--inline时忽略)
--modelopenbmb/MiniCPM-o-4_5服务端模型名,或--inline时的本地模型路径
--inline关闭进程内运行:DuplexOmni+InlineDuplexClient
--deploy-configNone--inline使用的 deploy YAML(默认取模型自带 profile)
--presetminicpmo_4_5会话预设,可选minicpmo_4_5/personaplex/none
--ref-audio参考人声 WAV(MiniCPM-o preset 必填,缺失时会话以ref_audio_required被拒)
--question-wav必填问题音频,mono 16 kHz PCM16
--interrupt-wav必填在回答中途说出的追问音频,mono 16 kHz PCM16
--output-dir./duplex_out输出目录(自动创建)
--chunk-ms依 presetappend 分块大小;默认按 preset 对齐模型单元(minicpmo_4_5为 100 ms,personaplex为 80 ms,none为 100 ms)
--listen-s2.0开始打断前"聆听"的秒数
--timeout-s120.0握手、等待事件等各阶段的超时上限

输入音频格式约束--question-wav--interrupt-wav必须是mono 16 kHz PCM16。脚本内部read_pcm16_wav会校验单声道与非压缩 PCM,随后_convert_to_session_format依据会话协商的输入格式(config.input_audio)做转换:当 preset 的会话采用不同采集格式(如 PersonaPlex preset 为 24 kHz float32)时,音频会被重采样并转码后再流式上传。MiniCPM-o 的会话输入为 16 kHzpcm16、输出为 24 kHzpcm16

输出产物与判定逻辑

脚本结束时会在--output-dir下生成三类产物:

  1. 每个回答一个 WAV:命名规则为response_<序号>_<status>.wav,其中<status>只有两种取值:
    • cancelled—— 该回答被 barge-in 掐断(半途而废);
    • completed—— 该回答完整讲完。
  2. summary.json:逐条记录每次回答的response_idstatusaudio_s(音频时长秒数)、text(累积的转写文本)与wav文件名;
  3. events.jsonl:本次运行收到的全部服务端事件的原始 JSON 逐行落盘,便于事后排查线上事件序列。

判定逻辑在源码_fold_responses中:脚本监听三类转写增量事件(response.output_audio_transcript.deltaresponse.output_text.deltaresponse.text.delta)累积文本;response.done事件携带status,若为cancelled则标记为 cancelled,否则标记为 completed;此外audio.cancelled事件会把仍在in_progress的回答强制标记为 cancelled。这与服务端response.donestatus_details.reason(如barge_in)语义一致——见 docs/serving/realtime_duplex_api.md 的事件目录。

脚本 stderr 最后会打印形如N responses (M interrupted); outputs in <dir>的汇总,其中 M 即被打断(cancelled)的回答数,可直接用于验证本次打断是否真的触发了 barge-in 分支。

底层机制:模型自主决定 vs VAD、重叠策略与播放确认

barge_in_client.py演示的会话走的是model-native 通道:不启用任何 VAD(turn_detection为 null),模型自己决定何时聆听、何时开口。脚本提交问题轮次时调用await client.commit(create_response=False),即把听/说决策完全交给模型本身。在 model-native 通道中,音频在 commit 之前就已经边流边喂给模型,因此模型可能在没有任何 commit 的情况下就开始回答或发出聆听决策。

当用户在回答中插入追问时,决定权在服务端的重叠策略(overlap policy)

  • MiniCPM-o 4.5 preset 的默认策略为overlap_policy="listen_only"(见 vllm_omni/clients/minicpmo_4_5.py 的create_duplex_session_config);
  • 服务端根据重叠语音的量级做出两类决策,并以overlap.decision事件上报:
    • 打断内容足够实质(如重叠时长超过阈值)→barge-in:正在进行的回答被掐断(response.done状态为cancelledstatus_details.reasonbarge_in),追问随后被回答;
    • 只是短暂插话 → 推迟到轮次末尾再处理:先讲完第一个回答,再回应追问。

以 MiniCPM-o 4.5 为例,session.created返回的会话对象中可见重叠相关默认阈值:overlap_short_ack_ms: 700overlap_barge_in_ms: 1200overlap_silence_rms: 0.003。能力协商(capabilities)中supports_barge_in: true表示该模型支持打断(PersonaPlex 与 Nemotron VoiceChat 当前不支持,见 docs/serving/realtime_duplex_api.md 的能力表)。

播放确认(playback ack)是保证打断后历史真实性的关键:MiniCPM-o preset 的playback_commit_policy="ack_only"意味着助手的回答只有被客户端确认播放到的部分才进入会话历史。脚本在 HANG UP 阶段调用acknowledge_collected_playback(client, collector)回报全部收集到的播放进度,使历史与真实播放内容对齐。若客户端不回报,服务端历史最多只会按commit_all_on_done的默认保守口径提交。这一设计正是"打断后模型知道自己说到哪、没说什么"的基础。

客户端 API 与模型预设

演示脚本建立在 vllm_omni/clients/duplex.py 之上,这是一个仅依赖标准库、pybase64websockets的客户端侧包,服务端代码从不导入它。核心组成:

  • DuplexClient:异步上下文管理器,进入时连接并完成session.update握手、等待session.created;退出时发送session.close并等待session.closed。支持ReconnectPolicy(默认 5 次尝试、0.25–4 秒抖动退避)与 30 秒心跳租约;
  • stream_pcm(pcm, chunk_ms=...):将 PCM 切成chunk_ms毫秒的 append 并按实时节奏发送(realtime=False则全速发送);
  • commit(create_response=False):把缓冲语音封装成用户条目并结束一个轮次,轮次只能通过 commit 结束;
  • responses():把事件流按回答解复用为ResponseHandle(含decisiontranscriptplayed_msaudio()wait()等);
  • EventCollector:累积事件用于断言与延迟统计(timing_summary可报告首字/首音频延迟、音频节奏,以及请求extra_body.return_stage_metrics=True时的 Stage 0 token 指标ttft_ms/tpot_ms/itls_ms);
  • ack_playback(played_ms, ...):报告累计播放位置,是ack_only策略下保持历史诚实的入口。

会话形态由SessionConfig决定,模型特有参数放在其extra_body中,因此客户端保持模型中立,每个模型各提供一个 preset:

Preset输入 / 输出音频设定内容
vllm_omni.clients.minicpmo_4_5.create_duplex_session_config(ref_audio=...)16 kHzpcm16/ 24 kHzpcm16force_listen_count=0overlap_policy="listen_only"playback_commit_policy="ack_only"ref_audio为助手音色片段
vllm_omni.clients.personaplex.create_duplex_session_config(voice="NATF2.pt", persona="")24 kHzpcm_f32le/ 24 kHzpcm16内置.pt音色提示与以instructions传入的人设
SessionConfig(...)16 kHzpcm16/ 24 kHzpcm16模型中立默认值;自行传extra_bodyturn_detectionoverlap_policyplayback_commit_policyinstructionsvoicetemperature

关键词覆盖会替换 preset 中对应SessionConfig字段,例如create_duplex_session_config(ref_audio=..., temperature=0.6)ref_audio通过audio_data_url()编码为data:audio/wav;base64,...数据 URL 后随会话下发。

与全双工运行时架构的关系

barge_in_client.py只是全双工能力的"前台"示例,其底层架构与完整线上协议分别记录在两份配套文档中:

  • 运行时架构:docs/design/fullduplex.md 描述 MiniCPM-o 4.5 全双工运行时(DuplexOmni、会话运行器、重叠与播放确认的规范性契约);
  • 线上协议与客户端 API:docs/serving/realtime_duplex_api.md 完整规定了/v1/realtime?duplex=1的线协议(21 个客户端→服务端事件、42 个服务端→客户端事件、三类 OpenAI Realtime 兼容层级、每个事件的消息示例),DuplexClient讲的正是这套契约;InlineDuplexClient则把DuplexOmni包装在同样的DuplexClientAPI 之后,实现"同一份应用代码,进程内或跨网络两用"。

写自己的全双工客户端时,建议先跑通本文演示脚本、检查events.jsonl理解事件序列,再对照 realtime_duplex_api.md 的事件目录逐类实现:模型自主听说的会话关掉 VAD(turn_detection: null),需要服务器 VAD 打断的会话则设置turn_detection.server_vad并配合overlap_policy="barge_in_on_speech"(注意interrupt_response=false会被拒绝);工具调用、摄像机帧、会话恢复等能力均以session.created返回的capabilities标志为准,按标志分支而不是按模型名硬编码。

【免费下载链接】vllm-omniA framework for efficient model inference with omni-modality models项目地址: https://gitcode.com/GitHub_Trending/vl/vllm-omni

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询