-
使用 Foundation Models 框架构建智能体 App 体验
了解如何利用 Foundation Models 框架原语,在动态上下文和智能体工作流程中进一步提升你的智能功能。我们将逐步讲解如何构建共享上下文、设置隐私边界,并管理键值缓存。你还将探索如何协调本地模型与服务器模型的平滑交接。
章节
- 0:00 - Introduction
- 2:47 - The example app and agents
- 3:47 - Declaring a dynamic profile
- 4:45 - Dynamic instructions
- 5:36 - Configuring models per phase
- 7:21 - Transcript management and history transforms
- 8:50 - Custom modifiers
- 9:39 - Lifecycle modifiers and session properties
- 12:52 - Orchestration: baton-pass
- 14:06 - Orchestration: phone-a-friend and skills
- 15:18 - Tool calling mode
- 17:12 - Transcript error handling
- 18:27 - Performance, accuracy, and evaluations
- 21:24 - Next steps
资源
-
搜索此视频…
大家好! 感谢大家的参与! 我叫Erik 我是Oliver 今天 我们将深入了解一组新API 它们开辟了全新的可能性 为你的App服务 Dynamic Profiles! 但在深入代码之前 我们需要先打好基础 了解这些API旨在解决的问题 以及我们的设计理念 这些API解决的第一个挑战 是上下文管理 在长时运行的会话中 Dynamic Profiles可修剪或汇总记录 将内容保持在 模型的上下文窗口内 这些API解决的第二个问题 是设定边界 使用多个模型时 你应围绕 能力和成本因素进行设计 Dynamic Profiles为你提供了这个选项 这个领域每周都在发生变化 我们推出的基础组件 被设计为高度灵活 确保你能构建 当下和未来的抽象层 完全正确! Dynamic Profiles实现了上下文工程 并能定义模型边界 可集成到几乎任何架构中 正是基于这一理念 今天我们宣布了一个新Package Foundation Models框架实用工具 Utilities是一个开源的Swift Package 包含构建智能体体验 所需的各种实用组件 它将在操作系统版本间持续更新 让你能使用新兴的 或实验性模式 均以Dynamic Profiles为基础 现在铺垫已做好 让我们进入今天的议程 在本视频的前半部分 Oliver将讲解 Dynamic Profiles的工作原理 下半部分 我将重新加入 介绍一些高级主题 涉及编排模式的相关内容 最后 我们将探讨 性能和准确性的相关考量 接下来交给你了 Oliver! 谢谢你 Erik 随着LanguageModel协议的引入 以及PrivateCloudComputeLanguageModel 你现在有比以往 更多的模型可供选择 DynamicProfile是一个新API 让你能够在模型间切换 在你的LanguageModelSession中 让你灵活选择最佳配置 以应对当前的任务 DynamicProfile是构建 众多实用抽象层的基础 例如Agent或Skill 今天 我将带你浏览这个API 从利用多个模型开始 然后深入探讨记录相关的注意事项 最后介绍会话生命周期事件 让我们从一个例子开始
我正在开发一款名为Origami 的手工艺App 可以生成折纸和钩针教程 用户可以上传图片 App将帮助他们 以图片为灵感进行头脑风暴 用户可对精选创意 提供反馈 然后为选定的概念生成教程 用户学习教程的同时 可上传制作过程中的照片 并获取技巧建议
App的每个阶段都需要 共享上下文 但各自 有其独特的优先级 它们可能受益于 多样化的模型 配备不同的指令和生成选项 这些配置就是Agent 代表你的App行事 以特定目标和 能力集为导向进行配置 DynamicProfile让你能够 声明独立的Profile 代表LanguageModelSession中 的配置状态或Agent Profile由指令 工具和修饰符组成 用于配置模型 temperature samplingMode等参数 那么 让我们为手工艺体验 声明一个DynamicProfile 这里 我们有一个名为 CraftOrchestrator的@Observable类 它将跟踪App的各个阶段 我们先聚焦于头脑风暴阶段 用于向用户展示 各种手工艺项目创意 这里 我们的新Profile 包含了一些指令 说明其目标 以及一个用于生成标题的工具 由于折纸是一项复杂的手工艺 我们也来添加一些 额外的指令和工具 但仅在用户进行折纸项目时启用 OrigamiExpert使用了另一种 名为DynamicInstructions的新类型 DynamicInstructions可将 相关工具和指令分组 组成单一组件 可在代码库中复用 OrigamiExpert包含相关知识和工具 可在每次向模型询问 折纸内容时复用 DynamicInstructions也是可组合的 将OrigamiExpert嵌套在 另一个DynamicInstructions主体中 会将指令和工具拼接在一起 这里 我们创建了BrainstormFacilitator 来存放Profile的指令 现在可以用新声明 来整理我们的头脑风暴Profile 由于头脑风暴需要广泛的 手工艺知识和创意思维 此Profile将使用 PrivateCloudComputeLanguageModel 这是Foundation Models中 的一个新模型
请务必查看Louis的演讲 了解Foundation Models中的PCC 以及此模型的更多内容 它所能提供的一切 我们还会将temperature设置为1 让模型生成更具创意的回答 我们刚刚用DynamicProfiles 定义了第一个Agent
让我们进入Profile的 下一个模式:规划 「planning」Profile负责 创建制作指导 用于商定的手工艺项目 同样 我们将使用PCCLanguageModel 因为这需要深厚的手工艺知识 我们还会配置reasoningLevel 这是大多数服务器模型 都支持的功能 它控制模型 思考问题的能力 在作出响应之前 由于生成教程较为复杂 我们将其设置为deep
最后 「reviewing」阶段 提供建议和指导 帮助用户完成教程学习 为减少不必要的服务器调用 这里使用SystemLanguageModel 就这样 我们完成了 手工艺DynamicProfile的定义
要在会话中使用DynamicProfile 只需使用新的 LanguageModelSession初始化器即可 请注意 DynamicProfile的主体 会被重新评估 每次模型被提示时 因此当App在各模式间切换时 LanguageModelSession的角色 也会随之改变 你可以把这理解为换个角色 或切换Agent 你可以从头脑风暴 切换到规划 再到审阅 只需切换模式即可 你已了解如何使用DynamicProfile 在不同模型间进行路由 但重要的是 每个模型 可能有不同的上下文大小限制 我们的手工艺示例在PCCLanguageModel 和SystemLanguageModel间切换 在模型间切换时 你可能需要修剪不必要的条目 以保持在上下文大小范围内 但这不是调整模型上下文 的唯一原因 你还可以通过删除无关条目 来提高模型的专注度 或从现有条目中删除私密信息 切换到隐私级别较低的模型时 记录是LanguageModelSession 对模型上下文的表示 DynamicInstructions提供了 一种修改记录的方式 更具体地说 它允许 修改指令条目 若要更新其余条目 我们将使用记录中 名为「history」的视图 删除工具调用是 修剪历史记录的一种简便方法 让我们看看如何实现这一点
historyTransform可应用于Profile 以便在提示模型之前 转换历史记录 这是筛除条目的最佳时机 这些条目对当前请求可能并非必要 对我们的「reviewing」Profile 应用转换 有助于将记录保持在 设备端模型的上下文大小内 转换不会永久性地修改 会话的记录 它们是在提示模型之前 应用的本地转换 这意味着你无需担心 丢失上下文 这些上下文在后续可能会变得相关 我们的historyTransform涉及很多内容 让我演示如何使用自定义修饰符 来隐藏转换的复杂性 首先 我们声明一个新类型 使其符合DynamicProfileModifier 并应用我们的historyTransform 然后 通过实现一个扩展 使其可复用 扩展于DynamicProfile 任何需要减少上下文的新Profile 现在都可以使用这个新修饰符 我们在新的Foundation Models 框架实用工具Package中 提供了多个实用修饰符 欢迎大家前往了解 自定义修饰符是构建 可复用配置的绝佳方式 但转换并非影响记录的唯一方式 让我们看看另一种 更有状态的方式 在会话的某些时间点 你可能需要汇总之前的条目 从现有记录中回收上下文 在每次模型响应后执行此操作 可在会话生命周期中 提供清晰的边界 让我们看看如何执行汇总操作 在每次响应后 使用一组新修饰符 生命周期修饰符提供了 访问Profile进度的渠道 让你有机会运行命令式代码 直接在Profile声明中 这对于更新状态非常有用 例如在会话外部 在UI中反映进度 它对于内部状态更新也很有用 例如更改手工艺Profile中的模式 或修改会话历史记录 让我们使用onResponse修饰符 来修改历史记录 在我之前提到的响应边界处 你会注意到这里还用到了 另一个新概念:会话属性 会话属性让你能够定义状态 可从任意Tool或Profile访问 我们刚刚使用的history属性 是一个内置属性 由框架提供 它捕获会话的历史记录 可作为替代方案 代替historyTransform来更新记录 请注意 history属性是有损的 其变更将反映在 会话中的所有Profile上 若要对特定Profile 进行无损转换 建议优先使用historyTransform 除history之外 你还可以创建自定义会话属性 让我们创建一个新属性 在onResponse调用时存储对话摘要 你可以使用@SessionPropertyEntry宏 来声明属性 在SessionPropertyValues的扩展中 所有会话属性都是可变的 且必须有初始值 这里 我们将summary 声明为可选字符串 每个Profile现在都能 读取summary的值 通过访问我们刚刚声明的会话属性 我们将把摘要纳入Profile的指令 以确保它们了解上下文 关于已被删除的记录条目
任何Profile都可以写入该属性 变更将在整个会话中可见
现在让我为大家总结一下 使用生命周期修饰符 在会话特定时间点运行代码 使用history属性 更新所有Profile的会话历史记录 并使用自定义会话属性 存储所有会话组件共享的状态 接下来 我将把话筒交还给Erik 由他来讲解Agent编排的内容 谢谢你 Oliver!
希望你已经开始对 如何使用Profile建立起直觉认知 来构建Agent之类的东西 让我们看看编排智能体体验 的两种常见模式 我们称这两种模式为 baton-pass和phone-a-friend Baton-pass是协作模式 phone-a-friend是咨询模式 我们先来看baton-pass
在此模式中 有两个或多个Profile 通常各自利用不同的模型 还需要一个变量 来控制哪个Profile处于活跃状态 最后 我们为每个Profile提供一个工具 允许模型设置该变量 这些组件共同构成了baton-pass模式
如果我们正在头脑风暴 并询问如何折叠纸鹤 头脑风暴Profile将调用工具 将接力棒传递给教程Profile 工具输出标志着成功交接 教程Profile随后生成 最终答案 baton-pass模式最重要的特性 是完整的记录历史 对两个Profile均可见 以及接到接力棒的Profile 能够一路完成任务 并提供最终响应 这两个特性与我们接下来 要看的模式截然不同 phone-a-friend
在phone-a-friend模式中 你同样依赖工具调用 关键区别在于 它不是切换变量 而是由工具生成一个 短生命周期的会话 如果我们请求 一个适合儿童的有趣项目 模型可能会推断 需要为项目起一个标题 并调用phone-a-friend工具 咨询标题Profile
phone-a-friend工具生成 一个拥有独立记录的新会话 提示它 然后将响应 作为工具输出返回
子会话消失 父会话 生成最终响应 phone-a-friend模式最重要的特性 是每个Profile的记录相互隔离 以及父Profile始终负责 给出最终答案 Baton-pass和phone-a-friend 都是实用的工具 但还有其他选择
例如Foundation Models 框架实用工具Package 包含一个Skills类型 这是一种广受欢迎的模式 用于程序化上下文加载 既然你已经掌握了 工具用于编排的多种方式 我们来看一个新的可调项 用于控制工具调用的时机 工具调用模式
工具调用模式有三个选项 allowed disallowed和required 默认值为「allowed」 即现有行为 模型可以发起工具调用 也可以直接响应 当你不确定是否需要工具时 就选用这个选项 这是最常见的情况 「disallowed」阻止模型调用工具 当用户进入App的某个部分时 这会很有帮助 该部分的会话工具 已知不相关 最后 「required」意味着 模型只能调用工具 这在某些智能体系统中 尤为有用 这些系统将所有动作表示为工具调用
如果使用Profile 可通过修饰符指定工具调用模式 如果不使用Profile 工具调用模式可以通过 调用respond(to:)时 使用GenerationOptions设置
以下是最重要的注意事项 当工具调用为required时 模型本质上处于一个while循环中 你需要确保 存在某种退出条件 一个好的选择是 根据变量有条件地设置工具调用模式 这里 我们要求调用工具 直到模型调用数据库工具为止
第二个更强硬的选择 是为模型配备一个最终答案工具 该工具会抛出错误 抛出错误会中止工具调用循环 并立即将控制流返回给你 默认情况下 当你从工具中抛出错误 或取消响应时 会话的记录将回滚 至之前的状态 对于希望允许取消 部分响应的高级用例 并在之后继续 你需要 在错误后保持记录的状态 我们新增了API来实现这一点 如果使用Profile 现在可以设置 「transcriptErrorHandlingPolicy」 通过修饰符设置 如果不使用Profile 可以直接在会话上设置
两个选项分别是 「.revertTranscript」和「.preserveTranscript」
使用「.preserveTranscript」时 你需要负责将记录恢复 到良好状态 以便继续使用会话
为此 会话上的「transcript」属性 现在是可变的 但请注意 只有当会话的 「isResponding」属性为false时 才能修改记录 在响应期间尝试修改记录 是编程错误
了解了我们的新API之后 我们需要讨论 修改记录对性能和准确性的影响 键值(KV)缓存 是一种重要的优化机制 应用于大型语言模型中 记录修改可能会使其失效
通常 向记录追加内容 可保留KV缓存 并最大限度减少首个Token的等待时间
如果你通过删除条目 来重写历史记录 更改已附加的工具 或更新指令 这通常会触发缓存失效 并可能增加延迟 去年我们没有提到这一点 因为我们刻意将 LanguageModelsSession的API 设计为仅追加模式 默认情况下 它们确保了最优化的使用 但今年 可以说 我们去掉了辅助轮
了解不同模型 具有不同的缓存行为非常重要 而确定这一点的唯一方法就是测量
最好的方式是使用Xcode中 升级后的Foundation Models Instrument 有关使用Instruments 检测缓存失效的更多内容 请务必查看我们关于 调试和性能分析的视频
除了性能影响 另一个需要注意的问题 在重写历史记录时是准确性 因为这可能会使模型感到困惑
假设我在一个会话中 请模型想一些有趣的 折纸项目名称 然后假设我向会话中 添加了一个生成标题的工具 并提示它给出更多创意 你认为接下来会发生什么?
幸运的话 模型会按 我们期望的方式使用工具
但模型也可能注意到 它之前生成了标题 而没有使用工具 并可能认为应该再次这样做 这不是我们想要的 我们的历史修改使模型感到困惑
当你开始进行这类精细的记录修改时 使用Evaluations框架创建评估集 就变得更加重要 并量化上下文工程策略的效果 数据驱动的优化 是获得信心的唯一途径 强烈建议观看我们关于 Evaluations框架的所有视频 好了 这就是我们关于 性能和准确性部分的全部内容 内容确实很多! Oliver 准备好收尾了吗? 当然没问题 我们已向你展示了Dynamic Profiles如何 引导模型行为并管理 你的会话记录 我们讨论了phone-a-friend 和baton-pass等模式 工具调用模式 手动记录管理 甚至KV缓存 我们希望你也同样对 Foundation Models框架实用工具 充满热情!
下一步 试用一下示例App吧 或者配合改进后的Xcode Instrument 测试PCC
下次见 感谢观看 谢谢大家!
-
-
5:04 - DynamicInstructions
// DynamicInstructions struct BrainstormFacilitator: DynamicInstructions { var orchestrator: CraftOrchestrator var body: some DynamicInstructions { Instructions { "You are a warm and friendly expert crafting brainstorm facilitator." } // Tools GenerateProjectTitle() // Conditionally include Origami knowledge if orchestrator.techniques.contains(.origami) { OrigamiExpert() } } } -
6:41 - DynamicProfile
// DynamicProfile struct CraftProfile: LanguageModelSession.DynamicProfile { var orchestrator: CraftOrchestrator var body: some DynamicProfile { switch orchestrator.mode { case .brainstorming: Profile { BrainstormFacilitator(orchestrator: orchestrator) } .model(orchestrator.pccLanguageModel) .temperature(1) case .planning: Profile { TutorialAuthor(orchestrator: orchestrator) } .model(orchestrator.pccLanguageModel) .reasoningLevel(.deep) case .reviewing: Profile { CraftCoach() } .model(orchestrator.systemLanguageModel) } } } -
6:43 - Initialize your session with your dynamic profile
// Initialize your session with your dynamic profile let session = LanguageModelSession(profile: CraftProfile(orchestrator: orchestrator)) -
8:33 - Transcript management
// Transcript management struct CraftProfile: LanguageModelSession.DynamicProfile { var orchestrator: CraftOrchestrator var body: some DynamicProfile { switch orchestrator.mode { case .reviewing: Profile { CraftCoach() } .model(orchestrator.systemLanguageModel) .historyTransform { history in // Update the history for your profile guard let latestResponseIndex = lastResponseEntryIndex(history) else { return history } let filteredHistory = history[0..<latestResponseIndex].filter { entry in isToolCallsOrToolOutput(entry) } return filteredHistory + history[latestResponseIndex...] } } } } -
9:15 - Custom modifiers
// Custom modifiers struct DroppingToolCallsProfileModifier: LanguageModelSession.DynamicProfileModifier { func body(content: Content) -> some DynamicProfile { content .historyTransform { history in guard let latestResponseIndex = lastResponseEntryIndex(history) else { return history } let filteredHistory = history[0..<latestResponseIndex].filter { entry in isToolCallsOrToolOutput(entry) } return filteredHistory + history[latestResponseIndex...] } } } extension LanguageModelSession.DynamicProfile { func droppingCompletedToolCalls() -> some DynamicProfile { self.modifier(DroppingToolCallsProfileModifier()) } } -
9:27 - History management modifiers
// History management modifiers import FoundationModelsUtilities struct CraftProfile: LanguageModelSession.DynamicProfile { var orchestrator: CraftOrchestrator var body: some DynamicProfile { switch orchestrator.mode { case .reviewing: Profile { CraftCoach() } // Keep the most recent 10 entries // after dropping finished tool calls .rollingWindow(size: .entries(10)) .droppingCompletedToolCalls() } } } -
10:48 - Lifecycle modifiers
// Lifecycle modifiers struct CraftProfile: LanguageModelSession.DynamicProfile { @SessionProperty(\.history) var history var orchestrator: CraftOrchestrator var body: some DynamicProfile { switch orchestrator.mode { case .planning: Profile { TutorialAuthor(orchestrator: orchestrator) } .model(orchestrator.pccLanguageModel) .reasoningLevel(.deep) .onResponse { // Update history if history.count > 50, let responseIndex = lastResponseIndex(history) { history = history[responseIndex...] } } } } } -
11:40 - Declare a custom session property
// Session properties — declaration extension SessionPropertyValues { @SessionPropertyEntry var summary: String? } -
12:24 - Read and write session properties in a profile
// Session properties struct CraftProfile: LanguageModelSession.DynamicProfile { @SessionProperty(\.history) var history @SessionProperty(\.summary) var summary var orchestrator: CraftOrchestrator var body: some DynamicProfile { switch orchestrator.mode { case .planning: Profile { TutorialAuthor(orchestrator: orchestrator) if let summary { Instructions { "Summary: \(summary)" } } } .onResponse { if history.count > 50, let responseIndex = lastResponse(history.prefix(40)) { summary = try await summarize(history[0..<responseIndex]) history = history[responseIndex...] } } } } } -
13:02 - Orchestration: baton-pass
// Baton-pass struct CraftProfile: LanguageModelSession.DynamicProfile { var orchestrator: CraftOrchestrator var body: some DynamicProfile { switch orchestrator.mode { case .brainstorm: Profile { BrainstormInstructions() BatonPassTool() } .onToolCall { orchestrator.mode = .tutorial } .model(orchestrator.serverModel) case .tutorial: Profile { TutorialInstructions() BatonPassTool() } .onToolCall { orchestrator.mode = .brainstorm } .model(orchestrator.systemModel) } } } -
14:14 - Orchestration: phone-a-friend
// Phone-a-friend struct CraftProfile: LanguageModelSession.DynamicProfile { var body: some DynamicProfile { Profile { BrainstormInstructions() PhoneFriendTool( name: "generate_title", description: "Generate a creative project title", profile: TitleProfile() ) } } } struct PhoneFriendTool<P: LanguageModelSession.DynamicProfile>: Tool { func call(arguments: GeneratedContent) async throws -> String { let session = LanguageModelSession(profile: profile()) let response = try await session.respond(to: arguments) return response.content } } -
15:15 - The skills pattern
// The skills pattern struct CraftingSkills: LanguageModelSession.DynamicInstructions { var activations: SkillActivations var body: some DynamicInstructions { Skills(activations: activations) { Skill( name: "origami_folds", description: "Details about specific types of folds", prompt: """ Valley Fold: Paper is folded toward you, creating a V-shaped crease Mountain Fold: Paper is folded away from you, creating an inverted V ... """ ) Skill(...) Skill(...) } } } -
15:31 - Tool calling mode
// Tool calling mode public struct ToolCallingMode: Sendable { public static let allowed: ToolCallingMode public static let disallowed: ToolCallingMode public static let required: ToolCallingMode } // Pass tool calling mode as a profile modifier struct OrigamiExpert: LanguageModelSession.DynamicProfile { var body: some LanguageModelSession.DynamicProfile { Profile { Instructions("You are an origami expert") QueryOrigamiDatabaseTool() ShowDirectionsTool() } .toolCallingMode(.required) } } // Or pass it as a generation option let response = try await session.respond( to: "Write out the instructions for folding a paper crane.", options: GenerationOptions(toolCallingMode: .required) ) -
16:47 - Escaping a tool call loop
// Escaping a tool call loop struct OrigamiExpert: LanguageModelSession.DynamicProfile { let state: OrigamiAppState var body: some LanguageModelSession.DynamicProfile { Profile { Instructions("Answer questions about how to fold origami") QueryOrigamiDatabaseTool() } .toolCallingMode(state.queriedDatabase ? .disallowed : .required) .onToolCall { state.queriedDatabase = true } } } -
16:57 - Define a tool that throws an error
// Define a tool that throws an error var output: String? @Generable struct Arguments { var answer: String } func call(arguments: Arguments) async throws -> Never { output = arguments.answer throw CancellationError() } } -
17:28 - Set the transcript error handling policy
// Specify transcript behavior on a profile struct OrigamiExpert: LanguageModelSession.DynamicProfile { let state: OrigamiAppState var body: some LanguageModelSession.DynamicProfile { Profile { Instructions("Answer questions about how to fold origami") QueryOrigamiDatabaseTool() } .transcriptErrorHandlingPolicy(.preserveTranscript) } } // Or specify it on a session let session = LanguageModelSession() session.transcriptErrorHandlingPolicy = .preserveTranscript // Policy options extension LanguageModelSession { public struct TranscriptErrorHandlingPolicy: Sendable { // Roll the transcript back to its previous state public static let revertTranscript: TranscriptErrorHandlingPolicy // Keep the transcript in state following an error public static let preserveTranscript: TranscriptErrorHandlingPolicy } } -
17:51 - Transcript mutation
// Transcript mutation public final class LanguageModelSession: Sendable { public var transcriptErrorHandlingPolicy: TranscriptErrorHandlingPolicy { get set } // Transcript is now settable public var transcript: Transcript { get set } // But you must not modify it during a response! public var isResponding: Bool { get } }
-
-
- 0:00 - Introduction
Erik Hornberger and Oliver O'Neill introduce Dynamic Profiles and the problems they solve, context management and model boundaries, plus the new open-source Foundation Models framework utilities package. Agenda: dynamic profiles, orchestration patterns, performance and accuracy.
- 2:47 - The example app and agents
Introduces the Origami craft app with three phases (brainstorming, planning, reviewing) that share context but have different priorities. Each phase becomes an agent: a configuration with its own model, instructions, and tools.
- 3:47 - Declaring a dynamic profile
A DynamicProfile declares individual Profiles representing a configuration or agent. Build the brainstorming profile from an Observable orchestrator, instructions, and tools, conditionally adding capabilities for origami projects.
- 4:45 - Dynamic instructions
DynamicInstructions groups related instructions and tools into a single reusable, composable component. Nesting one inside another concatenates their instructions and tools, such as an OrigamiExpert reused wherever needed.
- 5:36 - Configuring models per phase
Assign different models and options per profile: Private Cloud Compute with temperature and deep reasoningLevel for brainstorming and planning, and SystemLanguageModel for reviewing. The profile body is re-evaluated on each prompt, swapping the session's persona by mode.
- 7:21 - Transcript management and history transforms
Trim or redact the transcript to stay within context limits, keep the model focused, or protect privacy. historyTransform applies stateless, per-request transforms over the history window (such as dropping tool calls) without mutating the session.
- 8:50 - Custom modifiers
Custom modifiers hide transform complexity — a type conforming to DynamicProfileModifier, exposed through a DynamicProfile extension for reuse across profiles, alongside the ready-made history-management modifiers in the utilities package.
- 9:39 - Lifecycle modifiers and session properties
onResponse and other lifecycle modifiers run imperative code at session boundaries to update UI, profile state, or history. Session properties (the built-in history, plus custom @SessionPropertyEntry values) share state across tools and profiles, for example storing a conversation summary.
- 12:52 - Orchestration: baton-pass
A collaboration pattern: multiple profiles share the full transcript, and a tool toggles which profile is active. The profile that receives the baton produces the final response.
- 14:06 - Orchestration: phone-a-friend and skills
A consultation pattern: a tool spawns a short-lived child session with an isolated transcript, and the parent profile always gives the final answer. Also notes the Skills pattern in the utilities package for procedural context loading.
- 15:18 - Tool calling mode
Control when tools run via allowed, disallowed, or required (as a profile modifier or generation option). When required, the model loops, so ensure an exit condition by conditionalizing the mode or using a final-answer tool that throws to break out.
- 17:12 - Transcript error handling
By default a thrown tool error or cancellation reverts the transcript; the new transcriptErrorHandlingPolicy (.revertTranscript or .preserveTranscript) keeps it. With preserve, the now-mutable transcript is yours to fix, only when isResponding is false.
- 18:27 - Performance, accuracy, and evaluations
Transcript mutations can invalidate key-value caches and raise latency; appending preserves them. Rewriting history can also confuse the model, so measure with the Foundation Models Instrument and quantify changes with the Evaluations framework.
- 21:24 - Next steps
Where to go next — try the sample app, explore the Foundation Models framework utilities, and measure performance with Private Cloud Compute and the revamped Xcode instrument.