上下文工程 vs Agent 记忆 vs RAG:联系与优化实战
主题:Context Engineering(上下文工程)、Agent Memory(智能体记忆)、RAG(检索增强生成)三者的概念辨析、关系梳理与工程优化方案,含可直接运行的 Python 代码。
上下文工程(Context Engineering):负责设计和优化进入 LLM 上下文窗口的全部信息—— 系统提示词、任务指令、记忆、检索结果、工具输出、示例、格式约束,决定信息的选取、顺序与 Token 预算,目标是让模型在正确的时间、以正确的格式获得完成任务所需的信息。
RAG(检索增强生成):负责从外部知识库检索与当前问题相关的文档片段并注入上下文,解决模型参数中缺乏的最新知识、长尾知识与知识更新问题;无状态,每次查询独立执行,用完即弃。
Agent 记忆(智能体记忆):负责跨轮次、跨会话保存并召回用户偏好、历史决策、任务状态等内部信息;有状态,支持写入、更新与遗忘。
三者关系一句话:上下文工程是编排层,RAG 与记忆是它管理下的两类信息来源 —— RAG 提供外部知识(无状态、按需检索),记忆提供内部历史(有状态、跨会话持久化),三者共同决定模型每次推理时看到哪些信息、以什么顺序、占多少预算。
一、三个概念的精确定义
1.1 上下文工程(Context Engineering)
定义(Anthropic 提出,Josh Tobin 于 2024 年首次系统阐述):
设计和构建动态系统,这些系统能够在
正确的时间、以正确的格式
提供
正确的信息和工具
,使 LLM 能够合理地完成任务。
上下文工程的核心对象是上下文窗口(Context Window)。它优化的不是 “提示词怎么写”,而是 “模型这次推理时能看到的所有信息” 如何组织。典型组成:
| 组成要素 | 示例 |
|---|---|
| System Prompt(系统指令) | 角色、边界、输出规范 |
| Task / Instruction(当前任务指令) | 本次要完成的目标 |
| Memory(记忆) | 用户偏好、历史决策、未完成任务 |
| RAG 检索结果 | 产品文档、知识库片段 |
| Tool Results(工具输出) | 代码执行结果、API 返回 |
| Few-shot 示例 | 少量样例 |
| 对话历史 | 前序轮次 |
| 格式约束 / 结构化输出 | XML 标签、JSON Schema |
与 Prompt Engineering 的区别:
| 维度 | Prompt Engineering | Context Engineering |
|---|---|---|
| 优化对象 | 提示词文本本身 | 进入上下文窗口的全部信息 |
| 静态 / 动态 | 偏静态、人工编写 | 动态编排、程序化组装 |
| 关注点 | 措辞、角色、指令清晰度 | 信息选择、排序、压缩、预算分配 |
| 手段 | 改写 prompt | 检索 + 记忆 + 工具 + 压缩 + 缓存 |
1.2 RAG(Retrieval-Augmented Generation)
定义:2020 年 Lewis 等人提出的范式 —— 先从外部知识库检索相关文档片段,再连同问题一起交给 LLM 生成答案。流程:
文档库 ──切块 Chunking──▶ 向量化/索引 ──▶ 向量库 ▲ 用户查询 ──▶ 查询改写 ──▶ 检索 Retriever ──┴──▶ 重排 Rerank ──▶ 注入上下文 ──▶ LLM 生成本质:把知识从模型参数中转移到外部可检索的存储,推理时按需检索并注入上下文,解决知识更新慢、幻觉、长尾知识不足的问题。它是上下文工程里最常用的具体技术之一,对应上下文工程中的Select(检索)操作。
1.3 Agent 记忆(Agent Memory)
定义:智能体系统里负责跨轮次、跨会话保存和利用信息的子系统。参考认知科学的分层(参考 MemGPT 与主流 Agent 框架):
| 记忆类型 | 内容 | 生命周期 | 典型实现 |
|---|---|---|---|
| 工作记忆 Working Memory | 当前上下文窗口本身 | 本次任务 | 对话消息列表 |
| 情景记忆 Episodic Memory | 具体发生过的事件、任务过程 | 中期 | 任务记录、轨迹日志 |
| 语义记忆 Semantic Memory | 抽取出的稳定事实、用户画像 | 长期 | 向量库 + 键值存储 |
| 程序性记忆 Procedural Memory | 技能、偏好、做事方式 | 长期 | 偏好配置、技能文件 |
关键点:记忆不是简单的 “聊天记录存起来”,而是一个生命周期系统—— 编码(什么值得记)→ 存储(分层)→ 检索(怎么召回)→ 遗忘 / 压缩(什么时候忘)。
MemGPT(2023)借鉴操作系统虚拟内存的分层设计:上下文窗口作为高速小容量层,外部存储作为大容量持久层,由模型自动完成上下文分页换入 / 换出,从而支持长会话的上下文管理。
二、三者之间的区别(多维度对比)
| 对比维度 | 上下文工程 | RAG | Agent 记忆 |
|---|---|---|---|
| 范畴 | 编排层 / 方法论,覆盖整个上下文窗口 | 一种具体技术(检索注入) | 一个状态子系统(跨会话) |
| 回答的问题 | 这一刻该给模型看什么? | 外部资料里怎么说? | 这个用户 / 任务 / Agent 之前发生了什么? |
| 时间尺度 | 当前这一次调用的窗口构造 | 单次查询的检索 → 注入 | 跨轮次、跨会话、长期 |
| 信息源 | 窗口内的一切(指令 + 检索 + 记忆 + 工具) | 外部知识库(文档、FAQ、代码库) | Agent 自身与用户的交互历史 |
| 主要操作 | 组装、排序、压缩、预算分配 | 索引、检索、重排 | 写入、更新、召回、遗忘 |
| 优化目标 | 窗口内信息整体效用(相关性 × 顺序 ×token 效率) | 检索相关性、上下文忠实度 | 记忆的准确性、时效性、可遗忘性 |
| 失败表现 | 信息拥挤、关键信息丢失(Lost in the Middle) | 检不到、检错、幻觉引用 | 记忆过期、记忆污染、信息冗余 |
| 典型指标 | 任务成功率、输出质量 | Context Precision/Recall、Faithfulness、Answer Relevance | 记忆命中率、时效衰减、冲突率 |
最容易混淆的两点
“RAG 就是给模型加记忆”?不是。RAG 是无状态检索:每次查询独立执行,检索完不留下任何状态;记忆是有状态持久化:信息写入后长期保存,支持更新与遗忘,并跨会话持续演进。
“上下文工程 = 把东西塞进上下文”?不是。上下文工程同样包含Write(写入外部存储)、Compress(压缩)、Isolate(隔离),很多信息刻意不放进上下文,而是按需取用。
三、三者之间的联系(重点章节)
3.0 先建立一个统一视角
三者围绕同一个核心资源运作:LLM 每次推理只能基于有限上下文窗口中的信息。因此三者的分工为:
RAG:推理前从外部知识库检索当前任务所需的文档片段并注入窗口(外部知识来源);
记忆:保存并召回跨轮次 / 跨会话的内部状态 —— 用户偏好、历史决策、未完成任务等 —— 按需注入窗口(内部状态来源);
上下文工程:把系统指令、任务、记忆、RAG 结果、工具输出等信息,按优先级与 Token 预算统一组织进窗口(编排层)。
三者互补且相互制约:缺 RAG,模型缺乏最新外部事实;缺记忆,模型无法延续任务、无法个性化;缺上下文工程,信息杂乱堆积、预算失衡,模型难以有效利用。
3.1 架构关系图
上下文工程(编排层:窗口编排 + Token 预算管理) │ 负责编排以下五类信息(它们共同构成窗口的全部内容): ├─ System Prompt(系统指令,不可压缩) ←── 静态编写 ├─ 记忆注入(Write / Recall) ←── 来自 Agent 记忆子系统 ├─ RAG 检索(Select) ←── 来自 RAG 子系统 ├─ 工具输出(Tools) ←── 来自工具调用层 └─ Few-shot / 格式约束 ←── 静态编写 │ ▼ 预算分配 · 排序(关键信息置顶) · 压缩 · 截断 ┌──────────────────────────┐ │ 有限上下文窗口 │ ──▶ LLM 生成 ──▶ 输出 └──────────────────────────┘ │ 新的事实 / 决策回写(Write 通道) ▼ ┌──────────────────────────────────────────────┐ │ Agent 记忆子系统(内部经验 · 跨会话 · 有状态) │ │ 情景记忆 ──反思提炼──▶ 语义记忆 / 用户画像 │ │ └──────────────▶ 向量库 + 键值库 │ ──按需召回──▶ 记忆注入 └──────────────────────────────────────────────┘ ┌──────────────────────────────────────────────┐ │ RAG 子系统(外部知识 · 按需读取 · 无状态) │ │ 文档库 ──切块/索引──▶ 向量索引 + 倒排索引 │ ──检索注入──▶ RAG 检索 │ 检索 + RRF 融合 + 重排 │ └──────────────────────────────────────────────┘3.2 核心联系(六条,按重要性排序)
联系 1:范畴嵌套 —— RAG 与记忆都是上下文工程的手段,不是平级概念
三者是 “编排层 + 手段” 的关系。RAG 只是上下文工程里的Select(检索注入)手段,记忆是其中的Write / Recall(写入与召回)手段。判断标准:任何 “RAG 做得好的系统”,本质上都是 “上下文工程做得好”;反过来,上下文工程还能在 RAG 之外独立优化(压缩、排序、缓存、隔离)。
联系 2:时间维度互补 —— 无状态检索 × 有状态记忆
RAG 是无状态的:查询 → 检索 → 用完即弃,不改变系统状态;
记忆是有状态的:写入 → 长期保存 → 更新 → 遗忘,跨会话演进;
上下文工程把两者连起来:每次调用时,把 “无状态的检索结果” 与 “有状态的记忆” 一起编排进同一个窗口。
联系 3:信息源互补 —— 外部知识 × 自身经验
RAG 解决 " 模型不知道的外部事实"(产品文档、政策条款、代码库、合同条款);
记忆解决 " 模型不该忘记的用户 / 任务上下文"(偏好、历史决策、未完成任务、上次失败原因);
两者缺一不可:只有 RAG 而无记忆,Agent 缺乏用户与任务上下文,无法个性化回答、无法延续跨轮次任务;只有记忆而无 RAG,Agent 缺乏最新外部事实,容易依据过时或不确定的信息作答。
联系 4:预算耦合 —— 同一份上下文窗口,三者互相挤压
System Prompt、记忆、RAG 结果、工具输出共抢有限的 token:记忆注入多了 → RAG 被截断 → 事实性下降;RAG 塞多了 → 指令被稀释 → 行为跑偏。所以必须联合调优(统一预算表 + 统一排序),单独优化任何一个都只是局部最优。
联系 5:数据闭环 —— 生成结果回写记忆,记忆反哺检索
LLM 基于 “RAG + 记忆” 作答 → 新产生的事实 / 决策回写记忆→ 下次召回更准;记忆里沉淀的会话总结又可以作为第二级检索索引:把记忆当作检索对象,与 RAG 共用同一套检索机制。RAG 与记忆因此互相增强,而不是互相替代。
联系 6:同一套评估框架 —— 都服务于 “窗口质量”
三者共用同一评价口径:检索 / 记忆注入后,上下文越精炼、越相关、越不互相矛盾,输出的 Faithfulness 与任务成功率越高。工程上建议把 “检索结果放进去” 和 “记忆放进去” 统一作为 “上下文组装” 的前置步骤来评测,而不是把三者分开各自打分。
3.3 协同示例:三者如何一起工作(客服 Agent)
| 环节 | RAG 的职责 | 记忆的职责 | 上下文工程的职责 |
|---|---|---|---|
| 用户提问前 | —— | 从记忆库召回该用户画像(会员等级、历史投诉、语言偏好) | 分配记忆预算(如 12%),只注入 Top-3 条 |
| 收到提问时 | 检索最新退换货政策、订单状态 API 结果 | 补充该订单上次处理结论(情景记忆) | 排序:任务指令 > 记忆 > RAG,关键政策置顶 |
| 组装上下文 | 输出 Top-5 带来源标签的片段 | 输出压缩后的要点 | 按预算截断、去重、检查记忆与政策是否冲突 |
| 生成回复 | —— | —— | 把五类信息拼进窗口,控制总 token ≤ 预算 |
| 会话结束 | —— | 把 “该用户倾向退货而非换货” 回写语义记忆 | 对旧会话做摘要压缩,释放窗口 |
一句话:
RAG 提供生成所需的外部事实,记忆提供生成所需的用户 / 任务背景,上下文工程决定这些信息以何种顺序与占比进入窗口
—— 三者缺一,Agent 都做不好事。
四、优化方法论
4.1 上下文工程优化
① 上下文分层 + Token 预算表
把窗口内容按 “可压缩性 / 优先级” 分层,预先分配预算:
| 层 | 优先级 | 建议预算(示例) | 可压缩手段 |
|---|---|---|---|
| 系统指令 | 高(不可丢) | 8%–12% | 精简措辞 |
| 当前任务指令 | 高(不可丢) | 3%–5% | 简明指令 |
| 记忆 | 中 | 10%–15% | 只注入 Top-K 条 |
| RAG 结果 | 中 | 30%–40% | 重排后截断、压缩 |
| 工具输出 | 低 | 10%–15% | 清空旧结果、只留结论 |
| 对话历史 | 低 | 20%–25% | 摘要滚动、滑动窗口 |
② 关键信息置顶 / 置底(对抗 Lost in the Middle)
Liu et al. (2023) 的实验表明:模型对开头和结尾的信息利用最好,中间信息容易被 “遗忘”。做法:把最重要的约束放 System Prompt 末尾或开头,检索结果按相关度排序后,最高相关放最前。
③ 结构化与格式约束
使用 XML 标签 / Markdown 分区明确 “指令 - 数据 - 输出” 三层结构;
检索片段带上来源元数据
[来源:xxx, 时间:xxx],便于模型忠实引用;输出用 JSON Schema / 结构化格式约束。
④ 压缩与清理(Compaction)
历史轮次滚动摘要(summarize-then-truncate);
工具结果清理(tool-result clearing):保留 “发生过该调用” 的记录,删除可重新获取的大段输出;
记忆去重、合并重叠条目。
⑤ 提示缓存(Prompt Caching)
把不变的 system prompt + 高频静态上下文做缓存,降低成本与首字延迟,把预算留给动态信息。
4.2 RAG 优化
① 切块(Chunking)
固定切块:
chunk_size ≈ 300–500 tokens,overlap ≈ 10%–20%(默认值并非生产最优);更优:语义切块(按标题 / 段落 / 代码块边界切,保持语义完整),避免把表格、代码、条款拦腰截断;
小 chunk 利于精确定位,大 chunk 利于上下文完整性,通常配合重排解决。
② 混合检索 + RRF 融合
单独用稠密向量会丢精确关键词(编号、专有名词、公式),单独用 BM25 会丢语义。生产标准做法:
BM25 稀疏检索 Top-50 ─┐ ├─→ RRF 融合 → 重排 → Top-K 注入 向量 稠密检索 Top-50 ──┘RRF(Reciprocal Rank Fusion,k 通常取 60):score(d) = Σ 1 / (k + rank_i(d))
③ 重排(Rerank)
用 Cross-Encoder 对融合后的 Top-50 精排取 Top-5~10,显著提升上下文质量;同时做元数据过滤(时间、来源、权限、类别)。
④ 查询改写
把口语问题改写成检索友好的查询;
复杂问题分解为多个子查询(query decomposition);
HyDE:先让 LLM 生成假设性答案,再用答案向量检索,提高语义召回。
⑤ 时效加权
业务场景下给文档加last_modified,检索打分叠加时间衰减:final = sim * exp(-λ · age)。
4.3 Agent 记忆优化
① 写入策略:什么值得记
不要什么都存。优先级判断:
用户明确表达的偏好与约束(“以后都用中文”)→ 必记;
长期目标 / 未完成任务→ 必记;
可验证的事实(身份、时间、决策结果)→ 记;
一次性的临时状态 → 不记(或设短 TTL)。
写入前做重要度评分(如 0~1),低于阈值不写入;高频场景用异步写入,避免阻塞主流程。
② 分层存储
情景记忆:追加式日志(事件流),量大但轻量;
语义记忆:抽取为 “实体 - 属性 - 关系” 或自然语言画像,进向量库;
定期用 LLM 对情景记忆做reflection(反思 / 提炼),沉淀到语义记忆。
③ 检索与注入
打分 = 相关性 × 重要度 × 时效衰减 三因素加权;
注入前按 token 预算截断(Top-K + 长度限制);
只注入与当前任务相关的记忆,控制注入数量,避免信息过载。
④ 遗忘 / 压缩 / 冲突消解
TTL 过期清理、容量上限 LRU 淘汰;
摘要压缩:旧对话 → 一句话结论;
冲突消解:当检索到的旧记忆与用户最新指令冲突时,以最新指令 / 最新检索事实为准,并标记旧记忆 “已过时”。
⑤ 记忆与 RAG 协同
把每次会话的总结写入 “会话总结库”,下次同类任务先检索总结(相当于对自身历史的 RAG);
RAG 结果中的重要事实也回写记忆,形成两级缓存。
五、代码实战(可直接运行)
依赖:
numpy
、sentence-transformers
(或任意 embedding 服务)、rank_bm25
。为便于演示,部分实现用简化算法代替外部库,注释中给出生产替代。
5.1 上下文 Token 预算分配器
# context_budget.py from dataclasses import dataclass @dataclass class ContextBudget: system: int # 系统指令 task: int # 当前任务指令 memory: int # 记忆注入 rag: int # 检索结果 tools: int # 工具输出 history: int # 对话历史 BUDGET_RATIO = { "system": 0.10, "task": 0.05, "memory": 0.12, "rag": 0.35, "tools": 0.12, "history": 0.21, } def allocate_budget(context_window: int, reserve_headroom: float = 0.05) -> ContextBudget: """按比例分配上下文预算,预留 headroom 给不可控增量。""" usable = int(context_window * (1 - reserve_headroom)) return ContextBudget( **{k: int(usable * v) for k, v in BUDGET_RATIO.items()} ) if __name__ == "__main__": budget = allocate_budget(128_000) print(budget) # ContextBudget(system=12160, task=6080, memory=14592, rag=42560, tools=14592, history=25536)生产替代:按 tokenizer 精确计数(如tiktoken/transformers的AutoTokenizer),而不是字符近似。
5.2 混合检索(BM25 + 向量)+ RRF 融合
# hybrid_retriever.py import math import numpy as np from collections import Counter def bm25_score(query_tokens, doc_tokens, df, N, avgdl, k1=1.5, b=0.75): """BM25 单文档打分(简化实现,生产用 rank_bm25 库)。""" dl = len(doc_tokens) score = 0.0 tf = Counter(doc_tokens) for qt in set(query_tokens): if qt not in df or qt not in tf: continue f = tf[qt] idf = math.log(1 + (N - df[qt] + 0.5) / (df[qt] + 0.5)) score += idf * (f * (k1 + 1)) / (f + k1 * (1 - b + b * dl / avgdl)) return score def rrf_fuse(rankings, k=60): """Reciprocal Rank Fusion:融合多路检索排名。""" fused = {} for ranking in rankings: # ranking: [doc_id, ...] 按相关度降序 for rank, doc_id in enumerate(ranking, start=1): fused[doc_id] = fused.get(doc_id, 0.0) + 1.0 / (k + rank) return dict(sorted(fused.items(), key=lambda x: x[1], reverse=True)) def hybrid_search(query, docs, embed_fn, top_k=10): """ docs: list[dict] -> {"id": str, "tokens": list[str], "vec": np.ndarray} 流程:BM25 取 top50 -> 向量余弦取 top50 -> RRF 融合 -> 返回 top_k """ q_vec = embed_fn(query) q_tokens = query.lower().split() N = len(docs) df = Counter() total_len = 0 for doc in docs: for t in set(doc["tokens"]): df[t] += 1 total_len += len(doc["tokens"]) avgdl = total_len / max(N, 1) bm25_scores = { doc["id"]: bm25_score(q_tokens, doc["tokens"], df, N, avgdl) for doc in docs } dense_scores = { doc["id"]: float(np.dot(q_vec, doc["vec"]) / (np.linalg.norm(q_vec) * np.linalg.norm(doc["vec"]) + 1e-9)) for doc in docs } bm25_rank = [i for i, _ in sorted(bm25_scores.items(), key=lambda x: x[1], reverse=True)][:50] dense_rank = [i for i, _ in sorted(dense_scores.items(), key=lambda x: x[1], reverse=True)][:50] fused = rrf_fuse([bm25_rank, dense_rank]) return [doc_id for doc_id in fused][:top_k]生产替代:rank_bm25+FAISS/Milvus/pgvector+sentence-transformers,并叠加Cross-Encoder 重排:
# 重排(生产建议直接用 cross-encoder 模型) from sentence_transformers import CrossEncoder reranker = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2") def rerank(query, candidates, top_k=5): scores = reranker.predict([(query, c) for c in candidates]) top_idx = np.argsort(scores)[::-1][:top_k] return [candidates[i] for i in top_idx]5.3 分层记忆管理器(重要度 + TTL + 摘要压缩)
# memory_manager.py import json, time, heapq from dataclasses import dataclass, asdict @dataclass class MemoryItem: id: str content: str mem_type: str # episodic / semantic / procedural importance: float # 0~1,写入时评分 created_at: float ttl: float | None # 秒;None 表示长期 vec: list | None = None class MemoryManager: def __init__(self, storage_path="memory.jsonl", embed_fn=None): self.storage_path = storage_path self.embed_fn = embed_fn # 向量化函数,None 时退化为文本匹配 self.items: dict[str, MemoryItem] = {} self._load() # ---------- 写入 ---------- def write(self, content: str, mem_type="semantic", importance: float = 0.5, ttl: float | None = None) -> MemoryItem: """写入前的重要度门槛:低于阈值不存(减少记忆污染)。""" if importance < 0.3: return None item = MemoryItem( id=f"{int(time.time()*1000)}-{len(self.items)}", content=content, mem_type=mem_type, importance=importance, created_at=time.time(), ttl=ttl, vec=list(self.embed_fn([content])[0]) if self.embed_fn else None, ) self.items[item.id] = item self._save() return item # ---------- 检索(相关性 × 重要度 × 时效)---------- def recall(self, query: str, top_k: int = 5) -> list[MemoryItem]: now = time.time() q_vec = list(self.embed_fn([query])[0]) if self.embed_fn else None scored = [] for item in self.items.values(): if item.ttl and now - item.created_at > item.ttl: continue # 过期记忆不召回 rel = self._similarity(q_vec, item.vec) if q_vec else self._text_score(query, item.content) recency = max(0.0, 1.0 - (now - item.created_at) / (30 * 24 * 3600)) score = 0.6 * rel + 0.3 * item.importance + 0.1 * recency scored.append((score, item)) scored.sort(key=lambda x: x[0], reverse=True) return [item for _, item in scored[:top_k]] @staticmethod def _similarity(a, b): import numpy as np return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b) + 1e-9)) @staticmethod def _text_score(query, content): return len(set(query.split()) & set(content.split())) / max(len(set(query.split())), 1) # ---------- 遗忘与压缩 ---------- def compact(self, summarizer, max_items: int = 1000): """情景记忆超限时:旧事件 -> LLM 提炼一句结论(写入语义记忆后删除)。""" old = [i for i in self.items.values() if i.mem_type == "episodic" and i.created_at < time.time() - 7 * 24 * 3600] if len(self.items) > max_items: for item in sorted(old, key=lambda x: x.created_at)[: len(old) // 2]: summary = summarizer(item.content) # 例如: f"要点: {item.content[:200]}" self.write(f"[历史总结] {summary}", "semantic", importance=0.4) del self.items[item.id] self._save() # ---------- 持久化 ---------- def _save(self): with open(self.storage_path, "w", encoding="utf-8") as f: for item in self.items.values(): f.write(json.dumps(asdict(item), ensure_ascii=False) + "\n") def _load(self): try: with open(self.storage_path, encoding="utf-8") as f: for line in f: d = json.loads(line) self.items[d["id"]] = MemoryItem(**d) except FileNotFoundError: pass # 使用示例 # mem = MemoryManager(embed_fn=embed) # embed 来自 sentence-transformers # mem.write("用户偏好:回复始终使用简体中文", "semantic", importance=0.9, ttl=None) # hits = mem.recall("用户的语言偏好是什么?", top_k=3)5.4 上下文组装器(优先级排序 + 预算截断)
# context_assembler.py def count_tokens(texts) -> int: """生产环境请用 tiktoken / AutoTokenizer 精确计数。""" return sum(len(t.split()) for t in texts) def assemble_context(system_prompt, task, memory_items, rag_chunks, history, budget, key_rule="head"): """ 组装规则: 1) 顺序:system -> task -> memory -> rag -> history 2) 每层不超过预算 3) 最高优先级的 RAG 片段放最前(对抗 Lost in the Middle) 4) 预留部分预算给不可控增量 """ def fit(texts, cap): out, used = [], 0 for t in texts: cost = count_tokens([t]) if used + cost > cap: break out.append(t); used += cost return out system_block = fit([system_prompt], budget.system) task_block = fit([task], budget.task) # RAG 片段已按相关度降序,直接按预算截断 rag_block = fit(rag_chunks, budget.rag) # 记忆只取 Top-K,且压缩为 "要点" 形式 mem_block = fit([f"- {m.content}" for m in memory_items[:8]], budget.memory) hist_block = fit(history[-20:], budget.history) # 只取最近 20 轮 context = { "system": system_block, "task": task_block, "memory": mem_block, "rag": rag_block, "history": hist_block, "tools": [], # 由工具调用层维护 } # 最终注入顺序(关键信息在两端) final_prompt = "\n\n".join([ *system_block, *task_block, *mem_block, *rag_block, # 注意:最重要片段已排在最前 *hist_block, ]) return context, final_prompt5.5 简易评估脚本(Faithfulness / Context Precision)
# evaluate.py def faithfulness(claims: list[str], evidence: list[str]) -> float: """ 忠实度:答案中的每条论断能否被检索证据支持。 生产用 LLM-as-judge 逐条判定,这里用"词项覆盖 + 否定句惩罚"近似。 """ if not claims: return 0.0 supported = 0 for c in claims: ev = " ".join(evidence) overlap = len(set(c.split()) & set(ev.split())) / max(len(set(c.split())), 1) if overlap >= 0.5 and "不" not in c[:3]: # 近似判定 supported += 1 return supported / len(claims) def context_precision(relevant_positions: list[int], total_retrieved: int) -> float: """ 上下文精确率:相关片段在检索结果中排得越靠前越好。 relevant_positions: 相关片段在注入列表中的下标(0-based)。 """ if not relevant_positions: return 0.0 return sum(1.0 / (pos + 1) for pos in relevant_positions) / len(relevant_positions) # 示例 # claims = ["RAG 是无状态的", "记忆是跨会话的"] # evidence = ["RAG 每次查询重新检索外部知识库,检索完不留状态", # "Agent 记忆跨会话保存用户偏好和历史决策"] # print("Faithfulness:", faithfulness(claims, evidence)) # 0.5~1.0 # print("Context Precision:", context_precision([0, 2], 5)) # 0.5六、落地检查清单(自检用)
上下文工程
是否对上下文做了分层与 Token 预算,而不是 “全部塞满”?
关键约束是否放在窗口开头 / 结尾(避开中间遗忘区)?
工具输出是否做了清理 / 只留结论?
静态内容是否命中了 Prompt Cache?
RAG
切块是否按语义边界而非纯固定长度?
是否使用 BM25 + 向量混合检索 + RRF + 重排?
是否带元数据过滤与时效加权?
检索结果注入前是否截断到预算内?
记忆
写入是否有重要度门槛(不是什么都存)?
是否分层(episodic /semantic/procedural)?
召回打分是否 = 相关性 × 重要度 × 时效?
是否有 TTL 过期、摘要压缩、冲突消解?
记忆与 RAG 结果冲突时,是否以最新事实 / 最新指令为准?
评估
是否用 Faithfulness / Context Precision / Answer Relevance 定期回归?
是否有基线(Baseline)与 A/B 对比?
七、参考资料
Anthropic - Effective context engineering for AI agents:https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents
Anthropic Cookbook - Context engineering: memory, compaction, and tool clearing:https://platform.claude.com/cookbook/tool-use-context-engineering-context-engineering-tools
AI Wiki - Context engineering:https://aiwiki.ai/wiki/context_engineering
Mem0 - Context Engineering AI: How To Build Smarter LLM Agents:https://mem0.ai/blog/context-engineering-ai-agents-guide
Tierzero - Context Engineering Is What Comes After Prompt Engineering:https://www.tierzero.ai/blog/context-engineering-production-agents/
掘金 - Context Engineering 不是写更长 Prompt,而是管理 Agent 的注意力预算:https://juejin.cn/post/7640643117474201641
成都理工大学 - 大模型上下文工程 (Context Engineering) 指南:https://www.cdut.edu.cn/__local/4/A7/81/EA807C0CAAAF77FDAC03EC61C0B_FF6614F6_68E2A6.pdf
参考论文:Lewis et al. 2020《Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks》;Liu et al. 2023《Lost in the Middle》;Packer et al. 2023《MemGPT: Towards LLMs as Operating Systems》