第 08 章

上下文工程

控制 deep agent 可以访问哪些上下文,以及如何在长时间运行的任务中管理这些上下文

以正确的格式提供正确的信息和工具,让 agent 可靠完成任务

为什么需要它

你的 agent 是否经常"忘记"重要信息,或者上下文窗口被撑爆?

没有上下文管理的困境

  • 系统提示越来越长,token 消耗不可控
  • 长时间运行后上下文溢出,agent 丢失关键信息
  • 所有工具输出全塞进对话,信噪比极低
  • 子任务的中间过程污染主上下文,推理质量下降
  • 跨会话无法记住用户偏好,每次都从零开始

上下文工程的解法

  • 输入上下文:精确控制启动时注入什么信息
  • 上下文压缩:自动卸载大内容 + 智能摘要
  • 上下文隔离:子 agent 独立运行,只返回结果
  • 长期记忆:跨对话持久保存重要信息
  • 运行时上下文:按需传递用户元数据和配置
问题核心:上下文工程是指以正确的格式提供正确的信息和工具,使你的 deep agent 能够可靠地完成任务。不是"塞更多信息",而是"精准地提供需要的信息"。

五种上下文

deep agent 可以访问和管理的五种上下文类型

上下文类型作用触发时机
输入上下文 系统提示、记忆、技能等启动时注入的内容 静态,每次运行时应用
运行时上下文 用户元数据、API 密钥、连接等配置 每次运行,传播到子 agent
上下文压缩 卸载大内容 + 摘要旧消息,保持在窗口内 自动触发,接近限制时
上下文隔离 子 agent 隔离繁重工作,只返回结果 每种子 agent,委派时使用
长期记忆 跨线程持久存储用户偏好和知识 跨对话持久保存

启动时可用

输入上下文和运行时上下文在 agent 启动时提供,是你可以直接控制的部分。

运行时管理

压缩、隔离和长期记忆是 deep agent 内置的机制,在运行过程中自动或按需触发。

图:五种上下文类型及其触发时机

输入上下文

启动时提供给 agent 的信息,成为系统提示的一部分

最终的系统提示由多个来源组装而成,每个来源有不同的加载策略:

System prompt

你提供的自定义指令 + 内置的 agent 指导。定义角色、行为和知识。

静态注入

Memory(记忆)

配置后始终加载的 AGENTS.md 文件。用于项目约定和用户偏好。

始终加载

Skills(技能)

启动时只读 frontmatter,判断相关时才加载完整内容。按需能力。

渐进式披露

Tool prompts(工具提示)

内置工具和自定义工具的使用说明,包含 schema 和描述。

随工具加载

组装顺序:自定义 system_prompt → 基础 agent 提示 → 待办事项提示 → 记忆提示 → 技能提示 → 虚拟文件系统提示 → 子 agent 提示 → 自定义中间件提示 → 人在回路提示。理解顺序有助于调试。

系统提示

定义 agent 的角色、行为和知识的最直接方式

白话:系统提示就是你对 Agent 说的"第一句话",告诉它你是谁、你要做什么、怎么做。它不会变,每次运行都一样。

systemPrompt 参数是静态的,不会在每次调用时改变。用最简洁的语言告诉 agent 它是谁、应该怎么做:

import { createDeepAgent } from "deepagents";

const agent = await createDeepAgent({
  model: "google-genai:gemini-3.1-pro-preview",
  // systemPrompt 是静态的,每次运行都一样,定义 Agent 的角色和行为
  systemPrompt: `You are a research assistant specializing in scientific literature.
  Always cite sources. Use subagents for parallel research on different topics.`,
});

静态 systemPrompt

  • 每次运行都一样,适合稳定的角色定义
  • 不需要中间件,直接传字符串
  • 适合:角色、通用规则、工作流指导

动态 systemPrompt

  • 根据上下文变化,需要中间件
  • 用 dynamicSystemPromptMiddleware
  • 适合:权限控制、用户偏好注入
  • 中间件可读取 request.runtime.context 和 request.runtime.store
何时需要中间件?只有当系统提示本身需要根据请求变化时。如果只是工具需要上下文或 store,工具会直接接收 runtime 对象,不需要中间件。

记忆文件

始终加载的持久化上下文——AGENTS.md 文件

记忆文件提供了始终加载到系统提示中的持久化上下文。适合存放每次对话都适用的关键准则:

const agent = await createDeepAgent({
  model: "google-genai:gemini-3.1-pro-preview",
  // memory 中的文件在每次对话时始终加载到系统提示中
  // 适合存放项目约定、用户偏好、安全规则等始终需要的信息
  memory: ["/project/AGENTS.md", "~/.deepagents/preferences.md"],
});

适合放记忆的内容

  • 项目约定(代码风格、命名规范)
  • 用户偏好(简洁回复、语言偏好)
  • 关键准则(安全规则、合规要求)
  • 团队工作流(审批流程、部署步骤)

不适合放记忆的内容

  • 详细的工作流(太长,用技能代替)
  • 领域特定知识(用技能按需加载)
  • 临时的任务信息(用运行时上下文)
  • 敏感凭证(用运行时上下文传递)
注意:记忆是始终注入的,没有渐进式披露。保持记忆精简以避免上下文过载。将详细内容使用技能(Skills)来提供,只有始终相关的约定才放记忆。

技能系统

按需加载的能力——只在相关时才注入上下文

技能是 渐进式披露 的核心。agent 启动时只读每个 SKILL.md 的 frontmatter,判断技能相关时才加载完整内容,大幅减少 token 消耗:

const agent = await createDeepAgent({
  model: "google-genai:gemini-3.1-pro-preview",
  // skills 下的内容只在相关时才加载(渐进式披露),不是每次都消耗 token
  // 每个目录下的 SKILL.md 的 frontmatter 会先被读取,判断相关后才加载完整内容
  skills: ["/skills/research/", "/skills/web-search/"],
});

记忆 vs 技能

  • 记忆:始终加载,用于全局约定
  • 技能:按需加载,用于特定工作流
  • 记忆每次都消耗 token
  • 技能只在相关时消耗 token

技能设计原则

  • 每个技能聚焦单一工作流
  • 主要内容简洁,详细参考放单独文件
  • 避免宽泛或重叠的技能定义
  • 始终相关的规则放记忆,不放技能
渐进式披露的价值:假设你有 20 个技能,平均每个 2000 token。如果全部加载就是 40,000 token。但按需加载意味着一次对话可能只用到 2-3 个技能,只需 4,000-6,000 token。
卸甲:上面用"按需加载"这个比喻,听起来像是 Agent 有"判断力"去选择技能。实际上 Agent 只是根据用户的问话内容,对比技能 frontmatter 中的关键词,匹配上了才加载。这和你去图书馆翻目录找书是两回事——Agent 没有"想不想学"的意愿。

工具提示

塑造模型如何使用工具的指令——名称、描述和参数

所有工具都暴露模型能看到的元数据。清晰的工具描述能让模型更好地判断何时、如何使用工具:

import { tool } from "@langchain/core/tools";
import { z } from "zod";

const searchOrders = tool(
  async ({ userId, status, limit }) => { /* ... */ },
  {
    name: "search_orders",
    // 描述要写清楚"何时使用",帮助模型判断是否调用此工具
    description: `Search for user orders by status.

Use this when the user asks about order history or wants to check
order status. Always filter by the provided status.`,
    // schema 用 zod 定义,每个字段用 describe() 标注含义
    schema: z.object({
      userId: z.string().describe("Unique identifier for the user"),
      status: z.enum(["pending", "shipped", "delivered"])
              .describe("Order status to filter by"),
      limit: z.number().default(10)
             .describe("Maximum number of results to return"),
    }),
  }
);

内置工具提示

  • 规划提示:write_todos 用法
  • 文件系统提示:ls、read_file 等
  • 子 agent 提示:task 工具用法
  • 人在回路提示:interrupt_on 用法
  • 本地上下文提示:当前目录和项目信息(仅 CLI)

写好工具描述的要点

  • 包含何时使用该工具
  • 描述每个参数的作用
  • 提供清晰的名称和描述
  • 使用 describe() 标注参数含义
Harness Profile 技巧:使用 harness profile 中的 tool_description_overrides(按工具名称索引)可覆盖内置或自定义工具描述。用 excluded_tools 可从可见工具集中完全移除某个工具。

运行时上下文

调用 agent 时传递的每次运行配置——不会自动进入模型提示

运行时上下文用于传递用户元数据、API 密钥、功能标志等。模型只有在工具或中间件读取它时才会看到:

import { createDeepAgent } from "deepagents";
import { tool } from "@langchain/core/tools";
import type { ToolRuntime } from "@langchain/core/tools";
import { z } from "zod";

// 定义运行时上下文的 schema,调用时传入的用户数据必须符合此结构
const contextSchema = z.object({
  userId: z.string(),
  apiKey: z.string(),
});

// 工具通过第二个参数 runtime 获取上下文
const fetchUserData = tool(
  async (input, runtime: ToolRuntime) => {
    // 从 runtime.context 中读取调用时传入的 userId
    const userId = runtime.context?.userId;
    return `Data for user ${userId}: ${input.query}`;
  },
  {
    name: "fetch_user_data",
    description: "Fetch data for the current user",
    schema: z.object({ query: z.string() }),
  }
);

const agent = await createDeepAgent({
  model: "google-genai:gemini-3.1-pro-preview",
  tools: [fetchUserData],
  contextSchema,  // 绑定上下文 schema,工具才能通过 runtime.context 访问
});

// 调用时通过第二个参数的 context 字段传入运行时上下文
const result = await agent.invoke(
  { messages: [{ role: "user", content: "Get my recent activity" }] },
  { context: { userId: "user-123", apiKey: "sk-..." } },
);
上下文传播:运行时上下文会自动传播到所有子 agent。子 agent 运行时接收与父级相同的运行时上下文,无需手动传递。

内容卸载

当工具输入或结果超过 token 阈值时,自动存入文件系统

长时间运行的任务会产生大量工具输出。内容卸载是第一道防线,默认阈值为 20,000 token:

工具输入卸载

文件写入和编辑操作在对话历史中留下包含完整文件内容的工具调用。当超过窗口 85% 时,较旧的工具调用被截断,用指向磁盘上文件的指针替代。

触发条件:窗口 85%

工具结果卸载

当工具结果超过 20,000 token 时,响应被卸载到后端,用文件路径引用 + 前 10 行预览替代。Agent 可以按需重新读取或搜索。

触发条件:20,000 token

卸载前

  • 完整的文件内容在对话历史中
  • 一次大文件读写可能消耗数万 token
  • 多次操作后上下文快速膨胀

卸载后

  • 大内容存入文件系统
  • 对话中只保留文件路径引用
  • Agent 按需 read_file 获取详情
核心思路:已经持久化到文件系统的内容不需要在对话历史中保留。卸载机制自动识别冗余内容,用指针替代,保持活跃上下文的精简。
图:工具输入/结果的内容卸载流程

智能摘要

当卸载不足以释放空间时,自动压缩对话历史

当上下文超过窗口限制(如 max_input_tokens 的 85%),且没有更多可卸载的内容时,触发摘要:

1. 接近限制

上下文超过 85% 窗口

2. 生成摘要

LLM 压缩旧消息

3. 保留最近

保留 10% token 近期上下文

4. 存档原始

完整消息写入文件系统

// 过滤掉摘要步骤生成的 token,只处理用户可见的输出
for await (const [namespace, chunk] of await agent.stream(
  { messages: [...] },
  { streamMode: "messages" },  // 以消息模式流式输出
)) {
  const [message, metadata] = chunk;
  if (metadata?.lcSource === "summarization") {
    continue;  // 跳过自动摘要产生的 token,避免干扰用户界面
  } else {
    // 处理正常的输出 token
  }
}

摘要配置

  • 在 max_input_tokens 的 85% 时触发
  • 保留 10% token 作为最近上下文
  • 无 model profile 时回退到 170,000 / 6 条消息
  • 需要 deepagents>=1.6.0(含通过 wrapModelCall 进行模型内摘要、精确 token 计数和自动 ContextOverflowError 回退)

错误回退

  • 如果模型调用抛出 ContextOverflowError
  • Agent 立即回退到摘要
  • 用摘要 + 最近保留的消息重试
  • 无需手动处理溢出
图:四步智能摘要流程

上下文隔离

用子 agent 隔离繁重工作——主 agent 只接收最终结果

当主 agent 使用产生大量输出的工具时,上下文窗口很快被填满。子 agent 解决了这个上下文膨胀问题:

不用子 agent

  • 主 agent 直接调用搜索、文件读取、数据库查询
  • 所有中间结果都在主上下文中
  • 上下文快速膨胀,推理质量下降
  • 难以追踪哪部分有用

用子 agent

  • 主 agent 用 task 工具委派工作
  • 子 agent 用全新上下文独立运行
  • 子 agent 只返回单一最终报告
  • 主 agent 上下文保持干净
const researchSubagent = {
  name: "researcher",
  description: "Conducts research on a topic",
  // 子 agent 的 systemPrompt 不继承主 agent,必须完整定义
  // 关键:要求返回摘要而非原始数据,避免撑爆主 agent 的上下文
  systemPrompt: `You are a research assistant.
  IMPORTANT: Return only the essential summary (under 500 words).
  Do NOT include raw search results or detailed tool outputs.`,
  tools: [webSearch],  // 只给搜索工具,保持子 agent 专注
};
最佳实践:指示子 agent 返回摘要而非原始数据。如果子 agent 生成了很长的输出,在其 systemPrompt 中添加指导,要求生成摘要和综合发现。大型数据让子 agent 写入文件,主 agent 按需读取。

长期记忆

跨对话持久保存信息——使用虚拟文件系统 + LangGraph Store

默认情况下,deep agent 的文件仅在线程内持久保存。长期记忆通过 CompositeBackend 将特定路径路由到持久存储:

import { createDeepAgent, CompositeBackend,
         StateBackend, StoreBackend } from "deepagents";
import { InMemoryStore } from "@langchain/langgraph-checkpoint";

const agent = await createDeepAgent({
  model: "google-genai:gemini-3.1-pro-preview",
  store: new InMemoryStore(),  // LangGraph 存储,用于跨线程持久化
  backend: new CompositeBackend(
    new StateBackend(),                  // 默认后端:线程内临时文件
    { "/memories/": new StoreBackend() }, // /memories/ 路径走跨线程持久化
  ),
  // 在系统提示中告知 Agent 如何使用长期记忆
  systemPrompt: `When users tell you their preferences,
  save them to /memories/user_preferences.txt
  so you remember them in future conversations.`,
});

工作原理

  • CompositeBackend 混合存储系统
  • StateBackend:线程内临时文件
  • StoreBackend:跨线程持久文件
  • 路径匹配:/memories/ 路由到 Store

使用方式

  • 不需要预先填充文件
  • 提供后端配置 + store + 提示指令
  • Agent 按需用 write_file 创建记忆
  • 跨对话自动读取已有记忆
记忆分层:短期记忆(线程内的 State)用于当前任务,长期记忆(/memories/ 路径)用于跨会话。两层互不干扰,各有各的生命周期。
预填充记忆:如果需要在部署前预填充记忆内容,请在 LangSmith 上部署时使用 Store API 直接写入记忆数据。设置和用例请参阅长期记忆文档。

最佳实践

六条经过验证的上下文管理策略

1. 精简输入上下文

记忆只放始终相关的约定,特定任务能力用聚焦的技能提供。避免在系统提示中堆砌信息。

2. 子 agent 处理繁重工作

将多步骤、输出量大的任务委派给子 agent,保持主 agent 的上下文干净。

3. 配置子 agent 输出

在子 agent 的 system_prompt 中要求返回摘要,而非原始数据。从源头控制输出量。

4. 善用文件系统

将大型输出持久化到文件,活跃上下文保持较小。模型按需通过 read_file 和 grep 提取。

5. 记录记忆结构

在系统提示中告诉 agent /memories/ 中存放什么、如何使用。让 agent 知道记忆的组织方式。

6. 传递运行时上下文

用 context 传递用户元数据、API 密钥等静态配置,不要硬编码在提示中。

系统提示组装

理解完整的系统提示由哪些部分组成

模型在运行开始时接收到的系统消息,按顺序由以下部分组成:

序号组件条件
1自定义 system_prompt如果提供
2基础 agent 提示始终存在
3待办事项列表提示始终存在
4记忆提示(AGENTS.md + 使用指南)仅提供 memory 时
5技能提示(位置 + frontmatter + 用法)仅提供 skills 时
6虚拟文件系统提示如适用
7子 agent 提示(Task 工具用法)始终存在
8用户中间件提示如果提供自定义中间件
9人在回路提示设置 interrupt_on 时
图:系统提示多层组装顺序
调试提示:如果你觉得 agent 的行为不符合预期,可能是系统提示的某部分与你的自定义指令冲突。用 harness profile 的 base_system_prompt 直接替换基础提示,或用 system_prompt_suffix 追加内容。

本章回顾

上下文工程是让 deep agent 可靠运行的核心能力

你学到了什么

  • 五种上下文类型:输入、运行时、压缩、隔离、长期记忆
  • 输入上下文:系统提示 + 记忆(始终加载)+ 技能(按需加载)+ 工具提示
  • 上下文压缩:卸载(文件系统替代)+ 摘要(LLM 压缩旧消息)
  • 上下文隔离:子 agent 独立上下文,只返回最终结果
  • 长期记忆:CompositeBackend 实现跨对话持久化
  • 六条最佳实践:精简输入、委派繁重、控制输出、善用文件、记录结构、传递上下文

后续章节预告

  • 后端系统:文件系统后端和 CompositeBackend 深入配置
  • 子 Agent:上下文隔离、运行时上下文传播详解
  • 长期记忆:跨线程持久化的完整实现
  • 技能:渐进式披露和技能编写的最佳实践
  • 配置:Harness profiles 调整提示和工具描述
核心结论:上下文工程不是"塞更多信息",而是"精准地提供需要的信息"。通过记忆、技能、卸载、摘要和子 agent 的组合,你可以让 agent 在长时间运行中始终保持高效的上下文利用率。

延伸阅读

继续本课程

相关主题

  • Harness -- 上下文管理概述、卸载、摘要
  • 技能 -- 渐进式披露和技能编写
  • 配置 -- Harness profiles 调整提示和工具

官方文档