-
Foundation Modelフレームワークによる、エージェントを活用したアプリ体験の構築
Foundation Modelフレームワークのプリミティブを介して動的コンテキストやエージェント型ワークフローを活用し、インテリジェンス機能をさらに向上させる方法を学びましょう。共有コンテキストの処理、プライバシー境界の設定、キーバリューキャッシュの管理について概要を説明するほか、ローカルモデルとサーバモデルの間でのスムーズなやり取りのためのオーケストレーションの方法を紹介します。
関連する章
- 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セットを深掘りします アプリのための Dynamic profilesです! コードに入る前に、基礎固めとして 問題の特定を行います これらのAPIが解決する問題と 設計哲学について説明します。 最初の課題はコンテキスト管理です。 長時間のセッションでは dynamic profilesにより トランスクリプトをトリミングや要約して コンテキストウィンドウ内に収めます。 2つ目の問題は境界の設定です。 複数のモデルを使用する場合 能力とコストを考慮して設計します。 Dynamic profilesはその選択肢を提供します。 この分野は週単位で変化しています。 今回導入するプリミティブは 柔軟に設計されており 今日の抽象化も将来の抽象化も 構築可能です。 その通りです! Dynamic profilesはコンテキスト エンジニアリングとモデル境界の定義を可能にし ほぼあらゆるアーキテクチャに 組み込めます。 そのような精神のもと 本日は新しいパッケージを発表します。 Foundation Models framework utilitiesです。 Utilitiesはオープンソースの Swiftパッケージで エージェント体験の構築に役立つ コンポーネントを含んでいます。 OSリリースの合間に更新され 新興パターンへのアクセスを提供します 実験的なパターンも含めすべて dynamic profilesで支えられています。 準備が整ったので アジェンダに進みましょう。 このビデオの前半では Oliverがdynamic profilesの仕組みを説明します。 後半では私が再登場し 高度なトピックを取り上げます オーケストレーションパターンに関連した内容です。 最後にパフォーマンスと精度の 考慮事項についても触れます。 それでは、Oliverにバトンタッチです! ありがとう、Erik。 LanguageModelプロトコルの導入と PrivateCloudComputeLanguageModelにより 選択できるモデルがこれまで以上に増えました。 DynamicProfileはモデルを切り替える 新しいAPIです LanguageModelSession内で利用できます。 最適な設定を選択する柔軟性を提供し 当面のタスクに対応します。 DynamicProfileは多くの有用な抽象化を 構築できる基盤です。 エージェントやスキルなどが含まれます。 今日は複数モデルの活用から始まり APIのツアーをご案内します。 トランスクリプトの考慮事項に入り セッションライフサイクルイベントで締めくくります。 例を見てみましょう。
「Origami」というクラフトアプリを 作っています。 折り紙とかぎ針編みの チュートリアルを作成できます。 ユーザーが画像をアップロードすると アプリが支援します 画像をインスピレーションに アイデアのブレインストーミングを行います。 ユーザーはアイデアの候補に フィードバックを提供でき 選択したコンセプトのチュートリアルが 生成されます。 チュートリアルを進めながら 作業中の写真をアップロードして テクニックについてアドバイスを受けられます。
アプリの各段階では共有コンテキストが 必要ですが それぞれ固有の優先事項があります。 多様なモデルのセットが役立つ場合があり 異なる指示や生成オプションを使用します。 これらの設定はエージェントで アプリの代わりに動作します。 特定の目標と能力セットを念頭に置いて 設定されます。 DynamicProfileを使用して 個別のProfilesを宣言できます。 これはLanguageModelSession内の 設定状態やエージェントを表します。
Profileは指示、ツール、修飾子で 構成されます モデル、temperature、samplingModeなどの 設定に使います。 クラフト体験用のDynamicProfileを 宣言することから始めましょう。 CraftOrchestratorという Observableクラスがあります。 アプリのさまざまなフェーズを追跡します。 まずブレインストーミングフェーズに 焦点を当てます。 さまざまなクラフトプロジェクトのアイデアを ユーザーに提示するために使用されます。 ここでは新しいprofileに いくつかの指示があります 目標を説明するものと タイトル生成ツールもあります。 折り紙は複雑なクラフトなので 追加の指示とツールも含めましょう ただしユーザーが折り紙プロジェクトに 取り組んでいる場合のみです。 OrigamiExpertはDynamicInstructionsという 新しい型を使用します。 DynamicInstructionsは関連する ツールと指示をグループ化できます コードベース全体で再利用できる 単一コンポーネントにまとめます。
OrigamiExpertは知識とツールを含み 折り紙についてモデルにプロンプトするたびに 再利用できます。 DynamicInstructionsはコンポーズ可能です OrigamiExpertを別のDynamicInstructions内に ネストすると 指示とツールが結合されます。 BrainstormFacilitatorを作成して profileの指示を保持しています。 新しい宣言を使ってブレインストーミング profileを整理できます。 ブレインストーミングにはクラフトの幅広い知識と 創造的思考が必要なため このprofileはPrivateCloudComputeLanguageModelを使用します。 Foundation Modelsで利用できる 新しいモデルです。
LouisのトークをぜひCheck outしてください Foundation ModelsのPCCについて 詳しく学ぶことができます。 temperatureを1に設定し モデルがより創造的な応答を 生成できるようにします。 DynamicProfilesを使って 最初のエージェントを定義しました。
次のprofileモード「planning」に進みましょう。 「planning」profileは手順の作成を担当します 合意されたクラフトプロジェクトのためです。 クラフトの深い知識が必要なので PCCLanguageModelを使用します。 reasoningLevelも設定します。 これはほとんどのサーバーモデルで 利用できる機能です。 これにより応答前に 問題を考える能力を制御します。 チュートリアル生成は複雑なため deepに設定します。
最後に「reviewing」フェーズが チュートリアルを進めるユーザーに アドバイスとガイダンスを提供します。 不要なサーバー呼び出しを減らすため SystemLanguageModelを使用します。 これでクラフティングのDynamicProfileの 定義が完了しました。
セッションでDynamicProfileを使用するには 新しいLanguageModelSession イニシャライザを使うだけです。 DynamicProfileのボディは 再評価されることに注意してください モデルがプロンプトされるたびに アプリが各モードを切り替えると LanguageModelSessionの ペルソナが変わります。 これはハットを被り替えたり エージェントを切り替えるようなものです。 ブレインストーミングから planningへ reviewingへと モードを変えるだけで切り替えられます。 DynamicProfileを使って異なるモデル間を ルーティングする方法を見てきました。
ただし各モデルには異なるコンテキスト サイズ制限がある点に注意が必要です。 クラフトの例ではPCCLanguageModelと SystemLanguageModelを切り替えています。 モデルを切り替える際は コンテキストサイズ内に収めるために 不要なエントリをトリミングが必要な場合があります。 ただしモデルのコンテキストを調整する 理由はそれだけではありません。 関連性のないエントリを削除して モデルのフォーカスを改善したり 既存のエントリから プライベート情報を削除したりして プライバシーレベルの低いモデルに 移行することもできます。 トランスクリプトはLanguageModelSessionが モデルのコンテキストを表したものです。 DynamicInstructionsはトランスクリプトを 変更する1つの方法を提供します。 より具体的には instructionsエントリの変更が可能です。 残りのエントリを更新するには 「history」と呼ばれる トランスクリプトへのウィンドウを使用します。 ツール呼び出しを削除することが historyをトリミングする簡単な方法の1つです。 実装方法を見てみましょう。
historyTransformをprofileに適用すると モデルにプロンプトする前に historyを変換できます。 これはリクエストに不要なエントリを フィルタリングする絶好の機会です。 リクエストに不必要なものを除外できます。 「reviewing」profileに 変換を適用することで オンデバイスモデルのコンテキストサイズ内に トランスクリプトを収めることができます。 変換はセッションのトランスクリプトを 永続的に変更しません。 代わりに、モデルへのプロンプト前に 適用されるローカル変換です。
そのため後で関連性が出てくる コンテキストが失われる心配はありません。 後で関連性を持つ可能性があります。 historyTransformには多くの処理があります。 カスタム修飾子を使って 変換の複雑さを隠す方法を説明します。 まず新しい型を宣言します DynamicProfileModifierに準拠する型で historyTransformを適用します。 次にextensionを実装して 再利用可能にします DynamicProfileへのextensionです。 コンテキスト削減が必要な 新しいProfileは 新しい修飾子を利用できるようになります。 新しいFoundation Modelsに多数の 便利な修飾子を用意しました framework utilitiesパッケージです。 ぜひご覧ください。 カスタム修飾子は再利用可能な 設定を構築する優れた方法です 宣言に対する設定の 再利用に役立ちます。
ただし変換だけが トランスクリプトに影響を与える方法ではありません。 より状態を持つ別のアプローチを見てみましょう。 セッション中の特定の時点で 以前のエントリを要約して 既存のトランスクリプトから コンテキストを回収する必要があります。 各モデルの応答後にこれを行うことで セッションのライフサイクルに 明確な境界を設けられます。 要約操作を実行する方法を見てみましょう 新しい修飾子セットを使って 各応答後に実行します。 ライフサイクル修飾子はprofileの 進行状況へのアクセスを提供します 命令型コードを実行する 機会を与えることで profile宣言内で直接実行できます。 これはセッション外部の状態の更新に役立ちます UIに進行状況を 反映させるような場合です。 内部状態の更新にも役立ちます クラフトprofileのモード変更や セッションのhistory変更などです。 onResponse修飾子を使って historyを変更しましょう 先ほど説明した 応答境界で行います。 もう1つの新しい概念も活用しています セッションプロパティです。 セッションプロパティを使うと 状態を定義できます 任意のToolやProfileから アクセス可能な状態です。 今使用したhistoryプロパティは フレームワークが提供する 組み込みプロパティです。 セッションのhistoryをキャプチャして 代替手段として使用できます historyTransformの代わりに トランスクリプトを更新します。 historyプロパティはデータを失う点に 注意してください 変更はセッション内の すべてのprofileに反映されます。 特定のprofileを対象とした ロスレス変換には historyTransformを推奨します。 historyに加えて 独自のセッションプロパティも作成できます。 onResponse呼び出し時に会話の要約を 保存する新しいプロパティを作成しましょう。 @SessionPropertyEntryマクロを使って プロパティを宣言できます SessionPropertyValuesの extensionの中で宣言します。 すべてのセッションプロパティはミュータブルで 初期値が必要です。 ここではsummaryを オプショナルな文字列として宣言しています。 各Profileはsummaryの値を 読み取ることができます 宣言したセッションプロパティにアクセスして 読み取ります。 profileの指示にsummaryを含めて コンテキストを確保します 削除されたトランスクリプト エントリに関するコンテキストです。
どのprofileもプロパティに書き込むことができ 変更はセッション全体で見えます。
では会話の要約を 作成してみましょう。 ライフサイクル修飾子を使ってセッションの 特定時点でコードを実行します。 historyプロパティを使ってすべての profileのセッションhistoryを更新します。 カスタムセッションプロパティを使って すべてのセッションコンポーネントが 共有する状態を保存します。 それでは、Erikに戻して エージェントオーケストレーションに ついて説明してもらいます。 ありがとう、Oliver!
profilesをエージェント構築に どう活用できるかについて 直感が身に付いてきたと思います。 エージェント体験をオーケストレーションする 2つの一般的なパターンを見てみましょう。 これらのパターンを「baton-pass」と 「phone-a-friend」と呼んでいます。
baton-passはコラボレーションで phone-a-friendはコンサルテーションです。 まずbaton-passを見てみましょう。
このパターンでは 2つ以上のprofileがあり それぞれが異なるモデルを 活用することが多いです。 どのprofileがアクティブかを 制御する変数も必要です。 最後に、各profileにモデルが その変数を設定できるツールを与えます。 これらの要素が合わさって baton-passパターンを形成します。
ブレインストーミング中に 鶴の折り方を聞いた場合 brainstorm profileがツールを呼び出して tutorial profileにバトンを渡します。 ツールの出力はハンドオフ成功を示し tutorial profileが 最終的な回答を生成します。 baton-passパターンの 最も重要な特徴は 完全なトランスクリプト履歴が 両方のprofileに見えることと バトンを受け取ったprofileが ゴールまで運んで 最終的な応答を提供できることです。 これらの特徴は次のパターンとは 対照的です phone-a-friendです。
phone-a-friendパターンでも ツール呼び出しを使用します。 主な違いは変数を切り替える代わりに ツールが短命なセッションを 生成することです。 子供向けの楽しいプロジェクトを 尋ねた場合 モデルはプロジェクトの タイトルが必要だと判断し phone-a-friendツールを呼び出して title profileに相談します。
phone-a-friendツールは独立した トランスクリプトを持つ新しいセッションを作成して プロンプトし、応答を ツールの出力として返します。
子セッションは消え 親セッションが 最終的な応答を生成します。 phone-a-friendパターンの 最も重要な特徴は 各profileのトランスクリプトが 分離されていることと 親profileが常に 最終的な回答を提供することです。 baton-passとphone-a-friendは 便利なツールですが 他にも選択肢があります。
例えばFoundation Models framework utilitiesパッケージには Skills型があり、手続き的なコンテキスト 読み込みの一般的なパターンとして 知られているものです。
オーケストレーションにツールを 活用する多くの方法を把握したので 新しいコントロールを見てみましょう ツール呼び出しのタイミングを 制御するためのものです ツール呼び出しモードです。
ツール呼び出しモードには 3つのオプションがあります allowed、disallowed、requiredです。 デフォルト値は「allowed」で これが既存の動作です。 モデルはツール呼び出しを生成するか 直接応答する可能性があります。 ツールが必要かどうかわからない場合に 使用するオプションで 最も一般的なケースです。 「disallowed」はモデルが ツールを呼び出さないようにします。 ユーザーがアプリの特定の部分に 移動した場合に役立ちます セッションのツールが 無関係であることが分かっている場合です。 最後に「required」はモデルが ツールのみを呼び出せることを意味します。 これはすべてのアクションを ツール呼び出しで表すエージェントシステムで 特に役立ちます。
profilesを使用している場合は 修飾子でツール呼び出しモードを指定できます。 profileを使用していない場合は ツール呼び出しモードを respond(to:)呼び出し時に GenerationOptionsで設定できます。
最も重要なことを 覚えておいてください。 ツール呼び出しがrequiredの場合 モデルは実質的にwhileループ状態です 何らかの終了条件を確保するのは デベロッパの責任です。 良い選択肢の1つは 変数でツール呼び出しモードを条件付けることです。 ここではモデルがデータベースツールを 呼び出すまでツール呼び出しを要求しています。
2つ目のより強制的な方法は エラーをスローする 最終回答ツールを モデルに装備させることです。 エラーをスローすると ツール呼び出しループが中断され 制御フローが 即座に返ってきます。
デフォルトでは、ツールからエラーをスローするか 応答をキャンセルすると セッションのトランスクリプトが 前の状態にロールバックされます。 応答の途中でキャンセルして 再開したい高度なユースケースでは エラー後もトランスクリプトの状態を 保持する必要があります。 これを可能にする 新しいAPIを追加しました。 profilesを使用している場合は 「transcriptErrorHandlingPolicy」を 修飾子で設定できます。 profileを使用していない場合は セッションに直接設定できます。
2つのオプションは 「.revertTranscript」と「.preserveTranscript」です。
「.preserveTranscript」を使用する場合は トランスクリプトを適切な状態に戻す責任があります セッションを引き続き使用する場合です。
それを容易にするためにセッションの 「transcript」プロパティがミュータブルになりました。 ただしトランスクリプトを変更できるのは セッションの 「isResponding」プロパティが falseの場合のみです。 応答中にトランスクリプトを変更しよう とするとプログラマーエラーになります。
新しいAPIを見てきたところで 次の話題は トランスクリプトの変更が パフォーマンスと精度に与える影響です。
KVキャッシュ(キーバリューキャッシュ)は 大規模言語モデルの重要な最適化機構です 大規模言語モデルの トランスクリプトの変更で 無効化される場合があります。
一般的にトランスクリプトへの追記は KVキャッシュを保持し 最初のトークンまでの時間を最小化します。
エントリを削除して履歴を 書き換える場合や 付属のツールを変更したり 指示を更新したりすると 通常はキャッシュの無効化が発生して レイテンシが増加します。 昨年はこの話題を取り上げませんでした 意図的にLanguageModelSession APIを追記専用に 設計したためです。 デフォルトで最適な使用が 確保されていました。 ただし今年はいわば 補助輪を外しました。
異なるモデルはキャッシュ動作が 異なることを理解することが重要で 確認するには 計測するしかありません。
最良の方法はXcodeの Foundation Models Instrumentの強化版を使うことです。 Instrumentsでのキャッシュ無効化の 検出について詳しくは デバッグとプロファイリングの ビデオをご確認ください。
パフォーマンスへの影響に加えて 履歴を書き換える際に 注意が必要なのは精度です モデルが混乱する 可能性があります。
モデルに楽しい折り紙プロジェクト名を 考えるよう依頼したセッションを想定します 折り紙プロジェクト名についてです。 次にセッションにgenerate title ツールを追加して さらにアイデアを促します。 次に何が起こると思いますか?
うまくいけばモデルが 望み通りにツールを使用します。
ただしモデルが以前ツールなしで タイトルを生成したことに気づいて ツールなしで 同じことをすべきだと 思ってしまう可能性もあります。 それは望んでいません。履歴の変更が モデルを混乱させています。
このような微妙なトランスクリプトの 変更を始める場合は Evaluationsフレームワークを使って eval setを作成し コンテキストエンジニアリング戦略の 効果を定量化することがより重要になります。 データドリブンな最適化こそが 確信を持てる唯一の方法です。 evaluationsフレームワークに関する すべてのビデオを強くお勧めします。 これでパフォーマンスと精度の セクションは終わりです。 情報量が多かったですね! まとめをOliverにお願いしましょうか? もちろんです。 dynamic profilesでモデルの動作を制御し セッションのトランスクリプトの管理方法を紹介しました セッションのトランスクリプトをです。 phone-a-friendやbaton-passなどの パターンを説明しました ツール呼び出しモード、手動トランスクリプト管理、KVキャッシュについても触れました。 Foundation Models framework utilitiesに 皆さんも同じように ワクワクしていただけると嬉しいです!
次はサンプルアプリで 試してみてください。 または刷新された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.