ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

原理剖析:AnyLanguageModel 的 @Generable 宏如何自动生成 JSON Schema

原理剖析:AnyLanguageModel 的 @Generable 宏如何自动生成 JSON Schema 原理剖析AnyLanguageModel 的 Generable 宏如何自动生成 JSON Schema【免费下载链接】AnyLanguageModelAn API-compatible, drop-in replacement for Apples Foundation Models framework with support for custom language model providers.项目地址: https://gitcode.com/gh_mirrors/an/AnyLanguageModelAnyLanguageModel 是一个与 Apple Foundation Models 框架 API 兼容、可无缝替换的 Swift 开源框架它允许你接入任意自定义语言模型提供商。而让任意模型都能输出你想要的 JSON 格式的关键正是Generable宏——它能在编译期自动分析你的 Swift 类型并自动生成 JSON Schema从而让大模型严格按你的结构输出内容。本文将深入源码一步步拆解这套从 Swift 类型到 JSON Schema的自动生成机制。为什么需要自动生成 JSON Schema大模型本身并不知道你的 App 里定义了什么数据结构。要让模型返回正好符合你类型的结果传统做法有两种手写一段 JSON Schema再让模型照着输出让模型自由发挥回来后用 JSONDecoder 硬解析。两种方式都脆弱手写 Schema 容易和代码不同步硬解析则经常因为格式偏差而失败。AnyLanguageModel 的解法很优雅用 Swift 宏Macro在编译期读取你的类型定义自动生成对应的 JSON Schema做到代码即 SchemaSchema 永不掉队。宏是 Swift 5.9 引入的编译期代码生成特性Generable正是基于它构建的。核心流程从 Swift 类型到 JSON Schema 的三步走 Generable自动生成 JSON Schema 的过程本质上是三个环节的接力环节做什么对应源码① AST 解析用 SwiftSyntax 读取 struct/enum 的属性名、类型、Guide标注Sources/AnyLanguageModelMacros/GenerableMacro.swift② 生成 Schema 代码产出generationSchema静态属性构造一棵 Schema 节点树Sources/AnyLanguageModel/GenerationSchema.swift③ 序列化输出把节点树编码为标准 JSON Schema 字符串交给模型Sources/AnyLanguageModel/GenerationSchema.swift中的schemaPrompt()先看最直观的用法。给结构体加上Generable再用Guide描述每个字段的生成要求Generable struct SearchSuggestions { Guide(description: A list of suggested search terms, .count(4)) var searchTerms: [SearchTerm] Generable struct SearchTerm { var id: GenerationID Guide(description: A 2 or 3 word search term) var searchTerm: String } }就这么几行代码Generable宏会在编译期自动补全成员初始化器、generatedContent属性以及最关键的静态属性generationSchema。第一步宏如何读懂你的 Swift 类型 GenerableMacro同时实现了MemberMacro和ExtensionMacro也就是说它既能往类型里添加成员也能为类型自动声明Generable协议遵守。整个解析逻辑清晰分成三块识别类型通过StructDeclSyntax和EnumDeclSyntax区分结构体与枚举其他类型直接报错GenerableMacroError.notApplicableToType提取属性extractGuidedProperties遍历成员块拿到每个属性的名字name和类型type解析标注extractGuideInfo专门解析Guide(description:..., 约束...)里的描述文本和各类约束参数。关键点在于宏看到的不是运行时的值而是编译期的语法树。它逐字读取你在Guide中写的.count(4)、.range(1...100)这类表达式通过applyConstraints把函数调用名count、minimum、maximum、range、pattern映射成内部的Constraints结构。这也是GuideMacro本身几乎无事可做的原因——它只是个占位符真正的解释工作全部由GenerableMacro完成。第二步GenerationSchema 的树形结构 拿到属性清单后宏会生成类似下面的代码已简化nonisolated public static var generationSchema: GenerationSchema { return GenerationSchema( type: Self.self, description: Generated SearchSuggestions, properties: [ GenerationSchema.Property( name: searchTerms, type: [SearchTerm].self, guides: [.count(4)] ) ] ) }GenerationSchema内部是一棵递归的节点树Node枚举它几乎 1:1 对应 JSON Schema 的语义Node 节点对应 JSON Schema典型来源.objecttype: object properties/required结构体.arraytype: array items/minItems/maxItems数组.stringtype: string pattern/enum字符串、无关联值枚举.numbertype: integer或numberInt/Double/Float/Decimal.booleantype: booleanBool.ref$ref: #/$defs/...嵌套的自定义类型值得注意的是Property构造时有个关键判断buildNode会检查属性类型是否属于Bool/String/Int/Double等原始类型。原始类型直接内联成简单节点复合类型比如嵌套的SearchTerm则通过Value.generationSchema递归获取子 Schema并登记到$defs字典里外部只放一个$ref引用。这样就避免了重复定义也让任意层级的嵌套结构都能被完整描述。最终generationSchema序列化出来的标准 JSON Schema 大致长这样核心结构示意{ $defs : { SearchSuggestions : { type : object, properties : { searchTerms : { type : array, items : { $ref : #/$defs/SearchSuggestions.SearchTerm }, minItems : 4, maxItems : 4 } }, required : [searchTerms], additionalProperties : false } }, $ref : #/$defs/SearchSuggestions }注意required字段宏会检查属性是否可选类型是否带?非可选属性自动进入 required 列表可选属性则不强制这正是类型系统直接映射为 Schema 约束的体现。Guide 约束如何变成 Schema 关键字 Guide里写的每一句约束都会被翻译成 JSON Schema 的标准关键字这是从自然语言到机器约束的关键一步Guide 写法生成的 Schema 关键字说明.count(4)minItemsmaxItems都等于 4数组元素数量必须恰好为 4.minimum(1)minimum: 1数值下限含边界.range(1...100)minimummaximum数值闭区间.pattern(...)pattern: ...正则约束字符串格式字符串枚举类型enum: [caseA, caseB]枚举值白名单这套映射在GenerableMacro的buildGuidesArray中完成它会根据属性类型数组/数值决定将计数类约束转成 min/maxItems还是将范围类约束转成 min/max。而description描述文本则会原样进入 Schema 的description字段——这些自然语言描述正是模型理解该生成什么内容的重要依据。对于枚举宏还会做特殊处理没有关联值的枚举直接生成anyOf: [caseA, caseB]字符串枚举 Schema带关联值的枚举则被建模为带case和value两个字段的对象结构保证复杂枚举也能被结构化表达。第三步Schema 如何指挥模型输出 Schema 生成之后接下来就是驱动模型。AnyLanguageModel 根据模型类型走两条路云端 API 模型OpenAI、Anthropic、Gemini 等把 Schema 序列化成提示词片段。GenerationSchema.schemaPrompt()会生成Respond with valid JSON matching this schema: 完整 JSON Schema 的指令注入到发给模型的 Prompt 中让模型照着 Schema 说话。本地端侧模型CoreML、MLX、Llama采用更硬核的方案——ConstrainedJSONGenerator位于Sources/AnyLanguageModel/Shared/StructuredGeneration.swift。它基于 Token 后端做约束采样每生成一个 token 前都对照 Schema 检查哪些 token 合法比如数字字段只允许数字 token、字符串开头必须是引号把非法 token 直接过滤掉从机制上保证输出必然符合 Schema。这也是OptionalPropertyBudget这类启发式算法的用武之地——当 token 预算不足时会智能地跳过可选属性。总结一条从类型到约束的自动化流水线 回顾整条链路Generable宏的巧妙之处在于把类型定义变成了生成契约你定义 Swift 类型Generable宏在编译期读取语法树Sources/AnyLanguageModelMacros/GenerableMacro.swift属性类型、可选性、Guide标注被翻译成GenerationSchema节点树Sources/AnyLanguageModel/GenerationSchema.swift节点树序列化为标准 JSON Schema或直接驱动约束采样最终让大模型产出类型安全、格式严格的结构化内容返回结果再通过宏生成的init(_ generatedContent:)反序列化回你的 Swift 类型完成闭环。对开发者来说这意味着只要写好类型就同时拥有了一份永不过期的 JSON Schema再也不用手工维护提示词和 Schema 文档。如果你想在自己的 App 里体验类型即约束的魔力clone 仓库https://gitcode.com/gh_mirrors/an/AnyLanguageModel后从给第一个结构体加上Generable开始吧【免费下载链接】AnyLanguageModelAn API-compatible, drop-in replacement for Apples Foundation Models framework with support for custom language model providers.项目地址: https://gitcode.com/gh_mirrors/an/AnyLanguageModel创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表