使用LLMs进行自动化代码审查

使用LLMs进行自动化代码审查

Barry Lv6

Faire如何实施基于LLM的自动化审核流程以提升生产力

大型语言模型(LLMs)在科技行业引起了巨大的关注,改善了各种自然语言处理和任务自动化的形式,特别是在ChatGPT 推出后。OpenAI推出其API服务后不久,Faire组建了一个AI基础团队,以更好地理解和利用这一领域。我们举办了一场为期三天的AI黑客马拉松——就像我们的黑客周 ——公司各个部门的贡献者组成团队,原型设计了各种基于LLM的功能创意。此次活动取得了巨大的成功,共提交了92个项目。

Faire工程团队进一步投资的项目之一是自动化审核。在Faire,我们相信代码审核 的价值。这个过程是我们编写代码的核心。虽然代码审核的许多方面需要对项目有更深入的了解,但也有一系列更通用的审核要求可以在没有额外上下文的情况下考虑。这些包括期望有清晰的标题和描述、充分的测试覆盖、风格指南的执行,或者识别跨服务边界的向后不兼容更改。

通常,LLMs非常适合执行代码审核的更通用方面。只要提供足够的关于拉取请求的信息,例如元数据、差异、构建日志或测试覆盖报告,就可以有效地提示LLMs附加有用的信息、标记危险更改,甚至自动修复简单问题。

在本文中,我们描述了Faire开发的审核生命周期和RAG(检索增强生成)设置,以支持一系列特定上下文的自动化审核。我们还将重点介绍我们的测试覆盖审核,突出低覆盖率的区域并建议额外的测试用例,作为审核自动化灵活性和价值的示范。

Fairey,我们的 LLM 协调服务

Faire 开发了一种 LLM 协调服务,我们称之为 Fairey 🧚。该协调服务处理用户的聊天请求,将其分解为生成响应所需的所有必要步骤。这包括调用 LLM 模型、获取额外信息、调用函数以及其他各种逻辑。

Fairey 与 OpenAI 的 Assistants APIs 深度集成。我们创建了一个简单的用户界面来管理 AI 助手,例如调整助手的指令或添加可以调用的函数。函数为助手提供了额外的能力,例如获取额外信息 — 这种技术称为 RAG(检索增强生成)。

RAG 正迅速成为为 LLM 提供必要信息以执行特定上下文任务的行业标准方法。LLM 在极其广泛的信息集上进行训练,但通常不包括您公司的专有数据。而且,即使您已经开始微调开源模型,例如 Faire 所做的 ,LLM 通常仍然需要任何特定案例的信息 — 在我们的审查流程中,这就是关于正在审查的代码更改的信息。

每个函数调用在协调服务中都有一个定义的回调,我们在 GPT 决定我们应该调用时进行调用。这使得 Fairey 能够决定何时获取所需的数据,而不必在主提示中发送大量额外信息。

审核生命周期

Fairey 还连接到 Github 的 webhooks ,在 pull request 上发生有趣事件时会发送事件负载。我们以多种方式响应每个 Github webhook,包括检查是否有自动审核现在可以处理该 pull request。对于符合审核标准的 pull request(例如编程语言或特定的差异内容),Fairey 将与 OpenAI 进行交互以执行审核。

与 OpenAI 交互后,我们检查输出以查看 Fairey 是否有有用的信息。当有时,我们会在 pull request 上发布审核。审核通常包括评论和提示,甚至可能包括代码的具体更改建议。

为了避免重复审核,Fairey 留下的每个审核还将包括一些隐藏的元数据,以便我们可以检查已覆盖的内容。这也可能允许在先前的审核线程上继续,或者将早期审核的输出作为增量审核的输入。

测试覆盖率审查

为了更好地展示Faire的自动审查是如何工作的,我们将更深入地探讨测试覆盖率审查。测试覆盖率是一种衡量在运行测试套件时,特定代码分支是否实际执行的方法。为了简化说明,让我们想象一个实现新功能的拉取请求,该功能由我们的设置框架 中的新设置保护。代码可能看起来像这样:

1
2
3
4
5
6
const isSettingEnabled = getSettingValue();
if (myNewSetting) {
doTheNewThing();
} else {
doTheOldThing();
}

测试覆盖率告诉我们我们的测试是否覆盖了设置启用和未启用的代码分支。也许我们还没有为新代码添加测试,代码审查者会指出我们需要添加测试!

在我们的前端(Web)代码库中,Faire使用Jest 作为单元测试套件。我们强制要求用户可见的代码区域有最低覆盖率。我们在拉取请求中的测试覆盖率是增量计算的,这意味着当我们创建拉取请求时,我们的CI系统将使用--changedSince标志执行测试套件,从而导致Jest仅执行与拉取请求中包含的源文件相关的测试。我们还设置了--coverage标志,这会导致Jest收集并输出覆盖率报告,随后可以在我们的内部开发门户中查看(下面是一个示例)。

每当我们收到来自Github的Webhook,表明测试覆盖率检查运行已完成且结果为失败时,测试覆盖率审查会被触发。当覆盖率检查失败时,这意味着拉取请求中的增量覆盖率低于我们要求的覆盖率阈值。通常,这将是因为作者添加了新的源代码文件,但没有相应的单元测试,或者在代码中创建了新的分支,而这些分支没有被现有测试覆盖。

测试覆盖率审查使用一个自定义助手(如上图所示)。它可以访问几个重要的功能工具,包括:

  • **fech_github_diff** 用于获取拉取请求中更改的差异
  • **fetch_github_pull_request** 获取拉取请求的元数据,例如标题、描述等
  • **fetch_code_coverage_report** 从我们的CI构建工件中加载lcov报告
  • **fetch_github_file_contents** 加载给定文件的完整内容

助手的指令 ,与系统提示消息 同义,大致如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
You are an expert React and Typescript programmer.
Using the test coverage reports and contents of source code and corresponding
test files, suggest new test cases that would increase coverage of the source
code.

Test files in our frontend codebases are located in a `./__tests__/` folder
next to the source code, with `*.test.ts` suffix instead of `*.ts`. A couple
of examples,
```yaml
- source: `src/something.ts`
tests: `src/__tests__/something.test.ts`.
- source: `app/home/utils.ts`
tests: `app/home/__tests__/utils.test.ts`
1
2
3
4
5
6
7
8
9
10
我们的代码库在每个拉取请求中收集[lcov](https://wiki.documentfoundation.org/Development/Lcov)代码覆盖率报告。测试覆盖率审查被指示使用覆盖率报告来确定代码中覆盖率较低的区域。

除了这些系统级助手指令外,审查还有一个特定的提示模板。发出的聊天消息将大致如下:

```python
You are performing a code review of ${diffSource}. The PR has failing test coverage checks for ${failingCoverage.join(",")}.

Use the diff and the code coverage report to identify the changes in the PR that are not covered by tests.
For those changes, suggest test cases that should be added to the code.
Use the existing test file as a reference when suggesting test cases. Do not suggest cases that are already covered by the existing tests.

当用户提交拉取请求时,我们的构建流水线最终会报告测试覆盖率检查运行失败。这导致Fairey启动助手聊天,使用上述提示。为了产生响应,ChatGPT将发出功能调用,Fairey执行这些调用并将其提交回ChatGPT,最终生成回复消息。

然后,我们使用回复撰写Github审查,并将其发布到拉取请求中。

评估评论

LLMs 在输入方面非常灵活,但输出却各不相同。GPT(生成预训练变换器)模型只是预测模型,它们可能会出错,完全产生幻觉,或者生成过于冗长或无关的输出。我们使用两个信号来评估评论的质量:一个是定量的,另一个是定性的。我们的定量评估涉及使用 LLM 评估框架,而定性评估则涉及对最终用户(我们的工程师)进行调查。

为了量化质量,我们构建了一组测试用例,迭代评论并重新运行,计算正确性得分。为了实现这一流程,Faire 利用 LLM 评估工具,例如 Gentrace CometLLM Langsmith 。每当我们进行评论时,我们都会将输入和输出转发给评估工具。每对输入/输出都可以用来创建一个测试用例。然后,在我们对评论进行迭代时(例如调整提示、改变模型、获取不同数据等),我们可以在所有测试用例上重新运行评论管道。接着,我们使用 LLM 来评估结果的质量。

没错——LLM 评论输出是使用……一个 LLM 进行评估的!

至于定性评分,我们利用 DX ,这是一个用于衡量和提升开发人员生产力的平台。每当进行评论时,DX 会收到通知,并向拉取请求的作者征求调查反馈。反馈,无论好坏,都会被传入 Gentrace,在那里可以作为良好测试用例的过滤信号。

Fixtures

拉取请求的审查本质上是短暂的。通常,在工程师准备迭代行为并重新评估时,与审查相关的拉取请求信息会发生变化。

为了适应这一点,Fairey支持“fixtures”功能,其中函数调用的输出被保存为快照,以便在后续运行中重复使用。我们通过使用OpenAI线程历史来实现这一点,将函数调用的结果提取到fixture文件中,然后上传到存储。当在后来的日期运行审查时,这些fixture文件会被读取,并作为覆盖项传递给Fairey,以指定ChatGPT在调用函数时应提供的内容。

自动审查的早期影响

在Faire,我们相信赋予生产力。使用LLMs自动化审查有助于通过简化审查过程、减少简单问题的审查延迟,并使我们的才能能够专注于审查中最有影响力和复杂的部分,例如正确满足产品要求、实施清晰的架构、长期可维护性和适当的代码重用。

到目前为止,我们已经看到了成功——通过用户满意度高和准确性高来衡量——自动审查的效果:

  • 强制执行风格指南
  • 评估标题和描述的质量
  • 诊断构建失败,包括发出自动修复操作
  • 建议额外的测试覆盖率
  • 检测向后不兼容的更改

仍然有成长的空间——每种新类型的审查,输出质量最初会有很大差异。我们发现,使用LLMs,繁琐的输入会产生不可预测的输出。获得C的三重标准——一致、简洁和正确——需要对输入内容和结构进行大量的精炼,广泛的测试用例,以及使用更复杂的提示技术,如自我评估和CoT(链式思维)。通过我们的迭代周期,工程师能够自信地对提示和能力进行迭代,以提高效率。

当正确使用时,LLMs可以为工程师提供一个令人兴奋的新层次的帮助,以快速向我们的用户交付优质产品。我们对乘风破浪的生成式AI感到兴奋。🌊

  • 标题: 使用LLMs进行自动化代码审查
  • 作者: Barry
  • 创建于 : 2024-08-20 00:44:25
  • 更新于 : 2024-08-31 06:59:45
  • 链接: https://wx.role.fun/2024/08/20/ce45f6ffd53544688656341d4807d4fd/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。