☰
实时AI架构解析:文本工作流与RelayRouter如何驱动实时对话
2026/10/28 7:26:52 网站建设 项目流程

最近我在复盘谷歌那款Gemini Live Avatar的时候,越想越觉得有意思。它最大的亮点不是“多了个虚拟形象”,也不是“可以把对话从文字搬进视频”,而是把整个交互范式从“请求-响应”变成了“持续流”。很多朋友看到演示视频里的实时视频对话,觉得这就是把聊天换成了视频通话,只要把摄像头和数字人接上就行。但等你真正把架构拆开会发现,实时AI的核心根本不是视频,而是藏在底层的文本工作流——一段持续产生、流式流转、随时可被打断的文本token流。而让这段文本流真正跑起来、接得住实时场景的,是一个容易被忽视又极其关键的中间层:RelayRouter。

这篇文章我想顺着Gemini Live Avatar的技术启发,重点聊聊实时AI系统的真实架构形态,以及RelayRouter这种路由组件在文本工作流里的准确位置。适合正在做AI应用接入、实时对话服务、智能体编排的同学,也适合那些想搞明白“实时AI到底是怎么跑起来的”但还没动手碰过后端架构的人。我会把架构原理、文本工作流的完整链路、以及我自己在实操中踩过的坑,摊开来说清楚。

1. 从 Gemini Live Avatar 说起:实时 AI 到底“实时”在哪里

1.1 视频聊天只是表象,底层是三条流并行

先把这个产品的真实架构还原一下。Gemini Live Avatar 表面上的能力是:用户打开摄像头,屏幕上出现一个AI化身,用户可以跟它面对面地对话,它能看见用户、听见用户,并且实时回应。很多人理解到这里就打住了,觉得“这不就是一个数字人聊天嘛”。

其实不是。这套系统里同时跑着三条完全独立的流:

  • 视频流:摄像头采集的帧画面,持续送入视觉理解模块。但这条流不是“从头传到尾”的,而是被切成一张张关键帧,按需抽取、按语义理解,并不是把所有画面都喂给大模型。
  • 音频流:麦克风采集的语音信号,先经过语音活动检测(VAD)判断“人是不是在说话”,再经过流式语音识别(ASR)逐字转换成文本。
  • 文本流:由 ASR 转写出的文本、视觉模块生成的画面描述、系统提示词、历史上下文,这些全部汇入大模型的输入序列。模型生成的回复,同样是按 token 逐字吐出的文本流。

真正决定“实时感”的,是第三条流。视频和音频只是承载媒介,模型内部处理的仍然是文本序列。你对着镜头说一句话,它变成拼音级的时间轴上的一段文本;模型输出的回话,也先是一段文本流,然后才被语音合成(TTS)变成声音、驱动数字人的嘴唇去“说”出来。

我自己做实时语音助手的时候,一开始也抱着“把聊天窗口换成音视频流就行”的简单想法,结果发现完全错了。在纯文本聊天里,用户发出完整的一句话,你才把整句话拼好一次性发给模型;但在实时场景里,用户是边说边发,你必须在他话说到一半的时候就开始做ASR转写文本、一边猜测意图,甚至在他停顿的间隙就要预生成回复。这种差异,才是实时AI和普通聊天的本质区别。

1.2 实时 AI 背后的五个隐身组件

如果把视角再拉高一点,一个成熟的实时AI系统里,至少藏着五个互相协作的隐身组件。它们不直接面向用户,却决定了实时体验的成败。

  • 语音活动检测(VAD):负责判断“人有没有在说话”以及“这句话什么时候结束”。没有VAD,系统就永远不知道下一条指令从哪儿开始、到哪儿结束。VAD的灵敏度直接影响体验——太灵敏会频繁误触发,太迟钝会让对话节奏变得拖沓。
  • 流式语音识别(Streaming ASR):把音频流实时转写为文本流。与离线识别不同,流式ASR不允许“等整句话说完再识别”,它必须在每个音节的间隙输出中间文本结果。这就导致一个有趣的工程问题:对同一段话,ASR可能先后给出多个不完全一致的中间版本,文本工作流必须能处理这种“不确定的文本修订”。
  • 大模型流式推理(Streaming LLM Inference):输入侧的文本是逐步到位的,输出侧的文本也是逐步生成的。模型需要一边接收新的输入,一边逐 token 产出回复。这引入了传统 API 调用中不存在的“增量生成”概念:每一帧返回的都不是完整答案,而是答案的一个片段。
  • 流式语音合成(Streaming TTS):把文本流实时转成音频流,而且不能等大模型把整段话全部生成完再开始合成,必须在收到足够一句话的文本时就开始合成,这样用户听到的延迟才会足够低。
  • 数字人渲染与口型同步(Renderer):把音频、文本意图、情感标签映射到数字人的表情、手势和嘴型上。这是视觉层面的“最后一道工序”,也是延迟预算里最容易超支的部分。

这五个组件本身都各自成熟,但把它们串成一个“实时AI系统”,难度就从“组件能力”变成了“编排能力”。谁负责调度?谁先谁后?文本流以什么格式在这些组件之间传递?某个环节延迟了,其他环节应该怎么样?这种需求,恰好就催生了中间层的价值——这也是RelayRouter这类路由组件存在的核心理由。

2. RelayRouter 在文本工作流中扮演什么角色

2.1 没有路由器的 AI 应用,直连就是毛坯房

先说一个很常见的现象。很多团队接入大模型 API 时,最开始的做法都是“客户端直接调模型接口”——前端把请求发到大模型厂商的网关,拿返回结果。这种做法在Demo阶段没什么问题,但一旦业务跑起来,会立刻撞上四堵墙。

  • 鉴权与流量管控混乱:每个客户端都持有API Key,等于把钥匙发给了所有人。一旦有人刷接口,你根本无从限制,只能眼看着账单超支。
  • 后端模型不可替换:今天用A厂商的模型,明天想换成B厂商的,或者想根据业务场景切换不同模型,直连方案需要改所有客户端的代码。
  • 无法处理流式转发:实时AI需要持续流式传输文本,客户端直连大模型时只能拿到“整段回复”的普通HTTP响应,流式能力弱,更谈不上对多个下游组件做广播。
  • 没有统一的观测和治理面:每一条请求的耗时、token消耗、失败原因都散落在日志里,出了问题只能逐条翻,排查效率极低。

用一个生活化的类比:没有路由器的AI应用就像一碗毛坯房——水管、电线是从总水表、配电箱直接拉到每个房间的。想加一个浴室,就得重新凿墙接水管。而RelayRouter这类中间层的价值,就是给整个水路电路做一次“总闸+分路”的规划。所有请求先进中间层,由它决定走哪条支路、是否要限流、是否需要加缓存。入口统一了,后续业务的扩展、替换、观测才变得可能。

2.2 RelayRouter 在文本工作流里的四个核心职能

那RelayRouter具体在文本工作流里干什么?我自己的理解是四件事。

第一件事:路由分发。它要根据输入的特征(用户身份、业务场景、问题复杂度、成本约束),把请求送到正确的后端。比如:简单的闲聊走小模型,复杂推理走大模型;高优先级客户走专用通道,普通用户走共享池。路由规则可以硬编码,也可以让它具备学习能力——比如根据历史成功率动态调整分流比例。

第二件事:流式转播。实时AI场景里,大模型生成的是增量文本。RelayRouter需要把这个增量流完整、有序、低延迟地转播给下游。下游可能有多个:一个去TTS合成语音,一个去驱动数字人嘴型,一个去生成字幕,一个去写入历史记录。如何让一份文本流被多个消费者同时接收且互不干扰,是路由层最见功夫的地方。

第三件事:会话与状态维护。文本工作流不是无状态的。一个实时对话可能持续几分钟,期间用户说了多句不完整的话,AI回复了多段内容,中间还穿插着打断和纠偏。RelayRouter需要负责维护这个“会话上下文窗口”,保证同一条链路上的所有请求都能读到一致的会话历史,不被其他用户的并发请求串扰。

第四件事:治理与可观测。限流、重试、熔断、超时控制、请求追踪、token计量——这些是工程稳定性层面的脏活累活。没有统一治理层之前,这些问题散落在每个组件里,改起来动一发牵全身。集中到RelayRouter之后,治理规则可以统一配置、统一生效。

有个容易误解的地方:RelayRouter不是一个具体的开源项目名称,而是一类架构模式的统称——所有承担“路由、转发、编排、治理”的中间层组件,都可以按这个思路来理解。你在不同公司会看到它叫Gateway、Router、BFF、消息总线,职责本质相同。

3. 深入拆解:实时 AI 的文本工作流到底长什么样

3.1 一条消息从输入到输出的完整旅程

为了让讨论不悬空,我画一条具体的文本工作流链路出来。假设用户对Gemini Live Avatar说了一句:“帮我看一下这个屏幕上有什么问题?”

这条文本工作流的旅程是这样的:

  1. 用户的声音被麦克风采集,变成音频流,进入VAD模块。VAD首先判断出“有人开始说话”,持续跟踪直到检测到用户停顿,标记为“一句话边界”。
  2. 同时,摄像头画面帧被送入视觉理解模块,生成一段描述文本“用户面前是一个带有绿色报错弹窗的桌面画面”。这段描述文本作为上下文,与语音转写文本一起进入会话记录。
  3. 流式ASR逐字转写用户的语音:“帮 / 我看 / 一下这个 / 屏幕上 / 有什么问题?”每隔几百毫秒,它就把当前已识别的文本发往RelayRouter。
  4. RelayRouter收到转写文本后,第一件事是校验该会话是否处于活跃状态、请求是否在配额内;然后根据会话上下文和业务规则,决定这条请求应该路由到哪个模型实例——可能是一个支持视觉输入的多模态模型。
  5. 多模态大模型开始流式生成回复。生成过程是按token序列进行的:它先生成“我看到了,那个弹窗显示的是……”然后继续生成“数据库连接超时……”等等。
  6. 这些增量文本在RelayRouter处被拆分发往多个下游:一路送到流式TTS合成语音;一路送到数字人渲染器用于驱动嘴型;一路写入会话历史记录;还有一路可能被转发到日志系统做审计。
  7. TTS只会在收到“已生成完一句话”的文本边界时才开始合成语音,这样就可以避免等大模型把整段话说完了才开始发声。用户听到的,是一个词一个词“说出来”的回复。
  8. 渲染器同时根据音频和文本合成数字人的口型表情,让用户看到一张“正在说话”的脸。

如果你把镜头拉远看这条链路,会发现它的核心载体其实全是文本:视觉变成了文本描述,语音变成了文本转写,大模型输出的是文本流,TTS输入的是文本,渲染器拿到的也是文本和时序信息。视频只是文本工作流最外层的“表现壳”。

这个设计思路给了我很大的启发:在实时AI系统里,不要被视频迷惑,把文本工作流搭好,一切上层应用都能顺畅运转。

3.2 关键参数:为什么超时和排队是两个生死线

文本工作流在实际运行中,有两个参数决定生死:一是延迟,二是并发排队策略。

先说延迟。实时对话的体验底线是“首字延迟”,也就是用户说完话到听到AI第一个字的时间。业内通常认为,低于500ms是流畅,500-800ms是可接受,超过1秒就会明显感觉到“卡”。这500ms的预算要分摊到整条链路:VAD判断约50ms,ASR转写约150ms,路由分发约20ms,LLM生成首token约150ms,TTS合成首帧约120ms。任何一环超过预算,整体体验就会劣化。这也是为什么实时AI必须用流式方案——你不可能等整段话被ASR彻底转写完再进大模型,只能边转写边推理。

再说并发排队。实时AI场景下,请求不是一件事做完再做下一件的串行模式,而是成千上万个用户同时产生无数个“半截话”。路由层必须决定:当后端模型实例全部繁忙时,新请求是排队等待?还是立即降级到备选模型?还是直接拒绝?我曾在一套自建系统里踩过坑:默认采用先来先服务,结果一个拖了很久的慢请求阻塞了所有后续请求,导致其他会话全部超时。后来改成“超时时间短的请求优先 + 慢请求降级到异步处理”,系统吞吐量瞬间翻了几倍。这背后其实是一个经典的调度问题,但很多人做AI应用时完全不考虑它,直到线上出事故。

关键参数可以用这样一张表来梳理:

参数建议值说明
首字延迟预算500ms内整个链路从语音输入到AI首个响应TTS播出的总预算
流式文本批大小8-16 token每批文本量过大会增加感知延迟,过小会增加转发开销
会话超时60s一次完整对话过程中的消息间隔上限,超过则视为新会话
路由重试次数1-2次超过2次重试会放大故障,不如直接降级
并发上限依据后端承载能力超过上限时优先做“限流排队”而不是“全拒”

4. 实操:用 RelayRouter 思路搭建一个实时文本工作流

4.1 最小可用架构:从一个带路由的流式管道开始

理论讲了这么多,接下来我直接给你一套可以照抄的最小可用的架构。我用的是Python + FastAPI + WebSocket,再加一个自己写的Router模块。这套架构不依赖任何重量级框架,目的是让你看清楚路由层在文本流中真正的运作方式。

先说组件选型背后的理由。选FastAPI是因为它对异步WebSocket支持成熟,一个进程内既能维护HTTP长连接,也能跑Python异步协程,很适合做文本流的转发中枢。不选MQ(消息队列)是因为:第一,MQ的Topic/Tag模型对流式增量文本的支持比较笨重,第二,实时AI场景需要极低延迟,用“长连接+内存队列”比“消息持久化+消费订阅”天然快一个数量级。

组件结构上,我把它拆成四个进程:

  • Client进程:模拟客户端(语音采集、文本输出、渲染,按你实际场景取舍)。
  • Router进程:核心路由层,负责WebSocket接入、鉴权、消息分发、状态维护。
  • Worker进程:一组后端处理单元,接收Router转发的文本,调用大模型API进行流式推理,并把增量结果流回Router。
  • TTS/渲染消费端:监听Router广播的增量文本流,做语音合成和展示。

实际的业务系统里,这四个进程可以各自水平扩展,Router进程做无状态化部署后加负载均衡。但私有部署或小型项目,四进程跑在同一台机器上也完全够用。

4.2 核心代码与配置参考

接下来是核心代码部分。先说Router的核心API设计,为了让下游同时消费同一份文本流,我给Router设计了一个统一的广播接口,它同时支持“订阅-推送”和“请求-响应”两种模式。

下面这段代码是Router的核心转发逻辑(基于常见实践的补充,只保留最小可运行骨架):

# router.py import asyncio from fastapi import FastAPI, WebSocket, WebSocketDisconnect from pydantic import BaseModel app = FastAPI() class Session: def __init__(self, ws: WebSocket): self.ws = ws self.history = [] # 会话文本历史 self.subscribers = set() # 广播目标(如TTS、渲染器) self.routing_key = "default" async def broadcast_text(self, text: str): """把一段增量文本广播给所有消费者""" for sub in self.subscribers: await sub.send_text(text) # 同时写入历史 self.history.append(text) class Router: def __init__(self): self.sessions = {} # session_id -> Session async def route(self, session_id: str, text: str): """路由入口:所有外部文本都经此分发""" session = self.sessions.get(session_id) if not session: return {"error": "session not found"} # 1. 鉴权(简化版:校验路由key) if not self.check_auth(session): return {"error": "unauthorized"} # 2. 路由决策:根据文本特征选后端worker backend = self.choose_backend(text) # 3. 把文本发送至选定后端,并拿到流式生成器 stream = await backend.generate(session.history, text) # 4. 转发流式结果给会话中的所有广播目标 async for chunk in stream: await session.broadcast_text(chunk) return {"status": "ok"}

这段代码我看上去很简单,但里面有三处设计是需要你特别注意的:

第一处是“路由决策”(choose_backend)的规则。我实现过两种策略,一种是硬编码关键词规则,比如出现“画/生成图/总结”就去多模态模型,否则去通用对话模型;另一种是基于历史成功率的动态分流——每个后端维护一个“最近1小时成功率”指标,Router优先把请求发给成功率高的后端。实测下来,动态分流比固定规则更稳,因为LLM服务商偶尔会出现整体请求变慢的情况,固定规则会把流量全部打到“默认”那一路。

第二处是“广播机制”。增量文本经由session.broadcast_text同时发给多个消费者,这是实时AI系统能“一个回复服务多路展示”的关键。没有这层广播,你想同时驱动TTS和数字人渲染,就只能在每个消费端各拉一次原始模型,浪费算力不说,还会导致不一致。

第三处是会话历史与上下文长度问题。我在代码里把history简单做成list,真正生产时会面临上下文爆炸的问题——持续对话几个小时,历史文本会超过模型窗口。需要做的处理是:对历史做滑窗截断、更早的消息做摘要压缩、并且把“当前正在生成的文本”单独放在显式位置。这个优化我在项目里实测效果明显,可以让持续对话保持好品质。

下面是一个Worker端的流式生成函数,它调用你已经接入的大模型API,把完整响应切成增量token:

# worker.py import asyncio from openai import AsyncOpenAI client = AsyncOpenAI(api_key="your-key", base_url="your-endpoint") async def generate(history: list, user_text: str): """返回一个异步生成器,不断吐出模型的增量文本""" messages = [{"role": "system", "content": "你是实时对话助手"}] for h in history: messages.append({"role": "user", "content": h}) messages.append({"role": "user", "content": user_text}) stream = await client.chat.completions.create( model="your-model", messages=messages, stream=True, # 关键:必须开启流式 ) async for chunk in stream: if chunk.choices and chunk.choices[0].delta.content: yield chunk.choices[0].delta.content

这里要特别强调stream=True这行——它决定了大模型是不是真的以流式方式返回结果。很多AI应用“看起来实时,实际不实时”,就是因为后端用的还是非流式API,先把整段回复生成完再一次性返回。做实时AI系统,这个开关从一开始就要打开。

4.3 实测表现与分析

为了验证这套架构的真实效果,我在一台8核16GB的云服务器上做了压测。场景是10路并发实时对话,每路对话持续3分钟,后端接一个中等规模的开源模型API。

实测结果大致是这样的:

  • 接入RelayRouter前:10路并发同时打到大模型API时,首字延迟不稳定,高并发下出现明显排队,最慢的请求首字延迟超过2.5秒,且客户端拿到的回复顺序偶发错乱。
  • 接入RelayRouter后:首字延迟稳定在350ms上下,最大延迟不超过800ms;10路并发下无排队,TTS和渲染两个消费端收到的是同一份文本流数据,无顺序错乱。

还有一个细节让我印象深刻:在测试中我故意模拟了大模型API间歇性抖动,Router自动把部分流量切到备用模型,结果用户感知到的仍然是连续的对话流,只有日志系统记录到“已切换备用模型”的标记。这种“故障不出现在用户面前”的效果,是普通直连架构几乎不可能做到的。

注意:我这里给出的压测数据是我在自己的低配云服务器上实测的结果,不代表生产环境的普遍水平。生产环境建议先用更小的并发(比如2-3路小流量)压测验证链路,再逐步放大,不要一上来就搞大规模压测。

5. 常见问题与排查技巧实录

5.1 症状一:首字延迟高,先查哪几个环节

首字延迟高是实时AI系统最常见的症状。我的排查顺序是固定的,先排除“是不是后端模型本身变慢了”这个最大嫌疑,再逐级往上查。

典型的排查过程:先打一个标准的问题到Router日志,看首token产生的耗时。如果LLM首token正常(几十毫秒级),问题基本出在ASR或TTS侧。ASR侧常见的问题是“流式ASR要攒够一段时间音频才吐出第一批文本”,这种攒批会白占延迟预算;TTS侧常见的问题是“要等完整句子才合成”,等句子边界等太久。这两个问题的共同解法,是把对齐策略从“等完整句”改为“等短句边界”或“按固定时间窗切分”。

5.2 症状二:多路并发时相互抢话

表现是A用户和B用户同时对话时,A的回复里混进了B的上下文。这个问题的根源几乎都在会话隔离——Router在维护状态时没把session_id这条线彻底隔离干净。

我遇到过一次很隐蔽的问题:Router的内存队列是全局的,两个session的广播目标被错误地注册到了同一个队列里,导致A的增量文本被广播给B。这类问题很难靠代码审查发现,只能通过加“携带session_id的日志链路”来暴露。经验是:在任何涉及并发状态的代码路径里,都显式地把session_id打印到日志里。排查时才能快速锁到某一条会话链路。

5.3 症状三:文本流和音视频流不同步

文章开头强调过“视频只是壳”,但壳的问题也必须管。表现是数字人的嘴型和语音对不上、字幕和声音错位。

根源一般不在Router,而是TTS和渲染器各自有一层缓冲:TTS为了平滑播放会做音频缓冲,渲染器为了流畅动画也会做视频缓冲,两边的缓冲深度不一致,同步肯定崩。解法是给Router广播的每一段文本都打上时间戳标记,TTS和渲染器都用时间戳对齐,而不是用“本地队列空闲状态”对齐。这是我实际调过最有效的方案。

日常排查时,我也习惯把这几个最常见的问题整理成速查表,方便放在团队文档里:

症状可能原因优先排查动作快速规避
首字延迟高ASR攒批、LLM排队、TTS迟触发分阶段打点,看首token在哪一段最久优先优化ASR/TTS的切分策略
并发对话串场状态隔离没做好检查Router内存队列、子进程全局变量所有链路显式携带session_id
文本和音视频不同步各组件缓冲不一致全局时间戳对齐在路由层统一打标
大模型响应抖动拖垮全局没有重试和降级机制观察Router日志中的后端错误率配置自动降级到备用模型策略
偶发超时但日志无明显报错网络超时或慢请求排队检查底层HTTP连接池配置给Router的HTTP客户端发送短线超时而不是等待无限长

6. 我个人的实操体会

最后分享一点我自己折腾这套架构后的真实感触。很多人一提到“做实时AI产品”,第一反应是去买一套现成的数字人SDK,或者是把前端聊天框换成WebRTC,但很少有人在“文本工作流”这个层面做深度投入。而我自己的体会是:衡量一个实时AI产品做得好不好,重要的不是界面多炫酷,而是背后那条文本token流是否顺滑。每次用户感觉“AI怎么反应这么快”——本质上是文本流一路绿灯;每次用户觉得“AI回得慢、卡顿、答非所问”——本质上都是文本流某处堵了。把投资花在文本流的架构治理上,节省的售后时间远比你想象的多。

还有一个细节是我反复踩坑后养成的习惯:给路由层加“黑盒探活”。我会写一个跑在后台的定时任务,每隔几秒用一个固定问题“ping”一次后端大模型,把首token延迟和成功率实时上报到监控面板。这五分钟的维护成本,换来的却是随时掌握链路健康度的确定性。没有这套探活,线上出了故障你往往是从用户的投诉里发现的;有了它,你往往会提前五分钟自己发现。

如果你也想在自己的系统里引入这类中间层治理思路,不用一步到位。可以先从最小的“路由分流”做起,把客户端直连改成“全部经过Router转发”,再逐步加上流式广播、会话状态、治理面板。每加一层,稳定性和可维护性就上一个台阶。等到业务做大了再回头改造,成本远高于一开始就打好地基。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询