光会写 Prompt 不够用了——AI Agent 时代,你需要懂 Context Engineering
导读:你有没有发现,明明 Prompt 写得很认真,AI Agent 跑着跑着就"失忆"了,或者答非所问?
问题不在 Prompt,在Context(上下文)。本文带你搞清楚 2025 年 AI 工程圈最热的概念——Context Engineering,以及怎么用它让你的 Agent 聪明一倍。零基础友好,附实战代码。
一、那些年,Agent 给我整的笑话
说个真实场景。
你花了两周搭了一个客服 Agent,测试的时候表现完美——回答准确、有条理、语气友好。
上线第一天,用户问了一个稍微复杂点的问题,Agent 开始正常回答。
然后用户追问了三四轮……
Agent 的回答开始变味了。
先是忘了自己是"客服助手"的身份,开始自由发挥。
然后忘了用户前面说过的信息,开始重复问。
最后干脆给出了一个跟实际产品文档完全矛盾的答案。
你盯着日志,百思不得其解——Prompt 我没动啊,怎么就翻车了?
“RAG 80% 的问题,出在切文档那一步。Agent 80% 的问题,出在上下文管理那一步。”
哎,这就是 Context Engineering 要解决的核心问题。
二、Context Engineering 是什么?跟 Prompt Engineering 有什么区别?
先说清楚这两个概念,很多人混着用,其实差得远。
💡一句话定义:Prompt Engineering 是"如何问一个好问题",Context Engineering 是"如何把整个对话现场布置好,让 AI 一直保持最佳状态"。
用装修来类比——
Prompt Engineering 像是"怎么跟装修工人沟通需求",你要说清楚风格、预算、要求。
Context Engineering 像是"设计整个施工现场":工人在哪、图纸放哪、工具摆哪、今天干什么、昨天干了啥、下一步计划是什么。
现场设计好了,工人自然干得好;现场一团乱,沟通再清楚也没用。
这个概念在 2025 年被 AI 圈广泛采用,Andrej Karpathy(前 OpenAI 研究员、特斯拉 AI 负责人)明确表达过这个观点:
“The bottleneck is not the model. The bottleneck is context.”
(瓶颈不是模型,瓶颈是上下文。)
上下文窗口里,到底装了什么?
很多人以为"上下文"就是聊天记录。其实,一个 Agent 运行时,上下文窗口里塞满了各种东西:
| 内容类型 | 说明 | 举例 |
|---|---|---|
| System Prompt | Agent 的身份、规则、行为约束 | “你是一个客服助手,不得讨论竞品” |
| 对话历史 | 用户和 AI 的来回 | 前 N 轮问答 |
| 检索结果 | RAG 拉回来的文档片段 | 产品手册第 3-5 页 |
| 工具调用记录 | 调用了什么工具、返回了什么 | 查了数据库,结果是 xxx |
| 记忆注入 | 从长期记忆里提取的相关历史 | 这个用户上次说他是 VIP |
| 用户当前输入 | 这一轮的问题 | “我的订单在哪里?” |
这些东西加在一起,很容易就把上下文窗口撑满。
而模型处理上下文的能力,跟"塞了多少有用信息"直接相关。
三、为什么现在特别重要?选型前先搞清楚这件事
你品你细品——同样是 Claude 3.6 Sonnet,为什么有人用得飞起,有人用得一塌糊涂?
差别就在 Context 的质量。
3.1 Agent 把 Context 问题放大了 10 倍
单轮问答时代,Context Engineering 还不紧迫——一个问题进去,一个答案出来,上下文本来就简单。
但 Agent 不一样:
- • 它要多轮对话,历史越来越长
- • 它要调用工具,工具返回值也要塞进去
- • 它要自主规划,中间的推理步骤也占位置
- • 它可能要并行处理多个任务,上下文更复杂
不管好 Context,Agent 跑几步就"失忆",跑更多步就"人格分裂"。
3.2 三种典型失败模式
我总结了工程实践中最常见的三种 Context 翻车场景:
| 失败模式 | 表现 | 根本原因 |
|---|---|---|
| 上下文溢出 | Agent 突然忘记了早期的关键指令 | 对话太长,早期内容被"挤出"窗口 |
| 上下文污染 | Agent 被不相关的工具输出或检索结果带偏 | 往上下文里塞了太多低质量信息 |
| 上下文割裂 | Agent 无法把多轮信息联系起来 | 缺乏有效的记忆管理和信息压缩 |
3.3 Context Engineering 的四个核心维度
说白了,Context Engineering 就是回答四个问题:
1. 放什么进去? → 内容选择(相关性过滤)2. 怎么组织? → 结构设计(格式、顺序、标记)3. 放多少? → 预算管理(Token 分配)4. 什么时候更新? → 动态维护(压缩、遗忘、注入)决策流程动图
四、实战:五个让 Agent 不再失忆的 Context 管理技巧
好,原理讲完,进入实战环节。
下面这五个技巧,是我认为性价比最高的 Context Engineering 手段。代码用 Python + LangChain / 原生 API,你可以直接跑。
技巧一:结构化 System Prompt,用 XML 标签分区
很多人的 System Prompt 是一大坨纯文本。模型理解起来其实挺费劲的——它得自己猜哪段是规则、哪段是背景、哪段是约束。
给它分好区,效果立竿见影。
# -----------------------------------------------# 🔧 【按你的环境修改这里】# -----------------------------------------------# 把 YOUR_API_KEY 换成你的 Anthropic API Key# 在 https://console.anthropic.com 获取API_KEY = "YOUR_ANTHROPIC_API_KEY"# -----------------------------------------------import anthropicclient = anthropic.Anthropic(api_key=API_KEY)# ✅ 用 XML 标签结构化 System Prompt# 为什么用 XML?Claude 的训练数据里大量使用 XML,识别效果最好SYSTEM_PROMPT = """<role>你是一个专业的 RAG 技术顾问,帮助开发者解决检索增强生成相关问题。</role><rules>- 只回答 RAG、向量数据库、LLM 应用相关的技术问题- 代码示例必须可以直接运行,不允许使用占位符- 如果不确定,明确说"我不确定",不要编造答案</rules><context>当前用户是一名有 Python 基础的 AI 应用开发工程师,正在学习 RAG 技术。</context><output_format>回答分三部分:核心答案(1-2句)→ 原理解释 → 代码示例</output_format>"""response = client.messages.create( model="claude-sonnet-4-6", max_tokens=1024, system=SYSTEM_PROMPT, # 结构化的 System Prompt messages=[{"role": "user", "content": "FAISS 和 Milvus 该选哪个?"}])print(response.content[0].text) ``````plaintext # 运行结果示例核心答案:本地开发用 FAISS,生产环境用 Milvus。原理解释:FAISS 是内存索引库,轻量快速但无法持久化和水平扩展;Milvus 是完整的向量数据库,支持分布式部署、数据持久化和实时更新...技巧二:对话历史压缩,防止上下文溢出
Agent 跑多轮之后,历史记录越来越长,Token 消耗爆炸。
解法是:定期把历史总结成摘要,替换掉原始记录。
from langchain_anthropic import ChatAnthropicfrom langchain_core.messages import HumanMessage, AIMessage, SystemMessage# -----------------------------------------------# 🔧 【按你的环境修改这里】# -----------------------------------------------# Anthropic API KeyAPI_KEY = "YOUR_ANTHROPIC_API_KEY"# 触发压缩的消息轮数阈值,超过这个就压缩COMPRESS_THRESHOLD = 10# -----------------------------------------------llm = ChatAnthropic(model="claude-sonnet-4-6", api_key=API_KEY)def compress_history(messages: list, llm) -> list: """ 把历史消息压缩成摘要,保留最近 2 轮原始对话。 为什么保留最近 2 轮?保证模型能看到最新的上下文, 同时用摘要覆盖早期对话,大幅减少 Token 消耗。 """ if len(messages) <= COMPRESS_THRESHOLD: return messages # 还没到阈值,不用压缩 # 把早期消息(除最近 4 条)发给模型,让它总结 old_messages = messages[:-4] recent_messages = messages[-4:] # 保留最近 2 轮(4 条消息) # 构造压缩请求 summary_prompt = f"""请将以下对话历史压缩成一段简洁的摘要(200字以内),保留所有关键信息、决策和结论:{chr(10).join([f'{m.type}: {m.content}' for m in old_messages])}""" summary_response = llm.invoke([HumanMessage(content=summary_prompt)]) # 用摘要替换早期消息,插回对话历史 compressed = [ SystemMessage(content=f"[对话历史摘要]\n{summary_response.content}") ] + recent_messages print(f"✅ 压缩完成:{len(messages)} 条 → {len(compressed)} 条") return compressed# 模拟一个多轮对话conversation_history = []questions = [ "RAG 是什么?", "向量数据库怎么选?", "FAISS 怎么安装?", "chunk_size 设多少合适?", "怎么评估 RAG 效果?",]for question in questions: conversation_history.append(HumanMessage(content=question)) # 每次对话前检查是否需要压缩 conversation_history = compress_history(conversation_history, llm) response = llm.invoke(conversation_history) conversation_history.append(AIMessage(content=response.content)) print(f"Q: {question}") print(f"A: {response.content[:100]}...\n") ``````plaintext # 运行结果Q: RAG 是什么?A: RAG(检索增强生成)是一种将外部知识库与大语言模型结合的技术框架...Q: 向量数据库怎么选?A: 根据规模选择:本地开发用 FAISS,中等规模用 Chroma,生产环境用 Milvus...✅ 压缩完成:12 条 → 5 条 ← 触发压缩,历史从 12 条压到 5 条Q: 怎么评估 RAG 效果?A: 推荐用 RAGAS 框架,核心指标包括 faithfulness、answer_relevancy...技巧三:动态记忆注入,让 Agent 记住"重要的事"
不是所有信息都值得永远留在上下文里。
重要的是:在需要的时候,把相关的记忆注入进来。
# -----------------------------------------------# 🔧 【按你的环境修改这里】# -----------------------------------------------# 向量库保存路径,改成你本地的目录MEMORY_STORE_PATH = "./agent_memory"# 嵌入模型,不用改,用免费的本地模型EMBED_MODEL = "BAAI/bge-small-zh-v1.5"# 检索记忆时返回最相关的 K 条MEMORY_TOP_K = 3# -----------------------------------------------from langchain_community.vectorstores import FAISSfrom langchain_huggingface import HuggingFaceEmbeddingsfrom langchain_core.documents import Documentembeddings = HuggingFaceEmbeddings(model_name=EMBED_MODEL)def save_memory(content: str, metadata: dict = None): """把重要信息存进记忆向量库""" doc = Document(page_content=content, metadata=metadata or {}) try: # 已有记忆库则追加 store = FAISS.load_local( MEMORY_STORE_PATH, embeddings, allow_dangerous_deserialization=True # 本地文件,安全 ) store.add_documents([doc]) except Exception: # 第一次运行,创建新的记忆库 store = FAISS.from_documents([doc], embeddings) store.save_local(MEMORY_STORE_PATH) print(f"✅ 记忆已保存:{content[:50]}...")def retrieve_memory(query: str) -> str: """根据当前问题,检索最相关的历史记忆""" try: store = FAISS.load_local( MEMORY_STORE_PATH, embeddings, allow_dangerous_deserialization=True ) docs = store.similarity_search(query, k=MEMORY_TOP_K) if not docs: return "" # 把检索到的记忆拼成字符串,注入到 System Prompt memories = "\n".join([f"- {d.page_content}" for d in docs]) return f"<relevant_memory>\n{memories}\n</relevant_memory>" except Exception: return "" # 没有记忆库时静默返回空# 使用示例:保存用户偏好save_memory("用户偏好使用 FAISS 而不是 Milvus,因为他的项目是单机部署")save_memory("用户的技术栈:Python 3.11 + LangChain 0.3 + FastAPI")save_memory("用户正在做一个内部知识库 RAG 系统,文档量约 1000 篇")# 下次对话时,动态注入相关记忆query = "向量库应该用哪个?"memory_context = retrieve_memory(query) # 自动检索相关记忆print(f"注入的记忆:\n{memory_context}") ``````plaintext # 运行结果✅ 记忆已保存:用户偏好使用 FAISS 而不是 Milvus,因为他的项目是单...✅ 记忆已保存:用户的技术栈:Python 3.11 + LangChain 0.3 + FastAPI✅ 记忆已保存:用户正在做一个内部知识库 RAG 系统,文档量约 1000 篇注入的记忆:<relevant_memory>- 用户偏好使用 FAISS 而不是 Milvus,因为他的项目是单机部署- 用户正在做一个内部知识库 RAG 系统,文档量约 1000 篇</relevant_memory>技巧四:工具输出格式化,别把"垃圾"喂给模型
Agent 调用工具之后,返回值可能又长又乱——SQL 查询返回了几百行数据、API 返回了一大堆嵌套 JSON。
直接塞进上下文,模型会被"噎住"。
现在想想,当时我们组有个系统,工具返回值直接原样传给模型——一次工具调用产生 3000 个 Token 的返回值,调三次工具上下文就撑爆了。踩过才知道这个坑有多深。
正确做法:工具结果先提炼再注入。
import jsonfrom typing import Anydef format_tool_output(tool_name: str, raw_output: Any, max_tokens: int = 500) -> str: """ 把工具返回值格式化成上下文友好的格式。 核心思路:只保留模型决策所需的信息,其余截断或摘要。 """ if tool_name == "database_query": # 数据库查询结果:只保留前 N 行,附上总行数 rows = raw_output.get("rows", []) total = raw_output.get("total_count", len(rows)) preview = rows[:5] # 只给模型看前 5 行 return ( f"<tool_result name='database_query'>\n" f"查询成功,共 {total} 条结果,展示前 {len(preview)} 条:\n" f"{json.dumps(preview, ensure_ascii=False, indent=2)}\n" f"{'(已截断,仅展示前5条)' if total > 5 else ''}\n" f"</tool_result>" ) elif tool_name == "web_search": # 网页搜索结果:每条结果只保留标题 + 摘要,去掉正文 results = raw_output.get("results", []) formatted = [] for i, r in enumerate(results[:3]): # 最多用前 3 条结果 formatted.append(f"{i+1}. 【{r['title']}】\n {r['snippet']}") return ( f"<tool_result name='web_search'>\n" + "\n".join(formatted) + "\n</tool_result>" ) else: # 其他工具:把结果转成字符串,硬截断 result_str = str(raw_output) if len(result_str) > max_tokens * 4: # 粗略估算 Token 数 result_str = result_str[:max_tokens * 4] + "\n...[已截断]" return f"<tool_result name='{tool_name}'>\n{result_str}\n</tool_result>"# 使用示例raw_db_result = { "rows": [{"id": i, "content": f"文档{i}的内容..."} for i in range(100)], "total_count": 100}formatted = format_tool_output("database_query", raw_db_result)print(formatted)print(f"\n原始长度:{len(str(raw_db_result))} 字符")print(f"格式化后:{len(formatted)} 字符") ``````plaintext # 运行结果<tool_result name='database_query'>查询成功,共 100 条结果,展示前 5 条:[ {"id": 0, "content": "文档0的内容..."}, {"id": 1, "content": "文档1的内容..."}, ...](已截断,仅展示前5条)</tool_result>原始长度:2847 字符格式化后:312 字符 ← 压缩了 89%,且保留了所有关键信息技巧五:上下文预算管理,给不同内容分配 Token 配额
这是稍微进阶一点的技巧。说白了,就是事先规划好上下文窗口里各部分的 Token 占比,防止某一部分无限膨胀把其他部分挤掉。
# -----------------------------------------------# 🔧 【按你的环境修改这里】# -----------------------------------------------# 模型的总上下文窗口大小(Token 数)# claude-sonnet-4-6 最大 200k,这里保守设 32k 用于实验TOTAL_CONTEXT_BUDGET = 32000# -----------------------------------------------# 上下文预算分配方案(比例之和为 1.0)CONTEXT_BUDGET = { "system_prompt": 0.10, # 10% 给系统提示词(约 3200 tokens) "memory": 0.15, # 15% 给长期记忆注入(约 4800 tokens) "retrieved_docs": 0.35, # 35% 给 RAG 检索结果(约 11200 tokens,最大头) "tool_outputs": 0.20, # 20% 给工具调用返回值(约 6400 tokens) "chat_history": 0.15, # 15% 给对话历史(约 4800 tokens) "user_input": 0.05, # 5% 给用户当前输入(约 1600 tokens)}def check_budget(content: str, budget_key: str) -> tuple[bool, int, int]: """ 检查某部分内容是否超出预算。 返回:(是否超标, 实际token数估算, 预算token数) """ # 用字符数 / 4 粗略估算 Token 数(英文约 4字符/token,中文约 2字符/token) estimated_tokens = len(content) // 3 budget_tokens = int(TOTAL_CONTEXT_BUDGET * CONTEXT_BUDGET[budget_key]) over_budget = estimated_tokens > budget_tokens if over_budget: print(f"⚠️ [{budget_key}] 超出预算!" f"估算 {estimated_tokens} tokens,预算 {budget_tokens} tokens") return over_budget, estimated_tokens, budget_tokensdef truncate_to_budget(content: str, budget_key: str) -> str: """把内容截断到预算以内""" _, estimated, budget = check_budget(content, budget_key) if estimated <= budget: return content # 按预算比例截断(token * 3 还原成大约的字符数) max_chars = budget * 3 return content[:max_chars] + f"\n...[已截断,超出 {budget_key} 预算]"# 使用示例retrieved_docs = "这是从向量库里检索出来的超长文档内容..." * 200 # 模拟超长检索结果truncated = truncate_to_budget(retrieved_docs, "retrieved_docs")print(f"原始:{len(retrieved_docs)} 字符 → 截断后:{len(truncated)} 字符") ``````plaintext # 运行结果⚠️ [retrieved_docs] 超出预算!估算 14000 tokens,预算 11200 tokens原始:42000 字符 → 截断后:33605 字符五、总结:Context Engineering 的三条铁律
说到这里,核心就一句话:
上下文的质量,决定了 Agent 的上限。
三条铁律:
- • ✅放什么:宁缺毋滥,只放跟当前任务直接相关的内容
- • ✅怎么放:结构化优先,用 XML 标签告诉模型每段是什么
- • ✅管多少:预算意识,给每部分分配合理的 Token 配额
技术选型快速参考
| 你的场景 | 推荐方案 |
|---|---|
| 单轮问答,上下文简单 | 不用专门做 CE,把 Prompt 写清楚就够 |
| 多轮对话 Agent | 对话历史压缩 + 记忆注入(技巧二 + 三) |
| 调用多个外部工具 | 工具输出格式化(技巧四) |
| 复杂 RAG 系统 | 上下文预算管理 + 结构化 System Prompt(技巧一 + 五) |
| 生产级 Agent | 以上全部,配合 Prompt Caching 降成本 |
学AI大模型的正确顺序,千万不要搞错了
🤔2026年AI风口已来!各行各业的AI渗透肉眼可见,超多公司要么转型做AI相关产品,要么高薪挖AI技术人才,机遇直接摆在眼前!
有往AI方向发展,或者本身有后端编程基础的朋友,直接冲AI大模型应用开发转岗超合适!
就算暂时不打算转岗,了解大模型、RAG、Prompt、Agent这些热门概念,能上手做简单项目,也绝对是求职加分王🔋
📝给大家整理了超全最新的AI大模型应用开发学习清单和资料,手把手帮你快速入门!👇👇
学习路线:
✅大模型基础认知—大模型核心原理、发展历程、主流模型(GPT、文心一言等)特点解析
✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑
✅开发基础能力—Python进阶、API接口调用、大模型开发框架(LangChain等)实操
✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用
✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代
✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经
以上6大模块,看似清晰好上手,实则每个部分都有扎实的核心内容需要吃透!
我把大模型的学习全流程已经整理📚好了!抓住AI时代风口,轻松解锁职业新可能,希望大家都能把握机遇,实现薪资/职业跃迁~
