第 6 章 · 记忆与上下文

04 · 上下文工程

不是所有信息都值得给模型看。选择、压缩、排序、隔离——用好有限的上下文窗口。

上下文窗口是稀缺资源

每次调用模型 API,都需要把完整的消息历史发过去。模型能处理的内容量是有限的(即"上下文窗口"),而且按 token 计费——发得越多,花得越多。

所以不能把所有信息都塞进系统提示词。要像编辑一篇文章一样,精心选择哪些信息值得呈现,哪些应该省略

这就是"上下文工程"(Context Engineering)的核心思路。

四个操作

上下文工程可以分解为四个操作:

选择(Selection)

哪些信息应该给模型?

以会话记忆为例,agent 跟踪了三类信息:已读文件、失败命令、最近搜索。为什么是这三类?

  • 已读文件——高价值。模型知道"读过什么"就能避免重复读取,也能回忆之前看到的内容。
  • 失败命令——高价值。避免模型重复执行已经失败的命令,这是防止"无限重试"的关键信息。
  • 最近搜索——中等价值。帮助模型保持搜索方向的连续性。

没有跟踪的信息:

  • 成功的命令——低价值。npm test 成功了不需要特别记住,对话历史中已经有了。
  • write_file / patch_file 的结果——低价值。文件内容已经变了,下次读取会拿到新内容。
  • git_status / git_diff 的结果——低价值。这些是快照信息,很快会过时。

压缩(Compression)

怎么减少无效 token?

ContextManager 在记录信息时就做了压缩:

/** 文件内容只保留前 3 行 */
recordFileRead(path: string, content: string): void {
  const lines = content.split("\n").slice(0, 3);
  this.readFiles.set(path, lines.join("\n"));
}

/** 搜索结果只保留第一行,最多 200 字符 */
recordSearch(query: string, result: string): void {
  const summary = result.split("\n")[0] ?? "";
  this.recentSearches.push({ query, summary: summary.slice(0, 200) });
}

为什么前 3 行就够了?因为前 3 行通常是 import 语句或文件头——足够让模型知道"这是一个 TypeScript 文件,导入了什么模块"。如果模型需要看完整内容,它会再次调用 read_file

搜索结果只保留第一行也够——"找到 5 个文件"或"未找到结果"已经传达了关键信息。

排序(Prioritization)

哪些内容要优先呈现?

在系统提示词中,信息的排列顺序是:

  1. 角色定位(最重要——模型首先理解"我是谁")
  2. 工具列表(重要——模型需要知道能做什么)
  3. 工作方式(重要——模型需要知道怎么做)
  4. 行为准则(重要——模型需要知道什么不能做)
  5. 项目规则(补充——特定项目的约束)
  6. 会话上下文(补充——之前做过什么)
  7. 工作目录(基本——当前环境信息)

核心指令在前,补充信息在后。模型处理上下文时,越靠前的信息权重越高。

隔离(Isolation)

不同任务不要互相污染。

会话记忆中的搜索记录保留了最近 5 条。如果 agent 先搜索了"数据库连接",然后切换到"修复 CSS 样式"任务,旧的搜索记录不应该干扰新任务。

MAX_RECENT_SEARCHES = 5 就是一种简单的隔离机制——超过 5 条就丢弃最旧的,自然地让过时的搜索淡出上下文。

另一种隔离是 failedCommands 只记录失败——成功的命令不记录。这样"失败过的命令"列表只包含需要避免的操作,不会被成功的命令稀释。

系统提示词的结构

整合项目规则和会话上下文后,系统提示词变成了一个动态构建的文本:

const systemParts: string[] = [
  "你是一个代码仓库助手...",  // 角色
  "## 可用工具",              // 工具
  "## 工作方式",              // 方式
  "## 行为准则",              // 准则
];

// 可选注入
if (options?.projectRules) {
  systemParts.push("", "## 项目规则", "", "...", options.projectRules);
}

if (contextText) {
  systemParts.push("", contextText);  // 已读文件、失败命令、最近搜索
}

systemParts.push("", `工作目录:${state.workingDir}`);

这种数组拼接的方式比字符串模板更灵活——每个可选部分独立判断,不存在就跳过,不会产生多余的空行。

和第 5 章事件系统的关系

上下文工程和事件系统是互补的:

  • 事件系统解决的是"实时展示"——让用户看到 agent 在做什么
  • 上下文工程解决的是"信息传递"——让模型知道之前做过什么

两者都从工具执行的结果中提取信息,但提取的目的不同。事件系统提取完整信息(给用户看),上下文工程提取压缩后的摘要(给模型看)。

登录以继续阅读

解锁完整文档、代码示例及更多高级功能。

立即登录

On this page