Skip to main content

Exporting OTel Traces to Fiddler

Overview

This guide covers the client-side export scenario: your application has already generated OpenTelemetry traces, you manage their storage and processing, and you need to ship them to Fiddler. You are responsible for:
  1. Attribute mapping — translating your OTel span attributes to Fiddler’s schema
  2. Protobuf serialization — building ResourceSpans → ScopeSpans → Span structures
  3. Export — POSTing the compressed payload to Fiddler’s v1/traces endpoint
This differs from live instrumentation, where the OTel SDK exports spans automatically as your agent runs.
When to use this approachUse client-side export when you have:
  • Traces stored in a data warehouse, JSONL files, or a logging pipeline that you want to replay into Fiddler
  • A custom export pipeline that processes spans before sending (e.g., filtering, enrichment, or ID regeneration)
  • Batch backfill of historical trace data
For real-time agent instrumentation, use the OpenTelemetry Integration or a framework SDK instead.

Prerequisites

  • A Fiddler account with a GenAI application created — you will need its Application UUID
  • A valid Fiddler API token (from Settings > Credentials)
  • Python packages:
    pip install opentelemetry-proto httpx
    

The v1/traces Endpoint

Send traces as a gzip-compressed protobuf ExportTraceServiceRequest payload:
PropertyValue
URLhttps://<your-fiddler-instance>/v1/traces
MethodPOST
Content-Typeapplication/x-protobuf
Content-Encodinggzip
AuthorizationBearer <your-api-token>
fiddler-application-id<your-application-uuid>
import gzip
import httpx
from opentelemetry.proto.collector.trace.v1.trace_service_pb2 import ExportTraceServiceRequest

payload = ExportTraceServiceRequest(resource_spans=[...]).SerializeToString()

httpx.post(
    "https://your-instance.fiddler.ai/v1/traces",
    content=gzip.compress(payload),
    headers={
        "Authorization": "Bearer <YOUR_TOKEN>",
        "fiddler-application-id": "<YOUR_APP_UUID>",
        "Content-Type": "application/x-protobuf",
        "Content-Encoding": "gzip",
    },
)

Protobuf Structure

Fiddler expects the standard OTLP hierarchy:
ExportTraceServiceRequest
└── ResourceSpans[]
    ├── Resource
    │   └── attributes: [application.id, service.name, ...]
    └── ScopeSpans[]
        └── Span[]
            ├── trace_id, span_id, parent_span_id
            ├── name, kind, status
            ├── start_time_unix_nano, end_time_unix_nano
            └── attributes: [gen_ai.agent.name, fiddler.span.type, ...]
application.id must be set at the Resource level (not on individual spans):
from opentelemetry.proto.resource.v1.resource_pb2 import Resource
from opentelemetry.proto.common.v1.common_pb2 import AnyValue, KeyValue

resource = Resource(attributes=[
    KeyValue(key="application.id", value=AnyValue(string_value="<YOUR_APP_UUID>")),
    KeyValue(key="service.name",   value=AnyValue(string_value="my-agent-service")),
])

AnyValue Typing

Each KeyValue in the protobuf structure carries its value in an AnyValue message. AnyValue is a oneof — exactly one typed field must be set. Using the correct field ensures Fiddler classifies the data with the right type (e.g., numeric fields enable aggregation, charting, and alerting).
Python typeAnyValue fieldFiddler storage typeExample
strstring_valueStringAnyValue(string_value="gpt-4o")
intint_valueNumeric (stored as float)AnyValue(int_value=150)
floatdouble_valueNumeric (stored as float)AnyValue(double_value=0.95)
boolbool_valueStringAnyValue(bool_value=True)
Use int_value / double_value for numeric attributes. If you wrap a number in string_value (e.g., AnyValue(string_value="150")), Fiddler will treat it as a string column. This means you lose the ability to compute numerical aggregations (sum, average, percentiles) and set alerts on that attribute.
from opentelemetry.proto.common.v1.common_pb2 import AnyValue


def to_any_value(value: str | int | float | bool) -> AnyValue:
    """
    Convert a Python value to a correctly typed AnyValue.

    :param value: The value to convert.
    :returns: An AnyValue with the appropriate typed field set.
    :raises TypeError: If *value* is not a supported type (including
        ``None`` and ``bytes``). Filter ``None`` values upstream — OTel
        attributes have no null concept, so omit the attribute instead.
        For ``bytes``, decode to ``str`` first — Fiddler stores bytes as
        strings internally.

    Example::

        to_any_value(150)        # AnyValue(int_value=150)
        to_any_value("us-west")  # AnyValue(string_value="us-west")
    """
    # bool must be checked before int (bool is a subclass of int in Python)
    if isinstance(value, bool):
        return AnyValue(bool_value=value)
    if isinstance(value, int):
        return AnyValue(int_value=value)
    if isinstance(value, float):
        return AnyValue(double_value=value)
    if isinstance(value, str):
        return AnyValue(string_value=value)
    # For bytes, consider decoding to str to unblock: AnyValue(string_value=value.decode("utf-8"))
    # For None, consider Filtering upstream, omitting the attribute instead.
    raise TypeError(f"Unsupported type {type(value).__name__} for AnyValue")
For the full attribute quick overview (all attributes, types, and AnyValue fields), see Span and Resource Attributes — Quick Overview.

Attribute Mapping Reference

Span Structure Fields

The following fields control the span’s structural properties and are not sent as span attributes. Map them to the corresponding protobuf Span fields instead:
Your fieldProtobuf Span field
trace_idtrace_id (bytes, 16 bytes / 32-char hex)
span_idspan_id (bytes, 8 bytes / 16-char hex)
parent_span_idparent_span_id (bytes, same format)
span_name / namename
span_kindkind (SpanKind enum)
status_codestatus.code (StatusCode enum)
start_timestart_time_unix_nano
end_timeend_time_unix_nano

Required Span Attributes

Every span sent to Fiddler must include this attribute:
AttributeDescription
fiddler.span.typeSpan type: llm, tool, chain, or agent
application.id is required at the Resource level (see above).

Span Type: Deriving from gen_ai.operation.name

If your spans follow the GenAI semantic conventions and carry gen_ai.operation.name, map it to fiddler.span.type as follows:
gen_ai.operation.namefiddler.span.type
chatllm
execute_tooltool
invoke_agentchain
(any other value or absent)chain (default)

LLM Semantic Convention Mappings

Your OTel attributeFiddler attributeNotes
gen_ai.input.messagesgen_ai.llm.input.user + gen_ai.llm.contextSee message parsing below
gen_ai.output.messagesgen_ai.llm.output
gen_ai.system_instructionsgen_ai.llm.input.system
gen_ai.response.modelgen_ai.request.modelFiddler normalises to the request-side attribute
gen_ai.usage.input_tokensgen_ai.usage.input_tokensPass through unchanged
gen_ai.usage.output_tokensgen_ai.usage.output_tokensPass through unchanged
gen_ai.usage.total_tokensgen_ai.usage.total_tokensPass through unchanged
gen_ai.systemgen_ai.systemLLM provider identifier (e.g. openai)
gen_ai.request.modelgen_ai.request.modelPass through unchanged

Tool Semantic Convention Mappings

Your OTel attributeFiddler attribute
gen_ai.tool.call.argumentsgen_ai.tool.input
gen_ai.tool.call.resultgen_ai.tool.output
gen_ai.tool.namegen_ai.tool.name

Agent and Conversation Attributes

Your OTel attributeFiddler attributeNotes
gen_ai.agent.namegen_ai.agent.nameOptional
gen_ai.agent.idgen_ai.agent.idOptional
gen_ai.conversation.idgen_ai.conversation.idOptional — used for multi-turn conversation grouping
Set agent attributes on every span. If gen_ai.agent.name or gen_ai.agent.id are provided, set both on every span within the trace. Fiddler uses these attributes to attribute spans to the correct agent — spans missing these fields will be unattributed even if other spans in the same trace carry them.

Legacy / Underscore Field Names

If your traces use older underscore-style field names, map them to the Fiddler dotted equivalents before serialization:
Legacy fieldFiddler attribute
model_namegen_ai.request.model
model_providergen_ai.system
tool_namegen_ai.tool.name
tool_inputgen_ai.tool.input
tool_outputgen_ai.tool.output
llm_input_systemgen_ai.llm.input.system
llm_input_usergen_ai.llm.input.user
llm_outputgen_ai.llm.output
llm_contextgen_ai.llm.context

Custom User Attributes

To attach business-level metadata to spans, prefix your keys with fiddler.span.user.:
# Source field: custom_attributes = {"session_type": "onboarding", "region": "us-west"}
# Maps to:
span.attributes["fiddler.span.user.session_type"] = "onboarding"
span.attributes["fiddler.span.user.region"]       = "us-west"
These attributes are indexed and queryable in Fiddler’s Trace Explorer.

Parsing gen_ai.input.messages

The gen_ai.input.messages attribute is a JSON array of chat-style message objects. Fiddler expects it split into two separate attributes:
Output attributeContent
gen_ai.llm.input.userText content of the last message with role: user
gen_ai.llm.contextAll other messages (conversation history), formatted as [role]: content
Example input:
[
  {"role": "system",    "content": "You are a helpful assistant."},
  {"role": "user",      "content": "What is the capital of France?"},
  {"role": "assistant", "content": "Paris."},
  {"role": "user",      "content": "And Germany?"}
]
Resulting mapping:
AttributeValue
gen_ai.llm.input.user"And Germany?"
gen_ai.llm.context"[system]: You are a helpful assistant.\n\n[user]: What is the capital of France?\n\n[assistant]: Paris."
If no user message is found, all messages are placed in gen_ai.llm.context and gen_ai.llm.input.user is omitted.

Span Kind and Status Mappings

Span kind (SpanKind enum):
String valueProtobuf value
INTERNALSpanKind.INTERNAL (default)
SERVERSpanKind.SERVER
CLIENTSpanKind.CLIENT
PRODUCERSpanKind.PRODUCER
CONSUMERSpanKind.CONSUMER
Status code (StatusCode enum):
String valueProtobuf value
OKStatusCode.OK (default)
ERRORStatusCode.ERROR
UNSETStatusCode.UNSET

Minimal End-to-End Example

import gzip
import httpx
from opentelemetry.proto.collector.trace.v1.trace_service_pb2 import ExportTraceServiceRequest
from opentelemetry.proto.common.v1.common_pb2 import AnyValue, InstrumentationScope, KeyValue
from opentelemetry.proto.resource.v1.resource_pb2 import Resource
from opentelemetry.proto.trace.v1.trace_pb2 import ResourceSpans, ScopeSpans, Span, Status
from opentelemetry.trace import SpanKind
from opentelemetry.trace.status import StatusCode

FIDDLER_URL    = "https://your-instance.fiddler.ai"
APP_UUID       = "550e8400-e29b-41d4-a716-446655440000"
FIDDLER_TOKEN  = "your-api-token"

# Build a single LLM span
span = Span(
    trace_id=bytes.fromhex("4bf92f3577b34da6a3ce929d0e0e4736"),
    span_id=bytes.fromhex("00f067aa0ba902b7"),
    name="chat",
    kind=SpanKind.INTERNAL.value,
    start_time_unix_nano=1_700_000_000_000_000_000,
    end_time_unix_nano=1_700_000_001_000_000_000,
    status=Status(code=StatusCode.OK.value),
    attributes=[
        KeyValue(key="gen_ai.agent.name",         value=AnyValue(string_value="my-agent")),
        KeyValue(key="fiddler.span.type",         value=AnyValue(string_value="llm")),
        KeyValue(key="gen_ai.request.model",      value=AnyValue(string_value="gpt-4o")),
        KeyValue(key="gen_ai.llm.input.user",     value=AnyValue(string_value="What is the capital of France?")),
        KeyValue(key="gen_ai.llm.output",         value=AnyValue(string_value="Paris.")),
        # Numeric attributes — use int_value so Fiddler registers them as numeric columns
        KeyValue(key="gen_ai.usage.input_tokens", value=AnyValue(int_value=12)),
        KeyValue(key="gen_ai.usage.output_tokens",value=AnyValue(int_value=3)),
        KeyValue(key="gen_ai.usage.total_tokens", value=AnyValue(int_value=15)),
        # Custom user attributes — use the AnyValue field matching each value's type
        KeyValue(key="fiddler.span.user.confidence", value=AnyValue(double_value=0.97)),
        KeyValue(key="fiddler.span.user.region",     value=AnyValue(string_value="us-west")),
        KeyValue(key="fiddler.span.user.is_internal", value=AnyValue(bool_value=False)),
    ],
)

# Wrap in ResourceSpans
resource_spans = ResourceSpans(
    resource=Resource(attributes=[
        KeyValue(key="application.id", value=AnyValue(string_value=APP_UUID)),
        KeyValue(key="service.name",   value=AnyValue(string_value="my-agent-service")),
    ]),
    scope_spans=[ScopeSpans(
        scope=InstrumentationScope(name="my-tracer", version="1.0.0"),
        spans=[span],
    )],
)

# Serialize and compress
payload = ExportTraceServiceRequest(resource_spans=[resource_spans]).SerializeToString()

# Send
response = httpx.post(
    f"{FIDDLER_URL}/v1/traces",
    content=gzip.compress(payload),
    headers={
        "Authorization": f"Bearer {FIDDLER_TOKEN}",
        "fiddler-application-id": APP_UUID,
        "Content-Type": "application/x-protobuf",
        "Content-Encoding": "gzip",
    },
    timeout=30.0,
)
response.raise_for_status()