上下文工程(Context Engineering)是设计和控制智能体在运行过程中可获取的信息的过程。在 Deep Agents 中,智能体能够访问多种类型的上下文,有些在启动时提供,有些在运行时动态补充。框架内置了自动管理上下文的机制,使得智能体即使在长时间运行的任务中,也不会超出模型上下文窗口的限制。
下表总结了主要的上下文类型及其作用范围:
| 上下文类型 | 控制内容 | 作用范围 |
|---|---|---|
| 输入上下文 | 系统提示、记忆文件、技能文件、工具提示 | 每次运行均生效,静态加载 |
| 运行时上下文 | 用户元数据、API 密钥、数据库连接等 | 单次调用(invoke)级别,可传播给子智能体 |
| 上下文压缩 | 自动卸载大块内容、对话摘要 | 自动触发,保证不超出窗口 |
| 上下文隔离(子智能体) | 将繁重工作委托给独立智能体,仅返回结果 | 每个子智能体拥有独立上下文 |
| 长期记忆 | 跨对话的持久化文件存储 | 可跨线程/用户/组织持续存在 |
输入上下文
输入上下文是在智能体启动时注入其系统提示中的信息,它决定了智能体的基本行为、知识和能力。一个最终的系统提示由多个部分拼接而成,顺序如下:
- 自定义
system_prompt - 基础智能体提示
- 待办事项(planning)提示
- 记忆提示(如果配置了
memory) - 技能提示(如果配置了
skills) - 虚拟文件系统提示
- 子智能体提示
- 用户自定义中间件提示
- 人机交互提示(如果设置了
interrupt_on)
系统提示(system_prompt)
使用 system_prompt 参数定义智能体的角色和行为。它是一个静态字符串,在每次创建智能体时确定。如果需要动态生成(例如根据用户偏好注入不同提示),可以使用 @dynamic_prompt 中间件。
from deepagents import create_deep_agent
agent = create_deep_agent(
model="google_genai:gemini-3.1-pro-preview",
system_prompt=(
"你是一个专注于科学文献的研究助手。"
"始终引用来源。对不同主题的研究使用子智能体并行处理。"
),
)
# 调用智能体
agent.invoke({
"messages": [{"role": "user", "content": "解释一下变换器(transformer)模型的工作原理。"}]
})
记忆文件(Memory)
记忆文件(通常命名为 AGENTS.md)提供始终加载的持久化上下文。适合存放项目规范、用户偏好和必须在每次对话中生效的准则。与技能不同,记忆文件不会被渐进式加载,而是全部注入,因此应保持精简。
from deepagents import create_deep_agent
agent = create_deep_agent(
model="google_genai:gemini-3.1-pro-preview",
memory=["/project/AGENTS.md", "~/.deepagents/preferences.md"],
)
/project/AGENTS.md 的内容可以包含项目开发规范,例如:
## 项目规范
- 使用 Python 3.10+ 语法
- 优先使用 `pathlib` 处理文件路径
- 所有函数必须包含文档字符串
该文件会在每次对话开始时自动注入系统提示,智能体无需主动读取。
技能(Skills)
技能提供按需加载的能力。启动时智能体仅读取每个 SKILL.md 的前置元数据(frontmatter),只有当判断某项技能与当前任务相关时,才完整加载技能文件。这能显著节省 token 消耗,同时保有丰富的专项工作流。
from deepagents import create_deep_agent
agent = create_deep_agent(
model="google_genai:gemini-3.1-pro-preview",
skills=["/skills/research/", "/skills/web-search/"],
)
一个典型的技能文件 /skills/web-search/SKILL.md 可能如下:
---
name: web-search
description: 在互联网上搜索最新信息,获取实时数据。
---
# web-search
使用 `search_web` 工具进行搜索。每次返回结果后,应提炼出与用户问题最相关的三条摘要。
当用户提问涉及查询外部信息时,智能体会自动加载该技能的完整内容并执行其中的指令。(调用Skill)
“渐进式披露”如何工作?
简单来说,这个过程可以分三步走:
- 启动时:只看“名片” 当 Agent 启动时,它只会读取所有技能目录下
SKILL.md文件的 YAML 前置元数据部分。这部分内容就像一个精简版的名片,包含了技能的名称和简短描述(限制在1024个字符内),而最耗上下文的 Markdown 正文部分在此时是不会被加载的。 - 运作时:按需“调取详细档案” 只有当Agent在执行任务时,判断某个技能的描述匹配了当前需求,它才会去读取该技能的
SKILL.md的完整内容。这种“用谁读谁”的模式,正是“渐进式披露”(Progressive Disclosure)的核心思想。 - 与“Memory”的区别 这区别于始终驻留在上下文中的
Memory(AGENTS.md)。Skill 是按需的、有渐进式披露的,而 Memory 则是全时的。
工具提示(Tool Prompts)
工具提示是智能体关于如何使用工具的知识。内置工具(规划、文件系统、子智能体等)的提示由相应的中间件自动添加到系统提示中。你自行传入的工具,其名称、描述和参数描述将成为工具提示,直接影响模型何时以及如何使用工具。清晰的文档字符串至关重要。
from deepagents import create_deep_agent
from langchain.tools import tool
@tool(parse_docstring=True)
def search_orders(user_id: str, status: str, limit: int = 10) -> str:
"""根据状态搜索用户订单。
当用户询问订单历史或想检查订单状态时使用此工具。
始终根据提供的状态进行筛选。
Args:
user_id: 用户的唯一标识符
status: 订单状态:'pending'、'shipped' 或 'delivered'
limit: 返回的最大结果数量
"""
# 实际查询逻辑
return f"{user_id} 的 {status} 订单共 {limit} 条。"
agent = create_deep_agent(
model="google_genai:gemini-3.1-pro-preview",
tools=[search_orders],
)
运行时上下文(Runtime context)
运行时上下文是每次调用智能体时传入的配置数据,例如用户 ID、API 密钥、数据库连接等。它不会自动进入模型提示,只有你的工具或中间件显式读取并加入提示时,模型才能感知。这非常适用于多用户环境或不同运行环境需要切换。
定义上下文的结构:使用 dataclass 或 TypedDict。 传入上下文:在 invoke / ainvoke 时通过 context 参数提供。 在工具中访问:通过 ToolRuntime 的 context 属性。
from dataclasses import dataclass
from deepagents import create_deep_agent
from langchain.tools import tool, ToolRuntime
@dataclass
class Context:
user_id: str
api_key: str
@tool
def fetch_user_data(query: str, runtime: ToolRuntime[Context]) -> str:
"""获取当前用户的数据。"""
user_id = runtime.context.user_id
# 这里可以使用 runtime.context.api_key 进行认证
return f"用户 {user_id} 的数据:{query}"
agent = create_deep_agent(
model="google_genai:gemini-3.1-pro-preview",
tools=[fetch_user_data],
context_schema=Context,
)
# 调用时传入上下文
result = agent.invoke(
{"messages": [{"role": "user", "content": "获取我最近的活动"}]},
context=Context(user_id="user-123", api_key="sk-..."),
)
print(result)
运行时上下文还会自动传播给所有子智能体,父智能体的上下文在子智能体内部同样可用。
上下文压缩(Context compression)
长时间任务会产生大量工具输出和历史消息,逐渐占满模型的上下文窗口。Deep Agents 内置了卸载(offloading)和摘要(summarization)两种机制,自动维持上下文大小。
Deep Agents 中的上下文压缩机制,其底层逻辑源于一个已知的工程问题:LLM 的上下文窗口并非越大越好,当无关信息充斥时,模型反而会出现“上下文腐烂”(Context Rot)现象,导致性能下降。为此,Deep Agents 设计了大型工具结果卸载(Offloading )、大型工具输入卸载(Offloading )和对话摘要(Summarization)这三层压缩策略,分别在上下文窗口被耗尽的不同阶段触发。
大型工具结果剪裁(Offloading Large Tool Results)
此策略在每次工具调用返回结果时检查,旨在即时处理单次产生的大量数据,防止上下文窗口被一次性撑爆。底层是采用langchain的FilesystemMiddleware
- 默认阈值:工具返回结果超过 20,000 tokens。
- 触发条件:当Agent调用一个工具(如
read_file读取大文件、execute执行Shell命令、调用返回大量数据的API),其返回结果的大小超过阈值时,剪裁立即触发。
flowchart TD
A[代理调用工具]
A --> B{工具返回结果 tokens<br/>> 20,000?}
B -->|否| C[正常将结果追加到上下文]
B -->|是| D[执行卸载]
D --> E[将完整结果序列化<br/>并写入文件系统后端]
D --> F[生成一个文件路径引用<br/>和结果前10行的预览]
F --> G[用简短引用替换原结果<br/>加入上下文]
G --> H[代理上下文保持轻量]
H --> I{代理后续需要完整内容?}
I -->|是| J[调用 read_file 等工具<br/>按需读取]
I -->|否| K[继续执行其他任务]
classDef decision fill:#eee9ff,stroke:#8b7be8,stroke-width:1px;
classDef process fill:#f4f0ff,stroke:#8b7be8,stroke-width:1px;
class B,I decision;
class A,C,D,E,F,G,H,J,K process;
注意:此策略对Agent行为的影响是即时的,可以防止单个工具调用导致的上下文溢出。该阈值(BIG_TOOL_RESULT_LIMIT)目前是一个内部硬编码常量,无法通过外部API直接修改。
大型工具输入剪裁 (Offloading Large Tool Inputs)
此策略旨在清理上下文历史中的“冗余”信息。
- 默认阈值:上下文占用量达到模型最大输入窗口的 85%。
- 触发条件:当代理的整个会话历史(包括用户输入、模型输出、工具调用记录等)占用的token数越来越多,最终跨过模型上下文窗口85%的临界点时,该机制便会对历史消息中符合条件的条目进行清理。
这里清理的目标是历史中write_file或edit_file这类“写入型”工具调用的参数。因为这些参数包含了完整的文件内容,而且这些内容已经被持久化到了文件系统,所以在上下文中保留它们是冗余的。
flowchart TD
%% =========================
%% Agent 上下文管理流程
%% =========================
subgraph Agent["🤖 Agent 对话上下文管理流程"]
A["代理持续进行对话和工具调用"]
B{"上下文 tokens 数<br/>> 模型窗口的 85%?"}
C["遍历历史中的工具调用消息"]
D{"该调用是否为<br/>write_file 或 edit_file?"}
E["保留该消息<br/>不做处理"]
F["卸载该工具调用的输入参数"]
G["将原工具调用 input 参数<br/>(含完整文件内容)<br/>替换为文件路径指针"]
H["上下文大小显著缩减"]
end
%% =========================
%% 流程关系
%% =========================
A --> B
B -- "否" --> A
B -- "是" --> C
C --> D
D -- "否" --> E
D -- "是" --> F
F --> G
G --> H
H --> A
%% =========================
%% 样式定义
%% =========================
classDef start fill:#EDE7FF,stroke:#8B5CF6,stroke-width:2px,color:#333,font-size:16px;
classDef process fill:#F3F0FF,stroke:#8B5CF6,stroke-width:1.5px,color:#333;
classDef decision fill:#E9E6FF,stroke:#7C3AED,stroke-width:2px,color:#333;
classDef compress fill:#EDE9FE,stroke:#6D28D9,stroke-width:2px,color:#333;
classDef ignore fill:#F5F3FF,stroke:#A78BFA,stroke-width:1px,color:#333;
%% =========================
%% 应用样式
%% =========================
class A start;
class B,D decision;
class C,F,G process;
class H compress;
class E ignore;
注意:触发该机制的80%或85%这个比例,同样是一个内部硬编码常量,无法直接在外部修改。但它依赖的模型最大输入 token 数,可以通过 LangChain 的模型配置文件(Model Profiles) 进行管理。
对话摘要 (Summarization)
当前两层卸载策略已无法腾出足够空间,或上下文已接近极限时,会触发最后的保障——对话摘要。
- 默认触发阈值:通常是模型最大输入窗口的 85% 或总 token 数达到 170,000 时。
- 默认保留策略:保留最近 6条消息 或 总 token 数的 10% 作为“新鲜”上下文。
工作原理详解
- 双组件协同:摘要过程由两个紧密配合的组件完成。
- 上下文内摘要 (In-Context Summary):一个专门的 LLM 调用会阅读即将被移出的对话历史,并生成一个结构化的摘要,内容包括:用户的核心意图、会话中已完成的工件、以及后续计划。
- 文件系统归档 (Filesystem Preservation):被移出上下文的完整、原始的对话消息,会被序列化并安全地写入文件系统(例如
/conversation_history/{thread_id}.md),作为永久记录保存,以备将来审计或恢复。
- 上下文重组:生成的摘要替换了大量旧消息,模型的上下文窗口中被释放出巨大空间。同时,根据
keep策略保留的最新消息与摘要一同存在,确保了对话的近期连贯性。 - 可选的主动触发:除了被动触发,从
v0.4版本开始,Deep Agents 还提供了SummarizationToolMiddleware。它向代理暴露一个compact_conversation工具,让代理可以主动地在合适的时机(例如完成一个子任务后)触发摘要,变被动为主动。
graph TD
%% 定义节点样式类
classDef process fill:#e1f5fe,stroke:#0277bd,stroke-width:2px,rx:5,ry:5;
classDef decision fill:#fff9c4,stroke:#fbc02d,stroke-width:2px,stroke-dasharray: 2 2;
classDef container fill:#fcfcfc,stroke:#9e9e9e,stroke-width:1px,stroke-dasharray: 5 5;
subgraph Monitor [监控与触发]
Start[会话上下文不断增长]:::process
Check{上下文tokens数 > <br>触发阈值? <br>注: 默认85%或170K}:::decision
end
subgraph SummaryProcess [摘要压缩处理逻辑]
Step1[触发摘要流程]:::process
Step2[锁定摘要范围:<br>标记需要压缩的旧消息]:::process
Step3[保留策略:<br>根据 keep 参数保留最新消息]:::process
Step4[调用 LLM 对历史消息<br>生成结构化摘要]:::process
Step5[将旧消息从上下文中移除,<br>替换为生成的摘要]:::process
Step6[旧消息的完整对话历史<br>写入磁盘归档]:::process
Step7[代理上下文被压缩,<br>带着摘要和新消息继续工作]:::process
end
%% 流程连线
Start --> Check
Check -- 否 --> Start
Check -- 是 --> Step1
%% 摘要流程内部连线
Step1 --> Step2 --> Step3 --> Step4 --> Step5 --> Step6 --> Step7
%% 应用容器样式
class Monitor,SummaryProcess container
Deep Agents 使用 checkpointer 机制自动持久化每一次对话,因此天然支持情景记忆。你可以通过工具包装线程搜索,让智能体查询历史对话。
长期记忆(Cross-Session Memory)
Long-term memory 让智能体能够将信息持久化到跨对话/跨线程/跨用户的存储中。Deep Agents 把记忆实现为基于文件系统的读写,你可以通过配置不同的存储后端(backend)来控制文件实际保存的位置。
用户作用域长期记忆(User-scoped long-term memory)可以让 Agent 记住每名用户的偏好、历史上下文和特定指令,但核心指令保持不变。命名空间中的 rt.server_info.user.identity 会在部署后被解析为经过身份验证的呼叫用户(如 Alice 或 Bob),从而实现记忆的自动隔离。
命名空间决定了文件的“归属地”。不同命名空间的同名文件会被视为完全不同的文件,从而实现隔离。
- 用户作用域:
namespace=lambda rt: (rt.server_info.user.identity,)→ 用户 A 读写/memories/preferences.md实际对应的是(user-a, "/memories/preferences.md") - Agent 作用域:
namespace=lambda rt: (rt.server_info.assistant_id,)→ 所有用户共享同一个记忆文件 - 组合作用域:
(rt.server_info.assistant_id, rt.server_info.user.identity,)→ 为每个 Agent 的每个用户独立创建记忆文件
版本要求:访问
rt.server_info需要 **deepagents>=0.5.0**。在旧版本中,需要从get_config()["metadata"]["assistant_id"]读取 Assistant ID。
记忆工作原理
- 指向记忆文件:创建智能体时通过
memory=参数指定文件路径列表。CompositeBackend+StoreBackend:将/memories/路径映射到持久化存储。 - 智能体读取记忆:可以在启动时加载全部,也可以按需读取(技能即采用按需模式)。
- 智能体更新记忆(可选):使用内置的
edit_file或write_file工具修改记忆文件。更新可以发生在对话中(默认),也可以通过背景整合在对话之间进行。
安全与权限
用户作用域记忆本身已经提供了良好的隔离:用户 A 只能读取和写入自己的命名空间。但在以下场景中,需要额外关注安全性:
- 只读的共享策略:如果存在所有用户共享的组织级策略文件(如
/policies/compliance.md),应将其设置为只读,防止单个用户通过 prompt injection 恶意修改公司政策。 - 敏感路径写入审批:对于敏感的操作(如写入共享策略),可以使用中断(interrupt)机制要求人工审批。