View in English

  • 打开菜单 关闭菜单
  • Apple Developer
搜索
关闭搜索
  • Apple Developer
  • 新闻
  • 探索
  • 设计
  • 开发
  • 分发
  • 支持
  • 账户
在“”范围内搜索。

快捷链接

5 快捷链接

视频

打开菜单 关闭菜单
  • 专题
  • 相关主题
  • 所有视频
  • 关于

返回 WWDC25

  • 简介
  • 概要
  • 转写文稿
  • 代码
  • 深入了解 Foundation Models 框架

    使用 Foundation Models 框架提升性能。深入了解引导式生成的工作方式,并使用指南、正则表达式和生成方案来获取自定的结构化响应。我们将向你展示如何使用工具调用让模型自动访问外部信息并执行操作,从而实现个性化体验。 为了充分从这个视频中获益,建议你先观看“了解 Foundation Models 框架”。

    章节

    • 0:00 - 简介
    • 0:49 - 会话
    • 7:57 - Generable
    • 14:29 - 动态模式
    • 18:10 - 工具调用

    资源

    • Generate dynamic game content with guided generation and tools
    • Human Interface Guidelines: Generative AI
      • 高清视频
      • 标清视频

    相关视频

    WWDC25

    • 了解 Foundation Models 框架
    • 探索 Apple 平台上的机器学习和 AI 框架
    • 跟着视频学编程:使用 Foundation Models 框架将设备端 AI 引入你的 App
  • 搜索此视频…

    嗨 我叫 Louis 今天 我们来了解如何充分利用 Foundation Models 框架

    大家可能都知道 Foundation Models 框架 能让你直接访问设备上的 大语言模型 以及便捷的 Swift API 它适用于 macOS、iPadOS、 iOS 和 visionOS 由于它在设备端运行 你只需简单导入就可以 在自己的项目中使用 在本视频中 我们将了解会话如何 与 Foundation 模型配合使用 如何使用 Generable 获得结构化输出 如何获取在运行时定义的 动态架构的结构化输出 以及使用工具调用 让模型调用自定义函数 让我们从简单的开始 通过会话生成文本

    现在我一直在制作这款 咖啡店像素艺术游戏 我认为会非常有趣的是 通过 Foundation 模型来生成 游戏对话和其他内容 让游戏感觉更生动!

    我们可以提示模型回答玩家的问题 所以我们的咖啡师会给出独特的对话 为此 我们将通过自定义指令创建 LanguageModelSession 这样就能告诉模型 它在这个会话中的用途 对于提示 我们将采用用户的输入 这就是非常有趣的新游戏元素 所需的一切 让我们来问咖啡师 “你在这里工作多久了?” 让它回答我们的问题

    这完全是在设备端生成的 真是很神奇 它实际上是如何运作的呢? 我们来更好地了解一下 Foundation 模型的文本生成方式 以及注意事项 在会话中调用 respond(to:) 时 首先需要会话的指令和提示 在本例中就是用户的输入 它会将这个文本转换为令牌 令牌就是小字符串 有时是个单词 但通常只是几个字符 大语言模型采用一系列令牌 作为输入 然后生成一个新的令牌序列作为输出 你不必操心 Foundation 模型采用的具体令牌 API 能很好地为你将这个抽离 但重要的是要了解 令牌不是没有代价的 你的指令和提示中的每个令牌 都会额外增加延迟 在模型开始生成响应令牌之前 首先需要处理所有输入令牌 而生成令牌也会产生计算成本 这就是输出越长 生成时间也越长的原因

    LanguageModelSession 是有状态的 每次调用 respond(to:) 都会记录在脚本中

    脚本包含给定会话的 所有提示和回复

    这对于调试很有用 甚至可以显示在你的 UI 中

    但是会话的增长规模是有限制的 如果你提出很多请求 或者给出一个很大的提示 或是得到较大的输出 就可能达到上下文限制

    如果会话超出可用的上下文大小 就会抛出错误 你要准备好抓住这种错误 回到游戏中 当我们与某个角色交谈 并遇到错误时 对话就这样结束了 真不凑巧 我才刚刚认识这个角色! 所幸的是有办法 能从这个错误中恢复

    你可以捕捉 exceededContextWindowSize 错误

    完成后 你就可以开始全新的会话 没有任何历史记录 但在我的游戏中 这意味着 角色突然忘记了整个对话

    你也可以从当前会话中选择部分脚本 并带到新会话中

    你可以从某个会话的脚本中获取条目 将它压缩为新的条目数组

    因此对于游戏对话 我们可以使用会话脚本的 第一个条目 也就是指令 以及最后一个条目 也就是最后一个成功的响应 将这些传到新会话中后 我们的角色就很适合再聊一会儿 但请记住 会话脚本包括 作为第一个条目的初始指令 在为游戏角色转移脚本时 我们当然希望包括这些指令

    只包括脚本中的几个相关片段 也可能是个简单而有效的解决方案 但有时事情并没有那么简单 让我们设想一个包含更多条目的脚本 你肯定总是希望以转移指令来开场 但脚本中的很多条目可能具有相关性 因此对于这个用例 你可以对脚本进行归纳

    为此 你可以使用一些外部库 甚至通过 Foundation 模型本身归纳 部分脚本

    以上就是你对会话脚本 可以进行的操作 现在 让我们简要了解一下 响应实际上是如何生成的 在我们的游戏中 当你走到咖啡师面前时 玩家可以提出任何问题 但是 如果你开始两个新游戏 并在每个游戏中问完全相同的问题 你可能会得到不同的输出 这是怎么做到的呢? 这就是抽样的作用

    模型生成输出时 一次生成一个令牌 为此 它会针对既定令牌的概率 创建一个分发版本 Foundation 模型默认在 某个概率范围内选择令牌 有时它可能会以“啊”开头 有时它也可能会选择“嗯” 作为第一个令牌 每个生成的令牌都会这样 选择令牌就是我们所说的抽样 默认行为是随机抽样 获得不同的输出非常适合游戏等用例 但有时你可能想要确定性输出 比如当你编写应该可重复的演示时 GenerationOptions API 可让你控制采样方法 你可以将它设置为贪婪 来获得确定性输出 这样设置后 对于相同的提示 你将得到相同的输出 假设你的会话也处于相同状态 不过请注意 这只适用于 给定版本的设备端模型 当模型作为系统更新的一部分 更新时 提示可以确定地给出不同的输出 即使在使用贪婪采样时也是如此 你还可以调整温度来进行随机抽样 比如将温度设置为 0.5 就可以获得仅略有不同的输出 或者设置较高值 同一提示就可以获得截然不同的输出 另外请记住 如果提示使用的 是用户输入 语言可能不受支持

    有个专门的 unsupportedLanguageOrLocale 错误 会在这种情况下出现 这是在 UI 中显示 自定义消息的好方法 还有一个 API 用于检查模型是否 支持某种语言 比如检查用户的当前语言是否受支持 如果不受支持 就会显示免责声明 以上就是对会话的概述 你可以给它提示 将历史记录存储在脚本中 你还可以选择设置抽样参数 来控制会话输出的随机性 但我们可以做得更精彩! 当玩家四处走动时 我们可以生成 NPC 也就是非玩家角色 同样是使用 Foundation 模型 做到这一点 然而这一次 我们想要更复杂的输出 我们想要的不仅仅是纯文本 而是 NPC 的名字和咖啡订单 Generable 可以在这方面帮助我们 要从大语言模型获取结构化输出 可能并不容易 你可以通过预期的特定字段来提示它 并使用一些解析代码来提取它 但这很难维护 而且非常脆弱 它可能并不总是给出有效的密钥 这样整个方法就失败了 所幸的是 Foundation 模型有个 好用得多的 API 称为 Generable 你可以在结构体上应用 @Generable 宏 那么 什么是 Generable 是个单词吗? 是的 是个单词

    Generable 是让模型生成 结构化数据的一种简单方法 使用 Swift 类型 宏会在编译时生成架构 模型可以用它来生成预期的结构 宏还会生成一个构造器 在向会话发出请求时 系统会自动为你调用这个构造器

    然后我们可以生成结构体实例 和之前一样 我们将在会话中 调用响应方法 但这次是传递生成参数 告诉模型要生成哪种类型 Foundation 模型甚至会在提示中 自动包含 有关 Generable 类型的详细信息 采用模型训练所依据的特定格式 你不必告诉它 Generable 类型有哪些字段 在游戏中 我们现在将与 NPC 有不少精彩偶遇!

    Generable 实际上比看起来更强大 它在低级别使用约束解码 这是让模型遵循特定架构生成文本 的一种技术 请记住 这是宏生成的架构 正如我们之前看到的 LLM 会生成令牌 令牌稍后转换为文本 而借助 Generable 这个文本甚至会以类型安全的方式 自动为你解析 令牌在循环中生成 通常称为解码循环 如果没有约束解码 模型可能会产生一些无效的字段名称 比如“firstName”而不是名字 这样就无法解析为 NPC 类型

    但通过约束解码 可防止模型出现这样的结构性错误 对于生成的每个令牌 模型词汇表中 所有的令牌都会有自己的分发版本 约束解码的工作原理是屏蔽无效令牌 这样模型就不会随便选择任意令牌 而只能根据架构选择有效令牌

    这样就不用操心 手动解析模型输出 你就可以把时间花在 真正重要的事情上 比如与咖啡店里的虚拟客人交谈! Generable 确实是从设备端 LLM 获取输出的最佳方式 它的功能还远不止于此 你不仅可以将它用于结构体 还可以用于枚举! 因此 让我们用它来让偶遇 更加充满活力! 在这里 我添加了一个 Encounter 枚举 其中包含两种情境 枚举在情境中甚至可以包含关联值 因此 让我们用它来生成咖啡订单 或者创建一个想和经理交谈的角色

    现在 来看看我们在游戏中 遇到了什么!

    哇 真的有人需要一杯咖啡

    显然 并不是每个客人都 那么容易伺候 所以 让我们由此给 NPC 添加关卡 Generable 支持大多数开箱即用型 Swift 类型 包括 Int 我们来添加一个关卡属性 但我们不想生成任何整数 如果希望关卡处在特定范围内 可以用 Guide 来指定 可以在属性上使用 Guide 宏 并传递一个范围 同样 模型将使用约束解码 以保证值在这个范围内

    与此同时 我们还为 NPC 添加 一系列属性

    我们可以再次使用 Guide 这次是指定我们想要的 NPC 这个数组的属性 正好三个 请记住 Generable 类型的属性 是按照源代码中声明的顺序生成的 在这里 将首先生成名称 然后生成关卡 然后是属性 最后是偶遇

    这个顺序可能很重要 如果你期望某个属性的值 受到另一个属性影响 你甚至可以对属性逐个进行流式传输 而不必等到全部输出生成完毕 游戏现在很有趣! 差不多可以与朋友们分享了 但我注意到 NPC 的姓名 和我想的不太一样 我更喜欢名字加姓氏

    为此 我们可以使用 Guide 但这次只提供自然语言描述

    我们可以说姓名应该是“全名” 这实际上是另一种提示方式 你不必在提示中描述不同的属性 在 Generable 类型中可以 直接做到这一点 这样一来 模型与这些描述的关系更密切 如果现在在游戏中四处走动 我们会检查这些新姓名的实际应用 下面简要介绍可应用于 不同类型的所有 Guide

    对于常见的数值类型 比如 int 可以指定最小值、最大值或范围 对于数组 可以控制计数 或指定数组元素类型的 Guide

    对于字符串 可以让模型从 anyOf 数组中挑选 甚至限制为正则表达式模式

    正则表达式模式 Guide 特别强大 你可能熟悉用正则表达式来匹配文本 但是对于 Foundation 模型 你可以使用正则表达式模式 来定义要生成的字符串的结构 例如可以将姓名约束为一组前缀 你甚至可以使用正则表达式构建器 句法!

    如果这让你对正则表达式重燃兴趣 请务必观看永恒的经典之作 几年前的讲座“了解 Swift Regex” 总之 Generable 是 可以用于结构和枚举的宏 它为你提供了一种可靠的方法 从模型获取结构化输出 你无需操心任何解析 为了获得更具体的输出 你可以将 Guide 应用于属性 因此 如果你在编译时就知道结构 Generable 将非常有用 这个宏会为你生成架构 你会得到一个类型实例作为输出 但有时你只是在运行时知道结构 这就是动态架构发挥作用的地方 我要在游戏中添加一个关卡创建器 玩家可以动态定义在游戏中走动时 会偶遇的实体 比如 玩家可以创建一个谜语架构 谜语就是一个问题以及多项选择答案 如果我们在编译时知道这个结构 就只需为它定义一个 Generable 结构体 但是关卡创建器允许创建 玩家能想到的任何结构

    在运行时 我们可以用 DynamicGenerationSchema 创建架构 就像编译时定义的结构一样 动态 架构也有一个属性列表 我们可以添加能接受玩家输入的 关卡创建器

    每个属性都有自己的名称和 定义所属类型的架构 你可以将架构用于任何 Generable 类型 包括内置类型 如字符串

    动态架构可以包含一个数组 然后为数组元素指定架构 重要的是 动态架构可以引用 其他动态架构 因此在这里 我们的数组可以引用 同样是在运行时定义的自定义架构

    根据用户的输入 我们可以创建一个 具备两个属性的谜语架构 第一个是问题 这是字符串属性 其次是数组属性 一个名为 Answer 的自定义类型 然后创建答案 它具有字符串和布尔属性 请注意 谜语的答案属性 按名称引用答案架构 然后我们可以创建 DynamicGenerationSchema 实例 每个动态架构都是独立的 也就是说谜语动态架构 实际上并不包含答案的动态架构 在进行推理之前 我们首先必须将动态架构转换为 经过验证的架构 这可能会抛出错误 如果动态架构中存在不一致 例如不存在某些类型引用

    一旦有了经过验证的架构 就可以像往常一样提示会话 但这一次 输出类型是 GeneratedContent 实例 其中包含动态值 你可以使用动态架构中的 属性名称进行查询 同样 Foundation 模型将采用 引导式生成 来确保输出与架构匹配 绝不会编造出意外的字段! 因此尽管是动态的 你仍然无需担心要手动解析输出

    所以现在当玩家遇到 NPC 时 模型可以生成这个动态内容 我们将在动态 UI 中加以展示 让我们来看看遇到了什么 我是深色或是浅色 苦涩抑或甜蜜 我将你唤醒 带来热量 我是什么? 咖啡或热巧克力 我认为答案是咖啡 没错! 我想玩家会从创造各种趣味关卡中 获得很多乐趣 总结一下 利用 Generable 宏 我们可以从编译时定义的 Swift 类型轻松生成结构化输出 在后台 Foundation 模型负责架构 并将 GeneratedContent 转换为 你自己的类型的实例 动态架构的工作原理非常相似 但为你提供了更多的控制权 你可以在运行时完全控制架构 并直接访问 GeneratedContent 接下来 我们来探讨工具调用 它可以让模型调用你自己的函数 我正在考虑创建一个 DLC 也就是可下载内容 让我的游戏更具个性 通过工具调用 我可以让模型 自主获取信息 我认为集成玩家的通讯录和日历 可能会非常有趣 对于服务器模型 我通常不会这样做 玩家不会喜欢游戏上传 这类个人数据 但是由于使用了 Foundation 模型 这些都在设备端 我们就能这样做 同时还能保护隐私

    使用 Tool 协议来定义工具非常简单 你首先要给它一个名称和描述 这是 API 会自动放入提示中的内容 用于让模型决定 调用工具的时间和频率

    工具名称最好要简短 同时还要是易懂的英文文本 避免使用缩写 描述不要太长 也不要解释任何实现方式 因为请记住 这些字符串是 逐字逐句放在提示中的 所以字符串越长意味着令牌越多 就会增加延迟 相反 请考虑在名称中使用动词 比如 findContact 描述应该大约是一句话长度 与以往一样 务必尝试不同的 版本来找出 最适合你的特定工具

    接下来 我们可以定义工具的输入 我想让工具从特定年龄段获取联系人 比如千禧一代 模型将能够根据游戏状态 选择有趣的用例 我可以添加 Arguments 结构体 并让它成为 Generable 当模型决定调用这个工具时 它会生成输入参数 通过 Generable 这样可以保证 你的工具始终获得有效的输入参数 所以不会编造出不同的世代 比如 Gen Alpha 因为游戏不支持这个年龄段

    然后就可以实现调用函数 模型决定调用工具时 将调用这个函数 在本例中 我们将调用 Contacts API 并返回这个查询的联系人姓名

    要使用我们的工具 我们将它传递到会话构造器中 然后 当模型需要额外信息时 就会调用我们的工具 相比我们自己去获取联系人 这个功能更强大 因为模型只会在需要特定 NPC 时 调用这个工具 而且它可以根据游戏状态 选择有趣的输入参数 比如 NPC 的年龄段

    请记住 这是使用常规的 Contacts API 你可能对此很熟悉 第一次调用我们的工具时 它将请求玩家提供通常的许可 即使玩家不想授予通讯录访问权限 Foundation 模型 仍能像以前一样生成内容 但如果它们确实提供了访问权限 我们会让游戏更加个性化

    让我们在游戏中四处走走 直到遇到另一个 NPC 这一次 我将从自己的通讯录中 抽取一个名字! 哦 嗨 Naomy! 我们来看看她是怎么说的

    我不知道你喜欢咖啡 请注意 LanguageModelSession 接受了一个工具实例 这意味着你在控制工具的生命周期 这个工具的实例 在整个会话期间将保持不变 现在这个例子中 因为我们只是使用 FindContactsTool 随机获取角色 有时可能会得到同样的联系人 在游戏中 现在有多个 Naomy 这是不对的 只能有一个 Naomy 要解决这个问题 我们可以跟踪 游戏使用过的联系人 我们可以在 FindContactTool 中 添加状态 为此 首先将 FindContactTool 转换为一个类别 这样一来 它就能 从调用方法更改状态 然后我们就可以跟踪选取的联系人 在我们的调用方法中 我们不会再次选择相同的人

    现在 NPC 姓名都来自我的通讯录! 但与他们交谈的感觉还是不对 让我们用另一个工具来完成这个工作 这次是访问我的日历

    对于这个工具 我们将从游戏中正在 进行的对话传入联系人姓名 模型调用这个工具时 我们会让它生成 年月日 用来获取与这个联系人 有关的事件 我们将在 NPC 对话的会话中 传递这个工具

    现在 如果我们问我朋友 Naomy 的 NPC “发生什么事了?” 她就可以用我们一起计划的 真实事件来作答

    哇 现在就像在和真正的 Naomy 说话一样

    我们来详细了解一下工具调用的 工作原理 首先在会话开始时传递这个工具 以及相关指令 在本例中 我们提供了 今天的日期等信息 然后当用户提示会话时 模型可以分析文本 在本例中 模型理解提示是在 询问事件 因此调用日历工具就合情合理

    要调用工具 模型首先生成输入参数 在本用例中 模型需要生成日期 来获取当日事件 模型可以联系指令和提示中的信息 并了解如何根据这些信息 填写工具参数 因此在本例中 模型可以根据指令中 今天的日期来推断明天会发生什么 工具的输入生成后 将启用调用方法 这是你大放异彩的时候 你的工具可以随心所欲了 但请注意 会话会等待工具返回 然后才能生成更多输出

    之后 工具的输出将放在脚本中 就像模型输出一样 而且根据工具输出 模型可以生成对提示的响应 请注意 单个请求可以多次调用工具 在这种情况下 工具会被并行调用 因此通过工具调用方法访问数据时 请记住这一点 好吧 这很有趣! 我们的游戏现在会随机生成内容 依据是我的个人通讯录和日历 而且我的数据并没有离开我的设备 简而言之 工具调用可以让模型 调用你的代码 在请求期间访问外部数据 这可以是隐私信息 如通讯录 甚至可以是来自网络来源的外部数据 请记住 在给定请求中 可以多次调用工具 模型根据上下文确定这一点 工具也可以并行调用 可以存储状态 这真的很棒! 也许在做其他任何事情之前 先喝杯咖啡再说 要进一步了解 你可以观看 关于提示工程的专门视频 包括设计和安全技巧 另外 如果你想见见真正的 Naomy 请观看 code-along 视频 我希望你能像我一样 从 Foundation 模型中获得不少乐趣 谢谢观看!

    • 1:05 - Prompting a session

      import FoundationModels
      
      func respond(userInput: String) async throws -> String {
        let session = LanguageModelSession(instructions: """
          You are a friendly barista in a world full of pixels.
          Respond to the player’s question.
          """
        )
        let response = try await session.respond(to: userInput)
        return response.content
      }
    • 3:37 - Handle context size errors

      var session = LanguageModelSession()
      
      do {
        let answer = try await session.respond(to: prompt)
        print(answer.content)
      } catch LanguageModelSession.GenerationError.exceededContextWindowSize {
        // New session, without any history from the previous session.
        session = LanguageModelSession()
      }
    • 3:55 - Handling context size errors with a new session

      var session = LanguageModelSession()
      
      do {
        let answer = try await session.respond(to: prompt)
        print(answer.content)
      } catch LanguageModelSession.GenerationError.exceededContextWindowSize {
        // New session, with some history from the previous session.
        session = newSession(previousSession: session)
      }
      
      private func newSession(previousSession: LanguageModelSession) -> LanguageModelSession {
        let allEntries = previousSession.transcript.entries
        var condensedEntries = [Transcript.Entry]()
        if let firstEntry = allEntries.first {
          condensedEntries.append(firstEntry)
          if allEntries.count > 1, let lastEntry = allEntries.last {
            condensedEntries.append(lastEntry)
          }
        }
        let condensedTranscript = Transcript(entries: condensedEntries)
        // Note: transcript includes instructions.
        return LanguageModelSession(transcript: condensedTranscript)
      }
    • 6:14 - Sampling

      // Deterministic output
      let response = try await session.respond(
        to: prompt,
        options: GenerationOptions(sampling: .greedy)
      )
                      
      // Low-variance output
      let response = try await session.respond(
        to: prompt,
        options: GenerationOptions(temperature: 0.5)
      )
                      
      // High-variance output
      let response = try await session.respond(
        to: prompt,
        options: GenerationOptions(temperature: 2.0)
      )
    • 7:06 - Handling languages

      var session = LanguageModelSession()
      
      do {
        let answer = try await session.respond(to: userInput)
        print(answer.content)
      } catch LanguageModelSession.GenerationError.unsupportedLanguageOrLocale {
        // Unsupported language in prompt.
      }
      
      let supportedLanguages = SystemLanguageModel.default.supportedLanguages
      guard supportedLanguages.contains(Locale.current.language) else {
        // Show message
        return
      }
    • 8:14 - Generable

      @Generable
      struct NPC {
        let name: String
        let coffeeOrder: String
      }
      
      func makeNPC() async throws -> NPC {
        let session = LanguageModelSession(instructions: ...)
        let response = try await session.respond(generating: NPC.self) {
          "Generate a character that orders a coffee."
        }
        return response.content
      }
    • 9:22 - NPC

      @Generable
      struct NPC {
        let name: String
        let coffeeOrder: String
      }
    • 10:49 - Generable with enum

      @Generable
      struct NPC {
        let name: String
        let encounter: Encounter
      
        @Generable
        enum Encounter {
          case orderCoffee(String)
          case wantToTalkToManager(complaint: String)
        }
      }
    • 11:20 - Generable with guides

      @Generable
      struct NPC {
        @Guide(description: "A full name")
        let name: String
        @Guide(.range(1...10))
        let level: Int
        @Guide(.count(3))
        let attributes: [Attribute]
        let encounter: Encounter
      
        @Generable
        enum Attribute {
          case sassy
          case tired
          case hungry
        }
        @Generable
        enum Encounter {
          case orderCoffee(String)
          case wantToTalkToManager(complaint: String)
        }
      }
    • 13:40 - Regex guide

      @Generable
      struct NPC {
        @Guide(Regex {
          Capture {
            ChoiceOf {
              "Mr"
              "Mrs"
            }
          }
          ". "
          OneOrMore(.word)
        })
        let name: String
      }
      
      session.respond(to: "Generate a fun NPC", generating: NPC.self)
      // > {name: "Mrs. Brewster"}
    • 14:50 - Generable riddle

      @Generable
      struct Riddle {
        let question: String
        let answers: [Answer]
      
        @Generable
        struct Answer {
          let text: String
          let isCorrect: Bool
        }
      }
    • 15:10 - Dynamic schema

      struct LevelObjectCreator {
        var properties: [DynamicGenerationSchema.Property] = []
      
        mutating func addStringProperty(name: String) {
          let property = DynamicGenerationSchema.Property(
            name: name,
            schema: DynamicGenerationSchema(type: String.self)
          )
          properties.append(property)
        }
      
        mutating func addArrayProperty(name: String, customType: String) {
          let property = DynamicGenerationSchema.Property(
            name: name,
            schema: DynamicGenerationSchema(
              arrayOf: DynamicGenerationSchema(referenceTo: customType)
            )
          )
          properties.append(property)
        }
        
        var root: DynamicGenerationSchema {
          DynamicGenerationSchema(
            name: name,
            properties: properties
          )
        }
      }
      
      var riddleBuilder = LevelObjectCreator(name: "Riddle")
      riddleBuilder.addStringProperty(name: "question")
      riddleBuilder.addArrayProperty(name: "answers", customType: "Answer")
      
      var answerBuilder = LevelObjectCreator(name: "Answer")
      answerBuilder.addStringProperty(name: "text")
      answerBuilder.addBoolProperty(name: "isCorrect")
      
      let riddleDynamicSchema = riddleBuilder.root
      let answerDynamicSchema = answerBuilder.root
      
      let schema = try GenerationSchema(
        root: riddleDynamicSchema,
        dependencies: [answerDynamicSchema]
      )
      
      let session = LanguageModelSession()
      let response = try await session.respond(
        to: "Generate a fun riddle about coffee",
        schema: schema
      )
      let generatedContent = response.content
      let question = try generatedContent.value(String.self, forProperty: "question")
      let answers = try generatedContent.value([GeneratedContent].self, forProperty: "answers")
    • 18:47 - FindContactTool

      import FoundationModels
      import Contacts
      
      struct FindContactTool: Tool {
        let name = "findContact"
        let description = "Finds a contact from a specified age generation."
          
        @Generable
        struct Arguments {
          let generation: Generation
              
          @Generable
          enum Generation {
            case babyBoomers
            case genX
            case millennial
            case genZ            
          }
        }
        
        func call(arguments: Arguments) async throws -> ToolOutput {
          let store = CNContactStore()
              
          let keysToFetch = [CNContactGivenNameKey, CNContactBirthdayKey] as [CNKeyDescriptor]
          let request = CNContactFetchRequest(keysToFetch: keysToFetch)
      
          var contacts: [CNContact] = []
          try store.enumerateContacts(with: request) { contact, stop in
            if let year = contact.birthday?.year {
              if arguments.generation.yearRange.contains(year) {
                contacts.append(contact)
              }
            }
          }
          guard let pickedContact = contacts.randomElement() else {
            return ToolOutput("Could not find a contact.")
          }
          return ToolOutput(pickedContact.givenName)
        }
      }
    • 20:26 - Call FindContactTool

      import FoundationModels
      
      let session = LanguageModelSession(
        tools: [FindContactTool()],
        instructions: "Generate fun NPCs"
      )
    • 21:55 - FindContactTool with state

      import FoundationModels
      import Contacts
      
      class FindContactTool: Tool {
        let name = "findContact"
        let description = "Finds a contact from a specified age generation."
         
        var pickedContacts = Set<String>()
          
        ...
      
        func call(arguments: Arguments) async throws -> ToolOutput {
          contacts.removeAll(where: { pickedContacts.contains($0.givenName) })
          guard let pickedContact = contacts.randomElement() else {
            return ToolOutput("Could not find a contact.")
          }
          return ToolOutput(pickedContact.givenName)
        }
      }
    • 22:27 - GetContactEventTool

      import FoundationModels
      import EventKit
      
      struct GetContactEventTool: Tool {
        let name = "getContactEvent"
        let description = "Get an event with a contact."
      
        let contactName: String
          
        @Generable
        struct Arguments {
          let day: Int
          let month: Int
          let year: Int
        }
          
        func call(arguments: Arguments) async throws -> ToolOutput { ... }
      }
    • 0:00 - 简介
    • 了解面向 Apple 设备的 Foundation Models 框架,它提供了一个可通过 Swift API 访问的设备端大语言模型。 该框架涵盖了如何使用 Generable 获取结构化输出、动态架构和可调用自定义函数的工具调用。

    • 0:49 - 会话
    • 在本例中,Foundation Models 通过生成动态游戏对话和内容来提升像素艺术咖啡店游戏的体验。通过创建“LanguageModelSession”,向模型提供自定说明,从而让模型能够回应玩家提出的问题。该模型将用户输入和会话说明处理成令牌、小子字符串,然后生成一个新的令牌序列作为输出。 “LanguageModelSession”是有状态的,会将所有提示和回应保留在对话记录中。你可以利用这份记录进行调试,并在游戏的用户界面中展示对话历史记录。但是,会话的大小是有限制的,称为上下文限制。 默认情况下,响应的生成具有不确定性。模型会使用采样功能,为每个令牌生成一个概率分布,从而引入一定的随机性。这种随机性可以通过使用 GenerationOptions API 来控制,让你能够调整采样方法、温度值,甚至将其设置为“贪婪”以获得确定性输出。 除了简单的对话之外,还可以使用 Foundation Models 来生成更复杂的输出,例如为不可玩角色 (NPC) 生成名字和咖啡订单。这一功能为游戏世界增添了层次感和多样性,让游戏更生动逼真、更引人入胜。你还必须考虑并妥善处理不受支持的语言等潜在问题,以保障用户体验的流畅性。

    • 7:57 - Generable
    • Foundation Models 的 Generable API 是一款强大的工具,可简化从大语言模型中提取结构化数据的过程。通过将 @Generable 宏应用于 Swift 结构体或枚举,编译时会生成架构,从而引导模型的输出。 Generable 会自动生成构造器,并通过约束解码技术将模型生成的文本解析为类型安全的 Swift 对象。这项技术可确保模型的输出符合指定的架构,避免出现幻觉和结构错误。 你可以使用“指南”进一步自定义生成流程,这些指南可为特定属性提供约束条件、范围或自然语言描述。这样就可以更精细地控制生成的数据,例如指定名称格式、数组数量或数值范围等。Generable 可实现高效且可靠的数据生成,让开发者能够专注于应用程序中更复杂的部分。

    • 14:29 - 动态模式
    • 在游戏的关卡创建器中,动态架构让玩家能够在运行时定义自定实体。这些架构类似于编译时的结构体,其属性包含名称和类型,支持数组以及对其他动态架构的引用。 根据玩家的输入,可创建一个谜语架构,其中包含问题 (字符串类型) 和答案数组 (自定类型,包含字符串和布尔属性)。这些动态架构经过验证,然后由 Foundation Models 用于生成内容,确保输出与定义的结构相匹配。 这种动态方法让游戏能够在动态 UI 中显示玩家创建的谜语及其他实体,在保持结构化数据处理的同时,为玩家提供了高度的灵活性与创造力。

    • 18:10 - 工具调用
    • 借助 Foundation Models,游戏开发者可以使用工具调用功能创建个性化的 DLC。这使得模型能够自主从玩家设备上获取通讯录和日历等信息,同时由于数据始终保留在设备本地,隐私也得到了保障。 定义工具需要指定名称、描述和输入参数。模型会根据这些信息来判断何时以及如何调用该工具。然后,该工具的实现会与外部 API (如 Contacts API) 进行交互,以检索数据。

Developer Footer

  • 视频
  • WWDC25
  • 深入了解 Foundation Models 框架
  • 打开菜单 关闭菜单
    • iOS
    • iPadOS
    • macOS
    • Apple tvOS
    • visionOS
    • watchOS
    打开菜单 关闭菜单
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • SF Symbols
    打开菜单 关闭菜单
    • 辅助功能
    • 配件
    • App 扩展
    • App Store
    • 音频与视频 (英文)
    • 增强现实
    • 设计
    • 分发
    • 教育
    • 字体 (英文)
    • 游戏
    • 健康与健身
    • App 内购买项目
    • 本地化
    • 地图与位置
    • 机器学习与 AI
    • 开源资源 (英文)
    • 安全性
    • Safari 浏览器与网页 (英文)
    打开菜单 关闭菜单
    • 完整文档 (英文)
    • 部分主题文档 (简体中文)
    • 教程
    • 下载 (英文)
    • 论坛 (英文)
    • 视频
    打开菜单 关闭菜单
    • 支持文档
    • 联系我们
    • 错误报告
    • 系统状态 (英文)
    打开菜单 关闭菜单
    • Apple 开发者
    • App Store Connect
    • 证书、标识符和描述文件 (英文)
    • 反馈助理
    打开菜单 关闭菜单
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program (英文)
    • News Partner Program (英文)
    • Video Partner Program (英文)
    • 安全赏金计划 (英文)
    • Security Research Device Program (英文)
    打开菜单 关闭菜单
    • 与 Apple 会面交流
    • Apple Developer Center
    • App Store 大奖 (英文)
    • Apple 设计大奖
    • Apple Developer Academies (英文)
    • WWDC
    获取 Apple Developer App。
    版权所有 © 2025 Apple Inc. 保留所有权利。
    使用条款 隐私政策 协议和准则