← All courses ← Course home

Lesson 04 of 05

Add OpenTelemetry-style spans

Wrap each hop in a span. Use OpenTelemetry names. Record them with OSSignposter so they show up next to Instruments.

Agent brief (llms.md)

Apple does not ship an OpenTelemetry exporter for Foundation Models. You can still use the same span idea: a named interval, a parent, and a few attributes. On Apple platforms, OSSignposter is the recorder that Instruments already understands.

Spanai.context_pack
Spanai.request
Childai.context_pack
Spanai.request
Childai.context_pack
Childai.model
Childai.guardrail
import os

enum AITrace {
    static let signposter = OSSignposter(
        subsystem: "dev.edgefde.app",
        category: "ai-pipeline"
    )

    static func span(_ name: StaticString, _ work: () async throws -> T) async rethrows -> T {
        let id = signposter.makeSignpostID()
        let state = signposter.beginInterval(name, id: id)
        defer { signposter.endInterval(name, state) }
        return try await work()
    }
}

// Sketch. Confirm OSSignposter names in the installed SDK.
let reply = try await AITrace.span("ai.request") {
    let packed = try await AITrace.span("ai.context_pack") { packContext() }
    let text = try await AITrace.span("ai.model") { try await session.respond(to: packed) }
    return try await AITrace.span("ai.guardrail") { try applyGuardrail(text) }
}

Use dotted names the way OpenTelemetry semantic conventions do: ai.request, ai.context_pack, ai.tool, ai.model, ai.guardrail. Put the tool name, model name, profile name, and prompt version on attributes or a short signpost message. Do not put the user text there.

If you later export to an OpenTelemetry collector from a debug Mac, map those same names. Production on-device builds should keep signposts cheap and empty of content. A collector is optional. The names are not.

Nest ai.tool under ai.request for each tool. If the session calls two tools, you get two child spans. That matches the Instruments tool-call tree. Pair the ai.model span with clocks from Stream tokens and measure speed. Turn a bad tool path into a sample in Test AI behavior before you ship.

Rule. Same span names in every feature. No prompts in the span body.

Next, read a bad trace with those names.

Key concepts

  • Apple does not ship an OpenTelemetry exporter. Use the span idea with OSSignposter.
  • Parent ai.request has children for context pack, model, and guardrail.
  • Nest ai.tool per tool call.
  • Attributes carry tool, model, profile, and prompt version. No user text in the span body.

Takeaways

  • Keep the same span names in every feature.
  • Two tool calls means two ai.tool child spans.
  • Names matter even without a collector.