WWDC Quick Look 💓 By SwiftGGTeam
Debug and profile agentic app experiences with Instruments

Debug and profile agentic app experiences with Instruments

观看原视频

Highlight

Xcode 27 的 Foundation Models Instrument 将 LLM 智能体应用中的 prompt、tool call、instruction 切换和 token 消耗全部可视化到时间轴上,让开发者能直接定位静默失败、指令错配和性能瓶颈。

核心内容

LLM 开发的三道坎

01:58

写传统代码,输入 A 必然得到输出 B。但用 Foundation Models framework 构建智能体应用时,开发者要面对三个传统开发中没有的挑战。

第一,概率性输出。同一个 prompt 发给 LLM 两次,可能得到完全不同的回答。这意味着标准单元测试失效——你不能断言输出等于某个固定字符串,只能评估回答的质量和意图。

第二,模型间通信。复杂功能往往需要多个模型协作。比如一个食谱 App,一个模型识别照片中的食材,另一个模型根据识别结果生成食谱。数据在模型之间流转时,任何环节出错都会导致整个链路断裂。

第三,可观测性。多模型管道中某个环节出问题,很难判断到底坏在哪里。你需要看到每一步的输入、模型的决策过程、以及输出结果。

03:08

一个 LLM 应用的基本流程很简单:用户发送 prompt,模型推理,返回回答。但很多场景需要 tool call 循环:用户发送 prompt,模型推理后调用工具,工具执行动作,模型拿到结果再生成最终回答。这个循环可以反复触发。

每一步都增加延迟,每一步都是新的故障点。理解这个循环,是看懂 Foundation Models Instrument 所有数据的前提。

一个真实的调试案例

04:02

演示者正在开发一个手工日记 App,其中有一个交互式头脑风暴功能:用户和模型对话来细化创作想法,确定后 App 自动生成详细教程。这个功能用到了两套 instructions:一套用于头脑风暴,一套用于教程生成。头脑风暴的 instructions 包含两个工具:GenerateCraftIdeaToolSwitchToTutorialModeTool

演示者运行 App,模型给出了几个手工点子,选了 “Paper Butterfly”。但接下来模型没有切换到教程模式,而是继续生成更多点子。功能卡住了。

05:02

打开 Xcode,选择 Product > Profile,在模板选择器里选中 Foundation Models template,点击 Record。Instrument 会捕获设备上的 prompt 和 response 数据——这些数据可能包含敏感信息,生产环境默认关闭,仅在 trace 期间开启,所以 trace 文件要妥善保管。

时间轴上的六条轨道

06:36

Foundation Models Instrument 的时间轴有 6 个 lane:

  • Instructions lane:显示某套 instructions 和 tools 的活跃时长。一套 instructions 可以覆盖多个请求。
  • Model Inference lane:黄色条表示处理输入 prompt 的耗时,橙色条表示生成 response 的耗时。
  • 其余 lane 提供 session 结构和延迟的概览。

时间轴下方是 tree detail view,把一次 recording 中的所有日志按层级组织:session → request → model inference → instructions → prompt → response。

06:59

看 Instructions lane,整个 session 只激活了一套 instructions。但功能设计需要两套——头脑风暴和教程生成。说明 handoff 环节出了问题。

展开 tree view,Session 1 有两个 request。第一个 request 由 prompt “Please generate 3 craft ideas” 触发,包含两次 model inference 和几个 tool call。点击 model inference 节点,inspector 右侧显示 instructions、prompt 和 response 的摘要。

08:54

选中 Instructions 节点,inspector 显示这套 instructions 只绑定了一个 tool。prompt 里提到了 switchToTutorialMode,但 toolset 里并没有配置这个工具。没有它,App 就无法从头脑风暴模式切换到教程模式,用户被困在循环里。

更麻烦的是,这是一个静默失败。模型继续接受输入、继续调用工具,没有抛出任何错误。没有 Instruments,这种 bug 几乎不可能被发现。

修复与验证

09:38

回到 Xcode,检查 BrainstormDynamicInstructions 的定义。prompt 里提到了 SwitchToTutorialMode,但 toolset 里只注册了 GenerateCraftIdeasTool。把 SwitchToTutorialModeTool 补进 toolset,重新编译并再次用 Instruments 录制。

App 重新运行,选择 “necklace”,UI 成功切换到教程模式,模型生成了一份完整的教程。

10:37

回到 Instruments,Instructions lane 现在显示了两套独立的 instructions:先是头脑风暴指令,后是教程生成指令,与功能设计完全一致。

Tree view 里,第一套 instructions 现在同时包含 generateCraftIdeaswitchToTutorialMode 两个工具。第二个 request 的第二次 model inference 触发了 switchToTutorialMode tool call,传入选中的 craft 作为参数。接下来的 request 正确切换到了教程生成 instructions,并把选中的 craft 作为上下文传入。

三个性能指标

12:07

Instruments 的 model inference 节点里展示了三个关键性能指标:

  • 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 应用

05:02

在 Xcode 中打开项目,选择 Product > Profile(或按 Cmd+I),在模板选择器中选择 Foundation Models template,点击 Record。App 会在连接的设备上启动,Instruments 开始捕获数据。

时间轴顶部是 tracks,每个 track 包含多个 lane,用图表展示活动水平或区域。时间轴下方是 detail view,显示当前选中范围的摘要信息。点击时间轴上的条形或 detail view 中的行,右侧 inspector 会展示选中项的详细信息。

Tree view 是排查问题的核心工具。它按层级组织所有日志:

Session
  └── Request
        └── Model Inference
              ├── Instructions
              ├── Prompt
              └── Response (或 Error)

每个层级都可以展开和收起。点击任意节点,inspector 会显示该节点的完整内容。

定位静默失败的指令错配

08:54

演示中的 bug 是典型的 instruction-tool 错配。BrainstormDynamicInstructions 的 prompt 文本告诉模型可以调用 switchToTutorialMode,但 toolset 里漏掉了这个工具:

// 错误:prompt 提到了 switchToTutorialMode,但 toolset 没注册
DynamicInstructions {
    "...call switchToTutorialMode when ready..."
} toolset: {
    GenerateCraftIdeaTool()  // 只注册了一个工具
}

模型看到 prompt 里的指令,发现自己没有对应的 tool 可以调用,于是继续生成文本而不是切换模式。没有报错,没有异常,只有行为不符合预期。

修复方式是在 toolset 中补全工具:

// 正确:prompt 和 toolset 严格对齐
DynamicInstructions {
    "...call switchToTutorialMode when ready..."
} toolset: {
    GenerateCraftIdeaTool()
    SwitchToTutorialModeTool()  // 补全缺失的工具
}

关键点:

  • DynamicInstructionstoolset 参数决定模型实际能调用的工具集合
  • prompt 里提到的工具名称必须在 toolset 中有对应注册
  • LLM 不会因为 tool 未注册而抛 Swift 错误,只会静默地继续生成文本
  • Instruments 的 Instructions lane 可以一眼看出 session 中激活了几套 instructions
  • Tree view 的 Instructions 节点展示了实际绑定的 tool 列表,与 prompt 内容对比即可定位错配

优化性能:三个指标的实际用法

12:07

Model inference 节点的 inspector 里包含 Duration 和 Token Usage 两个可视化区域。

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 文件应视为敏感数据妥善保管,不要在包含真实用户隐私数据的环境中随意导出和分享。

核心启发

1. 给现有 App 加一个 AI 助手,用 Instruments 排查 tool call 循环

  • 做什么:在现有 App 中集成一个基于 Foundation Models 的智能助手,让它能调用 App 内的功能(如搜索、筛选、创建内容)。
  • 为什么值得做:大多数 App 已经有丰富的功能,但用户需要层层点击才能找到。一个 agentic 助手可以把 “帮我找上周拍的日落照片” 翻译成连续的 tool call。
  • 怎么开始:用 LanguageModelSession 定义 instructions 和 toolset,在 Instruments 中观察 tool call 的执行顺序和延迟,确保每个 tool 都被正确调用且没有陷入循环。

2. 构建多步骤工作流,用 instruction 切换管理上下文

  • 做什么:设计一个需要多阶段推理的功能,比如 “分析文档 → 提取要点 → 生成摘要 → 翻译成中文”。每个阶段用不同的 instructions。
  • 为什么值得做:单一大 prompt 会让 TTFT 急剧上升,且模型容易在复杂任务中迷失。分阶段处理可以降低每步的复杂度,同时通过 Instruments 观察每阶段的耗时。
  • 怎么开始:为每个阶段定义独立的 DynamicInstructions,在阶段完成时通过 tool call 触发 instruction 切换。在 Instruments 的 Instructions lane 中验证切换时机是否正确。

3. 用 streaming + 占位 UI 优化 LLM 功能的体感延迟

  • 做什么:把所有 LLM 交互从 “等待完整 response 再展示” 改为 “收到第一个 token 就展示,边收边更新”。
  • 为什么值得做:Total latency 在多步 agentic 流程中可能达到数秒甚至十几秒。streaming 可以把 TTFT 从 “用户等待时间” 变成 “用户已经看到内容的时间”。
  • 怎么开始:把 response(to:) 换成 streamResponse(to:),用 for try await 消费异步序列。在 UI 层加一个打字机效果,让用户感知到内容正在生成。

4. 建立 LLM 功能的性能基准测试

  • 做什么:为每个 LLM 驱动的功能建立 TTFT、TPS 和 Total Latency 的基准值,在 CI 中监控这些指标的变化。
  • 为什么值得做:prompt 的微小改动可能显著影响性能。没有基准数据,性能回退只能在用户投诉后才发现。
  • 怎么开始:在 Instruments 中录制典型场景的 trace,记录关键 model inference 节点的三个指标。每次改动 prompt 或模型配置后重新录制,对比指标变化。

5. 设计 LLM 功能的降级策略

  • 做什么:当 model inference 耗时过长或 token 消耗过大时,自动切换到更轻量的模型或更短的 prompt。
  • 为什么值得做:Instruments 的 info 列会标记长耗时和大 token 的节点。这些数据可以用来定义降级的触发条件。
  • 怎么开始:在代码中监控 streamResponse 的 TTFT,如果超过阈值则中断当前请求,用精简后的 prompt 重新发起。Instruments 帮你找到哪些场景的 prompt 最容易超时。

关联 Session

评论

GitHub Issues · utterances