《AI Agent 工程实战》6.2 语义缓存:相似问题复用与错配检测
6.1 的字面缓存只认逐字节相同的 key,可用户换个说法问同一个问题它就失灵。本节实现语义缓存:把问题向量化后按相似度阈值命中历史答案,用卷一 9.1 的手写哈希嵌入真跑三种情况——同义改写命中、无关问题不命中、相似但答案相反时误命中,贴出真实相似度数值,并重点讲错配检测与哪些问题绝不能走语义缓存。
tag
6.1 的字面缓存只认逐字节相同的 key,可用户换个说法问同一个问题它就失灵。本节实现语义缓存:把问题向量化后按相似度阈值命中历史答案,用卷一 9.1 的手写哈希嵌入真跑三种情况——同义改写命中、无关问题不命中、相似但答案相反时误命中,贴出真实相似度数值,并重点讲错配检测与哪些问题绝不能走语义缓存。
本节给 ResearchCopilot 的检索结果与嵌入向量加一层 Redis 缓存:缓存 key 带上 workspace_id 以对齐 4.2 的租户隔离,真连本机 Redis 跑出「首次未命中回源、二次命中」的耗时对比与 70% 命中率,并给出检索、嵌入、会话三类 TTL 策略表,以及缓存穿透、击穿、雪崩在 Agent 场景下的具体形态与对策。
本节给 minagent 加上结构化输出:先讲 JSON mode 与 schema 约束的接口设计(只讲设计不真调),再把重点放在解析兜底上——实现 parse_json 稳健处理代码围栏、前置寒暄、嵌套括号等脏输出,用一组样例做单元测试并真跑,最后演示解析失败时把错误回灌给模型重出。
本节给 minagent 加上主动压缩:区别于 2.3 的被动截断,这里把更早的轮次交给模型压成一段摘要再拼接,实现 compact(messages, budget) 与可插拔的摘要器,真跑对比压缩前后的消息条数与近似 token,并给出「何时摘要、何时直接丢」的取舍表。
把角色、任务和本轮约束分开,可以让调用方看清每段文字由谁提供、何时变化。应用中的分层约定不等于供应商角色协议或安全策略。优先级提示只帮助组织文字,模型仍可能忽略;不能依靠一句安全红线授权工具。工具白名单、参数校验和人工确认应在执行代码里完成;缓存也由供应商具体条件决定。
会话内拉最近 N 条消息是 ResearchCopilot 最热的查询。本节造 5 万条真实规模数据,用 EXPLAIN (ANALYZE, BUFFERS) 真跑,看它从 Seq Scan(8.8 ms、扫描 718 个页)变成 Index Scan(0.07 ms、只读 4 个页),并给出该加与不该加的索引清单。
run 是一台状态机:queued → running → succeeded/failed/cancelled。本节用 UPDATE ... WHERE status='queued' RETURNING 的返回行数判断谁抢到执行权,真跑两个并发 worker 抢同一个 run,并演示「先 SELECT 再 UPDATE」为什么会把同一个任务跑两遍。
ResearchCopilot 的数据层从四张表开始:sessions、messages、runs、memories。本节提供四表 DDL、复合会话归属外键及锁内初始化版本表的迁移器;2026-10-20 在 PostgreSQL 18.4 隔离集群完成九项机制检查并复跑原四条消息示例,原稿 17.11 输出保留为历史示例。
ReAct 与 Plan-and-Solve 都是「一次跑到底」,失败了不会回头。本节给循环外面再套一层 Reflexion:执行后由 evaluator 判定成败,失败就生成一段反思文本写回上下文,带着反思重跑,最多 N 轮。我们用 FakeModel 脚本化「第一次算错 → 反思 → 第二次算对」并真跑打印两轮轨迹,最后给何时值得 Reflexion 的决策清单。
ReAct 边想边做,Plan-and-Solve 则先谋后动。本节把循环拆成 Planner 与 Executor 两段:Planner 用结构化输出(JSON 数组)一次性产出子任务列表,Executor 顺序执行每一步且每步内部仍是一个完整小循环。我们用 FakeModel 演示一个三步计划,打印每步状态,并给出与 ReAct 的取舍表。
本节把已有工具循环的可见文本、动作和结果分成 thought/action/observation。它不提取隐藏推理,也不要求供应商输出完整思维过程。天气数据为固定教学值,FakeModel 决定两次查询和最终回答。增加 thought 日志不自动提高模型准确率;是否改善任务效果需要独立评测。
本节给 minagent 的 ToolRegistry 加一层权限包装:工具按只读、写入、危险分三档,危险工具调用前挂起等人工审批。我们用假模型驱动一条完整轨迹——模型请求危险工具、被挂起、审批通过、继续执行,把每一步贴出来,并给出权限档位表与审批超时策略。
本节把 workspace_id 贯穿所有资源,做行级租户隔离:所有查询强制带租户条件,同一个 run id 在别的 workspace 下取不到,且统一返回 404 而非 403,避免泄露资源是否存在。我们用真连的本机 Postgres 跑一遍「A 租户访问 B 租户资源被拒」,并列出隔离失效的三个高频坑。
本节给 ResearchCopilot 装上第一道门:用 FastAPI 依赖注入实现认证,同时支持 Bearer JWT 与 API Key 两种凭证。我们用 PyJWT 2.15.1 真跑签发与校验,演示合法 token 通过、过期 token 401、篡改签名 401 三种情况,并把认证失败统一到 3.3 的错误契约。
模型一轮可以同时发出多个 tool_call。本节先看串行执行的浪费,再用 concurrent.futures.ThreadPoolExecutor 把多个工具并发跑起来,对比串行与并行的实测耗时,并给出 asyncio.gather 的等价写法,最后讲清结果如何按 tool_call_id 对齐合并回消息。
用标准库保留贯穿项目接口,验证缺字段、类型错误、额外参数和序列化失败,并通过离线事件实验区分等待超时与终止执行。
本节把工具层拆成 Tool 与 ToolRegistry 两个小对象:先讲 JSON Schema 的两种生成方式(手写与从类型注解推导),再用极简版 get_type_hints 自动产出 schema,最后实现按名分发、未知工具返回 error 而不抛异常。全部代码在 Python 3.14 实测跑通。
单进程 ResearchCopilot 的幂等、游标和错误契约:会话内键作用域、并发重放、失效游标拒绝、四字段错误体,保留现有 SSE 接口及 202 创建语义。
接入卷一实际 stream_run 入口,交付完整 SSE 回放、失败事件与游标验收,明确进程内测试、真实网络和原生模型流的区别。
本节为 ResearchCopilot 设计会话与任务 API:把 session、run、message 三类资源钉成 REST 语义,讲清为什么 run 必须独立成资源而不是 session 的一个动作,给出资源关系图与状态字段,并用 FastAPI 0.143.0 真跑一组路由,贴出 TestClient 打出的真实 JSON 响应与 OpenAPI 契约。