一篇论文标题背后往往藏着整整几年的坑。DeepK 这个项目名字我盯了很久,一个自动程序修复框架,挂在 OceanBase 校企联合研究的背景下,乍看像是一个“大模型修 bug”的常规课题,但真正深入进去才发现,它要解决的问题远比“用 ChatGPT 改代码”复杂得多——尤其在数据库内核这种对正确性近乎偏执的领域。这篇文章我想以参与者的视角,把这套框架从设计动机、技术拆解、实验设计到论文写作的完整链路讲透,把我踩过的坑、试错过的方法、以及最终沉淀下来的经验一并交代清楚。适合正在做 AI4SE(人工智能辅助软件工程)方向的研究生、对自动化测试和缺陷修复感兴趣的后端开发者,以及所有想在校企合作项目中真正落地技术成果的人阅读。
1. 这项研究到底在解决什么问题
自动程序修复不是新概念,学术界搞了十几年,但真正能在工业级系统里跑起来的产品少之又少。DeepK 想做的是把这条“最后一公里”走通,而 OceanBase 分布式数据库内核就成了最好的试金石。为什么这么说,得从 APR 领域本身的困境和数据库场景的特殊性两方面看。
1.1 自动程序修复的三座大山
自动程序修复,英文叫 Automated Program Repair,缩写 APR。它的目标很朴素:给定一个有 bug 的代码仓库,自动定位缺陷位置并生成补丁,让测试从失败变通过。这个领域有大量前人工作,从早期基于模板的修复,到后来基于符号执行的修复,再到近几年大模型主导的生成式修复,范式换了几轮,但始终有几个绕不过去的坎。
第一座大山是缺陷定位。你让一个大模型直接读整个仓库,它基本抓瞎。代码量动辄几十万行,真正的 bug 可能就藏在某个边界条件里,而模型的注意力机制很难在没有先验的情况下定位到准确位置。传统方案用测试覆盖率和可疑度排序来找候选行,但数据库内核这种并发密集、状态耦合严重的代码,单测覆盖率高并不代表逻辑正确,定位准确率往往惨不忍睹。
第二座大山是补丁生成。即使定位到了可疑代码,生成一个能通过全部测试的补丁仍然极其困难。大模型擅长模仿代码风格,但它并不理解数据库事务的 ACID 语义,不知道什么情况下锁可以被释放,什么情况下 redo log 必须落盘。它生成的补丁经常是“语法上无懈可击,语义上一塌糊涂”,测试一跑就挂。
第三座大山是验证成本。修复是否成功的唯一标准是测试,但数据库的测试套件重得要命,跑一轮完整回归可能需要几十分钟。如果生成的补丁在验证阶段都要排队,整个修复循环的效率就被拖垮了。这也是很多 APR 框架停留在论文里的原因——实验室的玩具项目跑得飞快,一上真实系统就露馅。
DeepK 的设计初衷,就是把这“三座大山”按顺序一一拆掉:用检索和历史缺陷知识缩小定位范围,用场景化的提示词约束生成方向,用分层的验证策略把回归成本降到可控水平。
1.2 为什么偏偏选中数据库内核
OceanBase 这类分布式数据库的内核代码,可能是对 APR 系统最苛刻的试炼场,但也是最能证明技术价值的场景。
首先是代码质量的敏感性。数据库不允许“差不多能用”的补丁。一个并发控制的修复如果存在微小的竞态窗口,在低负载测试中可能完全看不出来,但一上生产环境、高并发场景下就会触发脏读或死锁。这意味着补丁的验证不能只看单测,还要做并发压力测试、故障注入测试、性能回归测试——一套完整的质量门禁。
其次是领域的强规则性。数据库内核里有大量不可破坏的语义约定:事务要么提交要么回滚,索引必须与数据一致,WAL 日志必须先于数据页落盘。这些规则不像通用代码里的函数契约那样写在注释里,而是散落在代码的各个角落,靠开发者多年的经验维系。对 APR 模型来说,它必须“学会”这些隐性规则,否则生成的补丁就是在跟整个系统的根基对着干。
再有一点是可复现性。数据库的 bug 往往由特定的执行序列触发,输入是一个复杂的 SQL 查询计划加上精心构造的数据分布,输出是错误的结果或崩溃。这种可复现但难理解的缺陷,恰恰是评估 APR 系统能力的理想样本。你可以在不泄露测试用例的前提下,构造出“已知 bug、未知根因”的评估集,然后看修复系统能在多长时间内给出正确补丁。
校企联合研究在这里的价值就体现出来了:学校团队有算法和模型训练的经验,企业团队掌握真实缺陷库和完整的测试与验证基础设施。两边一结合,DeepK 才有机会在一个真实、严格、且业务价值清晰的场景里完成闭环验证。
1.3 DeepK 项目的定位与目标
简单说,DeepK 是一个面向数据库内核场景的自动程序修复框架。它不追求“无所不能”的通用修复能力,而是聚焦在“已知缺陷描述、代码仓储全量可见、测试门禁可执行”这一假设下,把修复这件事做得尽可能稳、尽可能快。
具体目标有三条:
- 在给定缺陷报告或失败测试的前提下,自动定位到可疑代码范围,并把候选范围缩得足够小(通常控制在一个函数到几个相邻函数之间)。
- 基于大模型生成候选补丁,并通过多轮指令反馈让补丁质量逼近人工水平。这里不要求一次生成完美补丁,但要求迭代收敛。
- 建立一套可落地的验证流水线,能够在合理时间(通常几十分钟内)完成对候选补丁的回归验证,输出修复成功的证据链。
这三条目标听起来并不浮夸,但每一条落到实处都有大量细节要抠。接下来的章节我会逐层展开。
2. DeepK 框架的整体设计思路
DeepK 的架构并不是一开始就定下来的。我们最早也尝试过端到端的方式:把整个仓库丢给大模型,让它输出一个完整的 diff。结果并不意外,模型完全 hold 不住,输出质量远低于可用水平。后来逐步收敛到“检索增强 + 生成反馈 + 分层验证”三阶段架构。
2.1 不是从零训练模型,而是“检索+生成+验证”
很多 APR 论文喜欢微调一个专门的修复模型,但 DeepK 一开始就确定了方向:不做从零训练,也不做大规模微调。原因很实际,数据库内核的缺陷样本太稀缺了,整个企业级缺陷库里能拿出来做标注的也就几千个样本,拿这点数据微调百亿参数模型,性价比极低。
我们的方案是“检索增强生成”模式。系统维护一个历史缺陷知识库,里面包含四类信息:缺陷现象描述、可疑函数列表、最终修复补丁、修复后的验证记录。当遇到一个新缺陷时,先通过文本相似度和代码结构相似度检索出最接近的历史案例,然后把历史案例作为参考片段拼进大模型的上下文。
这里有一个非常关键的经验:直接给模型看“修复后的代码”作为示例,往往不如给模型看“修复前的 Bug 代码 + 修复后的 diff + 缺陷描述”效果更好。因为模型的注意力机制能从“变化”中更快学习到规律,而单纯的正确代码示例只会让它模仿风格,却学不会“为什么要这样改”。我们在实验中发现,这种带缺陷上下文的三元组示例,修复成功率比单纯正确代码提升约 23%。
生成之后的验证也分两层。第一层是静态验证,用编译器和基础 lint 工具检查补丁是否引入语法错误、类型错误、未使用变量等问题。这一层能过滤掉 60% 以上的垃圾补丁,而且成本极低,每条补丁不到一秒。第二层是动态验证,执行与缺陷相关的定向测试集,重点验证修复是否真正消除了问题。动态验证通过后,再进入完整的回归套件。
分层验证的价值不止是省钱,更重要的是给生成环节提供了更细粒度的反馈信号。如果一条补丁在静态层被拒绝,我们可以快速告诉模型是类型错误还是未定义符号;如果动态测试失败,我们可以把失败断言和堆栈信息回传给模型,让它基于失败原因做第二轮修复。这个“失败信息闭环”是 DeepK 迭代效率远高于一次性生成方案的核心原因。
2.2 知识注入:让模型读懂数据库的“脾气”
通用大模型对数据库代码的理解非常粗浅。它知道begin_transaction和commit这两个词的字面意思,但不知道在一个多副本同步提交的协议里,commit 之前必须等待多数派返回 ack;也不知道在内存表转储到磁盘的过程中,必须先冻结写入再拷贝数据。这种领域性的“脾气”,不写进提示词里,模型永远学不会。
DeepK 的做法是构建了一个三层知识注入体系。
第一层是通用代码规范,包括当前项目的编码风格、异常处理约定、日志格式要求。这些信息通过项目级提示词塞进系统上下文,保证生成的补丁在风格上与原有代码融为一体,避免出现“代码能跑但一看就是外来户”的情况。
第二层是数据库领域约束。我们手工整理了一批“不可 violate 的语义规则”,比如:所有需要跨节点协调的写操作必须走两阶段提交协议;所有索引的更新必须与数据行更新在同一事务内;任何 malloc 得到的内存,在错误返回路径上必须释放。这批规则以约束清单的方式随每个补丁请求发给模型,并明确要求“如果修复涉及这些模块,必须遵守对应规则”。
第三层是即时检索到的相似缺陷知识。也就是前面提到的问题历史案例,这一层不是静态的,每次请求都会动态检索,因此能保证即使是新增的缺陷类型,也能在知识库中找到最接近的参考。
三层知识注入的比例也非常讲究。我们试过把知识库里的所有信息一股脑全塞进去,结果上下文字数膨胀到几万 token,模型的输出质量急剧下降,同时单次请求的延迟和成本都翻了好几倍。后来我们用了一个简单的小技巧:动态摘要。系统先按关键词粗筛出前 20 个候选案例,再用一个轻量模型对每个案例生成 100 字以内的摘要,最终只把摘要和其中与当前缺陷最相似的三条完整案例放进上下文。这样上下文的控制量基本维持在 6000 token 左右,生成质量也稳定得多。
2.3 修复验证闭环:测试用例不是万能的
讲一个我们在项目中反复碰壁的点:测试用例并不是万能的。数据库的缺陷往往只在特定并发时序下触发,而现有测试用例可能根本没有覆盖到这条路径。你让模型修一个 bug,确实通过了所有现有测试,但缺陷依旧存在,只是因为测试没能复现出来。
这个问题在早期直接击穿了 DeepK 的验证机制。后来我们引入了两个补充手段。
第一个是变异验证。系统会拿修复前的缺陷版本,对可疑代码附近的表达式做变异,生成一组“仍然有 bug”的变体,以及一组“已经修复”的对照组。然后把待验证补丁分别应用在这两组代码上,观察测试结果。如果补丁对变形变体的测试表现与对照组一致(即变异体依旧失败、修复版通过),说明补丁真正命中缺陷的语义;如果补丁让变异体也通过了测试,那很可能说明它只是在“绕过测试”而不是“修复缺陷”。
第二个是对偶测试生成。我们会用大模型为修复后的代码生成一组新的边界测试用例,重点覆盖与缺陷相关的模块边界。这些新生成的用例会被回灌到原缺陷版本上,如果能复现失败,说明新用例确实触碰到了缺陷路径,然后再把新用例加入验证集,对候选补丁做二次验证。这一手能有效防止模型“刷测试”——只修了报告里的那一条失败用例,却把整个语义改歪了,搞出一堆新的隐蔽问题。
用这套闭环,DeepK 的补丁通过率有了质的提升。最终统计中,修复后的补丁通过完整回归测试的比例达到 81%,而早期没有闭环验证时的通过率只有 46%。差距非常明显。
3. 数据库场景下绕不开的实操细节
框架层面的架构讲完了,下面进入真正见血的部分——那些论文摘要里不会写、但实操中每个都能让你折腾一周的细节。这里我挑了三个最有代表性的:缺陷样本怎么构造、模型生成参数怎么调、回归验证怎么承受得住。
3.1 故障注入与崩溃数据构造
做 APR 研究的先决条件是有足够多的缺陷样本。但真实数据库项目的 bug 不像 Web 应用那样随手一抓一大把,而且很多缺陷一旦被修复,原始触发条件就永久失效了。所以校企联合项目中,我们花了不少精力在样本构造和复现上。
最有效的来源是历史缺陷库。企业版本的数据库项目经过常年的 Fuzz 测试和用户反馈,积累了数千个已修复缺陷的完整记录,包括触发 SQL、崩溃堆栈、修复前后的代码快照。这些数据是 DeepK 训练知识库和评估集的金矿。但原始记录往往是冗余且混乱的,需要人工清洗——把触发条件抽成最小可复现用例,把崩溃堆栈裁剪到与根因最相关的 30 行以内。
另一个重要手段是主动注入缺陷。我们从生产代码里挑选出一些修复过的关键函数,把修复补丁逆向删除或者故意改错,人为制造新的缺陷样本。这种做法有一个先天优势:我们知道正确答案(就是原始修复后的代码),可以自动评估模型补丁的正确性。但它的缺点也很明显,注入的缺陷在模式上可能与真实 bug 分布有偏差,所以我们在评估集设计时把真实缺陷和注入缺陷严格分开统计,防止注入样本的高修复率掩盖真实场景的低可用性。
缺陷数据构造完成后,标准化格式很重要。DeepK 内部采用统一的缺陷元数据格式,包含五个字段:repo_version(仓库版本)、trigger_case(最小触发用例)、stack_trace(异常堆栈)、buggy_fn_list(可疑函数列表)、golden_patch(正确修复补丁,仅供训练和评估使用)。有了这种标准化格式,后续的检索、评估、比对才有统一基准。
3.2 修复补丁生成的关键参数
大模型生成补丁看起来就是“写提示词、调 API”,但里面的参数设置能直接决定修复率的天花板。这里分享几个我们调参摸索下来的关键参数。
温度系数(temperature)最敏感。我们测过 0.1 到 1.2 的完整区间,发现 0.3 附近效果最稳。温度太低,模型输出保守,经常生成“啥也没改”的空补丁;温度太高,输出天马行空,频繁出现并发语义完全错误的改动。0.3 这个值能让模型在“基于示例做局部修改”和“探索性修复”之间达到相对好的平衡。需要说明的是,这个结论基于数十轮实验的结果,具体项目仍建议自己做小规模基线测试。
top_p 我们固定在 0.9,候选数量 n 设置为 8。也就是说每次请求会并行生成 8 条候选补丁,然后统一进入验证流水线按批处理。n 值不是越大越好,因为验证阶段是瓶颈,一次性生成太多候选会导致后续验证排队,整体修复耗时反而拉长。8 这个值是在吞吐量和验证吞吐之间取的一个合理平衡。
还有一个容易忽略的参数是 max_tokens。数据库代码里一个函数动辄几百行,如果补丁涉及重构,输出长度会很大。我们最初把 max_tokens 设置为 1024,结果很多有修复思路的补丁被硬生生截断,静态验证直接报语法错误。后来调整到 2048,截断率下降了一半还要多。但如果设到 4096,模型开始频繁“凑字数”,输出里出现大量无意义的空行和重复注释,反而干扰验证。所以 2048 是个比较合适的值。
3.3 回归验证与性能评估怎么做
验证流水线的核心是管理有限的计算资源。数据库中一次完整回归可能要跑半小时以上,如果每次修复候选都跑完整回归,那整个框架的效率会低到没法用。DeepK 采用三级验证策略。
第一级是静态检查,包括在容器内编译补丁后的项目。数据库内核的编译通常需要几分钟,这已经是流水线里最重的单步操作。我们做了增量编译优化,只编译受补丁影响的模块,能把编译时间压缩到 40% 左右,但如果补丁修改了头文件或数据结构的定义,就必须全量编译了。
第二级是定向测试集,也就是与缺陷模块强相关的测试。定向测试集的选择很有讲究,不能只看测试文件路径是不是与被改动代码相邻,还得看测试内部引用了哪些接口。我们的做法是建立模块与测试用例的映射关系表:每个可疑函数关联到一组已知覆盖它的测试用例,定向测试就从这份关联表里取。这一步执行时间通常在 1-3 分钟。
第三级是完整回归加性能快照。只有前两级全部通过后,才会触发完整回归。完整回归除了验证功能正确,还同时跑性能快照对比——数据库内核绝不能因为一个修复就把关键查询性能拖累超过 5%。我们设定了一个自动化脚本,解析性能测试结果,把超阈值的补丁直接标红并打回修复循环,附上性能回退的指标差值和采样日志。
整个验证流水线的耗时结构大致是:静态检查 5 分钟,定向测试 2 分钟,完整回归 30 分钟。如果一条候选补丁如果能连续通过三级验证,最终人工审查基本都是一次过。这个流水线听起来琐碎,但它是 DeepK 能走向论文实验和实际交付的共同底座。
4. 校企联合研究的组织与推进经验
校企联合研究项目,最大的风险往往不在技术上,而在两边团队的预期管理和协作模式上。这里想聊聊我们用真金白银换来的几条组织经验。
4.1 需求从哪来,成果到哪去
校企联合研究最容易犯的错误,是企业把学校团队当外包用,而学校团队把企业当数据提款机。DeepK 能走通,靠的是建立了清晰的“需求源”和“成果归口”机制。
需求源方面,企业团队每周整理一批来自生产环境的真实缺陷案例,这些案例不直接给学校团队用作训练数据,而是先经过一轮脱敏和技术拆解,把问题抽象成学术研究命题。比如“某并发事务调度场景下出现丢失更新”的案例,抽象出的研究问题可能是“如何在分布式事务调度中识别丢失更新模式的缺陷特征”。这样一来,学校团队的研究方向始终跟真实痛点绑定,而不是闭门造车。
成果归口方面,学校的算法原型会被企业团队做两层评估。第一层是算法效果评估,在固定基准集上对比 DeepK 与基线方法的表现;第二层是工程可行性评估,看算法输出的补丁是否满足代码规范、是否能通过完整 CI 流水线、是否有性能隐患。只有两层评估都通过的原型,才会被纳入企业内部的工具链试点。这个机制保证了学术成果和工程落地的转化路径清晰可见。
4.2 论文实验设计的三个坑
做 AI4SE 方向的论文,实验设计直接决定论文能否中稿。DeepK 的实验设计前前后后改了三版,踩过的坑我挑三个最典型的分享。
第一个坑是基线方法选择失衡。我们最初对比的基线只选了经典的模板修复方法和一个通用的代码生成模型,被审稿人指出“缺少与同类 AI 修复框架的对比”。后来我们重新收集了两个开源的 APR 工具,在相同的缺陷集上跑了一遍基准测试。对比结果其实对我们有利——DeepK 在数据库场景的修复率比通用方案高出很多——但如果没有补齐对比实验,再好的结果都难以让人信服。
第二个坑是评估集污染。我们在早期实验中不小心把部分缺陷样本的修复补丁也放进了训练数据,导致模型在评估集上表现得异常优秀。及时发现这个问题后,我们重建了评估集,保证训练集与评估集完全隔离,同一仓库版本的缺陷样本绝不交叉。这一点对 APR 研究来说尤其致命,因为大模型有极强的记忆能力,一旦见过答案,评估结果就基本失去意义。
第三个坑是消融实验不够细。我们早期版本的消融只做了“有知识注入 vs 无知识注入”两组,说服力有限。后来拆成四个维度分别做消融:缺陷定位模块开与关、知识库检索开与关、对偶测试生成开与关、静态检查反馈开与关。每一组消融都跑完整评估流程并记录耗时和修复率的差异。这样审稿人就能清晰看到每个模块的真实贡献度,而非堆一个端到端结果。
4.3 团队协作与代码交接心得
校企联合项目里,人员的流动性非常高。研究生会毕业,企业工程师会转岗,代码和技术决策如果在单一角色手里,项目就会变得极其脆弱。DeepK 项目的代码交接和知识沉淀是我们后期维护的重点。
我们坚持两个规则。第一,所有关键设计决策必须写成设计文档,哪怕只是半页 A4 纸的简短说明,也要记录当时为什么选这个方案、放弃那个方案。技术债可以还,设计决策丢失才是真正的灾难。第二,代码仓库严格执行提交信息规范,每个 merge request 必须注明关联的缺陷编号、实验批次和验证结论。这样新加入的成员可以凭 commit 历史在半小时内还原一个模块的完整演进脉络。
另一个很实用的技巧是建立“每周轮值”机制。每周让一名团队成员负责处理另一个模块的 issue 和代码评审,即使前期效率不高也必须坚持。经过两三个月,团队里每个人都对系统全貌有了基本认知,再遇到核心人员离开,项目也能保持惯性推进,而不至于失控停摆。
5. 常见问题与排查技巧实录
研究过程中积累了大量调试经验,这里挑几个出现频率高、又不太容易在网上找到答案的问题,按场景整理成速查表。
5.1 模型输出乱改 API 怎么办
这是最常遇到的问题。模型生成补丁时会对项目内部的 API 做“想象性重构”,比如把某个get_table_meta(handle)改成get_table_meta(handle, true),但函数本身根本没有第二个参数。这种补丁在静态检查阶段就会被拒,但被拒次数过多会拖累修复循环效率。
应对策略有两个层面。技术层面,我们在提示词中显式加入了一份本次修复允许调用的 API 签名清单,只列出与可疑代码相邻模块的接口。模型看到明确的白名单后,乱改 API 的现象大幅减少。管理层面,如果同一类 API 错误反复出现,我们会把这批错误案例收集起来,在知识库中标记为“高危误用模式”,在后续检索中优先推送给模型作为反面示例。经验上,展示三个以上反面示例的效果优于单纯的正向约束。
5.2 验证用例泄漏导致误报
“修复成功”的假象比修复失败更可怕。有一段时间,DeepK 在评估集上的修复率高达 65%,但人工复核后发现超过三分之一的“成功修复”存在验证用例泄漏——模型生成补丁时,间接接触到了测试代码信息,生成的补丁对特定用例做了过拟合。
排查过程很痛苦。最终我们加入了一个简单的防泄漏机制:把测试文件路径和测试用例内容从模型上下文中完全屏蔽,只允许提供“测试失败堆栈 + 失败的断言描述 + 相关业务模块的说明”。这样模型只能在语义层面理解问题,而不能直接参考具体断言。改动之后,评估集修复率降到了 41%,但人工复核通过率几乎翻倍。这也是一个重要的经验:指标的真实性比指标数值本身更重要。
5.3 跨版本代码漂移问题
数据库项目版本迭代极快,今年写的缺陷知识库,明年可能因为某次大规模重构而失效。代码漂移会让检索模块召回一堆过时代码片段,反而干扰模型判断。我们的处理方式是给知识库中的每个缺陷样本加上“适用版本范围”字段,检索时先做版本过滤。当发现某条历史案例在当前版本上无法正常编译或测试时,自动降权处理。
另外一个补充手段是持续跟踪代码变更日志。每当仓库有涉及关键模块的重构提交,脚本会自动生成一份变更摘要并推送到知识库管理后台,由人工决定哪些历史案例需要淘汰、哪些需要重新标注。这样知识库始终与代码演进保持同步,检索准确率才能长期稳定在较高水平。
6. 后续还能往哪些方向扩展
最后说说我对 DeepK 后续发展的观察。基于我在项目中的体验,这个框架的潜力不在“修更多 bug”,而是在“修复数据的反哺”上。每次修复闭环产生的缺陷样本、失败用例、补丁对比,本身就是高质量的训练语料。如果把一定量的修复历史回灌到模型的持续学习中,理论上可以形成“越修越准”的正循环。这不是遥远的设想,而是我们在内部已经跑通的实验形态,只是还未到公开发布的程度。
另一个值得探索的方向是把 DeepK 从“修复已报告的缺陷”延伸到“主动探测潜在缺陷”。配合变异测试和模糊测试,让框架在常规测试通过后继续尝试寻找隐蔽边界,把 APR 从被动响应变成主动防御。这个过程需要大量的算力和精细的验证设计,但方向已经很明确。
真实操作系统摩擦过才知道,自动修复真正稀缺的不是模型能力,而是对业务场景的理解深度和验证工程的控制力。如果这篇文章能帮准备入局 AI4SE 的读者少踩几个坑,或者给校企合作中的研究者提供一点协作思路,那这些经验就没白写。