Skip to content

Telemetry

Telemetry integrations receive the lifecycle events of each operation. OpenTelemetry exports spans using the OpenTelemetry GenAI semantic conventions through a Tracer you supply. Implement Tracer and Span to bridge to your observability backend.

import SwiftAISDK
import OpenAIProvider
let integration = OpenTelemetry(tracer: myTracer)
let result = try await generateText(
model: openai("gpt-4o"),
prompt: "Write a short story about a cat.",
telemetry: TelemetryOptions(
functionId: "story",
integrations: [integration]
)
)

Use registerTelemetry(integration) to register integrations for all calls. Per-call integrations replace the registered ones. TelemetryOptions(isEnabled: false) turns telemetry off for that call.

Inputs and outputs are recorded by default. Disable them independently:

let result = try await generateText(
model: openai("gpt-4o"),
prompt: "Sensitive user data...",
telemetry: TelemetryOptions(
recordInputs: false,
recordOutputs: false,
integrations: [integration]
)
)

recordInputs controls system instructions, input messages, tool definitions and arguments, embedding values, schema contents and audio inputs. recordOutputs controls output messages, tool results, embedding vectors and audio outputs. Model identifiers, settings and usage remain available. Supplemental request headers, runtime context and provider metadata have separate opt-in controls; input/output flags do not redact them.

generateText and streamText produce:

invoke_agent <model>
└─ step 1
├─ chat <model>
│ └─ execute_tool <provider-tool>
└─ execute_tool <client-tool>

Each additional step is a child of the operation. Client tools are children of their step; provider-executed tools are children of the inference span. A generation started inside a tool inherits that tool’s span through OpenTelemetry.activeSpan. Provider calls similarly run with the inference span active.

generateObject and streamObject create invoke_agent <model> with a chat <model> child. Both carry gen_ai.output.type = "json".

embed and embedMany create an embeddings <model> operation with an embeddings <model> child per model call. The child records gen_ai.usage.input_tokens. rerank creates a rerank <model> operation and model-call child. Speech and transcription operations use their operation identifier and model in the span name, with gen_ai.output.type set to speech or text and gen_ai.request.stream indicating streaming transcription.

Operation and inference spans identify the operation, provider and model with gen_ai.operation.name, gen_ai.provider.name and gen_ai.request.model. Provider names are mapped to semantic-convention names, for example openai, gcp.gemini, gcp.vertex_ai, aws.bedrock and azure.ai.openai. TelemetryOptions.functionId becomes gen_ai.agent.name on generation and audio operations.

Generation attributes include:

  • gen_ai.request.max_tokens, temperature, top_p, top_k, presence_penalty, frequency_penalty, stop_sequences and seed when supplied.
  • gen_ai.system_instructions: JSON parts with type and content.
  • gen_ai.input.messages: JSON messages with role and parts.
  • gen_ai.output.messages: JSON messages with role, parts and finish_reason.
  • gen_ai.response.finish_reasons, gen_ai.response.id and gen_ai.response.model.
  • gen_ai.usage.input_tokens, output_tokens, cache_read.input_tokens and cache_creation.input_tokens, where available.
  • gen_ai.tool.definitions: JSON tool definitions on inference spans.

Message parts preserve text, reasoning, tool calls and tool responses. Input files and images use uri or blob parts with modality and mime_type. Output files use base64 blob parts.

Inference timing is recorded in seconds:

  • gen_ai.client.operation.duration
  • gen_ai.client.operation.time_to_first_chunk
  • gen_ai.client.operation.time_per_output_chunk

Tool spans record gen_ai.tool.name, gen_ai.tool.call.id, gen_ai.tool.type (function or extension), gen_ai.tool.call.arguments, gen_ai.tool.call.result and, for client tools, gen_ai.execute_tool.duration in seconds. Failures record an exception and error status. Aborts and errors close every open span for the operation.

Supplemental groups are disabled by default:

let integration = OpenTelemetry(
tracer: myTracer,
usage: true,
providerMetadata: true,
embedding: true,
reranking: true,
runtimeContext: true,
headers: true,
toolChoice: true,
schema: true
)
OptionAdditional attributes
usageai.usage.inputTokenDetails.noCacheTokens, ai.usage.outputTokenDetails.textTokens, ai.usage.outputTokenDetails.reasoningTokens; audio ai.response.usage
providerMetadataai.response.providerMetadata
embeddingai.value, ai.values, ai.embedding, ai.embeddings
rerankingai.documents, ai.ranking.type, ai.ranking
runtimeContextai.settings.context.*, with nested objects flattened
headersai.request.headers.*
toolChoiceai.prompt.toolChoice on step spans
schemaai.schema, ai.schema.name, ai.schema.description, ai.settings.output

Audio operations always expose applicable ai.request.text, ai.request.audio.size, ai.request.audio.media_type, ai.response.text, ai.response.audio.size, ai.response.audio.media_type and ai.response.audio.format, subject to input/output flags. Provider-reported audio usage is normalized under gen_ai.usage.*.

Runtime context is delivered to integrations only for keys explicitly allowed by TelemetryOptions.includeRuntimeContext:

let result = try await generateText(
model: openai("gpt-4o"),
prompt: "Write a short story.",
telemetry: TelemetryOptions(
includeRuntimeContext: ["tenant": true],
integrations: [OpenTelemetry(tracer: myTracer, runtimeContext: true)]
),
runtimeContext: ["tenant": "acme"]
)

enrichSpan receives the span type, operation identifier, call identifier and permitted runtime context when each span starts. SDK-owned attributes take precedence over custom attributes. Exceptions thrown by this closure are ignored, and non-finite numeric attributes are dropped.

let integration = OpenTelemetry(tracer: myTracer, enrichSpan: { event in
["app.span_type": .string(event.spanType.rawValue)]
})