# 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 编译固定分为“开发调试线”和“部署验证线”。详细口径见: ```text doc/task/202606/0615-nativesdk-combrabo-voice-migration/29-helper编译与验证线路说明.md ``` 本机开发调试优先复用已下载的 LiveKit WebRTC 预编译缓存: ```bash export LK_CUSTOM_WEBRTC="$HOME/.cache/combrabo/livekit-webrtc/mac-arm64-release-webrtc-51ef663" ``` 然后执行: ```bash 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 镜像构建。 ```bash 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 编译链路的快速线: ```bash 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 基本约束,用于提前发现 `replyPlaybackMode`、`reply_state`、`reply_audio_chunk`、`turn_completed` 等字段破坏。它不能替代 Docker 镜像构建、真实 LiveKit smoke 或 iPhone 真机验收。 ## 本机运行 推荐通过包装脚本启动: ```bash ./tools/combrabo-voice-runtime-helper/run-local.sh ``` 如果本地还没有 release 二进制,脚本会先执行一次构建。 开发调试时建议显式带上本机 WebRTC 缓存: ```bash 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 外壳: ```bash 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`。因此本机探活使用: ```bash 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。 ```bash # 默认值和本轮目标链路: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 会回写 `audioProfile`、`sampleRate`、`numChannels`,这是 smoke 判断当前运行 profile 的准确信号。动态回复 `bot_reply_audio_write_finished` 会回写 source / target 元数据: - `sourceSampleRate` / `sourceChannels`:Java stream bridge 下发的 `reply_audio_chunk` 源格式。 - `targetAudioProfile` / `targetSampleRate` / `targetChannels`:helper 写入 LiveKit bot track 前的目标格式。 - `networkChunkCount`、`debugSourcePath`、`debugPcmWavPath`:用于脱敏排查 chunk/frame 对齐与听感问题。 本轮验收要求动态回复 `stream-reply-{turnId}-source.wav` 与 `stream-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: ```bash ./tools/combrabo-voice-runtime-helper/generate-local-fixture.sh ``` 默认输出: ```text tools/combrabo-voice-runtime-helper/.local/greeting-local.wav ``` ## 推荐 launch-command `lmrobot-app` local 推荐设置: ```bash 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 目录: ```bash export CV_AUDIO_DEBUG_DUMP_DIR=/tmp/combrabo-voice-audio-debug ``` 开启后 helper 会保留两类文件: ```text -greeting-source. -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 默认开启下一阶段入口观测: ```bash export CV_ENABLE_USER_AUDIO_OBSERVER=true ``` 如需临时回退为纯固定问候播放器,可关闭: ```bash 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_id`、`trace_id`、脱敏 participant alias、脱敏 track sid、采样率、声道、帧数和时间摘要。禁止记录 token、room secret、真实 participantIdentity、完整 roomId、音频内容、ASR 文本、用户语音内容或 AI 回复文本。 ## 轻量 VAD / turn detection 在确认 helper 能收到用户上行音频帧后,helper 默认开启轻量 VAD: ```bash 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 / 消息落库。 可调参数: ```bash 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 相关配置: ```bash 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_id`、`trace_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 音轨;不允许另起本地播放旁路。