尧图精选

实时AI文本工作流实战:RelayRouter与WebSocket长连接工程细节

🕒 发布时间:2026/10/2 22:54:58 📁 来源:尧图网络
实时 AI 这个词最近被聊得很多但大多数讨论都停在把聊天窗口换成视频通话这个层面。我一开始也这么理解直到自己动手把一套文本工作流接进实时通道之后才发现真正难的不是让画面动起来而是让实时这件事在文本链路里也成立。Gemini Live Avatar 这类产品给人的直观冲击是AI 有了脸可它背后暴露出来的工程问题——长连接怎么维持、工具调用怎么异步化、文本流怎么和音视频流共存——才是每个做 AI 应用的人都绕不开的。RelayRouter 这个名字最近在热搜里频繁出现很多人第一反应是又一个路由库但把它放进文本工作流里看它解决的其实是实时场景下消息分发和状态同步的老问题。这篇内容适合已经在做 AI 应用、正在被 WebSocket 长连接和异步工具调用折磨的开发者也适合想搞清楚实时 AI 到底实时在哪的产品和技术同学。我会从 Gemini Live Avatar 这个现象切入把 RelayRouter 在文本工作流里的真实位置讲清楚顺带把 WebSocket 心跳、断线重连、SSE 与 WebSocket 选型这些实操细节一次说透。1. 从 Gemini Live Avatar 反推实时到底实时在哪1.1 视频外壳下藏着的是一条文本主链路很多人看 Gemini Live Avatar 的演示注意力全在数字人的表情和口型上觉得这是一次多模态的胜利。但如果你把它的交互拆开看会发现整条链路的核心依然是文本用户的语音先转成文本文本进入模型推理模型输出的文本再驱动语音合成和口型动画。视频只是最外层的表现层真正决定响应速度和交互质量的是中间那条文本链路。这个认知很关键因为它直接决定了你该怎么优化自己的系统。我见过不少团队一上来就砸资源做音视频编解码优化结果用户还是觉得卡最后排查发现瓶颈在文本推理的排队上。实时 AI 的实时不是画面流畅而是从用户输入到系统给出有意义反馈的端到端延迟足够低。Gemini Live Avatar 之所以看起来自然是因为它把文本链路的延迟压到了人感知不到的程度视频层才有机会显得流畅。所以当我们讨论实时 AI 不只是把聊天变成视频时真正的意思是视频是结果不是原因。原因在于文本工作流被重新设计成了实时架构。这个架构里长连接、消息路由、异步工具调用三件事必须同时成立缺一个都会让实时变成看起来实时。1.2 实时文本工作流和传统请求响应的根本差异传统的文本工作流是请求-响应模型用户发一条消息服务端处理完返回一条结果连接就结束了。这种模型简单、好调试、好扩展但它有个致命问题——它假设处理是一个瞬时动作。可现实里一次 AI 交互往往包含多个步骤检索知识库、调用外部工具、等待第三方接口、多轮推理。这些步骤加起来可能几秒甚至几十秒请求-响应模型下用户只能干等。实时文本工作流把这条链路拆成了事件流。用户输入是一个事件工具调用开始是一个事件工具返回是一个事件模型开始生成是一个事件生成结束又是一个事件。每个事件都通过长连接实时推给前端用户能看到系统正在做什么而不是盯着一个转圈图标。这就是 Gemini Live Avatar 那种有生命感的来源——不是它更快而是它把过程暴露出来了。这个差异带来的工程挑战是巨大的。请求-响应模型下状态是短暂的处理完就丢实时模型下状态必须长期维护连接断了要能恢复消息丢了要能重发顺序错了要能纠正。这就是为什么 WebSocket 和 SSE 这类技术会重新成为焦点也是为什么 RelayRouter 这种消息路由层会变得重要。1.3 为什么实时在文本场景里反而更难做音视频的实时有成熟的协议栈和硬件加速丢一帧两帧用户感知不明显。但文本不一样文本是有语义的少一个字、顺序错一位意思可能完全变了。这意味着文本实时链路对可靠性的要求比音视频更高。更麻烦的是文本工作流里的工具调用天然是异步的。用户问帮我查一下明天的天气然后推荐穿什么这里有两个依赖步骤查天气、根据天气推荐。查天气可能要调外部 API耗时不确定推荐要等天气结果回来才能开始。在请求-响应模型里这很简单串行执行就行。但在实时模型里你得让用户看到正在查天气这个中间状态还得保证天气结果回来后能正确触发下一步同时不能阻塞其他用户的请求。我踩过的一个坑是早期用同步方式处理工具调用结果一个慢接口把整个连接堵死了前端一直收不到心跳误判断线然后疯狂重连服务端瞬间被打爆。后来才明白实时文本工作流的核心不是快而是不阻塞和可观测。这两点做到了用户自然觉得快。2. RelayRouter 在文本工作流里到底扮演什么角色2.1 别把 RelayRouter 当成普通路由库热搜里 relayrouter 这个词出现得很频繁但很多人对它的理解停留在消息转发层面觉得无非是把 A 的消息转给 B。如果只是这样那它确实没什么特别的一个简单的发布订阅就能替代。RelayRouter 真正的价值在于它处理的是带状态的、有依赖关系的、需要保证顺序的消息流。在实时文本工作流里消息不是孤立的。一条工具调用请求和它对应的返回是一对模型生成的多个 token 块属于同一个响应用户的连续输入可能构成一个会话上下文。这些消息之间有依赖、有顺序、有生命周期。普通路由只关心送到哪RelayRouter 这类方案还要关心什么时候送按什么顺序送送失败了怎么办。我自己的理解是RelayRouter 在文本工作流里的位置类似于音视频里的媒体服务器。它不生产内容但它决定了内容怎么在生产者模型、工具和消费者前端、其他服务之间流动。这个中间层做得好不好直接决定了整个实时系统的稳定性和可扩展性。2.2 消息分发、状态同步与背压处理三件事RelayRouter 要解决的核心问题可以拆成三块。第一块是消息分发一个用户的消息可能需要同时推给前端、写入日志、触发下游任务怎么保证每个消费者都拿到、且不重复。第二块是状态同步连接可能断用户可能切换设备怎么保证重连后状态能恢复不会出现消息发到旧连接上的情况。第三块是背压处理当生产速度大于消费速度时消息会堆积怎么优雅地降级而不是直接崩掉。这三块里背压处理最容易被忽略也最容易出事。我见过一个案例模型生成速度很快前端渲染速度跟不上消息在服务端队列里越堆越多最后内存爆掉。正确的做法是在路由层就做流控当某个消费者的待处理队列超过阈值时要么丢弃低优先级消息要么通知生产者降速。RelayRouter 这类方案如果设计得当应该把流控作为一等公民而不是事后补丁。状态同步这块也有讲究。很多人以为重连就是重新建立连接其实重连的关键是续传。客户端要告诉服务端我上次收到的是第 N 条消息服务端从 N1 开始补发。这要求路由层维护消息序号和短期历史。没有这个机制重连后要么丢消息要么重复处理用户体验都会崩。2.3 和直接裸用 WebSocket 的对比有人会问我直接用 WebSocket 不就行了为什么要加一层 RelayRouter这个问题很实在。如果你的场景很简单就是一对一推送那确实不需要。但只要出现下面任何一种情况裸 WebSocket 就会开始难受多个服务需要往同一个连接推消息、消息需要按类型路由到不同处理器、需要做消息持久化和重放、需要横向扩展多个服务端实例。裸 WebSocket 的问题在于它只提供了管道没提供调度。当你有十个服务都想往用户的连接上写数据时你得自己协调谁先谁后、怎么避免并发写冲突、怎么在服务端扩容时让消息找到正确的连接。这些协调逻辑如果散落在各个业务代码里很快就会变成一团乱麻。RelayRouter 的价值就是把这团乱麻收拢到一个中间层让业务代码只管我要发什么不管怎么发到正确的连接上。下面这张表是我在实际选型时整理的对比供参考维度裸 WebSocket加 RelayRouter 层消息分发业务代码自己维护连接映射路由层统一管理多服务推送需要共享连接状态难扩展路由层做汇聚服务无状态消息顺序依赖单连接多写者易乱序路由层可保证序号断线续传需自行实现消息缓存路由层内置历史与重放背压处理基本没有容易堆积可做队列与流控调试难度连接状态分散难追踪消息流集中可观测当然加一层也有代价多一次网络跳转、多一个要维护的组件、多一层可能的故障点。所以我的建议是先用裸 WebSocket 跑通最小闭环当你发现自己在写第三遍连接管理代码时就该考虑引入路由层了。3. WebSocket 长连接的工程细节心跳、重连与消息可靠性3.1 心跳机制不是定时发个 ping 那么简单WebSocket 心跳机制实现这个话题被搜了很多次说明大家都在踩这个坑。表面上看心跳就是每隔几秒发个 ping收到 pong 就说明连接活着。但实际做起来问题一大堆。第一个问题是间隔怎么定。太短浪费资源太长发现断线不及时。我的经验是服务端心跳间隔设在 15 到 30 秒之间比较合适客户端如果超过两个心跳周期没收到服务端消息就主动探测。为什么是两倍因为网络抖动是常态单次丢失不能判定断线连续两次才比较可靠。第二个问题是心跳和业务消息的关系。很多人把心跳做成独立通道结果业务消息堵住的时候心跳也发不出去误判断线。正确做法是心跳要能插队或者至少和业务消息走不同的优先级队列。我在一个项目里就遇到过模型生成大段文本时把发送队列占满心跳包排在后面发不出去客户端以为断了开始重连结果重连上来又收到一堆旧消息乱成一锅粥。第三个问题是服务端怎么判断客户端还活着。光靠 TCP 层的 keepalive 不够因为中间可能有代理层TCP 连接活着不代表应用层还通。所以应用层心跳是必须的而且服务端要维护一个最后活跃时间超过阈值就主动清理连接释放资源。// 客户端心跳的常见实现注意重连时的退避 let heartbeatTimer null; let reconnectDelay 1000; const MAX_DELAY 30000; function startHeartbeat(ws) { clearInterval(heartbeatTimer); heartbeatTimer setInterval(() { if (ws.readyState WebSocket.OPEN) { ws.send(JSON.stringify({ type: ping, ts: Date.now() })); } }, 20000); } function scheduleReconnect() { // 指数退避避免服务端被打爆 reconnectDelay Math.min(reconnectDelay * 2, MAX_DELAY); setTimeout(connect, reconnectDelay); }这段代码里有个细节值得说重连一定要用指数退避。我见过太多客户端断线后立刻重连服务端刚重启还没起来瞬间被几千个重连请求打挂然后客户端又断又重连形成雪崩。退避加上随机抖动能把这个冲击摊平。3.2 断线重连后的状态恢复才是真正的难点心跳解决的是发现断线重连解决的是重新连上但真正难的是连上之后状态对不对。用户断线期间服务端可能已经生成了好几条消息这些消息要不要补发补发的话从哪条开始用户断线前正在进行的工具调用重连后要不要继续我的做法是在协议里引入消息序号和会话游标。每条服务端推给客户端的消息都带一个单调递增的序号客户端本地记录已确认收到的最大序号。重连时客户端把这个序号带上服务端从序号加一开始补发。同时服务端要保留一段时间的消息历史比如最近 5 分钟或最近 1000 条超出范围的就不补了让客户端做一次全量刷新。这里有个坑补发的消息和实时产生的新消息可能交错。如果处理不好客户端会先收到新消息再收到旧消息顺序就乱了。解决办法是补发阶段先暂停新消息推送补完再恢复或者给补发消息打标记让客户端做缓冲排序。我倾向于前者简单可靠。还有一个容易被忽略的点工具调用的状态恢复。如果用户断线时正好有个工具调用在进行重连后这个调用的结果怎么处理我的方案是把工具调用也纳入消息流调用开始和调用结束都是消息客户端根据消息重建状态。这样即使断线只要消息历史还在状态就能恢复。3.3 消息可靠性至少一次、恰好一次还是尽力而为消息可靠性有三个层次尽力而为发了不管、至少一次保证送到但可能重复、恰好一次不丢不重。实时文本工作流里大部分场景用至少一次就够了但要在客户端做幂等处理。为什么不用恰好一次因为恰好一次需要分布式事务或者复杂的确认机制代价太高而且在实时场景里收益有限。文本消息重复一条客户端根据消息 ID 去重就行成本很低。但如果为了恰好一次引入两阶段提交延迟会上去实时性就没了。幂等处理的关键是给每条消息一个唯一 ID客户端维护一个已处理 ID 的集合可以用 LRU 缓存控制内存。收到重复 ID 直接丢弃。这个机制简单但极其有效我在多个项目里都靠它兜底。需要恰好一次的场景通常是涉及副作用的操作比如扣款下单。这类操作不应该走实时消息通道而应该走独立的、有事务保证的接口。实时通道只负责通知操作完成了不负责执行操作本身。这个边界划清楚系统会简单很多。4. 异步工具调用实时文本工作流里最容易翻车的地方4.1 同步工具调用为什么会拖垮整个连接前面提到过同步工具调用堵死连接的问题这里展开说。假设用户问了一个需要调用外部 API 的问题你的处理逻辑是收到消息调用 API等结果生成回复推送。如果这个 API 耗时 10 秒那这 10 秒里这条连接上的其他消息都处理不了。在低并发场景下这没什么但在实时场景下用户可能同时在打字、可能触发了其他操作这些消息都排在后面。更糟的是如果这个 API 挂了或者超时整个连接就卡死了。我早期的一个项目就是这么崩的一个第三方接口不稳定导致大量连接被占满新用户连不进来。正确的做法是把工具调用异步化。收到用户消息后立即返回一个已收到正在处理的事件然后后台异步执行工具调用完成后通过消息通道推送结果。这样连接始终是畅通的用户也能看到进度。4.2 工具调用的生命周期管理与超时设计异步化之后工具调用就有了生命周期创建、执行中、成功、失败、超时、取消。每个状态都要能推送给前端让用户知道发生了什么。这听起来简单但状态管理很容易乱。我的做法是给每个工具调用分配一个 ID维护一个状态机。状态转换必须合法比如不能从成功转到执行中。每次状态变化都产生一条消息推给前端。前端根据这些消息渲染不同的 UI比如正在查询...、查询完成、查询失败请重试。超时设计是重点。外部工具调用必须设超时而且超时时间要分层单次请求超时、整体调用超时、用户可感知的等待超时。单次请求超时比如 5 秒超了就重试整体调用超时比如 30 秒超了就放弃并告知用户用户可感知的等待超时比如 3 秒超过这个时间还没结果前端就要显示正在处理的提示不能让用户干等。# 异步工具调用的状态管理示意 import asyncio from enum import Enum class ToolState(Enum): PENDING pending RUNNING running SUCCESS success FAILED failed TIMEOUT timeout async def execute_tool(call_id, tool_fn, args, timeout30): await push_event(call_id, ToolState.RUNNING) try: result await asyncio.wait_for(tool_fn(**args), timeouttimeout) await push_event(call_id, ToolState.SUCCESS, result) except asyncio.TimeoutError: await push_event(call_id, ToolState.TIMEOUT) except Exception as e: await push_event(call_id, ToolState.FAILED, str(e))这段代码的关键点是asyncio.wait_for它保证了即使工具函数内部卡住外层也能按时超时。很多人只在自己的代码里加超时但工具函数可能调用了别的库那些库的超时不受你控制所以外层必须再包一层。4.3 多个工具并行调用时的结果聚合复杂问题往往需要多个工具并行调用。比如对比一下北京和上海明天的天气需要同时查两个城市的天气。并行调用能省时间但结果聚合是个麻烦事。首先是顺序问题。并行调用的返回顺序是不确定的但前端展示可能需要固定顺序。解决办法是给每个调用分配一个序号聚合时按序号排序而不是按返回时间。其次是部分失败的处理。如果北京天气查到了上海天气超时了怎么办我的策略是尽力而为加明确告知能拿到的结果先展示失败的部分明确标注上海天气获取失败而不是整个请求失败。用户要的是信息不是完美。还有一个坑是结果聚合的时机。你不能等所有调用都返回才推送那样就失去了并行的意义。正确做法是每个调用返回就推送一次前端增量更新。用户会看到北京天气先出来上海天气后出来体验反而更自然。5. SSE 还是 WebSocket文本工作流里的选型逻辑5.1 单向推送场景下 SSE 的隐性优势热搜里有个词是react sse/websocket 轮询文件变化说明很多人在纠结 SSE 和 WebSocket 怎么选。我的观点是如果只需要服务端往客户端单向推送SSE 往往是更好的选择尽管它看起来更弱。SSE 的优势在于它基于普通 HTTP天然支持自动重连、天然穿透大部分代理、天然支持事件 ID 和断点续传。浏览器对 SSE 的重连是内置的你什么都不用做断了它自己会重连还会带上 Last-Event-ID 让服务端知道从哪续。这些在 WebSocket 里都要自己实现。SSE 的劣势是单向客户端要发消息得另开一个 HTTP 接口。但在文本工作流里客户端发消息的频率通常远低于服务端推消息的频率用 HTTP 发消息完全够用。所以SSE 推 HTTP 发这个组合在很多场景下比纯 WebSocket 更简单可靠。我自己的经验是纯展示型的实时文本流比如日志、通知、AI 生成内容优先用 SSE需要双向高频交互的比如协作编辑、实时游戏才用 WebSocket。不要因为 WebSocket更强大就无脑选它强大意味着你要自己处理更多东西。5.2 双向交互和二进制场景下 WebSocket 不可替代当然WebSocket 有它不可替代的场景。需要客户端高频发消息的、需要传二进制数据的、需要极低延迟双向交互的这些 SSE 都做不了。Gemini Live Avatar 这种场景就必须用 WebSocket因为音视频数据是双向的、二进制的、高频的。还有一个实际考虑是连接数。SSE 每个连接占用一个 HTTP 连接浏览器对同域 HTTP 连接数有限制HTTP/1.1 下通常 6 个如果开多个 SSE 会互相挤占。WebSocket 不受这个限制。所以在需要多个实时通道的场景下WebSocket 更合适。HTTP/2 下这个限制缓解了但仍有并发流的上限。选型的时候我会问自己三个问题客户端需要频繁发消息吗需要传二进制吗需要多个实时通道吗三个都是否用 SSE有一个是考虑 WebSocket。5.3 混合方案用 SSE 做通知用 WebSocket 做交互实际项目里我越来越倾向于混合方案。用 SSE 做轻量通知比如有新消息了任务完成了用 WebSocket 做重交互比如实时对话、协同操作。这样通知通道简单可靠交互通道专注性能各司其职。混合方案的关键是两者要能协同。比如 WebSocket 断了SSE 通知还能工作用户至少知道连接有问题。或者 SSE 收到任务完成通知前端再通过 WebSocket 拉取详细结果。这种设计让系统在部分故障时仍能提供降级体验。不过混合也增加了复杂度要维护两套连接、两套重连逻辑。所以小项目别一上来就混合先用一种跑通遇到瓶颈再拆。6. 把 RelayRouter 放进真实文本工作流的落地路径6.1 最小可行架构先跑通一条消息的完整生命周期说了这么多原理落地的时候还是要从最小闭环开始。我的建议是先跑通一条消息从产生到消费的完整生命周期不追求功能全追求链路通。最小架构包含四个部分消息生产者比如模型服务、RelayRouter路由层、连接管理WebSocket 或 SSE、消费者前端。一条消息的流程是生产者产生消息带上会话 ID 和序号交给 RelayRouterRelayRouter 根据会话 ID 找到对应的连接按序号推给消费者消费者收到后确认RelayRouter 记录确认位置。这个闭环跑通后再逐步加东西加心跳、加重连续传、加工具调用、加多消费者。每加一个都要保证前面的闭环不被破坏。我见过太多项目一上来就设计大而全的架构结果每个部分都没跑通最后推倒重来。6.2 从单机到多实例连接状态该放哪单机的时候连接状态放内存就行。但一旦要多实例部署问题就来了用户的连接在实例 A 上但处理他消息的服务可能在实例 B 上B 怎么把消息推给 A 上的连接解决办法是把连接状态外置。常见方案是用 Redis 的发布订阅每个实例订阅自己负责的频道消息通过 Redis 广播实例收到后检查自己有没有对应的连接有就推送。这样实例之间不需要直接通信通过 Redis 解耦。但 Redis 发布订阅有个问题它不保证消息可靠实例短暂断连期间的消息会丢。对于实时文本流偶尔丢一条可能可以接受因为有重连续传兜底但如果要求高可靠就得用更重的方案比如消息队列加确认机制。我的经验是先用 Redis 发布订阅遇到可靠性问题再升级不要一开始就上重方案。还有一个细节是连接和实例的绑定关系要能查询。当需要主动给某个用户推消息时得知道他的连接在哪个实例上。这通常用一个 Redis 的映射表来维护连接建立时写入断开时删除。要注意处理实例崩溃导致的脏数据可以用心跳续期的方式让映射自动过期。6.3 可观测性实时系统没有日志就是瞎子实时系统最怕的是看起来在跑其实已经坏了。连接还在但消息不流动心跳还在但业务逻辑卡死。没有可观测性你根本不知道问题出在哪。我的做法是至少埋三类指标连接指标当前连接数、新建速率、断开速率、断开原因分布、消息指标生产速率、消费速率、队列深度、端到端延迟、错误指标工具调用失败率、超时率、重连率。这些指标要能按会话、按用户、按消息类型下钻。日志也很关键但实时系统的日志量很大不能什么都记。我的策略是记状态变化不记每条消息。比如连接建立、连接断开、工具调用开始、工具调用结束、重连发生这些事件记日志。消息内容本身只在采样或者出错时记避免日志爆炸。还有一个实用技巧是给每个会话分配一个 trace ID贯穿整条链路。这样排查问题时拿一个 trace ID 就能看到这个会话的所有事件不用在多个日志文件里翻。这个投入在出问题的时候回报巨大。7. 几个我踩过的坑和对应的解法7.1 心跳和业务消息抢通道导致的假断线前面提过这个坑这里说具体表现和解法。现象是用户在用的时候偶尔会看到连接已断开正在重连但网络明明没问题。排查发现是模型生成大段文本时发送队列被占满心跳包排在后面客户端等不到心跳就判定断线。解法有两个层面。一是发送队列要分级心跳和控制消息走高优先级队列业务消息走普通队列高优先级队列永远优先发送。二是客户端判定断线要更宽容不能一个心跳周期没收到就断至少给两个周期而且要考虑客户端自己的发送队列是否拥堵。这个坑的教训是实时系统里控制面和数据面要分开。心跳、重连、确认这些控制消息不能和业务数据挤在一起。7.2 重连风暴服务端重启后的雪崩服务端重启或者网络抖动后大量客户端同时重连瞬间把服务端打挂然后客户端又断又重连形成循环。这个坑我踩过一次印象很深。解法是客户端重连必须加随机抖动和指数退避。退避让重连分散开抖动避免所有客户端在同一时刻重连。服务端也要有限流超过承载能力的重连请求要排队或者拒绝而不是全部接受然后一起崩。还有一个进阶做法是服务端在关闭前主动通知客户端我要重启了请稍后重连并给出建议的重连时间。这样客户端可以有序重连而不是一窝蜂。当然这要求服务端能优雅关闭不是所有场景都能做到。7.3 工具调用结果回来时连接已经换了用户断线重连后连接 ID 变了但之前发起的工具调用还在跑。结果回来时按旧连接 ID 推送就丢了。这个坑很隐蔽因为工具调用通常很快不容易触发但一旦触发就是丢消息。解法是不要把消息绑定到连接 ID而是绑定到会话 ID。连接可以变会话不变。推送时根据会话 ID 查当前连接而不是用发起时的连接 ID。这样即使连接换了消息也能找到正确的去处。这个思路其实适用于所有实时消息连接是易变的会话是稳定的。以会话为中心设计而不是以连接为中心能避免很多这类问题。7.4 前端渲染跟不上导致的背压堆积服务端推得快前端渲染得慢消息在客户端堆积内存上涨最后卡死。这个坑在文本流场景特别常见因为文本渲染涉及 DOM 操作比纯数据更新慢得多。解法是前端要做节流和批量更新。不要每收到一个 token 就更新一次 DOM而是攒一小批比如 50 毫秒或 20 个 token一起更新。这样渲染次数大幅减少用户感知的流畅度反而更高。服务端也要配合如果发现某个客户端的确认延迟持续偏高要主动降速或者丢弃低优先级消息。这就是前面说的背压处理必须两端配合才能做好。8. 关于实时文本工作流的一些个人判断做了几个实时 AI 项目之后我有个越来越强的感受实时文本工作流的难点不在实时技术本身而在状态管理。WebSocket、SSE、心跳、重连这些都是成熟技术文档一搜一大把。真正让人头疼的是状态连接状态、会话状态、工具调用状态、消息确认状态这些状态交织在一起任何一个处理不好都会出问题。RelayRouter 这类方案的价值本质上就是把状态管理从业务代码里抽出来集中到一个地方处理。它不神奇但它让状态有了统一的归属而不是散落在各个服务里。这也是为什么我觉得它在文本工作流里有位置——不是因为它能做什么别人做不了的事而是因为它让复杂的事情有了秩序。Gemini Live Avatar 给我的启发是实时 AI 的竞争最终会落到工程细节上。模型能力大家都能买到但把模型能力稳定、低延迟、可恢复地送到用户面前这是工程活。谁把这些细节做扎实谁的实时体验就好。视频只是表象文本链路才是里子。如果你正在做类似的东西我的建议是别急着上复杂架构。先把一条消息的生命周期跑通把心跳和重连做扎实把工具调用异步化把状态管理收拢。这几件事做好了系统就稳了。至于用 SSE 还是 WebSocket、用不用 RelayRouter都是在这个基础上根据场景做的选择不是起点。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →