引言
提示工程(Prompt Engineering)已成为现代软件开发中杠杆率最高的技能之一。你提供给智能体(Agent)的提示(Prompt),塑造了它的规划方式、工具使用方式,并决定了它是构建还是破坏你的工作流程。细微的改动——比如增加一行上下文说明、澄清一个约束条件、调整指令顺序——往往能在准确性和可靠性方面带来超乎想象的提升。本文提炼了我们在 Augment Code 公司经过实战检验的策略,用于构建行为如同训练有素的团队成员、而非产生幻觉的“氛围编码工具”(hallucinating vibe coding tools)的自主智能体。
文中的示例侧重于编码智能体,但所述技术普遍适用。
什么是提示工程?
智能体的提示包含所有作为输入提供给模型的内容。这包括多种组件:
- 系统提示(System Prompt)
- 工具定义(Tool Definitions)
- 工具输出(Tool Outputs)
- 用户指令(User Instructions)
- 模型自身在先前轮次中的输出(The model’s own outputs from previous turns)
提示工程是一门艺术,旨在通过提供更好的提示来提升模型在特定任务上的表现。提示的所有部分都可能通过提示工程得到改进。例如:
- 系统提示可以包含通用指令,引导模型采用不同的响应风格或自主级别。
- 工具定义可以向模型解释在何种情况下应使用或不应使用某个工具。
- 工具输出可以告知模型错误情况。
- 用户指令可以在呈现给模型之前进行重写(提示增强,Prompt Enhancement)。
- 先前的模型输出可以被压缩或截断以节省 token,从而使更长的对话历史能放入上下文窗口。截断方式对质量至关重要。
如何理解模型
模型具有(人工)智能。提示模型更接近于与人交谈,而非编程计算机。模型构建的世界观完全基于提示中的内容。这种世界观越完整、越一致,模型的结果就越好。
模型向我们呈现一个自然语言界面,这不同于开发者使用的编程语言界面。将语言模型(LM)界面视为一个独立但真实的抽象层是很有用的。这个界面可用于呈现理想路径的结果,也可用于发出错误警报、通知变更等——本质上就是与模型进行沟通。
示例:
如果模型错误地调用了工具,请不要在你的智能体代码中抛出异常。相反,返回一个工具结果,解释错误所在:
工具调用缺少必需参数 xyz
。模型将恢复并重试。
如何评估提示
通常很难自动评估提示,除非目标是让模型执行非常具体的任务。尝试构思能多方位测试提示的场景,并努力找出提示更改可能导致性能退步(regressions)的示例。关于这些评估原则的实际应用,可参考这些相同的提示工程技术如何助力 Augment Code 在 SWE-bench 上获得开源项目第一名的具体案例。
提示工程技巧
遵循这些技巧,你将解锁通用人工智能(AGI)。
- 优先关注上下文
提示工程中最重要的因素是为模型提供尽可能最佳的上下文:即用户提供的信息(而非我们提供的提示文本)。这是模型执行任务的主要信号依据。
当前模型擅长在大型提示中找出相关的有用上下文信息,因此当有疑问时,倾向于提供更多信息,如果这能增加上下文包含有用相关信息的可能性。
关于一个提示应问的第一个问题是——它是否包含所有相关信息?可能性有多大?回答这个问题并非总是易事。示例:在为模型提供长命令输出而需进行截断时,截断方法很重要。通常,截断长文本涉及截断后缀。然而,对于命令输出,有用的信息更可能出现在前缀和后缀,而非中间部分。例如,崩溃的堆栈跟踪通常出现在后缀中。因此,为了最大化模型获得最相关上下文的可能性,截断命令输出的中间部分比截断后缀更好。 - 呈现完整的世界图景
通过解释模型所处的运行环境,并提供可能有助于其良好表现的细节,帮助模型进入正确状态。例如,如果你希望模型扮演软件开发者角色,就在系统提示中告知它。向其解释它可以访问哪些资源,以及应如何使用它们。示例:在 Augment 智能体开发的早期,系统提示中引入了以下两行,显著提升了其性能:你是一名 AI 助手,可以访问开发者的代码库。
你可以使用提供的工具读取和写入代码库。
- 保持提示组件间的一致性
确保提示的所有组件(系统提示、工具定义等)以及底层工具定义保持一致。示例:- 系统提示包含一行:
当前目录是 $CWD
execute_command
工具(允许智能体执行 shell 命令)包含一个可选的cwd
参数。一致性意味着该参数的默认值应为$CWD
。这可以在工具定义中指明。如果没有指明,模型很可能会默认如此。read_file
工具接受一个path
参数(要读取的文件路径)。如果提供的是相对路径,应解释为相对于$CWD
。
请求长度为 N 的输出,但因...原因,现返回长度为 K 的输出。
示例:如果提示包含可能在会话期间改变的状态(例如当前时间),不要将其包含在系统提示或工具定义中。
相反,在下一个用户消息中告知模型该变更。这保持了提示的内部一致性:模型可以看到每个轮次的状态是什么。 - 系统提示包含一行:
- 使模型与用户视角对齐
考虑用户的视角,并尝试使模型与该视角对齐。示例: 当用户在集成开发环境(IDE)中工作时,可以向模型呈现 IDE 状态的详细视图,聚焦于用户最可能关心或在其指令中引用的元素。有助于对齐模型的潜在信息示例:- 用户的当前时间和时区
- 用户的当前位置
- 用户的活动历史
用户在 IDE 中工作。当前 IDE 状态:
文件 foo.py 已打开。
IDE 类型为 VSCode。
描述 IDE 状态的更详细系统提示示例:用户在 IDE 中工作。当前 IDE 状态:
IDE 类型为 VSCode。
当前打开的文件是 foo.py。
屏幕上可见第 134 行至第 179 行。
以下是当前可见文本,光标位置由 <CURSOR> 标示:
python134 def bar(): 135 print(“hell<CURSOR>o”) … 179 # TODO 实现此功能没有选中的文本。
有 14 个打开的标签页。按最近访问顺序排列如下:
foo.py
bar.py ...
xyz.py
💡注意: 这并非暗示其中一个提示必然优于另一个。详细提示的潜在缺点是模型可能开始过度关注 IDE 状态,而这并不总是用户意图的最佳信号。 - 力求详尽
详尽的提示对模型有益。无需担心提示长度。当前的上下文长度很长且会持续增加:通过编写更长的提示,你无法显著影响提示的 token 预算。一个成功且详尽的提示示例,用于教导模型如何使用 Graphite(一种版本控制工具):## 使用 Graphite 进行版本控制
我们在 git 之上使用 Graphite 进行版本控制。Graphite 帮助管理 git 分支和 PR(Pull Request)。
Graphite 维护 PR 栈(stacks of PRs):对某个 PR 的更改会自动触发栈中更高层 PR 的变基(rebase),
节省大量手动操作。以下各节描述了如何使用 Graphite 和 GitHub 执行常见的版本控制工作流。
如果用户要求你执行此类工作流,请遵循以下指南。
### 禁止操作
不要使用
git commit、
git pull或
git push。这些命令都被以下以
gt开头的 Graphite 命令所取代。
### 创建 PR(及分支)
要创建 PR,请执行以下操作:
- 使用
git status查看哪些文件被更改,哪些是新增文件
- 使用
git add暂存(stage)相关文件
- 使用
gt create USERNAME-BRANCHNAME -m PRDESCRIPTION创建分支,其中:
USERNAME可获取(参见其他说明)
BRANCHNAME是你想出的一个好分支名
PRDESCRIPTION是你想出的一个好 PR 描述
- 这可能会因预提交(pre-commit)问题而失败。有时预提交会自行修复问题。检查
git status看是否有文件被修改。
如果有,用
git add添加它们。如果没有,请自行修复问题并用
git add添加。然后重复
gt create命令尝试再次创建 PR。
- 运行
gt submit在 GitHub 上创建 PR(如果只是创建分支,则跳过此步骤)。
- 如果
gh(GitHub CLI) 可用,请使用它设置 PR 描述。
注意:运行
gt create前别忘了添加文件,否则你会卡住!
### 更新 PR
要更新 PR,请执行以下操作:
- 使用
git status查看哪些文件被更改,哪些是新增文件
- 使用
git add暂存相关文件
- 使用
gt modify提交更改(无需提供消息)
- 这可能会因预提交问题而失败。处理方式同上(创建 PR 步骤)。
- 使用
gt submit推送更改
- 如果你还需要更新 PR 描述,请使用
gh(如果未安装,请告知用户但不要强求更新 PR 描述)
### 从主分支(main)拉取更改
要使本地仓库与主分支同步,请执行以下操作:
- 使用
git status确保工作目录是干净的
- 使用
gt sync拉取更改并进行变基
- 遵循指令。如果出现冲突,询问用户是否希望解决。如果是,则遵循
gt sync显示的指令。
### 其他 Graphite 命令
要查找其他命令,请运行
gt –help。
- 避免过度拟合特定示例
模型是强大的模式匹配器,会紧紧抓住提示中的细节。提供具体操作示例是一把双刃剑:它是引导模型走向正确方向的简单方法,但也存在模型过度拟合这些示例而在其他情况下性能下降的风险。务必进行实验,并包含可能暴露过度拟合问题的示例。
相比之下,告诉模型不要做什么是安全的(尽管并非总是有效)。 - 考虑工具调用的局限性
工具调用在多个方面受到限制:- 如果模型在训练中接触过类似工具,或者指令与工具之间的联系清晰,模型通常能选择正确的工具。但在许多情况下,即使有最好的提示,它们也可能无法选择正确的工具。
- 如果提供多个功能相似的工具,不能期望模型在任何给定情况下都能选择正确的工具。例如,当面对一个简单工具和一个复杂工具都能完成类似任务时,Claude 通常会选择简单工具。
- 模型经常以错误的方式调用工具,违反工具定义中的契约:参数类型可能错误、参数范围可能错误、可能缺少必需参数等。最佳实践是验证输入,并在失败时返回一个解释错误的工具输出。模型通常能恢复。
edit_file
工具来编辑文件的某个区域。
给模型一个剪贴板工具,模型可以剪切、复制和粘贴大量代码。告诉模型在移动大量代码时使用此工具。
指示模型将类Foo
从foo.py
移动到bar.py
。Sonnet 3.5 通常仍会选择使用edit_file
。 - 威胁和激发同理心有时确实奏效
告诉模型诸如“必须正确执行此操作,否则你将面临财务损失”之类的话,有时确实有助于提高性能。友好地请求或“大声呵斥”模型则很少有效。 - 注意提示缓存
尽可能构建你的提示,使其能在会话期间被追加内容,以避免提示缓存失效(invalidate)。示例:如果提示包含可能在会话期间改变的状态(例如当前时间),不要将其包含在系统提示或工具定义中,因为一旦它们改变,大部分提示缓存将失效。
相反,在下一个用户消息中告知模型该变更。 - 模型更关注提示开头或(尤其是)结尾的信息
模型对指令的关注程度似乎是:用户消息 → 输入开头 → 中间某处。如果某些内容很重要,考虑将其添加到用户消息中。(这是一个快照,优先级可能会随着模型训练的演变而改变。) - 警惕提示瓶颈
通过直接提示所能实现的提升存在极限。提示工程会进入收益递减区域,需要引入其他技术。
结论
掌握提示工程,与其说是技巧,不如说是严谨的沟通之道:为智能体提供完整、一致的上下文;像对待不可信的同事一样验证其行为;并通过经验不断迭代。当你将提示视为代码库的一部分——进行版本控制、审查和测试时——你就能解锁那些能放大你影响力、而非倍增你烦恼的智能体。