Mistral 复盘用 AI Agent 迁移 40000 行 Fortran 77 到 C++ 的经验
Modernizing complex legacy code with AI agents. Lessons from 40,000 lines of Fortran.
精选理由
原文来自一线项目复盘,给出了可迁移的遗留代码现代化方法,包括先建数值对齐校验和结构化 Agent 工作流的具体经验。
AI 摘要
Mistral 帮助一家欧洲能源运营商将 40000 行 Fortran 77 储层模拟器迁移到 C++,并复盘了方法与经验。
正文 · AI 翻译
Mistral 帮助一家欧洲能源运营商将 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, 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 辅助遗留代码现代化的局限性。
第一个冲刺周期覆盖了核心功能:30 万行代码中的 4 万行。该 Fortran 代码库是自包含且可运行的,这是一个有利的起始条件。依赖外部系统、缺乏可运行基线或包含未在任何文档中记录的物理知识的迁移,会带来本文未讨论的额外挑战。
应用三项原则来现代化复杂的遗留系统。
这个项目的三条经验应该适用于任何大型遗留系统迁移。
在编写迁移代码之前先构建一致性测试框架——数值一致性是证明模块已完成的最廉价、最有说服力的方式。
在依赖智能体之前先把文档整理好,因为你无法迁移没人能读懂的代码。
在这种规模下,带有审查关卡的结构化工作流胜过完全自主和纯手动操作。
我们正在招聘!
Mistral 的应用 AI 团队由一群工程师组成,他们围绕 Mistral 模型和企业级平台构建全栈解决方案。我们交付高价值、面向特定领域的解决方案,以攻克一些世界上最棘手的问题。
如果你希望参与这类工作,欢迎申请加入我们的团队。
原文
Original Title
Modernizing complex legacy code with AI agents. Lessons from 40,000 lines of Fortran.
Source
Mistral AI:News(网页)
Site
mistral.ai
Published
2026-09-10 02:59