试想一下:将一个 Golang 项目(大象)改写为(装进) Rust(冰箱) 总共需要几步?
当 Rust 语言为我们展示出在「性能」、「安全」、「协作」等方面诱人的特性之后,却因为其陡峭的学习/上手曲线拒人千里之外。是否存在一种科技,能够帮助我们的同学在语言学习和项目迁移上完美并行,最终真正将 Rust 项目迁移这个看似美好的荆棘之果转变为触手可得的「低垂果实」呢?
为了将美好的愿望转变为实际,我们结合 LLMs 做了一些尝试,利用 LLMs 在编程语言上体现出的「涌现」能力,设计了一套基于 LLMs 的应用开发基座(ABCoder),在这个基座之上进一步演进出了我们本篇的主角:「半空」。
ABCoder 是字节内部一个编程向 LLMs 应用开发基座,包含自研的 LLMs 原生解析器、工具(Tools)以及工作流(Workflows),对编程项目本身进行深度解析、理解和压缩,并将其制作为源码知识库(Source code as Knowledge),之后利用这类知识库实现对 LLMs 推理过程中所需上下文进行补齐,从而构建出高质量、低幻觉、稳定的编程类 LLMs 应用。有关 ABCoder 更多的介绍可以参考这里。
按照 ABCoder 的设想,让 LLMs 理解编程项目的入口就是结合对项目的解析、理解、压缩后的知识关联和构建,这对于一个轻量化的应用来说可能足够(ABCoder 当前已经能够实现将一个标准 Hertz 项目“转述”为一个 Volo-HTTP 项目),但对应到实际场景中的业务项目来说(增加大量业务属性且复杂度更高),要想真正让 LLMs 完整理解整个项目,并且在有需要的时候让 LLMs 完整的将整个项目“转述”为另外一个语言的项目时我们就需要对我们的解析、理解、压缩、应用流程进行更加细粒度的设计和优化了。
「半空」主要讨论的就是对于复杂项目的理解提升和辅助 LLMs 渐进式多轮迭代构建出一个复杂项目的可行性。核心需要解决的是因为项目规模提升所带来的复杂度以及上下文规模提升和 ABCoder 所制作的对应知识库知识密度跟不上的矛盾。
罗马不是一日建成的,参考软件工程标准的项目迭代方式,迭代一个越庞大的项目,引入的标准作业流程和所花费的迭代周期和人力就越多。ABCoder 要想深刻的解析并理解一个大型项目,一口永远吃不成一个胖子。
好消息是构建一个复杂项目的过程是有迹可循的的,ABCoder 需要做的其实就是逆着项目构建的路径,反向解析出项目构建过程中涉及到的不同粒度的知识库。
之后将这些知识库输入 LLMs 驱动的 Workflows,通过构建渐进式的多轮迭代流,将原来的项目以任意编程语言又输出出来,基于对知识库的持续构建,甚至实现为其他语言的项目:语言翻译。
相较于给 LLMs 一段代码,让他直接翻译为另外一个语言(直译),「半空」所做的类比下来更像是:帮助 LLMs 理解代码,之后经过抽象和设计结合我们希望它采纳的知识,重写出另外一个语言实现的版本(意译)。
按照 ABCoder 的通用处理流,一个任意庞大的项目我们几乎都可以通过解析、级联压缩的方式构建函数、方法、结构体、变量的语义化映射。但仅仅通过这些散落的信息 LLMs 是没有办法高效的建立一个对这个项目系统深刻的理解。因此我们在做 LLMs 辅助的项目文档梳理应用的时候,就已经开始下意识的做一些单元聚合工作了:通过将某个包(文件/模块)中的函数、方法、结构体、变量语义化含义进一步抽象,得到关于这个包(文件/模块)的语言和框架无关的高层次语义化抽象,按照这个思路,我们可以自底向上抽象,到最终项目维度。
举个直观的例子,对于 Hertz 的项目,任意一个 Hertz 项目在项目维度都能够抽象为形如:这个项目是一个基于 HTTP 应用框架的应用,它或许注册了/a/b/c 路由 (Route)的 GET 方法(Method),关联了某个对应的逻辑(Handler) 。
仔细分析这个抽象,尝试对其中蕴含的细节进行总结:
多轮的抽象和迭代的本质是项目在不同维度上多语言实现和 ABCoder 抽象语义的不断对齐:
配合语言对应的知识库建设,按照标准抽象块(已归一化逻辑)进行知识检索,分层分模块持续迭代,填充核心逻辑,辅助业务完成项目构建。
当我们通过上述解析和抽象,得到了关于一个项目完整的理解知识,之后就可以至顶向下辅助 LLMs 逐层实现项目的渐进式迭代了。同样,接着上一小结里提到例子来说,我们在这层抽象上做的事情就是:
以上即是对一轮迭代核心流程的描述,完成本轮迭代之后即可开启下一层抽象的对齐。之后按照这个流程持续的迭代这个项目。
因为抽象本身会丢掉本层部分细节,而丢掉的这部分细节其实还是保留在抽象前的层级中的,对应迭代路径来说,上一层丢掉的细节一定会在下一层迭代中被补充回来。因此,通过多轮的迭代构建出来的项目,理论上也并不会丢失具体的实现细节。
每一层迭代后都会有一次人工介入时机 —— 即可以及时人工介入修改代码并反馈到后续的翻译轮次中,这也是「半空」的核心能力之一 —— 在这个切面上能够按需的扩展任意的软件测试解决方案,包括时下流行的:LLMs 辅助 UT 生成等技术。等到所有的修改和测试通过之后,即可开启下一层的迭代或者选择直接退出手动接管剩余的翻译工作。
作为用户最为关心的部分,「半空」究竟在项目 Go2Rust 转换(存量 Golang 项目改写为 Rust)上帮助我们做到哪些事情呢?其实非常简单,好比将大象装进冰箱,「半空」辅助下的 Go2Rust 自动化迁移也是三个核心步骤:
实际上,结合简介中的描述,聪明的小伙伴也许已经发现:「半空」作为一套通用框架,应用面其实并不仅仅局限在 Go2Rust 上,对于任意语言之间的相互转换逻辑上都是完全一致的,区别在于对语言特异性处理和特定语言的知识库构建。「半空」一期重点针对 Go2Rust 场景完成内场的适配和持续打磨,后续如果有对更多语言栈(Python2Go/Java2Go/...)的切换诉求也非常欢迎勾搭~
一个使用「半空」做 Go2Rust 项目转换的示例
Easy_note 是 CloudWeGo 社区对外提供的一个基于 Hertz 和 KiteX 的实现复杂、功能覆盖度高的业务实战示例项目;其使用 Hertz 提供了若干 API 接口,并在接口实现中通过 KiteX client 发起对下游 KiteX Server RPC 接口的调用。
本次使用「半空」翻译的是其 API 模块,其主要功能列表如下:
用户管理
笔记管理
涉及到的 Hertz/KiteX 框架相关的核心能力如下:
从输入原始项目产出 ABCoder 理解知识原料开始,「半空」会结合函数粒度知识原料,自底向上完成整个项目的逐层抽象和理解,之后至顶向下完成重构设计的制定,同时确定项目渐进式构建顺序:从粗粒度知识映射到细粒度知识映射到最后逐个 Package 的实现,最终完成 Golang 项目到 Rust 项目的渐进式构建(意译)。这个过程中项目构建进度完全由用户掌控,结合人工修改反馈辅助协同,推动项目完成 Go2Rust 迁移落地。
上图提到的 Golang AST / Rust AST 是 ABCoder 在分析仓库代码,将函数、方法、结构体、变量等定义以树形关联出来的数据结构体集合,是一个能够与项目一比一映射的 LLMs原生抽象语法树。
根据 ABCoder 解析后的项目原料,「半空」自动化根据 Package 的依赖关系完成了使用 Rust 重构这个项目所需的设计文档的编写,自顶向下得到如下迭代顺序:
对应 MR: https://github.com/cloudwego/biz-demo/pull/83
main package,主要实现了 HTTP server 的初始化、路由注册调用等能力
Golang 原始实现 | 「半空」意译效果 |
---|---|
main() | main() |
customizedRegister() | customized_register() |
常量定义[本轮不实现,只mock] | 常量定义[mock实现] |
结果评估
main
package 的内容,都生成到 Rust 项目的 /src/bin/main.rs
下;后续支持细粒度的文件模块映射数据统计
生成节点完备率=无需改造的节点/生成节点总数
可编译度=1-修改的代码行数/生成的代码总行数
对应 MR: https://github.com/cloudwego/biz-demo/pull/84
hertz_handler package 主要实现了一个 ping handler,用于处理 ping-pong 请求
Golang 原始实现 | Rust 意译效果 |
---|---|
Ping() | ping() |
结果评估
cmd/api/hertz_handler
包的内容,都生成到 Rust 项目的 /src/cmd/api/hertz_handler/mod.rs
下数据统计
生成节点完备率=无需改造的节点/生成节点总数
可编译度=1-修改的代码行数/生成的代码总行数
对应 MR: https://github.com/cloudwego/biz-demo/pull/85
hertz_router 包主要实现 Hertz 路由的总体注册逻辑,调用 idl 生成的路由
Golang 原始实现 | 「半空」意译效果 |
---|---|
eneratedRegister() | generated_register() |
Register()[本轮不实现,只mock] | register()[mock] |
结果评估
cmd/api/hertz_router
包的内容,都生成到 Rust 项目的 /src/cmd/api/hertz_router/mod.rs
下数据统计
生成节点完备率=无需改造的节点/生成节点总数
可编译度=1-修改的代码行数/生成的代码总行数
对应 MR: https://github.com/cloudwego/biz-demo/pull/86
hertz_router/demoapi
package 主要实现了具体了路由注册(idl 映射)以及 Hertz 中间件的定义
Golang 原始实现 | 「半空」意译效果 |
---|---|
Register() | register()[路由注册有问题,需要 check & 修改] |
rootMw() | root_mw()[包含了中间件里的 mock 实现] |
mw 定义 | mw 定义 |
CreateUser[本轮不实现,只mock] | create_user[mock] |
结果评估
cmd/api/hertz_router/demoapi
包的内容,都生成到 Rust 项目的 /src/cmd/api/hertz_router/demoapi/mod.rs
下数据统计
生成节点完备率=无需改造的节点/生成节点总数
可编译度=1-修改的代码行数/生成的代码总行数
对应 MR: https://github.com/cloudwego/biz-demo/pull/87
Golang 原始实现 | 「半空」意译效果 |
---|---|
CreateNote() | create_note() |
SendResponse() | send_response() |
rpc CreateNote[本轮不实现,只mock] | rpc create_note[mock] |
ErrNo[本轮不实现,只 mock] | ErrNo[mock] |
以下都以"create_note" 接口为例,进行结果评估
结果评估
cmd/api/hertz_handler/demoapi
包的内容,都生成到 Rust 项目的 /src/cmd/api/hertz_handlers/demoapi/mod.rs
下数据统计
生成节点完备率=无需改造的节点/生成节点总数
可编译度=1-修改的代码行数/生成的代码总行数
至此,通过 5 轮迭代的拆解,我们就完成了 "github.com/cloudwego/biz-demo/easy_note/cmd/api" 这个 moudle 的全部翻译,用户在 check 完整个项目后,即可以编译 & 运行项目。
完备性说明:完全无需人工介入的函数统计为完备函数
package | 生成函数的个数 | 完备函数的个数 | 完备率 |
---|---|---|---|
easy_note/cmd/api | 4 | 2 | 50% |
easy_note/cmd/api/hertz_handler | 1 | 1 | 100% |
easy_note/cmd/api/hertz_router | 1 | 1 | 100% |
easy_note/cmd/api/hertz_router/demoapi | 13 | 8 | 62% |
easy_note/cmd/api/hertz_handler/demoapi | 7 | 1 | 14% |
可编译说明:相对于整体生成代码行数,人工介入修改的代码行数占比,需要修改的代码越少,可编译度越高
package | 生成函数的行数 | 人工修改的代码行数 | 可编译度 |
---|---|---|---|
easy_note/cmd/api | 106 | 28 | 73% |
easy_note/cmd/api/hertz_handler | 19 | 1 | 95% |
easy_note/cmd/api/hertz_router | 9 | 1 | 88% |
easy_note/cmd/api/hertz_router/demoapi | 173 | 38 | 76% |
easy_note/cmd/api/hertz_handler/demoapi | 254 | 30 | 88% |
整体上,通过知识库的持续建设和关键知识的补齐,「半空」也会“越用越好,越用越聪明”,在完备性和可编译度上也会随之持续提升。
在这个过程中,结合「半空」为我们生成的 Rust 项目设计文档,从整体项目的角度出发,逐步对每个包进行深入理解、翻译与确认。这一过程条理清晰、循序渐进地将一个 Golang 项目从零构建为一个 Rust 项目。同时,我们一同参与项目构建的每一个迭代,「半空」每一个迭代生成的代码完全遵循内场和业内 Rust 项目编写的最佳实践,这不仅帮助我们深刻理解整个项目,同时也为学习一门新语言提供了极大的支持。通过这种逐步渐进迁移的方式,我们能够不断深入学习并掌握 Rust 语言及项目本身,最终成功完成项目的转型。