AI 重写 Copilot 运行时:从 43 万行 TypeScript 迁移到 80 万行 Rust

2026-09-21 35 预计阅读时间: 1 分钟
来源: oschina.net AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:11 分钟

微软工程师做了一次颇具象征意义的迁移:让 AI agents 重写支撑 GitHub Copilot 产品族的运行时。公开摘要给出的数字很醒目——约 43 万行 TypeScript 变成约 80 万行 Rust,消耗约 12 万美元的 token,并由一名工程师投入三周时间,报告性能提升达到 15.9 倍。

真正值得工程团队关注的,不只是“AI 能写多少代码”,而是这种迁移如何改变软件重写的成本结构:人类不再逐文件翻译,而是定义边界、建立验证系统、审查风险,让 agents 在约束内批量推进。

这不是普通的语言迁移

被迁移的是 Copilot 运行时,而不是一个边缘工具。根据摘要,它支撑 GitHub Copilot CLI、Copilot App 和 SDK 等产品形态。这意味着迁移必须同时面对几类问题:

  • 行为兼容性:相同输入是否产生相同输出、错误和退出码;
  • 协议兼容性:CLI、应用和 SDK 依赖的接口不能随意变化;
  • 并发与资源管理:TypeScript 的异步模型与 Rust 的所有权、线程和异步运行时并非一一对应;
  • 发布安全性:新实现必须能够灰度、回退,并与旧实现并行验证;
  • 可观测性:性能提升不能以丢失日志、指标或错误上下文为代价。

因此,“三周完成”不应理解为三周内凭空生成 80 万行可用代码。更合理的工程解读是:原实现已经提供了相对明确的行为规范,而 AI agents 承担了大量机械迁移、编译修复和测试迭代工作,人类工程师则负责设计验证闭环与处理高风险决策。

80 万行 Rust 并不自动代表更复杂

迁移后代码量从 43 万行增加到 80 万行,很容易被解读成 Rust 版本更臃肿,但仅靠行数无法得出这个结论。

TypeScript 可以依赖运行时动态行为、npm 包以及隐式类型转换;Rust 往往会把类型、错误、协议结构和平台适配显式写进代码。生成式迁移还可能产生更多包装层、重复实现和展开后的样板代码。反过来,更多代码也不等于更安全:如果重复逻辑、无效抽象和未使用分支没有被清理,维护成本仍会快速上升。

报告中的 15.9 倍性能提升同样需要结合测试边界理解。摘要没有给出具体工作负载,因此不能直接推断每个 Copilot 请求、每个平台或所有端到端场景都会快 15.9 倍。评估类似结果时,至少要追问:

  1. 测量的是启动时间、吞吐量、延迟,还是 CPU 密集型任务?
  2. 是否包含网络请求和模型响应时间?
  3. 比较的是开发构建还是优化后的 release 构建?
  4. p50、p95 和 p99 延迟是否都改善?
  5. 内存峰值、二进制体积与崩溃率有没有变化?

在 AI 产品中,模型调用可能占据端到端延迟的大头。运行时快 15.9 倍,并不必然让用户感知到同样幅度的提升,但它仍可能显著改善 CLI 启动、请求编排、本地处理和资源占用。

可复制的迁移方法:先做差分测试,再让 Agent 改代码

大规模重写最危险的做法,是让 agent 一次性生成新仓库,然后依靠人工阅读判断是否正确。更稳妥的方式是把旧程序当作“行为预言机”:对同一批输入分别运行 TypeScript 和 Rust 实现,再逐字节或按结构比较结果。

下面是一个可以直接运行的最小示例。它不是 Copilot 的真实代码,而是演示迁移流水线的简化项目。运行前需要安装 Node.js 和 Rust 工具链。

set -euo pipefail

rm -rf runtime-migration-demo
mkdir runtime-migration-demo
cd runtime-migration-demo

cat > legacy.mjs <<'EOF'
const input = Number.parseInt(process.argv[2] ?? "0", 10);
if (!Number.isFinite(input)) {
  console.error("invalid integer");
  process.exit(2);
}
console.log(JSON.stringify({ input, result: input * 2 + 1 }));
EOF

cargo new --quiet rust-runtime
cat > rust-runtime/Cargo.toml <<'EOF'
[package]
name = "rust-runtime"
version = "0.1.0"
edition = "2021"

[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
EOF

cat > rust-runtime/src/main.rs <<'EOF'
use serde::Serialize;
use std::process::ExitCode;

#[derive(Serialize)]
struct Output {
    input: i64,
    result: i64,
}

fn main() -> ExitCode {
    let raw = std::env::args().nth(1).unwrap_or_else(|| "0".into());
    let input: i64 = match raw.parse() {
        Ok(value) => value,
        Err(_) => {
            eprintln!("invalid integer");
            return ExitCode::from(2);
        }
    };

    println!("{}", serde_json::to_string(&Output {
        input,
        result: input * 2 + 1,
    }).unwrap());
    ExitCode::SUCCESS
}
EOF

cargo build --quiet --release --manifest-path rust-runtime/Cargo.toml

for value in -10 0 1 42 9999; do
  old_output="$(node legacy.mjs "$value")"
  new_output="$(./rust-runtime/target/release/rust-runtime "$value")"
  test "$old_output" = "$new_output" || {
    echo "Mismatch for input $value"
    echo "TypeScript: $old_output"
    echo "Rust:       $new_output"
    exit 1
  }
done

echo "All differential tests passed"

真实项目需要把这个模式扩展到以下对象:

  • 正常输入、边界值和畸形输入;
  • stdout、stderr、退出码与错误类型;
  • 文件系统变更和网络请求序列;
  • 超时、取消、重试与并发行为;
  • 固定数据集上的性能和内存基线。

如果输出中含有时间戳、随机 ID 或字段顺序,比较前应先做规范化,而不是简单比较字符串。对于网络服务,可以录制请求与响应,或让新旧实现同时处理镜像流量,但只允许旧版本产生真实副作用。

Agent 应该接收任务单,而不是一句“全部改成 Rust”

可以把迁移切成可审查的小单元,并给 agent 明确的完成条件。下面这类提示词比开放式要求更适合进入工程流水线:

将 packages/protocol/session.ts 迁移到 crates/protocol/src/session.rs。

约束:
1. 不修改公开 JSON 字段名、默认值和错误码。
2. 不新增网络请求或文件系统副作用。
3. 禁止使用 unsafe。
4. 为每个导出函数添加正常、边界和错误输入测试。
5. 运行旧实现与新实现的差分测试。
6. 提交中附上未能证明等价的行为清单。

完成条件:
- cargo fmt --check 通过;
- cargo clippy -- -D warnings 通过;
- cargo test 通过;
- 差分测试无差异;
- 单次提交只迁移这个模块及其必要依赖。

这里最重要的一条不是“测试通过”,而是要求 agent 列出无法证明等价的部分。AI 很擅长生成看起来完整的实现,也可能在不确定时补出一个合理但错误的行为。显式暴露不确定性,能把人类审查集中到协议、并发、权限和数据处理等高风险区域。

如何判断这类重写是否值得做

12 万美元 token 成本和一名工程师三周投入展示了惊人的生成效率,但它们不应被直接当作完整项目成本。团队还需要计算测试基础设施、代码审查、安全评估、灰度发布、回归处理,以及未来维护 80 万行 Rust 的成本。

准备采用类似方法时,可以用下面的清单做决策:

  • 旧系统是否拥有足够完整的自动化测试和可重放样本?
  • 公共协议能否冻结,并用机器验证兼容性?
  • 新语言是否确实解决性能、内存、安全或部署问题?
  • 团队是否具备维护 Rust 的能力,而不只是生成 Rust 的能力?
  • 是否可以按模块双轨运行,并在异常时快速回退?
  • 性能数字是否来自代表真实流量的 release 构建?
  • 是否对依赖许可证、供应链和 AI 生成代码做了审查?

这次迁移最有价值的信号,不是“AI 可以替代几十万行手写代码”,而是软件重写正在变成一种由验证系统驱动的工作。Agents 可以快速搬运和改写实现,但行为规范、验收标准、风险边界与最终责任仍然必须由工程团队掌握。


相关推荐