语音交互正在从"演示级玩具"走向"生产级产品"。过去搭一个实时语音 Agent,你要自己搞定 WebSocket 管理、音频流缓冲、VAD(语音活动检测)、TTS/STT 编排、断线重连……光基础设施就够写一个月。Stream 的 Vision Agents 开源框架把这套管线标准化了,再配上 Amazon Bedrock 上的 Nova 2 Sonic 模型,从零到一个可用的语音 Agent,时间单位是分钟而不是周。
下面拆解这套组合的工作原理,跑一遍代码,再看看函数调用、自动重连和多语言支持这些进阶能力。
管线拆解:从麦克风到模型再到扬声器
实时语音 Agent 的核心挑战是全双工低延迟流——用户说话的同时 Agent 可以响应,延迟控制在几百毫秒内。Vision Agents 的架构把这条链路分成三层:
- 传输层:基于 WebSocket 的双向音频流,客户端持续推送 PCM 音频帧,服务端持续推送合成语音帧。Stream 在这层做了自动重连和心跳保活。
- 推理层:音频帧进入后先过 VAD,检测到完整语音片段后送入 Nova 2 Sonic 的语音理解接口,拿到文本/语义表示;再由 LLM 决策生成回复文本,最后通过 Nova 2 Sonic 的语音合成接口把文本转成音频帧回传。
- 工具层:LLM 如果决定调用外部函数(查库存、下单等),框架暂停音频输出,执行函数调用,把结果喂回 LLM,再继续语音合成。
Nova 2 Sonic 的关键优势是语音输入直接理解——不需要先跑一个独立的 STT 模型转文本再喂给 LLM,减少了串联延迟。同时它的语音合成也是原生能力,不需要外挂 TTS 服务。
最小可运行示例
先装依赖,再跑一个能听你说话、用语音回你的 Agent。以下代码基于 Vision Agents 的 Python SDK 和 Amazon Bedrock 的 boto3 接口。
安装依赖
pip install stream-vision-agents boto3
确保你的 AWS 凭证已配置好(aws configure 或环境变量 AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY),且你的 IAM 角色对 Bedrock 有 InvokeModel 权限。
最小 Agent 代码
import asyncio
from vision_agents import VoiceAgent, AudioStreamConfig
from vision_agents.models import BedrockNova2Sonic
# 1. 配置 Nova 2 Sonic 模型接入
model = BedrockNova2Sonic(
model_id="amazon.nova-2-sonic", # Bedrock 上的模型标识
region="us-east-1", # Bedrock 可用区域
voice="matthew", # 合成语音的角色名
language="en", # 默认语言
)
# 2. 配置音频流参数
audio_config = AudioStreamConfig(
sample_rate=16000, # 输入音频采样率
channels=1, # 单声道
format="pcm_s16le", # 16-bit little-endian PCM
chunk_duration_ms=100, # 每 100ms 发一个音频帧
)
# 3. 定义 Agent 行为(system prompt)
system_prompt = """
You are a helpful voice assistant for a coffee shop.
Answer questions about menu items, prices, and hours.
Keep responses short — under 2 sentences — since this is a voice conversation.
"""
# 4. 组装并启动 Agent
agent = VoiceAgent(
model=model,
audio_config=audio_config,
system_prompt=system_prompt,
)
async def main():
# 启动 Agent 服务,监听 WebSocket 连接
await agent.serve(host="0.0.0.0", port=8765)
asyncio.run(main())
启动后,Agent 在 ws://0.0.0.0:8765 上等待 WebSocket 连接。客户端连上来后持续发送 PCM 音频帧,Agent 检测到完整语句后调用 Nova 2 Sonic 理解+生成+合成,把音频帧回传。
命令行快速测试
不想写前端?用 Stream 提供的 CLI 工具直接从麦克风对话:
vision-agents chat \
--ws-url ws://localhost:8765 \
--mic-device 0 \
--speaker-device 0
--mic-device 和 --speaker-device 的编号可以用 vision-agents list-devices 查看。
函数调用:让语音 Agent 不只是聊天
语音 Agent 的真正价值在于它能动手做事。Vision Agents 支持在语音对话中插入函数调用,用户说完"帮我查一下美式咖啡的价格",Agent 理解意图后调用 get_menu_item_price 函数,拿到结果再用语音回答。
from vision_agents import VoiceAgent, tool
# 定义函数工具
@tool(
name="get_menu_item_price",
description="Get the price of a menu item by name",
parameters={
"item_name": {
"type": "string",
"description": "The name of the menu item, e.g. 'Americano'",
}
},
)
def get_menu_item_price(item_name: str) -> str:
# 实际项目中这里查数据库或调内部 API
menu = {
"Americano": "$3.50",
"Latte": "$4.75",
"Cappuccino": "$4.50",
"Mocha": "$5.25",
}
price = menu.get(item_name, "not found")
return f"{item_name} is {price}"
# 把工具注册到 Agent
agent = VoiceAgent(
model=model,
audio_config=audio_config,
system_prompt=system_prompt,
tools=[get_menu_item_price], # 注册函数
)
流程是这样的:Nova 2 Sonic 理解语音后,LLM 层判断需要调函数 → 框架暂停音频输出 → 执行 get_menu_item_price → 结果回传 LLM → LLM 生成文本"美式咖啡3.5美元" → Nova 2 Sonic 合成语音 → 音频帧回传客户端。用户全程用语音交互,感知不到中间有个函数调用。
自动重连:生产环境的底线要求
实时 WebSocket 连接在移动网络下随时可能断。Vision Agents 内置了自动重连机制,客户端不需要自己写重连逻辑:
from vision_agents import VoiceAgent, ReconnectConfig
reconnect_config = ReconnectConfig(
max_retries=5, # 最大重连次数
retry_interval_ms=2000, # 每次重连间隔
backoff_multiplier=1.5, # 间隔指数退避系数
replay_buffer_ms=3000, # 重连后回放最近3秒音频,避免上下文丢失
)
agent = VoiceAgent(
model=model,
audio_config=audio_config,
system_prompt=system_prompt,
reconnect_config=reconnect_config,
)
replay_buffer_ms 是一个值得调的参数——重连成功后,框架会把断线期间客户端发来的音频帧回放给模型,避免模型丢失上下文导致回复断裂。值越大越安全,但也增加重连后的首次响应延迟。移动端场景建议 2000-5000ms,稳定网络场景可以设 0。
多语言语音支持
Nova 2 Sonic 支持多语言语音理解和合成。切换语言只需要改配置,不需要换模型:
model = BedrockNova2Sonic(
model_id="amazon.nova-2-sonic",
region="us-east-1",
voice="zhen", # 中文语音角色
language="zh", # 切换为中文
)
system_prompt = """
你是咖啡店的语音助手。用中文回答关于菜单、价格和营业时间的问题。
语音对话请保持简短,每次回答不超过两句话。
"""
如果你的用户群体跨语言,可以在一个 Agent 里做动态切换——框架支持在对话中通过函数调用切换 language 和 voice 参数,让同一个 Agent 根据用户说的语言自动适配。
上线前的检查清单
把 Demo 变成生产服务,这几项别跳过:
| 检查项 | 要点 |
|---|---|
| 延迟基准 | 用 vision-agents benchmark --ws-url ... 测量端到端延迟,目标 < 800ms |
| 并发容量 | 每个 WebSocket 连接独占一个推理流,预估并发数后算 Bedrock 配额 |
| Bedrock 配额 | Nova 2 Sonic 有 TPM/RPM 限制,生产前申请提额 |
| 音频格式 | 客户端录音格式必须和 AudioStreamConfig 一致,否则 VAD 误触发 |
| 重连参数 | 根据网络环境调 replay_buffer_ms,移动端偏大,固定网络偏小 |
| 函数超时 | @tool 函数加 timeout_ms 参数,避免外部 API 卡住导致语音长时间沉默 |
| 日志与监控 | Vision Agents 支持 OpenTelemetry 导出,接入你的可观测平台 |
函数超时是个容易忽略的点。语音对话中用户对沉默的容忍度极低——如果查库存的 API 要 5 秒才返回,用户已经以为 Agent 崩了。给每个 tool 设超时,超时后让 LLM 用语音说"稍等,我还在查",比沉默好得多:
@tool(
name="get_menu_item_price",
description="Get the price of a menu item by name",
timeout_ms=3000, # 3秒超时
parameters={...},
)
def get_menu_item_price(item_name: str) -> str:
...
什么时候该用这套方案,什么时候不该
适合的场景:需要低延迟全双工语音交互、对话中要调外部 API、用户群体多语言、团队不想自己从零搭 WebSocket+VAD+STT+TTS 管线。
不太适合的场景:纯文本聊天(直接用 Bedrock Converse API 更简单)、对延迟要求极致(< 200ms,比如实时游戏语音指挥,需要更轻量的本地模型方案)、音频处理需要深度定制(比如自定义降噪算法,框架的抽象层会成为限制)。
Stream Vision Agents + Nova 2 Sonic 的组合,核心价值是把语音 Agent 的基础设施从"自己造"变成"配一下"。如果你正在评估语音交互方案,花半小时跑一遍上面的最小示例,比读十篇架构文章更有判断力。