你接入了最强的模型,写了一个 Agent,结果它表现得像个实习生:答非所问、乱调工具、忘了前面说过什么。你第一反应是"模型不行",于是换更贵的模型——然后发现没什么改善。
问题大概率不在模型,而在上下文。这篇文章讲清楚什么是上下文工程(Context Engineering),以及怎么系统化地"把正确的信息、用正确的格式、在正确的时机喂给模型"。
一、90% 的 Agent 失败,都不是模型的锅
打个比方。你请来一位顶尖维修专家,对他说:"帮我修好这台机器。"他失败了——不是因为他不行,而是因为你没告诉他:机器型号、故障现象、维修手册、可用工具。
换种说法:
“帮我修好这台机器。型号 X-200,故障是异常噪音,这是维修手册第 3 章,可用工具有扳手、螺丝刀、万用表。”
同一个专家,立刻解决问题。差别不在能力,在上下文。
Agent 也一样。一个被广泛认同的经验法则是:
Agent 失败的原因: ❌ 10% 因为底层 LLM 能力不足 ✅ 90% 因为没有传给 LLM "正确的"上下文所以Context Engineering(上下文工程)的定义就是:以正确的格式,为 LLM 提供正确的信息和工具,让它能完成任务。这是 AI 工程师的首要工作。
它不等于"提示词工程"
很多人把上下文工程简化成"怎么把提示词写好",这远远不够:
| Prompt Engineering | Context Engineering | |
|---|---|---|
| 关注 | 单次提示词怎么写更好 | 整个上下文系统怎么设计 |
| 范围 | 一句提示词的内容和结构 | 给什么、为什么给、给多少、何时给、如何压缩与维护 |
提示词工程 ⊂ 上下文工程。提示词只是上下文的一部分;上下文工程还要管对话历史、工具、记忆、状态、检索……它是这些技术的编排者,而不是其中任何一个。
二、一个贯穿全文的比喻:上下文工程 = 给决策者备简报的幕僚长
把模型想成一个日理万机的决策者(高管),而你(上下文工程师)是他的幕僚长:决定什么材料送到他桌上、什么被过滤掉、调哪份档案、哪些当下要看、哪些只是背景参考。
整篇文章你只需记住这个比喻,后面所有概念都能对号入座——而且每一项的核心动作都是为决策者筛选并适时递送信息:
| 概念 | 幕僚长比喻 | 作用 |
|---|---|---|
| 模型本身 | 做决策的高管 | 它的本职就是"决定调工具还是直接答" |
| Context Engineering | 幕僚长的工作 | 策展/筛选——决定什么上桌、什么拦下 |
| Model Context | 今天这场会的简报本 | 单次调用的输入(开完即作废) |
| Tool Context | 助理去调档、更新档案 | 工具读写的数据(改了后面一直生效) |
| Life-cycle Context | 幕僚长的把关流程 | 控制执行流程,不替高管做决定 |
| Middleware | 各环节的幕后人员 | 钩入流程的每个环节 |
三、Agent 的核心循环:上下文在哪里被"喂"进去
理解上下文工程,先看 Agent 怎么转:
用户输入 → 【模型调用】(传入提示词+工具)→ 需要调工具? ├─ 是 → 【工具执行】→ 工具结果 → 回到模型调用 └─ 否 → 输出答案上下文工程,就是在这个循环的每一步,决定"传入什么、何时传入、如何更新"。下面三大维度,正是对这个循环不同环节的控制。
四、三大控制维度:瞬态 vs 持久
这是整套体系的骨架。关键区分是瞬态(只影响这一次)还是持久(跨步骤保留)。
维度一:Model Context(模型上下文)—— 瞬态
只影响单次模型调用,演完就结束。包括:
- 系统提示词:模型的角色(“你是客服助手”)
- 消息历史:当前对话
- 可用工具:这次能调哪些工具
- 模型选择:用哪个 LLM
- 响应格式:结构化输出的 Schema
比喻:今天这场会的简报本。每次内容不同,开完即作废,不影响下一场会。
维度二:Tool Context(工具上下文)—— 持久
工具对数据源的读写,效果会留下来影响后续:
工具执行时: 读取:查数据库、取用户偏好、读配置 写入:更新会话状态、改长期记忆、记日志比喻:助理去调档、更新档案。一旦改了某条记录,后续所有人看到的都是新状态。
维度三:Life-cycle Context(生命周期上下文)—— 持久
控制"模型调用与工具执行之间"的逻辑——对话摘要、护栏检查、日志、动态提示词、模型切换。
比喻:幕僚长的把关流程。不替高管做决定,但决定什么先递、什么拦下、何时提醒、何时收尾。
五、三大数据源:信息从哪来,活多久
控制维度是"怎么管",数据源是"管的东西从哪来"。三者生命周期不同:
| 数据源 | 生命周期 | 作用域 | 装什么 |
|---|---|---|---|
| Runtime Context | 单次调用 | 会话内 | 用户 ID、API Key、环境标志(静态配置) |
| State | 会话期间 | 会话内 | 消息历史、上传文件、认证状态(短期记忆) |
| Store | 永久 | 跨会话 | 用户偏好、历史画像(长期记忆) |
对应代码大致长这样:
# Runtime Context:调用时传入的静态配置@dataclassclassAppContext:user_id:strenvironment:str# dev / prodrole:str# admin / viewerresult=agent.invoke({"messages":[...]},context=AppContext(user_id="u1",...))# State:会话内的短期记忆,会话结束即消失state={"messages":[...],"uploaded_files":[...],"auth_status":True}# Store:跨会话的长期记忆runtime.store.put(("users","u1"),"preferences",{"language":"zh","theme":"dark"})prefs=runtime.store.get(("users","u1"),"preferences")# 下次会话还在一句话记忆:Runtime Context 是"这次的配置",State 是"这场对话的记忆",Store 是"永久的档案"。
六、落地机制:中间件(Middleware)
前面都是"该做什么",Middleware 是"怎么做"——它能钩入 Agent 生命周期的任何一步:
用户输入 → before_agent 会话级钩子 → before_model 模型调用前 → wrap_model_call 包裹模型调用 → 【模型调用】 → after_model 模型调用后 → wrap_tool_call 包裹工具调用 → 【工具执行】 → after_agent 会话级钩子 → 输出 / 回到循环很多常见需求,官方已有内置中间件,别自己造轮子。比如对话超长时自动压缩历史:
fromlangchain.agents.middlewareimportSummarizationMiddleware summarizer=SummarizationMiddleware(model="gpt-4-mini",trigger=("tokens",4000),# 超过 4000 token 触发keep=("messages",20),# 保留最近 20 条)agent=create_agent(model="gpt-4",middleware=[summarizer])或者根据用户角色动态过滤工具(非管理员看不到危险工具):
@before_modeldeffilter_tools_by_role(state,runtime):ifruntime.context.role!="admin":request.override(tools=[tfortinrequest.toolsift.namenotin["delete_record","admin_tool"]])returnrequest七、最容易踩的坑:瞬态 vs 持久更新
这是上下文工程里最常见的误解,单独拎出来讲。
同样是"往消息里加一条指令",用不同钩子,效果完全不同:
# 瞬态:只影响本次调用,State 历史不变@wrap_model_calldeftransient_change(state,request,handler):request.messages=[SystemMessage("额外指令")]+request.messagesreturnhandler(request)# State 没动# 持久:永久改变 State@before_modeldefpersistent_change(state):return{"messages":[SystemMessage("额外指令")]+state["messages"]}# State 被更新典型翻车现场:在wrap_model_call里改消息,以为改了对话历史,结果只有这一次调用看得到,下一轮就没了。记住:
- 想"只这一次生效"(如临时注入一条约束)→瞬态(
wrap_model_call) - 想"永久留在历史里" →持久(
before_model返回 dict 更新 State)
八、几条高价值实践
1. 从简开始。先用静态提示词 + 固定工具跑通,验证确实需要后再加动态逻辑。不要一上来就上一堆中间件。
2. 增量测试。一次只加一个特性:静态 → 动态提示词 → 工具过滤 → Store 记忆,方便定位问题。
3. 工具定义就是给 LLM 的"说明书"。名称、描述、参数说明不是元数据,而是引导模型推理的关键:
# ✅ 好:说清"做什么、何时用、不适合什么"@tooldefsearch_web(query:str,max_results:int=5)->str:"""搜索互联网获取最新信息。当你需要实时数据、新闻或事实时使用。 不适合用于本地数据库查询或计算。 Args: query: 搜索关键词(2-10 个词) max_results: 最大返回结果数 """# ❌ 差:模型不知道何时该用@tooldefsearch(query:str)->str:"""搜索。"""4. 工具数量要适中(5~10 个为佳)。太多(50+)→ 上下文过载、模型混淆、乱调;太少(只给 1 个)→ 能力受限、模型用文本硬编替代工具。需要时动态过滤工具集。
5. 别把所有信息一次性塞进去。10 万字文档全文、500 条历史、50 个工具——这是上下文过载的标准姿势。正确做法:RAG 检索相关片段、Summarization 压缩历史、动态过滤工具。
九、总结
上下文工程的本质,是围绕"模型每次调用的输入"做系统化的设计与治理。它回答的不是"提示词怎么写"这一个点,而是一整套问题:
给模型什么上下文、为什么给、给多少、什么时候给、如何压缩和维护。把它拆解开就是这张图:
- 三大维度(怎么控制):Model(瞬态)/ Tool(持久)/ Life-cycle(持久)
- 三大数据源(信息从哪来):Runtime Context(配置)/ State(短期)/ Store(长期)
- 一个机制(怎么落地):Middleware,钩入生命周期每一步
当你下次觉得"模型不行"想换更贵的模型时,先停一下问自己:我是不是没把正确的上下文喂给它?九成情况下,答案在这里,而不在模型。