跳转到内容

Web 浏览器接入

对应示例:examples/web

完整示例可在 UI 中切换两条服务端通路:原生 Audio LLM,以及 ASR → LLM → TTS 级联。两者在浏览器里都实现为一个 audio-turn Provider, 因此共享同一套 Session、事件、播放和 barge-in 生命周期。只需要原生音频 输入/输出时,可从更小的 examples/web-audio-llm-only 开始;它不配置文本 LLM 和 TTS。

Terminal window
cd examples/web
bun run start # http://localhost:5173

线上 Demo 即本示例打包产物:ottervoice.vercel.app

UI 选择 服务端实现 输入字幕 联网搜索
原生 · GPT Audio Mini OpenRouter openai/gpt-audio-mini 并行 Qwen ASR 不支持
原生 · Gemini Live Google gemini-3.1-flash-live-preview 并行 Qwen ASR 可选 Google Search Grounding
级联 Qwen ASR → Gemini Flash Lite → MiniMax Speech 同一复合响应 可选 OpenRouter 搜索路由

这些模型、prompt、voice、搜索工具和预算都只存在于服务端。UI 的“输入 / 输出 文本”开关只控制字幕是否可见,不会关闭服务器需要的转写步骤。

import { createOtterVoiceSession } from '@ottervoice/core';
import { createOpenRouterGatewayVoiceTurn } from '@ottervoice/provider-openrouter';
import { createWebRuntime, prepareBrowserAudio } from '@ottervoice/runtime-web';
const runtime = createWebRuntime({
mimeType: 'audio/webm;codecs=opus',
timesliceMs: 100,
volumePollMs: 50,
bargeInPreRollMs: 500,
});
// 浏览器只知道自有复合路由,不包含模型、prompt、voice 或 Provider Key。
const voiceTurn = createOpenRouterGatewayVoiceTurn({
baseUrl: '/api/voice/asr-llm-tts',
requireDoneSentinel: true,
prepareAudio: (audio, format) => prepareBrowserAudio(audio, format, {
sampleRate: 16_000,
maxDurationMs: 60_000,
}),
});
const session = createOtterVoiceSession({
mode: 'full_duplex',
// 这里描述客户端与 Provider 的契约;服务端仍可组合 ASR → LLM → TTS。
audioLlmStartTiming: 'after_audio',
runtime,
// voiceTurn.transcribesInput === true,因此不需要额外的 ASR 或文本 LLM。
providers: { audioLlm: voiceTurn },
turnDetection: {
strategy: 'volume',
minSpeechMs: 180,
silenceTimeoutMs: 450,
volumeThreshold: 0.025,
},
policy: {
autoStartListening: true,
allowInterruption: true,
},
});
await session.start();

服务端用 createOpenRouterGateway() 为复合 asr_llm_tts profile 的三段 policy 固定模型、系统提示词、声音、temperature、token 上限与 reasoning,并在 authorize 中校验用户和会话归属。原生 Audio LLM 路线则使用 createOpenRouterGatewayAudioLLM;若它不返回权威输入转写,再并行配置一个 createOpenRouterGatewayASR。不要在浏览器 Session 中设置 audioLlmSystemPrompt;该字段只适合完全运行在可信 Node/服务端的 Session。

客户端只有一种 audio-turn 契约,不代表网关后面只能有一个模型。 createOpenRouterGatewayVoiceTurn 把服务端复合实现包装成 AudioLLMProvider,并通过 transcribesInput: true 把同一响应中的输入转写交给 Core,避免再发一次 ASR 请求。

Core 默认值是 hybridminSpeechMs: 500silenceTimeoutMs: 1200maxTurnMs: 120000volumeThreshold: 0.02,属于偏保守的跨运行时配置。产品调优可从下表起步,并用真实设备和声学环境验证:

浏览器 策略 minSpeechMs silenceTimeoutMs volumeThreshold 采集建议
桌面 Chrome hybrid 180 650 0.02–0.03 100 ms timeslice;500 ms 插话 pre-roll
Android Chrome hybrid 220 850 0.025–0.04 100 ms timeslice;600 ms pre-roll;单轮限制 60–90 秒
Safari(macOS/iOS) hybrid 250 900 0.025–0.04 WebM 不可用时让 MediaRecorder 选择受支持 MIME

环境噪声导致误触发时提高 volumeThreshold,轻声用户漏检时降低它。普通 VAD 与 interruptionDetection 要独立调参,插话检测应更严格。上线前必须覆盖内置麦克风、耳机、外放回声,以及权限/自动播放恢复场景。

  • 页面需 HTTPS 或 localhost
  • 在用户手势里请求麦克风,并尽量 audioOutput.unlock?.()
  • autoplay 解锁失败不应阻断 ASR
  • 离开页面时 await session.dispose()

模型锁定、录音存储与删除、安全日志、重试策略见生产接入

选项说明见 @ottervoice/runtime-web