专注于产品开发平台解决方案

借助生成式AI进行更智能的API审查
分享:

本文翻译自:Smarter API Reviews With Gen AI

原文作者:Qt Group软件工程师Daniel Smith


随着生成式AI的兴起,各企业正试图探索如何在其环境中实施,以提升流程的效率。或许最佳切入点是寻找流程中的现有痛点,然后思考AI如何应对这些问题。(本篇博文由真人撰写)

Dall-E 3图像生成提示语:一台设计时尚的未来智能机器人坐在电脑前,分析屏幕上代码的差异。机器人外观友好、平易近人,屏幕展示了带有高亮部分的复杂代码。周围环境暗示这是一个现代化的高科技办公空间。

历史背景

Qt Project的一个主要痛点历来是在版本发布前按时完成API审查。API的增加和改动对Qt框架的使用方式有重大影响,并且对已有API的改动经常会破坏用户的系统兼容性,所以这些变更必须在加入最终发布版本之前仔细审查。为Qt添加新功能通常意味着引入新的API,我们希望未来这些新API能够为用户提供良好的设计和稳定的使用体验。然而有时现有的API免不了发生变更,我们也需要确保这些变更是经过深思熟虑的,而且除了变更外,没有其他替代方案能够避免破坏兼容性。

这种做法虽然多年来行之有效,但在一些重要API更改初步合并后,却因为在发布审查时需要撤回或在最终发布前进行重大修改,而导致发布日期多次延误。为了缩短API变更实施与最终发布准备之间的时间差,我们希望在周期的早期阶段进行API变更审查。但该如何实现呢?

最初的讨论主要集中在简单地为任何头文件变动打上标签以供人工审查,但这样的解决方案太过繁缛。反而会导致工作量增加。但是,如果我们可以让AI承担一部分初步的代码分析任务,至少可以用它来判断某个改动是否“重大”,这样会不会更好呢?

什么是GPT?

GPT,即“生成式预训练模型”(Generative Pretrained Transformer),是一个能“理解”数据之间关系的复杂数学模型。在使用您可能熟悉的ChatGPT这类工具时,该模型通过语言数据进行了训练,创建了一个模型来描述语言中的词汇如何相互关联。通过研究书籍、文档、博文、录音转写等中的数十亿个词汇,该模型能够理解一个词汇如何根据不同的上下文与另一个词汇相关联。通过这种方式,它也能理解新的输入并逐词生成输出,形成类似我们聊天时的回应。当它为输出生成一个新词时,它会回顾上下文并生成句子中接下来最可能出现的词。

如今,市面上有很多LLM(Large Language Model,大型语言模型),而OpenAI的GPT-4模型就是其中之一。到目前为止,GPT已经经历了四次重大迭代,每一次都在功能、记忆力和理解力方面超越了之前的模型。

尽管GPT通常来说是用于理解人类语言,但它实际上只是一个模型,也可以被训练用来理解如代码等其他类型数据间的关系。这意味着我们可以与它讨论某段代码,并要求它进行分析,或是通过往复对话来帮助我们自己更好地理解代码。

API审查的现状

自去年12月中旬以来,我们一直在运行一个概念验证机器人,监控提交到codereview.qt-project.org的更改,并通过GPT-4进行diff(代码差异)分析。由于生成式AI不会自行作出反应,它必须根据提示语来生成输出。提示语可以包含指令、请求、上下文信息等。以下是我们用于API审查的提示语,以及提供的一段原始代码更改差异:

(摘要)“任务:对公共头文件中的更改进行分类,判定它们是否对API的行为和使用具有重大影响。其他说明:对公共头文件中‘private:’部分的更改并非重大;对平台插件的更改,有时通过文件路径标识,也是不重要的;仅限空格的更改也非重大变更;……”

除了需要结合一些后端方法以便强行让GPT提供相关响应之外,GPT-4的表现一般来说是可靠的。在大约一个月的运行时间中,超过230个更改被标记为“需要API审查”,每个更改都会接收到一个简要分析,解释了哪些变更对公共API的使用和操作有显著影响。

新增API的示例

在本示例中,QRemoteObjectHost增加了新的功能。

diff --git a/.../qconnectionfactories.h b/.../qconnectionfactories.hindex b56a34f..7eabfd6 100644--- a/src/remoteobjects/qconnectionfactories.h+++ b/src/remoteobjects/qconnectionfactories.h@@ -18,7 +18,7 @@#include <QtNetwork/qabstractsocket.h>#include <QtRemoteObjects/qtremoteobjectglobal.h>-+#include <QtRemoteObjects/qremoteobjectnode.h>QT_BEGIN_NAMESPACE@@ -116,6 +116,7 @@Q_SIGNALS:
 void shouldReconnect(QtROClientIoDevice*);+ void setError(QRemoteObjectNode::ErrorCode);protected:
 virtual void doDisconnectFromServer() = 0;


diff --git a/.../qremoteobjectnode.h b/.../qremoteobjectnode.h
index 18f75bc..13e2540 100644
--- a/src/remoteobjects/qremoteobjectnode.h
+++ b/src/remoteobjects/qremoteobjectnode.h
@@ -6,6 +6,7 @@
#include <QtCore/qsharedpointer.h>
#include <QtCore/qmetaobject.h>
+#include <QtNetwork/qlocalserver.h>
#include <QtRemoteObjects/qtremoteobjectglobal.h>
#include <QtRemoteObjects/qremoteobjectregistry.h>
#include <QtRemoteObjects/qremoteobjectdynamicreplica.h>
@@ -61,7 +62,8 @@
 MissingObjectName,
 HostUrlInvalid,
 ProtocolMismatch,
- ListenFailed
+ ListenFailed,
+ SocketAccessError
 };
 Q_ENUM(ErrorCode)
@@ -181,6 +183,7 @@
 ~QRemoteObjectHost() override;
 QUrl hostUrl() const override;
 bool setHostUrl(const QUrl &hostAddress, AllowedSchemas allowedSchemas=BuiltInSchemasOnly) override;
+ static void setLocalServerOptions(QLocalServer::SocketOptions options);
Q_SIGNALS:
 void hostUrlChanged();

在输入提示语后,GPT-4提供了以下对新增API的分析,并进行了很好的总结:

  1. 在qconnectionfactories.h中添加了新的#include 指令,这可能会暴露以前在该头文件中不可用的额外符号或类型。这一变更可能会影响那些包含此头文件的用户。

  2. 在qconnectionfactories.h文件中添加setError(QRemoteObjectNode::ErrorCode)信号是一个重大变更。这代表了一个通过错误代码发出错误信号的新API,这将影响API用户处理错误状态的方式。

  3. 在qremoteobjectnode.h中的ErrorCode枚举中新增了一个SocketAccessError枚举值,这是一个重大变更。这增加了一个API用户需要处理的新潜在错误状态。

  4. 在qremoteobjectnode.h中向QRemoteObjectHost类添加静态void setLocalServerOptions(QLocalServer::SocketOptions options)函数是一个重大变更。它引入了一个新的API,用于设置本地服务器的选项,这将影响用户配置QRemoteObjectHost实例的方式。

这些更改不仅会影响现有代码的行为(如果它依赖以前的错误代码或者没有考虑到新的setError信号),还会影响开发者使用API的方式(通过提供新的方法和配置选项)。

在这种情况下,GPT能够侦测到新的行为变化,并提醒开发者虽然这是一项新增功能,如果现有用户的代码还没有准备好处理新信号,它有可能导致问题。这样的警告可能有助于提醒审查者,在合并更改之前可能需要进行额外的审查,甚至需要在API文档中添加注释。

现有API更改示例

当在qvariantanimation.h中对现有API进行简单更改时,机器人会看到这些内容:

diff --git a/.../qvariantanimation.h b/.../qvariantanimation.h
index 640c057..4bdb971 100644
--- a/src/corelib/animation/qvariantanimation.h
+++ b/src/corelib/animation/qvariantanimation.h
@@ -26,7 +26,7 @@
BINDABLE bindableEasingCurve)
public:
- typedef QPair<qreal, QVariant> KeyValue;
+ using KeyValue = std::pair<qreal, QVariant>;
typedef QList<KeyValue> KeyValues;
QVariantAnimation(QObject *parent = nullptr);

输入提示语后,GPT-4提供了以下分析,可用于向审查者明确阐述所做更改的具体内容:

在qvariantanimation.h中,KeyValue的定义从typedef更改为using声明,这将基础类型从QPair更改为std::pair,可能会影响API用户与KeyValue实例的交互方式。

早期尝试

生成式AI总是有些难以捉摸。正如之前所述,它会不断地猜测下一个词,一次又一次,直到认为应该停止为止。(你可以了解一下LLM的原理,确实令人困惑。)虽然GPT 3.5非常适合用来闲聊,但它经常忘记你告诉它的内容,也不太能很好地遵循指令。它还会受到“近因偏见”的影响,即在你的提示语中,靠后的词被认为比前面的词更重要。这带来了一些问题,因为有些指令可能被忽略,或者更糟糕的是,生成输出时可能完全未考虑某些变更的差异。这导致使用GPT 3.5进行分析时效果不够理想。即便采取措施要求输出更加一致、降低创造性,它仍然会不一致地忽略掉请求的整个部分,或者完全臆想出变更中新增或删除了什么。

Dall-E 3提示语:一个自信的机器人在办公室里,自豪地在白板上展示错误信息。这个机器人显得自信满满,面带微笑地用记号笔划线标出错误的答案。这一幕略带幽默,突显了机器人对其错误答案的错误自信。环境是现代办公室,白板上满是方程式和文本,其中一些部分被明显地标记为错误,但机器人似乎毫无察觉

2023年11月,微软推出了GPT 3.5的增强版,命名为3.5-instruct,顾名思义,这一版本是为了提高其遵循指令的能力。尽管这样做有所改进,但模型仍然对实际改变的差异产生了错误的幻觉。

为了解决这些问题中的一些,尝试了best-of-three模型,需要三次尝试中的两次同意变更的重要性才能采纳结果。这至少提高了整体的准确性,但输出仍然缺乏细节,并且明显由于没有完全理解在软件开发层面所做的改变而受到影响。

当GPT-4的成本降低后,即使是粗略的测试也显示出了显著改善的结果。GPT-4对其所读内容有更好的记忆力,并能对其作出决策背后的推理进行更清晰的说明。由于准确性更高,机器人被重新调整为单次配置,自那以后只需对提示语做一些小调整。

下一步及未来发展

AI并不是万能的解决方案。即便它有这个潜力,那也与现在仍相去甚远。与人类不同,它不知道自己实际在做什么,它只是通过选择最可能的下一个词来响应上下文提示语。这意味着,如果它在任何中间点上选择错误,剩余的回复可能会走向错误的方向,产生一个充满自信但非常错误的答案。我们可以采取一些措施来缓解这个问题,但这需要时间和成本。尽管当前一代的生成式AI存在不足,但我们已经为减轻API审查的工作量打下了坚实的基础。

下一步,我们希望探索更大的上下文以评估变更的重要性,包括在单次评估中包含多个文件,以便完全理解整个变更,而不是像目前这样逐个文件进行评估。此外,我们希望未来的GPT迭代或相关LLM技术能够提高准确率和遵循指令的能力。尽管GPT-4在这个用例上远超GPT-3.5,但它仍然会不时犯错、忽略上下文。

结论

通过在代码合并时批量审查过渡到单次变更的审查,我们可以节省发布前紧急时期审查所需时间。新方法也为讨论提供了更多背景,如此变更的必要性的讨论可以一次到位。在我们传统的审查流程中,一个相当复杂的脚本执行了一堆硬编码逻辑来排除不相关的代码行,创建了一次性提交,集合了所有的API变更以供审查。虽然先前的方法能快速提供变更的概览,但缺失了必要的上下文。另外,追踪变更的源头和讨论其重要性也是一个耗时的过程。考虑到编写变更与进行API审查之间可能会有相当一段时间的间隔,回忆每个变更背后的原因可能颇具挑战性。

这个概念验证版的API审查机器人只是帮助Qt项目的所有参与者更方便地做出贡献,并更快地使变更得到关注的辅助工具。虽然到头来每次变更仍需人工审核,我们仍希望这一新机器人能让这一流程变得更加轻松。

行业痛点
解决方案
应用案例