这次我们来看一个偏工程落地的话题:企业级 Voice Agent 智能语音助手。很多人做语音助手还是老一套——“ASR 识别文本,然后查数据库匹配答案,再把结果拼出来朗读”,这套路在固定指令场景勉强能用,一旦遇到开放问答、多轮对话、工具调用,立刻就崩。解决思路也很明确:把 STT、Agent、LLM、TTS 串成一条流水线,用级联式三明治架构组织起来。今天这篇文章就把这个架构拆开讲清楚,并给出一套可以直接照着实践的部署、测试、接口验证流程。

这个方案的核心不是某个单一模型,而是一整套分层设计。最上层是 STT 语音识别,中间是 Agent 智能体,最下层是 TTS 语音合成,LLM 嵌入在 Agent 层里负责理解、决策和调用工具。这样设计能一次性解决语音助手的两个老大难问题:一是全链路延迟控制,二是“能聊天但不会干活”。延迟方面,STT 和 TTS 都用流式处理,Agent 层收到完整文本后再做决策,链路清晰、每层都可以单独压测和替换。能力方面,Agent 层通过 Function Calling 获取实时数据、查询订单、控制设备,回答不再只靠模型记忆,而是有真实工具做支撑。

下面会按这个顺序展开:先看架构怎么拆解,再讲环境准备和部署启动,然后分别对 STT、Agent、TTS 三个模块做功能验证,接着给出全链路调用示例和批量任务设计,最后聊资源占用、常见问题和工程建议。适合正在做客服机器人、语音助手、智能硬件语音交互,或者准备把大模型接入语音业务的技术同学。

1. 核心能力速览

能力项 说明
项目类型 级联式 Voice Agent 语音交互方案,三明治架构(STT-Agent/LLM-TTS)
核心思路 语音输入 -> STT 转文本 -> Agent 处理 -> TTS 合成语音 -> 语音输出
主要功能 流式语音识别、LLM 对话、Function Calling 工具调用、多轮上下文管理、TTS 语音合成
解决的核心问题 全链路响应延迟;语音助手“只会聊、不会做”
模型选型 STT:可替换(如 Whisper、FunASR、Paraformer 等);LLM:可替换(如 Qwen、GLM、DeepSeek 等);TTS:可替换(如 CosyVoice、ChatTTS 等)
推荐部署方式 模块化服务部署:STT 服务、Agent 服务、TTS 服务各自独立,再通过链路调度层串联
启动方式 命令行启动独立服务 / 一键脚本启动全链路
是否支持 CPU 取决于所选模型,纯 CPU 可跑小参数模型,推荐 GPU 或云端 API 混用
是否支持 API 支持,每个模块可暴露 HTTP/WebSocket 接口
是否支持批量任务 支持,Agent 层可扩展异步任务队列
适合场景 客服语音机器人、车载语音助手、智能硬件对话、电话外呼、企业知识库问答等

2. 级联式三明治架构:为什么要这样设计

先明确“三明治”的含义。整个系统从逻辑上分成三层,语音入口和语音出口在最外,Agent 在中间:

┌─────────────┐
│   STT 模块   │  音频流 -> 文本
└──────┬──────┘
       ▼
┌─────────────┐
│  Agent 层    │  意图理解、多轮记忆、工具调用
│  + LLM 引擎  │
└──────┬──────┘
       ▼
┌─────────────┐
│   TTS 模块   │  文本 -> 音频流
└─────────────┘

这个结构和以前的“语音识别 + 关键词匹配”方案最大的区别是:中间层从规则引擎换成了具备推理能力的 LLM Agent。用户说一句话,STT 先把音频变成文本,Agent 不是直接拿这句话去查库,而是先判断用户意图,决定是普通闲聊、知识问答,还是需要调用外部工具,然后再组织回答文本,最后交给 TTS 播报。

2.1 两大难题之一:全链路延迟控制

语音交互和文本交互的体感完全不同。用户打字问问题,等两三秒没感觉;但用户对着设备说话,超过 0.5 到 1 秒没有回应,就会明显觉得“卡”。

级联链路里延迟是叠加的:

  • 语音输入要等用户说完,STT 才能给出完整文本。
  • LLM 推理需要时间,模型越大耗时越长。
  • TTS 合成需要时间,合成完还要播放。

如果三段都串行处理,总延迟就等于三段之和。架构上解决思路是:

  1. STT 用流式识别,边说话边出中间结果,检测到停顿后再触发 Agent。
  2. Agent 层用小参数模型处理常见意图,复杂任务再升级到大模型,或者交给云端 API。
  3. TTS 采用分句合成和边生成边播放,不用等整段文本全部合成完。

2.2 两大难题之二:能聊天但不能干活

上一代语音助手经常被吐槽“智障”,根因是架构里没有“行动能力”。识别出文本后,只能走固定话术模板,无法获取实时数据,也无法执行操作。用户问“帮我查一下订单到哪了”,系统只能回答“查询功能暂未开通”。

引入 Agent 后,这一问题被 Function Calling 解决。LLM 负责理解用户的话,并输出结构化的工具调用参数,系统再执行真实函数,把结果回填给模型,最后由模型组织自然语言答案。这样语音助手就从“聊天机器人”变成了“能操作业务的语音入口”。

架构上把 Agent 单独做成一层,而不是把工具调用逻辑写死在应用里,好处是每个业务工具都可以独立维护、独立测试、独立扩充。后面我们会专门演示一个查询天气的 Function Calling 示例。

3. 使用场景与使用边界

先说适合哪些场景:

  • 客服语音机器人:用户来电咨询,STT 识别后由 Agent 查询订单状态、退换货政策、常见问题,TTS 播报结果。
  • 智能硬件控制:对音箱、车载系统说“打开空调”“导航到公司”,Agent 调用设备控制接口。
  • 企业知识库问答:把内部文档接入 RAG,员工用语音提问,系统检索资料后回答。
  • 外呼与回访:批量拨打用户电话,Agent 按脚本引导对话,同时记录用户反馈。

不适合的场景也要说清楚:

  • 对响应延迟要求极其苛刻的实时对讲场景,本地级联架构还需要额外做音频打断、快速应答等优化。
  • 纯离线且硬件资源非常有限的嵌入式设备,建议只保留轻量 ASR 和固定话术,不要强行塞大模型。
  • 涉及金融交易、医疗诊断、法律意见等高风险决策,Agent 只能做信息收集和初筛,最终判断仍需人工确认。

合规边界是重点。语音数据属于敏感个人信息,采集用户的语音前必须获得明确授权。使用 TTS 合成特定音色时,尤其是模仿真实人物的声音,必须确认已获得本人授权和商用许可。录音数据不得随意存储、共享或用于模型训练。涉及客户信息查询、支付、设备控制等操作,必须在 Agent 回复中加入二次确认机制,防止误操作。

4. 环境准备与前置条件

4.1 操作系统与运行环境

三个模块都是 Python 生态为主,推荐在 Linux 服务器上部署,Windows 也可以做本地开发测试。需要提前装好:

  • Python 3.10 或以上版本。
  • pip 包管理器。
  • 如果使用 GPU 推理,需要安装匹配的 CUDA 驱动和 PyTorch 版本。
  • 如果使用 Docker,可以用容器隔离每个模块。
  • 建议使用虚拟环境,避免依赖互相污染。

4.2 模型选择与硬件评估

这套架构中,模型规格决定硬件门槛。给你一个选型思路,具体数字按实际机器测试:

模块 轻量方案 高质量方案 硬件要求
STT 小参数 ASR 模型,CPU 可跑 大参数 ASR 模型,推荐 GPU 显存越高,识别速度越快
LLM 4B-7B 量化模型 14B 以上模型或云端 API GPU 至少能容纳量化后的模型
TTS 轻量合成模型 高质量多音色模型 可与 STT/LLM 共用一张卡

更稳妥的做法是混用:本地跑 STT 和 TTS,LLM 用云端 API。这样显存压力小,企业接入也方便。如果全链路都用本地模型,优先用小参数模型做首版验证,确认效果后再逐步升级。

4.3 磁盘与端口规划

模型文件体积不小,需要预留足够的磁盘空间。建议按角色规划目录:

voice-agent/
├── models/
│   ├── stt/
│   ├── llm/
│   └── tts/
├── services/
│   ├── stt_server.py
│   ├── agent_server.py
│   └── tts_server.py
├── logs/
├── input_audio/
├── output_audio/
└── config/

端口规划也很重要。建议 STT 服务、Agent 服务、TTS 服务分别使用不同端口,例如:

STT   服务端口:8801
Agent 服务端口:8802
TTS   服务端口:8803

实际端口以你的项目配置为准,只要保证不冲突即可。启动前可以先检查端口占用。

4.4 依赖安装

以下是一个通用依赖安装示例,实际按你的模型选型调整:

# 创建虚拟环境
python -m venv venv
source venv/bin/activate

# 安装 Web 服务框架
pip install fastapi uvicorn

# 安装音频处理库
pip install soundfile numpy librosa

# 安装 WebSocket 支持,用于流式传输
pip install websockets

# 按实际选择的模型安装对应依赖
# 例如:pip install openai-whisper
# 例如:pip install funasr
# 例如:pip install modelscope

5. 模块化部署与启动方式

三明治架构最大的好处是每个模块都可以单独启动、单独验证。下面给出每个模块的服务启动示例。注意这里的代码是结构示例,实际模型名、路径、参数需要替换成你选用的具体实现。

5.1 启动 STT 语音识别服务

STT 服务接收音频文件或音频流,返回识别文本。

# services/stt_server.py
from fastapi import FastAPI, UploadFile, File
import tempfile

app = FastAPI()

# 这里用你自己的 STT 模型做全局加载
# stt_model = load_stt_model("models/stt/your_model")

@app.post("/asr")
async def asr(audio: UploadFile = File(...)):
    audio_bytes = await audio.read()
    with tempfile.NamedTemporaryFile(suffix=".wav", delete=True) as tmp:
        tmp.write(audio_bytes)
        text = stt_model.transcribe(tmp.name)
    return {"text": text}

if __name__ == "__main__":
    import uvicorn
    # 实际启动命令按项目调整
    uvicorn.run(app, host="127.0.0.1", port=8801)

启动命令:

python services/stt_server.py

验证方式:

curl -X POST http://127.0.0.1:8801/asr \
  -F "audio=@test.wav"

正常返回示例:

{
  "text": "你好,帮我查一下今天的天气"
}

5.2 启动 Agent 智能体服务

Agent 服务接收文本,输出回答文本。核心逻辑包括三部分:组装对话上下文、调用 LLM、判断是否需要执行工具。

# services/agent_server.py
from fastapi import FastAPI, Request
from pydantic import BaseModel

app = FastAPI()

class ChatRequest(BaseModel):
    user_input: str
    history: list = []
    user_id: str = "default"

@app.post("/agent")
async def agent_chat(req: ChatRequest):
    # 1. 组装 prompt
    messages = build_messages(req.history, req.user_input)
    # 2. 调用 LLM
    response = llm_complete(messages)
    # 3. 判断是否有 function call
    if response.get("tool_calls"):
        tool_result = execute_tool(response["tool_calls"])
        messages.append(tool_result)
        response = llm_complete(messages)
    return {"reply": response["content"]}

启动命令:

python services/agent_server.py

5.3 启动 TTS 语音合成服务

TTS 服务接收文本,返回合成的音频字节流。

# services/tts_server.py
from fastapi import FastAPI
from fastapi.responses import Response
from pydantic import BaseModel

app = FastAPI()

class TTSRequest(BaseModel):
    text: str

@app.post("/tts")
async def tts(req: TTSRequest):
    audio_data = tts_model.synthesize(req.text)
    return Response(content=audio_data, media_type="audio/wav")

启动命令:

python services/tts_server.py

5.4 全链路一键启动脚本

模块都跑通后,可以写一个启动脚本把三个服务一起拉起:

#!/bin/bash
# start_all.sh
echo "Start STT server..."
python services/stt_server.py &
STT_PID=$!

echo "Start Agent server..."
python services/agent_server.py &
AGENT_PID=$!

echo "Start TTS server..."
python services/tts_server.py &
TTS_PID=$!

echo "All services started."
echo "STT: 127.0.0.1:8801"
echo "Agent: 127.0.0.1:8802"
echo "TTS: 127.0.0.1:8803"

# 按下 Ctrl+C 时结束所有进程
trap "kill $STT_PID $AGENT_PID $TTS_PID" INT
wait

6. 功能测试与效果验证

三个服务能启动,只是第一步。下面按模块给出测试项和判断标准。

6.1 STT 模块测试

测试目的:确认语音识别准确率、噪声环境下表现、流式识别的可用性。

操作步骤:

  1. 准备几段不同场景的音频:安静环境对话、带背景音乐、多人说话场景。
  2. 调用 /asr 接口逐段识别。
  3. 对比识别文本和真实文本。

判断标准:

  • 安静环境下,核心意图词应该被准确识别。
  • 背景音乐和多人说话场景下,允许部分误识别,但不能影响主任务。
  • 如果需要流式识别,要额外测试“说话过程中返回中间结果”的能力,观察停顿后是否能在短时间内给出最终结果。

常见失败原因:

  • 音频采样率与模型不匹配,导致识别率骤降。
  • 音频时长过长,超过服务的单次请求限制。
  • 模型未针对中文优化,中文识别率偏低。

6.2 Agent 模块测试

测试目的:验证 LLM 对话能力、Function Calling 工具调用能力、多轮记忆能力。

基础对话测试:

POST http://127.0.0.1:8802/agent

{
  "user_input": "你好,介绍一下你自己",
  "history": []
}

预期结果:返回一段自然语言自我介绍。

工具调用测试是重点。先定义一个函数,比如查询天气:

# tools/weather_tool.py
def get_weather(city: str):
    """查询指定城市的天气,返回结构化结果"""
    # 这里替换为真实的天气 API 调用
    mock_data = {
        "city": city,
        "temperature": "25",
        "condition": "晴",
        "wind": "东南风 2 级"
    }
    return mock_data

然后在 Agent 层注册这个函数:

# services/agent_server.py
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "查询指定城市的天气情况",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "城市名,例如北京"
                    }
                },
                "required": ["city"]
            }
        }
    }
]

测试输入:

{
  "user_input": "北京今天天气怎么样?"
}

预期结果分两阶段:

  1. LLM 首先输出工具调用请求。
  2. 系统执行工具后,LLM 基于工具结果生成最终回答。

判断标准:

  • Agent 能从自然语言中正确抽取参数,不能把“北京今天天气怎么样?”理解成城市名误填。
  • 工具返回后,Agent 回答里必须包含真实工具数据。
  • 多轮测试时,上一轮的信息应该能被下一轮记住,比如先问“北京天气”,接着问“那明天呢?”。

6.3 TTS 模块测试

测试目的:验证语音合成的自然度、延迟和稳定性。

操作步骤:

  1. 输入短句,测试基础合成。
  2. 输入长文本,测试分句合成能力。
  3. 连续请求多次,观察服务是否稳定运行。

预期结果:

  • 短句合成应该流畅自然。
  • 长文本播放时,首句音频应该在合理时间内返回,而不是等整段合成完再返回。
  • 合成音频无破音、无卡顿。

判断标准:

  • 音频时长和文本长度匹配,例如 200 字文本不应只合成出声时长为 10 秒的音频。
  • 多音字、数字、英文符号要读得合理。

6.4 全链路端到端测试

三模块分别测试通过后,做一次语音的全链路测试:

# test_full_pipeline.py
import requests

audio_file = "input_audio/question.wav"

# 第一步:STT 识别
with open(audio_file, "rb") as f:
    r = requests.post("http://127.0.0.1:8801/asr", files={"audio": f})
user_text = r.json()["text"]
print("识别文本:", user_text)

# 第二步:Agent 处理
r = requests.post("http://127.0.0.1:8802/agent", json={
    "user_input": user_text,
    "history": [],
    "user_id": "test-001"
})
agent_reply = r.json()["reply"]
print("Agent回答:", agent_reply)

# 第三步:TTS 合成
r = requests.post("http://127.0.0.1:8803/tts", json={"text": agent_reply})
with open("output_audio/answer.wav", "wb") as f:
    f.write(r.content)
print("回复音频已保存到 output_audio/answer.wav")

端到端通过的判断标准:

  • 音频进,音频出,中间两个环节自动完成,不需要人工干预。
  • 用户的提问和 Agent 的回答在语义上相关。
  • 从音频结束到音频播放的整体耗时,应该在可接受的交互范围内。

7. 接口 API 与批量任务设计

7.1 API 调用规范

三个模块的接口虽然独立,但在企业落地时建议统一 API 规范,比如统一请求格式:

{
  "request_id": "20260220-001",
  "user_id": "user_001",
  "audio": "base64编码的音频数据"
}

统一响应格式:

{
  "request_id": "20260220-001",
  "success": true,
  "data": {
    "text": "识别结果",
    "reply": "Agent回答",
    "audio": "base64编码的音频数据"
  }
}

这样上层业务只对接一个总入口即可,内部三个服务的变化不影响业务方。

7.2 Python 调用示例

以 Python 为例,封装一个全链路调用类:

import requests
import base64

class VoiceAgentClient:
    def __init__(self, stt_url, agent_url, tts_url):
        self.stt_url = stt_url
        self.agent_url = agent_url
        self.tts_url = tts_url

    def chat_with_audio(self, audio_bytes):
        # 1. STT
        r = requests.post(
            self.stt_url,
            files={"audio": ("input.wav", audio_bytes, "audio/wav")},
            timeout=30
        )
        user_text = r.json().get("text", "")

        # 2. Agent
        r = requests.post(
            self.agent_url,
            json={"user_input": user_text, "history": [], "user_id": "default"},
            timeout=60
        )
        reply_text = r.json().get("reply", "")

        # 3. TTS
        r = requests.post(
            self.tts_url,
            json={"text": reply_text},
            timeout=30
        )
        audio_data = r.content

        return user_text, reply_text, audio_data


client = VoiceAgentClient(
    stt_url="http://127.0.0.1:8801/asr",
    agent_url="http://127.0.0.1:8802/agent",
    tts_url="http://127.0.0.1:8803/tts"
)

with open("input_audio/question.wav", "rb") as f:
    audio_bytes = f.read()

user_text, reply_text, audio_data = client.chat_with_audio(audio_bytes)
print("用户说:", user_text)
print("助手答:", reply_text)

7.3 批量任务接口设计

语音交互中,批量任务常见于外呼、批量录音质检、批量语音问答。批量任务不适合前端同步等待,建议采用任务队列模式。

# 批量任务伪代码
from datetime import datetime
import uuid

task_queue = []

def submit_task(audio_path):
    task_id = str(uuid.uuid4())
    task_queue.append({
        "task_id": task_id,
        "audio_path": audio_path,
        "status": "pending",
        "created_at": datetime.utcnow().isoformat()
    })
    return task_id

def process_queue():
    for task in task_queue:
        if task["status"] != "pending":
            continue
        task["status"] = "processing"
        try:
            result = client.chat_with_audio(open(task["audio_path"], "rb").read())
            task["result"] = result
            task["status"] = "success"
        except Exception as e:
            task["status"] = "failed"
            task["error"] = str(e)

# 批量任务的工程化建议:
# 1. 用 Redis/RabbitMQ 等消息队列替代内存列表,防止任务丢失。
# 2. 记录每个任务的重试次数,失败超过阈值后进入人工处理队列。
# 3. 任务处理结果写入数据库,便于后续查询和统计。

批量任务的失败重试建议:

  • STT 失败可能是音频格式问题,不在重试循环里死循环。
  • Agent 失败可能是 LLM 服务超时,建议加超时重试,最多重试 2 次。
  • TTS 失败可能是文本长度超限,要拆分文本再合成。

8. 资源占用与性能观察

级联架构的资源占用是三个模块叠加的结果,观察时不能只看单个模块,要看全链路峰值。

8.1 显存和 CPU 占用观察

观察工具:

  • 使用 nvidia-smi 查看 GPU 显存占用。
  • 使用 top 或 htop 查看 CPU 使用率。

观察姿势:

  • 分别启动 STT、Agent、TTS 三个服务,逐个记录空闲时的基础占用。
  • 分别发请求,记录每个模块单独工作时的峰值占用。
  • 同时发请求,观察三个模块并发工作时的总占用。

重点观察项:

  • STT 在处理长音频时,是否出现显存峰值。
  • LLM 在多轮对话场景下,显存占用是否持续增长。如果上下文越来越长,显存可能被 KVCache 吃掉,需要设置最大上下文长度或历史窗口。
  • TTS 服务是否常驻显存,还是在请求到来时才加载模型。

8.2 延迟拆分方法

全链路延迟由三部分组成:

总耗时 = STT耗时 + Agent耗时 + TTS耗时

排查性能问题时,要在每个环节打印时间戳,明确瓶颈在哪一段。

import time

start = time.time()
# STT 调用
stt_start = time.time()
stt_result = run_stt(audio_file)
stt_cost = time.time() - stt_start
print(f"STT耗时: {stt_cost:.2f}s")

agent_start = time.time()
agent_result = run_agent(stt_result)
agent_cost = time.time() - agent_start
print(f"Agent耗时: {agent_cost:.2f}s")

tts_start = time.time()
tts_result = run_tts(agent_result)
tts_cost = time.time() - tts_start
print(f"TTS耗时: {tts_cost:.2f}s")

print(f"全链路总耗时: {time.time() - start:.2f}s")

优化方向:

  • 如果 STT 耗时高,换成更小的模型或改用云端 API。
  • 如果 Agent 耗时高,改用量化模型、减少上下文长度、把复杂工具调用改为预置接口。
  • 如果 TTS 耗时高,改成流式合成,边生成边播放,让用户感知不到全量等待。

8.3 如何降低资源占用

  • LLM 使用量化版本,例如 4bit 量化,能显著减少显存占用。
  • 三个服务不一定都要常驻显存。如果 TTS 不是高频请求,可以按需加载模型。
  • Agent 层对上下文做窗口截断,只保留最近几轮对话,防止上下文无限增长导致显存膨胀。
  • 并发场景要做请求排队,避免同时多个长文本进入 LLM 造成显存溢出。

8.4 端口冲突与进程残留

模块化服务最容易遇到端口冲突。启动前先用命令检查端口:

# Linux
netstat -tlnp | grep 8801
# 或
ss -tlnp | grep 8801

# Windows
netstat -ano | findstr 8801

如果服务异常退出后端口被占用,需要找到进程并清理:

# 找到占用端口的进程 PID
lsof -i :8801
# 结束进程
kill -9 PID

9. 常见问题与排查方法

问题现象 可能原因 排查方式 解决方案
STT 服务启动后无法识别中文 模型未适配中文或音频采样率不对 检查模型类型和音频格式 换成中文优化的 ASR 模型,统一音频采样率
Agent 回答与用户问题无关 LLM 系统提示词缺失、上下文顺序错误 打印实际发送给 LLM 的消息列表 检查 messages 组装逻辑,把 history 正确拼接
Function Calling 一直不生效 工具 schema 参数描述不清晰 打印 LLM 返回的原始响应 完善工具描述和参数描述,必要时示例化参数
工具参数提取错误 函数名或参数有歧义 增加测试用例,逐条验证 调整函数描述,让意图更明确
TTS 合成有杂音 音色参数设置不当或模型退化 换一条参考音频 / 换文本测试 检查音频后处理参数
全链路超时 某一段服务处理过慢 打印三段耗时 定位瓶颈,流式化或换模型
显存溢出 三个模型同时常驻 逐段请求观察显存变化 启用量化、按需加载模型、限制上下文长度
批量任务卡住 任务队列没有异常捕获 检查任务日志和 Python traceback 给每个任务加 try-except 和重试机制
音频文件过大,接口请求失败 单个请求体超出服务限制 检查服务日志中是否有请求体大小报错 增加请求体上限,或在客户端先做音频压缩
用户打断时系统仍在播放错误内容 缺少音频打断机制 实际模拟用户打断 接入音频中断检测;播放 TTS 时检测到人声立即停止输出

10. 最佳实践与工程建议

10.1 先跑最小闭环

第一次搭建,不要追求全链路都用最强模型。建议先把最简版本跑通:

  • STT 用本地小模型或者云端 API。
  • Agent 用小参数量化模型。
  • TTS 用轻量合成模型。

最小闭环跑通后再逐步替换模型,能有效降低排查难度。

10.2 三个模块独立演进

STT、Agent、TTS 必须保持接口稳定。任何模块升级都不应影响其他模块调用方式。这样 STT 从 A 模型换到 B 模型,Agent 层不需要改动。

10.3 数据目录规范化

录音输入、临时媒体、合成输出要分目录管理,并且定期清理:

input_audio/    # 用户录音输入
output_audio/   # TTS 合成输出
temp_media/     # 临时中转文件
logs/           # 日志

批量任务处理完的中间文件要及时清理,避免磁盘写满。

10.4 服务安全与权限控制

模块化服务默认只监听本机地址,不要直接暴露到公网。如果需要提供给其他机器调用,添加访问控制和安全验证。Agent 能调用工具意味着它具备操作能力,工具执行前必须做权限校验和数据合法性校验。

10.5 合规与授权提醒

再强调一次:采集用户语音必须获得明确授权;使用 TTS 合成模仿真实人物的声音,必须获得本人授权;涉及个人信息、客户订单、支付信息的查询和操作,必须经过用户确认并在日志中留痕。发布到生产环境之前,建议让法务和业务团队共同审核交互流程。

10.6 日志与指标体系

生产环境建议记录以下指标:

  • 三段服务各自的耗时。
  • 全链路成功率。
  • STT 识别置信度。
  • Agent 工具调用成功率。
  • TTS 合成失败率。
  • 用户打断次数和意图改变次数。

有了这些数据,才能持续优化交互体验。

11. 总结与下一步

级联式三明治架构的核心价值在于:它把语音交互从“识别-匹配-播报”升级成了“识别-理解-行动-表达”。STT 负责听清,Agent 负责听懂和操作,TTS 负责说好。每一层职责单一,都能独立替换和独立扩容,这个设计对团队协作也很友好,语音组、算法组、业务组可以并行开发。

建议先做三件事:第一,把三个模块的最小闭环跑通,用一句真实语音验证“音频进、音频出”是否顺滑;第二,重点测试 Agent 的 Function Calling,给语音助手绑定一个真实业务工具,验证“能干活”而不是“只会聊”;第三,把三段耗时打印出来,找到全链路延迟的瓶颈。

最容易踩的坑有三个:一是把 LLM 的上下文无限放长,导致显存持续增长;二是工具描述写得含糊,Function Calling 频繁提取错参数;三是只在小样本上测试,没在噪声环境、多人说话场景下做 STT 压力测试。这些细节都要在实际部署中反复调。

下一步可以考虑接入流式 STT 和流式 TTS,让用户刚说完就能听到反馈,而不是等整段链路结束。再往后可以做用户画像和历史记忆,让语音助手记住用户的偏好。如果团队资源充足,还可以把 Agent 层从单模型升级为多模型路由,简单问题用小模型快速响应,复杂任务再交给大模型,兼顾成本、延迟和效果。

建议收藏备用,实际部署时按照这篇文章的分层思路,先把模块拆开,再串联起来。

Logo

火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。

更多推荐