1. 为什么 AI Agent 绕不开 Redis 这层缓存
做 AI Agent 开发的人,迟早会撞上同一个问题:Agent 跑着跑着就慢了,慢到用户开始怀疑是不是网络卡了。我最早搭第一版 Agent 的时候,每个请求都要重新走一遍大模型的推理链路,工具调用结果、会话上下文、向量检索的中间结果全部现算。单次对话还好,一旦进入多轮工具编排,延迟直接飙到十几秒。后来我把 Redis 塞进架构里,同样的任务链路压到了两秒以内。这不是玄学,是缓存命中率带来的实打实的收益。
AI Agent 和传统 Web 服务的缓存需求有本质区别。传统接口缓存的是数据库查询结果,key 和 value 结构固定,过期策略简单。Agent 不一样,它的缓存对象五花八门:LLM 的推理结果、工具调用的返回值、RAG 检索的文档片段、会话状态、甚至整个 Agent 的中间推理步骤。这些东西的共同点是计算成本高、重复率高、时效性要求分层。一次向量检索可能要几百毫秒,一次工具调用可能涉及外部 API 的配额消耗,如果同样的输入反复触发这些操作,既浪费钱又拖慢响应。
Redis 在这个场景里的角色,不只是"缓存"两个字能概括的。它同时承担了分布式锁、会话存储、限流计数器、消息队列这几个职责。我见过不少 Agent 项目用内存字典做缓存,单机跑没问题,一上多实例就出现会话串号、工具重复调用、状态不一致。Redis 的原子操作和过期机制,恰好能把这些坑填上。
这篇文章面向的是正在搭建或优化 AI Agent 的开发者,不管你用的是 Python、Rust 还是 Java 技术栈,只要你的 Agent 需要处理多轮对话、工具编排、RAG 检索这些场景,Redis 的接入方式都值得认真设计一遍。我会从缓存对象分类讲到 key 设计、序列化选型、分布式锁、缓存一致性,再到线上治理和踩坑记录,尽量把每个决策背后的"为什么"说清楚。
提示:本文讨论的 Redis 用法聚焦在 AI Agent 场景下的缓存与状态管理,不涉及任何网络代理相关内容。所有配置和代码均基于标准 Redis 客户端库。
2. Agent 场景下到底该缓存哪些东西
2.1 四类缓存对象的成本与时效分析
在动手写代码之前,先想清楚一件事:你的 Agent 里哪些东西值得缓存。我的经验是,按"计算成本"和"时效敏感度"两个维度来分类,能覆盖绝大多数场景。
第一类是LLM 推理结果。同一个 prompt 在短时间内重复请求的概率不低,尤其是系统提示词加固定上下文的部分。这类缓存的计算成本极高(一次推理可能几毛钱),但时效敏感度中等,因为模型本身不会频繁变。缓存时间可以设到几分钟到几小时。
第二类是工具调用返回值。比如天气查询、汇率转换、数据库读取,这些外部调用的延迟和配额都是成本。时效敏感度高,天气数据五分钟就过期了,但汇率可能一分钟就变。这类缓存要按业务设短过期时间。
第三类是RAG 检索结果。向量检索的 embedding 计算和相似度匹配都很吃资源,同一批文档的检索结果在文档没更新前是稳定的。时效敏感度低,但要注意文档更新时的缓存失效。
第四类是会话状态与中间推理步骤。多轮对话的上下文、Agent 的思考链、工具调用的中间结果,这些需要持久化但不能永久保存。时效敏感度取决于会话长度,通常设几十分钟到几小时。
| 缓存对象 | 计算成本 | 时效敏感度 | 建议过期时间 | 典型 key 前缀 |
|---|---|---|---|---|
| LLM 推理结果 | 极高 | 中 | 5min - 2h | agent:llm: |
| 工具调用返回值 | 中高 | 高 | 30s - 5min | agent:tool: |
| RAG 检索结果 | 高 | 低 | 10min - 24h | agent:rag: |
| 会话状态 | 低 | 中 | 30min - 2h | agent:session: |
这张表不是拍脑袋定的,是我在实际项目里反复调参后总结的。刚开始我把所有缓存的过期时间都设成一样,结果要么是工具数据过期太慢导致用户看到旧信息,要么是 LLM 结果过期太快导致缓存命中率上不去。分层设置之后,命中率和数据新鲜度都明显改善。
2.2 缓存粒度:缓存整个响应还是缓存中间步骤
这是很多人纠结的点。缓存整个 Agent 响应,实现简单,命中就是一次 key 查询,但问题是只要输入有一点点变化(比如用户多打了一个字),整个缓存就失效了。缓存中间步骤,粒度细,复用率高,但管理复杂度上去了。
我的建议是分层缓存。最外层缓存完整的响应,key 由用户输入加会话 ID 的哈希生成,适合高频重复的简单问答。内层缓存各个中间步骤,比如工具调用结果、检索片段,这些的 key 由具体参数生成,复用率远高于完整响应。
举个例子,用户问"北京今天天气怎么样",完整响应缓存可能只对完全相同的问法生效。但如果把"天气查询工具 + 北京 + 今天"作为 key 缓存工具返回值,那么"北京今天天气如何""今天北京天气"这些变体都能命中同一份工具结果。实际测试下来,中间步骤缓存的命中率通常是完整响应缓存的三到五倍。
2.3 什么情况下坚决不能缓存
有些东西缓存了会出大问题。涉及用户权限校验的结果不能缓存,除非 key 里带上用户身份且做过隔离。实时性要求极高的数据不能缓存,比如股票价格、库存数量。带副作用的操作结果不能缓存,比如"发送消息""创建订单"这类工具调用,缓存了会导致重复执行。
还有一个容易被忽略的点:Agent 的随机性输出。如果你的 Agent 配置了较高的 temperature,同样的输入每次输出都不同,缓存完整响应就没意义了。这种情况下只缓存确定性的中间步骤,比如检索结果和工具返回值。
3. Key 设计与序列化选型:决定缓存效率的隐形战场
3.1 Key 命名规范与哈希策略
Redis 的 key 设计看起来简单,但在 Agent 场景下很容易失控。我见过最离谱的 key 是一长串 JSON 直接当 key 用,几百个字符,不仅占内存,查询还慢。好的 key 设计要满足三个条件:可读、定长、无冲突。
我的命名规范是业务前缀:对象类型:标识符。比如agent:llm:conv123:hash456,其中 hash456 是 prompt 内容的哈希值。用哈希而不是原文,是为了控制 key 长度。哈希算法选 SHA256 取前 16 位就够了,冲突概率在 Agent 这种量级下可以忽略。
这里有个细节:哈希的输入要包含所有影响输出的参数。比如 LLM 缓存,哈希输入应该包括 prompt、模型名称、temperature、max_tokens。少算一个参数,就可能返回错误的缓存结果。我踩过这个坑,早期只哈希了 prompt,结果换了模型之后返回的还是旧模型的结果,排查了半天。
import hashlib import json def build_llm_cache_key(prompt, model, temperature, max_tokens): raw = json.dumps({ "prompt": prompt, "model": model, "temperature": temperature, "max_tokens": max_tokens }, sort_keys=True) digest = hashlib.sha256(raw.encode()).hexdigest()[:16] return f"agent:llm:{digest}"sort_keys=True这个参数别省,它保证字典序列化顺序一致,否则同样的内容可能生成不同的哈希。
3.2 序列化格式的取舍:JSON、MessagePack 还是 Protobuf
序列化选型直接影响缓存的读写速度和内存占用。JSON 可读性好,调试方便,但体积大、解析慢。MessagePack 体积小、速度快,但可读性差。Protobuf 体积最小、速度最快,但需要定义 schema,改起来麻烦。
在 Agent 场景下,我推荐MessagePack 作为默认选择。原因是 Agent 缓存的对象结构通常比较复杂(嵌套的字典、列表),JSON 序列化后体积膨胀明显,而 MessagePack 能省下 30% 到 50% 的内存。解析速度上,MessagePack 也比 JSON 快不少,在高并发场景下这个差距会被放大。
import msgpack def serialize(data): return msgpack.packb(data, use_bin_type=True) def deserialize(raw): return msgpack.unpackb(raw, raw=False)use_bin_type=True和raw=False这两个参数要配对使用,否则字符串和二进制会混淆。这是 MessagePack 的经典坑,我第一次用的时候没注意,取出来的字符串全是 bytes。
如果缓存的对象结构非常固定,比如就是几个字段的会话状态,那 Protobuf 更合适。但 Agent 的中间结果往往结构多变,Protobuf 的 schema 维护成本反而成了负担。所以我的原则是:结构固定用 Protobuf,结构灵活用 MessagePack,调试阶段用 JSON。
3.3 大 key 的拆分与压缩
Agent 缓存里最容易出现大 key 的地方是会话历史和 RAG 检索结果。一个长会话的上下文可能几百 KB,一次检索返回的文档片段加起来也可能上百 KB。大 key 的问题不只是占内存,更严重的是阻塞 Redis 单线程,导致其他请求超时。
处理大 key 有两个思路。一是拆分,把会话历史按轮次拆成多个 key,用列表或有序集合存储,读取时按需取。二是压缩,对超过阈值的 value 做压缩再存。我通常两个一起用:会话历史拆成每轮一个 key,单个 value 超过 10KB 就用 zstd 压缩。
import zstd COMPRESS_THRESHOLD = 10 * 1024 def smart_set(redis_client, key, data, ex=3600): raw = serialize(data) if len(raw) > COMPRESS_THRESHOLD: raw = zstd.compress(raw) redis_client.set(key, raw, ex=ex) redis_client.set(f"{key}:compressed", "1", ex=ex) else: redis_client.set(key, raw, ex=ex) def smart_get(redis_client, key): raw = redis_client.get(key) if raw is None: return None if redis_client.get(f"{key}:compressed"): raw = zstd.decompress(raw) return deserialize(raw)压缩阈值设 10KB 是我的经验值,低于这个数压缩后的体积可能反而更大(压缩头开销),高于这个数压缩收益明显。这个值可以根据你的实际数据分布调整。
4. 分布式锁与缓存一致性:Agent 并发下的保命机制
4.1 为什么 Agent 比普通服务更需要分布式锁
普通 Web 服务的并发问题通常是数据库层面的,用事务就能解决。Agent 的并发问题更复杂,因为它涉及外部工具调用、LLM 推理、状态更新这一整条链路,而且这些操作往往不是原子的。
最典型的场景是:两个请求同时发现缓存未命中,同时去调用 LLM,同时写缓存。这不仅浪费了一次推理成本,还可能导致状态覆盖。更严重的是工具调用,比如"发送邮件"这种有副作用的操作,并发执行会发两封。
分布式锁的作用就是保证同一时刻只有一个请求去执行昂贵的操作,其他请求等待结果或直接返回旧值。Redis 的SET NX EX命令是实现分布式锁的基础,原子性由 Redis 单线程模型保证。
import uuid import time def acquire_lock(redis_client, lock_key, timeout=10): token = str(uuid.uuid4()) end = time.time() + timeout while time.time() < end: if redis_client.set(lock_key, token, nx=True, ex=timeout): return token time.sleep(0.05) return None def release_lock(redis_client, lock_key, token): lua = """ if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end """ redis_client.eval(lua, 1, lock_key, token)释放锁必须用 Lua 脚本,保证"检查 token"和"删除 key"是原子的。如果分两步做,可能在检查通过之后、删除之前锁刚好过期,然后删掉了别人的锁。这个坑我在早期项目里踩过,导致锁机制形同虚设。
4.2 缓存穿透、击穿、雪崩在 Agent 里的具体表现
这三个经典问题在 Agent 场景下有独特的表现形式。
缓存穿透:查询一个不存在的 key,每次都打到后端。在 Agent 里,这通常发生在用户输入了无法匹配任何缓存的结果,比如一个全新的问题。解决方案是缓存空结果,设一个较短的过期时间,比如 30 秒。
缓存击穿:某个热点 key 过期瞬间,大量请求同时打到后端。Agent 里的典型场景是热门问题的 LLM 缓存过期。解决方案是用分布式锁保证只有一个请求去重新计算,其他请求等待。
缓存雪崩:大量 key 同时过期,后端压力骤增。Agent 里如果批量预热缓存时设了相同的过期时间,就会遇到这个问题。解决方案是在过期时间上加随机抖动。
import random def set_with_jitter(redis_client, key, value, base_ex=3600): jitter = random.randint(0, int(base_ex * 0.1)) redis_client.set(key, value, ex=base_ex + jitter)抖动范围设基础过期时间的 10% 就够了,太大反而影响缓存命中率。
4.3 缓存与数据源的一致性维护
Agent 的缓存一致性比传统服务更难保证,因为数据源可能是外部 API、向量数据库、甚至另一个 Agent 的输出。我的策略是以过期时间为主,主动失效为辅。
过期时间保证最终一致,主动失效保证关键数据的及时更新。比如 RAG 的文档缓存,文档更新时主动删除对应的 key,同时保留过期时间作为兜底。主动失效用 Redis 的DEL或UNLINK命令,后者是非阻塞的,适合删除大 key。
注意:不要试图用"先更新数据库再更新缓存"或"先删缓存再更新数据库"这种经典模式来保证强一致,在 Agent 场景下数据源往往不支持事务,强一致的成本远高于收益。接受短暂的不一致,用过期时间兜底,是更务实的选择。
5. 从单机到集群:Redis 部署形态对 Agent 的影响
5.1 单机、主从、哨兵、集群的选型逻辑
Agent 项目在不同阶段对 Redis 的需求完全不同。开发阶段单机足够,一个redis-server起来就能跑。但上线之后,单机的可用性就成了问题,进程挂了整个 Agent 就瘫了。
主从复制解决的是读扩展和故障备份,但主节点挂了需要手动切换。哨兵模式在主从基础上加了自动故障转移,适合中小规模的生产环境。集群模式做的是数据分片,适合数据量大、并发高的场景,但运维复杂度也上去了。
我的建议是:日活一万以下的 Agent 用哨兵模式,以上考虑集群。哨兵模式配置简单,故障转移通常在几十秒内完成,对 Agent 这种允许短暂不可用的场景够用了。集群模式虽然扩展性好,但跨 slot 的操作受限,比如事务和 Lua 脚本在多 key 场景下会报错,Agent 的分布式锁如果涉及多个 key 就要小心。
| 部署形态 | 可用性 | 扩展性 | 运维复杂度 | 适用场景 |
|---|---|---|---|---|
| 单机 | 低 | 无 | 极低 | 开发测试 |
| 主从 | 中 | 读扩展 | 低 | 小规模读多写少 |
| 哨兵 | 高 | 读扩展 | 中 | 中小规模生产 |
| 集群 | 高 | 读写扩展 | 高 | 大规模高并发 |
5.2 Docker 环境下的 Redis 主从搭建实操
用 Docker 搭 Redis 主从是最快的方式,几条命令就能跑起来。我习惯用 docker-compose 管理,配置文件清晰,重启方便。
version: '3.8' services: redis-master: image: redis:7-alpine ports: - "6379:6379" command: redis-server --appendonly yes --requirepass yourpassword volumes: - ./master-data:/data redis-slave: image: redis:7-alpine ports: - "6380:6379" command: redis-server --appendonly yes --requirepass yourpassword --replicaof redis-master 6379 --masterauth yourpassword depends_on: - redis-master volumes: - ./slave-data:/dataappendonly yes开启 AOF 持久化,Agent 的会话状态丢了会很麻烦,AOF 比 RDB 的数据安全性更高。requirepass和masterauth要设成一样的,否则从节点连不上主节点。
启动之后用redis-cli -h localhost -p 6379 info replication检查主节点状态,看到connected_slaves:1就说明主从同步成功了。从节点用redis-cli -h localhost -p 6380 info replication检查,master_link_status:up表示连接正常。
5.3 持久化策略:RDB 和 AOF 在 Agent 场景下的取舍
RDB 是快照持久化,恢复快但可能丢数据。AOF 是日志持久化,数据安全性高但文件大、恢复慢。Agent 场景下我的选择是两个都开,但以 AOF 为主。
会话状态和工具调用结果丢了会影响用户体验,AOF 的everysec策略最多丢一秒的数据,可以接受。RDB 作为冷备份,定期生成快照,用于灾难恢复。AOF 文件会越来越大,需要配置auto-aof-rewrite-percentage和auto-aof-rewrite-min-size触发重写。
appendonly yes appendfsync everysec auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mbappendfsync有三个选项:always每条命令都刷盘,最安全但性能最差;everysec每秒刷一次,性能和安全的平衡点;no交给操作系统决定,性能最好但可能丢较多数据。Agent 场景选everysec就够了。
6. 线上缓存治理:命中率、内存与故障排查
6.1 命中率上不去的五个常见原因
缓存命中率是衡量缓存效果的核心指标,但很多人只看数字不分析原因。命中率低通常有五个原因,我按排查优先级列一下。
第一是过期时间设得太短。工具调用结果设 30 秒过期,但实际调用频率是每分钟一次,那命中率自然低。解决办法是分析实际访问模式,把过期时间调到访问间隔的两到三倍。
第二是key 设计不合理。哈希输入包含了不影响输出的参数,导致同样的逻辑请求生成了不同的 key。排查方法是打印实际生成的 key,看看有没有冗余字段。
第三是缓存对象太大导致写入失败。Redis 单 value 上限是 512MB,但实际超过几 MB 就会影响性能。大对象要么拆分要么压缩。
第四是内存不足触发淘汰。maxmemory-policy设成allkeys-lru时,内存满了会淘汰任意 key,包括热点 key。Agent 场景建议用volatile-lru,只淘汰设了过期时间的 key,保护持久化的会话数据。
第五是并发写入覆盖。多个请求同时写同一个 key,后写的覆盖先写的。用分布式锁或者SET NX可以避免。
6.2 内存告警的排查链路
Redis 内存告警是线上最常见的问题之一。我的排查链路是这样的:先用info memory看used_memory和used_memory_rss的差距,差距大说明有内存碎片。然后用redis-cli --bigkeys找出大 key,用redis-cli --memkeys按内存排序找 top key。
找到大 key 之后,分析它是正常的业务数据还是异常累积。如果是会话历史,考虑拆分或设置更短的过期时间。如果是某个工具返回了异常大的结果,要在工具层做截断。
redis-cli -h yourhost -p 6379 --bigkeys redis-cli -h yourhost -p 6379 --memkeys redis-cli -h yourhost -p 6379 info memory--bigkeys会扫描所有 key,在生产环境慎用,最好在从节点执行。--memkeys更精确但更慢,适合在低峰期做。
6.3 慢查询与阻塞操作的定位
Redis 是单线程的,一个慢查询会阻塞所有后续请求。Agent 场景下常见的慢查询来源是KEYS *、大 key 的DEL、以及复杂的 Lua 脚本。
用slowlog get 10查看最近的慢查询,slowlog-log-slower-than设成 10000 微秒(10 毫秒)比较合适。如果发现慢查询,先看命令类型,KEYS和FLUSHALL这类命令在生产环境应该禁用,用SCAN替代KEYS,用UNLINK替代DEL。
redis-cli config set slowlog-log-slower-than 10000 redis-cli slowlog get 10Lua 脚本的慢查询要特别注意,因为脚本执行期间 Redis 完全阻塞。脚本里不要做循环遍历大量 key 的操作,把复杂逻辑拆到客户端做。
7. 那些让我熬夜的坑:实战踩坑记录
7.1 序列化不一致导致的乱码问题
这个坑我踩过两次。第一次是 Python 端用 MessagePack 序列化,Java 端用 JSON 反序列化,取出来的数据全是乱码。原因是两边的序列化格式没对齐。解决办法是统一序列化协议,或者在 value 里加一个格式标识字段。
第二次是 MessagePack 的raw参数没设对,Python 3 里取出来的字符串是 bytes 类型,直接拼接到 prompt 里就出问题了。这个坑的教训是:序列化和反序列化的参数必须成对配置,最好封装成统一的工具函数,不要在每个调用点手写。
7.2 分布式锁过期时间设短了导致的并发问题
早期我把锁的过期时间设成 5 秒,觉得够用了。结果有一次 LLM 推理花了 8 秒,锁提前过期,第二个请求拿到锁又执行了一遍,导致同一条消息被处理了两次。后来我把过期时间改成根据操作类型动态设置,LLM 推理设 30 秒,工具调用设 10 秒,并且加了锁续期机制。
锁续期的思路是起一个后台线程,每隔过期时间的三分之一就检查锁是否还在,在的话就延长过期时间。这样即使操作时间超出预期,锁也不会提前释放。
import threading def start_lock_renewal(redis_client, lock_key, token, interval=5): def renew(): while True: time.sleep(interval) lua = """ if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('expire', KEYS[1], ARGV[2]) else return 0 end """ result = redis_client.eval(lua, 1, lock_key, token, 30) if result == 0: break t = threading.Thread(target=renew, daemon=True) t.start() return t7.3 缓存预热做过头反而拖慢启动
有段时间我想提高命中率,在 Agent 启动时把历史热门问题全部预热到 Redis。结果启动时间从几秒变成了几分钟,而且预热的数据大部分根本用不上。后来改成懒预热:只在第一次请求时缓存,同时用一个后台任务统计高频 key,定期预热 top 100。
这个教训是:预热要基于实际访问数据,不要凭感觉。没有数据支撑的预热,大概率是浪费资源。
7.4 集群模式下 Lua 脚本的跨 slot 报错
从单机迁到集群时,我的分布式锁 Lua 脚本报错了,原因是脚本里操作了多个 key,而这些 key 不在同一个 slot。集群模式下,涉及多 key 的操作必须保证所有 key 在同一个 slot,通常用 hash tag 实现。
# 用 {} 包裹的部分作为 hash tag,保证相关 key 在同一 slot lock_key = f"agent:lock:{{{conversation_id}}}" cache_key = f"agent:llm:{{{conversation_id}}}:{digest}"hash tag 的代价是数据分布可能不均匀,如果某个 conversation_id 特别热,对应的 slot 会成为热点。所以 hash tag 的粒度要选好,太细没效果,太粗会倾斜。
8. 监控指标与容量规划:让缓存系统可观测
8.1 必须盯住的六个核心指标
缓存系统上线之后,没有监控等于裸奔。我必看的指标有六个:命中率、内存使用率、连接数、慢查询数量、淘汰 key 数量、主从延迟。
命中率低于 70% 就要排查原因,内存使用率超过 80% 要扩容或清理,连接数接近maxclients要调大限制,慢查询数量突增说明有异常命令,淘汰 key 数量持续增长说明内存不够,主从延迟超过一秒说明网络或主节点压力有问题。
redis-cli info stats | grep keyspace_hits redis-cli info stats | grep keyspace_misses redis-cli info memory | grep used_memory_human redis-cli info clients | grep connected_clients redis-cli info replication | grep master_link_status这些指标建议接入 Prometheus 加 Grafana,用 redis_exporter 采集。告警阈值按业务特点设,比如命中率低于 60% 告警,内存使用率高于 85% 告警。
8.2 容量估算:一个 Agent 请求到底占多少内存
容量规划不能拍脑袋,要基于实际数据估算。一个典型的 Agent 请求涉及的缓存数据包括:会话状态约 2KB,LLM 结果约 5KB,工具调用结果约 1KB,RAG 检索片段约 20KB。加起来约 28KB,算上 Redis 的 overhead,按 40KB 估。
如果日活一万,每人每天 20 个请求,缓存保留 1 小时,那么峰值并发假设 100,缓存数据量约 100 × 40KB × 平均保留请求数。这个数算下来通常不大,但要注意大 key 和内存碎片的影响,实际预留要乘以 1.5 到 2 的安全系数。
8.3 扩容时机与缩容策略
扩容的触发条件通常是内存使用率持续超过 80%,或者命中率因为淘汰而下降。扩容方式取决于部署形态,哨兵模式加从节点扩展读能力,集群模式加分片扩展写能力。
缩容要谨慎,因为缩容涉及数据迁移,可能触发大量 key 的重新分布。我的做法是先缩过期时间,再缩数据量,最后才缩节点。通过调短过期时间自然淘汰冷数据,观察一段时间确认没有影响再动节点。
9. 写在最后:一些个人体会
Redis 在 AI Agent 架构里的位置,比很多人想象的重要。它不只是个缓存,更像是 Agent 的短期记忆和协调中枢。会话状态、工具结果、推理中间步骤、并发控制,这些都依赖 Redis 的原子性和低延迟。
我最大的体会是:缓存设计要从业务场景出发,不要为了缓存而缓存。见过太多项目上来就套一套缓存模板,结果命中率低、一致性差、维护成本高。先想清楚哪些数据值得缓存、缓存多久、失效了怎么办,再动手写代码,能省下大量返工时间。
另一个体会是监控比优化更重要。没有监控数据,优化就是盲人摸象。把命中率、内存、慢查询这几个指标盯住,问题往往在恶化之前就能发现。
最后分享一个小技巧:Agent 的缓存 key 里带上一个版本号,比如agent:llm:v2:hash。当你调整了 prompt 模板或者换了模型,只需要改版本号,旧缓存自然失效,不用手动清理。这个做法在快速迭代阶段特别省事。