
-
Foundation Modelフレームワークの詳細
Foundation Modelフレームワークで開発のレベルを引き上げましょう。ガイド付き生成の仕組みを理解し、ガイド、正規表現、生成スキーマを使用してカスタムの構造化された応答を取得する方法を学べます。外部情報へのアクセスとアクションの実行をモデルが自発的に行うためのツール呼び出しを解説し、パーソナライズされた体験を実現する方法も紹介します。 このビデオの内容を十分理解できるよう、まず「Meet the Foundation Models framework」を視聴することをおすすめします。
関連する章
リソース
- Generate dynamic game content with guided generation and tools
- Human Interface Guidelines: Generative AI
関連ビデオ
WWDC25
-
このビデオを検索
こんにちは Louisです 今日はFoundation Modelフレームワーク を 最大限に活用する方法を見ていきます
ご存知かもしれませんが Foundation Modelフレームワークを 使用すると 便利なSwift APIによって オンデバイスの大規模言語モデルを 直接利用できます このフレームワークはmacOS、 iPadOS、iOS、visionOSで利用できます また オンデバイスで動作するため プロジェクトで使用するのも 簡単なインポートで完了します このビデオで取り上げるのは Foundation Modelでのセッションの仕組み Generableを使用して 構造化された出力を取得する方法 実行時に定義される動的スキーマを使用して 構造化された出力を取得する方法 ツール呼び出しを使用してモデル呼び出しを カスタム関数に組み込む方法です まずはシンプルにセッションを使用して テキストを生成してみましょう
今 コーヒーショップを舞台にした ピクセルアートゲームを制作中なのですが Foundation Modelを使用して ゲーム内の会話やコンテンツを生成すれば より生き生きとしたゲームに なるのではないかと考えています
モデルにプロンプトを送り プレイヤーの質問に応答させることで バリスタのユニークなセリフを生成できます これを実現するために カスタムの指示を伴う LanguageModelSessionを作成します これにより モデルに このセッションの目的を伝え プロンプトではユーザーの 入力を受け取ることができます これだけで かなり楽しくて新しい ゲーム要素を追加できます 「ここで働いてどれくらいですか?」 とバリスタさんに聞いて その返答を確認してみましょう
これは完全にオンデバイスで 生成されました 大成功です では実際の仕組みは どうなっているのでしょうか Foundation Modelによる テキスト生成の仕組みを理解し 重要なポイントを確認しましょう セッションでrespond(to:)を呼び出すと まず セッションの指示とプロンプト (この場合はユーザーの入力)を取得し そのテキストをトークンに変換します トークンは小さな部分文字列であり 単語の場合もありますが 通常は数文字程度です 大規模言語モデルは 一連のトークンを 入力として受け取り トークンの新しいシーケンスを 出力として生成します Foundation Modelが処理する 具体的なトークンについて 心配する必要はありません APIがそれを適切に抽象化するため 気にせず利用できます ただし トークンにはコストがかかることを 理解しておく必要があります 指示とプロンプトの各トークンによって レイテンシが増加します モデルが応答トークンの 生成を開始するには 最初にすべての入力トークンを 処理する必要があります また トークンの生成には 計算コストもかかるため 出力する応答が長いほど 生成に時間がかかります
LanguageModelSessionは ステートフルです 各respond(to:)呼び出しは トランスクリプトに記録されます
トランスクリプトにはセッションに対する すべてのプロンプトと応答が含まれます
これは デバッグやUIでの表示に役立ちます
ただし セッションのサイズには 上限が設定されています 大量のリクエストを行ったり 大きなプロンプトを設定したり 大規模な出力を取得する場合 コンテキストの制限に達することがあります
セッションが利用可能なコンテキストサイズ を超えるとエラーがスローされるため 適切に捕捉する準備をしておく 必要があります ゲーム内でキャラクターと話していて エラーが発生すると 会話が途切れてしまい残念ですね キャラクターのことをもっと知りたかったのに 幸いなことに このエラーから 回復する方法があります
exceededContextWindowSize エラーを捕捉することです
そうすれば 履歴のない 新しいセッションを始めることができます ただし このゲームでは キャラクターが突然会話の内容を すべて忘れてしまうことになります
現在のセッションから トランスクリプトの一部を選択して 新しいセッションに引き継ぐこともできます
セッションのトランスクリプトから エントリを取得し それを新しいエントリの配列に まとめることもできます
このゲームのダイアログでは セッションのトランスクリプトの 最初のエントリ つまり指示を 取得できます また 最後のエントリとして 最後に成功した応答も取得できます それを新しいセッションに渡すことで キャラクターはしばらくの間 会話を続けられます ただし セッションのトランスクリプトには 最初のエントリとして 初期の指示が含まれています ゲームキャラクターの トランスクリプトを引き継ぐ場合 必ずその指示を含めることが重要です
トランスクリプトから特定の 関連部分を選んで引き継ぐことで シンプルで効果的な 解決策になる可能性があります ただし 状況によっては それほど単純ではありません より多くのエントリを含む トランスクリプトを考えてみましょう 必ず最初に指示を引き継ぐことから 始める必要があります トランスクリプトに 関連性の高い エントリが多数含まれる可能性があるため このようなケースでは トランスクリプトを 要約することも1つの方法です
外部ライブラリを利用して 実行することもできますし 場合によっては トランスクリプトの 一部を要約するのに Foundation Modelを 使用することもできます
これが セッションの トランスクリプトでできる処理です 次に 応答が実際にどのように 生成されるかを簡単に見てみましょう このゲームでは バリスタに歩み寄ると プレイヤーはどんな質問でもできます しかし 新たに2つのゲームを始めて それぞれで まったく同じ質問をしても おそらく返ってくる答えは異なるでしょう どんな仕組みでしょうか そこで サンプリングの出番です
モデルが出力を生成するときは トークンを1つずつ生成します モデルは 各トークンの出現確率の 分布を作成することで これを処理します デフォルトでは Foundation Modelは 一定の確率の範囲内でトークンを選択します 時には「ああ」と言って始まることもあれば 最初のトークンとして「ええと」を 選択する場合もあります この処理は生成されるすべての トークンに対して発生します このトークンの選択が サンプリングと呼ばれるものです デフォルトの動作は ランダムサンプリングです 様々な出力が得られることは ゲームなどのユースケースに最適です 一方で 決定論的な出力が 必要な場合もあります 例えば 再現性が求められる デモを作成する場合などです GenerationOptions APIを使用すると サンプリング手法を制御できます これを「greedy」に設定することで 決定論的な出力が得られます このように設定されていれば 同じプロンプトには同じ出力が返されます ただし セッションの状態も 同じであることが前提です さらに この条件が成り立つのは オンデバイスモデルのバージョンが 同じである場合のみです OSアップデートの一環として モデルが更新されると 同じプロンプトから異なる出力が 生成される場合があります サンプリングの設定を 「greedy」にしてもです ランダムサンプリングの「temperature」 で出力を調整することもできます 例えば temperatureを 0.5に設定すると 出力の変化は小さくなります これをより高い値に設定すると 同じプロンプトに対して 大きく異なる出力が生成されます また プロンプトで ユーザー入力を受け取る際に 未対応の言語である可能性もあります
このような場合は 専用の unsupportedLanguageOrLocaleエラーを 捕捉することで処理できます これは UIにカスタムメッセージを 表示するのに適した方法です また モデルが特定の言語に対応しているか どうかを確認するAPIもあります 例えば ユーザーの現在の言語に 対応しているかどうかを確認し 対応していない場合は 注意書きを表示できます 以上がセッションに関する概要です プロンプトを入力すると 履歴が トランスクリプトに保存されます 必要に応じて サンプリングパラメータを設定して セッションの出力の ランダム性を制御できます もっと凝ったものにしましょう プレイヤーが移動するときにNPC (非操作キャラクター)を生成できます ここでもFoundation Modelを使用します ただし 今回は より複雑な出力が必要です 単なるテキストではなくNPCの名前や コーヒーの注文も生成したいと思います こんなときにGenerableが役立ちます 大規模言語モデルから構造化された出力を 取得するのは困難な場合があります 必要なフィールドを指定して プロンプトを作成し それを抽出するための解析コードを 用意することもできます ただし この方法は維持が難しく 非常に脆弱です 常に有効なキーが返されるとは限らないため メソッド全体が失敗する可能性があります 幸い Foundation Modelには Generableと呼ばれる優れたAPIがあります 構造体に@Generableマクロを 適用できます Generableとは何でしょうか? 実在する単語でしょうか? はい 存在します
Generableは モデルが構造化データを 簡単に生成できるようにする仕組みで Swiftの型を使用します このマクロはコンパイル時に スキーマを生成し モデルはそれを使用して 期待される構造を生成します このマクロはイニシャライザも生成します これは セッションにリクエストを 実行するときに自動的に呼び出されます
それでは この構造体の インスタンスを作成してみましょう 前と同じように セッションで 応答メソッドを呼び出します ただし 今回はgenerating引数を渡して 生成する型をモデルに指示します Foundation Modelは Generable型の詳細を プロンプトに自動的に組み込みます その情報はモデルのトレーニングに 使用された特定の形式に沿っています Generable型に含まれるフィールドを モデルに伝える必要はありません これにより ゲームでは 生成された 魅力的なNPCの登場が実現します
Generableの機能は 見た目以上に強力です 低レベルの処理では 制約付きデコーディングが使用されます この手法により モデルは 特定のスキーマに沿った テキストを生成できます 先ほどのマクロが生成するスキーマを 思い出してください 前に説明したように LLMはトークンを生成し それらが後でテキストに変換されます Generableを使用すると そのテキストは型安全な方法で 自動的に解析されます トークンはループ処理で生成され これは 通常 デコードループと呼ばれます 制約付きデコーディングがなければ モデルは 誤って無効なフィールド名を 生成する可能性があります 「name」ではなく 「firstName」などです その結果 NPC型への解析に失敗します
しかし 制約付きデコーディングでは モデルがこのような構造的なミスを 犯すのを防ぐことができます 生成される各トークンについて モデルの語彙にあるすべてのトークンの 分布が存在します 制約付きデコーディングは 無効な トークンを除外することで機能します 任意のトークンを選択するのではなく モデルは スキーマに従って 有効なトークンのみを選択できます
モデルの出力を手動で解析する 必要がないということです つまり 本当に大切なことに 時間を使うことができます コーヒーショップでバーチャルゲストと 会話を楽しんだりできますね Generableは オンデバイスのLLMから 出力を取得するのに最適な方法です さらに多くのこともできます 構造体だけでなく 列挙型でも使用できるのです それを活用して私たちの出会いを よりダイナミックにしましょう ここでは 2つのcaseを定義した Encounter列挙型を追加しました この列挙型のcaseには関連する値を 格納することもできます この仕組みを使って コーヒーの注文を生成するか または 店長と話したがっている キャラクターを作成してみましょう
それでは このゲームで何に 遭遇するか確認してみましょう
あー 誰かが本当にコーヒーを 飲みたがっていますね
当然ですが すべてのお客様が簡単に 対応できるとは限りません そこで NPCにレベルを導入して これをレベルアップしてみましょう Generableは Intを含む一般的なSwiftの 型のほとんどを直接サポートしています それでは levelプロパティを追加しましょう ただし 整数を生成したいわけでは ありません レベルを特定の範囲に限定したい場合は Guideを使用してこれを指定できます プロパティでGuideマクロを使用して 範囲を渡すことができます ここでも モデルは 制約付きデコーディングを使用して 値がこの範囲内になるよう保証します
この機会に NPCに属性の配列も 追加しましょう
再びGuideを使用できますが 今回は NPCのこの配列に正確に 3つの属性が必要であることを指定します 覚えておいてください Generable型のプロパティは ソースコードで宣言されている 順序で生成されます ここでは 最初にnameが生成され 次にlevel その次にattributes 最後にencounterです
この順序は重要な場合があります プロパティの値が別のプロパティの 影響を受けることを 想定している場合などです プロパティごとのストリーム処理を 行うこともできるので 完全な出力が生成されるまで待たずに 処理する場合に便利です これでゲームはかなり楽しくなりました 友人と共有する準備がほぼ整いました しかし NPCの名前が 考えていたものと 少し違っていることに気づきました 私は名前と姓の両方を使いたいのです
これにはガイドを使用できます ただし 今回は自然言語の 説明を指定します
名前は「フルネーム」にするように 指定できます これは事実上 別のプロンプト入力の方法です プロンプトで個々のプロパティを 記述する代わりに Generable型で直接指定できます これにより モデルは これらの記述の 関連性をより強く認識できます ゲーム内を歩き回ってみると 新しい名前が 実際に使用されているのを確認できます ここで 様々な型に適用できる すべてのガイドの概要を示します
intなどの一般的な数値型では 最小値、最大値、範囲を指定できます また 配列を使用すると 個数を制御したり 配列の要素型でガイドを指定したりできます
文字列の場合 anyOfを使用して モデルに配列から選択させたり 正規表現パターンに制約することもできます
正規表現パターンのガイドは 特に強力です テキストとの照合に正規表現を 使用するのはおなじみかもしれません しかし Foundation Modelでは 正規表現パターンを使用して 生成する文字列の構造を定義できます 例えば 名前を特定の接頭辞の セットに制限できます また 正規表現ビルダーの構文を 使用することもできます
これで改めて正規表現への興味が わいたなら 時代を越えて名作となった 数年前の「Meet Swift Regex」を ご参照ください 要約すると Generableは 構造体と列挙型に適用できるマクロで モデルから構造化された出力を取得する 信頼性の高い方法を提供します 出力を解析する必要はなく さらに具体的な出力を取得するには プロパティにガイドを適用できます つまり Generableは コンパイル時に 構造が確定している場合に最適です このマクロはスキーマを自動生成し 指定した型のインスタンスを 出力として返します しかし 実行時に初めて構造が 判明するという場合もあります そこで役立つのが動的スキーマです ゲームにレベルクリエーターを追加して プレイヤーがゲーム内を歩き回る際に 遭遇するエンティティを 動的に定義できるようにしています 例えば プレイヤーは 謎解きの構造を作成できます その謎解きには質問と 多肢選択式の答えがあります コンパイル時にこの構造がわかっていれば Generable構造体を 定義するだけで済みます しかし レベルクリエーターでは プレイヤーが 思いつくあらゆる構造を作成できます
DynamicGenerationSchemaを使用 することで 実行時にスキーマを作成できます コンパイル時に定義される構造体と同様に 動的スキーマにもプロパティのリストがあります レベルクリエーターを追加して プレイヤーの入力を受け取ることができます
各プロパティには 名前と その型を定義する独自のスキーマがあります スキーマはあらゆるGenerable型に 使用でき これには組み込み型も含まれます Stringなどです
動的スキーマには配列を含めることができ ここで 配列の要素のスキーマを指定します 重要なのは 動的スキーマは他の動的 スキーマへの参照を持つことができる点です そのためここでは 配列は実行時に定義 される カスタムスキーマを参照できます
ユーザーの入力から 2つのプロパティを 持つ 謎解きのスキーマを作成できます 1つ目はquestionで これは文字列プロパティです 次に Answerというカスタム型の 配列プロパティです では 答えを作成してみましょう これには文字列とブール値の プロパティがあります 謎解きのanswersプロパティは その名前で解答スキーマを参照しています 次に DynamicGenerationSchemaの インスタンスを作成できます 各動的スキーマは独立しています つまり 謎解きの動的スキーマは 実際には解答の動的スキーマを 含んでいません 推論を開始する前に まず 動的スキーマを検証済み スキーマに変換する必要があります このとき 動的スキーマに不整合があると エラーが発生します 型参照が存在しないなどです
検証済みのスキーマが得られたら通常どおり セッションのプロンプトを入力できます ただし 今回は出力の型が GeneratedContentインスタンスです これは動的な値を保持しています これは 動的スキーマのプロパティ名を 使用して照会できます ここでも Foundation Modelは ガイド付き生成を使用して 出力がスキーマと一致することを確認します 想定外のフィールドが 生成されることはありません 動的ではあっても 出力を手動で解析することを 心配する必要はありません
これで プレイヤーがNPCに遭遇すると モデルは この動的コンテンツを 生成できます これを動的UIで表示します 私たちが遭遇したものを確認してみましょう 私は暗くも明るくも 苦くも甘くもなります 人を目覚めさせ 温かさをもたらします 私は何でしょう? コーヒーかホットチョコレート 答えはコーヒーだと思います 正解です プレイヤーは様々な楽しいレベルを 作れるのでとても楽しいと思います 要約すると Generableマクロを使用すれば コンパイル時に定義されるSwiftの型から 構造化された出力を簡単に生成できます 内部では Foundation Modelが スキーマを管理し GeneratedContentを独自の型の インスタンスに変換します 動的スキーマは非常によく似た仕組み ですが さらに細かい制御ができます スキーマは完全に実行時に制御でき GeneratedContentに 直接アクセスすることができます 次に ツール呼び出しを見てみましょう これにより モデルがユーザー独自の関数を 呼び出せるようになります 私はDLCの作成を考えています ダウンロードコンテンツによって ゲームに個人的な要素を追加するためです ツール呼び出しを使用すると モデルは自律的に情報を取得できます プレイヤーの連絡先とカレンダーを 統合すれば本当に楽しくなると思います 通常 サーバベースのモデルでは そのようなことはしません プレイヤーは ゲームが個人データを アップロードすることを喜ばないでしょう しかし Foundation Modelでは すべてがオンデバイスで処理されるため プライバシーを保護しながら これを実現できます
Toolプロトコルを使用すれば ツールの定義は非常に簡単です まず 名前と説明を指定します これは APIによって自動的に プロンプトに挿入され これを基に モデルは適切なタイミングと 頻度でツールを呼び出します
ツール名は短く 英語のテキストとして 読みやすいものにすることをお勧めします 略語を避け 説明は簡潔にまとめ 実装の詳細を 含めないでください なぜなら これらの文字列は プロンプトに そのまま入力されるからです 文字列が長くなるほどトークンが多くなり レイテンシーが増加する可能性があります 代わりに 名前に動詞を 含めることを検討してください findContactのようにします また 説明は1文程度にしてください いつものように 様々なバリエーションを 試して自分のツールに最適なものを 確認することが重要です
次に ツールの入力を定義します このツールで ミレニアル世代など 特定の 世代と連絡を取りたいと考えています モデルはゲームの状態に基づいて 面白いケースを選ぶことができますが Arguments構造体を追加して それを Generableにすることができます モデルがこのツールを呼び出すことを 決定すると入力引数が生成されます Generableを使用することで ツールが常に 有効な入力引数を取得することが保証されます したがって モデルが異なる世代 例えば ゲームで対象外の アルファ世代などは生成されません
次に呼び出し関数を実装します モデルは ツールを呼び出すことを 決定したときにこの関数を呼び出します この例では 次にContacts APIを 呼び出します そのクエリに対して連絡先の名前を返します
ツールを使用するには それを セッションイニシャライザに渡します その後 モデルは追加の情報が 必要なときにツールを呼び出します これは 単に自分で連絡先を 取得するよりも強力です モデルは特定のNPCに 必要な場合にのみツールを呼び出し また ゲームの状態に基づいて おもしろい入力引数を選択できます NPCの年齢世代などです
これは 通常のContacts APIを 使用しているので よくご存知かもしれません このツールは 最初に呼び出されたときに プレイヤーに通常の許可を求めます プレイヤーが連絡先への アクセスを許可しなくても Foundation Modelは以前と 同じようにコンテンツを生成できますが アクセスが許可されるとより個人的な 内容にすることができます
別のNPCに遭遇するまで ゲーム内を少し歩き回ってみましょう 今回は 連絡先から名前が取得されます こんにちは Naomy Naomyが何を言うか聞いてみましょう
君がコーヒー好きだったとはね LanguageModelSessionはツールの インスタンスを受け取ります これは ツールのライフサイクルを 制御することを意味します このツールのインスタンスは セッション全体で変わりません この例では FindContactsToolでランダムな キャラクターを取得しているだけなので 時々 同じ連絡先を 取得する可能性があります 現在 このゲームでは Naomyが複数います それは正しくありません たった1人しかいないのですから これを修正するために ゲームですでに 使用された連絡先を追跡できます FindContactToolに状態を追加できます そのために まずFindContactToolを クラスに変換します これにより 呼び出しメソッドから 状態を変更できます これで 選択された連絡先を追跡でき このcallメソッドでは 同じものは再度選択しません
NPCの名前は私の 連絡先に基づいています しかし NPCとの会話はまだ不自然です 最後にもう1つツールを紹介しましょう 今度はカレンダーへのアクセス用ツールです
このツールでは 連絡先の名前を渡す際に ゲーム内で続行中の ダイアログから取得します モデルがこのツールを呼び出すときに この連絡先とのイベントを取得するための 日付(年 月 日)を生成します このツールをセッション内で NPCとのダイアログに渡します
そこで 友人のNaomyのNPCに 「What’s going on?」と尋ねると NPCは私たちが一緒に計画している 実際のイベントを答えることができます
うわー 本物のNaomyと 会話しているみたいです
ツール呼び出しの仕組みを 詳しく見てみましょう セッションの冒頭で ツールを渡すことから始めます 指示も一緒に渡します この例では 今日の日付などの 情報を含めます その後 ユーザーがセッションにプロンプトを 入力すると モデルはテキストを分析します この例では モデルはプロンプトが イベントを求めていることを理解しており カレンダーツールの呼び出しは妥当です
このツールを呼び出すために モデルは最初に入力引数を生成します この場合 モデルはイベントを 取得する日付を生成する必要があります モデルは指示やプロンプトからの 情報を関連付けて それに基づいてツールの引数を 適切に補完することができます つまり この例では tomorrowの意味を推論する際に 指示にある今日の日付が使用されます ツールの入力が生成されると callメソッドが呼び出されます いよいよ見せ場です ツールは 必要に応じてあらゆる処理ができます ただし セッションはツールの 処理結果が返されるまで待機し それまで他の出力を生成しません
ツールの出力は その後 トランスクリプトに記録されます これはモデルからの出力と同様です また ツールの出力に基づいて モデルはプロンプトに対する 応答を生成できます ツールは 1つのリクエストに対して 複数回呼び出される可能性があります そのような場合 ツールは 並列に呼び出されます ツールのcallメソッドからデータに アクセスするときは これに注意してください とても楽しかったですね このゲームは ランダムなコンテンツの生成に 私の個人的な連絡先と カレンダーの情報を利用しています 私のデータがデバイスから 外部に送信されることはありません 要約すると ツール呼び出しによって モデルはリクエスト中に外部データに アクセスするコードを実行できます これは 連絡先などの 個人情報である場合もあれば Web上のソースからの 外部データである場合もあります ツールは 特定のリクエスト内で複数回 呼び出される可能性があります モデルは そのコンテキストに 基づいてこれを決定します ツールは並列で呼び出すこともでき 状態を格納することもできます 沢山ありますね とりあえずコーヒーでも飲んでから 次に進みましょう 詳細を学ぶには プロンプトエンジニアリングに関する 専用のビデオを確認してください 設計や安全性のヒントも含まれています また 本物のNaomyに会いたい場合は コード解説付きのビデオを確認してください 私のように皆さんもFoundation Modelで 楽しんでいただければ幸いです ありがとうございました
-
-
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 { ... } }
-