edit | blame | history | raw

Combrabo Voice Runtime Helper

这是 lmrobot-app Combrabo Voice 一阶段的最小 external runtime helper。它的长期定位是 LiveKit media worker:连接房间、发布 bot 音轨、订阅用户音轨、搬运 / 观测 PCM、输出脱敏诊断。

当前职责分两层:

一阶段固定主动问候职责有 4 件事:

  1. 读取 CombraboVoiceRuntimeServiceImpl 注入的 CV_* 环境变量;
  2. 使用 bot token 连接 local LiveKit room;
  3. 发布 bot 本地音轨;
  4. 播放一段固定问候音频,并保持连接直到 calls/end 触发 stop。

2026-06-24 起进入 helper 下一阶段入口:默认开启用户上行音频观测,先做远端用户音轨订阅和音频帧摘要日志,不做 ASR/LLM/TTS。

它**不承担**鉴权、数据库、订单、旧 TRTC、角色 / 提示词、正式 ASR/LLM/TTS 编排、消息写入、计费或 diagnostics 聚合。上述业务能力继续由 lmrobot-app Java 后端复用现有体系承接。

本定位已按 cb-sdk 真实链路校准:服务端 worker 负责 LiveKit 用户音频输入与 bot 音频输出,后端 speech-runtime 负责 ASR / Agent / TTS / turn 状态。lmrobot 当前 helper 只对齐 media worker / rtc 边界,不新建第二套业务后端。

本机构建

helper 编译固定分为“开发调试线”和“部署验证线”。详细口径见:

doc/task/202606/0615-nativesdk-combrabo-voice-migration/29-helper编译与验证线路说明.md

本机开发调试优先复用已下载的 LiveKit WebRTC 预编译缓存:

export LK_CUSTOM_WEBRTC="$HOME/.cache/combrabo/livekit-webrtc/mac-arm64-release-webrtc-51ef663"

然后执行:

cargo fmt --manifest-path tools/combrabo-voice-runtime-helper/Cargo.toml --check
cargo check --manifest-path tools/combrabo-voice-runtime-helper/Cargo.toml
cargo test --manifest-path tools/combrabo-voice-runtime-helper/Cargo.toml

LK_CUSTOM_WEBRTC 指向的是 macOS host 开发调试缓存,不能直接用于 Linux Docker 镜像构建。

cargo build --manifest-path tools/combrabo-voice-runtime-helper/Cargo.toml --release

helper 目录内带 .cargo/config.toml,本机构建建议通过 run-local.sh 或进入 helper 目录执行,确保使用同一套 registry / retry 配置。

Turn stream 快速校验

TTS streaming 相关改动优先跑一条不依赖 LiveKit / WebRTC native 编译链路的快速线:

node tools/validate-turn-stream-fixture.mjs fixtures/turn-stream-happy.ndjson
node tools/validate-turn-stream-fixture.mjs fixtures/turn-stream-mp3-chunks.ndjson

这条快速线只校验 NDJSON contract、事件顺序和 pcm_s16le / mp3 chunk 基本约束,用于提前发现 replyPlaybackModereply_statereply_audio_chunkturn_completed 等字段破坏。它不能替代 Docker 镜像构建、真实 LiveKit smoke 或 iPhone 真机验收。

本机运行

推荐通过包装脚本启动:

./tools/combrabo-voice-runtime-helper/run-local.sh

如果本地还没有 release 二进制,脚本会先执行一次构建。

开发调试时建议显式带上本机 WebRTC 缓存:

LK_CUSTOM_WEBRTC="$HOME/.cache/combrabo/livekit-webrtc/mac-arm64-release-webrtc-51ef663" \
COMBRABO_VOICE_RUNTIME_HELPER_MODE=host \
./tools/combrabo-voice-runtime-helper/run-local.sh --prepare

Service 模式

lmrobot-app 线上形态不再适合每次通话直接用命令行拉起 helper。当前 helper 支持 service 外壳:

CV_HELPER_SERVICE_ENABLED=true \
CV_HELPER_SERVICE_BIND=127.0.0.1:18080 \
CV_HELPER_AUTH_TOKEN=local-helper-token \
CV_RUNTIME_TURN_BRIDGE_TOKEN=local-turn-bridge-token \
./target/release/combrabo-voice-runtime-helper

service 模式下,helper 只暴露控制面接口:

  • GET /health
  • GET /internal/combrabo-voice/health
  • POST /internal/combrabo-voice/sessions/start
  • GET /internal/combrabo-voice/sessions/{callId}
  • POST /internal/combrabo-voice/sessions/{callId}/stop

sessions/start 收到 Java 传入的 LiveKit bot 入房材料后,会拉起现有 worker 子进程承接媒体链路。helper service 本身不做 ASR / LLM / TTS / 消息 / 计费,也不持久化业务数据。

鉴权口径:

  • Java 调 helper 控制面使用 Authorization: Bearer {helperAuthToken}
  • authProfile 只是非敏感 alias,首版允许 default / local-dev / dev
  • helper 根据自身部署配置读取同名 profile 的 turnBridgeToken,再用于调用 Java internal turn bridge。
  • token 只能来自本机私有 .env、Jenkins credentials 或服务器 ENC 配置,不写入 Git。

Docker service mode 本地启动时,run-local.sh 会把 CV_HELPER_SERVICE_BIND=127.0.0.1:18080
映射为容器内 0.0.0.0:18080 并发布到宿主 127.0.0.1:18080。因此本机探活使用:

CV_HELPER_SERVICE_ENABLED=true \
CV_HELPER_SERVICE_BIND=127.0.0.1:18080 \
CV_HELPER_AUTH_TOKEN=local-helper-token \
CV_RUNTIME_TURN_BRIDGE_TOKEN=local-turn-bridge-token \
COMBRABO_VOICE_RUNTIME_HELPER_MODE=docker \
./run-local.sh

curl http://127.0.0.1:18080/health

sessions/start 会等待 worker 回报真实 readiness:worker 在 LiveKit connect 后输出
bot_participant_joined,在 bot audio track 发布后输出 bot_track_ready。service 捕获这些
结构化事件后才返回 STARTED + botTrackReady=true;如果 worker 失败或超时,则返回结构化
RUNTIME_START_FAILED / RUNTIME_START_TIMEOUT

Bot 音频输出 profile

helper 的 bot 下行音频输出通过部署配置选择,不通过 sessions/start 临时传业务参数。本轮 Lmtest / Combrabo Voice 动态回复主链路按用户最新裁决收口为全链路 16kHz / mono / pcm_s16le:Java stream bridge 下发 sampleRate=16000/channels=1,helper 写入 LiveKit bot track 前也保持 16k,不在业务层显式转成 48k。

# 默认值和本轮目标链路:bot NativeAudioSource = 16kHz / mono。
export CV_BOT_AUDIO_PROFILE=pcm-16k

# 历史 / 紧急回退:bot NativeAudioSource = 48kHz / mono。
# 不作为本轮 Lmtest 动态回复主链路或音质验收目标。
export CV_BOT_AUDIO_PROFILE=livekit-48k

# 调试值:显式指定采样率和声道。
export CV_BOT_AUDIO_PROFILE=custom
export CV_BOT_SAMPLE_RATE_HZ=16000
export CV_BOT_NUM_CHANNELS=1

bot_track_ready activity 会回写 audioProfilesampleRatenumChannels,这是 smoke 判断当前运行 profile 的准确信号。动态回复 bot_reply_audio_write_finished 会回写 source / target 元数据:

  • sourceSampleRate / sourceChannels:Java stream bridge 下发的 reply_audio_chunk 源格式。
  • targetAudioProfile / targetSampleRate / targetChannels:helper 写入 LiveKit bot track 前的目标格式。
  • networkChunkCountdebugSourcePathdebugPcmWavPath:用于脱敏排查 chunk/frame 对齐与听感问题。

本轮验收要求动态回复 stream-reply-{turnId}-source.wavstream-reply-{turnId}-target.wav 都是 16000Hz/mono/pcm_s16le。如果 target 仍是 48000Hz,视为未完成全链路 16k 目标。

当前只把 bot 下行输出 profile 做成可配置。用户上行采集、VAD 和 turn artifact 首版仍保持 48kHz / mono,避免牵动 ASR 输入、turn bridge 和历史 smoke 口径。

本机固定问候音频

helper 当前 MVP 支持 WAV / MP3 音频自动识别。主动问候 prepare 生成的 MP3 可以直接进入 helper;本地 smoke 仍可使用 WAV fixture 作为固定兜底样本。

在 macOS local 环境,推荐先生成一份本机固定问候 WAV fixture:

./tools/combrabo-voice-runtime-helper/generate-local-fixture.sh

默认输出:

tools/combrabo-voice-runtime-helper/.local/greeting-local.wav

推荐 launch-command

lmrobot-app local 推荐设置:

export COMBRABO_VOICE_RUNTIME_WORKDIR=/Users/ar/Applications/combrabo/wwww
export COMBRABO_VOICE_RUNTIME_LAUNCH_COMMAND=./tools/combrabo-voice-runtime-helper/run-local.sh
export COMBRABO_VOICE_RUNTIME_FALLBACK_GREETING_AUDIO_PATH=/Users/ar/Applications/combrabo/wwww/tools/combrabo-voice-runtime-helper/.local/greeting-local.wav

主动问候音频质量诊断

helper 会在播放主动问候前输出脱敏音质摘要,覆盖:

  • 源格式、源字节数、源采样率、源声道数;
  • 解码样本数、解码时长、目标采样率、目标声道数;
  • 20ms frame 数、理论播放时长、实际推帧 wall duration、推帧漂移;
  • RMS、peak、削波样本数、静音比例;
  • MP3 SkippedData / InsufficientData 计数。

本地排查音质问题时,可以额外设置 debug dump 目录:

export CV_AUDIO_DEBUG_DUMP_DIR=/tmp/combrabo-voice-audio-debug

开启后 helper 会保留两类文件:

<callId>-greeting-source.<wav|mp3>
<callId>-greeting-target.wav

其中 source 是原始主动问候音频,target 是推送给 LiveKit 前的目标 profile WAV。默认是 16kHz/mono/16-bit;只有显式配置 CV_BOT_AUDIO_PROFILE=livekit-48k 时才会生成 48kHz/mono/16-bit target。文件只用于本地回听排查,不进入 Git、不写入协作事件正文,不上传到线上环境。

用户上行音频观测

helper 默认开启下一阶段入口观测:

export CV_ENABLE_USER_AUDIO_OBSERVER=true

如需临时回退为纯固定问候播放器,可关闭:

export CV_ENABLE_USER_AUDIO_OBSERVER=false

开启后 helper 会监听 LiveKit TrackSubscribed 事件,并对远端用户音频轨道输出脱敏日志:

  • runtime helper user_track_subscribe_requested
  • runtime helper user_track_subscribed
  • runtime helper user_audio_frame_received
  • runtime helper user_audio_frame_summary
  • runtime helper user_audio_stream_ended

这些日志只记录 call_idtrace_id、脱敏 participant alias、脱敏 track sid、采样率、声道、帧数和时间摘要。禁止记录 token、room secret、真实 participantIdentity、完整 roomId、音频内容、ASR 文本、用户语音内容或 AI 回复文本。

轻量 VAD / turn detection

在确认 helper 能收到用户上行音频帧后,helper 默认开启轻量 VAD:

export CV_ENABLE_SIMPLE_VAD=true

首版 VAD 不引入外部模型,只基于上行 PCM frame 的 RMS 做保守阈值判断,peak 只作为诊断字段记录。这个口径对齐 cb-sdk 的能量阈值分段思路,避免单个尖峰噪声反复打断静音窗口,使用户 turn 拖到 max_turn_ms。配置了 Java runtime turn bridge 后,helper 会在有效 vad_speech_end 后把本轮 PCM 写成 user.wav artifact,并调用 Java 内部 bridge;helper 仍不做 ASR / LLM / TTS / 消息落库。

可调参数:

export CV_VAD_RMS_THRESHOLD=0.012
export CV_VAD_PEAK_THRESHOLD=0.08
export CV_VAD_START_FRAMES=5
export CV_VAD_END_SILENCE_MS=400
export CV_VAD_MIN_SPEECH_MS=250
export CV_VAD_MAX_TURN_MS=10000
export CV_VAD_INITIAL_IGNORE_MS=500
export CV_VAD_GATE_UNTIL_GREETING_DONE=true
export CV_VAD_POST_GREETING_DELAY_MS=800

Java turn bridge 相关配置:

export CV_RUNTIME_TURN_BRIDGE_URL=http://127.0.0.1:11102/internal/sdk/combrabo-voice/runtime/turns
export CV_RUNTIME_TURN_BRIDGE_TOKEN=local-dev-token
export CV_RUNTIME_TURN_ARTIFACT_DIR=/tmp/combrabo-voice-runtime/turn-artifacts
export CV_RUNTIME_SESSION_NONCE=runtime-session-nonce

这些值由 lmrobot-app 启动 runtime helper 时注入。Docker 模式会透传 env,并把 CV_RUNTIME_TURN_ARTIFACT_DIR 挂载进容器。日志不得打印 CV_RUNTIME_TURN_BRIDGE_TOKEN

本机 Docker 模式下,run-local.sh 会自动把 CV_RUNTIME_TURN_BRIDGE_URL 中的 127.0.0.1 / localhost 改写为 host.docker.internal,避免 helper 容器把 Java internal turn bridge 误解析为容器自身。

开启后新增脱敏事件:

  • runtime helper vad_disabled_greeting
  • runtime helper vad_enable_scheduled
  • runtime helper vad_enabled
  • runtime helper vad_ignored_before_enabled
  • runtime helper vad_speech_start
  • runtime helper vad_speech_end
  • runtime helper vad_speech_too_short
  • runtime helper vad_no_speech_summary
  • runtime helper turn_artifact_written
  • runtime helper turn_bridge_completed
  • runtime helper turn_bridge_failed
  • runtime helper turn_bridge_skipped

这些事件只记录 call_idtrace_id、脱敏 participant alias、脱敏 track sid、turn 序号、起止时间、帧数、样本数、RMS / peak 摘要、结束原因、artifact 字节数、bridge HTTP / reasonCode 摘要。它们不记录用户音频内容、ASR 文本、LLM 回复、TTS URL、bridge token 或本机绝对 artifact 路径。

默认启用 CV_VAD_GATE_UNTIL_GREETING_DONE=true,即主动问候播放完成前不产生 vad_speech_start/end。问候播放结束后,helper 会按 CV_VAD_POST_GREETING_DELAY_MS 延迟开启 VAD;门禁期间收到的用户音频帧只输出 vad_ignored_before_enabled 摘要,用于证明上行音频仍在,但不会形成用户 turn。

当前 local 真机复测默认 CV_VAD_POST_GREETING_DELAY_MS=800,用于在主动问候实际写完后保留短尾缓冲,避免 15 秒固定禁听窗口吞掉用户首句。若需要做受控回声隔离实验,可以临时调大该值,但不得作为产品化默认值。

当前边界

  • CV_GREETING_AUDIO_FILE:优先读取本地 WAV / MP3 文件;
  • CV_GREETING_AUDIO_URL:支持下载后按 WAV / MP3 解析;
  • 没有问候音频时,helper 仍会发布 bot track 并保持连接,但不会主动播放音频;
  • 如果传入了问候音频路径/URL,但内容不是可解析的 WAV / MP3,helper 会启动失败并把错误返回给 calls/start
  • 用户上行音频观测只证明 helper 能订阅用户音轨并收到帧,不代表 ASR/LLM/TTS 动态对话已经完成。
  • 轻量 VAD 在 bridge 配置完整时会生成 user.wav artifact 并调用 Java internal turn bridge;bridge 配置缺失时只记录 turn_bridge_skipped,不阻断 helper 进程。
  • 当前阶段只完成用户 turn artifact 和 Java bridge 请求;第二段 AI 回复音频写回 bot track 属于下一阶段。
  • 主动问候、固定 TTS 和后续动态回复必须统一写入 helper 发布的 LiveKit bot 音轨;不允许另起本地播放旁路。