類型數(shù)據(jù)類并自動(dòng)綁定運(yùn)行時(shí) JSON Schema 的實(shí)戰(zhàn)指南)
Schemantic 與 Genkit Dart在 Dart 中定義強(qiáng)類型數(shù)據(jù)類并自動(dòng)綁定運(yùn)行時(shí) JSON Schema 的實(shí)戰(zhàn)指南【免費(fèi)下載鏈接】skillsAgent Skills for Google products and technologies項(xiàng)目地址: https://gitcode.com/GitHub_Trending/skills29/skills導(dǎo)讀Schemantic 是genkit-dart框架中用于定義強(qiáng)類型數(shù)據(jù)類的通用 Dart 庫其核心價(jià)值在于開發(fā)者只需編寫帶Schema()注解的抽象類構(gòu)建器即可自動(dòng)生成對(duì)應(yīng)的具體類與可復(fù)用的運(yùn)行時(shí) JSON Schema從而讓類型安全的數(shù)據(jù)解析、程序化 Schema 校驗(yàn)在 Tools、Flows、Prompts 與 Agents 中開箱即用。讀完本文你將掌握 Schemantic 的安裝、注解驅(qū)動(dòng)的代碼生成流程、基礎(chǔ)與進(jìn)階用法聯(lián)合類型、字段注解、遞歸 Schema并能在 Genkit Dart 的defineTool、ai.generate結(jié)構(gòu)化輸出、defineFlow、defineAgent等場景中正確落地這些類型。Schemantic 是 Genkit Dart 中所有數(shù)據(jù)模型的基礎(chǔ)庫genkit-dart的 SKILL.md 將其標(biāo)注為 CRITICAL skill。它雖然是genkit-dart框架的標(biāo)準(zhǔn)組件但同樣可以獨(dú)立使用——本文先從庫本身講透再結(jié)合倉庫中的實(shí)際調(diào)用場景展開。Schemantic 是什么強(qiáng)類型數(shù)據(jù)類與運(yùn)行時(shí) JSON Schema 的橋梁在編寫 AI 應(yīng)用時(shí)工具Tool的入?yún)ⅰ⒛P偷慕Y(jié)構(gòu)化輸出、Flow 的輸入輸出本質(zhì)上都是 JSON 數(shù)據(jù)。若直接手寫MapString, dynamic不僅失去編譯期類型保護(hù)還無法向模型描述應(yīng)該返回什么樣的 JSON。Schemantic 解決了這個(gè)問題一份聲明兩處受益用Schema()注解抽象類既得到帶類型的具體 Dart 類可用于fromJson/toJson又得到SchemanticTypeT形式的 JSON Schema 定義可用于向模型描述數(shù)據(jù)結(jié)構(gòu)、在運(yùn)行時(shí)校驗(yàn)任意 JSON。Genkit Dart 的標(biāo)準(zhǔn)依賴如 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等內(nèi)置數(shù)據(jù)模型本身也是用 Schemantic 實(shí)現(xiàn)的。三條核心約定用Schema()注解你的抽象類抽象 Schema 類名使用$前綴例如abstract class $User始終運(yùn)行dart run build_runner build生成.g.dartSchema 文件。當(dāng)你在代碼中看到Schema()、SchematicType或以$開頭的類名時(shí)就應(yīng)當(dāng)想到 Schemantic。安裝別忘了schemantic_builderSchemantic 0.2.x 起代碼生成器被拆分到了獨(dú)立的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會(huì)成功結(jié)束但只報(bào)告wrote 0 outputs且不會(huì)生成任何.g.dart文件也不會(huì)給出任何錯(cuò)誤提示。如果看到輸出為零請(qǐng)先確認(rèn)schemantic_builder是否已加入dev_dependencies。這是最容易踩的坑代碼生成靜默失敗時(shí)后續(xù)編譯會(huì)報(bào)找不到 part 文件排查方向卻往往被誤導(dǎo)。基本用法從抽象類到生成類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; }要點(diǎn)part指令的路徑必須與源文件名一致例如源文件是my_file.dart則寫part my_file.g.dart;build_runner才會(huì)生成對(duì)應(yīng)的my_file.g.dart。2. 使用生成的類構(gòu)建器會(huì)創(chuàng)建去掉$前綴的具體類MyObj并提供MyObj.fromJson工廠構(gòu)造與普通構(gòu)造函數(shù)// 創(chuàng)建實(shí)例 final obj MyObj(name: test, subObj: MySubObj(foo: bar)); // 序列化為 JSON print(obj.toJson()); // 從 JSON 解析 final parsed MyObj.fromJson({name: test, subObj: {foo: bar}});3. 運(yùn)行時(shí)訪問 Schema生成的類帶有靜態(tài)$schema字段類型為SchematicTypeT可以把它傳給函數(shù)也可以提取原始 JSON Schema// 訪問 JSON Schema final schema MyObj.$schema.jsonSchema; print(schema.toJson()); // 在運(yùn)行時(shí)校驗(yàn)任意 JSON final validationErrors await schema.validate({invalid: data});$schema字段正是 Genkit Dart 各 API 的接入點(diǎn)inputSchema/outputSchema參數(shù)接收的即是這類SchematicTypeT值。與 Genkit Dart 的三種典型結(jié)合方式在 genkit.md 中可以看到 Schemantic 在核心流程中的標(biāo)準(zhǔn)用法。場景一定義工具defineTool工具的輸入必須用 Schemantic 描述模型才能正確生成工具調(diào)用參數(shù)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 { // 在這里調(diào)用你的天氣 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], // 使用工具 );場景二結(jié)構(gòu)化輸出outputSchema強(qiáng)制模型返回符合 Schema 的 JSON并把結(jié)果直接還原成類型化對(duì)象Schema() abstract class $Person { String get name; int get age; } // ... 在 main 內(nèi) ... final response await ai.generate( model: googleAI.gemini(gemini-flash-latest), prompt: Generate a person named John Doe, age 30, outputSchema: Person.$schema, // 強(qiáng)制模型按此 Schema 返回 ); final person response.output; // 類型化的 Person 對(duì)象 print(Name: ${person.name}, Age: ${person.age});場景三Flow 與數(shù)據(jù)模型Flow 的輸入輸出也可以使用 Schemantic 類型化 Schema而 Genkit Dart 內(nèi)置的Message/Part數(shù)據(jù)模型同樣是 Schemantic 實(shí)現(xiàn)的你可以組合它們定義自己的模型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; }從源碼結(jié)構(gòu)看$前綴抽象類 生成的.$schema靜態(tài)字段構(gòu)成了 Genkit Dart 全框架統(tǒng)一的 Schema 接入?yún)f(xié)議——Tools、Flows、Promptsai.definePrompt的inputSchema、AgentsstateSchema都遵循同一約定這也是為什么理解 Schemantic 是使用 Genkit Dart 的前置條件。原始類型 Schema不需要完整數(shù)據(jù)類時(shí)的動(dòng)態(tài)方案當(dāng)只需要一個(gè)字段級(jí)別的 Schema、無需完整數(shù)據(jù)類時(shí)Schemantic 提供了按需創(chuàng)建 Schema 的函數(shù)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各構(gòu)造器要點(diǎn)構(gòu)造器說明常用參數(shù)SchematicType.integer(...)整數(shù)類型description、minimum最小值SchematicType.string(...)字符串類型minLength最小長度等SchematicType.voidSchema()無內(nèi)容void類型—SchematicType.dynamicSchema()任意動(dòng)態(tài)類型—SchematicType.map(keySchema, valueSchema)Map 類型可指定鍵值類型鍵 Schema、值 SchemaSchematicType.list(itemSchema)List 類型可指定元素類型元素 Schema這種形式在 Genkit Dart 中非常常見——例如 Flow 定義里直接寫inputSchema: .string(), outputSchema: .string()就是省略類型的快捷寫法見 genkit.md 中的defineFlow與defineRemoteAction示例。聯(lián)合類型AnyOf一個(gè)字段接受多種類型當(dāng)字段需要接受多種類型時(shí)使用AnyOfSchema() abstract class $Poly { AnyOf([int, String, $MyObj]) Object? get id; }Schemantic 會(huì)為聯(lián)合類型生成一個(gè)專用的輔助類例如PolyId用類型化工廠處理不同的值final poly1 Poly(id: PolyId.int(123)); final poly2 Poly(id: PolyId.string(abc));這樣既保持了運(yùn)行時(shí) JSON 的靈活性id可以是數(shù)字、字符串或?qū)ο笥滞ㄟ^生成的PolyId輔助類讓每種取值路徑在編譯期可見、可維護(hù)。字段注解更精細(xì)的校驗(yàn)邊界IntegerField、StringField等專用注解可以為字段設(shè)置更細(xì)的校驗(yàn)約束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; }參數(shù)說明name自定義 JSON 中的鍵名默認(rèn)與 getter 名一致用于與外部系統(tǒng)字段命名對(duì)齊description字段描述會(huì)寫入 JSON Schema模型生成結(jié)構(gòu)化輸出時(shí)會(huì)參考minimum數(shù)值下界超出則校驗(yàn)失敗defaultValue字段缺省值解析時(shí)缺失則回落到該值minLength字符串最小長度enumValues枚舉允許值列表運(yùn)行時(shí)校驗(yàn)會(huì)檢查取值是否在列表中。遞歸 SchemauseRefs樹形結(jié)構(gòu)的標(biāo)準(zhǔn)寫法對(duì)于樹等遞歸結(jié)構(gòu)必須在生成的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時(shí)遞歸結(jié)構(gòu)可能會(huì)被展開為無限嵌套的定義啟用后則以$ref引用自身節(jié)點(diǎn)類型既能正確表達(dá)遞歸又避免 Schema 無限膨脹。實(shí)戰(zhàn)警示非空 getter 在部分?jǐn)?shù)據(jù)上會(huì)拋異常Schemantic 為必填字段生成的 getter 是非空強(qiáng)制轉(zhuǎn)換例如_json[estimatedCostUsd] as num。如果一個(gè) JSON 對(duì)象缺少該字段訪問時(shí)會(huì)直接拋出異常Null is not a subtype of num而不是返回默認(rèn)值。這在實(shí)際項(xiàng)目中非常常見地咬到計(jì)算字段/可選字段當(dāng)重新加載一個(gè)部分填充的狀態(tài) Blob 時(shí)例如從持久化存儲(chǔ)恢復(fù)會(huì)話狀態(tài)某些從未寫入過的字段就會(huì)觸發(fā)此類異常。解決方案把計(jì)算或可選的字段聲明為可空num?或者為它們提供defaultValue這樣部分?jǐn)?shù)據(jù)仍可被安全讀取。該模式與 Agent 會(huì)話狀態(tài)尤其相關(guān)——在 agents.md 中defineAgent的stateSchema正是SchemanticTypeState類型自定義會(huì)話狀態(tài)經(jīng)序列化/反序列化往返后字段可空性設(shè)計(jì)直接決定狀態(tài)恢復(fù)的健壯性。最佳實(shí)踐小結(jié)遇到$前綴類、Schema()、SchemanticType就用 Schemantic它們是 Genkit Dart 全框架的 Schema 約定覆蓋 defineTool / outputSchema / defineFlow、.prompt 文件與 defineSchema、Agent stateSchema 等所有數(shù)據(jù)邊界。構(gòu)建失敗先查 dev_dependencieswrote 0 outputs且無報(bào)錯(cuò)時(shí)優(yōu)先確認(rèn)schemantic_builder已安裝。字段可空性要面向序列化往返設(shè)計(jì)需要持久化、恢復(fù)、部分更新的數(shù)據(jù)結(jié)構(gòu)為可選/計(jì)算字段使用num?或defaultValue避免訪問時(shí)拋Null is not a subtype of ...。遞歸結(jié)構(gòu)顯式開啟useRefs: true讓生成的 Schema 以$ref表達(dá)自引用。發(fā)布前運(yùn)行dart analyze確認(rèn)代碼可干凈編譯genkit-dartSKILL.md 的 Best Practices 亦如此要求。【免費(fèi)下載鏈接】skillsAgent Skills for Google products and technologies項(xiàng)目地址: https://gitcode.com/GitHub_Trending/skills29/skills創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考