☰
实时AI文本工作流核心:RelayRouter消息路由与低延迟流式架构
2026/10/7 13:31:28 网站建设 项目流程

1. 题外话:别把实时AI想成“带画面的聊天”

这两年被“实时AI”这个词轰炸得有点麻木,大部分宣传片里都是用户在跟一个虚拟人脸视频通话,助手不仅听得懂,还能实时生成表情回话。看得多了,很多人下意识觉得“实时AI”就等于“把聊天变成视频”。但我在实际折腾后台工作流时越来越确信:实时AI的核心不是画面,而是低延迟的请求-响应循环。视频、语音、AR面具都是这个循环的不同出口,可在文本工作流里,实时AI同样成立,而且更有工程味道。

我为什么会关注到这一点?因为手里正在做的一套文本生成服务,要求做到“用户敲完半句话,模型就开始补全下一位”,整套链路不需要任何视频,也不需要语音合成,只需要极快地接收流式token、做路由判断、再把结果回灌到上游。这里面真正卡脖子的,不是模型本身,而是消息路由。一次不合理的路由,可能让整个实时体验从“跟真人对话”退化成“发帖等审核”。这也是我研究Gemini Live Avatar那套交互逻辑之后,回过头来把重心放在RelayRouter上的原因。

这套经验适合谁?适合正在做聊天机器人、实时写作助手、自动化文本代理的开发者,也适合对“流式AI架构”感兴趣、想搞清楚实时AI到底吃什么硬件、拼什么软件的同学。今天我尽量把架构、路由、流式链路、坑位一次讲透,不绕弯。

2. 从Gemini Live Avatar看“实时”到底实时在哪

2.1 真人级对话体验的四道关卡

Gemini Live Avatar最惊艳的地方,是它把“电视新闻主播式”的互动搬到手机上。但拆开看,它跟普通ChatGPT语音模式没有本质区别,都是在解决四件事:听到、听懂、想好、说出。

  • 听到:麦克风低延迟采集,噪声抑制,断句切分。
  • 听懂:流式ASR(自动语音识别)在说话过程中就出中间结果,而不是等你说完。
  • 想好:LLM边收边出,以token流的形态预测回复。
  • 说出:TTS流式合成,第一个音节在LLM输出第一个完整词后就能合并。

这四步之间任何一步出现超过200毫秒的延迟,人就会觉得“卡”。实测里,Avatar能稳定压在1秒内出首字,靠的不只是模型快,更多是靠每个环节都在流式工作。这提醒我一件事:实时AI从来不是一个模型的事,而是一整条流水线的协同。

2.2 视频只是其中一种输出模态

Gemini Live Avatar之所以给人一种“颠覆感”,是因为它把输出从声音扩展到视频。但它的输入输出通道设计和文本聊天其实是共用的:同样的思维链、同样的记忆管理、同样的上下文拼接,只是最后把token渲染成表情和音频。

打个比方,实时AI是一台发动机,视频是跑车外壳,语音是喇叭,文本则是仪表盘。你不能因为外壳最抢眼,就以为发动机是给外壳造的。放到工程语境里,如果我只需要文本通道,我完全可以用那套“实时”的内核,把输出模态换成一个Markdown流,这正好是RelayRouter的机会:它负责在正确的时间把文本token送到正确的处理节点。

2.3 文本工作流才是实时AI最普及的场景

视频对话听着酷,可真正每天被调用几千次的实时AI场景,反而是文本:代码补全、会议纪要实时润色、客服实时推荐回复、写作助手边写边补。这些场景对画面没有需求,对“延迟”和“路由正确性”却极其敏感。

举个例子,我用过一个内部的知识库问答助手,用户提问后,如果路由层先去调一堆不相干的插件,再返回答案,整体体验会特别“笨”。后来我把路由策略改成“先做意图分类,再做工具挑选”,首token时间从1400毫秒降到600毫秒。这个优化对视频场景无所谓,对文本工作流却是生死线。

3. RelayRouter到底是个什么层

3.1 我们得先承认:LLM本身不擅长“决定下一步该干嘛”

纯LLM擅长的是续写,也擅长根据指令输出结构化内容,但让它自己决定该调用哪个工具、该跳过哪些插件、该把哪段上下文先送入窗口,它经常犹豫或出错。现实中,我们要么用Agent框架,要么在代码里写硬路由,可两者都有问题:Agent框架太黑盒,硬路由太僵硬。

RelayRouter的定位刚好落在这两者之间:一个显式的、可观测的、面向流式消息的路由层。它不替代模型,也不替代业务逻辑,它只回答三个问题:

  • 这条消息要去哪?
  • 它需要带哪些上下文?
  • 它要优先被谁消费?

在文本工作流里,回答完这三个问题,实时性就有了骨架。

3.2 RelayRouter的四大核心能力

我基于自己搭过的路由层,把RelayRouter的核心能力拆成四块,这也是你在选型或自建时最重要的参考维度:

  • 消息归一化:把不同来源(WebSocket、HTTP回调、Message Queue)进来的文本统一成一个内部消息结构。
  • 策略路由:基于规则、意图识别结果、context变量,把消息派发到不同处理器。
  • 流式转发:不缓存完整响应,而是在接收模型token的同时转发下游,实现端到端流式。
  • 回退与熔断:下游处理器超时或报错时,自动走备用路径,而不是让整个请求失败。

这四块能力听起来不难,但拼在一起要稳、要低延迟、要可观测,工作量非常大。RelayRouter的价值在于,它把这个层做成标准件,让我不用每次重写。

3.3 为什么不是“直接用消息队列”

有人会问,Kafka、RabbitMQ不也能路由吗?确实能,但那是为异步持久化设计的。实时文本工作流的特征是:消息生命周期短、延迟敏感、需要感知流式增量。传统MQ先落盘、再消费的模式,在首token延迟上就输了。

RelayRouter这类轻量路由层更适合直接嵌在应用进程或旁路进程里,优先保证毫秒级转发,而不是保证“消息永不丢失”。在实时场景,偶尔丢一条中间态消息,远好过让用户多等半秒。

4. 手把手搭一套基于RelayRouter的实时文本工作流

4.1 目标场景设计

为了把话题落地,我设计了一个具体场景:做一个“实时技术文章助手”,用户在编辑器里敲一句titel,系统自动把标题补全成摘要和关键词列表,并在用户继续输入时增量修正。

整个工作流的步骤是:

  1. 前端把用户敲击的文本通过WebSocket推给中继服务。
  2. 中继服务用RelayRouter接收消息。
  3. RelayRouter做意图判断,决定是调用标题生成模型、摘要模型还是关键词提取模型。
  4. 每个模型结果统一从RelayRouter流式返回前端。

这个场景不需要视频不需要语音,但你能清清楚楚看到“实时AI”的芯。

4.2 消息结构定义

所有进入RelayRouter的消息,我先定义一套统一的Schema。不要小看这一步,混乱的消息结构是路由层最大的敌人。

{ "messageId": "uuid-v4", "sessionId": "session-123", "timestamp": 1699999999999, "source": "editor:main", "channel": "text/stream", "payload": { "text": "实时 AI 不只是把聊天变成视频", "cursor": 12, "meta": { "userId": "user-07", "docId": "doc-99" } } }

统一结构的好处是:路由规则不用关心上游协议,处理器也不用关心消息来自WebSocket还是HTTP。只要schema统一,后面加新类型的输入源只需要写适配器。

4.3 路由规则配置

RelayRouter支持配置文件定义路由规则。下面是一段伪配置,但基本逻辑跟实际配置一致:

routes: - name: "title_enrich_route" match: intent: "enrich_title" sessionLength: ">= 3" to: "enricher-service" contextPolicy: includeLast: 5 maxTokens: 800 streaming: true - name: "keyword_extract_route" match: intent: "extract_keywords" confidence: ">= 0.7" to: "keyword-service" streaming: true - name: "fallback_route" match: catchAll: true to: "fallback-llm" streaming: false

每个路由都绑定了一个“意图”,这个意图可以由小模型在线分类产出,也可以由前端直接传入。第一版建议前端传入,跑通后再升级为模型判断,这样排障方便。

4.4 流式转发的实现要点

路由层转发LLM的流式响应时,最怕的是“缓存积累”。很多初学者会把LLM的完整响应存进一个Buffer,然后一次性转发给前端。这等于把流式交互变成了“等全文生成完再发”,实时性瞬间归零。

正确做法是拿到增量就转发增量。以Python为例,核心伪代码如下:

async def forward_stream(route, message): async with httpx.AsyncClient(timeout=None) as client: async with client.stream( "POST", route.upstream_url, json={"prompt": message.payload["text"]} ) as resp: async for chunk in resp.aiter_bytes(): # 解析chunk,通常是SSE格式 tokens = parse_sse_tokens(chunk) await relay_to_websocket(message.session_id, tokens)

注意keep-alive。如果上游模型长时间没有输出token,连接可能被中间代理掐断,需要在路由层维护心跳。

4.5 上下文管理策略

实时文本工作流里的上下文管理比离线场景更“抠”。因为用户还在打字,历史上下文可能还在变,路由层必须决定“快照哪一段上下文”。

我目前的策略是三层:

  • 核心上下文:系统提示词、用户当前输入,永不截断。
  • 短期上下文:最近几轮对话,尽量保留,但超长时按窗口比例压缩。
  • 长期记忆:从向量库里检索的片段,只把与当前输入相似度最高的前三条注入。

RelayRouter允许在路由规则中指定contextPolicy,目的就是不让每个处理器各自管理上下文,由路由层统一装配,避免上下文漂移。

4.6 连接管理与背压

实时WebSocket连接成千上万时,背压问题就会出现:处理器能力不足,但路由层还在拼命转发,最终导致内存暴涨。我这里有一个必须强调的血泪教训:流式系统一定要做背压控制。

我在RelayRouter里设置了一个简单信号量机制:

from asyncio import Semaphore, Queue, TaskGroup class StreamBridge: def __init__(self, max_concurrent: int): self._sem = Semaphore(max_concurrent) async def dispatch(self, message, route): async with self._sem: await forward_stream(route, message)

当并发超过阈值时,新请求进入等待队列,而不是直接涌向上游。这会导致个别请求的首token变慢,但能保住整体服务的稳定,不会出现“连环超时”。体验上,偶发变慢可以忍,全盘崩溃不能忍。

4.7 可观测性设计:追踪一条消息的一生

路由层能不能做好,最终看可观测性。我每次排查线上问题都靠“消息轨迹”:一个messageId从进来到离开,经过了哪些路由节点,每一跳耗时多少。

RelayRouter给我提供了这样一个表格视图:

节点耗时(ms)状态备注
ingress_ws12ok收到用户输入
intent_classifier38okintent=enrich_title
router_rule_match4ok命中title_enrich_route
upstream_llm_first_token520ok流式开始
upstream_llm_complete1930ok全部token发完
relay_to_frontend16ok推送结束

有了这张表,任何“哪一段慢了”都一目了然。我建议你在设计阶段就要求路由层导出这些trace数据,别等服务出事再补。

5. 踩坑实录:实时文本工作流最容易翻车的5个问题

5.1 流式响应的“断句”导致下游拼接混乱

LLM流式返回的token经常是半截,比如一个词的拼音被拆成几片。如果你在下游处理器里做“关键词提取”或“拼写检查”,拿半截词去处理就会误报。我踩过真实的坑:标题生成器连续收到“实”“时”“AI”三个token,把它当三个词,结果输出的扩展标题完全跑偏。

解决思路:在路由层为上向下游 NER(命名实体识别)场景引入“延迟窗口”,等语义完整的片段(比如句子边界或空格)出现后再投递,而不是每个token都投。实时性损失极小,但准确性大幅提升。

5.2 模型等待队列导致“假实时”

只优化单次推理延迟不够。多个用户同时触发请求时,如果模型服务排队很严重,实时体验照样崩。关键指标不是P50,而是P95。P50看着200毫秒,P95跑到5秒,用户感知就是“时快时慢”。

应对办法是容量预估和限流。我习惯按P95倒推:如果目标P95是800毫秒,模型单次推理300毫秒,那么每实例可用并发约2-3,然后根据在线峰值算好实例数。RelayRouter的并发信号量在这里正好充当“网关限流器”。

5.3 重试风暴让下游雪崩

某个模型服务不稳定时,路由层很容易触发重试逻辑。如果没有退避机制,一旦同时重试几十条消息,下游瞬间被打死。

我在RelayRouter里对重试做了两层约束:

  • 单条消息最多重试2次。
  • 两次重试之间至少间隔800毫秒。

这两条看似简单,实际能避免80%的雪崩场景。

5.4 前端断连后,路由层还在继续转发

用户关闭浏览器,WebSocket断开,但上游LLM还在生成。如果我们不取消上游调用,那些无用token会继续占用带宽和算力。正确的做法是在路由层监听WebSocket断开信号,并调用上游的cancel接口终止生成。

如果上游不支持cancel,至少丢弃后续token并释放信号量,避免连接资源被无用请求占着。

5.5 上下文被重复注入导致费用爆炸

在流式场景,用户每敲一个字符,如果都把全部历史消息塞进提示词,token消耗会随时间二次方增长。我见过月度成本飙到原来20倍的案例,纯粹是上下文管理太粗糙。

建议按会话状态做“动态截断”:只有用户停顿超过600毫秒时才触发一次完整上下文组装,否则只把增量部分发给模型。RelayRouter的会话级缓冲在这里很管用。

6. 在文本工作流之外的扩展思路

6.1 从文本到多模态:RelayRouter并没有被束缚

虽然我们聊的是文本工作流,但RelayRouter这套路由逻辑天然不绑定模态。你可以把“识别图片中的文字”也看成一个文本增强步骤,把TTS合成看成另一个下游服务。路由层只负责把消息送达,不关心下游是文本模型还是多模态模型。

6.2 实时工作流的下一个试点:人机协同写作

我最近在试验一个更有意思的场景:写作协作者未必是“后台全自动”,而是“人类+AI轮流接力”。RelayRouter把一段草稿路由给“人类审阅”回调,人类修改完后消息再次进入工作流,由AI继续补全。这类人机循环,在传统REST接口里特别别扭,但在流式路由层里显得非常自然。

6.3 路由即策略:把AI产品当管道而非单点

很多团队的AI架构是一堆模型直接对前端,改一个模型就要动前端。引入路由层后,模型变成后端的可替换节点,前端只跟RelayRouter通信。这个位置的真正价值是:你有机会在不改业务代码的前提下,持续调优AI策略。今天用大模型,明天换更快的模型,都只是路由配置的变化。

7. 最后说几句实在的

从Gemini Live Avatar聊到文本工作流,再落到RelayRouter,我真正的体会是:实时AI最性感的从来不是那张会动的嘴,而是背后那套能在几百毫秒内完成“接收-理解-路由-生成-转发”的管道架构。视频只是把管道终端做得更华丽,文本工作流才是检验管道是否扎实的训练场。

如果你现在准备搭建一个实时文本助手,我的建议是先别急着炼丹调模型,把路由层设计好,把消息结构定清楚,把背压和可观测性做完整。只要这几根柱子立住了,后面无论换成什么模型、加什么功能,都不会太狼狈。

我也还在边做边踩坑,特别是动态上下文预算和人机协同路由这两个方向,坑比想象中多。但如果你的项目也正好卡在这些问题上,欢迎沿着这条思路先搭一个最小闭环出来,跑几天数据再回来对照优化。毕竟,“实时”这种东西,光看架构图永远感觉不到,真跑起来才知道哪里疼。

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

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

立即咨询