官方更新 Mistral News 2026-09-18T00:00 正文 ok 1276 words

利用 AI Agent 现代化重构复杂遗留系统:Mistral 助力 4 万行 Fortran 到 C++ 的工程实践

英文标题:Modernizing complex legacy code with AI agents.

Mistral 近日分享了利用 AI Agent 协助欧洲能源运营商将 40,000 行物理密集型 Fortran 77 遗留代码迁移至现代 C++ 的实战方案。面对缺乏测试用例、文档缺失且高度依赖全局内存的极端挑战,团队通过构建数值对齐测试框架(Parity Harness)、结合 OCR 与调用链树重建文档,并设计了人机协同的多 Agent 结构化工作流。实践证明,在大型复杂代码重构场景下,具备人工把关节点的结构化 Agent 工作流显著优于完全自主迁移与纯人工编写。该成果为工业和科学计算领域利用 AI 解决技术债务、推进系统现代化提供了极具参考价值的工程范式。
打开原文相关 3.3 · 机会 2
media1.3 KB media1.3 KB media1.2 KB media1.2 KB media1.2 KB media1.2 KB media1.6 KB media1.6 KB media1.2 KB media1.2 KB media1.7 KB media6.3 KB media4.3 KB media10.1 KB media220.0 KB media220.0 KB media67.2 KB media67.2 KB media75.4 KB media75.4 KB

中文

遗留科学计算代码库往往沉淀了数十年。当最初的作者离职后,嵌入在代码中的领域知识便极难挽回。此外,使用缺乏活跃开发者生态的语言,意味着错失了站在他人肩膀上进行二次开发的机会。Mistral 最近协助一家欧洲能源运营商,将其 40,000 行的 Fortran 77 代码迁移至 C++。这是一个重度依赖物理计算的油藏模拟器(reservoir simulator),此前既没有自动化测试套件,也没有集中的文档记录。

超越代码直译:遗留代码现代化的深层挑战

将语法从一种编程语言翻译为另一种,基本上已是一个被解决的问题。让任何现有的前沿模型将一段非完全生僻语言的代码片段翻译为另一种语言,通常在几次迭代内就能收敛出可接受的结果。然而,若要将一个完整系统从面向过程的语言迁移到面向对象的 C++,则需要进行深层架构重构,这让任务变得极具挑战。

顾名思义,Fortran 77 于 1977 年完成标准化,用它编写的代码直接反映了那个时代的限制:没有模块(modules)、没有命名空间(namespaces)、没有结构化类型(structured types)。状态存储在 COMMON 块中——即在整个程序中共享的全局内存。变量类型由其首字母隐式决定,因此拼写错误不会引发编译器报错,而是会悄无声息地创建一个新变量。

来看一个简单但极具代表性的一阶泰勒展开(first-degree Taylor expansion)实现示例。输入和输出都是 COMMON 块中的全局变量,而 IC 仅仅因为其变量名以 I 到 N 之间的字母开头才成为整数。变量名也相当晦涩,因为其长度被限制在 6 个字符以内。

fortran 10 RHOG(IC) = ROG(IV) + DROG(IV)*(P(IC)-PTAB(IV))

而在 C++ 中,开发者可以利用显式类型、编写面向对象代码,并通过返回值替代对全局内存的直接写入:

cpp 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 等现代科学计算框架的要求,甚至在迁移启动前就引发了几个关键问题:

  • 如何在数值上证明迁移后的代码库与原代码库完全一致
  • 如何将迁移任务拆解为可管理的单元
  • 如何最佳地利用自主 Agent(Autonomous Agents)来加速这一过程

迁移前置:构建数值对齐测试框架(Parity Harness)

在放手让 Agent 运行之前,我们需要一种方法来证明两个代码库的运行结果一致。在这里,“一致”指的是输出的数值对齐(numerical equality)——不仅包括最终结果,还包括客户油藏工程师所标注的一系列关键中间检查点。

我们添加了:

  • 能够导出 Fortran 代码库运行状态的子程序(subroutines)
  • 用于将检查点数据加载到 C++ 中的测试框架
  • 用于引导 Agent 正确使用它们的 Skill.md 文件

在迁移工作流中,Agent 成功在 Fortran 代码库中植入探针以转储状态快照,并利用 C++ 测试框架来验证迁移后模块的正确性。

在项目初期率先构建这一套验证框架是一项高回报的正向投资:它让耗时较长的 Agent 任务运行更加安全,而且数值对齐是一种简单易验且极具说服力的指标,能够有力证明某段代码已被成功迁移。我们认为,这应该是任何代码现代化项目的首要步骤之一。

下面的示例说明了这一点。我们首先在 Fortran 代码中插入一行,用于转储 RHOG 变量的值(在本轮运行中为 42.71834),随后在测试迁移后的 C++ 模块时,将该数值用作参考检查点。

利用 AI Agent 理解并补充遗留代码库文档

该项目的文档零散散落在陈旧的 PDF 以及深埋在 Fortran 本身的注释中。整项工作最大的意外收获之一,就是梳理并整合了这些文档,并将其归位到代码旁边。

幸运的是,像 Fortran 这样的面向过程代码具有一个便利的特性:整个程序可以绘制为一棵单一的调用者-被调用者树(caller-callee tree)。我们通过使用自定义解析器分析代码库生成了该调用链树,随后利用 Vibe CLI 启动了一百多个 Agent 为其编写文档。每个 Agent 都可以通过文档库和 Mistral OCR 检索相关的 PDF 资料。

Agent 从树的叶子节点开始向上推进,每个节点都会派生出一个子 Agent(subagent)负责撰写文档并向原始仓库提交 PR。一个按 cron 定时循环运行的审核 Agent(reviewer agent)负责监控新提交的 PR,进行审查并在必要时指派修复任务。

利用 AI Agent 进行代码现代化重构

在我们的第一次尝试中,我们给予了 Agent 完全的自主权:每个 Fortran 子程序分配一个 Agent,让它们在一周内各自独立地将对应函数翻译为 C++。结果代码确实可以运行,但绝不能称之为“现代化重构”。COMMON 块被机械地一对一转化为全局结构体;基于 GOTO 的控制流原封不动保留,而不是被重构为循环或提前返回语句。它看起来更像是用 C++ 语法重新敲了一遍 Fortran,而非现代化代码。

在第二次尝试中,我们通过为 Agent 引入结构化分工而非单纯放权来解决此问题:为每个模块配备由规划器(planner)、编写者(coder)、测试者(tester)和代码质量审核者(code quality reviewer)组成的协同小组。代码质量相比第一次尝试有了实质性提升。然而,源代码本身的复杂度最终还是拖垮了 Agent:它们会遭遇 bug,尝试修复几次后便陷入停滞,且无人能够介入。

最终,我们找到了一个折中方案:由人类操作员掌控由 coder、tester 和 reviewer agents 组成的工作流,按模块逐一迁移代码库。这既保留了第二次尝试中的代码质量,又增加了一个人类检查关卡,以便在 Agent 陷入僵局时及时排障解锁。下一节将详细介绍该工作流。

针对复杂代码迁移运行结构化 AI Agent 工作流

有了完备的代码库文档和数值对齐测试框架后,剩下的关键环节就是微调我们能赋予 Agent 多大程度的自主权,同时依然能产出可合并的代码。我们测试了两种极端情况:从完全自主运行到高强度监督的人工作业。最终针对该场景确定了如下结构化工作流。

我们与客户的油藏工程师合作,利用调用链树划分出独立的模块——即规模可控的自包含子树(经验上,每个子树少于约 10,000 行 Fortran 代码)。每个模块都执行相同的流程:

  1. 生成目标 C++ 架构。
  2. 与油藏工程师共同进行审查。
  3. 审核通过后,将其拆解为任务队列。
  4. 针对每个任务运行实现子工作流:规划 → 实现 → 测试 → 重复。
  5. 人类审查生成的 PR 并提出修改意见,直至顺利合并。

认清 AI 辅助遗留代码现代化的局限性

首个冲刺阶段覆盖了核心功能:总计 300,000 行中的 40,000 行。该 Fortran 代码库本身是自包含且可运行的,这是一个有利的先决条件。如果迁移项目依赖外部系统、缺乏可运行的基准、或者涉及未在任何地方记录的物理学原理,将会带来本文未探讨的其他挑战。

复杂遗留系统现代化的三项核心原则

从该项目中总结出的三条经验,适用于任何大型遗留系统迁移:

  1. 在编写迁移代码之前先构建数值对齐测试框架——数值一致是证明模块迁移完毕成本最低、最具说服力的证据。
  2. 在借助 Agent 之前先整理好文档——因为谁也无法迁移一段没人读得懂的代码。
  3. 面对这种规模的工程,具备人工审查关卡的结构化工作流完胜完全自主运行与纯手工敲代码。

English / 原文

Legacy scientific codebases accumulate over decades, and when original authors leave, the knowledge embedded in the code becomes hard to recover. Moreover, using languages with no active developer ecosystem means missing out on the opportunity to build on top of others’ work. Mistral helped a European energy operator migrate 40,000 lines of Fortran 77 to C++, a physics-intensive reservoir simulator with no test suite and no centralized documentation.

Moving beyond code translation in legacy code modernization.

Translating syntax from one language to another is a largely solved task. Asking any recent model to translate a snippet from a non-completely-obscure language to another, will likely converge to an acceptable outcome in few iterations. However, migrating a full system from a procedural language to object-oriented C++ creates the need for architectural refactors that make the task non-trivial.

Fortran 77 was standardized in 1977, as the name may suggest, and code written in it reflects those constraints directly: no modules, no namespaces, no structured types. State lives in COMMON blocks—global memory shared across the entire program. Variables are implicitly typed by their first letter, so a misspelled name silently creates a new variable instead of raising a compiler error.

For a simple but illustrative example, take the following implementation of a first-degree Taylor expansion. Inputs and outputs are globals in a COMMON block, and IC is an integer only because its name starts with a letter between I and N. Variable names are quite cryptic, as their length is capped to 6 characters.

10 RHOG(IC) = ROG(IV) + DROG(IV)*(P(IC)-PTAB(IV))

In C++, one will be able to leverage explicit types, write object-oriented code, and return a value returned instead of a global write:

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]);

The scattered COMMON arrays become a single GasProperties parameter and the grid loop moved out to the caller, so there's no line-for-line correspondence to check, which is what makes verifying the migration hard.

These structural differences, plus the requirement to integrate modern scientific computing frameworks such as PetSc, raised a few important questions even before starting the migration:

  • How to prove the migrated codebase matches the legacy one numerically

  • How to split the migration into manageable chunks

  • How to best use autonomous agents to speed up the process

Building a parity harness before migrating legacy code.

Before letting agents loose, we needed a way to prove the two codebases agreed. Here, "agreement" meant numerical equality of the outputs—both the final results and a set of critical intermediate points flagged by the client's reservoir engineers.

We added:

  • subroutines allowing to export the state of the Fortran codebase

  • a test framework to load the checkpoints into C++

  • Skill.md files to steer agents into using them correctly

In the migration workflows, agents successfully instrumented the Fortran codebase to dump state snapshots and used the C++ test framework to verify correctness of migrated modules.

Building this part of the harness first was a net-positive investment for the project: it made long agent runs safer, and numerical parity is an easy-to-verify and compelling argument to show a piece of code has been successfully migrated. We believe this should be one of the first steps for any code modernization engagement.

The example below illustrates this. We first inserted a line into the Fortran code to dump the value of the RHOG variable (42.71834 in this run), and then used that same value as the reference checkpoint when testing the migrated C++ module.

Using AI agents to understand and document legacy codebases.

The project’s documentation was scattered across old PDFs and comments buried in the Fortran itself, and one of the largest side-wins of the whole effort was reconciling this documentation and moving it next to the code.

Luckily, procedural code like Fortran has a convenient property: the whole program can be drawn as a single caller-callee tree. We generated that tree by parsing the codebase with a custom parser, then used Vibe CLI to spawn over a hundred agents to document it. Each agent could pull in the relevant PDFs through document libraries and Mistral OCR.

Starting from the leaves of the tree and working upward, each node spawned a subagent to document it and open a PR to the original repository. A reviewer agent running in a loop on a cron schedule looked for newly opened PRs, reviewing them and scheduling fix tasks when necessary.

Utilizing the AI agents for code modernization.

On our first attempt, we gave agents full autonomy: one agent per Fortran subroutine, each translating its function to C++ independently over the course of a week. The result was functional, but it couldn't be called code modernization. COMMON blocks became global structs, one-to-one. GOTO-driven control flow stayed intact instead of being restructured into loops or early returns. It looked like Fortran retyped in C++ syntax rather than modernized code.

In the second attempt, we addressed this by giving agents structure instead of just autonomy: a planner, a coder, a tester and a code quality reviewer working together on each module. Code quality improved substantially over the first attempt. But the source code's complexity caught up with the agents eventually. They would hit a bug, attempt a few fixes, and stall, with no one available to intervene.

We landed on a middle ground: a human operating a workflow of coder, tester, and reviewer agents, migrating the codebase module by module. This preserved the code quality of the second attempt while adding a human checkpoint to unblock agents when they got stuck. The next section covers this workflow in detail.

Running structured AI agent workflows for complex code migration.

With the codebase documented and the parity harness in place, the remaining fun bit was tuning how much autonomy we could hand the agents while still getting mergeable code out. We tried both extremes, from fully autonomous runs to closely supervised manual sessions. The structured workflow below is where we landed for this use case.

Working with the client's reservoir engineers, we used the caller-callee tree to identify independent modules—self-contained subtrees of manageable size (empirically, less than ~10’000 lines of Fortran). Each module ran through the same workflow:

  1. Generate the target C++ architecture.

  2. Review it with a reservoir engineer.

  3. On approval, break it into a task queue.

  4. Run an implementation sub-workflow per task: plan → implement → test → repeat.

  5. A human reviews the resulting PRs and requests changes until they merge.

Recognizing the limits of AI assisted legacy code modernization.

The first sprint covered core functionality: 40,000 of 300,000 lines. The Fortran codebase was self-contained and runnable, which is a favorable starting condition. Migrations that depend on external systems, lack a runnable baseline, or encode physics documented nowhere would bring additional challenges not discussed in this post.

Applying three principles for modernizing complex legacy systems.

Three lessons from this project should carry over to any large legacy migration.

  1. Build the parity harness before you write migration code—numerical agreement is the cheapest, most convincing proof that a module is done.

  2. Get the documentation in order before you lean on the agents, because you can't migrate code nobody can read.

  3. And at this scale, structured workflows with human review gates beat both full autonomy and hand-driven manual sessions.


We are hiring!

Mistral's Applied AI team is a group of engineers building full-stack solutions around Mistral models and Enterprise platform. We ship high-stakes, domain-specific solution to solve some of the world’s hardest problems.

If this is the kind of work you want to be involved in, apply to join our team.