开发者分享 GPT Pilot 完整使用流程和最佳实践,涵盖代码生成、迭代优化到生产就绪的全周期。对 AI 编码工作流改进有直接参考。
这是一个三篇系列博客中的第二篇。在这个系列中,我将介绍我们如何创建 GPT Pilot——一个旨在大规模运行,并在开发者协助下构建可用于生产环境的应用的 AI 编程智能体。在本系列的第 1 部分中,我从宏观层面概述了 GPT Pilot。其核心理念是,如今 AI 已经能够完成我们开发者所做的全部编码工作中的 95%。你可以看看我是如何使用 ChatGPT 在 2 小时内编写出一个完整的 Redis 代理的,而这项工作通常需要开发者投入 20~30 个小时。不过,如果一个应用无法完全正常运行,或者不能解决用户的问题,那它就毫无用处。因此,在真正的 AGI 到来之前,你仍然需要一名开发者。
GPT Pilot 就是这样诞生的。它被设计为完成所需编码工作的 95%,并在需要时请求开发者审查,例如当它陷入困境、无法继续推进,或者需要 API 密钥等应用外部资源时。
在本文中,我将带你了解 GPT Pilot 编写应用时所经历的完整流程。我会通过图示直观呈现 GPT Pilot 幕后发生的一切。我是一个视觉型的人,所以总是会制作图示。要理解 GPT Pilot 的编码机制,需要掌握三个概念——上下文回退、递归对话和 TDD。可以参阅我在本系列第 1 部分中的介绍,我在那里对这些概念进行了说明。
GPT Pilot 的编码工作流包含 8 个步骤:
获取队列中的下一个开发任务
获取队列中的下一个开发任务
将任务拆分为多个开发步骤
将任务拆分为多个开发步骤
获取下一个开发步骤
获取下一个开发步骤
获取当前已经实现的代码
获取当前已经实现的代码
为当前步骤编写代码
为当前步骤编写代码
运行代码或命令
运行代码或命令
测试新的代码变更
测试新的代码变更
调试当前开发步骤,或者进入下一步
调试当前开发步骤,或者进入下一步
编码工作流是 GPT Pilot 中我最喜欢的部分,所以让我们深入了解一下。下面是一张直观展示其工作方式的图:
本文将反复提到两个重要概念——开发任务和开发步骤。
GPT Pilot 的工作方式是:在拆解应用开发规格之后,它会创建一系列开发任务,最终实现一个完全可用的应用。开发任务本质上是对需要完成的工作进行高层次描述,开发者会接手这些任务并开始实现。你可以把它们理解为 Jira 中的任务(顺便说一句,我讨厌 Jira……不知道有没有人有同感,但我就是想把这句话说出来)。下面是一个开发任务的示例:
在上图中,你可以看到任务包含 3 个属性:
description:为了完成这项任务,需要实现什么
description:为了完成这项任务,需要实现什么
user_review_goal:主开发者如何判断当前任务是否已经完成——GPT Pilot 的一个关键支柱是,开发者必须参与整个编码过程,这样你才能确保开发过程按计划推进,并在此过程中逐步理解整个代码库
user_review_goal:主开发者如何判断当前任务是否已经完成——GPT Pilot 的一个关键支柱是,开发者必须参与整个编码过程,这样你才能确保开发过程按计划推进,并在此过程中逐步理解整个代码库
programmatic_goal:GPT Pilot 应该编写哪种自动化测试,以验证整个开发任务是否按预期工作。完成一个开发步骤后,GPT Pilot 会编写单元测试;完成一个开发任务后,它会编写集成测试或 E2E 测试。
programmatic_goal:GPT Pilot 应该编写哪种自动化测试,以验证整个开发任务是否按预期工作。完成一个开发步骤后,GPT Pilot 会编写单元测试;完成一个开发任务后,它会编写集成测试或 E2E 测试。
现在,当你开始开发 Jira 中的一项任务(开发任务)时,会把它拆分成更小的部分(我们称之为开发步骤),这些部分是你准备在代码库中逐项实现的、可执行的工作项。每个开发步骤都可以属于以下类型之一:
运行命令——需要在机器上运行的命令,例如安装依赖项、启动应用以检查之前实现的步骤是否正常工作,或者创建文件夹。
运行命令——需要在机器上运行的命令,例如安装依赖项、启动应用以检查之前实现的步骤是否正常工作,或者创建文件夹。
代码变更——最重要的开发步骤,它准确说明了为了完成当前步骤,需要在实际代码中实现什么。它可能包含需要编写的新代码,也可能包含需要修改的代码。其工作方式是,代码变更会以详细且人类可读的方式描述需要实现的内容。其中既包含需要实现的代码,也包含对这些代码用途的说明。这与你要求 ChatGPT 编写某项功能时非常相似。它不仅会给出代码,还会解释为什么要这样编写。之所以如此,是因为代码实现并没有那么简单。有时,我们需要把一段代码添加到现有代码中,或者修改现有实现。因此,我们将编码变更的概要(这个开发任务)与变更的实际实现分离开来,而 CodeMonkey 智能体专门负责后者。我会在「#3 编码」一节中对此进行更深入的介绍。下面是一个代码变更的示例:
代码变更——最重要的开发步骤,它准确说明了为了完成当前步骤,需要在实际代码中实现什么。它可能包含需要编写的新代码,也可能包含需要修改的代码。其工作方式是,代码变更会以详细且人类可读的方式描述需要实现的内容。其中既包含需要实现的代码,也包含对这些代码用途的说明。这与你要求 ChatGPT 编写某项功能时非常相似。它不仅会给出代码,还会解释为什么要这样编写。
之所以如此,是因为代码实现并没有那么简单。有时,我们需要把一段代码添加到现有代码中,或者修改现有实现。因此,我们将编码变更的概要(这个开发任务)与变更的实际实现分离开来,而 CodeMonkey 智能体专门负责后者。我会在「#3 编码」一节中对此进行更深入的介绍。下面是一个代码变更的示例:
人工介入——AI 无法独立完成、需要人工协助才能完成的开发步骤。此时,GPT Pilot 会要求开发者执行某项操作;完成后,开发者输入“continue”,GPT Pilot 就会继续实现。以下是可能需要人工介入的一些原因:
人工介入——AI 无法独立完成、需要人工协助才能完成的开发步骤。此时,GPT Pilot 会要求开发者执行某项操作;完成后,开发者输入“continue”,GPT Pilot 就会继续实现。以下是可能需要人工介入的一些原因:
需要 API 密钥(例如,用于从 Twitter 获取数据的 Twitter API 密钥)
需要 API 密钥(例如,用于从 Twitter 获取数据的 Twitter API 密钥)
GPT Pilot 陷入了调试过程:它要么用完了整个上下文长度,要么递归对话层次太深,继续沿递归深度推进已经无法带来有效进展。
GPT Pilot 陷入了调试过程:它要么用完了整个上下文长度,要么递归对话层次太深,继续沿递归深度推进已经无法带来有效进展。
GPT Pilot 需要验证某项功能是否按预期工作——例如,GPT Pilot 不确定机器上是否正确安装了 Mongo,可能会要求开发者运行一些 sudo 命令,检查它是否按预期工作。
GPT Pilot 需要验证某项功能是否按预期工作——例如,GPT Pilot 不确定机器上是否正确安装了 Mongo,可能会要求开发者运行一些 sudo 命令,检查它是否按预期工作。
#2 获取当前已经实现的代码
对 AI 来说,编写一个包含代码的新文件很容易,但现实中很少会遇到这种情况。大多数时候,我们都是在现有文件中编写代码,要么修改已有代码,要么添加新代码。只要你把所有现有代码以及需要实现的内容说明都提供给 AI,它就能轻松完成这项工作。问题在于,当应用规模不断扩大、代码库变得非常庞大时,整个代码库就无法放入 LLM 的上下文中。事实上,这是一种非常常见的情况——至少在我们拥有支持 100 万 token 的 LLM 之前都会如此,而这看起来短期内不会实现。
当你在一个大型代码库中处理任务时,通常只会查看代码库中的一小部分(可能是 1,000 行代码),并且只使用这部分代码来实现任务。
因此,为了解决这个问题,并让 GPT Pilot 真正具备可扩展性,从而能够创建和升级大型、可用于生产环境的代码库,我们必须设计一种方法,让 AI 从代码库中选出一个较小的部分(例如那 1,000 行代码),并在其中实现当前任务。完成后,我们只需将最终生成的代码行合并回原始代码库即可。首先,我来解释一下 GPT Pilot 编写代码以及创建新文件和文件夹时会发生什么。对于它需要创建的每个文件和文件夹,它都必须写明自己想创建该文件或文件夹背后的设计思路。例如,它可能想创建一个 utils 文件夹,并为其写下如下描述:
Contains utility modules that provide generic, reusable solutions to common problems encountered throughout the application.
These utilities are not specific to the app's core domain but offer auxiliary functionality to support and streamline the primary codebase.
They encapsulate best practices, reduce code repetition, and make the overall code cleaner and easier to maintain. Examples include functions for data formatting, error handling, debugging tools, string manipulation, data validation, and other shared operations that don't fit within specific modules or components of the app.
现在,对于 GPT Pilot 创建的每个函数,它都会写一段描述,说明该函数应该做什么——这相当于整个代码库的伪代码。
既然你已经了解了 GPT Pilot 编写代码时会发生什么,就能理解它如何为每个开发步骤获取相关代码。
在 GPT Pilot 为每个步骤编写代码之前,它会先在一个完全独立的 LLM 对话中获取代码库的相关部分。该对话分 3 个步骤建立。
将开发步骤的描述、完整的项目文件/文件夹结构,以及每个文件和文件夹的描述提供给 AI。LLM 会据此告诉我们,哪些文件与上述步骤相关。
将开发步骤的描述、完整的项目文件/文件夹结构,以及每个文件和文件夹的描述提供给 AI。LLM 会据此告诉我们,哪些文件与上述步骤相关。
缩小所需文件的范围后,我们会把它列出的每个文件的伪代码提供给 LLM,并要求它告诉我们,哪些函数与当前开发步骤相关。
缩小所需文件的范围后,我们会把它列出的每个文件的伪代码提供给 LLM,并要求它告诉我们,哪些函数与当前开发步骤相关。
确定它选中的伪代码后,我们就可以获取实际代码,并将其放入原始对话中,LLM 会在那里编写需要实现的内容描述。
确定它选中的伪代码后,我们就可以获取实际代码,并将其放入原始对话中,LLM 会在那里编写需要实现的内容描述。
如果应用变得极其庞大,我们可以改进这一流程:先把文件夹提供给 LLM,让它从中选择相关文件夹,然后再向它提供相关文件。在每个步骤之前,我们还可以将对话回退到开头,以便为上下文留出更多空间。
下面的示意图展示了这一流程:
既然我们已经可以创建一条包含实现某项具体任务所需全部代码的 LLM 消息,就可以开始真正的编码过程了。这个过程分为两部分:
首先,LLM 会连同代码一起写出需要实现的内容描述。如果需要编写整个文件,LLM 的响应会包含全部代码;但如果只需修改文件中的一部分代码,LLM 会告诉我们类似这样的内容:在 Mongo 设置之后,添加以下代码行……可以想象,由于这个过程是随机性的,而不是确定性的,因此我们需要确保生成的代码被插入正确的位置,或者已有代码得到正确修改。
这时,CodeMonkey 智能体就会介入。之所以称它为 code monkey,是因为它不做任何决策,而只是实现 Developer 智能体编写的代码。它会接收到与当前任务相关的代码(这些代码此前已由 LLM 在代码获取阶段选出),以及 Developer 智能体在开发步骤 #1 中创建的描述。随后,它只需返回已完整编码的代码段或文件,我们可以直接将其插入代码库或替换代码库中的对应内容。
测试会在两个地方进行——(1)每个开发任务结束后,GPT Pilot 会创建集成测试,验证高层功能是否按预期工作;(2)每个开发步骤结束后,它会创建规模更小的单元测试,确保所有函数都能按预期工作。
GPT Pilot 可以执行 3 种不同类型的测试:
自动化测试是测试某个步骤或任务的首选方式,因为这些测试会被加入回归测试套件,从而让 GPT Pilot 确认新的代码变更没有破坏旧功能。不过,自动化测试并不总是测试新代码的最佳方式。
自动化测试是测试某个步骤或任务的首选方式,因为这些测试会被加入回归测试套件,从而让 GPT Pilot 确认新的代码变更没有破坏旧功能。不过,自动化测试并不总是测试新代码的最佳方式。
命令运行是一种测试方式:我们运行一条特定命令,并将输出提供给 LLM,然后由它告诉我们实现是否成功。例如,我们不需要创建自动化测试来检查能否使用 npm run start 启动应用——在这种情况下,只需运行一条简单命令,就足以检查环境是否已成功配置。
命令运行是一种测试方式:我们运行一条特定命令,并将输出提供给 LLM,然后由它告诉我们实现是否成功。例如,我们不需要创建自动化测试来检查能否使用 npm run start 启动应用——在这种情况下,只需运行一条简单命令,就足以检查环境是否已成功配置。
人工介入是测试应用的最后一种方式,只要 AI 无法自行测试实现,就需要人工介入。例如,当存在某些视觉效果(如 CSS 动画)时,就需要人工检查它们是否正常工作。
人工介入是测试应用的最后一种方式,只要 AI 无法自行测试实现,就需要人工介入。例如,当存在某些视觉效果(如 CSS 动画)时,就需要人工检查它们是否正常工作。
每次运行测试后,如果测试成功,GPT Pilot 就会开始下一个任务或步骤并继续编码;但如果测试失败,GPT Pilot 就需要调试错误。
调试流程必须足够健壮,无论出现什么错误,都能针对任何新产生的 bug 启动调试。它还必须能够调试在调试过程中出现的任何问题。这正是递归对话发挥作用的地方。递归对话是以一种可以“递归”使用的方式建立起来的 LLM 对话。
让我们看看下图中的示例。它展示了 GPT Pilot 在处理一个包含 5 个开发步骤的开发任务时所经历的流程。在这个示例中,开发步骤 #3 时出现了一个错误——假设它实现了一项特定的代码变更,但运行测试后测试失败。随后,它会进入递归层级 #1 来调试这个问题。它把修复该问题所需完成的工作拆分成两个步骤,但在实现第一个步骤时,又出现了另一个错误。例如,修复错误 #1 所需的某个依赖不存在。随后,GPT Pilot 会进入递归层级 #2,并将其拆分为 3 个步骤。在第三个步骤中,又出现了一个错误。于是,它会进入第三个递归层级,其中只有 1 个步骤。成功执行该步骤后,GPT Pilot 会返回递归层级 #2,并完成错误 #2 的调试。之后,它会回到错误 #1 的调试工作中;最后,错误 #1 修复后,它会返回开发步骤 #3,随后继续实现应用。
当递归深入到 5 个层级时,GPT Pilot 会停止调试流程,并要求开发者修复最初导致调试开始的问题。开发者解决该问题后,会将结果写入 GPT Pilot。随后,它便可以继续开发流程,就像这个问题是由它自己调试解决的一样。
在本系列的第一篇文章中,我从宏观层面介绍了 GPT Pilot 的工作原理。在本文中,我描述了 GPT Pilot 的编码工作流,包括:
Developer 和 CodeMonkey 智能体如何协作实现代码(编写新文件或更新现有文件),
Developer 和 CodeMonkey 智能体如何协作实现代码(编写新文件或更新现有文件),
递归对话和上下文回退在实践中如何运作,以及
递归对话和上下文回退在实践中如何运作,以及
在最后一篇文章中,我将深入探讨所有智能体的结构。我们以模块化方式构建这些智能体,因为我们知道它们会随着时间演变。请前往 GitHub、克隆 GPT Pilot 仓库、进行实验,并将你的反馈发送给我。我希望 GPT Pilot 对开发者尽可能有帮助,所以请告诉我你的想法、如何改进,或什么有效。在此文章下方添加评论或发送邮件至 zvonimir@gpt-pilot.ai。
🌟🌟🌟 最后,我们正在筹集资金以继续开发 GPT Pilot,所以如果你能给 GPT Pilot GitHub 仓库点星或与朋友分享,对我们意义重大。感谢你 🙏 🌟🌟🌟
部分评论仅对已登录的访客可见。登录以查看所有评论。
如需进一步操作,你可以考虑屏蔽此人和/或举报滥用。