WWDC Quick Look 💓 By SwiftGGTeam
Instruments でエージェント App 体験をデバッグしてプロファイルする

Instruments でエージェント App 体験をデバッグしてプロファイルする

元の動画を見る

ハイライト

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 には GenerateCraftIdeaToolSwitchToTutorialModeTool の 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 が generateCraftIdeaswitchToTutorialMode の 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 を追加
}

要点:

  • DynamicInstructionstoolset パラメータは、モデルが実際に呼び出せる 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 がタイムアウトしやすいか見つける手助けになります。

関連セッション

コメント

GitHub Issues · utterances