2

在 IDE 中构建 AutoDev AI 编程开发智能体框架与 AI 智能体语言?

 1 month ago
source link: https://www.phodal.com/blog/autodev-in-ide/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Posted by: Phodal Huang March 23, 2024, 10:24 p.m.

上周微软发布了自家的 AI 编程和软件开发智能体框架:AutoDev,其与我们开发的 IDE 插件 AutoDev 有颇多的相似之处,特别是一些设计思路,以及在对于辅助软件开发任务的智能体以及一些基础设施上。

稍有不同的是:

  • 交互介质。我们的 AutoDev 构建基于 IDE API 体系构建的,而微软的 AutoDev 则是构建以 CLI 为主。
  • 隔离环境。我们设计了 DevIns 语言来构建隔离环境,而微软的 AutoDev 则是基于 Docker 隔离环境。

由于我们的 AutoDev 主要是我在开发的,所以相比于微软的 AutoDev 成熟度上是相对比较低的。考虑到我们的 AutoDev 是一年前开源的,而微软的 AutoDev 是最近发布的,我觉得吧,他们取名有点不厚道,不过也是一种 “致敬” 吧。

AI 驱动软件开发的本质:“人类—AI—代码”的桥梁

对于 AI 驱动的自动编程来说,无非就是让 AI 能理解好人类的需求,然后实现 AI 与代码环境的自动交互。 更详细来说,便是:

  • 人类通过自然语言或者交互描述软件开发任务,如解释代码、生成代码、运行测试等。
  • AI 结合智能体与上下文理解人类的需求,并生成对应的指令文本。
  • 代码环境接收指令文本,并执行对应的操作,再返回结果给人类或者 AI。

也因此,我们所要构建的上是一个基于 “人类—AI—代码环境” 的沟通桥梁。实现让 AI 能理解人类的需求,并不是一件复杂的事情,通过额外的需求澄清、展开, 就有初步得到格式化后的需求。而让 AI 与代码环境进行交互,则是一件更复杂的事情,即如何通过指令文本来实现的。

方式 1:基于文本的函数调用

函数调用(Function calling)可以让开发人员声名一系列的函数,将其与对应的说明传递给语言模型,让语言模型根据这些说明来生成格式化的结果。随后, 在对应的工具中,调用对应的 API 来实现对应的操作。 诸如于 Google AI 中语言模型生成的返回结果示例:

{
  "functionCall": {
    "name": "find_theaters",
    "args": {
      "movie": "Barbie",
      "location": "Mountain View, CA"
    }
  }
}

相似的方式,还有让 AI 生成对应的代码,如 shell 等,然后执行对应的代码。

方式 2:语言抽象的开发环境

我们对于自动化的探索是来自于 AutoDev 第一个需求,针对 Spring 框架的 AutoCRUD。在这个需求中,我们发现在复杂的软件开发任务中,需要动态生成 高质量上下文,以让 AI 能在对应的问题域中生成对应的代码。诸如于,生成 Controller 代码,需要知道现有 Controller,规范,以及对应的 Service、Repository 等代码。这一系列的信息,意味着,我们需要一个更高级别的语言来描述这些信息。

随后,我们在 AutoDev 中构建了一系列 Auto 功能(针对 React 的 AutoPage、针对鸿蒙操作系统的 AutoArkUI 等),以探索更合适的语言抽象来描述 “人类—AI—代码环境”,即 DevIns 语言。通过语言来作为人机接口,并作为可执行的代码,来实现对代码环境的操作。诸如于:

/patch

```patch
// the patch to apply
```

通过形式化的方式来描述对 IDE 的操作,易于让 AI 理解,也易于让代码环境执行。

设计基于 IDE 的编程智能体开发

在设计 AutoDev 的自动编码功能时,我们依旧是按照在 Unit Mesh 架构范式下的设计思路来设计的, 即 AI 生成的都是可验证的代码。也因此,在我们设计 AutoDev 的自动测试功能时,也是基于这个思路来设计的。当然了,在有了 DevIns 语言后,就能实现 更多的自动化(理论上)。

接下来,让我们从实际的需求出发,以三个例子来看看日常的编码可以如何设计:

  1. 验证生成代码是否工作?
  2. 进行安全的代码信息提交?
  3. 探索自动化问题辅助修复?

当然了,还可以有更多的不同示例,这里就不一一列举了。

示例步骤 1:经验证可工作的代码

不论是人类,还是 LLM,要验证一段代码是否工作正确,最简单的方式就是运行它。运行它,通常有多种方式:

  1. 直接启动应用。通用 IDE 或者 CLI 来启动应用程序,通过交互界面或者 API 来验证代码的正确性。
  2. 单元测试验证代码。即通过生成单元测试,以验证生成业务代码的正确性。
  3. 构建 REPL 环境。而交互环境对于复杂应用的依赖管理并非易事,所以并非那么容易。

与启动应用的效率相比,显然通过测试驱动开发(TDD)来验证代码的正确性更加高效。 因此,在结合 IDE 时,则需要多考虑一步:如何运行测试以验证代码。

于是,我们设计了一个简单的测试运行指令:

/run:src/test/java/cc/unitmesh/MathHelperTest.java

这样当我们生成了代码后,便可以通过运行测试来验证代码的正确性。由于 Intellij IDEA 支持不同的语言,但是不同的语言运行方式等是不同的。而由于 JetBrains IDE 使用了统一的底层抽象:RunConfiguration,因此我们构建了一个 RunService 来封装不同语言的运行方式:

interface RunService {
    fun createConfiguration(project: Project, virtualFile: VirtualFile): RunConfiguration? = null
    fun runFile(project: Project, virtualFile: VirtualFile): String? { }
}

更详细可以参见 RunService.kt 代码。

示例步骤 2:安全的 Git 操作

既然,我们生成了可验证的代码,那么下一步,我们应该考虑的是结合 VCS 来进行代码提交。为了确保不执行不安全的操作,我们不直接执行 Git 操作,而 是借助于 IDE 的 VCS API 来执行对应的操作。

于是,我们设计了 /commit 指令来提交代码:

/commit

```commit
feat: add new feature
```

而对于需要更复杂的场景,诸如于远程生成的任务来说,我们通过 /patch 指令来

示例步骤 3:自动化问题辅助修复

接下来,我们的挑战就是如何在 IDE 获取运行结果,并根据结果来进行对应的操作。于是,我们在 AutoDev 中设计了一个 DevInsProcessProcessor 来 处理 DevIns 指令的执行结果:

when {
    event.exitCode == 0 -> {
        val comment = lookupFlagComment(devInFile!!).firstOrNull() ?: return
        /// handle flag comments
    }
    event.exitCode != 0 -> {
        project.service<DevInsConversationService>().tryFixWithLlm(scriptPath)
    }
}
  • 成功。当 exitCode 为 0 时,我们可以通过 flag comments 来决定如何处理
  • 失败。当 exitCode 不为 0 (如 -1)时,我们则可以继续通过 AI 来尝试修复对应的问题

在失败的场景时,我们需要构建完整的上下文:输入、编译输出、 执行结果/LLM 返回结果,以便于 AI 能更好的理解问题,再给出对应的修复方案。

更详细可以参见 DevInsProcessProcessor.kt 代码。

我们依旧还在设计适用于 IDE 的自动开发框架与 DevIns 语言,如果大家有兴趣,可以参与到我们的开发中来。

GitHub:https://github.com/unit-mesh/auto-dev


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK