大家好,我是程序员小策。

AI Agent 的 Context 是什么?
它有什么作用?
在项目中怎么去使用?
如果你有上述问题,不妨接着往下看。

一、大窗口为什么不是银弹

先说一个反直觉的事实:上下文窗口变大 ≠ 模型能更聪明地使用信息

2023 年 Stanford 和 Meta 的论文「Lost in the Middle」发现了一个 U 型曲线——相关信息出现在开头或结尾时模型表现很好,埋在中间准确率就掉。Chroma 在 2025 年「Context Rot」研究里测了 18 个模型,只是改变信息在上下文里的排列方式,GPT-4o 的准确率就能从 98.1% 掉到 64.1%

换句话说,你给模型一个 1M token 的窗口,它能"用上"的 token 可能就前 32K + 后 32K,中间那一片基本是噪声。

再看一个生产数据:在完整的 Agent 任务执行链路里,工具调用返回的结果占据整体 token 的 60%+,而系统提示词只占 30% 左右。大量珍贵的上下文空间被杂乱冗长的外部返回数据占据,真正用于思考决策的有效空间被严重挤压。

那 Context Engineering 到底是什么?用 Andrej Karpathy 的话说:“用下一步的正确信息填充上下文窗口的微妙艺术和科学。”

Cognition(Devin 的开发团队)说过一句我很认同的话:“Context engineering is effectively the #1 job of engineers building AI agents.” 翻译过来就是:写 Agent 的人,其实大部分时间不是在调 prompt,而是在管理上下文

二、4 大 Context 分类 + 引用块定义

业内对 Context 的分类有 3 套主流说法(LangChain 官方、Deep Agents 四大支柱、Galileo 四象限),本质上互相重叠。我把它们归一化,得到 4 大类。

Context(上下文):Agent 在推理前能看到的所有信息生态,由 4 层组成。

  • 输入上下文(Input):用户指令、System Prompt、RAG 检索片段、当前会话历史
  • 运行时上下文(Runtime):Agent 自身行动、工具调用结果、中间推理步骤
  • 压缩上下文(Compressed):对冗长历史做摘要、提取、嵌入后的精炼信息
  • 长期记忆(Long-term):跨会话持久化的用户偏好、历史交互、学到的知识

类比时间。把这 4 层映射到"自来水管网",我跟你讲,绝绝子:

  • 长期记忆 = 自来水公司的水库,容量大、调用慢、永远在
  • 压缩上下文 = 区域加压站,把支管的水汇总加压,按需供给
  • 运行时上下文 = 入户主水管,这一户用得最多的水都从这里走
  • 输入上下文 = 水龙头,你每次开水龙头就调一次温度(重新组装)

主水管(运行时)不能太粗,否则水压撑不住——这就是为什么 200K 窗口也会爆。输入层(指令)必须稳,否则水龙头出水忽冷忽热——System Prompt 不能随上下文变化被污染。

三、主流项目怎么存 Context:从 LangGraph 到 Claude Code

理论说完了,看实战。下面是 5 个主流项目的存储方案对比:

项目 短期存储 长期存储 核心创新
LangGraph Checkpointer(InMemory / Postgres) Store + 向量索引 命名空间隔离 (user_id, “profile”)
CrewAI 统一 Memory 同一类按 scope tree 分层 LLM 自动推断 scope/category/importance
AutoGen 对话 buffer 配合 OpenMemory 持久化 框架无关
Claude Code 五层压缩 + Session Memory 后台 fork 进程维护笔记 笔记复用 prompt cache,零额外成本
Cognee PostgreSQL + 知识图谱 图结构 + 向量双索引

两个值得抄的作业

  1. LangGraph Store:单线程用 Checkpointer(InMemorySaver / PostgresSaver),跨线程用 Store(InMemoryStore / PostgresStore)。命名空间隔离是事实标准。

  2. Claude Code 的 Session Memory:后台 fork 进程持续维护一份结构化笔记,主线程压缩时直接读笔记,避免重复 LLM 调用。这等于"压缩的输入成本被分摊到了所有模型调用"上。

四、代码实现:用 LangGraph 搭 4 层 Context

# 完整可运行 Demo:4 层 context 管理
# 依赖:pip install langgraph langchain-openai langchain-core
import os
from typing import Annotated, TypedDict
from operator import add
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.store.memory import InMemoryStore
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage, RemoveMessage

# ====== 1. 4 层 context 的状态 schema ======
class AgentContext(TypedDict):
    # 输入上下文(用户消息 + System Prompt)
    messages: Annotated[list, add]
    system_prompt: str
    # 运行时上下文(Agent 内部状态)
    current_step: str
    tool_call_count: int
    # 压缩上下文(历史摘要)
    history_summary: str
    # 长期记忆(从 Store 取,本地缓存)
    user_preferences: dict

# ====== 2. 长期记忆:跨会话 Store ======
long_term_store = InMemoryStore(
    index={"embed": lambda texts: [[0.0] * 1536 for _ in texts]}  # 生产换 OpenAI embedding
)

def load_long_term_memory(user_id: str) -> dict:
    """从 Store 读取用户偏好"""
    namespace = (user_id, "profile")
    prefs = {}
    for key, value in long_term_store.search(namespace):
        prefs[key] = value.value
    return prefs

def save_long_term_memory(user_id: str, key: str, value: dict):
    """写入用户偏好到 Store"""
    long_term_store.put((user_id, "profile"), key, value)

# ====== 3. 压缩节点:超过 8 条消息就触发摘要 ======
def compress_history_node(state: AgentContext) -> dict:
    messages = state["messages"]
    if len(messages) <= 8:
        return {}  # 未触发压缩

    # 保留 System + 最近 2 条,其余送 LLM 摘要
    to_summarize = messages[:-2]
    recent = messages[-2:]

    llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
    summary_prompt = (
        "请将以下对话历史压缩为 200 字内的关键信息摘要,"
        "保留:用户偏好、关键决策、未完成任务。\n\n"
        f"{chr(10).join(f'{m.type}: {m.content[:200]}' for m in to_summarize)}"
    )
    summary = llm.invoke(summary_prompt).content

    # 显式删除被压缩的消息(不靠截断最后 N 条那种暴力做法)
    return {
        "messages": [RemoveMessage(id=m.id) for m in to_summarize if hasattr(m, "id")],
        "history_summary": summary,
    }

# ====== 4. 主 Agent 节点:组装 4 层 context 后调用 LLM ======
def agent_node(state: AgentContext, config: dict) -> dict:
    user_id = config.get("configurable", {}).get("user_id", "default")
    prefs = load_long_term_memory(user_id)

    # 4 层组装:System + 长期偏好注入 + 压缩摘要 + 当前历史
    system_content = state["system_prompt"]
    if prefs:
        system_content += f"\n\n用户偏好:{prefs}"
    if state.get("history_summary"):
        system_content += f"\n\n历史摘要:{state['history_summary']}"

    full_messages = [SystemMessage(content=system_content)] + state["messages"]

    llm = ChatOpenAI(model="gpt-4o", temperature=0)
    response = llm.invoke(full_messages)

    return {
        "messages": [response],
        "current_step": "responded",
        "tool_call_count": state.get("tool_call_count", 0),
    }

# ====== 5. 搭图:compress → agent ======
workflow = StateGraph(AgentContext)
workflow.add_node("compress", compress_history_node)
workflow.add_node("agent", agent_node)
workflow.add_edge(START, "compress")
workflow.add_edge("compress", "agent")
workflow.add_edge("agent", END)

# Checkpointer 用于短期记忆(单线程)
checkpointer = InMemorySaver()
graph = workflow.compile(checkpointer=checkpointer)

# ====== 6. 运行测试 ======
if __name__ == "__main__":
    config = {"configurable": {"thread_id": "demo-1", "user_id": "alice"}}

    # 写入一条长期记忆
    save_long_term_memory("alice", "writing_style",
                          {"style": "简洁有力,避免废话"})

    # 模拟多轮对话触发压缩
    result = None
    for i in range(12):
        payload = {
            "messages": [HumanMessage(content=f"第 {i+1} 个问题:帮我写一段开场白")],
            "system_prompt": "你是一个小说写作助手",
            "current_step": "init",
            "tool_call_count": 0,
            "history_summary": "",
            "user_preferences": {},
        } if i == 0 else None
        result = graph.invoke(payload, config)

    print("最终消息数:", len(result["messages"]))
    print("历史摘要:", result.get("history_summary", "(未压缩)")[:100])

关键设计点

  • Checkpointer 管单线程(thread_id 隔离)
  • Store 管跨线程(namespace = (user_id, “profile”))
  • RemoveMessage 显式删除被压缩消息
  • 压缩触发阈值:8 条(生产环境建议 60-80% 窗口使用率)

五、看起来很完美,但这 4 个坑你可能踩过

坑 1:压缩时机太晚

现象:等到 prompt_too_long 报错才压缩
根因:触发后状态已污染,回滚困难
解法:60% 主动压缩,70% 强制压缩。Claude Code 团队的内部建议。

坑 2:摘要质量不稳定

现象:同样一段对话,不同次摘要差异巨大
根因:摘要 prompt 没固定结构
解法:用「Anchored Iterative Summarization」,固定 section(intent / decisions / files / next steps),强制结构化。

坑 3:原子消息组被拆散

现象:tool_call 消息被保留,但对应的 tool_result 被截断
根因:按条数截断,没考虑消息组完整性
解法:必须以 MessageGroup 为单位(Microsoft Agent Framework 的做法),保证 tool_call + tool_result 同时保留或删除。

坑 4:长期记忆没分类

现象:所有偏好混在一起,召回时一堆噪声
根因:直接 put(key, value),没分类
解法:用 scope tree 分层(CrewAI 做法),/user/alice/preferences/user/alice/projects 隔离。

六、生产考量:从 Demo 到上线还差 4 件事

1. 持久化:InMemorySaver 只适合 demo。生产换 PostgresSaver,Store 换 PostgresStore。命名空间隔离用 (user_id, "profile") 这种元组。

2. 监控:token 消耗分桶统计——输入 prompt、工具返回、压缩摘要调用、LLM 输出。LangSmith / Langfuse / Helicone 三选一。只监控总 token 等于没监控,因为压垮你的是某一桶。

3. 容错:压缩失败怎么办?保留原消息 + 标记 compression_failed=True,下次再试。绝对不能因为压缩失败导致会话中断

4. 资源:摘要本身要 LLM 调用,建议用 gpt-4o-miniclaude-haiku-4-5 而不是主力模型,省 80% 成本。

七、5 种压缩策略对比一览表

策略 核心思路 成本 保真度 适用场景
截断 (Truncation) 直接丢弃最早 N 条 闲聊、问候、已解决的支持问题
滑动窗口 (Sliding Window) 保留最近 N + 重叠 固定长度任务流
摘要 (Summarization) LLM 生成摘要替换原文 中(有 LLM 调用) 长对话、多任务连续
选择性保留 (Selective Retention) 按类型分级(System 永久、Tool 可压) 中-高 多类型混合内容
外部化 (Offloading) 大块内容写盘,只回注引用 工具返回超长、文档处理

一句话总结单独用哪种都不对,业界共识是"分层组合"——截断/滑窗打头阵(便宜确定),摘要兜底(保真),必要时外部化或子代理隔离。Claude Code 的五层压缩(microcompact → autocompact → context collapse)就是这个套路。

八、面试官还会追问什么?

追问 1:Context 压缩和 RAG 检索本质区别是什么?
→ 压缩是把"已有信息"瘦身,RAG 是从"外部知识库"补血。一个是减法,一个是加法。生产环境里两者配合用,先 RAG 补全缺什么,再压缩减重。

追问 2:为什么不让 LLM 自己决定"该压缩哪部分"?
→ LLM 决策失败时会把关键决策一起压掉,反而更糟。正确做法是规则优先(按时间、长度、类型),LLM 只负责生成摘要内容,不决定"压不压"。

追问 3:长期记忆和 RAG 看起来很像,怎么选?
→ 长期记忆是"用户画像 + 历史决策",强个性化、低更新频率。RAG 是"领域知识 + 文档片段",强客观性、高更新频率。前者用 Store 配 embedding,后者用向量数据库。

追问 4:如果模型支持 1M token,是不是就不用压缩了?
→ 大窗口有"Context Rot"问题:信息越多,关键信息被淹没的概率越高。而且成本线性增长、延迟线性增加。1M token 不是不用压缩,而是更该分层管理

九、总结:Context 工程的核心是「分层 + 预算」

Context Engineering 不是"把 prompt 写长一点"也不是"换个大窗口模型",它的本质是用分层 + 预算管理把 200K token 运营成可控资源

读完这篇你应该能:

  • 区分 4 大 context 类型,知道它们各自的生命周期
  • 选型主流存储方案(LangGraph Checkpointer + Store 是事实标准)
  • 选型压缩策略(截断优先 + 摘要兜底 + 外部化兜底)
  • 避开 4 个常见坑(时机、结构、原子组、分类)

如果这篇文章让你对 context 工程少一些敬畏,欢迎点赞 + 在看

最后一个问题留给你:你的项目里,"运行时上下文"现在有多重?有没有量过工具返回占多少比例

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐