- 数据目录
- AI Agent
- 人工智能
- 知识管理
- 示例工程
【免费下载链接】knowledge-catalog
Google Cloud Knowledge Catalog Tools and Samples
本文是 toolbox/mdcode/docs/semantic-model/fidelity.md 的深度解读与扩展。核心主题是:在
kcmd(本仓库toolbox/mdcode中的语义模型命令行工具)中,一份语义模型文档被kcmd push推送到 Knowledge Catalog 与 BigQuery / Spanner 属性图、再由kcmd pull拉回时,每个方向分别保留什么、丢弃什么、规范化什么。读完本文,你将掌握往返矩阵(round-trip matrix)的每一格含义、BigQuery 与 Spanner 两个后端的差异、pull返回的"规范化视图"为何不等于你的原始文件,以及如何围绕这些保真度限制组织你的工作流——结论是:始终以你亲手编写的模型文档为唯一事实来源(source of truth)。
kcmd是语义模型(Semantic Model)的部署工具:它以一份 YAML 文档描述业务的实体、字段、关系、度量,把模型一次性部署到两类目的地——Knowledge Catalog(治理与检索元数据)和属性图(BigQuery Graph 或 Spanner Graph,供查询)。"推"与"拉"都不是无损的:目录只保存元数据,图只保存它可查询的部分,而pull只能返回目录当初被给予的内容。本文档就是一张"每个方向存活什么"的权威对照图。
一、先理解:三个目的地各自的"内存"
在逐格分析前,先明确三个目的地的本质差异,这是理解整个保真度矩阵的前提:
- Knowledge Catalog:持有元数据而非模型全量拷贝。它用一组系统类型(
dataplex-types/global下的内置类型)记录模型的条目、方面(aspect)与链接。唯一的例外是自定义类型semantic-action与semantic-constraint,由kcmd init预置(见 Reference → What gets created in Knowledge Catalog)。push 只引用这些类型,从不创建它们。 - BigQuery Graph:部署一个
CREATE OR REPLACE PROPERTY GRAPH,实体→节点表(NODE TABLE)、关系→边表(EDGE TABLE)、度量→MEASURE;描述性元数据写入每个元素的OPTIONS(...)(可用--print查看生成的 DDL)。 - Spanner Graph:同样的
CREATE OR REPLACE PROPERTY GRAPH,但没有MEASURE、没有OPTIONS元数据——只保留可查询的结构(节点表、边表、LABEL层次)。
哪个后端接收 push,不由命令行标志决定,而是由模型的deployment target(或所选绑定 profile)决定。详见 部署指南 与 binding profiles。
二、往返矩阵(round-trip matrix):一图看懂每个要素的宿命
行是你编写(authored)的模型要素,列是它在每个方向上的结局:✓= 按原样返回;—= 在该目的地不存在。BigQuery 与 Spanner 各占一列;两者在所有结构性行上一致,仅在有 Spanner 目标无MEASURE、无OPTIONS元数据之处不同。
| 编写的要素 | → Knowledge Catalog | pull恢复 | → BigQuery | → Spanner |
|---|---|---|---|---|
实体(name、source) | semantic-entity条目¹⁰ | ✓ | NODE TABLE | NODE TABLE |
| 字段 | schema方面列 | ✓(类型收缩²) | 节点表上的列¹ | 节点表上的列¹ |
字段label | schema逐字段注解 | ✓ | 并入OPTIONS(description) | — 丢弃 |
| 字段表达式(规范 SQL) | 仅--emit-expressions | 仅当随表达式推送 | 生成 DDL | 生成 DDL |
字段维度角色(is_time) | 仅--emit-expressions³ | 仅当随表达式推送³ | 记入OPTIONS(description) | — 丢弃 |
| 主键 | schema.primaryKey | ✓ | 节点表KEY(...) | 节点表KEY(...) |
| 唯一键 | schema.uniqueConstraints | ✓ | — 丢弃(只发主键) | — 丢弃(只发主键) |
| 度量 | semantic-metric条目 | name、entity、description、instructions、type⁵ | MEASURE⁴ | — 丢弃(无MEASURE) |
| 关系(1:1 / 1:N) | schema-join链接 | ✓(名称规范化⁶) | EDGE TABLE | EDGE TABLE |
关系(M:N /association) | — 不存储 | — | EDGE TABLE(经 junction 表) | EDGE TABLE(经 junction 表) |
实体extends | — 未建模 | — | LABEL子句 + 字段展平 | LABEL子句 + 字段展平 |
| 动作(Action) | semantic-action条目¹² | ✓¹² | — 不部署¹² | — 不部署¹² |
| 约束(Constraint) | semantic-constraint条目¹³ | ✓¹³ | — 不部署¹³ | — 不部署¹³ |
description(实体/度量/字段/关系) | 条目描述 / 方面 | ✓ | OPTIONS(description) | — 丢弃 |
ai_context.synonyms | — 不存储 | — | OPTIONS(synonyms=[...]) | — 丢弃 |
ai_context.instructions | guidelines方面⁷ | ✓⁷ | 并入OPTIONS(description) | — 丢弃 |
ai_context.examples | — 不存储 | — | 并入OPTIONS(description)(Examples:行) | — 丢弃 |
模型级description/instructions | 模型条目上 | ✓⁸ | — 丢弃⁸ | — 丢弃⁸ |
模型级ai_context.synonyms/examples | — 不存储 | — | — 丢弃 | — 丢弃 |
| 部署目标 | 记录在模型条目上 | ✓ | 命名图 | 命名图 |
| 绑定 profile(备选物理绑定) | — 只记录被选中的那个¹⁴ | —¹⁴ | 每个 BigQuery 绑定的 profile 一张图 | 每个 Spanner 绑定的 profile 一张图 |
厂商方言expression变体(非规范的dialects[]条目)¹¹ | — 不存储 | — | 回退——仅当无规范expression时用于生成 DDL | 回退——仅当无规范expression时用于生成 DDL |
custom_extensions(超出部署目标) | — 不存储 | —⁹ | — 不在图中 | — 不在图中 |
脚注详解:单元格背后的细微差别
- 字段类型来源。图使用源列(source column)自身的类型;字段编写的
datatype不会被携带。也就是说,字段类型在图中由物理列决定,而不是由逻辑声明决定。 - 字段类型收缩(collapses)。字段类型基本可往返,仅有两处收缩:无类型 →
Opaque,以及String→ 无类型。两者在目录中都存为dataType STRING,靠metadataType区分(OTHER→ 读回Opaque;STRING→ 读回无类型)——这正是它们能以不同方式往返的原因。这一点在源码中有直接印证:knowledge_catalog.ts 的 schemaAspectData 中,无类型字段发布为Opaque(STRING +metadataType OTHER,显式的"类型未知"标记),使 pull 恢复为Opaque而非丢弃类型;编写的String映射为 STRING。测试夹具 actions_place_order.knowledge_catalog.golden.json 与 actions_place_order.pull.golden.yaml 分别展示了这一发布与恢复形态。 - 维度角色。默认推送省略逐字段的
semantics块,因此维度角色只有在--emit-expressions下才会写入——且回来后只是一个裸dimension: {}标记(不带is_time等细节)。默认推送则完全丢弃该标记。 - 度量形态。一个度量必须解析到恰好一个实体(否则 push 被拒绝),并且归约到一个受支持的聚合——
SUM/AVG/COUNT/MIN/MAX——作用于单个操作数(否则以警告跳过)。 - 度量类型。度量的表达式受
--emit-expressions门控;其数据类型仅在为具体类型(如Decimal)时可往返——无类型、String或Opaque度量回来时无类型。(原因在源码中可见:metricAspectData 的注释说明,度量方面模板只携带dataType而没有metadataType,所以Opaque序列化为 STRING,pull 将度量恢复为无类型。) - 关系名称。关系名称回来时被小写化/连字符化(
Places Order→places-order)——目录只在链接 id 中保存名称。见下文"Writer-side follow-up"。 - guidelines 方面。
guidelines方面只存在于模型、实体和度量上——字段与关系没有。因此字段级、关系级的ai_context.instructions在 Knowledge Catalog 中没有归宿(关系的 instructions 仍会到达 BigQuery,折入边的OPTIONS(description))。源码印证见 guidelinesAspectData:只有ai_context.instructions被路由进该方面,synonyms/examples在该方面没有位置。 - 模型级元数据。两个图都没有语句级元数据的归宿——BigQuery 静默丢弃图语句的
OPTIONS,Spanner 完全没有OPTIONS——所以模型的description与ai_context.instructions改由 Knowledge Catalog 承载。 - 其他自定义扩展。在香草
0.2.0.dev0profile 下,custom_extensions块(GOOGLE 部署目标之外的)是厂商元数据的唯一载体;它在 push 时惰性、不持久化到 Knowledge Catalog,因此pull永远不会恢复它。pull还发出扩展的0.2.0.dev0/googleprofile,它没有custom_extensions载体,所以这样的块也不会被重新序列化——保留你的编写文件。OWL 导入器不发出任何此类块:它只导入(见 导入 OWL 本体)。 - 逻辑(未绑定)模型。无绑定的模型仍然发布到 Knowledge Catalog:每个实体的
source记录为空(resources: []),因为背后没有表;不带连接列的关系以警告跳过。当同一次 push 还部署了图时,目录条目首先被裁剪为图所绑定的部分——见下文"To Knowledge Catalog"。 - 厂商方言回退。你编写的是
expression.dialects[]列表;图从规范(BigQuery/ANSI)变体构建。importedExpression/importedDialect不是编写键——loader 从非规范方言条目(例如度量导入自的 MAQL 或 Snowflake 形式)推导它们,并在无规范变体时逐字用作回退。见 Model spec §2.5。 - 动作(Actions)。动作只到达 Knowledge Catalog——作为模型条目下的一个
semantic-action条目,pull从该条目读回。其他每个 push 目标都不为它部署任何东西并警告一次。其guards以约束名存储,并逐字往返。名称所引用的约束若在一次 pull 中缺失,会被保留而非丢弃,因此部分 pull 绝不会静默改写作者的模型;pull 对保留的名称发出警告,push 在约束回归前拒绝该模型。名称被方面重复是唯一例外——会被丢弃到单一出现,因为 loader 拒绝重复且文档必须保持可加载。其affects以同样方式往返,对重复的 concept-and-operation 对也遵循同样的例外。见 Modeling write operations。 - 约束(Constraints)。约束只到达 Knowledge Catalog——作为模型条目下的一个
semantic-constraint条目,pull读回它。其他每个 push 目标都不为它部署任何东西并警告一次。发布是 push 对约束所做的全部;"解决它"的是运行(run),而应用所嵌入的运行时是在写入前把每个 guard 交给 judge 的东西。 - 绑定 profiles。一个模型可以定义多个物理实现,每个绑定 profile 一个。
--all-profiles为每个声明了部署目标的 profile 部署一张图,各自部署到其目标命名的后端;没有目标的 profile 被跳过,两个 profile 声称同一张图则在任何部署前被拒绝。Knowledge Catalog 仍只记录一个绑定——--profile命名的那个,否则是默认绑定——而--all-profiles运行总是记录默认绑定,两个标志互斥。见 Binding profiles。
三、To Knowledge Catalog:目录记录什么、以什么为条件
目录保存元数据而非模型的完整拷贝。它使用的每个资源类型都是dataplex-types/global下的内置系统类型,唯有kcmd init预置的自定义semantic-action/semantic-constraint类型对除外。push 引用这些类型,从不创建它们。
记录什么取决于 push 的类型:
- 仅目录的 push(
--no-profile),或无绑定的纯逻辑模型,记录整个模型:文档声明的每个实体、度量和关系。 - 同时部署图的 push先把模型裁剪为图所绑定的部分,因此未绑定字段——以及任何依赖它的实体、度量或关系——也一并被排除在目录条目之外。对这类 push 的 pull 返回的是绑定视图而非完整编写模型。
- 逻辑模型仍产生完整条目:每个实体的
source记录为空(resources: []),因为背后没有表;不带连接列的关系以警告跳过。这一行为在源码中有明确注释与实现:deploy_knowledge_catalog.ts 中对source.resources的处理(空列表是诚实的"尚无绑定"),测试 deploy_knowledge_catalog.test.ts 验证了逻辑模型的发布形态。
一次绑定,而非全部绑定。模型可定义多个物理实现——比如一个面向 BigQuery 的分析绑定和一个面向 Spanner 的操作绑定——kcmd push --all-profiles为每个部署一张图。目录这条腿不随之扇出:它只运行一次,针对--profile命名的 profile,否则针对默认绑定。因此裸kcmd push与kcmd push --all-profiles写入相同的条目,区别仅在于部署了多少张图。
连续 push 不会累积。条目 id 只从逻辑名推导——模型、实体、度量——不带 profile 成分,所以推送第二个 profile 会把目录调和到那个绑定,而不是叠加到第一个写的内容上:共享元素把source换成新 profile 的表;在部署图(因此会裁剪)的 push 上,新绑定无法回答的实体或度量被删除而非遗留。目录因此一次只描述一个物理实现:它记录该绑定的部署目标和表,但从不记录 profile 的名字,也从不记录其他 profile 存在。pull返回最后写入的那个。保留 profile 文件——目录不是它们的仓库。
默认不存 SQL 表达式。已发布的系统类型模板尚未携带逐字段semantics块或semantic-metric.expression字段,因此默认 push 省略它们。传入--emit-expressions可在模板获得这些字段后写入规范的 GoogleSQL/ANSI 表达式。该门控在源码中明确实现:knowledge_catalog.ts 第 125 行const emitExpr = opts.emitExpressions ?? false;,并在 schemaAspectData 与 metricAspectData 中据以决定是否发出semantics/expression键;KcGenerateOptions.emitExpressions的声明见 deploy_knowledge_catalog.ts。
目录从不存储:ai_context.synonyms/examples、字段级ai_context(只有模型、实体、度量的instructions在guidelines方面有归宿)、以及原始厂商 SQL(importedExpression——例如度量导入自的 MAQL 或 Snowflake 形式)。这些留在你的编写文档中;厂商 SQL 与表达式在生成图 SQL 时仍会被使用。
动作(Actions)在目录中的往返
每个动作遵循与其他一切相同的"每条目一元素"规则:各自成为模型条目下的一个semantic-action条目,在semantic-action方面携带其 executor、类型化参数、guards与affects。它们通过pull无损往返(名称、描述、executor、类型化参数、guards、affects、instructions)。
- 从字段投影的参数以其编写时的投影形式往返:方面存储
concept与field,连同解析出的标量type;pull 把投影写回时不带类型,因此重新加载时从同一字段解析、得到同一参数。 - 两样东西不随投影往返:其
label与ai_context被丢弃——方面没有地方放它们——所以 pull 恢复的是参数的description而非这两者。且 pull 恢复的措辞以参数自身的面目出现:loader 在模型加载时把继承的description解析进参数,下游无法区分其与作者手写的差别,所以 pull 出的文档把它写在原作者留白的位置。重新加载得到同一参数,但字段与参数如今各持一份副本,改字段不再联动。 - 投影字段不在 pull 文档中(裁剪丢弃了无绑定可达的实体)时,参数以声明参数出现并携带解析出的
type——因为发出一个解析不到任何东西的concept/field对会写出无法加载的文档。 sqlexecutor随其余内容往返其statements:它们是写入本身,不是关于写入的注释,因此丢弃它们的目录会描述一个无人能重新部署的动作。- 条目类型是自定义的,所以
kcmd init创建它;不声明任何动作的模型永远不需要它。
affects作为事实而非原文往返。两种编写形态——裸名称与记录——在模型中是同一形态,所以只携带 concept 的记录(- concept: Account)回来时是裸Account,说的是同一件事。首遍之后逐字节稳定。affects只存储作者所写的内容,这正是它能在部分 pull 下完好存活的原因。一个concept是实体还是边不记录,所以没有任何东西需要重新解析——也就不会有东西变陈旧。这对多对多关系最重要:它根本到不了目录;pull 从 schema-join 条目链接恢复关系,而链接只携带外键边,因此命名 M:N 边的 concept 在一个完全良构的模型上会显得无法解析——它却原样返回。
约束(Constraints)在目录中的往返
每个约束以同样方式发布:各自成为模型条目下的一个semantic-constraint条目,规则与任何instructions放在semantic-constraint方面,description作为条目自身的摘要。名称、描述、on_violation、severity、instructions,以及声明规则的任一体——expression或judgment——通过pull无损往返。声明了两种路由词的约束会原样返回(不填充默认值)。方面还携带一个派生的evaluation字段(deterministic或judged),pull 从规则体重新计算而非读取它,因此它永远不会与旁边的规则不一致;它不会写入编写文档,因为编写文件中的派生值是会变陈旧的值。同时声明两种规则体(或都不声明)的条目以警告跳过,而不是被 pull 成一个自己 push 时会失败的模型。该条目类型也是自定义的,不声明任何约束的模型永远不需要它。
四、To BigQuery:结构与描述元数据的分流
push 同时保留可查询的结构与附着其上的描述性元数据。结构变成节点表、边表与度量;描述性元数据写入图中每个元素的OPTIONS(...)(用--print可见)。
BigQuery 图的OPTIONS给元素一个description字符串与一个synonyms数组。synonyms是唯一拥有专属选项的部分,因此它结构性地、作为自己的数组携带过去。其余部分——description、instructions、examples和字段的label——共享单一的description字符串,因此被合并进它(examples 以Examples: …行呈现)。它们的内容被保留;它们的独立结构没有。
模型自身的语句级元数据无处可去:BigQuery 静默丢弃图语句的OPTIONS,所以模型的description/ai_context不在图中——description与ai_context.instructions改由 Knowledge Catalog 承载。主键之外的唯一键也被丢弃(只发主键)。导入的厂商 SQL 不作为独立形式携带:图在规范expression存在时从中构建,仅当模型从未被转译成规范形式时才逐字回退到导入的厂商 SQL。
extends层次在 BigQuery 端以LABEL子句 + 字段展平呈现,一个实体可extends: [Parent, …],push 把超类型字段展平到每个子类,子类表携带自己的KEY与全部继承属性的列绑定——具体规则与 SQL 示例见 Reference → Class hierarchies 与 Modeling class hierarchies。
五、To Spanner:只保留可查询的结构
Spanner 目标保留可查询的结构,丢弃描述性元数据。节点表、边表与LABEL(含extends层次,字段展平)与 BigQuery 完全一致地部署,但使用裸的表名与图名。两样东西按设计不上船:
- 度量(Metrics)。Spanner 没有
MEASURE,所以每个模型级度量都以警告丢弃。BigQuery 独有的"度量必须解析到单个实体"规则在此不适用。照常编写你的度量——BigQuery 目标仍会发出它们,Knowledge Catalog 仍记录每个semantic-metric条目——它们只是不在 Spanner 图中。 OPTIONS元数据。Spanner 不携带逐元素OPTIONS,所以description、synonyms、instructions、examples与字段label不写入 Spanner DDL。它们在同一次 push 中仍到达 Knowledge Catalog(模型/实体/度量的描述与instructions落在条目与guidelines方面上),因此描述层活在目录中而非 Spanner 图中。这镜像了 BigQuery 丢弃图语句OPTIONS而保留逐元素OPTIONS的方式。
其余一切——键、关系、标签层次——与上文的→ BigQuery列一致。动作与约束不到达任一图;它们发布到 Knowledge Catalog,各为一个semantic-action/semantic-constraint条目,并通过pull无损往返。
Spanner 端还使用异步 DDL:语句经 Spanner AdminupdateDatabaseDdl长时运行操作应用并轮询完成(BigQuery 经jobs.query运行其 DDL)。见 Reference → What gets created in Spanner。
六、What pull recovers:返回的是"规范化视图"
pull返回 push 写入的内容——上文pull恢复列即摘要。当 push 部署了图时,push 写入的已被裁剪为绑定视图,所以 pull 返回该视图。关于它如何回来,有两类:
规范化(Normalized)——内容幸存,形式改变:
- 关系名称回来时被小写化/连字符化(
Places Order→places-order);目录只在链接 id 中存名称。测试验证了这一 id 形态:deploy_knowledge_catalog.test.ts 断言直接外键关系写为一条schema-join链接、链接 id 为sales-orders-to-customer;action_affects.test.ts 与 actions.test.ts 则确认 pull 仅从schema-join条目链接重建关系。见下文"Writer-side follow-up"。 - 字段类型往返,除两处收缩:无类型字段回来为
Opaque,String字段回来为无类型(两者都存dataType STRING,靠字段的metadataType区分——见脚注²)。度量的数据类型仅在具体类型(如Decimal)下往返;无类型、String或Opaque度量回来为无类型,因为度量方面存数据类型但不存标记其为Opaque的元数据类型。 - 字段的维度角色(仅当以
--emit-expressions推送时存在)回来为裸dimension: {}标记,不带其细节(is_time等)。 - 顺序:每个实体内的字段顺序被保留,但实体与度量的顺序不保留——它们按目录自己的顺序回来,而非编写顺序。原 YAML 中的注释不被保留。
因此,push 后接 pull 不会返回你的原始文件。把 pull 出的文档视为目录元数据的忠实副本,而非编写模型的副本,并把编写文档保留为事实来源。
七、Writer-side follow-up:当前 push 写入侧的已知限制
上文有一处缩减是 push 当前写入的限制,而非 pull 能恢复的极限。它在此记录为写入侧后续事项;读者(pull)已返回目录持有的全部内容。
- 关系名称。
schema-join方面类型的metadataTemplate没有关系名称的字段,因此 push 无法存储它,pull 从链接 id 恢复它——而链接 id 被小写化与连字符化(条目链接 id 格式禁止原始大小写/下划线)。逐字返回名称需要在 Knowledge Catalog(服务端)为内置schema-join方面类型添加名称字段,之后客户端写/读是小事;这与门控--emit-expressions的semantics字段属于同一类缺口。
(非规范的部署目标不是pull 缺口:push 在任何一条腿运行前就在验证门拒绝它,因此它永远不会被写入——见 Validation。)
八、实操建议:围绕保真度组织工作流
把上面的矩阵落到日常操作,几条可执行的结论:
- 把编写文档当唯一事实来源。目录是元数据视图、图是可查询视图、pull 是目录视图——三者都非你的 YAML。
pull适合恢复工作区、查看目录实际持有内容、或确认别人部署的模型,但不适合作为编辑主副本。删除模型文档不会自动清目录条目;跨模型删除需要--force-remove(见 README → Updating and removing models)。 - 需要表达式与维度角色进目录时,用
--emit-expressions。默认关闭;开启后规范表达式与semantics角色随条目发布并可被 pull 恢复。kcmd push --print与--validate-only可在写任何东西前预览生成的 DDL 与条目计划。 - 区分后端能力。Spanner 目标丢弃度量与全部
OPTIONS描述层(描述活在目录中);BigQuery 目标把描述合并进OPTIONS(description)但丢弃图语句级元数据与唯一键。为查询性选后端时,先对照本文矩阵确认你依赖的要素在目标后端有归宿。 - 绑定 profile 与目录的关系是"一次一个"。
--all-profiles部署多张图,但目录只记录一个绑定(默认绑定);连续推送不同 profile 会调和(替换source、删除新绑定无法回答的元素)而非累积。保留 profile 文件——目录不是它们的仓库。 - 动作与约束只活在目录。它们不到达任一图;依赖目录的
semantic-action/semantic-constraint条目做治理与发现,别指望图 DDL 中出现它们。kcmd init --semantic-model会预置这两个自定义类型对。
九、相关文档与源码指引
- fidelity.md(本文来源,原文档)
- Deploying a semantic model(部署总览)
- Model specification(格式规范,§2.5 表达式、§6 扩展机制、§7 绑定层)
- Binding profiles(一个逻辑模型、多个物理绑定)
- Reference(CLI 标志、各目的地创建物、验证、权限)
- Modeling write operations(动作与约束的完整建模指南)
- 源码实现:knowledge_catalog.ts(目录条目/方面生成)、deploy_knowledge_catalog.ts(目录推送选项)、deploy_bigquery.ts 与 deploy_spanner.ts(图 DDL 生成)、pull_kc.ts(pull 重建)
- 测试验证:deploy_knowledge_catalog.test.ts、actions.test.ts、action_affects.test.ts,以及 golden 夹具(如 actions_place_order.knowledge_catalog.golden.json)
- 数据目录
- AI Agent
- 人工智能
- 知识管理
- 示例工程
【免费下载链接】knowledge-catalog
Google Cloud Knowledge Catalog Tools and Samples
相关推荐
Apache HBase 的 ACID 语义:它到底保证什么、不保证什么,以及如何按需调节
Apache HBase 的 ACID 语义:它到底保证什么、不保证什么,以及如何按需调节 本文以 Apache HBase 官网的 ACID 语义说明为骨架,
数据库大数据列式数据库分布式数据库后端这个PR做了什么?对用户有什么影响?
这个PR做了什么?对用户有什么影响? 如何测试这个更改? 逐行检查代码,注意: 代码是否覆盖了所有边界情况? 代码是否遵循命名、格式、模块化等约定? 测试步骤:
数据可视化前端Open edX 平台错误监控决策:为什么移除了 EXPECTED_ERRORS 并只保留 IGNORED_ERRORS
Open edX 平台错误监控决策:为什么移除了 EXPECTED_ERRORS 并只保留 IGNORED_ERRORS 本文为 Open edX 平台(ope
后端教育
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考