上下文工程笔记:前缀缓存、长上下文与记忆分层

多轮 Agent 如何组织输入、保留状态和控制上下文开销

Posted by Yvain Zhang on August 18, 2026 主题:技术

1. 从提示词到整轮任务的上下文

给模型写清楚目标、约束和示例,是提示词工程的一部分。任务进入多轮执行后,还会增加工具输出、修改记录、环境状态和历史决策。此时需要考虑的不只是某一句提示词,而是每次请求应该带上哪些信息。

上下文工程(Context Engineering)关注这些信息的选择、排序、压缩和更新。它包含提示词设计,也涉及检索、工具接口和运行时状态管理。本文按这几个问题整理一种实现思路。

维度 提示词设计 多轮上下文管理
组织对象 指令、约束、Few-shot 示例 当前状态、历史消息、检索结果、工具输出
常见调整 澄清歧义,补充必要示例 分配 Token 预算,压缩日志,保留相关证据
验证方式 检查回答是否满足要求 同时检查任务完成情况、时延与成本
相互关系 是模型输入的一部分 决定提示词和其他信息如何一起进入请求

2. 物理成本:Attention 机制与 KV Cache 的底层制约

上下文长度会影响计算量和缓存占用,需要先区分预填充(Prefill)与逐 Token 解码(Decode)。

2.1 计算复杂度与内存墙

对于标准稠密自注意力,预填充阶段需要计算 Token 之间的相关性: \(\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V\) 其中注意力部分的计算量随序列长度近似平方增长,KV Cache 占用通常随长度线性增长。这不等于总推理时延必然按平方增长:模型结构、注意力实现、批处理和缓存命中都会影响结果。Agent 的工具日志若持续累积,会增加请求开销,也可能挤占并发请求可用的缓存空间。

2.2 前缀缓存如何复用计算

支持 Prefix Caching(前缀缓存)的服务可以复用相同输入前缀的 KV Cache,减少这部分的预填充计算。以 vLLM 为例,缓存不会加速新 Token 的解码阶段。具体命中规则和生命周期应查看所用服务的文档。见 vLLM 前缀缓存说明。

 ┌─────────────────────────────────────────────────────────────┐
 │  Stable Shared Prefix (Cache Hit: Reuse Prefill Results)   │
 ├─────────────────────────────────────────────────────────────┤
 │ [System Prompt] ── [Tool Declarations] ── [Core Constraints]│
 └─────────────────────────────────────────────────────────────┘
                                │
                                ▼
 ┌─────────────────────────────────────────────────────────────┐
 │  Dynamic Volatile Suffix (Sliding Window & Recent Steps)    │
 ├─────────────────────────────────────────────────────────────┤
 │ [Working Scratchpad] ── [Step History] ── [Tail Assertion]  │
 └─────────────────────────────────────────────────────────────┘

一种便于缓存复用的组织方式

  • 将稳定的系统提示词、代码规范和工具声明放在前面,并保持序列化顺序一致。
  • 将时间戳、当前步骤和会话状态放在后面。前缀变化会缩短可复用的部分,但不一定使变化位置之前的缓存失效。
  • 用实际请求测量命中率、首字延迟(TTFT)和成本。缓存粒度、最小长度、有效期和计费方式因服务而异,不宜套用一个固定折扣。

3. 长上下文中的信息位置

Lost in the Middle 在多文档问答和键值检索实验中观察到:相关信息的位置会影响回答表现,放在开头或结尾时往往优于放在中间。这是特定模型和任务上的表现曲线,不能直接解释为所有 Transformer 的注意力权重都固定呈 U 型。

下面只示意这种位置效应,不表示实测数值:

 Retrieval Performance (schematic)
   ▲
1.0│ *                                                  *
   │  *                                                *
0.5│   *                                              *
   │    *                                            *
   │     * * * * * * * * * * * * * * * * * * * * * *
0.0└───────────────────────────────────────────────────────►
   Start (Prefix)           Middle (Lost)          End (Tail)

可尝试的办法:在尾部重述当前状态

可以在请求末尾增加简短的当前目标与预算提示,例如:

[CURRENT CRITICAL ANCHOR]
- Ultimate Target: Complete Wi-Fi driver packet trace analysis.
- Step Budget: 18 / 25 consumed.
- Rule: Before claiming SUCCESS, you MUST run `./tests/verify.sh` and verify exit code 0.

这样便于模型看到本轮状态,但是否改善任务表现仍需验证。权限和验收规则还应由运行时执行,不能只靠重复提示。


4. 三层记忆体系分级架构 (Layered Memory Hierarchy)

可以借用 L1 Cache / L2 Cache / 主存的分层概念,将信息分为当前状态、近期历史和跨会话记录。这里的 L1/L2/L3 是便于讨论的命名,并非 Agent 必须采用的标准架构:

 ┌──────────────────────────────────────────────────────────────┐
 │ L1: Working Scratchpad (工作记忆)                            │
 │ - 当前执行步骤的状态机寄存器、待办列表、局部环境变量           │
 │ - 放在动态上下文区域,随步骤更新,完成后移出活跃状态        │
 └──────────────────────────────────────────────────────────────┘
                                ▲
                                │ 状态提炼与压缩
                                ▼
 ┌──────────────────────────────────────────────────────────────┐
 │ L2: Ephemeral Sliding Buffer (短暂会话缓冲)                  │
 │ - 最近 3~5 轮交互历史与直接工具返回结果                       │
 │ - 超出步数阈值执行结构化有损压缩(只保留 Exit Code 与错误行)   │
 └──────────────────────────────────────────────────────────────┘
                                ▲
                                │ 经验沉淀与向量召回
                                ▼
 ┌──────────────────────────────────────────────────────────────┐
 │ L3: Semantic & Episodic Memory (长期语义与情景记忆)          │
 │ - 本地向量数据库 (Chromadb/Qdrant) + 知识图谱               │
 │ - 跨会话沉淀历史排错反思、代码模式与领域规范                 │
 └──────────────────────────────────────────────────────────────┘

4.1 L1 工作记忆 (Working Scratchpad)

  • 记录当前目标、子步骤列表(Checklist)、当前假设和已验证状态。
  • 尽量保持简短,容量根据任务设定;不要为了固定 Token 上限删掉必要约束。

4.2 L2 短暂滑动缓冲 (Ephemeral Buffer)

  • 保存最近的执行动作与工具输出。
  • 输出清洗与压缩:例如 make 产生 5000 行输出时,可以先保留构建概况、退出码和关键错误行,完整日志另存。简单截取前 20 行与后 50 行适合作为起点,但可能漏掉中间的首个错误,需要按日志格式调整。

4.3 L3 长期情景记忆 (Episodic Memory)

  • 保存经过核对的排查结论、修复 Diff、适用版本和来源,可通过向量 embedding 或关键词索引检索。
  • 遇到相似的内核崩溃(如 Kernel panic - not syncing: Fatal exception in interrupt)时,将历史记录作为参考召回;相似报错不代表同一原因,还要重新验证。

5. 分层子代理 (Hierarchical Subagent DAG)

任务涉及很多源文件和规范时,搜索结果可能占用主会话的大部分上下文。是否需要拆分,应看信息相关性和任务边界,而非固定的文件数量。

需要留意的表现

  1. 回答遗漏先前约束。
  2. 重复调用工具,却没有新的证据或进展。
  3. 声称文件已修改,但实际 Diff 中没有对应变化。

将独立任务交给子代理

                   ┌───────────────────────┐
                   │  Parent Agent (主代理) │
                   │  - 掌控全局任务计划    │
                   │  - 维持最终验收断言    │
                   └───────────┬───────────┘
                               │
            ┌──────────────────┼──────────────────┐
            │ 派发临时子任务    │ 派发临时子任务    │ 派发临时子任务
            ▼                  ▼                  ▼
     ┌─────────────┐    ┌─────────────┐    ┌─────────────┐
     │ Subagent A  │    │ Subagent B  │    │ Subagent C  │
     │ 代码全局检索│    │ 单元测试构建│    │ 文档规范查阅│
     └──────┬──────┘    └──────┬──────┘    └──────┬──────┘
            │                  │                  │
            │ 回传结构化结论   │ 回传结构化结论   │ 回传结构化结论
            ▼                  ▼                  ▼
     [返回摘要与来源]   [返回摘要与来源]   [返回摘要与来源]
  • 独立上下文:子代理承担搜索或文档阅读,主会话不必接收全部中间输出。子会话是否保留、能否恢复,由具体运行时决定。
  • 摘要与来源:返回符号位置、结论和未确认项,主代理必要时回读原文。摘要会丢失细节,多个子代理也会增加调用成本;共享文件的编辑还需协调。

6. 实现时的检查清单

下面这些问题可以帮助检查上下文是否服务于当前任务:

  • 稳定前缀:系统提示词与工具声明能否保持一致,缓存是否实际命中?
  • 工具输出:摘要是否保留关键错误和来源,完整日志是否可回查?
  • 当前状态:目标、已完成步骤和剩余预算是否明确?
  • 任务拆分:独立搜索是否适合交给子代理,返回结论是否可核对?
  • 验收机制:是否有覆盖任务要求的测试或其他外部检查?