做AI Agent的人,很多人第一反应是“怎么让模型更聪明”,真正把系统推到线上以后才发现,“扛不扛得住并发”才是决定生死的那个问题。我接手一个企业招聘场景的 agent 时,单用户对话状态全放进程内存,一上并发各种错乱,用户上下文互相串,超时率肉眼可见地涨。后来把会话状态、工具调用结果、限流配额全部迁到 Redis,并发量直接翻了一个数量级,整个系统才算真正能睡个安稳觉。
这篇文章就从“AI Agent 为什么要配 Redis 缓存”讲起,把会话状态持久化、语义缓存、分布式锁、限流这些场景逐个拆开讲清楚,再给出一套可以照着抄的实现方案和参数选型。无论你是在用 LangChain、LangGraph 编排 agent,还是单纯想给已有服务加缓存层,这篇都能给你省不少摸索的时间。
1. AI Agent 系统的并发困境与缓存定位
1.1 Agent 和传统接口到底差在哪
先说一个经常被忽略的事实:AI Agent 本质上不是“一个接口”,而是一串动态编排的决策循环。普通接口拿到参数、查个库、返回结果,整个链路是确定的,缓存和限流都是老一套玩法。Agent 不一样,它要经过“理解意图 → 拆解任务 → 选择工具 → 调用工具 → 观察结果 → 调整计划”这种多轮循环,而且每一轮都可能触发一次 LLM 调用,一次用户请求背后可能是一个持续几十秒甚至几分钟的有状态长任务。
这个差异直接决定了它的并发特征。传统接口的峰值流量用网关和水平扩容就能顶住,因为每个请求的资源占用和耗时是可预估的。Agent 的请求进来以后,占用的不是固定资源,而是一个随时可能变化的动态流程。你在单机内存里保存中间状态,单实例跑没问题,一旦横向扩展成多实例,用户的对话上下文、工具执行进度、临时变量全部散落在不同进程里,下一个请求打到另一个实例上,agent 就“失忆”了。这是我做的 agent 项目里踩出来的第一个坑:单机演示跑得飞起,部署到 Kubernetes 两个副本之后就各种答非所问,排查半天,发现会话状态只存在每个 worker 的本地内存里。
所以 AI Agent 扛并发,本质上不是“怎么把 QPS 堆上去”,而是“怎么让一个有状态的长流程,在多实例环境下依然保持状态一致”。这个问题最标准的解法就是引入一个共享的、分布式的状态与缓存层,而 Redis 是当前性价比最高、生态最成熟的选择。
1.2 为什么偏偏是 Redis
缓存选型不止 Redis 一个,Memcached、本地 Caffeine、甚至业务数据库都能承担一部分职责。但 Agent 场景里需要的是三样东西的组合:低延迟读写、丰富的数据结构、可靠的分布式语义。
低延迟这块,Redis 纯内存操作,单命令的 P99 通常在亚毫秒级,对 agent 循环里每次状态读取、工具结果写入来说,这个延迟成本可以忽略不计。Memcached 虽然也快,但只支持简单 KV,而 Agent 的会话状态往往是一个包含多轮消息、任务计划、工具执行记录的复杂结构,需要 Hash、List、Stream 这种数据结构来组织。更重要的是,Redis 天然提供分布式锁、过期时间、发布订阅、流式队列这些“分布式系统零件”,Memcached 给不了。本地 Caffeine 虽然快,但它解决不了多实例共享的问题。
再把工程账也算进去:Redis 有成熟的高可用方案(主从 + Sentinel 或 Cluster),客户端库覆盖所有主流语言,Docker 部署一条命令,运维几乎没有额外学习成本。做 AI Agent,技术栈里已经有 LangChain、LangGraph、向量库这些新东西了,缓存层就不要再引入冷门组件增加心智负担。我的结论很直接:Agent 系统的状态层、缓存层、协调层,现阶段用 Redis 一套全包,是投入产出比最高的选择。
2. Redis 在 AI Agent 中的核心应用场景
2.1 会话状态与上下文持久化
Agent 对话天然是“session 式”的,用户说一句话,背后可能是 agent 十几轮内部推理和工具调用。这些中间状态如果只存在进程内存里,实例重启、发版、扩缩容都会导致对话断片。把会话状态放进 Redis 以后,每个实例通过相同的 key 读取同一个 session,状态就从“进程私有”变成了“集群共享”。
我用得最多的是 Hash 结构来存会话状态,字段大致是这样:
| 字段 | 内容 | 示例 |
|---|---|---|
| messages | 裁剪压缩后的多轮消息列表 | [{role:"user", content:"..."}] |
| plan | 当前任务拆解计划 | {"steps": [...], "current_step": 2} |
| tool_results | 最近 N 次工具调用结果摘要 | {"sku_query": "12 rows"} |
| graph_state | LangGraph 运行时状态快照 | {"node":"search","retry":0} |
| updated_at | 最后活跃时间戳 | 1730000000 |
存储的时候有一个关键细节:不要直接把原始消息对象丢进去。Agent 的消息里可能包含整套系统提示词、很长的工具返回体,全塞进一个 key 里又臃肿又不好维护。我在实践里会先做一次“提炼”,把系统提示词剥离、老轮次消息裁剪、工具结果只保留结构化摘要,再序列化存入。这样单个会话的 Redis 占用能控制在几 KB 以内,大并发下省下来的内存非常可观。
另外要配合滑动过期:每次用户有新请求时,重新对 session key 执行 EXPIRE,把生命周期往后推。不活跃的会话被 Redis 自动清理,活跃会话永远不会丢。注意这个“更新状态 + 续期”的操作最好用 pipeline 或事务包在一起,避免出现“状态写进去了但没续期”这种半成品数据。
2.2 语义缓存:把重复的 LLM 调用直接砍掉
这是 AI Agent 场景里 Redis 最有价值、也最容易被忽略的一个用途。LLM 调用又贵又慢,一次动辄几百毫秒到几秒,费用按 token 算。而实际业务里用户的问题大量重复,尤其是企业内部机器人和客服场景,常见问题占比能到 40% 以上。
传统缓存是 key 精确匹配,但用户提问是自然语言,同一个意思可以有一百种说法,所以这里要用“语义缓存”:先把用户问题向量化(embedding),再到 Redis 里找之前存过的问题向量,相似度超过阈值(比如 0.92)就直接把当时的答案返回,不再调用 LLM。
实现上推荐一个轻量方案:用 Redis 的 RediSearch 模块支持向量相似度搜索(KNN),把“问题向量 → 答案”的映射直接存进 Redis,省掉一个单独的向量数据库。对于中小规模 agent 项目(几十万条缓存以内),这个方案部署简单、延迟又低,完全够用。命中之后,单次对话响应时间可以从秒级降到几十毫秒,成本同步大幅下降。
语义缓存要处理两个问题。一是阈值灵敏度:设太高命中率上不去,设太低容易把语义不同的错误答案当缓存返回,我通常从 0.9 开始,拿线上真实问题压测后微调。二是失效:答案可能跟随知识库更新而改变,缓存必须带版本号或按主题设置合理 TTL,动态类问题缓存短一点,常识类问题可以放很久。
2.3 分布式锁:多实例下的任务协调
Agent 有一个绕不开的场景:同一个用户、同一个任务,可能因为前端重试、WebSocket 断线重连、用户连续点按钮,产生多个并发的 agent 执行流。这些执行流如果不加约束,会同时调用工具、同时写状态、重复扣费。用 Redis 分布式锁给“每个用户同一时刻只允许一个 agent 执行流”上一个保险,是最直接的解法。
锁的 key 一般设计成agent:{user_id}:lock,value 放一个随机 UUID,用SET NX EX原子命令,只有拿到锁的实例才能开始执行。执行结束后校验 value 再删锁,防止“自己锁被别人删”的经典问题。更稳妥的做法是用 Lua 脚本把“检查 + 删除”两个动作做成原子操作。
分布式锁有个容易踩的坑:锁超时设太短,长任务没跑完锁就自动释放了,另一个请求拿到锁进来,两个流又打架;设太长,如果持有锁的实例挂了,其他请求要干等很久。我的做法是把锁拆成两层:外层锁设置较短超时(比如 30 秒),内层用一个后台任务每 10 秒刷新一次过期时间。这样既防了死锁,又保证正常长任务不会中途丢锁。如果不想自己实现,可以用 Redisson 的看门狗机制,原理一样。
2.4 限流与配额管理
AI Agent 的成本大头是 LLM 调用,所以限流不只是保护系统,直接关系到账单。Redis 的滑动窗口限流做这个非常顺手:用 Sorted Set 记录每个用户每次 LLM 调用的时间戳,调用前把窗口外的记录清掉,再数一下窗口内数量,超过阈值就拒绝或降级。
更细一步,按模型和按接口分别限流。重型模型和轻量模型的成本差几十倍,我会给每个用户配置一套配额:每分钟最多 N 次重型模型调用、每天最多 M 个任务。配额数据也放 Redis,key 带 TTL,自然过期就是周期重置,省掉定时任务的维护。
这里有个实践原则:限流一定要在 agent 编排器的入口做,而不是在单个工具函数里做。因为一个任务可能循环调用同一个工具很多次,你要限的是“每个用户整体消耗”,不是“单个工具的调用频率”。限流放入口,所有内部循环先过配额检查,才能真正控住成本。
3. 数据结构选型与序列化细节
3.1 Redis 数据类型在 Agent 里的正确用法
很多人用 Redis 是 SET/GET 一把梭,遇到 Agent 这种复杂场景马上露怯。其实 Redis 的每一种数据类型在 Agent 系统里都有非常贴合的用途,整理成表格可以直接照着选:
| 数据类型 | Agent 场景 | 典型 key 设计 |
|---|---|---|
| String | 用户级配置、token 配额、简单缓存 | agent:{uid}:config |
| Hash | 会话状态、多字段对象 | agent:{uid}:session |
| List | 消息队列、工具调用日志 | agent:{uid}:history |
| Set | 事件去重、会话集合维护 | agent:{uid}:processed_events |
| ZSet | 滑动窗口限流、任务优先级 | agent:rate:{uid}:{model} |
| Stream | 事件管道、异步任务编排 | agent:events:{uid} |
| RediSearch | 语义缓存的向量检索 | idx:agent_semantic_cache |
我最早做异步 agent 时,把任务下发直接用 List 当队列,BLPOP 阻塞式消费。后来任务量上来、需要消费者组和消息确认,迁移到了 Stream。Stream 的优势是天然支持多消费者组,多个 agent worker 可以并行消费不同用户的任务流,而每个用户内部保持消息顺序。对“用户维度严格有序 + 整体并行扩展”这种矛盾需求,Stream 几乎是唯一顺手的选择。
3.2 序列化方案怎么选
Redis 里只能存字节,Python 的 dict、LangGraph 的状态对象全都需要序列化。方案选错了,轻则体积大效率低,重则直接埋雷。
我的优先级建议:优先 JSON,搭配 Pydantic 这类强类型库做模型定义;性能敏感的场景用 MessagePack;坚决不用 pickle。JSON 的好处是跨语言、可调试,redis-cli 里直接能看内容,出问题容易排查。缺点是有类型不支持,比如 datetime,需要自己写编解码器。
pickle 的问题必须单独说:很多人图省事,直接把 agent 状态对象 pickle 后塞进 Redis,单机脚本没问题,一上生产就是双重坑。第一,pickle 反序列化存在任意代码执行风险,Redis 里的数据被污染就等于远程执行;第二,类结构一变动,老 pickle 数据全部反序列化失败,代码升级直接导致线上会话恢复崩溃。用 JSON 存数据,用类做运行时转化,升级结构只需要加兼容逻辑。
序列化还有体积问题。LangGraph 的完整状态里有很多临时字段和中间堆栈,全量存进 Redis 太浪费。我通常定义一个 SessionSnapshot 模型,只挑恢复执行所需的最小字段集,tool_results 这类重字段默认只留最近 5 条,更早的转存到对象存储或数据库,Redis 里只留近期“热状态”。
3.3 TTL 与缓存更新策略
缓存不是“存进去就完事”,TTL 策略直接决定这套缓存会不会变成脏数据制造机。Agent 场景里我习惯把缓存分成四类对待:
- 纯临时数据:单次工具调用的中间结果、锁标记,TTL 给几十秒到几分钟,过期拉倒。
- 会话状态:滑动过期,不活跃自动清理,活跃会话一直续期。
- 语义缓存:按知识变更频率设置,常识类 7 天、业务规则类 24 小时、实时数据类 5 分钟。
- 长期稳定配置:prompt 模板、工具注册表,几乎不变,但必须加版本号,发布新版本时主动失效。
缓存更新最容易出问题的点是“先更库再删缓存”还是“先删缓存再更库”。在 Agent 场景里,知识库更新后语义缓存经常出现不一致,用户拿旧答案。我的做法是双删:先删缓存,更新数据源,等几秒再删一次。并发下第一次删除后可能有一个请求回源把旧数据写回缓存,延迟几秒再删一次能堵住这个时间窗。为了彻底兜底,语义缓存里存了数据源版本号,查询时比对版本,不一致直接穿透,从根源上解决脏缓存问题。
4. 实操:一套可复制的 Agent + Redis 实现
4.1 架构概览与组件选型
上面讲了一堆理论,这里给一套可以直接照搬的组合。技术选型用目前社区最主流的一套:FastAPI 做 API 层,LangGraph 做 agent 编排,Redis 做状态和缓存层。FastAPI 胜在异步、类型友好、生态成熟;LangGraph 适合把 agent 的节点和边显式定义出来,中间状态可控,这对接 Redis 持久化特别重要。
部署上,Redis 用 Docker 起一个主从 + Sentinel 的高可用组,master 挂了自动切换,agent 服务无需感知。数据量特别大再考虑 Redis Cluster,但大多数 AI 应用的会话数据规模,主从 Sentinel 已经绰绰有余。别一上来就上 Cluster,运维复杂度和客户端配置成本会成倍上升。
这里顺带说一句 Docker 部署主从的常见坑:很多新手卡在“主从复制连不上”。我一般用 docker-compose 把 master 和 slave 放进同一个自定义网络,slave 启动参数里指向 master 的服务名而不是 localhost。最容易错的两个点:一是忘配 replicaof 指令,二是把地址写成本机回环地址,容器里当然连不通。配好后用INFO replication一眼看到 role 和 slave 连接状态,这是最直接的验证手段。
4.2 核心代码实现
下面用一个“带缓存的检索问答 Agent”作为例子,完整覆盖会话状态、语义缓存、分布式锁三个核心点。为了不暴露完整业务代码,我缩成了最小可复现的核心逻辑。
先看 Redis 连接与基础封装:
# redis_client.py import redis from redis.connection import ConnectionPool POOL = ConnectionPool( host="redis-master", port=6379, password="your_password", max_connections=64, socket_timeout=3, socket_connect_timeout=3, decode_responses=True, # 字符串场景直接返回 str ) def get_redis(): return redis.Redis(connection_pool=POOL)注意 max_connections 很关键。FastAPI 是异步框架,但 redis-py 默认客户端是同步阻塞的,如果每个请求都新建连接,Redis 连接数很快被打满。用连接池复用是基本操作。如果你走 FastAPI 原生异步生态,也可以考虑redis.asyncio,但 LangGraph 很多内部操作是同步的,混用时容易阻塞事件循环。我个人在这种 agent 场景里更倾向连接池 + 同步客户端,配合 FastAPI 的线程池执行,反而更好控制。
接着是会话状态读写:
# session_store.py import json, time from redis_client import get_redis SESSION_TTL = 1800 # 30分钟不活跃过期 def save_session(uid: str, messages: list, plan: dict = None): r = get_redis() key = f"agent:{uid}:session" data = { "messages": messages[-20:], # 只维护最近20轮 "plan": plan, "updated_at": int(time.time()), } # 用 pipeline 保证 "写状态 + 续期" 一批执行 with r.pipeline(transaction=True) as pipe: pipe.delete(key) # 先删后写,避免 Hash 里残留旧字段 pipe.hset(key, mapping=data) pipe.expire(key, SESSION_TTL) pipe.execute() def load_session(uid: str): r = get_redis() key = f"agent:{uid}:session" data = r.hgetall(key) if not data: return None r.expire(key, SESSION_TTL) # 每次读取都续期,实现滑动过期 return datahset 之前先 delete,是为了避免 Hash 里旧字段残留。HGETALL 会把所有字段读出来,老字段不清掉会污染下一次读取。写入和 expire 用 pipeline 包住,保证会话不会出现“状态写了但没设置过期时间”的孤儿数据。
再看语义缓存实现,这是很多人抄作业最容易抄歪的部分:
# semantic_cache.py import numpy as np def embed(text: str) -> np.ndarray: from your_llm_api import get_embedding vec = get_embedding(text) return np.array(vec, dtype=np.float32) def cosine_sim(a: np.ndarray, b: np.ndarray) -> float: return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b) + 1e-9)) def semantic_lookup(question: str, threshold: float = 0.92): r = get_redis() vec = embed(question) # 生产环境建议改用 RediSearch KNN,这里用遍历写法便于理解 keys = r.keys("agent:semantic:*") best_sim = 0.0 best_key = None for k in keys: meta = r.hgetall(k) if meta.get("version") != CURRENT_KB_VERSION: continue old_vec = np.frombuffer(meta["vec"], dtype=np.float32) sim = cosine_sim(vec, old_vec) if sim > best_sim: best_sim, best_key = sim, k if best_key and best_sim >= threshold: return r.hget(best_key, "answer") return None def semantic_store(question: str, answer: str, ttl: int): r = get_redis() key = f"agent:semantic:{uuid4().hex}" r.hset(key, mapping={ "q": question, "answer": answer, "vec": embed(question).tobytes(), # 向量必须按字节存 "version": CURRENT_KB_VERSION, "created_at": int(time.time()), }) r.expire(key, ttl)这段代码有两个实战里抠出来的细节。一个是向量必须用tobytes()存原始 float32,不要存成 List,否则体积大好几倍还影响检索速度。另一个是缓存必须带 version 字段,知识库发布时整体替换 CURRENT_KB_VERSION,旧缓存因版本不匹配被跳过,语义缓存就不会返回过时答案。生产环境里逐条遍历太慢,建议直接上 RediSearch 向量索引,这里为了可读性才用遍历写法。
最后是分布式锁:
# agent_lock.py import uuid from redis_client import get_redis LOCK_TTL = 30 def acquire_lock(uid: str, token: str, timeout: int = LOCK_TTL) -> bool: r = get_redis() return r.set(f"agent:{uid}:lock", token, nx=True, ex=timeout) def release_lock(uid: str, token: str): r = get_redis() script = """ if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end """ r.eval(script, 1, f"agent:{uid}:lock", token)acquire 用SET NX EX一条命令完成,不要先 EXISTS 再 SET,那是两个操作,并发下必然出错。release 用 Lua 脚本校验 token,避免持锁线程超时后另一个线程拿到锁,却被前面线程误删。需要续期的话,后台协程每 10 秒对这个 key 重新 EXPIRE 即可。
4.3 并发扛量与 Redis 参数调优
分享几个压测和线上调优过程中验证过的参数,照着设置基本不会出大问题。
首先是 Redis 端的三个配置。maxmemory 一定要设,比如 2gb,并配合 allkeys-lru 或 volatile-lru 淘汰策略。不设 maxmemory 的后果是内存被打满后 Redis 进入保护模式开始拒绝写入,线上 agent 的会话全部无法保存,这是事故级别的故障。我推荐 volatile-lru,只淘汰设置了过期时间的 key,锁和临时数据优先被淘汰,常驻配置不会被误伤。第三个是 timeout 设置成 0,防止服务端断开空闲连接后,客户端还拿着旧连接重试,反而人为制造命令超时。
客户端这边,redis-py 连接池的 max_connections 不要盲目调大。我见过有人直接设 500,连接全堆在 Redis 上本身就是资源开销,通常 32-128 足够。socket_timeout 设 3 秒左右,Redis 一旦出问题,快速失败比让用户无限转圈好得多。
再强调一个容易被忽略的点:批量操作。Agent 每轮循环要读会话、查缓存、记日志、续期,如果是十几条命令逐条发,网络往返成了最大瓶颈。一律用 pipeline 合并。我压测验证过,50 个并发 agent 请求,每条状态链路 8 个操作,从逐条执行改成 pipeline,整体耗时从 400ms 降到 90ms,这个优化比加机器还立竿见影。注意 pipeline 的代价是命令执行结果不是实时可见的,所以只适合“写批量”场景。
5. 常见问题与排查技巧实录
5.1 Redis command timed out 怎么排查
很多 Java 项目用 Lettuce 客户端时会遇到这个经典报错:“redis command timed out; nested exception is io.lettuce.core.RedisCommandTimedOutException”。这个问题的本质是 Lettuce 默认只给 60 秒命令超时,而 Agent 系统里常常会有“一次操作打包几百个命令”或“写入一个大 KV”的场景,单条命令执行超时就直接抛异常。诱因通常集中在三类:
第一是 Redis 持久化阻塞。开启 RDB 快照且数据量大时,BGSAVE 瞬间会阻塞主线程,命令自然超时。解法是调整 RDB 快照触发频率,或者改用 AOF 的 everysec 模式减小阻塞窗口。第二是客户端连接池耗尽,前面说了,连接数设太大反而加剧问题。第三是 big key,单个 key 的 value 过大,比如几 MB 的序列化结果,写一次要几十毫秒甚至更久,并发一高就开始成片超时。
排查手段我习惯三步走。先redis-cli执行SLOWLOG GET 100看慢命令是什么,如果是针对某个巨型 key 的操作,直接进业务层拆 key;再看INFO stats里的 instantaneous_ops_per_sec,如果高到几万而上层 QPS 并不高,多半是业务逻辑在循环里频繁打 Redis,用 pipeline 合并;最后看INFO memory和 used_memory 的波动,配合监控确认是不是持久化阻塞。这套流程跑下来,大多数超时问题都能定位到根因。
5.2 缓存穿透、击穿、雪崩的应对
这三个词在缓存面试题里是标配,但放在 Agent 系统里各有各的“变种”。
缓存穿透:用户问题不在知识库里,语义缓存和数据库都查不到,请求直接打到 LLM 上。语义缓存场景的穿透尤其隐形——你没法靠“把 null 值也缓存”来防,因为 LLM 对同一个问题的回答每次都不一样。我的应对是维护一份“不可应答问题”的高频特征布隆过滤器,比如命中拒绝策略的输入,在入口直接拦截,不走缓存也不走 LLM。
缓存击穿:热点 key 过期瞬间大量请求同时回源。Agent 场景里常见于某个爆款 prompt 的语义缓存或热门知识库版本切换。对付击穿,推荐“逻辑过期”而不是“真正过期”:缓存数据里存一个过期时间字段,查询时发现超期后用分布式锁让一个请求去刷新,其他请求暂时返回旧值。这在语义缓存上特别实用,知识库更新后旧答案最多错几分钟,用户几乎无感。
缓存雪崩:大量 key 同一时间过期。Agent 场景里常见于批量发布的 prompt 模板全设了同一个 TTL,到点集体失效,所有请求全部回源,把 LLM 和数据库打崩。解法是老一套但很有效:过期时间加随机抖动,比如 TTL 在 24 小时基础上加 0-30 分钟的随机值。我要求团队所有“批量写缓存”的定时任务,发布前检查过期时间分布,强制加抖动,这也是压测时容易漏掉的一环。
5.3 序列化与数据一致性避坑
最后一个坑来自我带团队时亲眼见过的线上事故。同事把 LangGraph 的关键节点输出整个 JSON 存进缓存,凌晨发版改了节点结构,旧缓存反序列化全部报 validation error,所有老会话恢复时统统 500。从那以后我定了一条铁律:缓存里的数据模型必须有版本号,读取时校验版本,低版本数据宁可全量重建也绝不强行兼容;同时所有模型字段用可选 + 默认值,新增字段不破坏老数据。
数据一致性的最典型问题在语义缓存上:知识库更新后,用户问同一问题,缓存里还存着旧答案。光靠 TTL 兜不住,必须靠版本号主动失效才稳。另外,重新计算 embedding 也是坑:知识库条目更新后,旧向量在向量索引里继续命中,返回的却是老答案。所以语义缓存的 key 设计必须包含 data_source_version,查询时构造带版本的 key,版本一换自然 miss。
还有一个不常见但很要命的场景:Redis 主从切换时的数据丢失。Agent 的会话状态只有一份存在 Redis 里,master 故障切换时如果 AOF 没同步到最新,会有几十秒的数据丢失,用户感知就是“聊着聊着 agent 失忆了”。要完全避免需要业务层做兜底,我的方案是把“已完结任务”的最终会话快照异步写一份到对象存储,Redis 只放“进行中任务”的短期状态。丢失窗口从“永久丢失”变成“最多回滚到上一个已完结任务”,这个设计建议所有生产级 agent 都参考。
这套东西我前后磨合了大概三个月,最深的体会是:AI Agent 本身是新鲜事,但它下面的地基仍然是分布式系统那套老学问。会话状态该放哪、锁怎么保证原子性、缓存命中率怎么调、超时怎么快速失败,这些问题跟业务逻辑无关,但决定了一个 agent 到底是 demo 还是产品。
如果你正准备给自己的 agent 加缓存层,我的建议是别贪多,先把“会话状态 Redis 化”和“语义缓存命中率”这两件事做扎实,这两个收益最直接,做完再上锁、再上限流,一层层加。踩过的坑写出来就是给别人省时间,这篇文章要是能让你少走我在命令超时、pickle 反序列化、缓存雪崩上走过的弯路,那这三个月就没白费。