ハイライト
Xcode 27 の Foundation Models Instrument は、LLM エージェント App の prompt、tool call、instruction 切り替え、token 消費をすべてタイムライン上に可視化します。これにより開発者は、静かな失敗、指示の不一致、性能ボトルネックを直接特定できます。
主要内容
LLM 開発の 3 つの壁
(01:58)
従来のコードでは、入力 A は必ず出力 B になります。しかし Foundation Models framework でエージェント App を構築すると、開発者は従来開発にはなかった 3 つの課題に向き合います。
1 つ目は確率的な出力です。同じ prompt を LLM に 2 回送ると、まったく違う回答が返ることがあります。これは標準的な単体テストが通用しにくいことを意味します。出力が固定文字列と等しい、と断言することはできず、回答の品質や意図を評価する必要があります。
2 つ目はモデル間通信です。複雑な機能では複数のモデルが協力することがよくあります。たとえばレシピ App では、あるモデルが写真中の食材を識別し、別のモデルがその識別結果からレシピを生成します。モデル間でデータが流れる途中のどこかで失敗すると、全体の流れが壊れます。
3 つ目は可観測性です。マルチモデルのパイプラインでどこかが壊れたとき、何が原因か判断するのは難しいものです。各ステップの入力、モデルの判断過程、そして出力結果を見る必要があります。
(03:08)
LLM App の基本的な流れは単純です。ユーザーが prompt を送り、モデルが推論し、回答を返します。しかし多くの場面では tool call ループが必要です。ユーザーが prompt を送り、モデルが推論してツールを呼び出し、ツールが動作を実行し、モデルが結果を受け取って最終回答を生成します。このループは繰り返し発生することがあります。
各ステップは遅延を増やし、各ステップが新しい故障点になります。このループを理解することが、Foundation Models Instrument のすべてのデータを読む前提です。
実際のデバッグ事例
(04:02)
登壇者は手芸日記 App を開発しています。その中に、ユーザーとモデルが会話しながら創作アイデアを詰め、決定後に App が詳細なチュートリアルを自動生成する、インタラクティブなブレインストーミング機能があります。この機能は 2 種類の instructions を使います。1 つはブレインストーミング用、もう 1 つはチュートリアル生成用です。ブレインストーミング用の instructions には GenerateCraftIdeaTool と SwitchToTutorialModeTool の 2 つのツールが含まれます。
登壇者が App を実行すると、モデルはいくつかの手芸アイデアを出し、「紙の蝶」が選ばれました。しかしその後、モデルはチュートリアルモードに切り替わらず、さらにアイデアを生成し続けます。機能はそこで詰まってしまいました。
(05:02)
Xcode を開き、Product > Profile を選び、テンプレート選択画面で Foundation Models template を選択して Record をクリックします。Instrument はデバイス上の prompt と response データを捕捉します。これらのデータには機密情報が含まれる可能性があります。本番環境ではデフォルトで無効で、trace 中だけ有効になるため、trace ファイルは慎重に扱う必要があります。
タイムライン上の 6 つのレーン
(06:36)
Foundation Models Instrument のタイムラインには 6 つの lane があります。
- Instructions lane:特定の instructions と tools が有効だった時間を表示する。1 セットの instructions が複数の request をまたぐこともある。
- Model Inference lane:黄色のバーは入力 prompt の処理時間を示し、オレンジ色のバーは response 生成時間を示す。
- 残りの lane は session 構造と遅延の概要を示す。
タイムラインの下には tree detail view があり、1 回の recording 内のすべてのログを階層構造で整理します。session -> request -> model inference -> instructions -> prompt -> response という形です。
(06:59)
Instructions lane を見ると、session 全体で 1 セットの instructions しか有効になっていません。しかし機能設計では 2 セット、つまりブレインストーミングとチュートリアル生成が必要です。これは handoff の部分に問題があることを示しています。
tree view を展開すると、セッション 1 には 2 つの request があります。最初の request は「手芸アイデアを 3 つ生成してください」という prompt によって発生し、2 回の model inference といくつかの tool call を含みます。model inference ノードをクリックすると、右側の inspector に instructions、prompt、response の要約が表示されます。
(08:54)
Instructions ノードを選ぶと、inspector はこの instructions が 1 つの tool にしか結び付いていないことを示します。prompt には switchToTutorialMode が登場しますが、toolset にはその tool が設定されていません。これがないため、App はブレインストーミングモードからチュートリアルモードに切り替えられず、ユーザーはループに閉じ込められます。
さらに厄介なのは、これが静かな失敗であることです。モデルは入力を受け続け、tool を呼び続け、エラーは一切投げられません。Instruments がなければ、この種の bug を見つけるのはほぼ不可能です。
修正と検証
(09:38)
Xcode に戻り、BrainstormDynamicInstructions の定義を確認します。prompt には SwitchToTutorialMode が登場しますが、toolset には GenerateCraftIdeasTool しか登録されていません。SwitchToTutorialModeTool を toolset に追加し、再ビルドして、もう一度 Instruments で録画します。
App を再実行し、「ネックレス」を選ぶと、UI は正常にチュートリアルモードへ切り替わり、モデルは完全なチュートリアルを生成します。
(10:37)
Instruments に戻ると、Instructions lane には 2 つの独立した instructions が表示されています。先にブレインストーミング指示、次にチュートリアル生成指示があり、機能設計と完全に一致しています。
tree view では、最初の instructions が generateCraftIdea と switchToTutorialMode の 2 つの tool を含むようになっています。2 つ目の request の 2 回目の model inference が switchToTutorialMode tool call を起動し、選択された craft を引数として渡しています。次の request は正しくチュートリアル生成 instructions に切り替わり、選択された craft をコンテキストとして渡しています。
3 つの性能指標
(12:07)
Instruments の model inference ノードには、3 つの重要な性能指標が表示されます。
- Time to First Token(TTFT):prompt を送信してからモデルが最初の token を生成し始めるまでの時間。TTFT が高いと、ユーザーは空白画面を見つめることになる。prompt を短くすることで下げられる。
- Tokens per Second(TPS):response 全体の生成速度。異なる prompt 構成のベンチマークや、変更後の性能回帰検出に使う。
- Total Latency:リクエスト送信から完全な response を受け取るまでの総時間。ユーザーが最も直接感じる数字。streaming を使って早めに部分結果を表示すると、体感遅延を下げられる。
tree view の info 列は、異常なノードを自動でマークします。エラー、長い処理時間、大きな token 数などです。Request 1 の最初の model inference は想定より時間がかかっています。クリックして token usage と duration の詳細分解を見ると、最適化の出発点を見つけられます。
詳細
Instruments で Foundation Models App をデバッグする
(05:02)
Xcode でプロジェクトを開き、Product > Profile(または Cmd+I)を選び、テンプレート選択画面で Foundation Models template を選択して Record をクリックします。App は接続されたデバイス上で起動し、Instruments がデータを捕捉し始めます。
タイムライン上部には tracks があり、各 track には複数の lane が含まれ、活動レベルや範囲をグラフで表示します。タイムラインの下には detail view があり、現在選択している範囲の要約情報を表示します。タイムライン上のバーや detail view の行をクリックすると、右側の inspector が選択項目の詳細を表示します。
tree view は問題調査の中心となるツールです。すべてのログを階層構造で整理します。
セッション
└── リクエスト
└── モデル推論
├── 指示
├── プロンプト
└── レスポンス(またはエラー)
各階層は展開や折りたたみができます。任意のノードをクリックすると、inspector にそのノードの完全な内容が表示されます。
指示の不一致による静かな失敗を見つける
(08:54)
デモの bug は典型的な instruction-tool の不一致です。BrainstormDynamicInstructions の prompt テキストは、モデルが switchToTutorialMode を呼び出せると伝えていますが、toolset にはその tool がありません。
// エラー:prompt は switchToTutorialMode に言及しているが、toolset には登録されていない
DynamicInstructions {
"...準備ができたら switchToTutorialMode を呼び出す..."
} toolset: {
GenerateCraftIdeaTool() // 登録されている tool は 1 つだけ
}
モデルは prompt 内の指示を見ますが、対応する tool がないため、モードを切り替える代わりにテキスト生成を続けます。エラーも例外もなく、期待と違う動作だけが起きます。
修正方法は toolset を補完することです。
// 正しい例:prompt と toolset が厳密に一致している
DynamicInstructions {
"...準備ができたら switchToTutorialMode を呼び出す..."
} toolset: {
GenerateCraftIdeaTool()
SwitchToTutorialModeTool() // 欠けていた tool を追加
}
要点:
DynamicInstructionsのtoolsetパラメータは、モデルが実際に呼び出せる tool の集合を決める- prompt に登場する tool 名は、
toolset内に対応する登録が必要 - tool が未登録でも LLM は Swift エラーを投げず、静かにテキスト生成を続ける
- Instruments の Instructions lane を見ると、session 内で何セットの instructions が有効だったか一目で分かる
- tree view の Instructions ノードは実際に結び付いた tool 一覧を表示するため、prompt 内容と比べれば不一致を特定できる
性能最適化:3 つの指標の実際の使い方
(12:07)
Model inference ノードの inspector には、Duration と Token Usage の 2 つの可視化領域があります。
Time to First Token(TTFT) は、prompt 送信からモデルが最初の token を出力するまでの時間を測定します。この段階の計算量は prompt の長さに依存します。prompt が長いほど prefill 段階の負荷が高くなります。最適化の方向は、prompt を簡潔にし、不要なコンテキストを削ることです。
Tokens per Second(TPS) は response の生成速度を測定します。この指標はモデル構成や prompt バージョンの変化に敏感で、ベンチマークの基準に適しています。prompt を変更したりモデルを切り替えたりしたあと、TPS を比較すると性能回帰を早く見つけられます。
Total Latency はエンドツーエンドの待ち時間です。複数ステップの tool call を含む agentic な場面では、この数字は大きく膨らみます。Apple の推奨は streaming を使って体感遅延を下げることです。
let session = LanguageModelSession(instructions: myInstructions)
// ストリーミング応答を使い、部分結果を早めに表示する
let responseStream = session.streamResponse(to: userPrompt)
var isFirstToken = true
for try await partialResponse in responseStream {
if isFirstToken {
// TTFT を記録する。この時点でユーザーはすでに最初の文字を見ている
recordTTFTMetric()
isFirstToken = false
}
// UI をリアルタイム更新し、ストリーミング出力で総遅延を目立たなくする
updateUI(with: partialResponse)
}
要点:
streamResponse(to:)は token ごとに消費できる非同期シーケンスを返す- 最初の partial response が届いた時点で UI を更新すると、体感 TTFT を大きく下げられる
- streaming を使わない場合、複雑な agentic フローではユーザーが数秒間空白画面を見ることがある
- Instruments の token usage データは性能最適化の出発点であり、時間とリソースがどこに使われているかを示す
プライバシー上の注意
(05:16)
Foundation Models Instrument は完全な prompt と response 内容を捕捉します。これらのデータにはユーザーの機密情報が含まれる可能性があります。ログは本番環境ではデフォルトで無効で、trace 録画中だけ有効になります。trace ファイルは機密データとして扱い、実ユーザーのプライバシーデータを含む環境から安易に export や共有をしないでください。
核心的な示唆
1. 既存 App に AI アシスタントを追加し、Instruments で tool call ループを調査する
- 作るもの:既存 App に Foundation Models ベースのスマートアシスタントを統合し、検索、フィルタ、コンテンツ作成など App 内機能を呼び出せるようにする。
- 作る価値:多くの App にはすでに豊富な機能がありますが、ユーザーはそれを見つけるために何層もクリックする必要があります。agentic アシスタントは「先週撮った夕日の写真を探して」を連続した tool call に変換できます。
- 始め方:
LanguageModelSessionで instructions と toolset を定義し、Instruments で tool call の実行順序と遅延を観察する。各 tool が正しく呼ばれ、ループに陥っていないことを確認する。
2. 多段階ワークフローを構築し、instruction 切り替えでコンテキストを管理する
- 作るもの:「文書を分析 -> 要点を抽出 -> 要約を生成 -> 日本語に翻訳」のような、多段階推論が必要な機能を設計する。各段階で異なる instructions を使う。
- 作る価値:巨大な 1 つの prompt は TTFT を急増させ、複雑なタスクでモデルが迷いやすくなります。段階ごとに処理すれば各ステップの複雑さを下げられ、Instruments で各段階の所要時間も観察できます。
- 始め方:各段階に独立した
DynamicInstructionsを定義し、段階完了時に tool call で instruction 切り替えを起動する。Instruments の Instructions lane で切り替えタイミングが正しいか検証する。
3. streaming とプレースホルダー UI で LLM 機能の体感遅延を改善する
- 作るもの:すべての LLM インタラクションを「完全な response を待ってから表示」ではなく、「最初の token が届いたら表示し、受信しながら更新」に変える。
- 作る価値:多段階 agentic フローでは Total latency が数秒、場合によっては十数秒に達します。streaming は TTFT を「ユーザーが待っている時間」から「ユーザーがすでに内容を見ている時間」に変えます。
- 始め方:
response(to:)をstreamResponse(to:)に置き換え、for try awaitで非同期シーケンスを消費する。UI 層にタイプライター効果を加え、内容が生成中であることをユーザーに伝える。
4. LLM 機能の性能ベンチマークを作る
- 作るもの:各 LLM 駆動機能について TTFT、TPS、Total Latency の基準値を作り、CI でそれらの変化を監視する。
- 作る価値:prompt の小さな変更が性能に大きく影響することがあります。基準データがなければ、性能回帰はユーザーからの苦情で初めて見つかります。
- 始め方:Instruments で代表的なシナリオの trace を録画し、主要な model inference ノードの 3 指標を記録する。prompt やモデル構成を変更するたびに再録画し、指標を比較する。
5. LLM 機能のフォールバック戦略を設計する
- 作るもの:model inference の時間が長すぎる、または token 消費が大きすぎる場合、自動的に軽量モデルまたは短い prompt に切り替える。
- 作る価値:Instruments の info 列は、長時間処理や大きな token 数のノードをマークします。このデータを使ってフォールバックの発動条件を定義できます。
- 始め方:コード内で
streamResponseの TTFT を監視し、しきい値を超えた場合は現在のリクエストを中断し、簡略化した prompt で再実行する。Instruments は、どの場面の prompt がタイムアウトしやすいか見つける手助けになります。
関連セッション
- Foundation Models フレームワークでエージェント App 体験を構築する — 多段階の agentic ワークフローを構築する方法を解説し、この session でデバッグする対象を理解する前提になる
- Evaluations フレームワークに出会う — Instruments は bug を見つけ、Evaluations framework は prompt 品質を採点する。両者で LLM App の品質保証ループを構成する
- Foundation Models フレームワークの新機能 — DynamicInstructions の改善を含め、Foundation Models framework の最新 API 追加を学ぶ
- App の応答性をプロファイルして最適化する — 一般的な Instruments 性能分析テクニック。本 session の LLM 専用分析を補完する
- Swift Testing に出会う — LLM の確率的出力により従来の単体テストは効きにくくなる。Swift Testing の新機能はより適したテスト戦略を提供できる可能性がある
コメント
GitHub Issues · utterances