Schemantic 与 Genkit Dart:在 Dart 中定义强类型数据类并自动绑定运行时 JSON Schema 的实战指南
Schemantic 与 Genkit Dart在 Dart 中定义强类型数据类并自动绑定运行时 JSON Schema 的实战指南【免费下载链接】skillsAgent Skills for Google products and technologies项目地址: https://gitcode.com/GitHub_Trending/skills29/skills导读Schemantic 是genkit-dart框架中用于定义强类型数据类的通用 Dart 库其核心价值在于开发者只需编写带Schema()注解的抽象类构建器即可自动生成对应的具体类与可复用的运行时 JSON Schema从而让类型安全的数据解析、程序化 Schema 校验在 Tools、Flows、Prompts 与 Agents 中开箱即用。读完本文你将掌握 Schemantic 的安装、注解驱动的代码生成流程、基础与进阶用法联合类型、字段注解、递归 Schema并能在 Genkit Dart 的defineTool、ai.generate结构化输出、defineFlow、defineAgent等场景中正确落地这些类型。Schemantic 是 Genkit Dart 中所有数据模型的基础库genkit-dart的 SKILL.md 将其标注为 CRITICAL skill。它虽然是genkit-dart框架的标准组件但同样可以独立使用——本文先从库本身讲透再结合仓库中的实际调用场景展开。Schemantic 是什么强类型数据类与运行时 JSON Schema 的桥梁在编写 AI 应用时工具Tool的入参、模型的结构化输出、Flow 的输入输出本质上都是 JSON 数据。若直接手写MapString, dynamic不仅失去编译期类型保护还无法向模型描述应该返回什么样的 JSON。Schemantic 解决了这个问题一份声明两处受益用Schema()注解抽象类既得到带类型的具体 Dart 类可用于fromJson/toJson又得到SchemanticTypeT形式的 JSON Schema 定义可用于向模型描述数据结构、在运行时校验任意 JSON。Genkit Dart 的标准依赖如 genkit.md 所述Genkit uses standard data models for representing prompts (messages parts) and responses. These classes are implemented using schemantic library.——也就是说 Genkit Dart 的Message、Part等内置数据模型本身也是用 Schemantic 实现的。三条核心约定用Schema()注解你的抽象类抽象 Schema 类名使用$前缀例如abstract class $User始终运行dart run build_runner build生成.g.dartSchema 文件。当你在代码中看到Schema()、SchematicType或以$开头的类名时就应当想到 Schemantic。安装别忘了schemantic_builderSchemantic 0.2.x 起代码生成器被拆分到了独立的schemantic_builder包中因此除了主依赖还必须把它作为 dev 依赖加入dart pub add schemantic dart pub add dev:schemantic_builder dart pub add dev:build_runner常见陷阱Gotcha如果缺少schemantic_builderdart run build_runner build会成功结束但只报告wrote 0 outputs且不会生成任何.g.dart文件也不会给出任何错误提示。如果看到输出为零请先确认schemantic_builder是否已加入dev_dependencies。这是最容易踩的坑代码生成静默失败时后续编译会报找不到 part 文件排查方向却往往被误导。基本用法从抽象类到生成类1. 定义 Schemaimport package:schemantic/schemantic.dart; part my_file.g.dart; // 必须与文件名匹配 Schema() abstract class $MyObj { String get name; $MySubObj get subObj; } Schema() abstract class $MySubObj { String get foo; }要点part指令的路径必须与源文件名一致例如源文件是my_file.dart则写part my_file.g.dart;build_runner才会生成对应的my_file.g.dart。2. 使用生成的类构建器会创建去掉$前缀的具体类MyObj并提供MyObj.fromJson工厂构造与普通构造函数// 创建实例 final obj MyObj(name: test, subObj: MySubObj(foo: bar)); // 序列化为 JSON print(obj.toJson()); // 从 JSON 解析 final parsed MyObj.fromJson({name: test, subObj: {foo: bar}});3. 运行时访问 Schema生成的类带有静态$schema字段类型为SchematicTypeT可以把它传给函数也可以提取原始 JSON Schema// 访问 JSON Schema final schema MyObj.$schema.jsonSchema; print(schema.toJson()); // 在运行时校验任意 JSON final validationErrors await schema.validate({invalid: data});$schema字段正是 Genkit Dart 各 API 的接入点inputSchema/outputSchema参数接收的即是这类SchematicTypeT值。与 Genkit Dart 的三种典型结合方式在 genkit.md 中可以看到 Schemantic 在核心流程中的标准用法。场景一定义工具defineTool工具的输入必须用 Schemantic 描述模型才能正确生成工具调用参数import package:schemantic/schemantic.dart; Schema() abstract class $WeatherInput { String get location; } final weatherTool ai.defineTool( name: getWeather, description: Gets the current weather for a location, inputSchema: WeatherInput.$schema, fn: (input, _) async { // 在这里调用你的天气 API return Weather in ${input.location}: 72°F and sunny; }, ); final response await ai.generate( model: googleAI.gemini(gemini-flash-latest), prompt: What\s the weather like in San Francisco?, toolNames: [getWeather], // 使用工具 );场景二结构化输出outputSchema强制模型返回符合 Schema 的 JSON并把结果直接还原成类型化对象Schema() abstract class $Person { String get name; int get age; } // ... 在 main 内 ... final response await ai.generate( model: googleAI.gemini(gemini-flash-latest), prompt: Generate a person named John Doe, age 30, outputSchema: Person.$schema, // 强制模型按此 Schema 返回 ); final person response.output; // 类型化的 Person 对象 print(Name: ${person.name}, Age: ${person.age});场景三Flow 与数据模型Flow 的输入输出也可以使用 Schemantic 类型化 Schema而 Genkit Dart 内置的Message/Part数据模型同样是 Schemantic 实现的你可以组合它们定义自己的模型import package:genkit/genkit.dart; import package:schemantic/schemantic.dart; Schema() abstract class $MyDataModel { // 注意这里用的是 Genkit 的 Message schema而不是 schemantic 自带的 Message List$Message get messages; List$Part get parts; }从源码结构看$前缀抽象类 生成的.$schema静态字段构成了 Genkit Dart 全框架统一的 Schema 接入协议——Tools、Flows、Promptsai.definePrompt的inputSchema、AgentsstateSchema都遵循同一约定这也是为什么理解 Schemantic 是使用 Genkit Dart 的前置条件。原始类型 Schema不需要完整数据类时的动态方案当只需要一个字段级别的 Schema、无需完整数据类时Schemantic 提供了按需创建 Schema 的函数final ageSchema SchemanticType.integer(description: Age in years, minimum: 0); final nameSchema SchemanticType.string(minLength: 2); final nothingSchema SchemanticType.voidSchema(); final anySchema SchemanticType.dynamicSchema(); final userSchema SchemanticType.map(.string(), .integer()); // MapString, int final tagsSchema SchemanticType.list(.string()); // ListString各构造器要点构造器说明常用参数SchematicType.integer(...)整数类型description、minimum最小值SchematicType.string(...)字符串类型minLength最小长度等SchematicType.voidSchema()无内容void类型—SchematicType.dynamicSchema()任意动态类型—SchematicType.map(keySchema, valueSchema)Map 类型可指定键值类型键 Schema、值 SchemaSchematicType.list(itemSchema)List 类型可指定元素类型元素 Schema这种形式在 Genkit Dart 中非常常见——例如 Flow 定义里直接写inputSchema: .string(), outputSchema: .string()就是省略类型的快捷写法见 genkit.md 中的defineFlow与defineRemoteAction示例。联合类型AnyOf一个字段接受多种类型当字段需要接受多种类型时使用AnyOfSchema() abstract class $Poly { AnyOf([int, String, $MyObj]) Object? get id; }Schemantic 会为联合类型生成一个专用的辅助类例如PolyId用类型化工厂处理不同的值final poly1 Poly(id: PolyId.int(123)); final poly2 Poly(id: PolyId.string(abc));这样既保持了运行时 JSON 的灵活性id可以是数字、字符串或对象又通过生成的PolyId辅助类让每种取值路径在编译期可见、可维护。字段注解更精细的校验边界IntegerField、StringField等专用注解可以为字段设置更细的校验约束Schema() abstract class $User { IntegerField( name: years_old, // 修改 JSON 键名 description: Age of the user, minimum: 0, defaultValue: 18, ) int? get age; StringField( minLength: 2, enumValues: [user, admin], ) String get role; }参数说明name自定义 JSON 中的键名默认与 getter 名一致用于与外部系统字段命名对齐description字段描述会写入 JSON Schema模型生成结构化输出时会参考minimum数值下界超出则校验失败defaultValue字段缺省值解析时缺失则回落到该值minLength字符串最小长度enumValues枚举允许值列表运行时校验会检查取值是否在列表中。递归 SchemauseRefs树形结构的标准写法对于树等递归结构必须在生成的jsonSchema属性上使用useRefs: true。定义方式与普通 Schema 无异Schema() abstract class $Node { String get id; List$Node? get children; }注意Node.$schema.jsonSchema(useRefs: true)生成的才是带 JSON Schema$ref引用的 Schema。不使用useRefs时递归结构可能会被展开为无限嵌套的定义启用后则以$ref引用自身节点类型既能正确表达递归又避免 Schema 无限膨胀。实战警示非空 getter 在部分数据上会抛异常Schemantic 为必填字段生成的 getter 是非空强制转换例如_json[estimatedCostUsd] as num。如果一个 JSON 对象缺少该字段访问时会直接抛出异常Null is not a subtype of num而不是返回默认值。这在实际项目中非常常见地咬到计算字段/可选字段当重新加载一个部分填充的状态 Blob 时例如从持久化存储恢复会话状态某些从未写入过的字段就会触发此类异常。解决方案把计算或可选的字段声明为可空num?或者为它们提供defaultValue这样部分数据仍可被安全读取。该模式与 Agent 会话状态尤其相关——在 agents.md 中defineAgent的stateSchema正是SchemanticTypeState类型自定义会话状态经序列化/反序列化往返后字段可空性设计直接决定状态恢复的健壮性。最佳实践小结遇到$前缀类、Schema()、SchemanticType就用 Schemantic它们是 Genkit Dart 全框架的 Schema 约定覆盖 defineTool / outputSchema / defineFlow、.prompt 文件与 defineSchema、Agent stateSchema 等所有数据边界。构建失败先查 dev_dependencieswrote 0 outputs且无报错时优先确认schemantic_builder已安装。字段可空性要面向序列化往返设计需要持久化、恢复、部分更新的数据结构为可选/计算字段使用num?或defaultValue避免访问时抛Null is not a subtype of ...。递归结构显式开启useRefs: true让生成的 Schema 以$ref表达自引用。发布前运行dart analyze确认代码可干净编译genkit-dartSKILL.md 的 Best Practices 亦如此要求。【免费下载链接】skillsAgent Skills for Google products and technologies项目地址: https://gitcode.com/GitHub_Trending/skills29/skills创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联
返回资讯列表 →