Prototiller:Protobuf Editions 迁移的批量重构工具——设计文档与需求解析
本文基于 Prototiller 设计文档目录 及其收录的三份历史设计文档,梳理 Prototiller 的定位、Editions 迁移工具链的总体设计(janitor / adopter / updater)、作为输入协议的 ChangeSpec 推荐 schema,以及 Edition Zero 从 proto2/proto3 迁移时的各类特征(feature)转换规则。读完后,你将理解 Protobuf 团队如何用一套"机械、可增量、语义无副作用"的批量重构工具,把任意 legacy syntax 文件安全地升级到 Editions 模型。
Prototiller 是什么:定位与文档性质
目录说明 对这批文档的定性是:历史设计文档(historical design documents),描述的是 Prototiller 的实现计划。Prototiller 被定位为 Protobuf 的"批量重构瑞士军刀"(mass refactoring Swiss army knife),类比构建系统界的 Buildozer,其用途包括:
- 在 Google 内部(google3)支撑 LSC(Large-Scale Change,大规模变更);
- 允许内部和外部用户安全地批量修改
.proto文件,尤其是把.proto文件更新到符合 Protobuf Editions 的形态。
目录说明同时给出三条阅读须知,读本文时应保持同样的心态:
- 这些文件代表文档首次发布时的状态,后续可能有更新,阅读时应假设其可能已过时;
- 它们纯粹具有历史价值,不应被视为当前实现状态的文档;
- 各文档中署名的作者是原始版本的作者,文档在其最后一次经手之后可能已有变动。
该目录收录三份文档:
- Prototiller Requirements for Editions(作者 @mcy,2022-11-29 批准);
- Prototiller Requirements for Edition Zero(作者 @mkruskal-google,2023-07-07 批准);
- Editions Tooling(作者 @mcy,2022-08-09 批准)。
Editions 工具链总体设计:janitor、adopter 与 updater
Editions Tooling 是三者中最早的一份,回答了"为什么需要工具链"以及"工具链长什么样"。
动机:机械化、增量化的升级
Protobuf Editions 要引入新的语义,但极度强调机械化、增量化的可升级性,以避免 proto2 与 proto3 的"双系统问题"。第一个 edition(很可能是 "2023")会引入收敛语义(converged semantics),允许 proto2 和 proto3 所允许的一切:任何非 editions 文件都可以以最少的人工干预变成 editions 文件。
工具链的设计目标有两条:
- 让 editions 空间内**无法自动化的大规模变更工作,被限制在"修 generated code 的使用处"和"翻转特定字段上的 feature"**这两件事上;
- 给外部用户最无痛的迁移体验,即"运行这个工具,提交结果"。
文档设定了一个前提:内部设计文档 Protochangifier Backend Design Doc(未对外公开)已作为后端集成进 protoc,因此工具可以随 protoc 一起发布。由于这些工具必须知道一个 edition 的完整定义才能工作,文档认为这"近乎硬性地把工具与 protoc 绑定在了一起"。
三个工具
- features janitor(特征清道夫):
protoc的一个模式,输入一个.proto文件,产出一个ProtoChangeSpec,描述如何增删 feature,使清理后的文件显式 feature 更少,但语义不变。 - editions adopter(采纳器):另一个
protoc模式,产出把proto2/proto3文件带入 editions 模式(从某个特定 edition 起步)的ProtoChangeSpec。 - editions upgrader(升级器):adopter 的泛化,接收一个 editions 文件,产出将其带入更新 edition 的
ProtoChangeSpec。
这三个工具本质上都说 ProtoChangeSpec,但文档同时建议提供就地(in-place)版本,因为对外部 OSS 用户来说"对整棵项目原子地跑一次工具"更有用。
Janitor 的启发式最小化算法
Janitor 的典型作用,是把一个到处散落重复 feature 的文件:
edition = "2023";
message Foo {
optional string a = 1 [features.(pb.cpp).string_type = VIEW];
optional string b = 2 [features.(pb.cpp).string_type = VIEW];
optional string c = 3 [features.(pb.cpp).string_type = VIEW];
optional string d = 4 [features.(pb.cpp).string_type = VIEW];
optional string e = 5 [features.(pb.cpp).string_type = VIEW];
}
message Bar {
optional string a = 1 [features.(pb.cpp).string_type = VIEW];
optional string b = 2;
optional string c = 3;
optional string d = 4;
optional string e = 5;
}
收敛为把公共 feature 上提到最近可承载的作用域:
edition = "2023";
message Foo {
option features.(pb.cpp).string_type = VIEW;
optional string a = 1;
optional string b = 2;
optional string c = 3;
optional string d = 4;
optional string e = 5;
}
message Bar {
optional string a = 1 [features.(pb.cpp).string_type = VIEW];
optional string b = 2;
optional string c = 3;
optional string d = 4;
optional string e = 5;
}
文档坦承"真正做最小化是非线性的",因此给出了一个启发式草图:
- 每个可以显式出现在 AST 节点上的 feature,对该节点要么是关键(critical)的,要么仅用于分组。例如
string_type对字段是关键,但对 message 不是; - 把 feature(包括 edition 默认值)显式传播到每一个节点;
- 对每个 feature
f,对每个f非关键、但其(递归)内部含有f关键节点(按 DFS 序)的节点n:把f在n上设为其直接子节点中占多数(plurality)的值,并从那些子节点上删除显式f;平局时若 edition 默认值在多数派之中则取默认值,否则随机选; - 重复到根节点后,删除所有"从根出发、不跨越另一个非默认显式 feature 即可到达"的显式 feature——即被 edition 默认值所蕴含的那些。
文档明确指出:构造出该算法不最优的例子很容易,但那不重要;它存在的目的只是在保持等价的前提下让文件更漂亮,而且由构造可证它满足"语义 no-op"要求。
Adopter 与 Updater:三步 no-op 变换
adopter 只是 updater 的特例——把 proto2/proto3 也看作"edition"(edition 本质是一组默认值)。把旧 edition("old")更新到新 edition("new",不一定更新)的完整流程:
- 补齐显式 feature:凡是顶层尚未显式设置的 feature,都设为 "old" 给出的默认值;只设置在最外层未显式指定该 feature 的作用域上(例如文件级 feature 就全部在文件级显式化;非文件级的 message 级 feature 则放在每个顶层 message 上)。由于
edition = "old";本就蕴含这些值,这一步是 no-op; - 改 edition 声明:把文件的 edition 从 "old" 改为 "new"。由于所有可能显式的 feature 都已是显式,这一步同样是 no-op;
- 运行 feature janitor:显式传播所有 feature,然后按 "new" edition 清理(janitor 偏好 edition 默认值)。由于 janitor 本身是 no-op,整条链路语义不变。
这三步的巧妙之处在于:"先固化旧默认 → 换 edition 标签 → 再按新默认去冗余",每一步单独看都保持语义等价,从而整体升级是安全的。
与 protoc 绑定的 UX 设计
工具随 protoc 分发的用户体验约定(适用于所有打包进 protoc 的 Protochangifier 工具):
--change_spec=changespec.pb:让 protoc 对传入的.proto文件应用一个 changespec,例如protoc --change_spec=spec.pb --change_out=foo-changed.proto foo.proto,改动写入foo-changed.proto;--change_out可以与输入同文件(就地更新),也可以省略(改动打印到 stdout)。这是 Protochangifier 的核心入口;- 每个分析项有一个形如
--my_analysis(如--janitor)的 flag,可选参数:若给出路径(如--janitor=spec.pb)则把 changespec 输出到该路径;若不给,则直接就地应用改动,无需再走--change_spec。
文档还讨论了"为什么不做成独立二进制":从分发和用户教育角度,"这就是编译器的一部分"更容易被找到;团队预期会持续基于 Protochangifier 产出新的迁移工具,让用户学会"所有分析长一个样"很重要。文中以 Rust 的 rustfix 为例:它虽是独立二进制,但通过 cargo fix 暴露——cargo 才是 Rust 的用户界面;把工具放进"瑞士军刀"里才能放在用户面前。
ChangeSpec 输入协议:面向 Editions 的推荐 schema
Prototiller Requirements for Editions 从原始设计 Protochangifier Semantic Actions(未对外公开)出发——该设计让 Prototiller 消费一个描述"对输入 .proto 文件要做什么改动"的 Protobuf 消息——规定了一个只满足 Editions 需要、但保留未来扩展性的接口变体。
总需求有两条:
- 动作必须覆盖以下 Editions 升级工作流:
- 把文件升级到某个特定 edition,无论它当前是 syntax 模式还是 editions 模式,且更新 feature 的方式保证 no-op——这是最高优先级工作流;
- "清理"某个文件的 feature:即运行简单算法,求出每一层级必须存在的最小 feature 集合;
- 修改某个语法元素上的 feature;
- 动作既要能精确到特定语法元素(供 Schema Consumers 把变更规格与
.proto文件一起 check in 的场景),也要通用(一份或一组变更规格即可驱动大规模变更)。
文档随后给出一份推荐的 Protobuf schema(注意:这只是建议,Prototiller 项目所有者可以按实现需要修改;约束性的只有需求,schema 只是需求的图示):
syntax = "proto2";
package prototiller;
// This is the proto that Prototiller accepts as input.
message ChangeSpec {
// Actions to execute on the file.
repeated Action actions = 1;
// Some changes may result in a wireformat break; changing field type is
// usually unsafe. By default, Prototiller does not allow such changes,
// users can set allow_unsafe_wire_format_changes to true to force the change.
optional bool allow_unsafe_wire_format_changes = 2 [default = false];
optional bool allow_unsafe_text_format_changes = 3 [default = false];
optional bool allow_unsafe_json_format_changes = 4 [default = false];
}
// A single action. See messages below for description of their
// semantics.
message Action {
oneof kind {
UpgradeEdition upgrade_edition = 20;
CleanUpFeatures clean_up_features = 21;
ModifyFeature modify_feature = 22;
}
}
// Upgrades the edition of a file to a specified edition.
// Treats syntax mode as being a weird, special edition that cannot be
// upgraded to.
//
// This action is always safe.
message UpgradeEdition {
// The edition to upgrade to.
optional string edition = 1;
}
// Cleans up features in a file, such that there are as few explicitly set
// features as necessary.
//
// This action is always safe.
message CleanUpFeatures {}
// Modifies a specific feature on all syntax elements that match and which can
// host that particular feature.
//
// Prototiller must be aware of which changes affect wire format, so that it
// can flag them as unsafe.
message ModifyFeature {
// The name of the feature to modify.
repeated proto2.UninterpretedOption.NamePart feature = 1;
// A pattern for matching paths to syntax elements to modify.
//
// Elements of this field can either be identifiers, or the string "*", which
// matches all identifiers. Thus, ["foo", "Bar"] matches the message foo.Bar,
// ["foo", "Bar", "*"] matches all fields and nested types of foo.Bar
// (recursively), and ["*"] matches all elements of a file.
repeated string path_pattern = 2;
// The value to set the feature to. If not set, this means that the
// feature should be deleted.
oneof value {
int64 int_value = 20;
double double_value = 21;
// ... and so on.
}
}
几个关键设计点:
ChangeSpec顶部的三个allow_unsafe_*开关默认都是false:改变字段类型之类可能破坏 wireformat 的操作,Prototiller 默认拒绝,只有用户显式打开对应开关(wire format / text format / json format)才强制执行。这把"安全性"做成了协议的显式一等公民;Action用 oneof 承载三种动作,且UpgradeEdition与CleanUpFeatures在注释中被明确标记为"always safe"——正好对应前文最高优先级工作流和 janitor 能力;UpgradeEdition把 syntax 模式视为一种"无法被升级到的古怪特殊 edition",即 legacy syntax 文件是升级的起点而非终点;ModifyFeature.path_pattern的匹配语义:数组元素可以是标识符或通配符"*"。["foo", "Bar"]匹配 messagefoo.Bar;["foo", "Bar", "*"]递归匹配foo.Bar的全部字段与嵌套类型;["*"]匹配文件内所有元素。这同时满足了"精确到某个元素"与"一份规格驱动全库"两种需求;ModifyFeature.value未设置即表示删除该 feature;而feature字段复用proto2.UninterpretedOption.NamePart,使得扩展 feature(如features.(pb.cpp).string_type)也能按路径寻址。
文档的 Alternatives Considered 还解释了两处取舍:
- 为什么不把 feature 清理作为其他动作的隐式副作用:因为期望能在各处激进地单独运行它,甚至嵌入 Cider 等 IDE 的"保存时格式化";
- 为什么不让
ModifyFeature一次作用于整个文件的所有元素:它的设计目标是让 SchemaConsumers 对自己 import 的.proto文件做细粒度控制——有时想把整个文件所有字段上的某个 feature 抹掉,有时只关心少数字段。简单的模式匹配两者都能支持。
Edition Zero 迁移:具体转换规则全解
Prototiller Requirements for Edition Zero 是最实操的一份:Edition Zero 用 feature 统一 proto2 与 proto3,要迁移内部仓库(并协助 OSS 迁移),Prototiller 负责把 legacy syntax 升级到新模型。文档的关键论断是:由于 edition zero 的 feature 就是这样推导出来的,从 proto2/proto3 到 edition zero 一定存在一个 no-op 变换——但细节相当复杂,取决于很多因素。
文档还提到一个基准:当时已有一个临时脚本作为占位实现,覆盖了相当多的规则,但无法处理 extension 或 oneof 里的 group;它连同它的 golden 测试可以作为 Prototiller 的有用基准(这一点在当前仓库中仍有呼应,见文末"仓库中的佐证")。
Feature 优化阶段
降低大规模变更摩擦的关键一环是 feature 优化:如果某个 feature 不是让升级成为 no-op 所必需的,就不该添加它,而应依赖 edition 默认值以利于后续变更;同时应通过把 feature 规格合并到更高层级(例如把字段 feature 折叠成文件级默认)来最小化变更总量。
前端特征转换(Frontend Feature Transformations)
Field Presence
field_presence 默认值为 EXPLICIT,对应 proto2/proto3 的 optional 行为;LEGACY_REQUIRED 对应 proto2 required 字段;IMPLICIT 对应非 optional 的 proto3 字段。为最小化变更,应利用文件级默认。
转换示例 1(proto2 起点):
// 迁移前
syntax = "proto2";
message Foo {
optional string bar = 1;
required string baz = 2;
}
// 迁移后
edition = "2023";
message Foo {
string bar = 1;
string baz = 2 [
features.field_presence = LEGACY_REQUIRED];
}
转换示例 2(proto3 起点,mixed presence,用文件级默认 + 字段级例外):
// 迁移前
syntax = "proto3";
message Foo {
optional string bar = 1;
string baz = 2;
string bam = 3;
}
// 迁移后
edition = "2023";
features.field_presence = IMPLICIT;
message Foo {
string bar = 1 [features.field_presence = EXPLICIT];
string baz = 2;
string bam = 3;
}
转换示例 3(proto3 起点,全是 optional——此时默认 EXPLICIT 已覆盖,什么都不用写):
// 迁移前
syntax = "proto3";
message Foo {
optional string bar = 1;
optional string baz = 2;
}
// 迁移后
edition = "2023";
message Foo {
string bar = 1;
string baz = 2;
}
Enum Type
enum_type 默认 OPEN,对应 proto3 行为;CLOSED 对应典型 proto2 行为。同样应优先用文件级默认。
// 迁移前(proto2)
syntax = "proto2";
enum Foo {
VALUE1 = 0;
VALUE2 = 1;
}
// 迁移后
edition = "2023";
features.enum_type = CLOSED;
enum Foo {
VALUE1 = 0;
VALUE2 = 1;
}
而 proto3 起点则完全无需 feature:
// 迁移前(proto3)
syntax = "proto3";
enum Foo {
VALUE1 = 0;
VALUE2 = 1;
}
// 迁移后(与迁移前等价,只换 edition 声明)
edition = "2023";
enum Foo {
VALUE1 = 0;
VALUE2 = 1;
}
Repeated Field Encoding
repeated_field_encoding 默认 PACKED(proto3 行为);EXPANDED 对应 proto2 的默认。proto2 和 proto3 都可以用 packed 字段 option 覆盖默认——这些覆盖在迁移中都要被替换。最小化变更在这里更复杂,因为可能存在"多数 repeated 字段都被手动覆盖"的文件。
转换示例 1(proto2,默认 EXPANDED + 个别 PACKED 例外):
// 迁移前
syntax = "proto2";
message Foo {
repeated int32 bar = 1;
repeated int32 baz = 2 [packed = true];
repeated int32 bam = 3;
}
// 迁移后
edition = "2023";
features.repeated_field_encoding = EXPANDED;
message Foo {
repeated int32 bar = 1;
repeated int32 baz = 2 [
features.repeated_field_encoding = PACKED];
repeated int32 bar = 3;
}
转换示例 2(proto3,packed = false 例外):
// 迁移前
syntax = "proto3";
message Foo {
repeated int32 bar = 1;
repeated int32 baz = 2 [packed = false];
}
// 迁移后
edition = "2023";
message Foo {
repeated int32 bar = 2;
repeated int32 baz = 2 [
features.repeated_field_encoding = EXPANDED];
}
转换示例 3(proto2,全部是"默认"情形:packed = true 与默认 PACKED 一致,string 字段本就不 packed):
// 迁移前
syntax = "proto2";
message Foo {
repeated int32 x = 1 [packed = true];
// Strings are never packed.
repeated string z = 1;
repeated string w = 2;
}
// 迁移后:无任何显式 feature
edition = "2023";
message Foo {
repeated int32 x = 1;
repeated string z = 1;
repeated string w = 2;
}
Message Encoding
message_encoding 用于取代 proto2 独有的 group 语法(对应值 DELIMITED),默认恒为 LENGTH_PREFIXED。这个转换在一般情况下比较别扭:group 定义允许出现在字段可出现的任何地方,而 message 定义不允许。基本变换是:在最外层作用域新建一个与字段同名的 message 类型,把原字段改名为小写并使用该类型。
转换示例 1(普通字段位置的 group):
// 迁移前
syntax = "proto2";
message Foo {
optional group Bar = 1 {
optional int32 x = 1;
}
optional Bar baz = 2;
}
// 迁移后
edition = "2023";
message Foo {
message Bar {
int32 x = 1;
}
Bar bar = 1 [features.message_encoding = DELIMITED];
Bar baz = 2;
}
转换示例 2(oneof 内的 group——正是前文所说临时脚本覆盖不了的难点区域之一):
// 迁移前
syntax = "proto2";
message Foo {
oneof foo {
group Bar = 1 {
optional int32 x = 1;
}
}
}
// 迁移后
edition = "2023";
message Foo {
message Bar {
int32 x = 1;
}
oneof foo {
Bar bar = 1 [
features.message_encoding = DELIMITED];
}
}
JSON Format
json_format 是个例外项:至少在 edition zero 中它只影响 proto 文件的前端构建。ALLOW(proto3 行为)启用对字段名的全部 JSON 映射冲突检查,除非设置了 deprecated_legacy_json_field_conflicts;LEGACY_BEST_EFFORT(proto2 行为)关闭这些检查。理想的最小变换是:除"设置了 deprecated_legacy_json_field_conflicts"或"存在 JSON 映射冲突"的情况外一律切到 ALLOW,其余情形回退到 LEGACY_BEST_EFFORT。备选方案是:如果 Prototiller 处理困难,可以事后做一次大规模变更,删掉所有能通过构建的 LEGACY_BEST_EFFORT 实例。
转换示例 1(proto2,无冲突字段):
// 迁移前
syntax = "proto2";
message Foo {
optional string bar = 1;
optional string baz = 2;
}
// 迁移后
edition = "2023";
message Foo {
string bar = 1;
string baz = 2;
}
转换示例 2(proto3 起点):
// 迁移前
syntax = "proto3";
message Foo {
string bar = 1;
string baz = 2;
}
// 迁移后
edition = "2023";
features.field_presence = IMPLICIT;
message Foo {
string bar = 1;
string baz = 2;
}
转换示例 3(proto2,存在 JSON 名冲突,仅告警级):
// 迁移前
syntax = "proto2";
message Foo {
// Warning only
string bar = 1;
string bar_ = 2;
}
// 迁移后:回退到 LEGACY_BEST_EFFORT
edition = "2023";
features.json_format = LEGACY_BEST_EFFORT;
message Foo {
string bar = 1;
string bar_ = 2;
}
转换示例 4(proto3 + deprecated_legacy_json_field_conflicts,json_name 可以随之删除):
// 迁移前
syntax = "proto3";
message Foo {
option
deprecated_legacy_json_field_conflicts = true;
string bar = 1;
string baz = 2 [json_name = "bar"];
}
// 迁移后
edition = "2023";
features.field_presence = IMPLICIT;
features.json_format = LEGACY_BEST_EFFORT;
message Foo {
string bar = 1;
string baz = 2;
}
后端特征转换(Backend Feature Transformations)
文档首先提出一个"控膨胀"原则:理想情况下应检查一个 proto 文件是否真的会被用来生成目标语言的代码;如果不是,就没有理由添加后端特定 feature。
Legacy Closed Enum
Java 与 C++ 默认把"用在 proto2 message 中的 proto3 enum"当作 closed enum 处理。内部的 cc_open_enum 字段 option 可以覆盖这一行为,但使用面极窄,未必值得考虑。虽然 enum 类型行为仍应由 Enum Type 决定,但对于使用 proto3 message 的 proto2 文件(反向则被禁止),需要添加此 feature。
转换示例:
// 迁移前
syntax = "proto2";
import "some_proto3_file.proto"
enum Proto2Enum {
BAR = 0;
}
message Foo {
optional Proto3Enum bar = 1;
optional Proto2Enum baz = 2;
}
// 迁移后
edition = "2023";
import "third_party/protobuf/cpp_features.proto"
import "third_party/protobuf/java_features.proto"
import "some_proto3_file.proto"
features.enum_type = CLOSED;
message Foo {
Proto3Enum bar = 1 [
features.(pb.cpp).legacy_closed_enum = true,
features.(pb.java).legacy_closed_enum = true];
Proto2Enum baz = 2;
}
注意扩展 feature 的写法 features.(pb.cpp).legacy_closed_enum——这正是 ModifyFeature.feature 复用 UninterpretedOption.NamePart 所要支持的多段命名。
UTF8 Validation
该 feature 在设计文档撰写时尚待审批(对应内部文档 Editions Zero Feature: utf8_validation,未对外公开)——这体现了这批文档"历史快照"的属性。
其他转换:Reserved 标识符语法
除 feature 外,edition zero 还有一类非 feature 变更。按内部 PEP Protobuf Change Proposal: Reserved Identifiers(未对外公开),reserved 字段从字符串改为标识符。文档认为这"应该是个琐碎变更",但若文件里含有非法标识符的字符串,存在歧义:它们今天会被忽略,但可能是我们不想盲目删除的笔误。因此变换策略是:合法标识符直接转换,非法的留下注释而非删除。
转换示例 1:
// 迁移前
syntax = "proto2";
message Foo {
reserved "bar", "baz";
}
// 迁移后
edition = "2023";
message Foo {
reserved bar, baz;
}
转换示例 2(非法标识符保留为注释):
// 迁移前
syntax = "proto2";
message Foo {
reserved "bar", "1";
}
// 迁移后
edition = "2023";
message Foo {
reserved bar;
/*reserved "1";*/
}
仓库中的佐证与阅读边界
以下仓库证据可以帮助定位 Prototiller 在 protobuf 仓库中的实际足迹(均为只读查看路径):
- upb_generator/minitable/BUILD 中依赖了
//third_party/prototiller/transformer(第 105 行附近),说明 prototiller 的 transformer 曾作为 Bazel 构建依赖参与 golden 文件变换的校验; - src/google/protobuf/compiler/java/doc_comment.cc 第 131 行附近存在一条以 "TODO: Remove once prototiller can avoid making..." 开头的注释——从源码结构看,代码生成器中留有等待 prototiller 能力提升后可移除的过渡性处理,说明该工具与 codegen 行为改进存在联动;
- editions/golden/edition2023_transform/ 下收录了 proto2/proto3 输入经变换到 edition 2023 后的 golden 产物(如
proto2.proto、proto3.proto、test_messages_proto3.proto等),配合 editions/input/edition2023_transform/ 的输入与 editions/generated_files_test.cc 的校验测试,正呼应了 Edition Zero 需求文档中"临时脚本及其 golden 测试可作为 Prototiller 有用基准"的说法——仓库用 input/golden 对加回归测试的方式,把变换正确性固化了下来; - editions/BUILD 中大量
compile_edition_defaults目标以protoc_minimal编译各 edition 的默认值,佐证了 Editions 工具链"必须知道一个 edition 的完整定义"这一设计前提在构建层面确实存在。
最后重申阅读边界:本仓库中 Prototiller 相关代码并未开源(//third_party/prototiller/transformer 为外部依赖),ChangeSpec schema 也只是需求文档中的推荐图示而非最终接口;上述三份文档描述的是 2022–2023 年间的实现计划。若要在当前代码库上实施 editions 迁移,应以 editions/ 目录下的现行实现、golden 文件与测试为准,把这些设计文档作为理解"为什么这样设计"的历史参考。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00