news 2026/8/18 18:14:53

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
原理剖析:AnyLanguageModel 的 @Generable 宏如何自动生成 JSON Schema

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

【免费下载链接】AnyLanguageModelAn API-compatible, drop-in replacement for Apple's Foundation Models framework with support for custom language model providers.项目地址: https://gitcode.com/gh_mirrors/an/AnyLanguageModel

AnyLanguageModel 是一个与 Apple Foundation Models 框架 API 兼容、可无缝替换的 Swift 开源框架,它允许你接入任意自定义语言模型提供商。而让"任意模型都能输出你想要的 JSON 格式"的关键,正是@Generable宏——它能在编译期自动分析你的 Swift 类型,并自动生成 JSON Schema,从而让大模型严格按你的结构输出内容。本文将深入源码,一步步拆解这套"从 Swift 类型到 JSON Schema"的自动生成机制。

为什么需要自动生成 JSON Schema?🎯

大模型本身并不知道你的 App 里定义了什么数据结构。要让模型返回"正好符合你类型"的结果,传统做法有两种:

  1. 手写一段 JSON Schema,再让模型照着输出;
  2. 让模型自由发挥,回来后用 JSONDecoder 硬解析。

两种方式都脆弱:手写 Schema 容易和代码不同步,硬解析则经常因为格式偏差而失败。

AnyLanguageModel 的解法很优雅:用 Swift 宏(Macro)在编译期"读取"你的类型定义,自动生成对应的 JSON Schema,做到"代码即 Schema,Schema 永不掉队"。宏是 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同时实现了MemberMacroExtensionMacro,也就是说它既能往类型里添加成员,也能为类型自动声明Generable协议遵守。

整个解析逻辑清晰分成三块:

  • 识别类型:通过StructDeclSyntaxEnumDeclSyntax区分结构体与枚举,其他类型直接报错(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典型来源
.object"type": "object"+ properties/required结构体
.array"type": "array"+ items/minItems/maxItems数组
.string"type": "string"+ pattern/enum字符串、无关联值枚举
.number"type": "integer""number"Int/Double/Float/Decimal
.boolean"type": "boolean"Bool
.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)minItems+maxItems都等于 4数组元素数量必须恰好为 4
.minimum(1)minimum: 1数值下限(含边界)
.range(1...100)minimum+maximum数值闭区间
.pattern(...)pattern: "..."正则约束字符串格式
字符串枚举类型enum: ["caseA", "caseB"]枚举值白名单

这套映射在GenerableMacrobuildGuidesArray中完成:它会根据属性类型(数组/数值)决定将计数类约束转成 min/maxItems,还是将范围类约束转成 min/max。而description描述文本则会原样进入 Schema 的description字段——这些自然语言描述正是模型理解"该生成什么内容"的重要依据。

对于枚举,宏还会做特殊处理:没有关联值的枚举直接生成anyOf: [caseA, caseB]字符串枚举 Schema;带关联值的枚举则被建模为带casevalue两个字段的对象结构,保证复杂枚举也能被结构化表达。

第三步: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宏的巧妙之处在于把"类型定义"变成了"生成契约":

  1. 你定义 Swift 类型,@Generable宏在编译期读取语法树(Sources/AnyLanguageModelMacros/GenerableMacro.swift);
  2. 属性类型、可选性、@Guide标注被翻译成GenerationSchema节点树(Sources/AnyLanguageModel/GenerationSchema.swift);
  3. 节点树序列化为标准 JSON Schema,或直接驱动约束采样,最终让大模型产出类型安全、格式严格的结构化内容;
  4. 返回结果再通过宏生成的init(_ generatedContent:)反序列化回你的 Swift 类型,完成闭环。

对开发者来说,这意味着:只要写好类型,就同时拥有了一份永不过期的 JSON Schema,再也不用手工维护提示词和 Schema 文档。如果你想在自己的 App 里体验"类型即约束"的魔力,clone 仓库(https://gitcode.com/gh_mirrors/an/AnyLanguageModel)后,从给第一个结构体加上@Generable开始吧!🚀

【免费下载链接】AnyLanguageModelAn API-compatible, drop-in replacement for Apple's Foundation Models framework with support for custom language model providers.项目地址: https://gitcode.com/gh_mirrors/an/AnyLanguageModel

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/18 18:11:30

拼团小程序制作软件有哪些?从99元到3万元,预算决定你能用哪类

拼团小程序制作软件有哪些?从99元到3万元,预算决定你能用哪类!据行业研究数据,2026年拼团电商已占社交电商整体规模的约29%,对应交易规模约5278亿元;在下沉市场,拼团、秒杀等社交裂变模式的转化…

作者头像 李华
网站建设 2026/8/18 18:07:11

claude code多次配置path,仍显示异常,如何解决?

🏆本文收录于 《全栈 Bug 调优(实战版)》 专栏。专栏聚焦真实项目中的各类疑难 Bug,从成因剖析 → 排查路径 → 解决方案 → 预防优化全链路拆解,形成一套可复用、可沉淀的实战知识体系。无论你是初入职场的开发者&…

作者头像 李华