什么是上下文工程?
几年前,许多人,包括顶级AI研究人员都声称,提示工程已经过时了。显然,他们大错特错。实际上,提示工程现在比以往任何时候都更加重要,重要到它现在被重新命名为"上下文工程"。是的,又一个时髦的术语,用来描述为LLM有效执行任务而提供必要指令和相关背景信息的重要过程。
关于上下文工程已经有很多讨论(Ankur Goyal、Walden Yan、Tobi Lutke和Andrej Karpathy),但我想分享一些关于这个话题的思考,并为您提供一个具体的分步指南,展示如何在开发AI代理工作流程中运用上下文工程。
虽然我不确定是谁创造了"上下文工程"这个术语,但我们先来看看 Dex Horthy 对上下文工程的简要解释。
我喜欢"上下文工程"这个术语,因为它更全面,能够更好地概括提示工程(包括其他相关任务)中的大部分工作。
关于提示工程是否能成为一项严肃技能的质疑,主要是因为很多人将它与简单的提示(就是您在LLM中输入的简短任务说明)混淆了。在简单提示中,您只是在向系统问一个问题。而在提示工程中,您必须更仔细地思考提示的上下文和结构。也许从一开始就应该叫做上下文工程。
上下文工程是下一个阶段,您需要构建完整的上下文,在很多情况下,这些上下文需要超越简单的提示,采用更严格的方法来获取、增强和优化系统的知识。
从开发者的角度来看,上下文工程涉及一个迭代过程,用于优化您提供给LLM的指令和上下文,以获得期望的结果。这包括建立正式的流程(例如评估管道)来衡量您的策略是否有效。
考虑到AI领域的快速发展,我建议对上下文工程有一个更广泛的定义:设计和优化指令及相关上下文的过程,使LLM和高级AI模型能够有效执行任务。 这不仅涵盖基于文本的LLM,还包括日益普及的多模态模型的上下文。这可以包括所有提示工程工作和相关过程,例如:
- 设计和管理提示链(如果适用)
- 调整指令/系统提示
- 管理提示的动态元素(例如用户输入、日期/时间等)
- 搜索和准备相关知识(即RAG)
- 查询增强
- 工具定义和说明(用于代理系统)
- 准备和优化少样本示例
- 构建输入和输出格式(例如分隔符、JSON模式)
- 短期记忆(即管理状态/历史上下文)和长期记忆(例如从向量存储中检索相关知识)
- 以及许多其他用于优化LLM系统提示以完成所需任务的技术。
换句话说,上下文工程的目标是优化您在LLM上下文窗口中提供的信息。这也意味着要过滤掉噪声信息,这本身就是一门科学,因为它需要系统地衡量LLM的性能。
虽然每个人都在谈论上下文工程,但在这里,我们将通过构建AI代理时上下文工程的具体示例来指导您。
上下文工程实践
让我们看一个具体的示例,展示我为个人使用而构建的多代理研究应用程序所做的一些最新上下文工程工作。完整代理架构如下:
其中工作流程中的搜索规划代理负责根据用户查询生成搜索计划。
系统提示
以下是需要为这个子代理准备的系统提示:
text
You are an expert research planner. Your task is to break down a complex research query (delimited by <user_query></user_query>) into specific search subtasks, each focusing on a different aspect or source type.
The current date and time is: {{ $now.toISO() }}
For each subtask, provide:
1. A unique string ID for the subtask (e.g., 'subtask_1', 'news_update')
2. A specific search query that focuses on one aspect of the main query
3. The source type to search (web, news, academic, specialized)
4. Time period relevance (today, last week, recent, past_year, all_time)
5. Domain focus if applicable (technology, science, health, etc.)
6. Priority level (1-highest to 5-lowest)
All fields (id, query, source_type, time_period, domain_focus, priority) are required for each subtask, except time_period and domain_focus which can be null if not applicable.
Create 2 subtasks that together will provide comprehensive coverage of the topic. Focus on different aspects, perspectives, or sources of information.
Each substask will include the following information:
id: str
query: str
source_type: str # e.g., "web", "news", "academic", "specialized"
time_period: Optional[str] = None # e.g., "today", "last week", "recent", "past_year", "all_time"
domain_focus: Optional[str] = None # e.g., "technology", "science", "health"
priority: int # 1 (highest) to 5 (lowest)
After obtaining the above subtasks information, you will add two extra fields. Those correspond to start_date and end_date. Infer this information given the current date and the time_period selected. start_date and end_date should use the format as in the example below:
"start_date": "2024-06-03T06:00:00.000Z",
"end_date": "2024-06-11T05:59:59.999Z",
这个提示的许多部分都需要仔细考虑我们为规划代理提供的确切上下文,以便它能够有效执行任务。如您所见,这不仅仅是设计简单的提示或指令;这个过程需要实验,为模型提供最佳执行任务的重要背景信息。
让我们将问题分解为有效上下文工程的几个核心组件。
指令
指令是提供给系统的高层次指导,准确说明要做什么。
text
You are an expert research planner. Your task is to break down a complex research query (delimited by <user_query></user_query>) into specific search subtasks, each focusing on a different aspect or source type.
许多初学者甚至经验丰富的AI开发者都会在这里停止。考虑到我分享的完整提示,可以看到我们需要更多的上下文来让系统按需要工作。这就是上下文工程的全部意义;它为系统提供更多关于其任务范围和我们期望的输出的信息。
用户输入
用户输入没有在系统提示中显示,但下面是一个示例:
text
<user_query> What's the latest dev news from OpenAI? </user_query>
注意分隔符的使用,这是更好的提示结构的一部分。这对于避免混淆很重要,明确区分用户输入和我们希望系统生成的内容。有时,我们输入的信息类型与我们希望模型输出的内容相关(例如,查询是输入,子查询是输出)。
结构化输入和输出
除了高层次指令和用户输入外,您可能已经注意到,我在规划代理需要产生的子任务上花费了大量精力。以下是我为规划代理提供的详细说明,用于创建给定用户查询的子任务。
text
For each subtask, provide:
1. A unique string ID for the subtask (e.g., 'subtask_1', 'news_update')
2. A specific search query that focuses on one aspect of the main query
3. The source type to search (web, news, academic, specialized)
4. Time period relevance (today, last week, recent, past_year, all_time)
5. Domain focus if applicable (technology, science, health, etc.)
6. Priority level (1-highest to 5-lowest)
All fields (id, query, source_type, time_period, domain_focus, priority) are required for each subtask, except time_period and domain_focus which can be null if not applicable.
Create 2 subtasks that together will provide comprehensive coverage of the topic. Focus on different aspects, perspectives, or sources of information.
如果您仔细查看上面的说明,我构建了一个规划代理所需信息的列表,并提供了一些提示/示例,以更好地指导数据生成过程。这对于为代理提供关于期望输出的额外上下文至关重要。例如,如果您没有说明您希望优先级级别为1-5的范围,系统可能会使用1-10的范围。同样,这种背景信息很重要!
接下来,让我们谈谈结构化输出。为了从规划代理获得一致的输出,我们还提供了关于我们期望的子任务格式和字段类型的一些上下文。以下是我们作为额外上下文传递给代理的示例。这将为代理提供关于我们期望的输出的提示和线索:
text
Each substask will include the following information:
id: str
query: str
source_type: str # e.g., "web", "news", "academic", "specialized"
time_period: Optional[str] = None # e.g., "today", "last week", "recent", "past_year", "all_time"
domain_focus: Optional[str] = None # e.g., "technology", "science", "health"
priority: int # 1 (highest) to 5 (lowest)
此外,在n8n内部,您还可以使用工具输出解析器,它本质上用于构建最终输出。我使用的选项是提供一个JSON示例,如下所示:
json
{
"subtasks": [
{
"id": "openai_latest_news",
"query": "latest OpenAI announcements and news",
"source_type": "news",
"time_period": "recent",
"domain_focus": "technology",
"priority": 1,
"start_date": "2025-06-03T06:00:00.000Z",
"end_date": "2025-06-11T05:59:59.999Z"
},
{
"id": "openai_official_blog",
"query": "OpenAI official blog recent posts",
"source_type": "web",
"time_period": "recent",
"domain_focus": "technology",
"priority": 2,
"start_date": "2025-06-03T06:00:00.000Z",
"end_date": "2025-06-11T05:59:59.999Z"
},
...
}
然后该工具会自动从这些示例中生成模式,这反过来允许系统解析并生成适当的结构化输出,如下所示:
json
[
{
"action": "parse",
"response": {
"output": {
"subtasks": [
{
"id": "subtask_1",
"query": "OpenAI recent announcements OR news OR updates",
"source_type": "news",
"time_period": "recent",
"domain_focus": "technology",
"priority": 1,
"start_date": "2025-06-24T16:35:26.901Z",
"end_date": "2025-07-01T16:35:26.901Z"
},
{
"id": "subtask_2",
"query": "OpenAI official blog OR press releases",
"source_type": "web",
"time_period": "recent",
"domain_focus": "technology",
"priority": 1.2,
"start_date": "2025-06-24T16:35:26.901Z",
"end_date": "2025-07-01T16:35:26.901Z"
}
]
}
}
}
]
这看起来很复杂,但现在许多工具都提供开箱即用的结构化输出功能,所以您可能不需要自己实现。n8n让上下文工程的这一部分变得轻而易举。这是上下文工程的一个被低估的方面,出于某种原因,我看到许多AI开发者忽略了这一点。希望上下文工程能够让更多人了解这些重要技术。这是一种非常强大的方法,特别是当您的代理产生不一致的输出,需要以特定格式传递到工作流程中的下一个组件时。
工具
我们使用n8n构建代理,所以很容易将当前日期和时间添加到上下文中。您可以这样做:
text
The current date and time is: {{ $now.toISO() }}
这是n8n中一个简单、方便的功能,但通常会将其构建为专用工具,这有助于让事情变得更加动态(即,只有在查询需要时才获取日期和时间)。这就是上下文工程的意义所在。它迫使您,作为构建者,对要传递的上下文以及何时传递给LLM做出具体决策。这很好,因为它消除了应用程序中的假设和不准确性。
日期和时间是系统的重要上下文;否则,当需要了解当前日期和时间的查询时,它往往表现不佳。例如,如果我要求系统搜索上周发生的OpenAI最新发展新闻,它只会猜测日期和时间,这将导致次优的查询和结果,因为网络搜索不准确。当系统有正确的日期和时间时,它可以更好地推断日期范围,这对搜索代理和工具很重要。我将其添加为上下文的一部分,以允许LLM生成日期范围:
text
After obtaining the above subtasks information, you will add two extra fields. Those correspond to start_date and end_date. Infer this information given the current date and the time_period selected. start_date and end_date should use the format as in the example below:
"start_date": "2024-06-03T06:00:00.000Z",
"end_date": "2024-06-11T05:59:59.999Z",
我们专注于架构中的规划代理,所以这里不需要太多工具。唯一有意义的其他工具是检索工具,用于检索给定查询的相关子任务。让我们在下面讨论这个想法。
RAG和记忆
我构建的深度研究应用程序的第一个版本不需要短期记忆,但我们已经构建了一个版本,它会重用不同用户查询的子任务。这对于在工作流程中实现一些加速/优化很有用。如果用户之前使用过类似的查询,这些结果可以存储在向量存储中并进行搜索,以避免为我们已经生成并存在于向量存储中的计划创建新的子查询。请记住,每次调用LLM API时,您都在增加延迟和成本。
这是巧妙的上下文工程,因为它使您的应用程序更加动态、更便宜、更高效。您会看到,上下文工程不仅仅是优化提示;它是为您的目标选择正确的上下文。您还可以在如何维护该向量存储以及如何将这些现有子任务提取到上下文中方面更加创新。创新和新颖的上下文工程是护城河!
状态与历史上下文
我们在深度研究 Agent 的V1中没有展示这一点,但该项目的重要部分是优化结果以生成最终报告。在许多情况下,Agent 系统可能需要修改所有查询、子任务的全部或子集,并可能重新运行从网络搜索API获取的数据。这意味着系统将对问题进行多次尝试,并需要访问之前的状态以及系统的所有历史上下文。
在我们的用例中这意味着什么?在我们的示例中,它可能为代理提供对子任务状态、修订版本(如果有)、工作流程中每个代理的过去结果以及在修订阶段有用的任何其他上下文的访问。对于这种类型的上下文,我们传递的内容将取决于您的优化。这里会发生许多决策。上下文工程并不总是简单的,我认为您可以开始想象这个组件需要多少次迭代。这就是为什么我继续强调评估等其他领域的重要性。如果您不衡量所有这些,您如何知道您的上下文工程工作是否有效?