RepairAgent: An Autonomous, LLM-Based Agent for Program Repair

基本信息

ICSE’25

博客贡献人

JokerLin

作者

Islem Bouzenia, Premkumar Devanbu, Michael Pradel

标签

Large Language Models, Automated Program Repair, Agent

摘要

自动程序修复已成为一种强大的技术,可以减轻软件错误对系统可靠性和用户体验的影响。本文介绍了 RepairAgent,这是第一个通过基于大语言模型 (LLM) 的自治代理来解决程序修复挑战的工作。现有的基于深度学习的方法通过固定提示或固定反馈循环来提示模型,而本文的工作将 LLM 视为能够自主规划和执行操作以通过调用合适的工具来修复错误的代理。 RepairAgent 自由地交叉收集有关错误的信息、收集修复成分和验证修复,同时根据收集的信息和之前修复尝试的反馈来决定调用哪些工具。启用 RepairAgent 的关键贡献包括一组可用于程序修复的工具、允许 LLM 与这些工具交互的动态更新的提示格式,以及指导代理调用工具的有限状态机。本文对流行的 Defects4J 数据集的评估证明了 RepairAgent 在自主修复 164 个错误方面的有效性,其中包括现有技术未修复的 39 个错误。与 LLM 交互的每个 bug 的平均成本为 270k 代币,根据 OpenAI 的 GPT-3.5 模型的当前定价,每个 bug 的成本为 14 美分。据本文所知,这项工作首次提出了一种用于程序修复的基于 LLM 的自主代理,为软件工程中未来基于代理的技术铺平了道路。

简介

软件错误会导致系统故障、安全漏洞和用户体验受损。修复错误是软件开发中的一项关键任务,但如果手动完成,则需要大量的时间和精力。自动程序修复 (APR) 有望通过以自动化方式解决有效和高效的错误解决的关键需求来显着减少这项工作。研究人员和从业者探索了各种方法来应对自动修复错误的挑战,包括基于手动设计的和(半)自动提取修复模式的技术,基于符号约束,以及各种基于机器学习的方法。

APR 目前最先进的技术主要围绕大型语言模型 (LLM) 展开。第一代基于LLM的修复使用与模型的一次互,其中模型接收到包含错误代码的提示并生成一个固定版本。第二代也是当前的基于LLM的修复引入了迭代方法,该方法根据从之前的修复尝试中获得的反馈反复查询LLM。当前基于 LLM 的迭代修复技术的一个关键限制是,它们的硬编码反馈循环不允许模型收集有关错误或可能提供修复成分的现有代码的信息。相反,这些方法对提示中提供的代码上下文进行硬编码,通常是错误代码,有时还会详细说明失败的测试用例。然后,反馈循环对有缺陷的代码的不同变体执行测试,并将任何编译错误、测试失败或其他输出添加到下一次迭代的提示中。然而,这种方法与人类开发人员修复错误的方式有着根本的不同,人类开发人员修复错误的方式通常涉及收集信息以了解错误、搜索可能有助于修复错误的代码以及试验候选修复程序的时间交错。

本文介绍了 RepairAgent,这是第一个用于自动化程序修复的基于 LLM 的自主代理。本文的方法将 LLM 视为一个自主代理,能够规划和执行作以实现修复错误的目标。本文为 LLM 配备了一组特定于修复的工具,模型可以调用这些工具以类似于人类开发人员的方式与代码库进行交互。例如,RepairAgent 具有通过读取特定代码行来提取有关错误的信息的工具,通过搜索代码库来收集修复成分,以及通过应用补丁和执行测试用例来提出和验证修复。重要的是,本文不会对如何使用以及何时使用这些工具进行硬编码,而是让 LLM 根据之前收集的信息和从之前修复尝试中收集的反馈自主决定下一步调用哪个工具。

本文的方法由三个关键组成部分实现。首先,通用的 LLM,例如 GPT-3.5,本文使用动态更新的提示反复查询。本文提供了一种新颖的提示格式,指导 LLM 完成错误修复过程,并根据 LLM 调用的命令和先前命令执行的结果进行更新。其次,LLM 可以调用的一组工具来与代码库交互。本文提供了一组 14 种工具,旨在涵盖人类开发人员在修复错误时采取的不同步骤,例如阅读特定代码行、搜索代码库和应用补丁。第三,协调 LLM 和工具之间通信的中间件。本文提出了通过有限状态机引导工具调用以及启发式解释可能不正确的 LLM 输出的新技术。RepairAgent 的迭代循环一直持续到代理声明找到合适的修复程序,或者直到用尽迭代预算。为了评估本文方法的有效性,本文将其应用于Defects4J数据集中的所有835个错误,这是一个广泛使用的评估程序修复技术的基准。RepairAgent 成功修复了 164 个 bug,其中 Defects4J v1.2 和 v2.0 分别有 74 个和 90 个 bug。正确修复的错误包括 49 个需要修复多行的错误,这表明 RepairAgent 能够修复复杂的错误。与最先进的技术相比,RepairAgent成功修复了39个先前工作未修复的错误。衡量与 LLM 交互所施加的成本,本文发现 RepairAgent 每个错误的平均成本为 270k 代币,根据 OpenAI 的 GPT-3.5 模型的当前定价,相当于每个错误 14 美分。为了解决从 Defects4J 到 LLM 的潜在数据泄露问题,对最近的 GitBug-Java 数据集 的额外评估表明,RepairAgent 能够在单行错误上实现类似的性能,而在多行和多文件错误上则稍差,因为这些错误的复杂性更高。总体而言,本文的结果表明,本文基于代理的方法在程序修复方面建立了新的技术水平。综上所述,本文贡献如下:

  • 一个基于 LLM 的自主程序修复代理。
  • 动态更新的提示格式,指导 LLM 完成错误修复过程。
  • 一组工具,使 LLM 能够执行人类开发人员在修复错误时会采取的步骤。
  • 通过有限状态机指导 LLM 如何与工具交互的中间件。
  • RepairAgent 在程序修复方面建立了新的技术水平的经验证据。
  • 以开源形式发布本文的实现:https://github.com/sola-st/RepairAgent。

据本文所知,目前还没有关于任何代码生成任务的基于 LLM 的自主代理的已发表工作。本文设想 RepairAgent 为未来软件工程中基于代理的技术铺平道路。

背景

通过接受包括自然语言和源代码在内的大量网络知识的训练,LLMs 在各种任务中表现出了卓越的能力。使用这些能力的一种有前途的方法是基于 LLM 的代理,本文指的是具有两个属性的基于 LLM 的技术:(1) LLM 自主计划和执行一系列作来实现目标,而不是响应硬编码查询或在硬编码算法中被查询。(2)LLM执行的作包括调用外部工具,使LLM能够与其环境进行交互。在软件工程,特别是自动修复的背景下,这些工具可以是开发人员通常使用的工具,例如,作为集成开发环境 (IDE) 的一部分。基本思想是使用包含世界当前状态、要实现的目标以及接下来可以执行的一组作的提示来查询 LLM。模型决定要执行哪个作,执行作的反馈被集成到下一个提示中。最近的调查全面概述了基于LLM的自主代理和配备通过API调用的工具的LLM代理。目前,此类代理在软件工程方面的潜力尚未得到很好的探索,本文旨在解决自动化程序修复这一具有挑战性的任务。

方法

图 1 概述了 RepairAgent 的方法,它由三个组件组成:LLM 代理(左)、一组工具(右)和协调两者之间通信的中间件(中)。如果存在需要修复的 bug,中间件会使用提示初始化 LLM 代理,其中包含任务信息以及如何使用提供的工具执行任务的说明(箭头 1)。LLM 通过建议调用一个可用工具(箭头 2)来响应,中间件解析然后执行该工具(箭头 3)。然后,该工具的输出(箭头 4)被集成到下一次调用 LLM 的提示中,并且该过程会迭代地继续进行,直到错误得到修复或预定义的预算用完。

在这里插入图片描述

RepairAgent 以多次迭代或周期进行:

定义1 一个周期表示与 LLM 代理的一轮交互,包括以下步骤:

  • 查询代理
  • 对响应进行后处理
  • 执行代理建议的命令
  • 根据命令的输出更新动态提示

在每个周期中,该方法查询 LLM 一次。模型的输入会根据 LLM 在先前周期中调用的命令及其结果进行更新。本文将模型输入称为动态提示:

定义2 动态提示是一系列文本部分 P = [ s 0 , s 1 , . . . , s n ] P = [s_0,s_1,...,s_n] P=[s0,s1,...,sn],其中每个部分 s i s_i si 是以下之一(其中 s i ( c ) s_i(c) si(c) 指的是循环 c c c 中的一个部分):

  • 静态部分,在所有循环中保持不变,即所有 c c c c ′ c' c s i ( c ) s_i(c) si(c) = s i ( c ′ ) s_i(c') si(c)
  • 动态部分,可能在周期之间有所不同,即可能存在 c c c c ′ c' c s i ( c ) s_i(c) si(c) s i ( c ′ ) s_i(c') si(c)
动态提示

RepairAgent 使用由一系列静态和动态部分组成的动态提示查询 LLM,如表 I 所示。

在这里插入图片描述

角色 提示的这一部分定义了代理的专业领域,即解决 Java 代码中的错误,并概述了代理的主要目标:理解和修复错误。提示强调智能体的决策过程是自主的,不应依赖用户协助。

目标 本文为代理定义了五个目标,这些目标在所有周期中保持不变:

  • 定位错误:执行测试并使用故障定位技术来查明错误的位置。当提示中已提供故障定位信息时,请跳过此目标。
  • 收集有关错误的信息:分析与错误关联的代码行以了解错误。
  • 对错误提出简单的修复建议:首先提出简单的修复建议。
  • 提出复杂的修复建议:如果简单的修复被证明无效,请探索并提出更复杂的修复。
  • 迭代以前的目标:继续收集信息并提出修复建议,直到找到修复方法。

指南 本文提供了一套指南。首先,本文告知 LLM 存在多种类型的错误,从单行问题到可能需要更改、删除或添加行的多行错误。基于观察到许多错误可以通过相对简单的重复修复模式来修复,本文提供了一个重复修复模式列表。该列表基于之前关于 Java 中单语句错误的工作中描述的模式 。对于每种模式,本文都提供了简短的自然语言描述以及有缺陷和修复代码的示例。其次,本文指示模型在修改后的代码上方插入注释。这有助于模型解释其推理,增强其能力,并帮助人类开发人员理解编辑。第三,本文指示模型以明确定义的下一步来结束其推理,该步骤可以转化为对工具的调用。最后,本文指定了有限的工具调用预算(默认为 40 个周期),强调效率的重要性。

状态描述 为了指导 LLM 代理以有效且有意义的方式使用可用工具,本文定义了一个有限状态机,用于约束哪些工具在给定时间点可用。动机是,在没有这种指导的早期实验中,本文观察到 LLM 代理经常迷失在漫无目的的探索中。图 2 显示了有限状态机,本文设计它是为了模仿人类开发人员在修复错误时所经历的状态。每个状态都与一组代理可用的工具,如第 III-D 节所述。重要的是,代理可以使用工具在任何时间点在状态之间自由转换。也就是说,尽管提供了指导,但状态机不会强制执行严格的工具调用顺序。提示的状态描述部分通知代理其当前状态:

在这里插入图片描述

  • 了解 bug:代理在此状态下启动,它可以收集与失败的测试用例和 bug 位置相关的信息。一旦代理了解了错误,它就会提出一个假设来描述错误的性质及其背后的原因。在整个修复过程中,智能体可能会反驳早期的假设并表达新的假设。表达假设后,智能体会自动切换到下一个状态。
  • 收集信息以修复错误:在此状态下,代理收集信息,帮助建议修复假设所表达的错误,例如,通过搜索特定的修复成分或阅读可能的相关代码。一旦代理收集了足够的信息来尝试修复,它就可以过渡到下一个状态。
  • 尝试修复错误:在此状态下,代理会根据其当前假设和收集的信息尝试修复错误。每次修复尝试都会修改代码库,并通过执行测试用例进行验证。如果有必要,代理可以返回到以前的状态之一以建立新的假设或收集其他信息。

除了这三种状态外,RepairAgent 还有一个最终状态“完成”,代理可以通过调用指示修复成功的命令来达到该状态。

可用工具 提示的这一部分描述了代理当前状态可以调用的一组工具,每个工具都有一个名称、一个描述和一个键入参数列表(第 III-D 节)。

收集的信息 RepairAgent 的一个关键功能是收集有关错误和代码库的信息,这是决定接下来调用哪些命令的基础。为了向代理提供此信息,本文维护了一个提示部分,其中列出了不同工具调用收集的信息。直观地讲,提示的这一部分充当代理的记忆,使其能够回忆起先前周期的信息。收集的信息分为不同的小节,其中每个小节都包含特定工具产生的输出。

输出格式规范 给定动态提示,LLM 代理每个周期提供一个响应。为了使中间件能够解析响应,本文指定了预期的输出格式(图 3)。“想法”用于对智能体在决定下一个命令时的推理进行文本描述。要求智能体表达其想法可以增加方法的透明度和可解释性,为智能体决策过程中的潜在问题提供调试方法,并有助于提高LLM的推理能力。“command”字段指定要执行的下一个命令,由要调用的工具的名称和参数组成。

例如,图 4 显示了 LLM 代理的响应。该模型表示需要收集更多信息来了解错误,并建议使用关键字列表搜索代码库的命令。

在这里插入图片描述
在这里插入图片描述

上次执行的命令和结果 提示符的这一部分包含执行的最后一个命令(工具名称和参数)(如果有)及其产生的输出。其基本原理是提醒代理它采取的最后一步,并让它意识到在执行命令期间发生的任何问题。此外,本文提醒代理已经执行了多少个周期,还剩下多少个周期。

代理使用的工具

本文方法的一个关键新颖之处是让 LLM 代理自主决定调用哪些工具来修复错误。本文提供给代理的工具(表 II)的灵感来自开发人员在其 IDE 中使用的工具。

在这里插入图片描述

读取和提取代码:修复错误的先决条件是阅读和理解代码库的相关部分。本文没有对提供给 LLM 的上下文进行硬编码,而是让智能体根据四种工具决定要读取代码的哪些部分。读取范围工具允许代理从特定文件中提取一系列行,这对于获得特定代码部分的重点视图非常有用。为了获取代码结构的概述,获取类和方法工具检索给定文件中的所有类和方法名称。通过调用提取方法工具,代理可以检索与给定文件中给定方法名称匹配的方法的实现。最后,提取测试工具提取失败测试用例的代码,帮助了解输入值和预期输出等细节。

搜索和生成代码 出于人类开发人员通常搜索代码这一事实,本文提供了允许代理搜索特定代码片段的工具。这些工具对于代理来说非常有用,可以更好地了解错误的上下文并收集修复成分,即可能成为修复一部分的代码片段。搜索代码库工具使代理能够在整个代码库中查找特定关键字的实例。例如,代理可以使用此工具查找变量、方法和类的出现。给定一组关键字,该工具会对项目中的所有源代码文件执行近似匹配。具体来说,该工具根据驼峰式大小写、下划线和句点将每个关键字拆分为子标记,然后在代码中搜索每个子标记。例如,搜索 quickSortArray 会产生 sortArray、quickSort、arrayQuickSort 和其他相关变体的匹配项。该工具的输出是一个嵌套字典,按文件名、类和方法名称组织,提供与方法内容匹配的关键字。另一个搜索工具,查找类似的 api 调用,允许代理识别和提取方法的用法,这对于修复不正确的方法调用很有用。没有这样的工具中,LLM往往会产生代码库中不存在的方法调用的幻觉。给定包含方法调用的代码片段,该工具将提取被调用方法的名称,然后搜索对具有相同名称的方法的调用。代理可以将搜索限制为特定文件或搜索整个代码库。

除了搜索现有代码外,RepairAgent 还提供了一个工具,可以通过调用另一个 LLM 来生成新代码。该工具的灵感来自基于 LLM 的代码补全工具的成功,例如 Copilot ,人类开发人员在修复错误时越来越多地使用该工具。给定方法前面的代码和方法的签名,生成方法主体工具会要求 LLM 生成方法的主体。对代码生成 LLM 的查询独立于 RepairAgent 使用的动态提示,并且可以使用不同的模型。在本文的评估中,本文对该工具的主代理和代码生成 LLM 使用相同的 LLM。该工具将给定的代码上下文限制为 12k 令牌,将生成的代码限制为 4k 令牌。

测试和修复 下一类工具与运行测试和应用补丁有关。运行测试工具允许代理执行项目的测试套件。它生成一份报告,指示测试是通过还是失败。如果测试失败,该工具会清理测试运行程序的输出,例如,通过删除当前项目之外的堆栈跟踪条目。其基本原理是 LLM 的提示大小有限,不相关的信息可能会混淆模型。运行错误本地化工具检索错误本地化信息,这对于了解代码的哪些部分可能包含 bug 非常有用。RepairAgent提供了该工具的两种变体:要么提供完美的故障定位信息,要么调用现有的故障定位工具,如GZoltar 来计算故障定位分数。在完美的故障定位的情况下,该工具会提供所有需要编辑以修复错误的文件和行。作为程序修复领域中常见的那样,本文假设完美故障定位为默认值。

一旦代理收集了足够的信息来修复错误,它就可以使用写修复工具将补丁应用到代码库。RepairAgent 旨在修复任意复杂的错误,包括多行甚至多文件错误。写修复工具需要特定 JSON 格式的补丁,该格式指示要在每个文件中进行的插入、删除和修改。图 5 显示了这种格式的补丁示例。给定修补程序,该工具将更改应用于代码库并运行测试套件。如果测试失败,写修复将恢复更改,为代理提供干净的代码库以尝试其他修复。由于观察到某些修复尝试几乎是正确的,写修复工具请求 LLM 对建议修复的多个变体(默认:30)进行采样。给定生成的变体,该方法会删除重复项并为每个变体启动测试。

在这里插入图片描述

控制 最后一组工具并不直接对应于人类开发人员可能使用的工具,而是允许代理在状态之间移动(图 2)。快速假设工具使代理能够阐明有关错误性质的假设,并过渡到“收集信息以修复错误”状态。相反,丢弃假设工具允许代理丢弃不再可行的假设,这会导致返回“了解错误”状态。这两个命令共同强制执行结构化的假设制定方法,与科学调试工作保持一致。如果代理尝试了多次修复但未成功,则收集更多信息工具允许代理恢复到“收集信息以修复错误”状态。最后一旦代理找到至少一个通过所有测试的修复程序,它就可以调用目标完成工具,该工具会终止 RepairAgent。

中间件

中间件组件在 RepairAgent 中起着至关重要的作用,协调 LLM 代理和工具之间的通信。它执行定义 1 中的步骤,如下所述。 解析和完善 LLM 输出:在每个周期开始时,中间件使用当前提示查询 LLM。理想情况下,响应完全符合预期格式(图 3)。在实践中,LLM 可能会产生偏离预期格式的响应,例如,由于幻觉或句法错误。例如,LLM 可能会提供“路径”参数,而工具需要“文件路径”参数。RepairAgent 尝试通过分三个步骤将输出映射到预期格式来启发式地纠正此类问题。首先,它尝试将响应中提到的工具映射到可用工具之一。具体来说,该方法检查预测的工具名称 npredicted 是否是任何可用工具 nactual 名称的子字符串,反之亦然,如果是,则将 nactual 视为所需的工具。如果上述匹配失败,则方法检查 npredicted 和任何 nactual 之间的 Levenshtein 距离是否低于阈值(默认为 0.1)。其次,该方法尝试遵循与上述相同的逻辑,将响应中提供的参数名称映射到工具的参数。第三,该方法通过启发式映射或替换无效参数值来处理它们,例如,通过将预测的文件路径替换为有效文件路径。如果启发式方法失败或产生多个可能的工具调用,中间件会通过“上次执行的命令和结果”提示部分通知 LLM 该问题,并进入一个新的循环。除了纠正响应中的小错误外,中间件还会检查是否使用相同的参数重复调用同一工具。如果代理建议的命令与上一个周期中完全相同,则中间件会通知代理重复并进入一个新周期。

调用工具 给定来自 LLM 的有效命令,中间件调用相应的工具。为了防止工具执行干扰主机环境或 RepairAgent 本身,中间件在隔离环境中执行命令。

更新提示 给定工具的输出,中间件会更新下一个周期提示的所有动态部分。特别是,它更新状态描述和可用工具,将工具的输出附加到收集的信息中,并替换显示上次执行命令的部分。

实验

使用 Python 3.10 作为本文的主要编程语言。Docker 用于容器化和隔离命令执行,以增强可靠性和可重复性。RepairAgent 建立在 AutoGPT 框架和 OpenAI 的 GPT-3.5-0125 之上。为了解析 Java 代码并与之交互使用 ANTLR。

为了评估本文的方法,本文旨在回答以下研究问题:

  • RQ1 RepairAgent 在修复现实世界错误方面的效果如何?
  • RQ2 该方法的成本是多少?
  • RQ3 RepairAgent 的不同组件的影响和重要性是什么?
  • RQ4 代理如何使用可用工具?
实验设置
数据集

本文将 RepairAgent 应用于 Defects4J 数据集中的所有错误,该数据集由来自 17 个 Java 项目的 835 个真实错误组成,其中包括来自 Defects4Jv1.2 中 6 个项目的 395 个错误,以及 Defects4Jv2 中添加的另外 440 个错误和 11 个项目。通过对整个数据集进行评估,本文可以评估 RepairAgent 对不同项目和错误的泛化能力,而不限制评估,例如,基于需要修复的行数、块数或文件数。

为了评估本文结果的普遍性和数据泄露的潜在影响,本文还评估了 RepairAgent 对来自较新数据集 GitBugJava的错误,重点关注在本文使用的 GPT 3.5 版本的截止日期(2022 年 1 月)之后发现和修复的错误。GitBug-Java 包含来自 55 个项目的 199 个错误。由于预算限制,本文随机抽样了 100 个这些错误,每个项目至少采样一个,最多两个错误。随机样本由 19 个单行错误、64 个多行错误和 17 个多文件错误组成。

基准

本文比较了三种现有的修复技术:ChatRepair 、ITER和SelfAPR 。ChatRepair 和 ITER 是两种非常新的方法,已被证明是当前最先进的方法。所有三种基线方法都遵循迭代方法,该方法结合了先前修补尝试的反馈。与 RepairAgent 不同,基线不使用基于 LLM 的自主代理。本文根据各自方法的作者提供的补丁与基线进行比较。

评价指标

与过去的工作类似,本文报告了合理和正确补丁的数量。如果修复通过所有测试用例,则修复是合理的,但不一定正确。为了确定修复是否正确,本文会自动检查它在语法上是否与开发人员创建的修复匹配。如果不是,本文将手动确定 RepairAgent 生成的修补程序在语义上是否与开发人员创建的修补程序在语义上一致。当且仅当两项检查中的任何一项成功时,本文才认为修复是正确的。

实验
问题1

总体结果 表 III 总结了 RepairAgent 在修复 Defects4J 中的 835 个错误方面的有效性。该方法为 186 个错误生成了合理的修复程序。虽然不一定正确,但合理的修复可以通过所有测试用例,并且仍然可能为开发人员提供有关应该更改的内容的提示。RepairAgent 为 164 个错误生成正确的修复程序,其中 116 个与开发人员修复的完全相同,48 个在语义上与开发人员提供的补丁一致。能够修复来自不同项目的错误表明,该方法可以推广到多个域的代码库。此外,RepairAgent 还会为不同复杂程度的错误创建修复程序。具体来说,如表 IV 所示,该方法修复了 115 个单行错误、46 个多行(单文件)错误和 3 个多文件错误。

在这里插入图片描述

在这里插入图片描述

表 III 的右侧比较了 RepairAgent 与基线方法 ChatRepair、ITER 和 SelfAPR。在这项工作之前,ChatRepair 已经通过修复 Defects162J 中的 162 个错误成为了一个新的SOTA方法。

与之前的工作比较 RepairAgent 通过修复总共 164 个错误取得了类似的记录。本文的工作在 Defects4Jv2 中尤其出色,其中 RepairAgent 修复了 90 个错误,而 ChatRepair 仅修复了 48 个错误。为了进一步比较修复的错误集,图 6 显示了不同方法之间的重叠。正如在 APR 领域经常观察到的那样,不同的方法在某种程度上是相辅相成的。特别是,RepairAgent 修复了 39 个未被三个基线中的任何一个修复的错误。在这 39 个错误中,有 18 个是单行错误,20 个是多行错误,还有 1 个是多文件错误。比较错误修复的复杂性,如表 IV 右侧所示,RepairAgent 在需要多于单行修复的错误方面尤其优于其他工具。本文将这一结果归因于 RepairAgent 能够自主检索合适的修复成分,以及编辑任意数量的行和文件的能力。

在这里插入图片描述

示例 图 7 是由 RepairAgent 专门修复的 bug,其中代理使用 f i n d _ s i m i l a r _ a p i _ c a l l s find\_similar\_api\_calls find_similar_api_calls 工具来搜索类似于 c f a . c r e a t e E d g e ( f r o m N o d e , B r a n c h . U N C O N D , f i n a l l y N o d e ) cfa.createEdge(fromNode,Branch.UNCOND,finallyNode) cfa.createEdge(fromNode,Branch.UNCOND,finallyNode); 的调用。它从另一个文件返回调用,该文件将 B r a n c h . O N _ E X Branch.ON\_EX Branch.ON_EX传递给方法调用,而不是 B r a n c h . U N C O N D Branch.UNCOND Branch.UNCOND。然后,代理将此字段名称用作修复成分。在仅由 RepairAgent 修复的另一个示例中(图 8),该方法受益于 generate 方法主体工具来生成缺失的 if 语句,这导致之后建议正确的修复。这些示例说明了代理对可用工具的巧妙和正确使用。他们还表明,这些工具可用于寻找以前工作未能考虑的修复成分。

在这里插入图片描述

在这里插入图片描述

泛化和外部有效性 为了评估 RepairAgent 的泛化能力,本文在 GitBug-Java 上评估了该方法,结果如表 V 所示。总体而言,RepairAgent 找到了 19 个合理的修复和 13 个正确的修复。该表显示,该方法对于单行错误特别有效,它正确修复了 19 个错误中的 9 个。相比之下,该方法与 81 个多行和多文件错误作斗争,它只找到了 4 个正确的修复程序。这一结果至少可以部分归因于GitBug-Java数据集包含比Defects4J更复杂的错误。对于 Defects4J,每个基本实况错误修复的平均添加和删除行数分别为 2.9 和 9.3,但对于 GitBug-Java,平均为 6.2 和 14.4。同样,Defects4J 的平均修改令牌数为 381 个,但 GitBug-Java 的平均修改令牌数为 577 个。本文得出的结论是,RepairAgent 可以很好地推广到新项目和错误,并且不会受到潜在数据泄露(例如 Defects4J)的强烈影响。

在这里插入图片描述

问题2

本文衡量 RepairAgent 施加的三种成本:(i) 修复错误所需的时间;(ii) 查询 LLM 消耗的代币数量;(iii) 与代币消费相关的货币成本,基于 OpenAI 截至 2024 年 3 月的定价。本文的研究结果总结在图 9 中。解决 bug 所需的中位时间为 920 秒,已修复和未修复 bug 之间的差异最小。竟然修复的错误不会始终表现出更短的修复时间。这是由于 RepairAgent 的自主性质,修复过程会一直持续到调用 goal accomplished 命令或周期预算用完为止。该图显示了几个异常值,其中错误修复尝试需要数小时。RepairAgent 将 99% 的总时间用于工具执行,其中大部分用于运行测试。

在这里插入图片描述

分析 LLM 施加的成本,本文发现平均消耗约 270k 个代币,相当于 14 美分(美元)左右。已修复的 bug 消耗的令牌数低于未修复的 bug。这种差异是因为代理继续为尚未修复的错误提取附加信息,使提示充满作,例如读取更多代码行或执行大量搜索。

与之前工作的比较 本文根据各自论文中报告的内容将 RepairAgent 的时间和金钱成本与其他工作进行比较。ChatRepair 的货币成本报告为每个错误 42 美分,基于与本文工作中相同的模型 (GPT-3.5)。根据两次评估之间的定价变化进行调整,ChatRepair 的成本与 RepairAgent 的成本大致相同。ITER和SelfAPR的货币成本没有报告,因为这些方法使用自训练模型。然而,ITER 的作者报告说,每个错误的错误修复时间中位数为 4.57 小时,远高于 RepairAgent 的中位数 920 秒。虽然由于硬件和软件配置不同,比较可能存在偏差,但这表明 RepairAgent 在时间成本方面效率更高。本文主要将这种差异归因于需要验证的补丁数量(例如,RepairAgent 生成的平均补丁为 117 个,而 ITER 生成的补丁为 1,000 个)。

问题3

为了更好地了解 RepairAgent 的不同组件和配置的影响,本文进行了表 VI 中总结的消融研究。由于预算限制,消融是针对整个 Defects4J 的随机选择的 100 个错误(所有配置相同的 100 个)完成的,其中完整的 RepairAgent 方法修复了 21 个错误。本文报告合理且正确的补丁数量、以美元为单位的成本,以及正确修复的细分,分为单行 (SL)、多行 (ML) 和多文件 (MF) 错误。

在这里插入图片描述

搜索工具的重要性 如果没有搜索工具,RepairAgent 会修复默认修复的一半错误。缺少搜索工具还会导致代理更频繁地读取长序列代码,这会使提示迅速饱和并使成本翻倍。

状态机的重要性 如果没有状态机的指导(图 2),代理修复的错误也更少,成本也更高。有效性降低的主要原因是代理没有遵循结构化的方法来修复错误。例如,在许多情况下,代理直接从建议修复开始(通常是错误的),而不收集任何信息。

长期记忆的重要性 表的第三行显示了 RepairAgent 的变体,它仅在单个周期内保留新信息,而不是累积所有收集的信息。同样,错误修复的有效性受到显着影响。原因是代理在几个周期后重复相同的命令(例如,再次请求相同的信息),并且它使用了错误的文件名和函数名。拥有长期记忆有助于代理为未来的周期保留有用的信息,避免重复查询。

故障定位的影响 最后,本文基于基于频谱的GZoltar技术,评估了具有真实故障定位的RepairAgent。RepairAgent 总共以 16 美元修复了 29 个错误,修复能力下降了 25%,成本增加了 81%。这些结果是在没有给 RepairAgent 更多周期的情况下实现的,否则这会有所帮助,因为代理会花费额外的时间来定位错误。

问题4

本研究问题旨在通过分析代理如何使用可用工具来更好地理解该方法。平均而言,RepairAgent 可生产每个错误 35 个工具的调用次数,这也对应于周期数。图 10 显示了工具调用的频率,本文在其中区分了固定的(即“正确”)和未修复的(即仅“合理”或完全未修复)的错误。代理使用各种工具,最常调用的工具是写修复(平均 6 次调用已修复的错误,17 次未修复的错误调用)。在未修复的错误中,大约 7% 的写修复调用会产生合理的补丁,而在修复的错误中,这一比例为 44%。使用最少的工具是运行测试,它使用频率之低,因为最初提供的有关错误的信息已经提供了有关任何失败测试用例的信息,并且写修复工具会自动调用测试套件。

在这里插入图片描述

讨论

定性见解

下面描述了从检查 RepairAgent 的日志中获得的定性见解。

了解 bug RepairAgent 能够主动检索有助于了解 bug 的信息,从而在不增加成本的情况下修复一组新的 bug。具体而言,本文观察到四种有用的信息:(i)失败的测试用例的代码和初始执行结果,本文在第一个周期的提示中提供;(ii) 通过搜索相似代码检索到的代码片段,例如使用查找相似 API 调用工具;(iii) 有关代码结构的详细信息,例如文件中的类和方法;(iv) 通过应用修复获得的反馈,该修复会触发测试执行并显示任何仍然失败的测试用例。

未修复的错误和修复复杂性 如表 IV 所示,RepairAgent 在多行错误方面的表现明显优于之前的工作,但未能修复一些由 ChatRepair 修复的更简单的单行错误。本文观察到,代理有时会对只需要简单修改的错误提出复杂的修复建议。一种可能的补救措施可能是最初限制候选修复的复杂性,促使代理首先尝试简单的修复。对于多行、多文件的错误,本文观察到 RepairAgent 通常只编辑所需位置的子集。未来的工作可以探索人机交互方法,其中代理找到的部分修复可以让开发人员抢占先机。

对有效性和限制的威胁

虽然 RepairAgent 显示出有希望的结果,但本文承认对有效性和固有局限性存在一些潜在威胁:(i) 数据泄露:GPT-3.5 可能已经看到了本文在训练期间评估的部分 Java 项目。本文最接近的竞争对手 ChatRepair 也使用 GPT-3.5,因此面临同样的风险。此外,在 GitBug-Java 的实验上表明 RepairAgent 对保证不是训练数据一部分的错误也有效。(ii) 缺少测试用例:Defects4J 的每个 bug 至少有一个失败的测试用例,这在实际使用场景中可能并非如此。在未来的工作中,在没有先验可用的错误揭示测试用例的情况下评估 RepairAgent 将会很有趣。(iii) 故障定位:不准确或不精确的故障定位可能会导致次优的维修建议或错误的诊断。(iv) LLM 的非确定性输出:LLM 固有的非确定性可能会导致 RepairAgent 的连续两次运行之间产生不同的结果。本文评估的大量错误减轻了这种风险。此外,与 LLM 的交互日志可用于进一步分析。

总结

本文提出了一种基于大型语言模型(LLM)支持的自主代理的错误修复开创性技术。通过广泛的实验,本文验证了本文方法的有效性和潜力。如果配备正确的工具,进一步探索和完善基于自主代理的技术将有助于推广到更困难和更多样化的错误类型。

启发

  • 对于教学来说,修复的过程远比结果更重要。这篇论文大型语言模型(LLM)视为一个能够自主规划、调用工具、分析反馈的“智能代理”(Agent),模拟了人类开发者解决问题的完整流程:理解问题 -> 搜集信息 -> 提出假设 -> 尝试修复 -> 验证结果 -> 迭代优化。

相关知识链接

@inproceedings{10.1109/ICSE55347.2025.00157,
	author = {Bouzenia, Islem and Devanbu, Premkumar and Pradel, Michael},
    title = {RepairAgent: An Autonomous, LLM-Based Agent for Program Repair},
    year = {2025},
    isbn = {9798331505691},
    publisher = {IEEE Press},
    url = {https://doi.org/10.1109/ICSE55347.2025.00157},
    doi = {10.1109/ICSE55347.2025.00157},
    pages = {2188–2200},
    numpages = {13},
    location = {Ottawa, Ontario, Canada},
    series = {ICSE '25}	
}
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐