Mistral AI 用 AI agents 迁移 40,000 行 Fortran 77 遗留代码
Modernizing complex legacy code with AI agents.
Mistral AI 帮助一家欧洲能源运营商将 40,000 行 Fortran 77 迁移到 C++,对象是一个物理密集型油藏模拟器,原项目没有测试套件和集中化文档。
案例给出从校验基准到人机协作流程的迁移路径,可迁移到复杂遗留系统改造。
遗留科学代码库会在数十年间不断积累,而当原作者离开时,嵌入在代码中的知识就变得难以恢复。此外,使用没有活跃开发者生态的语言,意味着会错失在他人成果之上继续构建的机会。Mistral 帮助一家欧洲能源运营商将 40,000 行 Fortran 77 迁移到 C++:这是一个物理密集型的油藏模拟器,没有测试套件,也没有集中式文档。
超越遗留代码现代化中的代码翻译。
将语法从一种语言翻译到另一种语言,在很大程度上已经是一个解决了的任务。让任何近期模型把一段来自并非极端冷门语言的代码片段翻译成另一种语言,通常会在少数几轮迭代内收敛到可接受的结果。然而,将一个完整系统从过程式语言迁移到面向对象的 C++,会产生进行架构重构的需求,这使得任务并不简单。
Fortran 77 顾名思义于 1977 年标准化,用它编写的代码直接反映了这些限制:没有模块、没有命名空间、没有结构化类型。状态存在于 COMMON 块中——即整个程序共享的全局内存。变量会根据其首字母被隐式定型,因此拼错的名称会悄悄创建一个新变量,而不是引发编译器错误。
举一个简单但有说明性的例子,看看下面的一阶泰勒展开实现。输入和输出都是 COMMON 块中的全局变量,而 IC 之所以是整数,只是因为它的名称以 I 到 N 之间的字母开头。变量名相当晦涩,因为其长度被限制为 6 个字符。
SUBROUTINE GASDEN
INCLUDE 'common.h'
DO 10 IC = 1, NCELL
10 RHOG(IC) = ROG(IV) + DROG(IV)*(P(IC)-PTAB(IV))
END
在 C++ 中,可以利用显式类型,编写面向对象的代码,并返回一个值,而不是写入全局变量:
double gasDensity(const GasProperties& gas, double pressure) {
size_t i = lookup(gas.pressure, pressure);
return gas.density[i] + gas.slope[i] * (pressure - gas.pressure[i]);
}
分散的 COMMON 数组变成了一个 GasProperties 参数,网格循环也被移到了调用方,因此没有逐行对应关系可供检查,这正是验证迁移变得困难的原因。
这些结构性差异,再加上集成 PetSc 等现代科学计算框架的要求,使得即使在开始迁移之前,也出现了几个重要问题:
如何证明迁移后的代码库在数值上与遗留代码库一致
如何将迁移拆分为可管理的块
如何最好地利用自主智能体来加速这一过程
在迁移遗留代码之前构建等价性验证框架。
在放手让智能体运行之前,我们需要一种方法来证明两个代码库的结果一致。这里的“一致”指的是输出在数值上相等——既包括最终结果,也包括客户的油藏工程师标记的一组关键中间点。
我们添加了:
允许导出 Fortran 代码库状态的子程序
用于将检查点加载到 C++ 中的测试框架
用于引导智能体正确使用它们的 Skill.md 文件

在迁移工作流中,智能体成功地对 Fortran 代码库进行了插桩,以转储状态快照,并使用 C++ 测试框架来验证已迁移模块的正确性。
首先构建这部分验证框架,对项目而言是一项净收益投资:它让长时间运行的智能体更安全,而数值等价性是一种易于验证且有说服力的论据,可以用来表明某段代码已成功迁移。我们认为,这应当是任何代码现代化项目最先采取的步骤之一。
下面的例子说明了这一点。我们首先在 Fortran 代码中插入了一行,用来转储 RHOG 变量的值(本次运行中为 42.71834),然后在测试迁移后的 C++ 模块时,将同一个值用作参考检查点。

使用 AI 智能体理解并记录遗留代码库。
该项目的文档分散在旧 PDF 中,以及埋在 Fortran 本身里的注释中;整个工作的一个最大的附带收获,就是统一整理这些文档,并把它们移到代码旁边。
幸运的是,像 Fortran 这样的过程式代码有一个方便的特性:整个程序可以绘制成一棵单一的调用者-被调用者树。我们通过使用自定义解析器解析代码库生成了这棵树,然后使用 Vibe CLI 启动了一百多个智能体来为其编写文档。每个智能体都可以通过文档库和 Mistral OCR 引入相关 PDF。
从树的叶节点开始向上推进,每个节点都会派生一个子智能体来为其编写文档,并向原始代码库提交 PR。一个按 cron 定时循环运行的审查智能体会查找新打开的 PR,对其进行审查,并在必要时安排修复任务。

利用 AI 智能体进行代码现代化改造。
第一次尝试时,我们赋予智能体完全自主权:每个 Fortran 子程序对应一个智能体,各自在一周时间里独立地将其功能翻译成 C++。结果是可运行的,但称不上代码现代化。COMMON 块一对一地变成了全局结构体。由 GOTO 驱动的控制流原封不动地保留下来,而不是被重构为循环或提前返回。它看起来更像是用 C++ 语法重新键入的 Fortran,而不是现代化后的代码。
第二次尝试中,我们通过给智能体提供结构而不只是自主权来解决这个问题:让一个规划者、一个编码者、一个测试者和一个代码质量审查者共同处理每个模块。与第一次尝试相比,代码质量有了显著提升。但源代码的复杂性最终还是让智能体难以应对。它们会遇到一个 bug,尝试几种修复方法,然后陷入停滞,而没有人可以介入。
我们最终选择了一个折中方案:由人类操作一个由编码者、测试者和审查者智能体组成的工作流,逐个模块迁移代码库。这样既保留了第二次尝试中的代码质量,又增加了一个人工检查点,以便在智能体卡住时解除阻塞。下一节将详细介绍这个工作流。
为复杂代码迁移运行结构化 AI 智能体工作流。
在代码库已完成文档化、等价性测试框架也已就位后,剩下有趣的部分就是调校可以交给智能体多少自主权,同时仍能得到可合并的代码。我们尝试了两个极端,从完全自主运行到严密监督的手动会话。下面的结构化工作流就是我们在这个用例中最终采用的方案。
在与客户的油藏工程师合作时,我们使用调用者-被调用者树来识别独立模块——即规模可控的自包含子树(根据经验,小于约 10’000 行 Fortran)。每个模块都会经过相同的工作流:
生成目标 C++ 架构。
与油藏工程师一起审查。
获批后,将其拆分为任务队列。
针对每个任务运行一个实现子工作流:规划 → 实现 → 测试 → 重复。
由人类审查生成的 PR,并要求修改,直到它们被合并。
认识 AI 辅助遗留代码现代化的局限性。
第一轮冲刺涵盖了核心功能:300,000 行中的 40,000 行。这个 Fortran 代码库是自包含且可运行的,这是一个有利的起点。依赖外部系统、缺少可运行基线,或编码了无处有文档记录的物理学内容的迁移,会带来本文未讨论的额外挑战。
应用三项原则来现代化复杂的遗留系统。
这个项目的三条经验应该适用于任何大型遗留迁移。
在编写迁移代码之前先构建一致性验证框架——数值一致性是证明一个模块已完成的最廉价、最有说服力的证据。
在依赖代理之前,先把文档整理好,因为没人读得懂的代码是无法迁移的。
在这种规模下,带有人类审核关卡的结构化工作流,胜过完全自主和人工驱动的手动会话。
我们正在招聘!
Mistral 的 Applied AI 团队由一群工程师组成,围绕 Mistral 模型和 Enterprise 平台构建全栈解决方案。我们交付高风险、特定领域的解决方案,用于解决世界上一些最艰巨的问题。
如果这是你想参与的工作,申请加入我们的团队。
来源:Mistral AI · mistral.ai