跳到主要内容

无共享智能体:为水平可扩展性设计 AI 智能体

· 阅读需 13 分钟
Tian Pan
Software Engineer

你的负载均衡器将一个传入的智能体请求分配给副本 3。但用户的对话历史存储在副本 7 的内存中。副本 3 完全不知道过去六轮发生了什么,于是它从头开始,让用户一头雾水,你的值班工程师在凌晨 2 点被叫醒。你启用了会话粘滞。现在该用户的所有请求永远路由到副本 7。你用一个正确性问题换来了一个可扩展性天花板。

就在这一刻,团队意识到:AI 智能体的"水平扩展"和 Web 服务器的水平扩展根本不是同一个问题。修复方式不同,而那些看似直接的路径会以可预见的方式失败。

根本原因是隐式状态。大多数智能体实现将上下文积累在进程内部——对话历史、临时文件、中间工具输出、检查点数据——从未考虑当该进程消失或第二个进程尝试处理同一用户时会发生什么。这对原型来说没问题,在生产环境中却会严重失效。

解决方案来自分布式系统,而非 AI 研究:无共享架构原则。将每个智能体副本设计为对先前工作没有任何记忆。通过外部系统引入所有所需上下文,并将所有输出写回这些系统。让每个操作都可以安全重试。坚持这样做,你就能实现真正的水平可扩展性——添加副本、分发负载、优雅处理故障。本文将介绍如何实现这一目标。

为什么智能体会积累状态(以及为什么这很重要)

LLM 在设计上是无状态的。每次推理调用接收一个完整的提示并生成响应,模型内部没有隐藏的会话对象。这就是 LLM 提供商能够服务数百万用户的原因:每个请求都是独立的。

智能体打破了这一特性。智能体框架用控制循环包裹 LLM 调用,维护以下内容:

  • 对话历史:提供上下文所需的轮次序列
  • 工具输出:用于指导当前推理的先前调用结果
  • 工作文件:执行过程中写入的文档、日志、数据文件
  • 检查点数据:循环中断时恢复所需的足够状态

当这些数据存活在进程内存或本地文件系统中,你就创建了一个有状态的进程。添加第二个副本没有帮助——它无法访问第一个副本的内存。你需要会话粘滞,这意味着一个副本处理来自特定用户或会话的所有请求。你以运维麻烦换来了毫无意义的扩展。

随着智能体能力增强,问题会进一步恶化。一个处理 20 步研究任务的智能体可能积累数百 KB 的中间上下文。运行了 90 分钟的智能体积累了工具输出、部分结果和推理步骤,这些东西只存在于运行它的那台机器上。当那台机器重启,一切都丢失了。

无共享原则在智能体上的应用

无共享是一种分布式系统架构,其中每个节点在不假设共享状态的情况下运行。节点不共享内存,不共享本地磁盘,只通过显式消息传递——API、队列、数据库——进行协调。经典实现是 Web 请求:请求到达池中的任意服务器,该服务器从数据库获取所需内容,处理请求,写回结果,然后返回。下一个请求可以去任意服务器。

对于智能体,等价原则是:每次智能体调用必须能在任何可用副本上运行。这需要三件事:

所有上下文必须存在于进程之外。 对话历史、工具结果和检查点数据存入外部存储——Redis 用于快速读取,数据库用于持久性,对象存储用于大型工件。智能体在每轮开始时获取所需内容,在结束时写回结果。

上下文重建必须可从元数据实现。 你不应该需要重放整个历史来从中间任务恢复。存储每个步骤的元数据——发生了什么、做出了什么决定、指向存储工件的指针——这样新副本可以在毫秒内重建足够的上下文。

所有工具调用必须可以安全重试。 当工具调用中途失败,或当智能体从检查点重启时,它可能会再次调用相同的工具。非幂等的工具在重试时会损坏状态。

这三个要求是独立的,每个都可以单独解决。但你需要全部三个才能实现完整的水平可扩展性。

外部状态:三个层级

生产智能体系统通常根据访问速度和成本将外部状态组织为三个层级。

热上下文(Redis):当前轮次的工作上下文——活跃会话的对话历史、最近的工具输出、智能体的当前计划。Redis 查询运行在 10-50ms,当你的目标是交互式智能体 200ms 以下的响应时间时,这很重要。这是每轮立即需要的数据。Redis 还提供基于 TTL 的过期,因此会话状态在对话结束时自动清理。

温上下文(向量数据库和关系型存储):历史上下文、检索到的事实、先前会话摘要。并非每次调用都需要这些——只有当智能体决定需要背景信息时。获取需要 50-200ms,作为按需操作是可以接受的。这里的访问模式是基于检索的:智能体查询相关的先前上下文,而不是加载所有内容。

冷存储(对象存储):大型工件——智能体处理的文档、完整的工具输出日志、归档的会话数据。这些很少被访问,即使被访问,几秒钟的延迟也可以接受。智能体在热上下文中存储指针而不是工件本身:不是将 50KB 的工具响应嵌入上下文窗口,而是将其存储到 S3 并保留 URL。

这些层级之间的实际分配因使用场景而异。有许多短会话的交互式智能体大量使用 Redis,很少接触对象存储。运行 30 分钟工作流的批处理智能体对持久性使用更多关系型存储,对中间工件使用更多对象存储。

关键设计规范:没有重要内容只存在于进程内存中。当智能体将文件写入 /tmp 时,这个决定就是技术债务。当工具结果只积累在内存中的对话对象中时,这就是扩展性负债。

幂等工具设计

智能体重试工具调用的频率比工程师预期的要高——由于超时、验证错误或模型不确定性,大约 15% 到 30% 的调用会被重试。当智能体从检查点重启并恢复部分完成的工作流时,它会重新调用已经调用过的工具。当网络错误在服务器端操作完成后丢弃响应时,智能体不知道调用已经成功。

非幂等工具使这些场景变得危险。创建数据库记录、发送电子邮件或收取信用卡费用的工具在重试时会损坏状态。后果从重复数据到财务错误不等。

解决方案是幂等键,与支付 API 中使用的模式相同。智能体为每个逻辑工具调用生成唯一键——通常是会话 ID、步骤编号和工具输入参数的哈希值。工具服务器检查是否已经处理了具有该键的请求。如果是,返回存储的结果而不重新执行。如果否,执行、存储结果并返回。

这提供了两个属性:重试安全性(即使调用者多次发送,操作最多运行一次)和审计跟踪(每个完成的工具调用都记录了其结果,这在调试时非常有帮助)。

幂等键还解决了一个更微妙的问题:模型幻觉。语言模型偶尔会忘记它已经调用了某个工具,并尝试在同一轮中再次调用它。有了幂等键,这会被透明地处理——第二次调用返回与第一次相同的结果,没有任何新的副作用。

关于分类的实用说明:大多数工具分为三类。只读工具天然是幂等的——查询数据库、获取 URL、读取文件。创建资源的写工具可以通过先检查是否存在或使用幂等键来实现幂等性。少数工具具有不可逆的效果(例如发送外部消息),需要显式的幂等基础设施。从前两类开始,用显式键管理处理第三类。

可重现的上下文重建

副本之间的状态迁移在规模上会失败。序列化完整的智能体状态对象并通过网络传输速度慢、脆弱且昂贵。更好的方法是上下文重建:存储足够的元数据,使任何副本都能重建当前轮次的足够上下文,而无需迁移完整状态。

这在实践中看起来像事件溯源。在每个主要步骤之后,智能体将事件追加到持久日志:做出了什么决定、调用了哪些工具并返回了什么、当前计划是什么、工件的指针。事件日志是任何副本都可以重放的不可变记录——不是通过重新执行工具调用,而是通过读取存储的结果并重建智能体当前的理解。

当新副本接管会话时,它不会从头开始,也不需要原始副本的内存。它读取事件日志,从 Redis 加载最近的上下文,从对象存储获取任何所需的工件,并重建工作上下文窗口。这通常需要几百毫秒——快到足以重试,慢到你不想在每轮都这样做。

实际影响:摘要不仅是节省 token 的优化,也是可扩展性的推动者。随着对话增长,对所有先前轮次的原始重放消耗过多 token 并花费太长时间。定期将对话摘要为紧凑表示——存储在温上下文层——保持重建速度快且上下文窗口有界。

没有这种架构时不可见的故障模式

有状态智能体最危险的故障模式不是崩溃——而是静默的质量下降。当智能体在记忆中积累了错误假设,或者当上下文在多轮中偏离现实,或者当副本之间的状态同步失败时,结果不是错误。没有异常抛出,没有警报触发。智能体只是开始产生更差的结果,用户开始绕过它。

这很难检测,正是因为它看起来像模型质量变化,而不是系统故障。团队通常将其归因于模型的不确定性,或提示不够精确,而实际原因是已经积累了数小时的损坏状态。

无共享架构防止了这类故障,因为没有持久的进程内状态可以损坏。每轮从外部系统获取干净的上下文,运行模型,并写回结果。上下文漂移不会积累,因为上下文默认不被积累——它是显式重建的。

一个相关的故障是会话粘滞饱和。当来自高流量用户的所有流量路由到一个副本时,该副本成为瓶颈。用户遇到高延迟或超时。其他副本处于空闲状态。负载均衡器为无状态服务设计;会话粘滞打破了负载均衡假设,创建了难以推理的不均匀负载分布。

实用迁移路径

大多数团队不是从无共享智能体开始的——他们从原型开始,在架构改变之前原型就变成了生产系统。迁移可以增量进行。

首先将对话历史外部化。用 Redis 支持的会话存储替换内存中的对话状态。单是这一步就允许你移除会话粘滞并正确运行多个副本。这通常是最高杠杆的改变。

接下来,为写工具添加幂等性。审计你的工具集,找出有副作用的工具。为这些工具添加幂等键支持,并更新智能体以生成和传递键。这使你的智能体对重试和模型幻觉具有弹性。

最后,为长期运行的工作流添加检查点事件。在每个主要决策点之后,将检查点事件写入持久日志。这支持从任务中途安全恢复,并提供调试所需的审计跟踪。

每个步骤都有独立的价值。完整的无共享架构是目标,但路径是渐进的。

这无法解决的问题

无共享架构解决了智能体计算的水平可扩展性。它不能解决规模化外部存储的成本问题——用于数百万并发会话的 Redis 集群会变得昂贵。它不能解决冷重建问题:长时间空闲后的第一个请求需要从较慢的存储获取上下文,这会增加延迟。它也不能解决长对话的基本 token 成本:即使是摘要化的上下文也需要 token,非常长的交互最终会遇到实际限制。

这些都是真实的约束,但它们是有已知解决方案的工程问题——存储分层、TTL 策略、主动缓存预热、积极摘要。相比之下,有状态智能体施加的可扩展性天花板没有已知的解决方案,除了重新设计架构。

无状态 Web 服务器模式花了几年时间才成为 Web 应用的默认模式。无共享智能体模式正在经历同样的转变。从一开始就为其设计的团队会发现它很简单;将其改造到有状态系统上的团队会发现它痛苦但必要。

原则很简单:像构建 API 一样构建你的智能体——默认无状态,具有显式的外部状态管理。其他一切都从这里开始。

Let's stay in touch and Follow me for more thoughts and updates