cai
2026-07-09 2bb392c03cdc76e27716f1d957cbb59016feaa4d
README.md
@@ -1,100 +1,297 @@
# lm-livekit-helper
# Combrabo Voice Runtime Helper
`lm-livekit-helper` 是 Combrabo Voice 的 LiveKit 媒体 worker。它从 `lmrobot-app` 接收每次通话的 `CV_*` 运行参数,加入 LiveKit 房间,发布 bot 音轨,播放主动问候音频,并在 S7 阶段承接用户语音 turn 的媒体输入输出。
这是 `lmrobot-app` Combrabo Voice 一阶段的最小 external runtime helper。它的长期定位是 LiveKit media worker:连接房间、发布 bot 音轨、订阅用户音轨、搬运 / 观测 PCM、输出脱敏诊断。
它不是业务服务,不承担鉴权、数据库、订单、ASR/LLM/TTS 权威、消息落库或 diagnostics 聚合。业务权威仍在 `lmrobot-app`。
当前职责分两层:
## 当前职责
一阶段固定主动问候职责有 4 件事:
1. 读取 `lmrobot-app` 注入的 `CV_*` 环境变量;
2. 使用 bot token 连接 LiveKit room;
1. 读取 `CombraboVoiceRuntimeServiceImpl` 注入的 `CV_*` 环境变量;
2. 使用 bot token 连接 local LiveKit room;
3. 发布 bot 本地音轨;
4. 播放 WAV / MP3 主动问候音频;
5. 在 smoke 模式下通过 LiveKit reliable Data Message 下发 `device_output`;
6. S7 阶段通过 Java runtime turn bridge 复用后端 ASR / TextChat / TTS / Message 能力。
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 边界,不新建第二套业务后端。
## 本机构建
```bash
cargo build --release
helper 编译固定分为“开发调试线”和“部署验证线”。详细口径见:
```text
doc/task/202606/0615-nativesdk-combrabo-voice-migration/29-helper编译与验证线路说明.md
```
helper 目录内带 `.cargo/config.toml`,本机构建建议通过本仓库根目录执行,确保使用同一套 registry / retry 配置。
本机开发调试优先复用已下载的 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
./run-local.sh
./tools/combrabo-voice-runtime-helper/run-local.sh
```
如果本地还没有 release 二进制,脚本会先执行构建。macOS 默认优先使用 Docker 模式,避免 host 侧 WebRTC native 依赖链路反复阻塞。
如果本地还没有 release 二进制,脚本会先执行一次构建。
可预先准备运行模式:
开发调试时建议显式带上本机 WebRTC 缓存:
```bash
./run-local.sh --prepare
./run-local.sh --prepare --rebuild
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 支持 WAV / MP3 音频自动识别。主动问候 `prepare` 生成的 MP3 可以直接进入 helper;本地 smoke 仍可使用 WAV fixture 作为固定兜底样本。
helper 当前 MVP 支持 **WAV / MP3** 音频自动识别。主动问候 `prepare` 生成的 MP3 可以直接进入 helper;本地 smoke 仍可使用 WAV fixture 作为固定兜底样本。
在 macOS local 环境,可先生成一份本机固定问候 WAV fixture:
在 macOS local 环境,推荐先生成一份本机固定问候 WAV fixture:
```bash
./generate-local-fixture.sh
./tools/combrabo-voice-runtime-helper/generate-local-fixture.sh
```
默认输出:
```text
.local/greeting-local.wav
tools/combrabo-voice-runtime-helper/.local/greeting-local.wav
```
## lmrobot-app 接线示例
## 推荐 launch-command
`lmrobot-app` local 推荐设置:
```bash
export COMBRABO_VOICE_RUNTIME_WORKDIR=/opt/lmrobot/lm-livekit-helper
export COMBRABO_VOICE_RUNTIME_LAUNCH_COMMAND=/opt/lmrobot/lm-livekit-helper/run-local.sh
export COMBRABO_VOICE_RUNTIME_HELPER_MODE=docker
export COMBRABO_VOICE_RUNTIME_HELPER_IMAGE=registry.example.com/lm-livekit-helper:git-sha
export COMBRABO_VOICE_RUNTIME_FALLBACK_GREETING_AUDIO_PATH=/opt/lmrobot/lm-livekit-helper/.local/greeting-local.wav
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
```
`CV_LIVEKIT_URL`、`CV_LIVEKIT_ROOM_ID`、`CV_LIVEKIT_BOT_TOKEN`、`CV_LIVEKIT_BOT_PARTICIPANT_IDENTITY` 等连接材料由 `lmrobot-app` 在每次 `calls/start` 时生成并注入,不写入仓库。
## 主动问候音频质量诊断
## LiveKit Data 设备输出 smoke
helper 会在播放主动问候前输出脱敏音质摘要,覆盖:
需要验证 NativeSDK 是否能收到设备输出 Data Message 时,在启动 `lmrobot-app` 前打开:
- 源格式、源字节数、源采样率、源声道数;
- 解码样本数、解码时长、目标采样率、目标声道数;
- 20ms frame 数、理论播放时长、实际推帧 wall duration、推帧漂移;
- RMS、peak、削波样本数、静音比例;
- MP3 `SkippedData` / `InsufficientData` 计数。
本地排查音质问题时,可以额外设置 debug dump 目录:
```bash
export CV_DEVICE_OUTPUT_SMOKE_ENABLED=true
export CV_AUDIO_DEBUG_DUMP_DIR=/tmp/combrabo-voice-audio-debug
```
helper 会在 bot 进房并发布音轨后,向 `CV_LIVEKIT_USER_PARTICIPANT_IDENTITY` 指向的 SDK client 发送 reliable Data Message:
开启后 helper 会保留两类文件:
- `topic`: `device_output`
- `type`: `device_output`
- `schemaVersion`: `1.0`
- `commandCode`: 例如 `vibration.start`
- `params`: 设备参数
```text
<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 默认开启下一阶段入口观测:
```bash
export CV_DEVICE_OUTPUT_DESTINATION_IDENTITIES=client-identity-a,client-identity-b
export CV_ENABLE_USER_AUDIO_OBSERVER=true
```
如果没有指定接收方且没有 `CV_LIVEKIT_USER_PARTICIPANT_IDENTITY`,helper 会广播到 room。设备控制建议继续使用 reliable/ordered Data Message;ACK / 执行结果首版建议走 HTTP,便于落库、重试和排查。
如需临时回退为纯固定问候播放器,可关闭:
## 文档
```bash
export CV_ENABLE_USER_AUDIO_OBSERVER=false
```
- [Runtime Contract](docs/runtime-contract.md)
- [Jenkins Build And Deploy Runbook](docs/jenkins-build-deploy.md)
开启后 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`
仓库内只允许提交 `.env.example` 这类脱敏样例。禁止提交真实 LiveKit token、API key、secret、roomId、participantIdentity、用户语音内容、ASR 文本、LLM 回复全文或完整 prompt。
这些日志只记录 `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 音轨;不允许另起本地播放旁路。