上下文工程(Context Engineering)正在成为构建和使用现代AI智能体时绕不开的核心概念。你可能听过一种说法:很多AI系统不过是“包了一层Prompt的大语言模型(LLM)”。坦白说,在智能体变得复杂、运行周期拉长、对工具的依赖越来越强之后,这种观点就站不住脚了。你会发现,上下文从哪来、为什么它会不断膨胀、以及糟糕的上下文管理如何导致性能下降、成本飙升、幻觉频出和行为失控——这些才是真正决定智能体成败的关键。
这篇文章的目标很直接:帮你建立一个关于上下文工程的清晰心智模型,并展示它在实践中怎么落地。我们会先聊上下文工程如何从提示词工程进化而来,然后用Claude Code作为具体案例,看看现代智能体到底怎么编写、选择、压缩和隔离上下文。后半部分我们会深入系统提示词,分析它为什么依然重要,以及怎样才能设计得高效。
为了保持阅读节奏,先列一下这篇会覆盖的内容:
- 上下文工程导论
- Claude Code及其上下文工程理念
- 系统提示词及其重要性
需要说明的是,本文讨论基于公开信息(包括Anthropic工程博客及社区分析),不代表Anthropic官方立场。
上下文工程导论
如果你一直在用AI智能体——尤其是Cursor和Claude Code这类编码智能体,或者你甚至自己为公司或个人开发过——那你大概率已经意识到:归根到底,这一切就是一个被扔给LLM的Prompt,外加围绕它的海量工程工作。把Cursor和Claude Code叫成“套在LLM外面的一层壳”,并不是全无道理。但问题在于,想把这层壳做得真正优秀,需要极深的知识积累和大量工程投入。业内常用另一个术语来描述这套外围系统:agent harness(智能体运行框架)。这个harness是围绕LLM的编排层,负责管理工具调用、控制智能体循环、处理错误、实施护栏,以及——最关键的一点——决定每一步到底把哪些上下文发给模型。
实践中,绝大多数真正的工程工作并不发生在LLM调用本身内部,而发生在它的外围。模型调用本身通常很直接。真正决定一个智能体能否可靠工作的,是外围系统如何管理状态、工具、记忆和上下文。因为LLM的调用永远都伴随着上下文,而这些上下文来自多种来源和持续进行的过程。举个例子,上下文可能来自应用的开发者、来自用户、来自用户之前的交互、工具调用结果或其他外部数据。每天都会有新的上下文来源被加进来,总量也在不断增长。把正确且相关的上下文发送给LLM,远没有早期想象的那么简单。
从提示词工程到上下文工程
早期我们曾以为提示词工程就够了。写一些漂亮的Prompt,就能修复问题、得到想要的结果。然而问题在于:Prompt是静态的,上下文却是极其动态的。如果上下文是动态的,那么构造正确上下文这事,本身就需要一个动态系统来完成。它已经不再是“写一个静态Prompt”这么简单了。这正是上下文工程诞生的原因——它是提示词工程的自然演进,但本身是一个更深层的概念。
为什么上下文对智能体如此重要
老话常说:garbage in, garbage out(垃圾进,垃圾出)。很多智能体系统表现不如预期的最常见原因之一,就是它们根本没拿到正确的上下文。LLM并不会读心术,我们必须为它提供正确的信息。而且,这里说的并不总是“信息”或“数据”——有时候,我们还要给它正确的工具,让它能自己去获取信息、执行操作并完成任务。
图1.1 —— LLM上下文窗口(改编自LangChain讨论的相关概念,www.langchain.com)
LLM的推理能力正在变得越来越强。现在有了工具调用能力,我们可以构建能运行工具、调用它们、获取输出并循环执行直到任务完成的AI智能体。
图1.2 —— LLM工具调用循环(改编自LangChain讨论的相关概念,www.langchain.com)
然而,一旦任务变得复杂且运行时间较长,我们往往会不断积累来自工具调用的反馈结果。这会导致上下文窗口不断膨胀,里面塞满了工具调用结果和中间输出。
图1.3 —— 多轮交互中的上下文增长(改编自LangChain讨论的相关概念,www.langchain.com)
这带来几个问题:上下文窗口可能超出容量上限;成本和延迟上升;智能体性能开始下降。如果不采取任何措施,这种退化几乎是不可避免的。近期有不少讨论在分析长上下文在实践中失效的原因,以及性能退化如何随着无关信息或冲突信息积累而逐步出现(想深入了解可参考Dan Breunig的博客《How Long Contexts》,www.dbreunig.com/2025/06/22/…)。如果让上下文在没有结构、没有筛选、没有控制的情况下持续增长,智能体系统中就会开始出现各种问题:
- 上下文污染:某次工具调用或LLM调用产生的幻觉信息被带入上下文,开始影响后续输出。
- 上下文混淆:不必要的上下文对响应产生了影响,尽管它与当前任务并不相关。
- 上下文冲突:上下文的不同部分彼此矛盾。
接下来要聊一些更好的上下文工程技术。其中一些由应用开发者实现,比如在Claude Code这样的工具中;还有一些则掌握在用户自己手中。作为Claude Code的用户,我们其实对最终发送给LLM的上下文以及得到的回答有很大影响力。这意味着——即便你不是开发者,如果想从AI系统和AI智能体那里获得更好的响应,也需要理解上下文工程。Claude Code这类编码智能体,恰恰是上下文工程技术同时作用在开发者侧和用户侧的典型案例。这也是下一节要讨论的重点。
Claude Code及其上下文工程策略
本节聊聊Claude Code的上下文工程理念,以及它是如何应对上述挑战的。Claude Code通过实现四种方法落地这些策略:
- Write Context:写入上下文——持久化记忆系统
- Select Context:选择上下文——智能的上下文检索与注入
- Compress Context:压缩上下文——高效的上下文表达与摘要
- Isolate Context:隔离上下文——多智能体与作用域化上下文管理
图1.4 —— 上下文工程的分类(改编自LangChain讨论的相关概念,www.langchain.com)
先从第一种开始。
策略1:写入上下文与持久化记忆
第一种策略是写入上下文及其持久化记忆架构。Claude拥有一个多层记忆系统,而Claude Code实现了一个三层记忆层级结构,用来在编码过程中跨会话持久保存上下文。
首先是项目记忆,也就是./CLAUDE.md。这是一种团队共享的上下文,适合存放项目架构、团队规范或任何与特定项目相关的内容。这类记忆通常会被纳入版本控制,对所有团队成员可见。
# Project Context
## Architecture Overview
This is a microservices application using:
- Node.js with Express for API services
- React with TypeScript for frontend
- PostgreSQL with Prisma ORM
- Redis for caching
## Coding Standards
- Use functional components with hooks
- Implement error boundaries for all route components
- Follow RESTful API conventions
- Write unit tests for all business logic
接着是用户记忆,即~/.claude/CLAUDE.md。它存放在用户主目录下的claude目录中。这里保存的是这个用户跨所有项目共享的个人偏好和快捷习惯。它不会被提交到GitHub,是用户私有的。每个用户的内容都可能不同,并且会在所有Claude Code会话之间持续保留。
# Personal Development Preferences
## Code Style
- Always use explicit return types in TypeScript
- Prefer const assertions over type annotations
- Use descriptive variable names, a void abbreviations
## Workflow Shortcuts
- When writing tests, use Jest with React Testing Library
- Always run `npm run lint` before commits
- Prefer composition over inheritance
最后是动态记忆导入。它允许使用@符号及相应语法,从其他记忆文件中导入内容。这与向Claude Code加载普通上下文类似,但区别在于:现在可以拥有专门的记忆文件,把特定信息放进去,然后在记忆文件内部进行引用。
# In any CLAUDE.md file
@path/to/memory/file.md
@./relative/path/context.md
@~/global/user/context.md
还可以通过编写脚本,根据Git分支动态更新上下文,从而定制行为。然后再把这个脚本连接到一个上下文切换hook上,构建更具适应性的工作流。这里有个重要的细节:应避免反复把同样的上下文引用追加到CLAUDE.md中。如果脚本每次执行时都简单地用>>追加,文件就会无限增长并充满重复条目。需要加一个小的保护逻辑:在追加之前,先检查某个上下文引用是否已经存在。下面是一个改进后的脚本版本,可以避免重复导入:
# Script to dynamically update context based on git branch
#!/bin/bash
# context-switcher.sh
# Dynamically load relevant context based on user query
# Safe against duplicate imports
CLAUDE_MD="CLAUDE.md"
# Create CLAUDE.md if it doesn't exist
touch "$CLAUDE_MD"
add_context() {
local context_ref="$1"
grep -qxF "$context_ref" "$CLAUDE_MD" || echo "$context_ref" >> "$CLAUDE_MD"
}
# --- Branch-based context ---
branch=$(git branch --show-current 2>/dev/null)
case $branch in
"feature/auth-"*)
add_context "@/context/auth-system.md"
;;
"feature/payment-"*)
add_context "@/context/payment-flow.md"
;;
"hotfix/"*)
add_context "@/context/production-hotfix.md"
;;
esac
# --- Query-based context ---
user_input="$1"
if [[ -n "$user_input" ]]; then
if [[ $user_input == *"database"* || $user_input == *"migration"* ]]; then
add_context "@/context/database-context.md"
elif [[ $user_input == *"API"* || $user_input == *"endpoint"* ]]; then
add_context "@/context/api-context.md"
elif [[ $user_input == *"frontend"* || $user_input == *"component"* ]]; then
add_context "@/context/frontend-context.md"
fi
fi
关键变化是add_context这个辅助函数。它使用grep -qxF检查某个上下文引用是否已存在于CLAUDE.md中。如果已存在,就不会重复追加。这样一来,脚本就是幂等的,可以安全地在每次Prompt提交时执行。然后把这个脚本连接到一个hook上:
{
"hooks": {
"UserPromptSubmit": {
"command": "./scripts/context-switcher.sh \"$PROMPT\"",
"description": "Dynamically load relevant context based on branch and user query"
}
}
}
通过这套机制,上下文切换变成了动态的,同时又能随着时间推移保持稳定。
策略2:智能上下文检索
第二种策略是智能上下文检索,通常通过动态上下文发现来实现。Claude会自动浏览文件夹,寻找有帮助的上下文文件。如果当前位于某个子文件夹中,它也会从父级文件夹拉取上下文,但当更具体的信息存在时,它会优先使用更具体的内容。它还会优先考虑最近使用过以及频繁访问的信息。这属于应用层面的上下文工程,逻辑由Claude Code的开发者实现。不过,用户也可以通过/memory命令自行添加持久化上下文。例如:
/memory add Always use descriptive variable names
Claude会进一步询问这条记忆应存储在项目级别还是用户级别,然后自动更新对应的CLAUDE.md文件。这样一来,用户就可以在不手工编辑上下文文件的情况下影响Claude未来的行为。根据使用的工具不同,传播给LLM的上下文也会不同。比如,当Claude Code即将编辑一个文件时,它会自动记住和编辑工具相关的信息:先检查现有代码风格、在创建新函数之前先寻找已有函数等。当它要运行一个终端命令时,它会记住另一类上下文:先检查是否有现成的npm script可用,或者先确认文件路径存在。
策略3:压缩上下文
第三种策略是压缩上下文,重点在于更高效地表示上下文。Claude Code内置了上下文压缩命令。/clear会重置当前上下文窗口中的对话历史,但保留底层的项目记忆和用户记忆。当当前对话已经朝无益方向发展,或你想重新开始一次全新交互但又不想丢掉系统已学到的项目知识时,这个命令非常有用。/compact则会把现有对话压缩总结成更短的形式。它不是丢掉所有内容,而是保留关键决策和重要信息,同时舍弃次要细节。压缩后的历史占用更少的上下文窗口,进而降低成本与延迟,并为模型留出更多空间处理新输入。
策略4:通过子智能体进行上下文隔离
第四种策略是上下文隔离。Claude Code使用了子智能体(sub-agents)。每个子智能体都运行在自己独立隔离的上下文窗口中,并不会继承主智能体的完整对话历史。核心思想在于为不同任务创建不同的Claude Code专家版本,每个版本拥有自己聚焦的知识范围。与其让Claude Code一次性带着所有信息去处理所有事情,不如创建专门的子智能体,让它们分别成为某个领域的专家。我们可以有一个主Claude智能体作为管理者来分派任务;一个代码审查智能体,专注于代码质量与安全;一个测试智能体专注于编写和运行测试;一个研究智能体专注于查找信息和最佳实践。示例如下:
// Code review agent – focused context
Task(description: "Code review",
prompt: "Review this PR focusing only on security and performance",
subagent_type: "code-reviewer")
// Research agent – broad context
Task(description: "Research implementation",
prompt: "Find best practices for OAuth2 implementation",
subagent_type: "general-purpose")
这样做的好处在于:如果不进行隔离,Claude Code往往会因为试图一次性处理所有事情并同时考虑所有可能信息而陷入混乱。而通过隔离,每个专家型子智能体都有明确的知识边界,并能在自己的职责范围内高效完成工作。需要说明的是,上述语法只是演示性的简化示例,Claude Code当前并不使用这种精确格式。实际情况下,定义子智能体需要借助带有frontmatter的YAML配置文件,后面的章节会进一步介绍。
系统提示词及其重要性
接下来聊聊系统提示词,以及它们在上下文工程中的重要性。你可能已经在X和LinkedIn上看过成千上万次类似的说法:系统提示词很重要,你应该认真打磨它们、不断迭代它们、把它们做得非常好。说一句“你得有个好的system prompt”,大概已经成了AI工程里最泛泛而谈的建议之一。目前已经有一些公共仓库专门收集知名AI智能体所使用的系统提示词示例,聚焦于Claude Code、Cursor和Devin等编码智能体,也包括其他智能体型系统。引用这些仓库的目的,并不是验证每一个Prompt的真实性,而是为了说明:在实践中,系统提示词往往体量庞大、结构化强、经过精心工程化设计。本节的目标也不是逐条分析每一个Prompt或解释其中每一种技巧——光是系统提示词这个主题就足以写成一本书或单独开一门课程。这里想强调的核心是:系统提示词确实重要。它们持续演化,随着LLM的演进一起进化。为了打磨这些Prompt,背后投入了大量工程努力,这是一个持续迭代的过程。
设计系统提示词的最佳实践
现在来谈一些在整理和设计系统提示词时的最佳实践。你可以把它理解成“给别人指路”。如果我们只说一句“往那边走”,对方一定会困惑,因为他根本不知道该去哪里。但如果我们给他一本50页厚的说明书,把每一个可能的转弯和街道都写进去,信息又会多到让人不堪重负,他依然未必能到达目标地点。我们想要做到的是:表达清楚、足够具体,并且提供恰到好处的信息,让对方能顺利到达目标。最难的地方,正是在于找到那个“刚刚好”的平衡点。
系统提示词的“金发姑娘区”(Goldilocks Zone)
在编写系统提示词时,我们追求的是Anthropic所说的Goldilocks zone(金发姑娘区):既不过于模糊,也不过于细碎,而是恰到好处。你可以把它想象成一条刻度线。最左边是那些过于具体的Prompt,最右边是那些过于模糊的Prompt。我们真正想要的,是正中间那个位置。
图1.5 —— 系统提示词的Goldilocks Zone(来源:Anthropic,《Effective Context Engineering for AI Agents》,www.anthropic.com/engineering…)
我们来拆开看。
过于具体的提示词有什么问题
在最左侧,我们面对的是非常具体的Prompt。核心问题在于:我们把LLM当成了一个确定性的状态机,而不是一个智能体。我们在硬编码逻辑。例如,我们可能会写:如果用户的意图是事故处理,那就追问三个后续问题。可为什么偏偏是三个?如果两个就够了怎么办?如果需要五个呢?还会看到一种现象:穷举式枚举,试图把所有可能的升级处理场景都列出来。这件事实际上不可能做完,而且它会强迫模型沿着预设路径运行,即便这些路径和真实输入并不匹配。同时,这也带来可维护性的噩梦:每出现一个新的边界情况,你都得去改Prompt。到了那个地步,如果一切都已被预先决定好,那我们甚至可能根本不需要一个自主智能体——一个确定性的工作流可能就已经足够。
过于模糊的提示词有什么问题
在光谱的另一端,我们看到了非常模糊的Prompt。核心问题是:它没有为模型提供足够的信号,无法支撑一致性的行为。在这种Prompt里,常常缺乏可执行指导。比如下面这个例子:
问题是,这些“原则”到底是什么?这里还存在一种错误假设:它默认模型与我们共享相同背景。这个Prompt假设模型知道公司是什么、品牌是什么、客户服务规范是什么,但事实并非如此。还会看到边界不清的问题,比如“如有需要,请升级给人工处理”。那到底什么情况下算“有需要”?模型根本无从判断。它也没有为模型提供一个系统性处理问题的框架或结构,导致行为不一致:面对同一个问题,不同次运行可能会采取截然不同的处理方式。归根到底,这类Prompt本质上只是在说一句“做正确的事”,却并没有定义“正确”到底是什么。
一个好的中间态Prompt长什么样
现在来看中间地带。请看下面这个例子:
你可以看到:首先给出了一个清晰的身份和职责边界,立刻建立范围——它做客户支持,不是做市场营销也不是做销售;它处理订单和基础咨询,而不是复杂的业务运营问题。其次,这种Prompt是在赋能模型而不是束缚模型。它并没有规定每一种情形下都必须使用哪个工具,而是定义了一个目标——比如高效且专业地解决问题。这里的启发式思路是:我们信任智能体能够在需要时选择合适的工具。我们给出的也不是一个死板的流程图,而是一个推理框架。例如使用一个四步响应框架:识别核心问题、收集必要上下文、提供清晰解决方案、确认顾客满意度。这样的框架能够适用于各种不同场景,而不是刚性分支逻辑。最后,还明确建立了边界和原则。比如“如果有多个解决方案,就选最简单的那个”——这是一条启发式原则,而不是硬规则,有点类似计算机科学里的贪心策略。
过于具体的Prompt试图替LLM完成思考,一旦现实情况不符合预设脚本,它就会崩溃。过于模糊的Prompt则没有给LLM足够的抓手去开展工作。而居中的Prompt,恰恰利用了现代LLM最擅长的事情:识别模式,并把一般原则应用到具体情境中。它之所以能较好地处理新情况,是因为教给模型的是原则而不是规则。它之所以高效,是因为不浪费字数——每一条指导都能覆盖大量场景。这些原则是被压缩过的,不存在重叠或相互矛盾的指令。
总结
本章介绍了上下文工程,解释了为什么它对构建可靠且可扩展的AI智能体至关重要。我们分析了上下文与静态Prompt的差异、上下文从何而来,以及为什么失控的上下文会导致性能退化、幻觉和行为不一致等问题。以Claude Code为实际案例,探讨了四种核心的上下文工程策略:通过持久化记忆写入上下文、动态选择相关上下文、通过压缩控制上下文规模,以及利用专门化子智能体实现上下文隔离。我们还讨论了系统提示词的作用,并说明高质量的Prompt应当在“过于刚性”和“过于模糊”之间取得平衡。到这里,你应该已经对现代AI智能体如何管理上下文,以及开发者和用户分别如何影响这种行为有了整体认识。下一章会在这一基础上继续深入,开始接触更高级的智能体工作流,并借助HookHub项目探索Claude Code如何协调任务、管理上下文,以及如何超越简单的单步交互来运行。下一章还将进一步考察Claude Code如何与代码库进行交互。
