超越Vibe Coding——提示的艺术:与 AI 有效沟通

172 阅读38分钟

在氛围编码中,提示(prompt)就是新的源代码。

你向 AI 传达意图的方式,直接决定了它生成代码的质量。编写优质提示既是一门艺术,也是一门科学,通常被称为提示工程(prompt engineering)。本章将为你提供一系列技巧,帮助你充分发挥 AI 编码助手的潜力。我们将先讲解提示为何至关重要的基本原理,再深入介绍从简单到高级的提示方法工具箱。通过学习如何设计高效的提示,以及如何迭代优化它们(见图 2-1),你将能够更高效、更精准地与 AI 共创。

image.png

提示工程基础

如果说氛围编码是你与 AI 模型之间的对话,那么提示工程就是学会用 AI 的“母语”交流以获得最佳效果的技能。一个精心设计的提示,往往能决定是得到一段无关或错误百出的代码建议,还是一个完美可用的解决方案。掌握提示工程,就要了解如何有效地引导 AI、如何提供必要的上下文,以及在首次回答不够理想时如何与 AI 迭代交互。

当你通过 AI 进行编程时,本质上就是在用自然语言“编写”高级源代码,由 AI 解释器将其翻译成真正的代码。如同编译器的输出质量取决于输入的源代码,AI 的输出质量同样取决于你给出的提示。

为什么提示如此重要?尽管 LLM(大型语言模型)非常强大,但它们并非读心术。它们只能响应所接收到的输入。模糊或措辞不当的提示,会导致无关甚至错误的代码;而清晰、具体的提示,往往能一次性产出精确解答。在传统编码中,你花时间思考算法并敲写代码;在氛围编码中,你花时间思考如何向 AI 描述需求。它改变了“写代码”的含义:写的可能是一段文字,而非一个函数,但同样需要精准和合乎逻辑。

把编写提示想象成为一位字面意义极端认真、且仅按文档执行的“初级开发者”写文档或用户故事。他知识丰富,却毫无常识,唯一遵循的是从未见过之外的文字模式。如果你的指令留有解读余地,AI 可能会按照它“猜测”的方式填补空白,导致与你期望背道而驰。因此,学会与 AI 有效沟通,犹如当年学会编程语言语法一般重要。

另一个原因是可复现性与面向未来性:如果你发现某个提示能稳定生成高质量代码,它就成了一条宝贵经验(如同代码片段或模板)。你可能会将其保存,以便在类似场景中重用。团队内部也可分享高效提示模式,正如共享编码最佳实践一样。

随着模型不断进步并与工具深度融合,将来可能出现更复杂的交互方式。熟练的提示能力能让你快速掌握新功能。例如,一些高级系统允许你附加大段指南,或将整份参考文档作为上下文传给模型。学会如何组织这些输入,是充分利用其潜力的关键。

因此,应将提示编写视为一项新必备技能。在很多方面,提示就是编程。主要区别在于你用自然语言(如英语)“写”出意图,再由 AI 转换成代码,但同样需要清晰、合乎逻辑,并预见潜在的边缘情况。

具体性与清晰度:撰写能产出所需结果的提示

提示工程的黄金法则之一(第 3 章将详细阐述)是:对所需内容要具体且清晰。与人类协作者不同,AI 只理解你字面上提供的信息。常见错误是给出过于笼统的指令,如“做个网站”,却奢望收到魔法般的结果;实际上,AI 更喜欢具体细节。

  • 假设它对项目一无所知:在提示中提供编程语言、框架、库,乃至待实现的函数或代码片段。若遇到错误,要贴出完整报错信息,并说明代码目标。任何模糊或可多重解读之处,都可能导致意外输出。

  • 示例对比

    • 不要写:“写一个排序函数。”

    • 可以写:

      “请用 Python 实现一个函数 sort_by_lastname(customers),它接收一个包含 first_namelast_name 字段的客户记录列表,并按 last_name 字母顺序返回排序后的列表。函数应包含简要 docstring,并在遇到缺失 last_name 时将其视为空字符串处理。”
      这个提示明确了语言(Python)、函数名与功能、输入结构、排序键、附加要求(docstring)和边缘情况,几乎能一次性获得你所需或非常接近的结果。可将提示撰写看作编写规格说明:你指定得越精细,AI 就越少猜测,也就越少需要返工。

提升具体性的策略

  1. 指明语言或环境
    若需 JavaScript,请写 “Write a JavaScript function...”,不要只说 “Write a function...”。如需基于特定框架或版本,也请注明(“使用 React Hooks...” 或 “在 Python 3.10 环境中...”)。
  2. 限定输出范围
    你想要单个函数?整个文件或模块?还要包含测试?如 “仅提供函数实现” 与 “提供可运行的完整脚本” 会有完全不同的结果。
  3. 添加需求与约束
    在登录示例中,可指定密码长度、尝试次数限制。若需性能优化或特定算法,也要在提示中说明(“要求 O(n) 时间复杂度、O(1) 空间复杂度” 或 “使用二分查找”)。
  4. 避免模糊指代
    不要写 “处理它并返回结果”,要写 “处理该数组并返回处理后的数组”。
  5. 指定输出格式
    若只要代码,写 “只给出代码,不要解释”;若要代码和简要注释,则可写 “请提供代码,并为每步添加简要注释”。

一个清晰的提示能显著提高 AI 的成功率。如果你发现 AI 的回答经常需要大量改动,不妨检查提示是否不够具体。

以下做法应避免

  • 不要写长篇大论
    冗长且夹杂无关信息的提示,会让模型分心或关注错误重点。要简明扼要,却又包含完成任务所需的所有细节。通常无需加 “你是一位世界顶级程序员” 之类的前言,这对编码任务而言多余且易生噪声。

  • 不要指望 AI 自动补全所有细节
    如果线程安全、特殊字符处理等很重要,就要写进提示。没写,AI 不会主动处理。

  • 在需确定性输出时,避免开放式提示
    例如,“写一些数据分析代码”容易引起歧义;可改写为:

    “计算一个数字列表的平均值和标准差。”

总之,要“说到点子上”。AI 对你“真正想要什么”了解越多,产出就越符合预期。若需多次纠正 AI,请先反思:我的初始提示是否足够清晰?

迭代优化:与 AI 的反馈循环

即便提示再清晰,首次输出也未必完美。应将与 AI 的互动视作一次对话或迭代式开发过程——正如第 1 章中提到的反馈循环。

当 AI 给出代码后,要像审查人类写的代码那样进行严格评审:它是否满足需求?如果不符合,找出遗漏或错误之处,然后给出反馈或改进后的提示。在对话式 AI 中,只需继续对话;在编辑器中,可写下新的注释让 AI 响应。

通过向 AI 提供反馈,你会将它逐步引向理想结果。从某种意义上说,你是在为特定问题即时“训练”它。高级提示工程如图 2-2 所示的循环:

提示 → AI 输出 → 审查 → 优化提示 → AI 输出 → …  

直到满意为止。保持每次迭代的改动量较小很重要:如果一次性彻底改写提示,可能会丢失前一次输出中已有的优质内容。

image.png

示例

  1. 初始提示:

    Write a function that takes a list of integers and returns their sum.
    
  2. AI 返回代码后发现它假设输入列表非空,未妥善处理空列表情形。

  3. 反馈提示:

    That looks good. However, please modify it to return 0 if the list is empty.
    
  4. AI 则更新函数,添加了空列表返回 0 的逻辑。

这样,你无需重写提示,只需让 AI 作出调整,它已经知道之前生成的上下文。如果你使用行内助手,则可能在代码中添加注释 # TODO: handle empty list,然后查看 AI 是否给出修复建议。

另一种迭代方式是当首轮输出不符合预期时,用更精确的提示重发请求。比如,若你提示“Sort a list of names”,AI 默认区分大小写排序,但你想要不区分大小写排序,则可以改写提示:

Sort a list of names case-insensitively.

或者更加明确地:

The previous code sorts case-sensitively. Modify it to be case-insensitive.

在调试更复杂的逻辑错误时(没有明显报错但结果不正确),可让 AI 模拟执行过程,例如:

Walk through this function line by line and track the value of total at each step. It’s not accumulating correctly—where does the logic go wrong?

这类似“橡皮鸭”调试:让 AI 列出每一步的状态,可帮助你发现诸如变量未重置或条件逻辑错误等细微问题。若怀疑某处,可进一步聚焦:

Explain what the filter call is doing here and if it might be excluding more items than it should.

让 AI 解释可助你找出漏洞。随后可直接请求修复:

What might be causing this issue, and how can I fix it?

若 AI 首次回答不够清晰或仅有部分帮助,请继续跟进:

That explanation makes sense. Can you show me how to fix the code? Please provide the corrected code.

在对话中,AI 会记住历史,直接输出修改后的代码。如果你在 VSCode Copilot 或 Cursor 等行内工具中,则可写注释:

// BUG: returns NaN, fix this function

一般来说,聊天式交互能给出更详尽的解释。

另一个跟进模式:若 AI 给出了修复但你不明白原因,可问:

Can you explain why that change solves the problem?

这样你既能为下次学习,也能确认 AI 的推理是否可靠。

LLM 喜欢示例与纠正:若你指出:

If input is [], it should return 0, but now it errors.

AI 就能快速融入该新信息。事实上,一次性将所有细节压进提示,往往不如几轮往返更高效。利用这一点:在反馈时耐心且具体,不要简单说“错了”,而要说明哪里出了问题或需要怎样的改动:

This code doesn’t handle negative numbers correctly. It should treat them as 0 in the sum.

若 AI 偏离方向,不妨重新澄清或重写提示。有时重置比修补更省时。若 AI 明显误解了意图,请从头再来。

在迭代过程中,你也会逐渐理解 AI 如何解读你的提示,从而改进未来的提示写法。你可能意识到,“原来它把 ‘login system’ 理解为包含完整 UI,下次我要明确只要后端实现。”

把这看作是“调试提示”:当 AI 产出不符合预期时,“错误”可能在你的提示里,而非 AI。正如你调试代码一样,当结果有误,你会检查并修正源代码;同理,当 AI 输出有误,你也要优化提示。你与 AI 的对话,就像一场调试会话,每次交互都助你找准并修复问题。

对比两个提示

假设你有一个简单的 Node.js 函数,用于将用户对象数组转换成以用户 ID 为键的查找映射,但它抛出了错误。以下是含 bug 的代码:

// Buggy function: converts array of users to a map by ID
function mapUsersById(users) {
  const userMap = {};
  for (let i = 0; i <= users.length; i++) {  
    // 注意:<= 会导致最后一次迭代越界
    const user = users[i];
    userMap[user.id] = user;
  }
  return userMap;
}

// Example usage:
const result = mapUsersById([{ id: 1, name: "Alice" }]);

**问题所在:**循环条件应为 i < users.length 而非 <=,否则 users[i] 在最后一次迭代时会是 undefined,访问 user.id 就会报错。

下面展示两个不同的提示,以及 AI 可能的回应(见图 2-3)。

image.png

不佳的提示

以下是不够具体的提示示例:

Why isn’t my mapUsersById function working?

AI 对该提示的模拟回应:

很难说,需要更多信息。你确定传入的是一个有效的用户数组吗?也许 users 为空或不是数组,导致循环无法正常执行。也可能是在给 userMap 设置属性时出错。请检查每个用户对象是否都有 id 属性。如果 usersundefinednull,也会导致无法工作。

这个回答相当空泛。提示中除了函数名之外,没有任何细节信息,所以 AI 只能猜测一些通用的调试原因(“也许数组为空”、“检查用户是否有 ID”),却未能指出真正的 bug。正如社区经验所说,模糊提示往往只能得到不痛不痒的答复——就像 AI 版的“你有没有关机再开机试试?”

改进后的提示

让我们重写提示:

I have a JavaScript function mapUsersById that should convert an array of user objects into a map (object) keyed by user ID. However, it throws an error when I run it. For example, when I pass [ {id: 1, name: "Alice"} ], I get TypeError: Cannot read property 'id' of undefined. Here is the function code:

function mapUsersById(users) {
  const userMap = {};
  for (let i = 0; i <= users.length; i++) {  
    const user = users[i];
    userMap[user.id] = user;
  }
  return userMap;
}

It should return { "1": {id: 1, name: "Alice"} }. What is the bug and how can I fix it?

AI 对改进提示的模拟回应:

错误 Cannot read property 'id' of undefined 表明在某次迭代中,userundefined,然后尝试访问 user.id 导致 TypeError。检查你的循环条件:你使用了 i <= users.length。若 users.length 为 1,则循环在 i = 0i = 1 两次执行,当 i = 1 时,users[1]undefined,于是 user 成为 undefined,访问 user.id 报错。
修复方法是将条件改为 i < users.length,例如:

for (let i = 0; i < users.length; i++) {
  const user = users[i];
  userMap[user.id] = user;
}

这样 i 只会从 0 遍历到 users.length - 1,不会越界,函数在给定输入时将正确返回 { "1": {id: 1, name: "Alice"} }

该回答精准指出了循环边界的 bug(使用 <= 而非 <),并解释了原因,随后给出了正确示例代码并确认预期输出。改进后的提示之所以成功,是因为它符合提示工程原则:明确了语言(JavaScript)、函数目的,提供了完整报错信息、示例输入,以及相关代码片段。这样,AI 可利用错误信息聚焦到循环边界问题,像人类调试一样:“undefined 从哪里来的?很可能是索引越界。”这正是详尽提示带来的价值。

提示技巧:高效沟通的工具箱

现在让我们进入一些能大幅提升你提示(prompting)技巧的具体方法。可以把它们看作当直接指令不够用,或你想以某种方式引导 AI 时的“模式”或“配方”。
掌握这些技巧后,你可以应对各种场景:用普通英语指示 AI、提供示例、让它解释或组织输出、或把它设定在不同的心态/角色中。所有这些都能帮助你更精准地引导 AI 产出你需要的内容。
提示技巧并不是互斥的;在处理复杂任务时,你常常会组合使用多种技巧以获得最佳效果。

【风格说明】
使用这些技巧时,请根据模型调整语气。许多模型对礼貌或中性的指令反应良好。无需使用古板或过于正式的语言。直接但礼貌通常更有效:“Please do X” 或 “Let’s do Y”。例如,在思维链(CoT)提示中,常见的一句话是“Let’s think step-by-step.”(让我们一步一步思考)。像 GPT-4 这样的模型会把这当作展示推理过程的提示。

零样本提示(Zero-Shot Prompting)

零样本提示就是只给出任务指令,不提供任何示例或额外引导;本质上,模型在“零”示例的情况下完成任务。
何时使用:这是最常见的情景——你用朴素的语言直接提出需求。如果任务标准、表述清楚,往往足够。
示例:
“写一个 Python 函数来判断一个数是否为质数。”
这就是零样本。AI 很可能会给出使用循环或试除法的质数判断函数。
优点:快捷,依赖模型已有知识。对许多常见编程任务(如质数判断、排序、字符串处理),现代模型的零样本表现就已相当不错。
缺点:如果任务不寻常,或输出格式有特定要求,零样本的首次结果可能不完全符合你的预期,因为模型可能会有多种解读方式。
小建议:对于简单任务,先试零样本。若结果有偏差,再转向精炼或使用其他技巧。

单样本与少样本提示(One-Shot / Few-Shot Prompting)

单样本提示指在提示中提供恰好一个示例(输入与期望输出);少样本提示则提供几个示例(通常 2–5 个),然后让模型在新的输入上按同样方式作答。
这就像对模型说:“我会这样解一道题。现在你也照这个样子来做下一道。”
何时使用:当模型可能不清楚你需要的具体格式或风格,或任务有些非常规时,用示例能减少歧义。
示例(单样本):假设你想要特定格式的伪代码。你的提示可以是:
“将以下英文指令转换为类 Python 的伪代码。
示例指令:‘Calculate the factorial of n’
示例伪代码:

function factorial(n):
    if n <= 1:
        return 1
    else:
        return n * factorial(n-1)

指令:‘Find the largest number in a list’
伪代码:”
你给出了一个(阶乘)示例和目标格式。模型更可能用相同风格给出“最大值”指令的伪代码(包含函数、必要的 if/else 或循环逻辑等)。
示例(少样本):如果你希望 AI 采用某个特定算法,可以给一个小例子作为暗示。或者当任务有多种正确答案,而你偏好某一种时,示例能引导模型靠近你的偏好。
少样本对“格式对齐”尤其有效,例如:
“将以下英文语句转换为 SQL 查询。

  1. ‘Get All Employees Hired After 2020’ → Select * From Employees Where Hire_Date > '2020-01-01';
  2. ‘List Customer Names Who Made A Purchase In The Last Month’ → Select Name From Customers Join Purchases On ... Where Purchase_Date > ...;
  3. ‘Count Of Products That Are Out Of Stock’ →”
    一旦你给出两个英转 SQL 的示例,模型很可能会按同样模式完成第三个。对编码任务也一样:先用小样本示范你想要的风格,再让模型扩展。这就像在提示里塞进一个迷你训练数据集。
    优点:能获得非常特定的风格或结构;对需要遵循固定模式、反复应用同一概念的任务尤其有用。
    【上下文窗口】
    “上下文窗口”(context window)指模型在一次交互中能处理的最大文本量(以 token 度量),包含你的输入提示和模型的输出。这是当前模型的一个限制——一旦超出该上限,模型就无法再处理更多信息。写提示时,你提供的所有内容(说明、示例、数据,以及留给响应的空间)都必须装进这个固定的容量里。
    缺点:少样本提示会让提示变长(占用上下文窗口)。如果示例很大/很复杂,就会“吃掉”不少容量。不过,通常一两个小示例是可行的。
    提示:若你需要模型严格遵循某种输出结构,给出示例几乎能“锁定”格式,避免它给出需要你再去解析的自由形式回复。

思维链(CoT)提示(Chain-of-Thought Prompting)

思维链提示是鼓励模型在给出最终答案前“逐步思考”或显式展示推理过程。换言之,你让模型把问题拆解开来。
何时使用:用于需要推理与多步计算的复杂问题,或你担心模型若直接跳到答案可能出错时;当你希望输出包含解释时也很合适。
【组合数学】
组合数学研究在特定规则/约束下的计数、排列与选择。常见问题包括排列(顺序有关)与组合(顺序无关)等。常用记号“从 n 个中选 k 个”(记作 C(n, k) 或 nCk),其公式为 n! / (k!(n−k)!)。这些计算常见于概率、统计与离散数学。
示例:与其只问“12 选 4 等于多少?”,不如说:“分步求解 C(12, 4)。”模型可能写出:
C(12, 4) = 12! / (4! * 8!) = ... = 495
在编码中,CoT 对棘手算法题很有用。你可以这样提示:
“分步骤解释如何合并两个有序列表,然后给出 Python 代码。”
模型会先勾勒:
“使用两个指针分别从两表头开始,比较元素,把较小的附加到结果列表,并移动相应指针,依次进行……”
然后再给出代码。这样可先把方案理顺,再动手写码。
另一个用途是调试或理解输出:
“逐步推理判断 19 是否为质数,然后给出结论。”
模型可能列出按质数尝试除的过程,最后得出:“19 是质数。”
优点:在需要推理的任务上提升正确率;研究显示,鼓励模型“出声思考”能改进数学和逻辑题表现。同时你也能看到过程,有助于理解与信任答案。
缺点:输出更冗长(可能不是你最终代码想要的样子)。而且有些界面(如典型的代码补全)并不适合把推理和代码分开展示。不过,你可以要求模型把推理写成代码注释,这样既保留思路又得到带详注的代码。

角色提示(Role Prompting)

角色提示是让 AI 扮演某个身份或角色,从而影响它的回应方式。
何时使用:当你想影响答案的风格与细节、或从某个视角出发时。例如,“专家”角色可能给出更进阶的方案或更多解释;“初学者”角色可能会更耐心地讲基础。
示例:
“你是一名 Python 讲师。请先解释下面这段代码,再把它改写得更 Pythonic。”
“扮演安全分析师。这里有一段代码,请识别潜在的安全漏洞。”
“假装你是一个检查代码风格问题的静态检查器(linter)。”
在编码中,你也可以先说:
“你是一位精通优化的 C++ 专家,正在指导一名初级开发者。”
这样产出的结果往往会使用更高级的 C++ 特性,并解释选择背后的理由,兼顾技术深度与教学清晰度。
优点:能强力引导语气和深度,把解决方案调整到合适的复杂度与周详度。如果你想要非常朴素的方案(让它扮演新手以避免过度技巧)或想要极致优化(让它扮演性能高手),都很有用。
缺点:有时模型会过度关注“人设”(例如“讲师”可能给出你已经知道的基础解释)。此外,部分 AI 安全系统对某些角色描述更敏感——尤其是涉及欺骗、冒充权威或潜在有害活动的描述——不过像“数据分析师”或“软件工程师”等正当专业角色一般没有问题。

上下文提示(Contextual Prompting)

“上下文提示”是指在任务描述之外,向 AI 提供额外的上下文或信息。除非你把信息放进提示里(或通过某些高级 IDE 集成带来的上下文窗口),否则 AI 模型并不会对你的整个项目有持久记忆。因此,如果你希望 AI 编写能融入你现有代码库的代码,就把相关上下文提供给它——也就是把相关的数据或背景作为提示的一部分给出。

何时使用: 当解决问题需要某些模型可能不了解或可能从训练中回忆不准的数据/定义时使用;或者当你想确保与外部信息(比如某个 API 规范或对话前文)保持一致时使用。

示例:

  • 如果你有一个数据结构,并希望生成的代码能正确配合它使用,你可以把它的定义粘贴出来:
    “Given the class below, implement the function X.”

    class Node:
        def __init__(self, value, next=None):
            self.value = value
            self.next = next
    # 现在请编写一个函数,从 head 开始统计链表的节点数量。
    

    通过包含类定义,你会大幅提高 AI 正确使用 Node.valueNode.next 的概率。

  • 如果你要使用某个特定 API,请在提示中包含一段文档片段:
    “使用 requests 库从该 API 获取数据。(该 API 返回 JSON,格式为:{...})”
    即便只给出一小段官方用法示例,AI 也能据此模仿。

  • 为了消除歧义:
    “将 student 一词限定为指‘高中生’,然后编写一个函数……”
    如果 “student” 在上下文中可能含糊,这样就把范围说清楚了。

优点: 你在用你关心的上下文“锚定”AI。如果你提供事实依据,AI 更不容易做出错误假设。对于模型可能不了解或记不清你特定用例细节的情形,这尤其有帮助。

缺点: 这会让提示更长。另外,模型有时可能把你提供的上下文原样“复述”到答案里(例如不小心把文档片段的行复制进代码),不过通常它会恰当地使用这些信息。

提示: 如果上下文很大(例如一份庞大的模式/Schema或很多行代码),有时把关键要素做个摘要比原样贴上更好。这既能控制上下文长度,又能确保模型拿到最相关的信息。不过,如果内容足够小,直接原样包含也无妨。

还可以显式声明约束条件:
性能约束(“优化到 O(n log n) 或更优”)、兼容性约束(“必须在 Python 3.8 上运行”)、库选择(“仅用标准库,不要外部依赖”)等。这些都像护栏,能确保 AI 不会给出超出可接受范围的方案。

元提示(Metaprompting)

“元提示”是关于输出本身的指令,而不只是解决方案要做什么。它就像是在告诉 AI 应该如何组织、格式化或推进解题过程。

何时使用: 当你需要特定格式或风格的答案,或你想控制 AI 处理问题的方式时。

示例:

  • “先用两句话解释思路,再给出代码。”
    这可以防止 AI 一上来就贴代码。
  • “解决方案中不要使用任何库。”
    这给出了实现约束。
  • “将输出格式化为 JSON。”
    当你要的是数据而不是代码时很有用。
  • “只提供函数体,不要函数定义行。”
    当你要把函数体插入现有代码时很方便。
  • “如果输入无效,请不要报错,返回 None。”
    这虽然不是格式要求,但指示了在特定情形下的行为规范。

优点: 你可以精确得到你需要的内容和形式,减少后期编辑。这在自动化流水线中尤为关键,因为你通常强烈需要一致的格式。

缺点: 如果这些指令与模型的默认风格冲突,有时模型只会部分遵循,或者需要你更强调。例如,即使你说“只输出代码,不要解释”,模型偶尔也可能加一句小注释。一般把要求写成明确的命令式有助于遵循:
“不要包含任何解释;只在一个代码块中输出代码。”
像 GPT 这样的模型通常会很好地遵守。

自一致性(多次输出与多数投票)

“自一致性”更像一种策略而不是某种提示写法。思路是:对同一提示获取多份答案,然后挑选最佳或最常见的那份。正如 Learn Prompting 的 Sander Schulhoff 所述,自一致性利用了这样一个想法:当你在有少量随机性的前提下多次询问模型,如果多数答案一致,那么这个共识往往更可靠。

何时使用: 当问题复杂、你不确信模型的首次回答是否正确,尤其是你自己不易验证,或者你想通过“是否多次给出相同答案”来提升信心时。

如何手动使用: 在一些平台(如 ChatGPT)可以点击“重新生成回答”,或把同一提示复制到一个新会话,看它是否给出相同结果。如果你得到三份答案,其中两份一致、一份不同,那么你可能更信任那两份(前提是问题确实有唯一正确答案)。

在编程场景中,如果是生成确定性的代码,通常每次生成的代码会非常相似(变量名或风格可能略有不同)。但若是算法推断类问题(例如“这段代码的输出是什么?”),你可以比较多次结果。

这种技术在非编码任务(如逻辑谜题)中更强,但仍值得了解。

另一种思路——“集成提示”(ensemble prompting): 你可以在一个提示里就让模型给出多种可能:
“请给出该问题的两种不同解法。”
然后你可以选择更喜欢的那一个,或都试一试。这相当于一次性拿到多份“自一致性”的备选答案。

优点: 如果多次尝试收敛到同一答案,这能提升你对结果的信心;此外还能获得多样性(当你想从多种方案中挑最优雅的一种时很有用)。

缺点: 需要多次调用并比较输出,较耗时。

实际中,如果我拿不定答案,常会换一种表述再问一次。如果多次得到相同结果,我会更有信心它是正确的。

ReAct(推理 + 行动)提示

ReAct 是一种更高级的提示技术,把“推理”和“行动”结合起来。它让模型不仅像 CoT 那样思考,还能采取行动,比如做计算、调用 API 或使用某个工具(参见 ReAct 提示工程指南)。在目前的实践中,这常与 LangChain 之类的框架配合使用:AI 以某种程序可解析为“行动”的特殊格式输出,系统执行该行动(如运行命令或发起查询),再把结果回传给模型。

在我们的范围内(不把执行环境纳入回路) ,你仍可以采用一种“轻量 ReAct”:先让 AI 制定计划,再产出结果。这与 CoT 类似,但更强调面向工具或子任务的执行。

示例:
“使用 Python 获取巴黎当前天气并打印出来。”
如果 AI 没有浏览或工具能力,它无法真正获取“当前天气”。一个 ReAct 思路会先说明:
“我需要访问巴黎的实时天气数据,这需要调用天气 API。”
随后,AI 会尝试使用可用工具来调用该 API;若成功,就能拿到真实天气数据;若无此工具,它会说明限制,或用假设数据进行演示。最后,它会写出展示天气信息的 Python 代码,并整合它通过推理与行动获得的数据。

在没有外部工具访问的情况下,ReAct 对简单提示任务的意义不大。然而,在评估组织内的 AI 工具时,能否从互联网获取最新信息是一项关键能力,因为许多模型存在“知识截止日期”,在快速变化的主题上可能给出过时信息。

如果你使用的是能执行代码的环境(如 Jupyter 集成等),你可以这样组织 ReAct:
“先为该函数写一个测试,运行它,然后根据结果修改代码。”
这体现了 ReAct 模式:先推理(写测试),再行动(执行测试),再根据结果调整代码。当然,纯靠提示来编排此类流程,通常需要更高级的提示技巧与适配的技术基础设施。

更简单的用法: 你可以模拟一个带“中间步骤”的问答,让 AI 在需要时进行计算:
“逐步思考,如有需要请先做计算。”
这本质上是 CoT,但语气更具命令性、更贴近“行动”。

优点: 一旦具备工具调用能力,它可以解决需要外部信息或需要反复试错的问题(比如 AI 能通过实际运行代码自我修正)。在调试场景中,能执行代码的 AI 非常强大。

缺点: 若无特定工具支持,这种技术并不普及。若只在普通聊天环境中这么提示,模型要么“想象”行动,要么退化为 CoT。

对我们的提示写作而言: 记住,一些系统(如能用工具的代理等)确实存在;但在“纯提示”的情形下,我们主要还是做 CoT,然后由我们自己来执行那些行动(如跑代码或测试)。

高级提示(Advanced Prompting):组合技巧与处理复杂性

提示技巧是可以组合使用的。比如,你可以在少样本(few-shot)示例里同时演示思维链(CoT);或者把“角色”与 CoT 结合起来:
作为一名资深工程师一步一步思考这个问题,然后给出代码。”

在了解了各种提示技巧之后,让我们通过一两个场景看看它们如何协同工作,并讨论如何审阅与改进 AI 的输出(这也引出下一章:理解并“掌控”生成的代码)。

设想你有一个函数无法正常工作。你可以把“角色”与 CoT 结合起来:
你是一名 Python 调试器。让我们一步一步思考,找出下面代码中的错误。
随后贴上代码。AI 可能逐行分析并指出漏洞所在。

再比如,你想为一个相对复杂的算法生成代码,确保有良好注释,并且还要配套测试用例。一个复合提示可以是:
你是一位资深的 Python 开发者。让我们一步一步来解决。我们需要一个函数 merge_sorted_lists(list1, list2),把两个有序列表合并为一个有序列表。先解释思路,然后给出带注释的 Python 代码。最后提供 2–3 个代码形式的示例测试来证明可用。
这个提示已相当全面:第一句设定角色;第二句要求逐步推理;第三句给出主要任务;第四句要求带解释性注释的代码;第五句甚至提出测试的需求。AI 随后可能先给出思路说明,再给出带行内注释的代码,最后附上若干测试用例。这就是一种“高级用法”,展示了如何引导 AI 产出多层次的响应。

了解模型的局限(Know the Model’s Limits)

提示工程也包括知道不该怎么问以及如何规避陷阱。如果提示过大或包含过多指令,模型可能困惑,或在输出时被截断。如果你发现它开始忽略提示的部分内容,试着简化指令,或把任务拆分成多步。
若模型有时会编造事实或代码(“幻觉”),就要养成二次核对的习惯,不要把它当成事实权威。
如果它倾向于给出过度冗长的代码,可以预先声明:“请尽可能简洁”。
如果它会使用并不存在的函数,你可以指示:“只使用以下列出的 API 函数”,并把它们列出来。
越了解模型的行为,你就越能通过提示来弥补其弱点。

当任务非常复杂时,你也可以把它拆解成子任务交给 AI。比如先提示:
列出实现一个简单算术表达式语言的基础编译器所需的步骤。
得到步骤后,再逐步推进,甚至分文件/分会话处理:
现在实现第 1 步:词法分析(tokenization)。
这就像与 AI 一起做系统设计:先总览,再精炼每个部分,利用 AI 在规划编码两方面的辅助能力。

有状态对话 vs 一次性提示(Stateful Conversation Versus One-Shot Prompting)

在聊天场景中,你有对话历史(state),可以通过连续交流累积上下文。在 IDE 补全场景中,主要上下文来自文件内容与注释。两者都能形成“累积上下文”,只是方式不同。
当你需要 AI 记住之前说过的话(例如持续精炼一个答案)时,适合使用对话;当你希望它只关注当下相关信息时,适合用全新的提示或仅依赖文件上下文。有时“清空上下文”能避免模型被先前可能错误的假设所束缚。

多练习、多对比,你就能更熟练地判断何时用哪种方法:

  • 如果输出格式很重要:给示例(少样本)或明确的格式指令
  • 如果逻辑很棘手:使用 CoT/逐步提示。
  • 如果方案质量差异较大:设定角色(如“资深工程师”)以获得更优的风格与取舍。
  • 如果模型不太遵从:把提示拆成小块简化要求,或更强势地表达约束。

常见提示反模式及其规避方法(Common Prompt Antipatterns and How to Avoid Them)

并非所有提示都一样有效。到目前为止,我们已经看过许多有效提示的例子,但识别“反模式”(会导致糟糕 AI 响应的常见错误)同样重要。本节将介绍一些常见的提示失败及其修复方法。

含糊的提示(The vague prompt)

这类提示典型如:“它不工作,请修复”或“写点能做 X 的东西”,却没有足够细节。比如,“为什么我的函数不起作用?”通常会得到无用的回答。含糊的提示会迫使 AI 去猜测上下文,往往产出泛泛的建议或不相干的代码。

修复方式: 增加上下文与具体信息。如果你问了一个问题,而答案像 Magic 8-Ball 那样随缘(“你试过检查 X 吗?”),请停下来,带着更多细节重构你的问题(错误信息、代码片段、期望结果 vs 实际结果等)。一个好习惯是读一遍你的提示并问自己:“这个问题是否适用于几十种不同场景?”如果答案是“是”,那就太含糊了。让它具体到只能适用于你的场景。

过载的提示(The overloaded prompt)

这是相反的问题:一次让 AI 做太多事。例子:

  • “生成一个带认证的完整 Node.js 应用,前端用 React,并附带部署脚本。”
  • 或者小规模一些:“一次性修这些 5 个 bug,并新增这 3 个功能。”

AI 也许会尝试,但你很可能得到混乱或不完整的结果,或者它会忽略部分请求。即便面面俱到,输出也会很长且更难验证。

修复方式: 拆分任务并设定优先级:一次只做一件事(我们之前也强调过)。这样更容易发现错误,也能让模型保持聚焦。如果你发现自己写的指令段落里“和/并且(and)”出现了多次,考虑把它们切分成独立的提示按顺序的步骤

缺少明确问题(Missing the question)

有时用户会给出大量信息,却没有明确询问或说明需要什么,比如直接贴一大段代码然后说:“这是我的代码。”这会让 AI 困惑——它不知道你要什么。

务必包含一个清晰的请求,如:

  • “请找出上面代码中的任何 bug。”
  • “解释这段代码的作用。”
  • “完成代码中的待办事项(TODOs)。”

提示必须有目的。如果你只提供文本而没有问题或指令,AI 可能会做出错误假设(比如去总结代码而不是修复它)。确保 AI 知道你为何展示这段代码。即便只补上一句“这段代码有什么问题?”或“请继续实现这个函数”,也能给它明确方向。

含糊的成败标准(Vague success criteria)

这点更微妙。有时你会要求优化或改进,但没有定义成功的标准——例如:“让这个函数更快。”更快按什么指标?如果 AI 不知道你的性能约束,它可能微优化无关痛痒的部分,或采用一种理论上更快、实际上收益很小的方法。再比如“让这段代码更干净”: “更干净”是主观的。我们可以通过明确目标来处理,如“减少重复”或“改进变量命名”。

修复方式: 量化或定性地界定改进目标:

  • “将该函数优化到线性时间复杂度(当前为二次)。”
  • “对其进行重构,移除全局变量,改用类。”

本质上,要明确这次重构或功能要解决什么问题。如果过于开放,AI 可能会解决一个你并不关心的问题。

忽视 AI 的澄清或输出(Ignoring AI’s clarification or output)

AI 有时会先提出澄清问题或做出假设:

  • “你在用 React 的类组件还是函数组件?”
  • “我假设输入是字符串——请确认。”

如果你忽略这些,只是重复原请求,你就错过了改进提示的机会。AI 已在表明它需要更多信息。务必回答它的问题,或在提示中补充这些细节。

另外,如果 AI 的输出明显跑偏(比如误解了问题),不要原封不动地再试一遍同一个提示。花点时间调整表述。也许你的提示中有歧义词,或遗漏了关键信息。把它当成一次对话:如果一个人误解了你的意思,你会换种说法;对 AI 也该如此。

不一致性(Inconsistency)

如果你不断改变提问方式,或在一次提示中混合多种格式,模型可能会被搞乱。两个常见例子是:在指令里在第一人称和第三人称之间来回切换;把伪代码和真实代码混在一起、表达不清。

尽量在单个提示内保持一致风格。如果你提供示例,确保它们界限清晰(代码用三反引号 Markdown 块,输入/输出示例用引号等)。一致性有助于模型正确解析你的意图。另外,如果你有偏好的风格(比如 ES6 vs ES5 语法),请持续说明;否则模型可能这次建议一种、下次换另一种。

含糊的引用(如“上面的代码”)(Vague references like “the above code”)

在聊天中,如果你说“上面的函数”或“前一个输出”,务必确保引用清晰。如果对话很长,你说“重构上面的代码”,AI 可能会跟丢,或者挑错了片段来重构。

更安全的做法是再次贴出代码,或明确指名你要重构的函数。模型的注意力窗口是有限的;尽管许多大模型可以引用对话早先部分,但在相隔了若干条消息后再次提供明确上下文,能有效避免混淆。对此类情况尤其适用。

总结与下一步(Summary and Next Steps)

提示的艺术是迭代且富有创造性的。随着模型不断演进,提示的最佳实践也可能改变(例如,未来的模型也许用更少的文字就能更好地理解意图)。但底层原则不变:有效沟通,AI 才能更好地为你服务。

从本质上说,精通提示工程就像精通一门新的编程语言——一种面向 AI 的指令语言。它融合了技术写作、前瞻性思维,以及对提示本身的交互式调试。一旦你上手熟练,AI 就会像你思维的延伸:你能以最小阻力稳定地“抽取”出你设想的解决方案(甚至是你尚未完全成形、但能引导 AI 去发现的方案)。这项技能很可能会像会用搜索引擎或会用调试器一样基础——在 “vibe coding(氛围编码)” 时代,它是现代开发者技能栈的一部分。

如果 AI 能解决约 70% 的问题,你该如何把它当作编码伙伴来协作?第三章将探讨开发者实际如何使用 AI,并给出 “vibe coding” 的若干“黄金法则”。