用 Stream Vision Agents 和 Amazon Nova 2 Sonic 搭建实时语音 Agent

2026-05-15 21 预计阅读时间:1 分钟
来源:aws.amazon.com AI 摘要 原文链接

免责声明:本文为 AI 摘要整理,建议结合原文阅读。摘要可能省略上下文、版本差异或边界条件,不作为官方说明。

预计阅读时间:11 分钟

语音交互正在从"演示级玩具"走向"生产级产品"。过去搭一个实时语音 Agent,你要自己搞定 WebSocket 管理、音频流缓冲、VAD(语音活动检测)、TTS/STT 编排、断线重连……光基础设施就够写一个月。Stream 的 Vision Agents 开源框架把这套管线标准化了,再配上 Amazon Bedrock 上的 Nova 2 Sonic 模型,从零到一个可用的语音 Agent,时间单位是分钟而不是周。

下面拆解这套组合的工作原理,跑一遍代码,再看看函数调用、自动重连和多语言支持这些进阶能力。

管线拆解:从麦克风到模型再到扬声器

实时语音 Agent 的核心挑战是全双工低延迟流——用户说话的同时 Agent 可以响应,延迟控制在几百毫秒内。Vision Agents 的架构把这条链路分成三层:

  1. 传输层:基于 WebSocket 的双向音频流,客户端持续推送 PCM 音频帧,服务端持续推送合成语音帧。Stream 在这层做了自动重连和心跳保活。
  2. 推理层:音频帧进入后先过 VAD,检测到完整语音片段后送入 Nova 2 Sonic 的语音理解接口,拿到文本/语义表示;再由 LLM 决策生成回复文本,最后通过 Nova 2 Sonic 的语音合成接口把文本转成音频帧回传。
  3. 工具层: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 里做动态切换——框架支持在对话中通过函数调用切换 languagevoice 参数,让同一个 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 的基础设施从"自己造"变成"配一下"。如果你正在评估语音交互方案,花半小时跑一遍上面的最小示例,比读十篇架构文章更有判断力。


相关推荐