如何构建你的AI智能体:让AI更出色的11种提示技巧

如何构建你的AI智能体:让AI更出色的11种提示技巧

前言(Intro)

在现代软件开发中,Prompt Engineering(提示工程)已经成为一种极具影响力的技能。你提供给AI智能体的提示(prompt)不仅决定了它的规划方式、使用工具的方式,也决定了它是能顺利构建还是会破坏你的流程。往往只需一些细微改变——比如多加一条上下文、澄清一个约束条件或调整指令顺序——就能带来对准确性和可靠性的大幅提升。本文总结了我们在 Augment Code 打造自治AI智能体时所用的实践技巧,帮助大家构建出像严谨队友一样工作的AI,而不是只会“瞎编乱码”的工具。

文中的示例主要围绕编码型智能体,但这些方法大多具有普适性。

什么是提示工程(Prompt Engineering)?

一个AI智能体的Prompt包括提供给模型的所有输入内容。其中常见的几个组成部分有:

  • 系统提示(System prompt)

  • 工具定义(Tool definitions)

  • 工具输出(Tool outputs)

  • 用户指令(User instructions)

  • 模型在之前对话回合中的输出

提示工程(Prompt Engineering)指的就是,通过给模型提供更好的Prompt,来让它在特定任务上发挥更好表现的艺术。我们可以对提示的所有部分进行改进,比如:

  • 在系统提示中加入通用性指令,以引导模型朝不同的回复风格或自主度前进

  • 在工具定义中向模型解释在什么情况下应该或不应该使用该工具

  • 在工具输出中告知模型错误信息或异常状态

  • 在把用户指令展示给模型前,对其进行重写(提示增强)

  • 对过去模型输出内容进行压缩或截断,从而节省tokens,让更多的对话历史能放进上下文窗口。而截断的方式也会影响最终质量

如何看待模型

模型是(人造的)智能体。给模型下指令更像是和一个人对话,而不是编程。模型对世界的理解只基于提示中包含的内容。你给它的世界信息越完整、越一致,它的表现往往也就越好。

我们看到的是一个自然语言接口,其实是独立于编程语言的。最好把大型语言模型(LM)的接口当作一个真实的抽象层来看待:我们不仅可以在这里获取到“理想化的输出”,也可以在这里提示错误、发送变更提醒等——和模型进行充分的沟通。

举个例子:

  • 如果模型错误地调用了某个工具,不要在智能体的代码中抛出异常,而是返回一个工具的结果,说明错误原因:工具被调用时缺少必须的参数xyz。这样模型就能自行纠正并重新尝试。

如何评估提示

如果目标是让模型执行非常具体的任务,自动化评估提示往往就不再困难。但在大多数场景下,需要通过人为设计一些场景来测试提示在各方面的表现,并检查在某些特定示例中是否会出现退化现象。一个能说明问题的例子是:Augment Code 通过同样的提示工程手段,拿下了 SWE-bench开放源代码排名第一(官方验证),从而可以看到这些评估方法的实际效果。

提示工程技巧

只要遵循以下提示,你就能解锁AGI。

1. 首先专注于上下文

在提示工程中,最重要的一点是为模型提供最优质的上下文信息:也就是用户真正需要给模型的内容,而非我们自行添加的文本。这些上下文信息通常是模型最主要的信号来源。

当前的模型非常擅长从大段内容中提取有用的部分。所以,如果有疑虑,宁愿多给一些信息,只要能保证其中包含了对模型有帮助的、与任务相关的上下文。

在设计提示时,你应该问自己的第一个问题是:“提示中是否包含了所有可能相关的信息?出现的可能性有多大?” 这个问题并不总是好回答。

举例:

  • 当我们想要把很长的命令输出截断后再提供给模型时,截断的方法很重要。通常情况下,我们会截断末尾部分。但对于命令输出来说,前缀和末尾信息往往最关键;比如崩溃时的堆栈信息通常在最后。所以,为了让模型尽可能拿到最有用的信息,可考虑在中间部分进行截断,而把开头和结尾都保留给模型。

2. 为模型提供完整的世界观

要让模型进入合适的“工作状态”,可以在系统提示中描述其所处的环境,以及它所需要知道的任何细节。如果你希望模型表现得像一位软件开发者,那就在系统提示里告诉它,并解释它可以用到哪些资源,以及如何使用它们。

举个例子,以下两行话我们很早就加到了Augment智能体的系统提示中,带来了显著的效果提升:

你是一位AI助手,你可以访问开发者的代码库。
你可以通过提供的工具来读取或修改该代码库中的内容。

3. 在提示的各组件间保持一致

要保证系统提示、工具定义等部分的内容,以及工具自身的定义都彼此保持一致。

举例:

  • 系统提示里写了 当前目录是 $CWD

  • 有个名为 execute_command 的工具,用于在shell中执行命令,其中包含一个可选的 cwd 参数。要想保持一致,最好让这个参数的默认值就是 $CWD,并在工具定义里明确指定。否则模型很可能会自作主张地去假设也是这样。

  • 如果 read_file 工具接受一个文件路径参数,那么当给它传递一个相对路径时,就应该理解为相对于 $CWD 的路径。

💡提示:避免让模型感到“意外”。模型其实很容易被搞糊涂。如果你预期模型会从某个工具调用中得到特定输出,就尽量保证返回的结果与其预期一致;若有偏差,就在工具结果中解释原因。
举例

  • 如果工具定义说输出一定是固定长度,那就要么真的返回固定长度,要么先解释:本来需要长度为N的输出,但由于……所以实际返回了长度为K

  • 如果提示中包含可能在会话中发生变化的状态(比如当前时间),不要把它们写到系统提示或工具定义里。

  • 取而代之,可以在下一条用户消息中告诉模型此状态已发生了哪些变化。这样能够让提示内部的一致性更好:模型在每一回合都能看到之前的状态信息是如何一步步演变的。

4. 让模型与用户视角对齐

要想让模型真正理解用户的需求,可以尝试让模型去“站在用户的角度”思考问题。

举例:当用户在IDE里工作,你可以向模型呈现IDE的具体信息,着重强调用户最关心或最常提及的内容,从而让它与用户需求对齐。

可以用于对齐模型的一些信息包括:

  • 用户当前的时间与时区

  • 用户当前所在位置

  • 用户的历史操作记录

一个简要的系统提示示例

用户在使用一个IDE。当前的IDE状态如下:

文件 foo.py 正在打开中。
IDE 类型为 VSCode。

一个更详细的系统提示示例

用户在使用一个IDE。当前的IDE状态如下:

IDE类型:VSCode

当前打开的文件是 foo.py
可视区域里能看到第134到179行代码
以下是可见的文本,其中<CURSOR>代表光标所在位置:

```python
134  def bar():
135    print("hell<CURSOR>o")
...
179  # TODO implement this
```

目前没有选中的文本。
共有14个标签页打开,列表从最近访问到最早访问为:
foo.py
bar.py ...
xyz.py

💡提示:这并不是说更详细的提示就一定更好。有时过度详细的提示会让模型花太多精力关注IDE状态,而可能忽略了用户真正想做的事。

5. 充分详尽,不要吝啬提示内容

模型通常能从详细的提示中受益。无需特别担心提示过长。现在的上下文窗口已经很大,并且未来只会更大。想通过“缩短提示”来节省token往往得不偿失。

下面是一个非常详尽的提示示例,向模型说明如何使用Graphite做版本控制:

## 使用Graphite进行版本控制

我们在git的基础上使用Graphite来进行版本控制。Graphite可以管理git分支和PR(Pull Request)的堆叠:
对某个PR做出改动时,会自动触发对堆叠在其之上的其他PR进行rebase,节省了大量手动操作。
下面的每个部分都会描述如何在Graphite和GitHub上完成常见的版本控制工作流。
如果用户让你执行这些工作流,请遵循这些指南。

### 不要做的事情

不要使用 `git commit``git pull``git push`。这些命令都被Graphite的 `gt` 开头命令所取代,详情见下文。

### 创建一个PR(以及对应分支)

想要创建一个PR,可以按以下步骤进行:

- 先执行 `git status` 查看哪些文件被修改了,哪些是新文件
- 使用 `git add` 将相关文件添加到暂存区
- 使用 `gt create USERNAME-BRANCHNAME -m PRDESCRIPTION` 来创建分支,其中:
  - `USERNAME` 可以从其他地方获取到
  - `BRANCHNAME` 由你来想一个合适的分支名
  - `PRDESCRIPTION` 由你来写一个合适的PR描述
- 如果命令因为pre-commit检查失败而报错,你可能需要先解决那些自动修复或手动修复的问题,然后再执行 `git add` 把修复的文件加进去。然后再次执行 `gt create`- 运行 `gt submit` 会在GitHub上真正创建PR(如果你只想先创建本地分支,可以跳过这步)。
- 如果安装了 `gh`,可以用它来设置PR的描述。

注意:千万不要在没有 `git add` 的情况下就运行 `gt create`,否则会导致卡住!

### 更新PR

想要更新已经存在的PR,可以这样做:

- 执行 `git status` 查看哪些文件被修改了,哪些是新文件
- 使用 `git add` 把相关文件放到暂存区
- 执行 `gt modify` 来提交改动(不需要额外信息)
- 如果因为pre-commit检查失败,可以先查看 `git status` 看是否有自动修复的文件。如果没有修复,就需要你手动修复,然后 `git add`。修复完再重新执行一次 `gt create`-`gt submit` 提交更新
- 如果还需要更新PR的描述,可以用 `gh`(如果系统没有安装就告诉用户,但不用强求一定要更新PR描述)

### 从main分支拉取更新

如果想把main分支上的最新改动同步到本地,可以这样做:

- 先执行 `git status` 确保工作区是干净的
- 执行 `gt sync` 拉取并rebase
- 按命令提示操作。如果出现冲突,可以询问用户是否要处理冲突。如果需要,就按照 `gt sync` 的步骤解决冲突。

### 其他Graphite命令

如果想查更多命令,可以执行 `gt --help`

6. 避免过度拟合特定示例

模型非常善于模式匹配,它们可能会死死记住提示中的某些特定细节。给模型提供“具体该怎么做的例子”确实能让它更快找到正确方向,但也可能让它过度依赖示例,而在其他场景中表现变差。因此要记得多做实验,也可以准备一些能够暴露过度拟合风险的示例进行测试。

相较之下,告诉模“不要做什么”倒是比较安全(尽管不一定总是有效)。

7. 注意模型调用工具的局限性

模型在调用工具时有很多限制:

  • 如果模型在训练时看过类似的工具,或者指令与工具的功能非常契合,它往往能正确调用相应的工具。但即使提示很完美,模型依然可能失败。

  • 当给模型同时提供多个功能相似的工具时,不要指望它一定能按我们的意图去选用某个特定工具。比如给它一个简单和一个复杂的工具,功能上都能解决同样问题,Claude 往往会选用简单的那个工具。

  • 模型常常会以错误的方式调用工具,违背工具定义的约束:可能会用错参数类型、超出参数范围,或者漏掉必需参数等。因此,最好在工具实现里对输入做验证,并在返回给模型的结果中明确错误原因。模型通常会再次尝试。

举例:

  • 给模型一个 edit_file 工具,用来编辑文件中某个具体区域

  • 给模型一个 clipboard 工具,用于剪切、复制和粘贴大段代码,并告诉它当移动大量代码时应使用此工具

  • 指示模型“把类Foo从foo.py移动到bar.py”。然后“Sonnet 3.5”通常还是会用 edit_file 来完成。

8. 有时“威胁”或“唤起同理心”会奏效

跟模型说“如果你没做好就会带来经济损失”有时会提升它的表现。至于对模型说“请你好好做”或者“吼它”就很少有用。

9. 留意提示缓存(prompt caching)

如果有可能,最好让你的提示能在会话中不断追加,而非频繁修改开头的大块内容,否则会失去提示缓存的优势。

举例:

  • 如果提示中包含会话内会变化的状态(比如当前时间),就别把这个信息放在系统提示或工具定义里,因为一旦状态变了,就会使大部分提示缓存失效。

  • 相反,可以在下一步的用户消息里告诉模型时间变了,从而尽量保留前面长文本的缓存。

10. 模型更关注提示开头以及用户消息结尾

模型对信息的关注程度大致是:用户消息 → 输入开头 → 中间部分。所以如果有特别重要的内容,可以考虑放在用户消息中或者提示开头。(随着模型训练的演进,这种优先顺序随时可能变化,仅供当下参考。)

11. 当心进入提示工程的“瓶颈区”

在简单的提示层面能改进的终究有限,超过一定程度后会出现递减效应。此时就需要引入其他方法,而不只是简单地“再优化提示”。

总结(Conclusion)

掌握提示工程更多的意义在于通过“有纪律、有条理的沟通”来让模型更好工作:给AI足够完整且一致的上下文,用与不可信人类同事类似的方式去校验它的行动,然后进行反复试验。当你像管理代码库那样去管理Prompt(进行版本控制、审阅并测试),就能让智能体变成真正扩展你能力的伙伴,而不是制造更多麻烦。