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)
任务涉及很多源文件和规范时,搜索结果可能占用主会话的大部分上下文。是否需要拆分,应看信息相关性和任务边界,而非固定的文件数量。
需要留意的表现
- 回答遗漏先前约束。
- 重复调用工具,却没有新的证据或进展。
- 声称文件已修改,但实际 Diff 中没有对应变化。
将独立任务交给子代理
┌───────────────────────┐
│ Parent Agent (主代理) │
│ - 掌控全局任务计划 │
│ - 维持最终验收断言 │
└───────────┬───────────┘
│
┌──────────────────┼──────────────────┐
│ 派发临时子任务 │ 派发临时子任务 │ 派发临时子任务
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Subagent A │ │ Subagent B │ │ Subagent C │
│ 代码全局检索│ │ 单元测试构建│ │ 文档规范查阅│
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
│ 回传结构化结论 │ 回传结构化结论 │ 回传结构化结论
▼ ▼ ▼
[返回摘要与来源] [返回摘要与来源] [返回摘要与来源]
- 独立上下文:子代理承担搜索或文档阅读,主会话不必接收全部中间输出。子会话是否保留、能否恢复,由具体运行时决定。
- 摘要与来源:返回符号位置、结论和未确认项,主代理必要时回读原文。摘要会丢失细节,多个子代理也会增加调用成本;共享文件的编辑还需协调。
6. 实现时的检查清单
下面这些问题可以帮助检查上下文是否服务于当前任务:
- 稳定前缀:系统提示词与工具声明能否保持一致,缓存是否实际命中?
- 工具输出:摘要是否保留关键错误和来源,完整日志是否可回查?
- 当前状态:目标、已完成步骤和剩余预算是否明确?
- 任务拆分:独立搜索是否适合交给子代理,返回结论是否可核对?
- 验收机制:是否有覆盖任务要求的测试或其他外部检查?