人工智能智能体的上下文工程:筛选模型可见的内容
为何智能体的性能会随着上下文规模的增加而下降,上下文工程与提示词编写的区别,以及用于决定每个模型调用所接收内容的简单流程。
那些在最初几步表现良好,但随后开始忽视指令、重复工作或依赖过时信息的AI智能体,通常并非存在文本表述问题,而是存在上下文问题:模型接收到了过多信息、错误的信息,或是将正确信息放在了错误的位置。上下文工程是一门旨在精确决定模型在需要推理之前应接收哪些内容的学科,这不仅包括提示词,还包括所有的指令、历史记录、检索到的数据以及工具定义。本指南将解释为何随着智能体能力的提升这一整体信息包愈发重要、你可以控制的四个组成部分、最简的整合流程,以及需要警惕的故障模式。
核心理念:简洁且相关的信息片段
目标绝不是向模型提供所有可能有助于它工作的信息,而应是仅给予那些真正能让其正确作答的有限信息,其余的则不予提供。
用一个人类类比就能说明这一点。如果让新工程师去修复百万行代码库中的错误,只告诉他们“阅读代码并找到问题”,他们会不知所措——在成千上万的文件中找不到那个相关的文件。但如果告诉他们“问题很可能出在支付模块,这三份文件是上周修改过的,这是前人尝试过的方法”,那么解决问题可能只需几分钟。工程师本身并没有变化,变化的只是他们所掌握的信息。
模型的表现也是如此。上下文工程的作用就是提供那三份文件,而非整个代码库。
为何仅靠文字描述已不够用
早期关于如何使用语言模型的建议主要集中在表述方式上:如何措辞指令以及何种表达方式能获得更好的答案。这就是提示工程,对于单个问题而言它依然很重要。
然而,智能体并不会回答单一问题。它会不断循环:读取内容、调用工具、接收结果、选择下一步行动,如此反复,有时甚至数十次。每次迭代都会为模型所处理的资料增添更多内容。最终积累的上下文量会变得如此之大,以至于重要的细节会被忽略,就像那些一整天都在开会的人,最终记不起第一次会议决定了什么一样。
Anthropic将上下文工程视为在每个特定时刻为模型寻找最佳信息组合的问题,而非一次性编写出完美的指令。这体现了两者之间的差异:提示工程关注的是文字本身;而上下文工程则涉及模型响应时存在的所有内容。
提示工程与上下文工程
两者存在重叠,但在范围、典型应用场景及故障模式上有所不同:
- 范围。提示工程仅调整单条指令的表述;而上下文工程则掌控整个输入内容,包括指令、之前的对话记录、检索到的事实以及可用的工具。
快速诊断:如果你的解决方案只是更换文字,那你在做提示工程;如果你的解决方案改变了模型最初接收的信息,那你在做上下文工程。如需一种系统化的方式来判断智能体故障属于哪一层,可参阅按层调试 AI 智能体。
你需要管理的四个组成部分
每个智能体的上下文都是由四个来源构成的,每个来源都有可能出现故障。
指令:工作描述
这就是系统提示,用于说明智能体应该做什么以及如何做。如果指令过于僵化,智能体就无法处理脚本之外的任何情况;如果指令过于宽松,智能体就会随意发挥。应制定足够具体的指导方针来规范行为,而不必试图提前列举所有可能的情况。
信息检索:研究助手
信息检索通过搜索文档、查询数据库或读取文件来获取外部信息。糟糕的检索能力是导致人工智能系统自信地输出错误内容的主要原因。通常情况下,模型并非凭空编造内容,而是得到了错误或无关的信息,并合理地依赖了这些信息。相比修改提示词,优化检索到的内容往往更能提升准确性。
记忆功能:笔记本
记忆功能涉及智能体在单次对话及多次会话之间所保留的信息。如果没有总结和剔除旧信息的机制,记忆功能就无法保持有效性。它会不断积累,直到大部分内容都变成与当前重要信息相冲突的冗余信息。
工具功能:工具箱
工具决定了智能体可以执行的操作,例如网络搜索、代码执行或发送邮件。每个工具的名称和描述也会构成上下文信息。十种相互重叠、几乎相同的工具会像十把无法区分的螺丝刀让新员工困惑一样,让模型也陷入混乱。而那些功能划分明确的较少工具则能让选择变得更简单、成本更低。
最小化上下文构建流程
以下伪代码展示了在一次模型调用中这四个组件是如何协同工作的。辅助函数只是用于替代你所使用的搜索、排序和总结功能的占位符,但其结构反映了实际系统解决该问题的方式。
它的运作分为四个阶段。首先进行广泛检索,允许一定程度的噪声存在,最多返回50个候选结果。其次对这些候选结果进行排序,仅保留排名前五的。第三,将对话历史压缩为不超过500个单元的摘要,而非完整重现。最后有针对性地安排内容顺序,指令放在最前,当前问题放在最后,并根据令牌限制对结果进行裁剪。
def get_context_for_the_model(question, past_conversation, budget):
# Step 1: Cast a wide net — search broadly, don't worry about noise yet
possible_facts = search_everywhere(question, limit=50)
# Step 2: Narrow it down - keep only the genuinely relevant ones
best_facts = keep_most_relevant(possible_facts, top=5)
# Step 3: Summarize old conversation instead of keeping all of it
short_memory = summarize(past_conversation, max_length=500)
# Step 4: Put the most important things first and last, not buried in the middle
final_context = [
job_description, # instructions
short_memory, # memory
*best_facts, # retrieval
question, # what's being asked right now, last
]
return trim_to_fit(final_context, budget)
这个示例中有两个值得借鉴的习惯。
先广泛搜索,再精准筛选。广泛的检索能降低遗漏相关文档的概率;严格的排序则能使最终呈现的上下文保持简洁。只做第一步会让模型信息过载,而只做第二步则可能永远找不到合适的资料。
将重要内容放在边缘。应把最关键的内容置于输入内容的开头或结尾,而非中间。这并非出于风格上的考虑。针对长文本输入的研究反复表明,模型对隐藏在长上下文中的信息的关注度较低,这种现象常被称为“淹没在中间”。这种效应的严重程度因模型而异,因此需通过自身实验进行验证,但无论怎样,调整内容顺序都是一种成本低廉的有效方法。
基于同样的逻辑,还有一些实用的改进方法。事先确定预算在各个组成部分之间的分配方式,这样大量的检索结果就不会悄无声息地挤占指令的显示空间。在筛选时,先去掉最不相关的检索结果,再处理指令或当前问题。同时为每次调用记录最终整合后的上下文;当智能体表现异常时,这些记录通常能说明原因。
若不精心筛选上下文会出什么问题
为求稳妥而包含所有内容
试图加入所有可能相关的信息看似负责任,但实际上往往适得其反。模型需要处理的无关内容越多,就越难以找到那个关键的事实。这就好比在五分钟的决策之前先阅读一份百页的报告。
过时的信息
如果没有任何机制来验证检索到的内容是否仍然准确,模型就会基于数月前就已过时的信息给出看似可靠的答案。更新时间元数据、设置过期规则,或对时效性强的信息源进行重新验证,都能起到帮助作用。
隐藏关键事实
即便检索系统找到了完全正确的事实,若将其置于长段落中间,从统计角度来看被忽略的概率也会增加。事实本身并未改变,只是位置不同,结果却变得更差。
相似选项过多
如果团队中的成员无法确定在特定情况下该使用哪种工具或文档,模型的表现也不会更好。应在相关内容进入上下文之前,整合重复的工具并去重几乎完全相同的文档。
常见问题
上下文工程正在取代提示词工程吗?
不。明确的指令仍是任务的一部分。上下文构建则是围绕这些指令的更宏大的任务:决定模型除了指令本身之外还能看到什么内容。
为何更多信息反而会降低结果质量?
对于模型和人类而言,注意力都是一种有限的资源。模型需要筛选的信息越多,就越有可能忽略那个至关重要的细节,就如同人在长篇文档中难以找到一条重要信息一样。
更大的上下文窗口能解决这个问题吗?
它确实通过提供更多空间有所帮助,但并不能消除根本问题。长输入中的中间信息仍然较难被可靠利用,而且随着输入量的增加,延迟和成本也会上升。即便使用非常大的上下文窗口,筛选哪些信息纳入其中依然十分重要。
关键要点
- 应将模型的输入视为每次调用时专门构建的产物,而非仅用于追加记录的日志。
- 需有意识地管理四个组成部分:指令、检索、内存和工具,它们各自都可能出故障。
- 广泛进行信息检索,高效对结果排序,总结历史记录,并将关键内容置于开头或结尾。
- 当智能体在多步操作后表现下降时,应先检查其之前接收到的输入,再重新编写需执行的指令。
- 更长的处理窗口只能提供更多空间,无法确保系统免于出错;关键仍在于只传递正确的三个文件,而非整个代码库。