返回 文章 apply CMS 文章

Mistral 用 AI 智能体迁移 4 万行 Fortran 77 遗留代码

Mistral 分享如何用 AI 智能体将 4 万行 Fortran 77 科学计算代码迁移到 C++,并提炼出可复用的方法论。

AI智能体遗留代码现代化FortranC++
成长分 / 100 79 综合收获、行动、留存与影响

Mistral 用 AI 智能体迁移 4 万行 Fortran 77 遗留代码
为什么值得读了解 AI 智能体在复杂遗留代码现代化中的实际应用与局限。

学习如何通过并行测试框架确保迁移前后的数值一致性。

关键洞察
  1. 迁移前构建并行测试框架,通过数值一致性验证迁移正确性,是最高效的证明方式。
  2. 利用 AI 智能体解析调用者-被调用者树并自动生成文档,可解决遗留代码知识丢失问题。
  3. 完全自主的智能体迁移会导致代码仅语法转换而非架构重构,需引入结构化工作流和人工审查。
转成行动

深入阅读

正文与原文对照

原文保真覆盖:全文原文字符:9256

思考

总结

遗留代码现代化极具挑战,尤其是在将复杂的 Fortran 77 系统迁移至现代 C++ 时。Mistral 通过构建用于数值验证的并行测试框架、利用 AI 代理记录代码库,并采用结构化工作流配合人工监督,成功迁移了 40,000 行物理密集型油藏模拟器代码,确保了高质量、可维护的代码。关键经验包括:优先保证数值一致性、在迁移前整理文档,以及平衡代理自主性与人工审查。

遗留科学代码库历经数十年积累,当原作者离开后,代码中蕴含的知识便难以恢复。此外,使用没有活跃开发者生态的语言意味着错失了在他人工作基础上构建的机会。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, NCELL10 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 的应用 AI 团队是一群围绕 Mistral 模型和企业平台构建全栈解决方案的工程师。我们交付高风险的、特定领域的解决方案,以解决世界上一些最难的问题。

如果你想参与这类工作,申请加入我们的团队