SGP Traces must be enabled on your deployment. The OpenTelemetry GenAI
semantic conventions are still evolving, so the attribute mapping below can
change.
openai-agents, use the native
OpenAI Agents Integration.
Prerequisites
- An SGP API key and the ID of the account that will own the traces.
- Your SGP Traces host. The default is
sgp-traces.<deployment-url>, where<deployment-url>is the domain your SGP API runs under asapi.<deployment-url>. The development deployment, for example, ishttps://sgp-traces.dev-sgp.scale.com/v1/traces. Deployments can override the host; ask your deployment owner if the default does not resolve. - A framework or SDK that exports over OTLP/HTTP protobuf. For hand instrumentation in Python:
@opentelemetry/exporter-trace-otlp-proto. The -http package sends JSON
regardless of OTEL_EXPORTER_OTLP_TRACES_PROTOCOL and is rejected with 400.
Configure the exporter
Set these variables before starting your application. Every OpenTelemetry SDK reads them. Frameworks that manage their own exporter or keep tracing opt-in need one extra step, listed under Framework notes./v1/traces included. The generic
OTEL_EXPORTER_OTLP_ENDPOINT must be the bare https://sgp-traces.<deployment-url>: the SDK
appends /v1/traces, and a value that already has it is exported to /v1/traces/v1/traces.
LangSmith is the exception, see below. Prefer the trace-specific variables; the generic ones also
drive metric and log exporters, and SGP Traces accepts traces only.
If your framework emits through the global tracer provider but does not register an exporter, add
one at startup. Existing instrumentation is unaffected:
Framework notes
- Pydantic AI emits spans only after
Agent.instrument_all()orinstrument=True, through the global tracer provider, so register the exporter as above. With Logfire,logfire.configure(send_to_logfire=False)builds the exporter from theOTEL_EXPORTER_OTLP_*variables andlogfire.instrument_pydantic_ai()turns the spans on. - OpenLLMetry (Traceloop) ignores the
OTEL_EXPORTER_OTLP_*variables. SetTRACELOOP_BASE_URL="https://sgp-traces.<deployment-url>"(it appends/v1/traces) andTRACELOOP_HEADERS="x-api-key=...,x-selected-account-id=...", or passapi_endpointandheaderstoTraceloop.init(). - LangSmith and LangChain need
pip install "langsmith[otel]",LANGSMITH_TRACING=trueto create runs at all, andLANGSMITH_TRACING_MODE=otelto export them over OpenTelemetry (older releases:LANGSMITH_OTEL_ENABLED=trueplusLANGSMITH_OTEL_ONLY=true, otherwise runs also go to LangSmith). LangSmith reads only the genericOTEL_EXPORTER_OTLP_ENDPOINTandOTEL_EXPORTER_OTLP_HEADERSand uses the endpoint verbatim, so include/v1/traces. An already-configured global tracer provider takes precedence. - Claude Agent SDK and Claude Code keep telemetry opt-in and trace export in beta. Add
CLAUDE_CODE_ENABLE_TELEMETRY=1,CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1,OTEL_TRACES_EXPORTER=otlp, andOTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/protobufto the variables above. Only the built-inclaude_code.llm_requestspans carrygen_ai.systemandgen_ai.request.model, and none carry the semconv input, output, or token keys, so those land as completions with a model and nothing else while interaction and tool spans get no derived fields; the prompt is redacted unlessOTEL_LOG_USER_PROMPTS=1. For full input, output, and token mapping, instrument the Agent SDK withotel-instrumentation-claude-agent-sdkinstead.
Endpoint contract
The default SDK batch of 512 spans fits.
Errors
Service errors follow the OTLP/HTTP specification: a protobufStatus body with a message, and
the HTTP status decides retry behavior. On mesh-fronted deployments an unauthenticated request may
be answered by the mesh instead, as 403 with a plain-text RBAC: access denied body or 401
with an empty body.
400: the whole request is rejected. Causes: a missingx-selected-account-id(including the legacyx-sgp-account-idin its place), non-protobuf content type,Content-Encodingother thangziporidentity, invalid gzip, empty body, more than 1,000 spans, an invalid span such as an all-zero trace ID or an end time before its start, or a malformedsgp.obs_trace_idorsgp.obs_span_id. Fix the request or producer; do not retry. With an OpenTelemetry Collector, set bothsend_batch_size: 1000andsend_batch_max_size: 1000; under load the defaults emit 8,192-span batches, which are rejected.401: invalid API key.403: the key is not a member of the account, or its role there is not admin, manager, or editor.413: body over the deployment limit. Lower the export batch size.429: rate limited. The ingress gateway’s limit is one bucket per gateway pod shared by every caller; it answers with a plain-text body and noRetry-After. The service’s own load shedding answers with an OTLPStatusandRetry-After. The OTLP specification makes429,502,503, and504retryable and exporters from 2026 onward follow it, but older ones differ: the Python OTLP/HTTP exporter up to 1.44.0 retries only408and5xx, so a429drops the batch. Keep exporters current, stay within your deployment’s limit, or export through a Collector, which retries429with backoff.
How spans map to SGP
Every span keeps its OpenTelemetry identity and structure: trace and span IDs, parent, name, kind, status, timing, attributes, events, links, and resource and scope attributes. AnUNSET status is
stored as success, matching SGP SDK writes. The keys sgp.obs_trace_id and sgp.obs_span_id are
reserved: they move into SGP’s observability correlation fields and must be valid W3C IDs.
Spans with GenAI semantic convention
attributes also get SGP’s tracing fields, so they render like SGP SDK spans. This is best effort
and never rejects a span:
- Input and output:
gen_ai.input.messagesandgen_ai.output.messages, then the indexedgen_ai.prompt.Nandgen_ai.completion.Nattributes, thengen_ai.promptandgen_ai.completion, then framework-specific keys, then a tool span’sgen_ai.tool.call.argumentsandgen_ai.tool.call.result, and finally the legacygen_ai.content.*events. - Token usage:
gen_ai.usage.input_tokensandgen_ai.usage.output_tokens, or theprompt_tokensandcompletion_tokensspellings. - Operation type:
gen_ai.operation.name, thentraceloop.span.kind, then rerank and vector-store signals, then a tool span (gen_ai.tool.name,gen_ai.tool.call.*) with no model or tokens (custom step), thengen_ai.systemorgen_ai.provider.name(completion). A span with derived input or output and none of these is also a completion. The operationsfetch_response,guardrail_check, andagent_handoffare custom steps, and so isunknownon a span withgen_ai.systemorgen_ai.provider.namebut no model, tokens, input, or output. - Model and provider metadata from the same keys are merged into the span’s attributes.
gen_ai.* attributes gets these fields, including LangSmith and
otel-instrumentation-claude-agent-sdk. If you use the opentelemetry-instrumentation-openai-agents-v2
bridge instead of the native integration, each agent’s name arrives on its invoke_agent span, and
the bridge’s default name and ID (OpenAI Agent, agent) on its other spans are read as unknown.
Its agent_name and agent_id arguments replace every agent’s name with one, so pass them only in
a single-agent app, and not under opentelemetry-instrument, which instruments the bridge first and
ignores a second call. SGP also reads Pydantic AI (pydantic_ai.*) and OpenLLMetry (traceloop.*,
llm.*) keys, and OpenInference and Vercel AI SDK token counts. Spans with none of these keep
their raw attributes and get no derived fields.
Migrating from the legacy forwarder
Theopentelemetry.<deployment-url> forwarder is deprecated and being decommissioned. It spoke
the same protocol, so migration is a host and header change:
Do not mix the two. The native endpoint does not read the legacy headers: without
x-api-key the
request is rejected as unauthenticated by the service or, behind a mesh, as 403 RBAC: access denied; with x-api-key but only the legacy account header it is a 400 for the missing
x-selected-account-id.

