Storybook @storybook/angular-cm 深度解析:基于热 LanguageService 的进程内 Angular Docgen 元数据解析器
@storybook/angular-cm 是 Storybook 仓库中为 Angular 渲染器提供的进程内(in-process)docgen 引擎:它不再依赖外部 Compodoc 进程和 documentation.json,而是直接用 TypeScript LanguageService 从组件源码里读出输入、输出、方法与 JSDoc 元数据,再转成 Storybook 的 argTypes。读完本文,你将理解这条"源码 → 元数据记录 → argTypes → props 表"的完整数据链路、propsTable 配置项的三档过滤语义,以及该解析器目前已知但未修复的三类边界限制(数字枚举、编译库继承 IO、hostDirectives),从而能够在自定义 Angular 项目集成与上游协作时做出准确判断。
一、包定位:一个不发布的内部包,是 Compodoc 管线的继任者
包的 README 开宗明义:
- 这是"进程内 Angular docgen"——用热(warm)
LanguageService直接从 TypeScript 源码读取组件元数据; - 核心类
AngularComponentMetaManager为每个匹配到的 tsconfig 保持一个热LanguageService,产出 Compodoc 形状的(Compodoc-shaped)记录,再由本包自带的extractArgTypes把记录转成 argTypes; - 包是 Node-only 且 internal 的:它不会独立发布,而是被打进
@storybook/angular-vite。package.json 中"private": true、sideEffects: false,唯一peerDependency是工作区内的storybook,devDependencies里只带了typescript(^6.0.3,仅用于测试),生产环境中分析器会在运行时使用宿主项目自己的 TypeScript(见第五节)。
与 @storybook/angular-compodoc 的关系:有意不同步的 fork
README 专门用一节说明了它与老管线 angular-compodoc 的关系,这是理解本包代码注释的关键背景:
这里的转换是
@storybook/angular-compodoc中那份的有意 fork。后者将随 Compodoc 管线一起在 Storybook 11 中删除。那份副本被其已提交的基线(baselines)钉死在旧行为上保持冻结;本包是继任者,只携带修正后的规则,因此两者不做同步,修复应该提到这里来。
extract-arg-types.ts 的文件头注释也重复了这一点:老包被基线测试钉住不能改行为,新包只写"修正过的规则"。这意味着阅读新代码时不能拿老包的行为当作参照,反之亦然。
二、核心架构:AngularComponentMetaManager 与每个 tsconfig 一个热 LanguageService
2.1 Manager 的公共 API
manager.ts 中导出两个关键符号:
export class AngularComponentMetaManager extends ComponentMetaManager<
AngularComponentMetaProject,
ts.ParsedCommandLine
> {
constructor(typescript: typeof ts) {
super(typescript, createAngularProjectFactory(typescript));
}
/** 提取单个组件的元数据;文件及其 re-export 目标里都找不到
* 匹配 `names` 的类时返回 undefined */
extractComponentMeta(
componentPath: string,
names: { exportName: string; localName?: string }
): AngularComponentMetaResult | undefined
/** 堆内存压力下释放缓存项目;没有批量接口时按每次 payload 调用 */
recycleIfHeapPressured(): void
}
- 继承自
storybook/internal/component-meta的通用基类ComponentMetaManager,项目工厂负责 tsconfig 解析(parseTsconfigCommandLine)、项目创建与回收; - 类上的文档注释给出了生命周期契约:热
LanguageService会常驻内存,因此要用startWatching()/dispose()包住 manager 的整个存活期; extractComponentMeta是唯一面向调用方的提取入口,参数是一个文件路径加{ exportName, localName }两个名字——localName用于 re-export 场景(export { X as Y })下按源文件的真实类名兜底。
工厂 createAngularProjectFactory 中有三个值得注意的工程细节:
- 共享的
FileSnapshotCache(fsFileSnapshots: Map):所有项目共用同一份文件文本快照,快照比任何单个项目活得都久,所以recycle/dispose时必须fsFileSnapshots.clear()(L56-L59); - 共享的
DocumentRegistry:注释写明这是为了让各项目复用已解析的SourceFile,而不是每个项目各自持有一份lib.d.ts的解析结果; - 推断项目(inferred project)的宽松默认值:对于任何 tsconfig 都不覆盖的文件(例如放在工作区外或
include之外的组件),会创建一个带以下选项的"推断项目"(L37-L48):
{
strict: false,
allowJs: true,
skipLibCheck: true,
experimentalDecorators: true, // 这些"无主"文件仍在使用旧式装饰器
target: ts.ScriptTarget.Latest,
module: ts.ModuleKind.ESNext,
moduleResolution: ts.ModuleResolutionKind.Bundler,
}
测试 manager.test.ts 中"serves files no tsconfig covers from the inferred project"用例验证了这条兜底路径:一个没有 tsconfig 的临时目录里写入组件文件后,getProjectForFile 返回 configFileName 为 undefined 的项目,且仍能提取出 @Input() amount。
2.2 项目内部:手写 LanguageServiceHost 与文件新鲜度
project.ts 的 AngularComponentMetaProject 继承通用基类 ProgramBackedProject,构造时创建 ts.LanguageService。有几处实现决策都写在注释里:
- Host 是手写的(L47-L68),而不是用 Volar 那套——因为 Angular 组件就是普通 TS 文件,不需要语言插件或 script-id 映射;
- 刻意不实现
getProjectReferences:注释说明"遵守它会丢弃被引用项目里的文件",这里要的是把它们都拉进来; - 实现
realpath:不做 realpath,TS 无法对符号链接包去重,会导致类型身份(type identity)分裂。
extract() 的主流程(L76-L135)是理解"为什么提取永远是新鲜的"的关键:
this.files.ensureFresh([fileName])——即使没有 watcher、或提取落在 watcher 防抖窗口内,也先强制核对目标文件的磁盘状态;- 若文件不在 program 根集里(推断项目、或组件不在 tsconfig 的
include内),调ensureFiles后重新取 program; - 对该组件的 import 闭包做一次
ensureFresh扫描(importClosure 以node_modules为边界——注释的理由是"依赖的源码不会在运行中的 dev server 下变化"),保证基类文件的修改也能让继承来的成员刷新; - 拿到
TypeChecker后调用analyzeSourceFile得到整文件的AngularFileMeta,再依次尝试:按导出名直查(pickEntry,含 default export 的类名解析)→ 沿模块导出解析 re-export(extractViaModuleExports,处理export { X as default }、桶文件等,且跳过声明文件里的声明)→ 都失败时打出一条列出该文件实际声明了哪些类的 debug 日志——注释说"把名字不匹配从静默失败变成显眼失败,这是 props 表为空的常见原因"。
2.3 热更新与测试基线
manager.test.ts 用一个共享 manager 覆盖了整条生命周期(与生产环境一致,因为"构建 program 很昂贵")。值得留意的用例:
- 嵌套 tsconfig:
nested/子目录下的组件解析到子 tsconfig 的独立项目,而不是根项目; - 继承提取:
ToggleComponent的输入同时包含本类的@Input() label、signalinput()的size、以及来自 base-toggle.ts 的继承输入disabled; - 桶文件 / 别名 re-export / default 导出:
barrel-star.ts、barrel-named.ts、aliased-default.component.ts分别验证星号桶导出、改名 re-export、export { X as default }; - 热更新三件套:
onFilesChanged之后重提取(含 mtime 被钉在同一秒的极端用例)、基类编辑后继承成员刷新、以及没有任何变更事件时靠ensureFresh的 mtime 扫描兜底拾取磁盘变化。
三、分析器:把 SourceFile 解析成 Compodoc 形状的记录
3.1 分类与跳过规则
analyze-file.ts 的 analyzeSourceFile(ts, sourceFile, checker) 遍历顶层语句:
enum/type声明先喂给TypeIndex(供第 4 节的枚举与类型别名解析用);- 类声明先查两个"静默丢弃"条件:带 JSDoc
@ignore标签的类、以及@NgModule类(都只打 debug 日志,L38-L46); - 其余类按装饰器名分类(classify):
Component/Directive/Pipe/Injectable/ 普通 class; - 对 component/directive,额外从装饰器对象字面量里静态读出
selector和standalone;读取时支持"标识符引用其初始化器"这一级求值(resolveInitializer 注释称这是"ngtsc 自己的部分求值器所接受的一小部分")。
3.2 记录的数据模型
types.ts 定义了全部形状,注释自述"原始形状贴近 Compodoc 的 JSON schema,分析器自有的字段则使用更严格的契约"。要点:
Directive(含type: 'component' | 'directive')记录被拆成四个桶:propertiesClass/methodsClass/inputsClass/outputsClass;普通 class、Pipe、Injectable则是properties/methods(普通类也会附带inputsClass?/outputsClass?,提取器对非指令条目忽略它们);Property上几个对下游规则至关重要的字段(L58-L78):visibility?: 'private' | 'protected'——public 成员省略;internal?: boolean——是否带 JSDoc@internal标签;type?——分析器无法定型的成员(如@HostBinding)省略;required?——仅 signal input 与@Input({ required })有;initializer?: PropertyInitializer——语法分类后的源码初始化器,分literal(含string/number/boolean/null/undefined/bigint/enum/composite八种literalKind)与expression两种,注释明确"表达式是元数据,不是展示用默认值";line?: number——成员声明的行号(1-based),这是model()结构性识别的基础(见 4.5);
AngularFileMeta把整文件按 components/directives/pipes/injectables/classes 分桶,外加miscellaneous: { typealiases, enumerations };AngularComponentMetaResult是extractComponentMeta的返回值:entry(解析到的类记录)+json(整文件记录,供提取器做别名/枚举解析)+ 可选jsDocInfo(类级 JSDoc 描述与标签)。
3.3 继承合并与类型渲染
inheritance.ts 的 resolveClassMembers 把基类成员折叠进子类:沿 checker 的 getBaseTypes 递归走继承链(带环保护),处理泛型基类的类型参数替换(extends 子句省略的参数会回退到参数默认值,默认值本身也会做替换),最后才应用本类自己的 @Directive({ inputs }) 元数据映射——注释强调"递归在这里完成而不是分开导出各 pass,就是为了保证这个顺序不需要调用者记住"。
其中有一段代码直接对应 README 的第二条已知限制(见第六节):
// 声明文件不记录装饰器或 signal 调用,所以来自它的基类
// 对 IO 桶没有任何可贡献的内容。
if (!declaration.getSourceFile().isDeclarationFile) {
mergeBucket(ctx, members, baseMembers, 'inputs');
mergeBucket(ctx, members, baseMembers, 'outputs');
}
mergeInto(members.properties, baseMembers.properties, members);
mergeInto(members.methods, baseMembers.methods, members);
(L84-L91)——来自 .d.ts 的基类,属性和方法照常合并,但 IO 桶被跳过。
type-index.ts 的 TypeIndex 负责"渲染类型文本 + 记录文本提及的命名类型",注释解释了为什么两半必须绑在一起:提取器仅凭名字把成员 type 字符串对照 miscellaneous 解析,如果登记的名字和渲染文本用的名字不一致,就永远查不到。其 render 是刻意语法化的(L37-L104):字符串字面量双引号(JSON.stringify,保证内嵌引号/反斜杠仍可被提取器的 JSON.parse 兜底解析)、联合用 | 连接、函数/构造函数渲染为真实签名 (...) => T / new (...) => T——后者对应提取器里 isFunctionTypeString 对 new 前缀和泛型签名的放宽(早期只认开头 ( 会把这两种都打进 empty-enum 兜底)。
四、extractArgTypesFromData:从记录到 argTypes 的转换规则
extract-arg-types.ts 是本包导出面的一部分(见 index.ts),签名:
extractArgTypesFromData(
componentData: Class | Directive | Injectable | Pipe,
{ metadataJson, propsTable, logger }: ExtractArgTypesOptions
): StrictArgTypes
其中 propsTable 没有默认值,注释写明"故意设为必传,让宿主继承不到静默默认"。
4.1 PropsTableMode:一条严格的档位阶梯
L35-L48 定义了核心概念:
/**
* 哪些成员进入 props 表,作为一条严格阶梯:`all` ⊃ `api` ⊃ `inputs`。
*
* - `all`:所有分区的每个成员。
* - `api`:组件面向模板的表面——无论 TypeScript 可见性如何声明的输入与输出,
* 加上所有非 TS `private`、非 ES `#`、且不带 JSDoc `internal` 标签
* 的属性和方法。
* - `inputs`:inputs 分区,外加一个被文档化的 `model()` 为了让双向绑定成立
* 而必需的 `${name}Change` 输出。
*/
export type PropsTableMode = 'all' | 'api' | 'inputs';
过滤函数 documentedInMode(L405-L417)对 inputs/outputs 分区只按 @internal 过滤,注释解释了一个容易被误解的点:TypeScript private 与 ES # 只挡代码与本组件模板的访问,消费方模板绑定 input/output 不看修饰符(Angular 只在 opt-in 的 strictInputAccessModifiers 下才看),所以 inputs/outputs 区不因可见性被隐藏;而属性和方法分区则要求非 private。
4.2 required 语义与分区
required判定(L85-L88):required跟随@Input({...})键的存在与否,optional必须与之一致;键缺失时看初始化器——带默认值的输入永远不是必绑的:(item.required ?? item.initializer === undefined) && !item.optional;- 分区顺序固定为
SECTION_ORDER(L71-L80):properties → inputs → outputs → methods → view child → view children → content child → content children。带@ViewChild/@ViewChildren/@ContentChild/@ContentChildren装饰器的属性被分流到对应分区,而不是堆在 properties 里。
4.3 枚举、联合与类型别名的控制(control)推导
extractType(L244-L275)决定一个 input 显示什么控件:
string/boolean/number直接对应同名 SB 类型;- 函数类型字符串:
function或以(、new、<...>(开头的箭头/构造签名 → function 控件; - 类型别名递归解析:
resolveTypealias沿miscellaneous.typealiases的rawtype递归,seen集合防type A = B; type B = A互引用例——注释说这不是优化,而是"递归会把整个同步 docgen worker 拖垮,而不是只影响一个组件"; - 纯原语联合:联合成员全是
boolean/number/string时,控件取最窄成员(boolean优先于number优先于string),但summary保留完整联合——这是为了 coercion 变换产生的boolean | string、可选的string | undefined之类不再掉进 JSON object 控件; - 枚举:
extractEnumValues在miscellaneous.enumerations里按名字找,且所有 child 都必须带值才成立,否则回落到对联合字面量的JSON.parse,再失败就是{ name: 'other', value: 'empty-enum' }兜底。
多声明同名时由 pickDeclaration(L143-L161)做确定性裁决:优先选与组件同文件的声明,再按文件路径 localeCompare 排序取第一个——注释直指 Compodoc 的痼疾:"Compodoc 每次运行枚举条目的顺序都不同,而像 Size 这种名字通常每个组件文件夹都声明一次,first-wins 会让控件类型在源码没变的情况下漂移。"
4.4 默认值:绝不发明数值
extractDefaultValue 的取值优先级是:JSDoc @default/@defaultvalue(裸标签不可用,多标签取最后一个,字符串会去掉成对引号)→ 源码初始化器(仅 literal 六种 literalKind 直接取值,bigint/enum/composite 保留文本,expression 走 debug 日志"non-literal default not shown")→ undefined。castDefaultValue(L294-L316)对 boolean/number 做安全转型,EventEmitter 一律不给默认值,注释原则是"绝不发明数值:缺失的默认保持缺失,而不是变成 NaN/false"。
4.5 model() 的结构性识别与 Change 合成
这是与老 Compodoc 管线共享、且被 angular-compodoc README 专门记录的一条规则:Angular 的 model() 同时是输入 foo 与输出 fooChange,但元数据只在两个数组里以裸名 foo 各出现一次,从不发 fooChange。JSON 里没有任何 model() 标记,所以检测是结构性的:同名 input 与 output 出现在同一声明行。仅凭同名不够——@Input('shared') 与 @Output('shared') 会形成同样的名字碰撞但没有 model(),把这种当双向绑定会删掉真实输出、发明出不存在的 sharedChange。这正是 Property.line 字段存在的理由("两个生产者因此都必须在能被该规则匹配的成员上记录 line")。
实现见 getModelProperties 与 L514-L541:识别出 model() 后丢弃裸名输出副本,并合成 ${name}Change 输出(类型为 (e: T) => void、无 defaultValue 无 required)。该合成在 inputs 档下也会发生——注释说明这一档"收窄分区但不能拆散一对被文档化的绑定",只有当该档把 model 本身藏掉时才跳过。
五、接入点:@storybook/angular-vite 的 docgen 管线
@storybook/angular-cm 被打包进 code/frameworks/angular-vite 使用,接入点有两层:
1. worker 中间件(docgen-worker.ts):
const createManager = async (): Promise<AngularComponentMetaManager | undefined> => {
try {
// 首次使用时才导入,让分析器看到的是项目构建所用的 TypeScript 版本,
// 而这个版本正是本包刻意不随包发布的。
const typescript = await import('typescript');
const manager = new AngularComponentMetaManager(typescript.default ?? typescript);
manager.startWatching();
return manager;
} catch (error) {
logger.warn(`Angular docgen is unavailable: ...`);
return undefined;
}
};
(L15-L31)——manager 在整个 worker 生命周期内只建一次,LanguageService 因此在组件之间保持热状态;创建失败时降级为把请求放行给下一个 provider,而不是让 docgen 整体报错。createDocgenProvider 返回的中间件对每个 story 条目:非 story 文件直接放行 → 懒建 manager → buildDocgenPayload → 每次提取后调 recycleIfHeapPressured()(注释:"没有批量表面可挂这个钩子,所以堆压力检查按每次提取做一次")→ 我们的 payload 若带 error 则不覆盖链上其他 provider 的结果("用我们自己的错误替换别人的 payload,等于让我们这条链对一个自己不了解的组件行使一票否决权")。
2. payload 构建(build-docgen.ts):buildDocgenPayload 从 story 文件解析出 meta.component 指向的类,然后依次处理几类失败——组件表达式无法解析、import 无法解析(提示检查 import specifier 与 tsconfig 路径别名)、extractComponentMeta 抛异常(AngularComponentMetaExtractionFailed)、提取结果为空(AngularComponentMetaNotFound,提示"确认文件导出组件类且被其目录或上层的 tsconfig.json 覆盖")。成功路径上:
- 用
propsTable配置调用extractArgTypesFromData得到argTypes; - 另跑一遍
propsTable: 'api'生成apiArgTypes供apiDescription使用(L220-L229)——注释说明这是为 Agent 文档固定的:"无论用户给 props 表选了什么,Agent 文档都钉在api:all会把 Agent 无法绑定的私有布线交出去,inputs又会让 Outputs 分区变空"; metaToSnippetMeta(L79-L104)把记录压成组件片段元数据(name/selector/standalone/inputs/outputs/enums),其中 output 绑定名对model()加Change后缀,与 4.5 的合成规则一致。
六、配置面:propsTable 的三档取值与兼容映射
面向用户的开关只有一个,解析逻辑在 props-table.ts:
export const resolvePropsTable = (
frameworkOptions: PropsTableInput,
features: Features
): PropsTableMode => {
const deprecatedFlag = features?.angularFilterNonInputControls;
const inherited = deprecatedFlag === undefined ? undefined : deprecatedFlag ? 'inputs' : 'all';
return configuredMode(frameworkOptions) ?? inherited ?? 'api';
};
(L27-L35)语义为:
@storybook/angular-viteframework 选项里的propsTable('all' | 'api' | 'inputs')优先;拼错的值会被configuredMode拦下——注释解释"拼错的值来自无类型 JS 配置,放任它会半生效,各管线各读各的垃圾字符串",并由warnAboutPropsTable打印 "Ignoring the unknownpropsTablevalue ... expected 'all', 'api' or 'inputs'";- 未配置时回落到已废弃的
features.angularFilterNonInputControls:true→'inputs',false→'all',并打 deprecation 提示"将在 Storybook 11 移除",同时告知propsTable框架选项优先级更高; - 两者都没有 → 默认
'api'。
一个典型配置形态(framework 选项传入 options 对象,与 resolvePropsTable 读取的 frameworkOptions.propsTable 对应):
// .storybook/main.ts
export default {
framework: '@storybook/angular-vite',
options: {
propsTable: 'api', // 'all' | 'api' | 'inputs',缺省即 'api'
},
features: {
experimentalDocgenServer: true, // 'api' 档依赖该特性开启,见下
},
};
warnAboutPropsTable(L44-L72)还有一条与特性开关联动的警告:propsTable: 'api' 需要 experimentalDocgenServer 特性开启,否则"props 表仍会显示所有成员"(因为该特性关闭时 docgen preset 整体被跳过)——即配置了 'api' 但特性关闭时实际效果等价于 'all',需显式开特性或改设为 'all'。
七、已知限制:继承自 Compodoc 的三类缺口(当前未修复)
README 的 "Known limitations" 一节列出了三条从 Compodoc 行为继承、尚未修复的限制。结合源码可以看清每条的成因与边界:
7.1 数字枚举产生不了控件
数字枚举产生不了控件。
enum Size { Small, Medium }与enum Size { Small = 1, Medium }都表现为empty-enum而非 select,因为自增成员没有可读的初始化器。字符串枚举与联合类型别名能正确解析。TypeChecker 可以通过getConstantValue回答这个问题,所以这在本文的分析器里以 Compodoc 做不到的方式可修。
成因链路:TypeIndex.enumMemberValue(L344-L360)只在成员有字符串字面量或数字字面量初始化器时产出 value,注释特意说明"数字初始化器保持为 number,所以 0 成员是 falsy,能正确禁用提取器的枚举路径";自增成员没有初始化器 → value 缺失 → extractEnumValues 要求 childs.every(hasEnumValue) 不成立 → 回落到 empty-enum。字符串枚举每个成员都有字面量,所以正常;type Size = 'small' | 'medium' 走联合字面量的 JSON.parse 路径也正常。README 同时指出修复方向:checker.getConstantValue 能算出自增成员的实际值——这是 Compodoc 静态解析做不到、而本包(持有 TypeChecker)做得到的一点。
7.2 从编译库继承的输入/输出被跳过
从编译库继承的 inputs 和 outputs 被跳过。 来自
.d.ts的基类会贡献其普通属性与方法,但不贡献其 IO,因为装饰器已被擦除;Angular 把它们记录在ɵɵDirectiveDeclaration的类型参数里,而本分析器不读它。
对应 inheritance.ts L84-L91 的 isDeclarationFile 判断:声明文件里 @Input() 装饰器与 input() 调用都不存在(编译产物只留下类型签名),分析器选择只合并属性与方法、放弃 IO,而不是猜测。后果是:基类在 npm 包(如某个组件库)里、只以 .d.ts 形式进入本项目时,其输入/输出不会出现在 props 表里。
7.3 hostDirectives 绑定缺席
hostDirectives绑定缺席。 组件通过指令组合(directive composition)暴露的输入和输出不是类成员,因此永远进不了 props 表。
分析器的工作单位是"类成员 + 装饰器",@Component({ hostDirectives: [...] }) 引入的绑定发生在 Angular 编译期的宿主合成上,源码里没有任何类字段可分析,属于结构性盲区。
八、小结
@storybook/angular-cm 展示了"用编译器替代文档生成器"的一条完整工程路线:
- 进程内:热
LanguageService+ 共享DocumentRegistry/快照缓存,替代 Compodoc 外部进程与 JSON 产物;manager.ts 负责多 tsconfig 项目划分与生命周期,project.ts 负责文件新鲜度与 re-export 解析; - 确定性:Compodoc 形状的记录(types.ts)刻意保留
line等强契约字段,pickDeclaration的同文件优先裁决与seen防环,都是针对老管线非确定性行为的定点修正; - 可控的文档表面:
propsTable: 'all' | 'api' | 'inputs'三档阶梯(extract-arg-types.ts + props-table.ts),默认api,且 Agent 侧文档固定使用api档; - 已知边界:数字枚举、
.d.ts基类 IO、hostDirectives三类缺口均有明确成因(见第七节),且 README 明确它们是"继承而来、尚未修复",其中数字枚举一项已被标注为在本包中可修。
相关入口文件:code/lib/angular-cm/src/index.ts(extractArgTypesFromData / AngularComponentMetaManager 及类型的公共导出)、code/frameworks/angular-vite/src/docgen/build-docgen.ts(payload 构建)、code/lib/angular-compodoc/README.md(将被删除的旧管线及其 model() 规则的背景)。
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