上下文工程(Context-Engineering)

上下文工程(Context Engineering)是设计和控制智能体在运行过程中可获取的信息的过程。在 Deep Agents 中,智能体能够访问多种类型的上下文,有些在启动时提供,有些在运行时动态补充。框架内置了自动管理上下文的机制,使得智能体即使在长时间运行的任务中,也不会超出模型上下文窗口的限制。

下表总结了主要的上下文类型及其作用范围:

上下文类型 控制内容 作用范围
输入上下文 系统提示、记忆文件、技能文件、工具提示 每次运行均生效,静态加载
运行时上下文 用户元数据、API 密钥、数据库连接等 单次调用(invoke)级别,可传播给子智能体
上下文压缩 自动卸载大块内容、对话摘要 自动触发,保证不超出窗口
上下文隔离(子智能体) 将繁重工作委托给独立智能体,仅返回结果 每个子智能体拥有独立上下文
长期记忆 跨对话的持久化文件存储 可跨线程/用户/组织持续存在

输入上下文

输入上下文是在智能体启动时注入其系统提示中的信息,它决定了智能体的基本行为、知识和能力。一个最终的系统提示由多个部分拼接而成,顺序如下:

  1. 自定义 system_prompt
  2. 基础智能体提示
  3. 待办事项(planning)提示
  4. 记忆提示(如果配置了 memory
  5. 技能提示(如果配置了 skills
  6. 虚拟文件系统提示
  7. 子智能体提示
  8. 用户自定义中间件提示
  9. 人机交互提示(如果设置了 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)

“渐进式披露”如何工作?

简单来说,这个过程可以分三步走:

  1. 启动时:只看“名片” 当 Agent 启动时,它只会读取所有技能目录下 SKILL.md 文件的 YAML 前置元数据部分。这部分内容就像一个精简版的名片,包含了技能的名称和简短描述(限制在1024个字符内),而最耗上下文的 Markdown 正文部分在此时是不会被加载的。
  2. 运作时:按需“调取详细档案” 只有当Agent在执行任务时,判断某个技能的描述匹配了当前需求,它才会去读取该技能的 SKILL.md 的完整内容。这种“用谁读谁”的模式,正是“渐进式披露”(Progressive Disclosure)的核心思想。
  3. 与“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 密钥、数据库连接等。它不会自动进入模型提示,只有你的工具或中间件显式读取并加入提示时,模型才能感知。这非常适用于多用户环境或不同运行环境需要切换。

定义上下文的结构:使用 dataclassTypedDict。 传入上下文:在 invoke / ainvoke 时通过 context 参数提供。 在工具中访问:通过 ToolRuntimecontext 属性。

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_fileedit_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% 作为“新鲜”上下文。

工作原理详解

  1. 双组件协同:摘要过程由两个紧密配合的组件完成。
    1. 上下文内摘要 (In-Context Summary):一个专门的 LLM 调用会阅读即将被移出的对话历史,并生成一个结构化的摘要,内容包括:用户的核心意图、会话中已完成的工件、以及后续计划。
    2. 文件系统归档 (Filesystem Preservation):被移出上下文的完整、原始的对话消息,会被序列化并安全地写入文件系统(例如 /conversation_history/{thread_id}.md),作为永久记录保存,以备将来审计或恢复。
  2. 上下文重组:生成的摘要替换了大量旧消息,模型的上下文窗口中被释放出巨大空间。同时,根据 keep 策略保留的最新消息与摘要一同存在,确保了对话的近期连贯性。
  3. 可选的主动触发:除了被动触发,从 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。

记忆工作原理

  1. 指向记忆文件:创建智能体时通过 memory= 参数指定文件路径列表。CompositeBackend + StoreBackend:将 /memories/ 路径映射到持久化存储。
  2. 智能体读取记忆:可以在启动时加载全部,也可以按需读取(技能即采用按需模式)。
  3. 智能体更新记忆(可选):使用内置的 edit_filewrite_file 工具修改记忆文件。更新可以发生在对话中(默认),也可以通过背景整合在对话之间进行。

安全与权限

用户作用域记忆本身已经提供了良好的隔离:用户 A 只能读取和写入自己的命名空间。但在以下场景中,需要额外关注安全性:

  • 只读的共享策略:如果存在所有用户共享的组织级策略文件(如 /policies/compliance.md),应将其设置为只读,防止单个用户通过 prompt injection 恶意修改公司政策。
  • 敏感路径写入审批:对于敏感的操作(如写入共享策略),可以使用中断(interrupt)机制要求人工审批。
--- 本文结束 The End ---