语义缓存:一次推理服务一千次提问
这一章讲三件事: 为什么「缓存」在 agent 时代突然变得如此划算(长尾分布); 一个语义缓存的完整构造——从基础版到四层增强;以及让它长期不腐化的填充与驱逐策略。 原书给它的定位:能把延迟和推理成本降 10–100 倍,同时保持准确1。
1. 为什么现在需要它:长尾分布
看你应用的查询流量分布——大概率是「长尾」:少数查询占了大头流量(原书观察:单个热门查询 可以独占 20% 以上的全部流量),而 60–70% 的查询只被问过几次2。
这决定了优化的正确姿势:抓住「头部」——原书的算术是 top 20% 的查询类型覆盖约 80% 的流量; 他顺手讽刺了一句:头部 agent 公司 广告里吹的「成功处理 68% 的查询」,也不过如此3。
那让 agent 自己处理一切行不行?原书算了一笔硬账:agent 每次都要为新查询「推理」 (规划用什么工具、怎么检索),这意味着多次 LLM 调用;单次调用就要 2–20+ 秒, 消费级应用等不起。可以穷尽各种手段压单次成本,但规模化之下「你总会无解」—— 除非上语义缓存(他还开了个玩笑:除非你有十亿美元的量子计算机,那大概是本书出版 5-10 年后的事)4。
2. 语义缓存是什么:拦截规划,复用路径
普通缓存存「答案」;语义缓存存「solution path(解决路径)」——agent 为某类问题规划出的 工具调用方案。它蹲在查询入口,干一件事:拿新查询做向量搜索,如果命中缓存里某个查询 (相似度过阈值),直接复用它的解决路径,把 agent 的规划步骤整个跳过5。
重要澄清(原书专门强调):语义缓存通常不消灭推理,只替代规划段。 回答仍要生成,但「该走哪条路」这个最贵的思考被复用了。特例也存在:FAQ 类静态内容 可以把现成答案整个存进缓存,零推理返回;甚至可以缓存调优过的 SQL,连检索都提速6。
效果数字(原书引用 AWS、OpenAI、Intercom 的报告): 语义缓存加持的完整 agent, 响应 600 毫秒–2 秒;不加持的同类 agent 要 5–6 秒7。
一笔更直观的账:设一次推理念 1 美元,某热门查询每天来 1000 次—— 无缓存:每天 1000 美元;有缓存:第一次 1 美元,之后全免。而且省的不止这一个查询: 所有与它相似度过阈值的变化说法都被覆盖——一次推理,服务一片查询8。 还有两条容易被忽略的收益:一致性(LLM 非确定,同一问题两次回答可能不同; 金融场景里「换个说法答案就变」是事故,缓存钉死同一解决路径)和可控性 (发现某个路径能答得更好,人工替换缓存里的方案即可,不动其他任何东西)9。
3. 主走查:从基础版到四层增强
原书的代码实验室用 all-MiniLM-L6-v2(本地小模型)+ ChromaDB 搭了个语义缓存, 然后一步步打补丁。每一步都在治一种真实的病:
基础版(步骤 3)
SemanticCache 类:add(query, soln_path) 把「查询→解决路径」存入;
search(query, threshold=0.75) 拿查询嵌入做相似搜索,分数 ≥ 0.75 才返回缓存的路径10。
增强 1:实体遮蔽,治「缓存碎片化」
「AAPL 2023 年股价?」和「TSLA 2024 年股价?」应该走同一个工具——区别只是传参。
但字面不同就各自嵌入,缓存被无意义的变体撑爆。实体遮蔽(entity masking) 在嵌入前
把变量替换成占位符:AAPL/TSLA → [TICKER],2023/2024 → [YEAR]——
两条查询坍缩成「What was [TICKER] stock price in [YEAR]?」,一条缓存通吃11。
增强 2:交叉式的编码器(cross-encoder),治「假相似」
召回越好,误伤越多:「What's my balance?」和「What's my account number?」 在向量空间里很近,但意思完全不同——余额和账号答错了都是事故12。
普通嵌入把两条查询各自独立编码;交叉编码器(cross-encoder)则, 交叉编码器把「候选查询+缓存查询」放在一起重读、按「是不是同一意图」打分。 流水线变成:向量粗筛(快,宽)→ 交叉编码器精验(慢,严),各取所长13。
增强 3:自适应阈值,治「一刀切」
金融交易类查询,匹配阈值该调高——宁可放过,不可答错;探索性闲聊,阈值放低, 多命中几个无妨。阈值按查询类型动态调整,而不是全局一个数14。