# OpenTelemetry

> Send existing OpenTelemetry traces through the project endpoint without replacing your instrumentation.

Send your application’s existing OpenTelemetry traces to Radiant through its SDK provider or collector. Choose the [provider setup](#add-an-exporter-to-an-existing-provider) when the application exports directly, or [configure the collector](#export-through-a-collector) when it already receives your traces.

Export configuration preserves the recorded operations and their relationships. If you need to instrument a custom agent, [add agent and tool spans](#add-agent-and-tool-spans) after configuring export.

Get the project’s traces endpoint and ingestion key from [Set up your project](/docs/platform/set-up-your-project.md). The examples below use OTLP over HTTP/protobuf.

## Add an exporter to an existing provider

For a Python application using the OpenTelemetry SDK:

**Terminal**

```sh
python -m pip install opentelemetry-exporter-otlp-proto-http
```

**Application environment**

```sh
export RADIANT_TRACES_ENDPOINT='https://YOUR_INGESTION_HOST/v1/traces'
export RADIANT_INGESTION_KEY='YOUR_INGESTION_KEY'
```

Add the processor during application initialization, using the existing SDK `TracerProvider` as `provider`:

**Existing tracing setup**

```python
import os
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.trace.export import BatchSpanProcessor


provider.add_span_processor(
    BatchSpanProcessor(
        OTLPSpanExporter(
            endpoint=os.environ["RADIANT_TRACES_ENDPOINT"],
            headers={"Authorization": "Bearer " + os.environ["RADIANT_INGESTION_KEY"]},
        )
    )
)
```

Keep the provider’s existing resource attributes, including `service.name`, and its other exporters. Configure shutdown or flushing through the lifecycle that already owns the provider. Do not initialize another global provider or create one for every request.

If the application has no SDK provider yet, create a `TracerProvider` with the application’s resource attributes, add the processor, and register it with `trace.set_tracer_provider(provider)` before instrumented work begins.

## Export through a collector

If the application already sends spans to an OpenTelemetry Collector, add a named OTLP/HTTP exporter instead of sending a duplicate stream from the application. Add this exporter definition, then append `otlp_http/radiant` to the existing traces pipeline’s `exporters` list. Keep its receivers, processors, and other exporters. Set the same Radiant endpoint and key in the collector’s environment.

**Collector exporter**

```yaml
exporters:
  otlp_http/radiant:
    traces_endpoint: ${env:RADIANT_TRACES_ENDPOINT}
    headers:
      Authorization: 'Bearer ${env:RADIANT_INGESTION_KEY}'
    compression: none
```

Radiant requires uncompressed requests; the collector exporter enables gzip by default. Current collectors name the component `otlp_http`. On older collectors, use `otlphttp/radiant` in both the exporter definition and the pipeline.

Use [the collector’s OTLP/HTTP exporter configuration](https://github.com/open-telemetry/opentelemetry-collector/tree/main/exporter/otlphttpexporter) for the exact configuration keys. A traces-specific endpoint contains `/v1/traces`; a base OTLP endpoint can have that path appended automatically, so do not append it twice.

## Add agent and tool spans

If your application already emits agent and tool spans, keep that instrumentation. For a custom agent, follow the [OpenTelemetry GenAI agent and tool conventions](https://github.com/open-telemetry/semantic-conventions-genai/blob/main/docs/gen-ai/gen-ai-agent-spans.md) at the existing invocation and tool-execution boundaries.

With your SDK provider registered, obtain a tracer and wrap the application’s existing agent call. Here, `agent_name`, `run_agent` and `request` are the name, invocation function and input already used by your application:

**Existing agent invocation**

```python
from opentelemetry import trace


tracer = trace.get_tracer(__name__)
with tracer.start_as_current_span(
    f"invoke_agent {agent_name}",
    attributes={
        "gen_ai.operation.name": "invoke_agent",
        "gen_ai.agent.name": agent_name,
    },
) as agent_span:
    result = run_agent(request)
```

Inside the existing tool dispatcher or tool function, add a span around the call it already makes. `tool_name`, `execute_tool` and `arguments` below represent that existing tool and its input:

**Existing tool execution**

```python
with tracer.start_as_current_span(
    f"execute_tool {tool_name}",
    attributes={
        "gen_ai.operation.name": "execute_tool",
        "gen_ai.tool.name": tool_name,
    },
) as span:
    result = execute_tool(arguments)
```

When the tool executes within the active agent span, it becomes that invocation’s child. Keep the parent span open through asynchronous work or stream consumption. Propagate OpenTelemetry context explicitly across process or queue boundaries.

If the application captures tool payloads, record the selected arguments and result as JSON strings on the tool span:

**Optional payload capture inside the tool span**

```python
import json


span.set_attribute("gen_ai.tool.call.arguments", json.dumps(arguments))
span.set_attribute("gen_ai.tool.call.result", json.dumps(result))
```

Set these attributes before the tool span ends. Include only the values intended for telemetry. You can also record `gen_ai.tool.call.id` when the application has a stable call identifier. See the [telemetry reference](/docs/platform/reference/telemetry.md) for additional recognized agent, model, and tool fields.

## Record model inputs and outputs

Agent and tool spans do not capture model requests automatically. Add the instrumentation for the model client your application actually uses. For an OpenAI Python client, the upstream instrumentor records Chat Completions and Responses calls, including model identity and token usage:

**Terminal**

```sh
python -m pip install opentelemetry-instrumentation-genai-openai==1.3b0
```

Enable message content before starting the application, then register the instrumentor at startup, after the provider and before creating model clients:

**Application environment**

```sh
export OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT='SPAN_ONLY'
```

**Existing tracing setup**

```python
from opentelemetry.instrumentation.genai.openai import OpenAIInstrumentor


OpenAIInstrumentor().instrument()
```

Existing client calls inside the active agent span now produce child model spans. With content capture enabled, those spans contain the actual request and response messages in `gen_ai.input.messages` and `gen_ai.output.messages`. Token usage comes from the model response. Radiant uses the captured messages to render the model record’s **Thread**.

This configuration applies to the OpenAI client; use the corresponding instrumentation for other model libraries. If you instrument a model client yourself, record its actual messages and usage using the [GenAI model fields and message format](/docs/platform/reference/telemetry.md). Avoid adding another client instrumentor when a framework integration already records the same model calls.

The [upstream OpenAI instrumentor](https://github.com/open-telemetry/opentelemetry-python-genai/tree/main/instrumentation/opentelemetry-instrumentation-genai-openai) documents supported client versions and content-capture controls. Message capture is off by default; enable it for the payloads your application intends to export.

## Add attributes

Resource attributes describe the process and apply to all of its spans. Put request-specific identifiers on the operation that owns them.

Inside the agent span, add identifiers that your application already uses. Set these before `run_agent(request)` in the invocation example:

**Inside the agent span**

```python
agent_span.set_attribute("gen_ai.conversation.id", conversation_id)
agent_span.set_attribute("gen_ai.agent.version", agent_version)
```

Here, `conversation_id` identifies the conversation and `agent_version` identifies the agent release. These attributes apply to this invocation, not automatically to its child spans. The **Conversations** view groups agent runs by `gen_ai.conversation.id`; the version appears in [Agents and versions](/docs/platform/logs/agents-and-versions.md). See [Telemetry reference](/docs/platform/reference/telemetry.md) for field definitions.

## Inspect the exported activity

Run an existing application request and find its service in the project’s **Logs**. Open the trace and confirm that the expected operations and relationships are present. The exporter sends recorded values; it does not infer missing messages, token usage or agent identity.

Recognized agent invocations also require the [agent span attributes](/docs/platform/reference/telemetry.md#agent-runs). The [telemetry reference](/docs/platform/reference/telemetry.md) defines model and tool fields, usage, content formats and correlation. For missing traces, check [export and authentication troubleshooting](/docs/platform/reference/telemetry.md#troubleshoot-tracing).
