让AI替代50%的工作 ——2. AI辅助编程协作逻辑

让AI替代50%的工作 ——2. AI辅助编程协作逻辑


chinese AI

本文以及本专栏正在参加豆包 Marscode 专栏征文活动,如何喜欢欢迎投票支持,这将对我们意义非凡
投票链接

在上一章中,我们用通俗的语言简单科普了大模型的底层原理,以及我们对 AI 辅助编程的思考。这些是我们从根本上思考如何更好地利用 AI 进行编程辅助的基础逻辑。

如果说,原理是在理论层面,应用是在实战层面,那么本章就位于这两者的中间,讨论我们在应用 AI 时的一些通用思考方式和应用逻辑。可以说,实战中的各种应用技巧,都是基于本章中的思考逻辑,在实践中派生而出的。

实战技巧和本章中的思考是相辅相成的,是双向的:

  1. 我们从大模型的原理出发,分析其擅长的点,然后在实战中应用。
  2. 在实战中,我们尝试出了某些效果很好的 prompt,我们反向思考其体现了大模型的哪些特点,凝聚出的思考又可以指导新的实战技巧。

基本理念

这一节中,我会分享一些我在应用 AI 时的基础性想法,初看可能有些模糊,大家可以先尝试理解。在后续的实战章节中,大家会看到这些想法在实战技巧中的应用。看了足够多的实战技巧后,再回头看看本章和这一节,相信会有更多的体会。

首先,我们应该把自己的视野拉高,从底层细节中剥离出来,从一个更全局的角度去分析和拆分问题。人类负责高层次的设计和决策,AI 负责具体实现和优化。并且在 AI 输出的结果中进行持续的交互和优化,通过给 AI 有效的反馈来提高和引导 AI 的输出。

其次,因为 AI 是极度廉价的劳动力,我们可以大量地使用 AI 来节约自己的思考成本。用 AI 进行头脑风暴,提供足够多的思考方向,然后由我们筛选其中有效的思路,并对这些思路持续追问。

类似于很多时候我们卡在一个问题上时,喜欢跟别人讨论,有时候别人一句话就能启发我们进一步的思考,AI 扮演的就是这个朋友的角色。时刻记住,AI 的思考是极度廉价的,当不知道怎么走的时候,让 AI 代替我们去试错。

作为程序员,专业素养是我们的优势,而不是 AI 的。AI 是有出错可能的,我们要时刻牢记,向 AI 提问时,我们至少要有鉴别其回答质量的基本能力,或者在 AI 的辅助下理解其输出的能力。例如,我让它帮我写一段 Go 代码,虽然我对 Go 语言不熟练,但我至少大概能看懂,对于看不懂的逻辑可以反复追问 AI 进行解析,直到我们能理解整段代码。

我们应该利用自己的专业能力,对 AI 给出的输出取其精华,并根据自己的理解进行引导和追问,在不擅长的领域完全信赖会导致非常严重的问题

不要懒得写长的 Prompt。

我观察身边的朋友应用 AI,常见的情况是,跟 AI 说“帮我写一个登录界面”,然后 AI 输出了一个与自己代码不兼容的方案,或者效果较差的方案,就说“AI 效果很差”。

再强调一次,AI 不是银弹,只是把我们 50% 写代码的时间转换成 10% prompt AI 的时间,并且如果应用得好,其输出代码的质量要比我们写的更高。

所以,不要懒得写长的 Prompt,提供足够的 context 才能让 AI 更好地理解你的需求。如果要说一个原则的话,你提供给 AI 的上下文,至少应该达到如果你自己被给到同样的上下文,也可以输出高质量结果的水平。

最后,不要过分轻视或高估编程这件事。

一个学习过海量开源代码的大模型的编程能力是很强的,其输出代码的质量至少不弱于人类,但在很多场景下存在缺点。我们需要做的是,找到在编程场景中适合 AI 去做的事,和适合人类去做的事。

例如,AI 可以很容易写出无 bug 的快速排序算法,这对很多程序员来说都是困难的。人类程序员很容易理解业务需求,而 AI 对很多特殊场景的业务需求理解较弱,需要人类去拆解和简化任务。

Context!Context!Context!

这可能是 AI 辅助编程中最重要的技巧。在上一章大模型基础中,我们讲过,大模型是根据人类提供的上下文来逐步生成回复的,即其回复完全依赖于我们提供的上下文。这意味着,我们的输入或提示的上下文越丰富,输出的内容就越好,也越符合我们的需求。

上下文包括但不限于:

  1. 代码上下文:当前文件的代码、项目的结构、关键的从其他文件引入的函数
  2. 任务描述:开发者的需求说明、功能描述、架构思路、实现思路
  3. 文档:内部库的文档、第三方库的文档
  4. 报错信息:调试输出、同时提供的提示和想法
  5. 经验性内容:自己的历史经验、自己的猜测和之前尝试的效果
  6. ……

当然不是让大家在每次请求中都包含这些内容,而是根据需要去提供,原则就是,提供的信息,足够你自己解决这个问题。

例如,如果遇到了一个报错的场景(以下例子皆为虚构,核心在 prompt 技巧)

  1. 报错内容是:
TypeError: Cannot read property 'findUser' of undefined
    at UserService.getUser (UserService.js:45)
    at UserController.handleRequest (UserController.js:30)
    ...
  1. 文件在系统架构中的位置:
  • UserService.js 位于 services 目录下,负责处理与用户相关的业务逻辑。
  • 该文件依赖于 Database.js 中的核心函数 findUser 来从数据库中检索用户信息。
  1. 依赖文件的核心函数实现(简化版):
// Database.js
class Database {
  findUser(userId) {
    // 模拟数据库查询
    return this.users.find((user) => user.id === userId);
  }
}

module.exports = new Database();
  1. 我们自己的猜测(可选,如果自己有大致的感觉可以提供):
  • 可能是 Database 实例未正确导入或初始化。
  • findUser 方法在 Database 类中被错误地命名或未定义。
  • UserService.js 中调用 findUser 时,Database 对象为 undefined

那么,我们在 prompt AI 时就可以:

我在运行应用时,`UserService.js` 文件抛出了以下错误:

TypeError: Cannot read property 'findUser' of undefined
    at UserService.getUser (UserService.js:45)
    at UserController.handleRequest (UserController.js:30)

这是 `UserService.js` 的完整源代码:
{source_code}

我们使用的 `Database` 数据库可能与此报错相关的文档部分:
{doc}

这是一些可能对你解决问题有帮助的信息:
1. `UserService.js` 位于 `services` 目录下,负责处理与用户相关的业务逻辑。
2. 该文件依赖于 `Database.js` 中的核心函数 `findUser` 来从数据库中检索用户信息。
3. `Database.js` 的核心函数实现如下:
    ```javascript
    class Database {
        findUser(userId) {
            return this.users.find(user => user.id === userId);
        }
    }

    module.exports = new Database();
    ```
4. 我的初步猜测是 `Database` 实例未正确导入或初始化,或者 `findUser` 方法存在命名错误。

帮我分析导致这个报错的三个可能性,以及对应的解决方案。

在这个 prompt 中,我们提供了足够解决问题的上下文,并且因为 AI 的思考是廉价的,我们让它提供三种可能性,看看这里面有没有能够激发我们进一步思考的点。

提供高维度的信息

这本质上是 Context 理论的延续。因为 AI 是在全范围的大规模数据上训练而来,而我们的项目是垂直领域的,所以我们需要限定 AI 的思考范围和技术栈领域。提供高维度的信息不仅帮助 AI 理解项目的整体架构,还能确保其生成的代码或建议与项目的特定需求和标准相符。

还可以提供代码风格上的要求,例如简洁、详细注释、避免使用复杂的 API、注重安全性等,这些有时对 AI 输出的代码影响效果很明显。

例如,就像上一章提到的,AI 可能默认会使用陈旧的技术(例如已经弃用的 Moment.js),你可以简单提供:

技术栈要求:TypeScript、React 19、Day.js
每个函数和组件必须有 JSDoc 注释
代码要简洁,并附有详细的注释

依然,不是每次 prompt 都需要这么麻烦,只是在 AI 出现技术栈错误时进行提示。现代的 AI 辅助编程工具,例如 MarsCode,都能自行读取整个项目的技术栈,保证 AI 不会在技术栈上偏离。

Few-Shot Prompting

Few-Shot Prompting 和 Zero-Shot Prompting 是大家经常会听到的提示技巧。

Few-Shot Prompting(少量示例提示)是一种向 AI 提供有限数量的输入-输出示例,以帮助 AI 更好地理解任务需求的方法。使用场景是,当要求 AI 执行一个在训练数据中没有见过的场景(例如,一个新的语言,或者新的配置文件)或者强化 AI 在某个已知场景的能力。

通过在 prompt 中输入几个具体的例子,让 AI 捕捉到任务的模式和期望,从而生成更准确和一致的输出。

这是大模型技术智能的体现。在旧的技术路线如深度学习中,一个新的任务基本都需要针对新任务的数据进行训练,而大模型只需要简单的几个实例,就能学会执行新的任务。

Zero-Shot Prompting(零示例提示)是在没有任何示例的情况下,直接向 AI 提出任务。尽管缺乏示例,Zero-Shot Prompting 依然在许多场景中表现出色,特别是在任务相对简单或 AI 已经具备相关知识的情况下。

其实我们大多数使用 AI 时,都是 Zero-Shot Prompting,因为准备例子较为繁琐。但因为大模型的能力,其在没有任何示例,只有对任务描述的情况下,输出的质量依旧非常高。

Few-Shot Prompting 在 API 调用的时候应用场景更多,例如要求 AI 按照给定的 JSON 格式生成数据,一般有三种方式:

  1. 使用 OpenAI 的 Function Calling 来控制 AI 的输出格式,这是最稳定的
  2. 使用 JSON Schema 来描述输出的格式
  3. 使用示例来帮助 AI 理解需要的输出格式(Few-Shot Prompting)

一般,Few-Shot Prompting 会和方式 1、2 一起加强 AI 对任务的理解。

在 AI 辅助编程场景下,也有很多场景适合 Few-Shot Prompting。例如,我们实现后端 API 时,很多操作和代码是类似的,已经实现的 API 代码中已经能够展示项目使用的技术栈和代码风格,那么很适合以这种方式 prompt AI:

请根据以下示例,生成一个新的 API 接口代码。

示例 1:{xxx}
示例 2:{yyy}

请根据以上示例,生成一个用于删除用户的 API 接口代码(DELETE /api/users/:id),并确保遵循相同的代码风格和错误处理机制。

当然这种场景比较少,我们更多的是利用 Few-Shot Prompting 的思想,去优化我们其他的 prompt 场景。Few-Shot Prompting 本质上是通过丰富的上下文,来让 AI 更加理解任务(是不是很熟悉,跟 Context 小节的思想近似)。

在这种思想的指导下,例如,当我们发现 moment.js 已经被废弃了,我们需要迁移到 day.js,那么我们就可以这样迁移:

你需要把现有基于 moment.js 库的业务代码,迁移到 day.js 库。

官方提供的迁移指南:{xxx}
迁移示例:{xxx}

现有源代码:{xxx}

如果在某次迁移中,发现了 AI 迁移的错误,你手动修改成正确答案后,就可以把这次迁移作为示例放在 prompt 中,帮助 AI 避免类似的错误。

Chain-of-Thought Prompting

Chain-of-Thought(思维链)是一种通过引导 AI 分步骤思考来解决复杂问题的方法。通过让 AI 展开其思考过程,能够提高其解决问题的准确性和逻辑性。这也就是新闻里常出现的 CoT。

思维链的优势:

  1. 增强逻辑性:通过分步骤的思考,AI 能够更有条理地处理复杂问题,减少遗漏和错误。
  2. 提高透明度:展示 AI 的思考过程,便于我们理解其决策路径,从而更好地评估和优化。
  3. 促进问题分解:将复杂问题拆解为更小的子问题,使得解决过程更加高效和可控。

本质上就是在 prompt AI 时,从“帮我解决这个问题”变成“请按照以下步骤帮助我解决该问题”,例如当我们遇到一个报错时,我们可以这样 prompt:

我在运行以下 JavaScript 代码时遇到了错误:
{xxx}

这是我的代码:
{xxx}

请按照以下步骤帮助我分析导致这个错误的原因,并提供相应的解决方案:

1. 分析错误信息:解释错误信息中提到的具体问题。
2. 检查代码逻辑:逐步检查代码中的潜在问题。
3. 提出解决方案:基于上述分析,提供修复代码的建议。

这种 prompt 技巧,可以引导 AI 按照固定的流程方向进行思考,从而产生更高质量的结果。

比较适合 CoT 的场景还有:

我需要开发一个用户登录系统,要求如下:

1. 用户输入用户名和密码。
2. 系统验证用户名和密码是否正确。
3. 如果验证通过,记录用户的登录时间。
4. 返回登录成功的信息。
5. 如果验证失败,返回相应的错误信息。

请按照以下步骤帮助我设计并实现这个功能:

1. 功能分解:将整个登录流程分解为具体的子任务。
2. 技术选型:选择合适的技术栈和工具。
3. 代码实现:提供关键部分的代码示例。
4. 安全考虑:指出需要注意的安全问题及解决方法。

甚至,有论文发现,在正常的 prompt 中加入一句“请一步一步地思考(please think step by step)”就能有效地提升输出的效果,并且事实也确实如此。

所以,prompt 中有很多有趣的技巧和知识,甚至会让人会心一笑。

总结

在大模型出现后,有各种各样的 prompt 指导思想、高质量 prompt 集合、prompt 技巧等等,很多是有价值的。但我认为,在 AI 辅助编程的过程中,不是复制粘贴已有的 prompt,而是在理解和掌握常用技巧和思想后,根据当前的场景综合应用。prompt 的核心是思想,而不是单纯的复读机。