【CodeAgent论文】VERSICODE: TOWARDS VERSION-CONTROLLABLE CODE GENERATION
https://arxiv.org/pdf/2406.07411
提出问题
现有的大模型可以完成代码生成的任务,但是生成代码所用的库的版本号与原先的代码所用的版本号不一定一致,提出两个问题:
- 大模型生成指定版本号的代码的可靠性如何?
- 如果不同版本的API改变了,大模型如何有效的调整代码?
为此,1.提出了两个新的任务:特定版本代码完成 (VSCC) 和版本感知代码迁移 (VACM),2.构建了用于评估的benchmark: VersiCode,3.提出一个新的指标Critical Diff Check去评估。
怎么构建数据集的呢?
VersiCode数据集的构建过程包括以下几个关键步骤:
-
数据收集:从GitHub、Stack Overflow和下游应用中收集数据。
-
数据预处理:制定规则筛选出包含库版本信息的代码片段。
-
元数据生成:将代码片段、库版本和功能描述整合为结构化的元数据。
-
质量控制:通过人类专家和LLM(如ChatGPT)的混合注释方法,确保数据的准确性和一致性。
-
任务设计:根据元数据设计版本特定代码补全和版本感知代码迁移任务。
-
测试用例生成:为代码补全任务生成可执行的测试用例,以评估模型的动态代码生成能力。
数据收集
数据来源:
(1) 库源代码,从GitHub收集了这些库的所有可用版本的源代码,并通过PyPI进行验证,以确保所收集的版本是正式发布的,并且可以通过pip安装。从库的源代码中,我们从文档字符串中提取了每个API的官方使用示例。
(2) 下游应用代码,鉴于Python在科学编程中的流行性,我们收集了过去十年顶级研究论文的源代码作为下游应用。这些应用因其轻量级、自成一体、主题多样以及与发布场所相关联的时间戳而具有价值。考虑到时间跨度,这一数据源隐含地包含了演变的库。
(3) Stack Overflow,使用库名称作为查询,我们从Stack Overflow收集了常见问题解答(FAQ)数据,这些数据提供了真实用户的问题和多样化的用户答案。我们制定了一系列规则对数据进行筛选,以包含那些明确提到所使用库版本的查询,如表3所示。
每个实例的元数据被结构化为一个包含以下字段的元组:
-
库名称(Library Name):使用的库名称(如
pandas
)。 -
版本(Version):代码片段对应的库版本(如
pandas==1.3.5
)。 -
功能描述(Functionality Description):对代码片段功能的简要描述,由人类专家或LLM生成。
-
代码片段(Code Snippet):具体的代码片段,可能包含被遮蔽的部分(如
[token_mask]
)。 -
数据来源(Source):数据的来源(如Stack Overflow、库源代码或下游应用)。
-
特征类型(Feature Type)[没有在数据集里找到?]:API生命周期特征,如“添加”(新版本中添加的API)、“废弃”(已废弃的API)或“通用”(继承自上一版本的API)。
-
发布时间(Release Time):代码片段的发布时间戳,用于分析模型对不同时间版本的适应能力。
质量控制:
(1)库源代码:库版本是明确且具体的,但示例用法在不同库和版本之间各不相同。我们使用具有上下文学习能力的大型语言模型来帮助从文档字符串中提取示例代码,准备库版本和代码片段。
(2)下游应用:版本可以从通常名为“requirements.txt”的配置文件中轻松提取。我们仔细筛选出那些过长、未提及库版本或无法编译的Python文件。
(3)Stack Overflow:鉴于问题的多样性,我们设计了严格的启发式规则来初步注释库名称、版本以及在答案中提到的Python代码片段。然后,我们将这些预注释的数据分发给六位合格的人类专家进行验证和更正,以确保库版本和代码片段的准确性。在准备好库版本和代码片段的配对后,我们使用具有上下文学习能力的ChatGPT来为每个代码片段生成功能描述。
往数据集里看了眼,每个实例长这样。
任务设计
考虑一个API a 在库 L 的版本 中被添加,并在版本
中被废弃,并且在中间版本
中处于活跃状态,其中 。我们将区间
称为API a 的生命周期。为了详细分析模型性能,我们评估每个LLM对每个版本中新添加或废弃的API的了解程度。我们比较每个库的任意两个连续版本之间的源代码,以检测API或方法名称的变化。根据检测结果,我们将从库源代码中获得的数据集标记为以下几类:
- 添加:表示当前版本中新增的API,并且在后续版本中仍然适用;
- 废弃:表示当前版本是该API的最后一个可用版本;
- 通用:表示API的使用方法是从上一个版本继承而来的。
我们定义元实例为 m=[l;v;d;c]∈M,其中 l、v、d 和 c 分别代表库名称、版本、功能描述和代码片段。
针对上面的两个问题构建下面两个问题:
版本特定代码补全(VSCC):给定一个元实例 ,输入为
,其中 ci′ 是代码片段
的部分遮蔽版本,将库和版本敏感的内容替换为特殊标记。根据遮蔽内容的长度,特殊标记定义为“[token-mask]”、“[line-mask]”或“[block-mask]”,分别反映不同粒度级别的代码补全。输出 y 是被遮蔽的内容,通常包含函数名或变量。
版本感知代码迁移(VACM):给定一对元实例 ,输入为
,输出
。需要注意的是,版本编辑可能需要重构代码结构,因此很难像在 token 级别或 line 级别补全那样详细地格式化。此外,根据
和
的数值关系,会出现各种场景,例如从旧版本迁移到新版本,或者反之。
测试用例生成
如图5所示,我们构建了一个包含可执行测试用例的子集。从VersiCode中源自库源代码的数据中,我们筛选出包含完整上下文(例如,导入语句)的代码片段。专家与GPT-4的网络版本进行交互,将代码片段重构为任务函数。在手动检查任务函数后,专家与GPT-4进行交互,为其编写测试用例。在交互过程中,专家为GPT-4提供适当的反馈。测试用例在包含特定库版本(例如,pandas==1.3.5)的测试环境中运行;如果成功,则在进一步手动验证后完成注释,如果失败,则向GPT-4提供更详细的反馈以协助更正。经过注释的任务函数被处理成三种粒度级别的代码补全形式:Token、Line和Block。可执行测试用例包括以下四种类型:(1)测试返回类型:测试返回类型是否正确。(2)测试正常输入:测试是否能产生预期的输出。(3)测试边界值:测试是否能正确处理特殊值(例如空值、错误类型等)。(4)测试功能:测试函数是否实现了其主要功能。前三种类型的测试用例每个任务函数有1个实例,而第四种类型有1-3个实例。
如图2所示,考虑到代码迁移实例是从元数据对构建的,源代码和目标代码版本之间的差异导致了各种情况,例如从旧版本更新到新版本或反之亦然。此外,我们根据版本模式对版本进行分类,例如将torch v1.0.0视为主要版本,而将torch v1.3.1视为次要版本,以识别主要和次要版本迁移案例的组合。
用EM@k(Exact Match)作为评价指标,公式如下:
其中:
-
N 是测试集中的实例总数。
-
C(n,k) 是组合数,表示从 n 个候选中选择 k 个的组合方式。
-
C(c,k) 是从正确生成的 c 个候选中选择 k 个的组合方式。
也就是如果生成的 k 个候选代码片段中至少有一个与参考代码完全匹配,则该实例的EM@k得分为1。如果没有一个候选代码片段与参考代码匹配,则该实例的EM@k得分为0。最终的EM@k是所有测试实例得分的平均值。
【收获】通过这篇文章大致了解了构建一个benchmark基本要做的事情:数据收集,检验,设计任务,设计指标,测试;这个benchmark蛮有趣的还。