生产接入
生产客户端只应知道业务会话、自有网关和获准的业务 profile。供应商密钥、system/developer prompt、模型、voice、temperature、token 上限、reasoning、工具权限、对象存储 Key 和留存策略都必须由服务端控制。
| 边界 | 必须落实的控制 |
|---|---|
| 浏览器 / App → 网关 | 鉴权;每次请求都校验会话归属 |
| 上游请求 | 不透传客户端 JSON;只提取用户内容,再由服务端重建 Body |
| 模型与提示词 | 固定 model 和 system/developer prompt;拒绝客户端特权角色 |
| 生成与声音 | 固定/白名单 voice、speed、temperature、max tokens、reasoning、tools |
| 跨站请求 | 精确校验 Origin;Cookie 鉴权时配合 SameSite 与 CSRF Token |
| 滥用控制 | 按用户、会话、profile、IP、并发和供应商成本预算限流 |
| 输入 | 限制请求体和 SDK maxTurnMs;拒绝不支持的 Content-Type |
| 输出 | 设置上游与总请求超时;客户端断开时取消上游任务 |
可运行的 Audio LLM-only 示例 的客户端不包含模型、prompt、voice 或生成参数;服务端通过 createOpenRouterGateway() 重建请求、校验 Origin 并限制请求体/历史/文本。请把强制 authorize 钩子接入自己的用户与会话鉴权;示例不是完整账号服务。
| 标准客户端工厂 | Base URL | 服务端锁定 |
|---|---|---|
createOpenRouterGatewayASR |
/api/voice/asr |
ASR model、language 策略 |
createOpenRouterGatewayAudioLLM |
/api/voice/audio-llm |
model、system prompt、voice、temperature、tokens |
createOpenRouterGatewayVoiceTurn |
/api/voice/asr-llm-tts |
ASR、LLM、TTS 三段 policy;同一 SSE 响应返回输入转写、文本与 MP3 分片 |
复合语音工厂实现 AudioLLMProvider 并声明 transcribesInput: true。配合
createOtterVoiceSession 时可以省略客户端字幕 asr;但网关仍必须对
asr_llm_tts profile 单独鉴权、限流和记账。
密钥与请求头
Section titled “密钥与请求头”- 长期 Key 不能进入浏览器代码、App 包、
EXPO_PUBLIC_*或任何 API 响应。 - 不要把共享“网关密码”放进前端环境变量;客户端应携带当前用户的短期应用登录态,服务端再校验会话归属。
- 请求/响应类 API 走同源策略网关;客户端必须直连流式供应商时,只发短期且限 route/model/budget 的 Token。
- 自定义请求头必须先校验为浏览器兼容字符;任意业务元数据放 JSON Body,不要硬塞 Header。
- Trace 中必须脱敏
authorization、Cookie、签名 URL、供应商原始响应和 Base64 音频。
单轮音频与私有存储
Section titled “单轮音频与私有存储”直接订阅最终音频事件,不再包装 Provider 或用临时变量关联时序:
session.on('user_audio_final', async ({ turnId, audio, format }) => { await uploadPrivateTurn({ turnId, role: 'user', bytes: audio, format });});
session.on('assistant_audio', async ({ turnId, audio, mimeType }) => { if (audio) await uploadPrivateTurn({ turnId, role: 'assistant', bytes: audio, format: mimeType });});- 录音必须进入私有 Bucket;对象 Key 不可猜测,并绑定租户/会话记录。
- 回放走用户鉴权代理或短时签名 URL,不能把 Bucket 改成公开。
- 对象 Key、校验和、留存截止时间、
turnId与数据库 Turn 放进同一套可审计流程。 - 删除会话/账号时,幂等地排队删除对象并记录结果;数据库级联不会自动删除对象存储。
- 开启录音前取得合规同意并定义留存周期;转写和声音都按敏感个人数据处理。
错误中的 raw 和 cause 可能含上游响应或用户内容,只用于开发诊断。生产日志只记录 code、stage、provider、httpStatus、retryable、fatal、safeMessage 和自有 request id。
只重试 retryable 错误,在网关使用带随机抖动的指数退避并遵守供应商 Retry Header。鉴权、额度、音频解码、参数校验失败不能在请求不变时盲目重试。客户端 audioLlmRetry、滚动 ASR、after_audio 和后端选择都可能增加请求/成本;服务端预算与幂等控制不能依赖这些客户端值。
const session = createOtterVoiceSession({ // 标准客户端默认单次请求;确需重试时仍由网关独立限制预算。 audioLlmRetry: { maxAttempts: 1 }, // ...});Core 只会在还没有输出任何文本/音频流时重试当前轮。一旦开始输出,自动重试可能导致重复播报,因此会直接报告失败。启用 continueSessionOnFailure 后,error.fatal 为 false,会话恢复监听;否则进入终止性的 error 状态。
- 分别测试 401/403、402/额度、429、5xx、超时、客户端取消、SSE 中断、解码失败和播放失败。
- 上报
user_audio_end→assistant_audio_start延迟,以及错误stage/ HTTP 状态。 - CI 执行
bun run test:smoke:audio-llm;它不使用麦克风,以固定 WebM 覆盖转换、网关、SSE 和最终音频。 - 告警样本必须隐私安全,不能自动附带完整请求/响应 Body。
- 上线前明确删除流程、留存周期、供应商区域和事故响应责任人。