> ## Documentation Index
> Fetch the complete documentation index at: https://docs.galtea.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Monitor Production via OpenTelemetry

> What Galtea's OpenTelemetry compatibility covers, what your setup needs before you start, and how OTLP spans become production sessions and traces.

If your AI system already emits [OpenTelemetry](https://opentelemetry.io/) (OTel) traces, you can send them to Galtea. This page explains what "Galtea is OTel compatible" means in practice, what your setup needs **before** you start, and which span shapes Galtea understands.

<Info>
  There are three ways to get traces into Galtea. They use two endpoints: the **REST Trace API** and the **public OTel endpoint** (the OTLP collector).

  1. **Galtea SDK** — decorate your code with `@trace`. The SDK posts to the REST Trace API. See [Tracing Agent Operations](/sdk/tutorials/tracing-agent-operations).
  2. **OpenTelemetry export** — send OTLP traces to the public OTel endpoint. This is what this page covers.
  3. **REST API** — create traces directly through the [REST Trace API](/api-reference) with your API key. Same endpoint the SDK uses.
</Info>

## What "OTel Compatible" Means

Galtea accepts standard OTLP traces (OTLP is the OpenTelemetry export protocol) at its collector endpoint. To configure your exporter or your own OTel Collector, follow [Point your OTel exporter to the Galtea collector](/sdk/tutorials/direct-inferences-and-evaluations-from-platform#3-point-your-otel-exporter-to-the-galtea-collector) — the endpoint, authentication, and configuration are the same for everything on this page.

Once spans arrive, each one takes exactly one of three paths:

* **Enrich an existing inference result.** Spans correlated to an existing [Inference Result](/concepts/product/version/session/inference-result) become [Trace](/concepts/product/version/session/trace) records attached to it. Correlation happens through a shared W3C trace ID (when Galtea propagated a `traceparent` header during [Direct Inference](/sdk/tutorials/direct-inferences-and-evaluations-from-platform#option-3-w3c-trace-context-propagation)) or through an explicit `galtea.inference_result.id` span attribute.
* **Create a production conversation.** Uncorrelated spans that carry both a conversation key and a product-version route are assembled into production [Sessions](/concepts/product/version/session) with conversation turns. See [Create Production Conversations from OTel Traces](#create-production-conversations-from-otel-traces).
* **Rejected.** Uncorrelated spans missing a conversation key, a version route, or both are rejected. A span that names an inference result which does not exist (or is not visible to your API key) is also rejected; it does not fall back to conversation assembly. Galtea never guesses where your data belongs.

## Before You Start: A One-Minute Eligibility Check

You can use this path if you can do **at least one** of the following:

1. **Add `galtea.*` attributes to your spans.** You control the instrumentation code.
2. **Set resource attributes on your own OTel Collector.** This works even when the spans are emitted by a managed platform you cannot edit.
3. **Propagate a `traceparent` header Galtea mints.** Available only with [Direct Inference](/sdk/tutorials/direct-inferences-and-evaluations-from-platform), where Galtea calls your endpoint.
4. **Your platform already emits a recognized span shape.** Either the OpenTelemetry [GenAI semantic conventions](https://opentelemetry.io/docs/specs/semconv/gen-ai/) or a supported managed-platform shape (see [How Span Content Maps to Trace Records](#how-span-content-maps-to-trace-records)).

Find your situation:

| Your situation                                                                                    | What works                                                                                                                                                                                                                                                                                                                                   |
| ------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| You control the instrumentation and can add span attributes                                       | Full control. Set the `galtea.session.custom_id` span attribute and the `galtea.version.id` resource attribute to create production conversations, and `galtea.trace.*` span attributes to fill each trace's input and output.                                                                                                               |
| A managed platform emits your spans and you cannot edit them, but you run your own OTel Collector | Set `galtea.version.id` once on the Collector as a resource attribute. If the platform is built on Temporal, the conversation key is derived automatically, and each turn's input and output are read automatically from the platform's span shapes (see [How Span Content Maps to Trace Records](#how-span-content-maps-to-trace-records)). |
| Your service is OTel-instrumented and Galtea calls it via Direct Inference                        | Enable `traceparent` propagation on the endpoint connection. Your spans enrich the inference results Galtea creates. No Galtea attributes needed.                                                                                                                                                                                            |
| Your platform emits the OpenTelemetry GenAI semantic conventions                                  | Input and output map automatically from the `gen_ai.*` spans, with zero Galtea-specific attributes — whether the spans enrich an existing inference result or are assembled into a new production conversation. To create new sessions you still need a conversation key and a version route.                                                |
| None of the above                                                                                 | This path does not work for you yet. Use the [Galtea SDK](/sdk/tutorials/tracing-agent-operations) or the [REST API](/api-reference) instead.                                                                                                                                                                                                |

## Create Production Conversations from OTel Traces

Galtea can assemble uncorrelated spans into full multi-turn production conversations. It needs two pieces of information: a **conversation key** (which spans belong to the same conversation) and a **version route** (which product version the conversation belongs to).

### Set a Conversation Key

The conversation key decides which spans belong to the same Galtea session. Provide it in whichever way fits your setup.

<Tabs>
  <Tab title="Your own session ID">
    Set the `galtea.session.custom_id` **span attribute** to your own conversation identifier, for example your chat session ID. Spans carrying it are grouped into one Galtea session per distinct ID. Within a session, one OTel trace (all spans sharing a W3C trace ID) becomes one conversation turn.
  </Tab>

  <Tab title="Managed platform (vendor-derived)">
    Some platforms cannot attach `galtea.session.custom_id` to their spans. For these, Galtea derives the conversation key from a recognized vendor attribute, so you do not change the platform's instrumentation.

    Temporal is a workflow engine; Mistral's agent platform is built on it. For platforms built on Temporal, Galtea uses the `temporalWorkflowID` span attribute as the conversation key, and splits the conversation into turns at workflow-update spans (spans named `HandleUpdate:*`).
  </Tab>
</Tabs>

### Route to a Product Version

Assembly requires the `galtea.version.id` **resource attribute**. It routes the spans to a [Version](/concepts/product/version) of your product and is validated against the organization of the API key you authenticate with. Without it, uncorrelated spans are rejected.

A resource attribute describes the whole telemetry source, not a single span. So you can set it once on your own OTel Collector, even when a managed platform emits the spans and you cannot edit them:

```yaml theme={"system"}
# Add this to the Collector config from the setup section linked above.
# The otlp receiver, the batch processor, and the otlphttp/galtea exporter
# are defined there; here you only add the resource/galtea processor and
# reference it in the traces pipeline.
processors:
  resource/galtea:
    attributes:
      - key: galtea.version.id
        value: "<your-version-id>"
        action: upsert

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [resource/galtea, batch]
      exporters: [otlphttp/galtea]
```

### What You Get

* **Production sessions.** Sessions created this way are production sessions, so [Monitors](/concepts/product/monitor) evaluate them automatically.
* **Automatic input and output.** Each turn's input and output are read from the turn's own spans using the same per-field precedence as the enrichment path (`galtea.trace.*` > `gen_ai.*` > managed-platform span shapes). A managed platform whose LLM text lives only in `gen_ai.*` attributes or vendor span events — with no `galtea.trace.*` attributes — still produces turns with non-empty input and output. When a turn contains several LLM calls, the first prompt becomes the turn input and the last completion becomes the turn output. See [How Span Content Maps to Trace Records](#how-span-content-maps-to-trace-records). For turns answered without any LLM call (a templated or guardrail reply), see [Recover replies that do not come from the LLM](#recover-replies-that-do-not-come-from-the-llm).
* **Idempotent ingestion.** Re-sent or late span batches do not create duplicate sessions, turns, or traces. Later turns append to the same session.
* **Works together with the SDK.** If a session with the same custom ID already exists (for example, created by the SDK), ingestion attaches to it instead of creating a new one.

<Note>
  Two limits to plan for:

  * **Finished sessions reject new turns.** A finished session still accepts late traces for its existing turns, but does not open new ones. To start new turns, begin a new conversation with a fresh `galtea.session.custom_id` (or a new Temporal workflow).
  * **One payload can open at most 100 new conversations.** New sessions beyond that in the same OTLP payload are rejected (they appear in the export response's rejected-span count). Continuations of conversations that already exist do not count toward the limit. If you send a large backfill or a high-volume export, split it across smaller export batches. Self-hosted deployments can raise the limit with the `OTEL_MAX_NEW_CONVERSATIONS_PER_PAYLOAD` setting.
</Note>

## How Span Content Maps to Trace Records

When a span becomes a [Trace](/concepts/product/version/session/trace) record — whether it **enriches an existing inference result** or is **assembled into a production conversation** — its input and output are filled **per field**, using the first source that provides a value:

1. **`galtea.trace.*` explicit attributes** — full control, always wins.
2. **`gen_ai.*` GenAI semantic conventions** — the OpenTelemetry standard for LLM telemetry.
3. **Recognized managed-platform span shapes** — platform-specific formats Galtea knows.

Supported `gen_ai.*` forms:

* `gen_ai.input.messages` / `gen_ai.output.messages` (the current semantic conventions)
* the legacy `gen_ai.prompt` / `gen_ai.completion` attributes
* the GenAI content span events

<Tip>
  If your system is already instrumented with plain OpenTelemetry GenAI conventions, input and output map with **zero** Galtea-specific attributes.
</Tip>

For managed platforms, Galtea recognizes some span shapes automatically. For example, platforms whose telemetry carries the abraxas span-event format (such as Mistral's agent platform) are recognized without any configuration.

<Warning>
  Some common instrumentation styles do **not** populate input/output yet: Traceloop / OpenLLMetry (`gen_ai.prompt.0.content`-style flattened attributes) and OpenInference (`input.value`). Their data is still stored in the trace's `metadata` field, so nothing is lost, but evaluations that read the trace input and output will not see it.
</Warning>

For the full `galtea.trace.*` attribute table and the automatically mapped OTel fields, see [Controlling how spans map to Trace records](/sdk/tutorials/direct-inferences-and-evaluations-from-platform#4-controlling-how-spans-map-to-trace-records).

## Recover Replies That Do Not Come from the LLM

Some turns are answered without an LLM completion. A guardrail can replace the answer, a template can redirect the user, or a tool can return the final reply on its own. For these turns the user-visible answer lives on a non-LLM span. So by default the assembled turn output is empty, or it shows an internal label such as an intent-classifier tag, instead of the real reply.

The `otelOutputSourceRule` config field fixes this. It names the exact span and field that carry the real reply, so the turn shows the true answer. Set it on a [Product](/concepts/product) or a [Version](/concepts/product/version). The Version value overrides the Product value. Leave it `null` (the default) to keep the rule off.

The rule has four parts:

* **`spanName`** — the exact OTLP span name to match. For a Temporal (managed-platform) activity, this is the activity span name.
* **`attribute`** — the span attribute that holds the reply, stored as a JSON string.
* **`path`** — a JSONPath into that parsed JSON that points at the output text. JSONPath is a small query syntax for JSON, for example `$[0].message.content`.
* **`predicate`** (optional) — an object `{ path, equals }`. The rule applies only when the node at `predicate.path` equals `equals`. Use it when the user message and the assistant message share the same span and attribute, so you do not pick the user's own message as the reply.

Here is a complete rule:

```json theme={"system"}
{
  "spanName": "ExecuteActivity:save_reply",
  "attribute": "app.arguments",
  "path": "$[0].message.content",
  "predicate": { "path": "$[0].message.role", "equals": "assistant" }
}
```

Read it step by step. The assistant's reply is saved on the `ExecuteActivity:save_reply` span, inside the `app.arguments` attribute, which holds a JSON array. `$[0].message.content` is the reply text inside that array. The predicate keeps user messages out: the same span also carries the user's message with role `"user"`, so the predicate makes the rule fire only for role `"assistant"`.

<Note>
  This rule is a last resort. It fills only the turn output, and only when the turn has no LLM output. Galtea first tries the normal per-field sources (`galtea.trace.*`, then `gen_ai.*`, then recognized managed-platform span shapes). The rule applies only if none of those produced an output. Within one turn, the latest span with output by start time wins. So the rule recovers the reply when the reply-bearing span is emitted after the LLM or classifier span. This is the common classify-then-reply order.
</Note>

Set `otelOutputSourceRule` on a product or a version. In the Python SDK you can set it when you create a version. The field is `otel_output_source_rule`, and you pass the same four-part object shown above. The SDK does not set it on products or on version updates. To set it on a product, or to change it on an existing version, use the API or the dashboard. The version value wins over the product value.

## Learn More

<CardGroup cols={2}>
  <Card title="Exporter and Collector setup" icon="tower-broadcast" href="/sdk/tutorials/direct-inferences-and-evaluations-from-platform#3-point-your-otel-exporter-to-the-galtea-collector">
    Configure your OTel exporter or Collector to send traces to Galtea.
  </Card>

  <Card title="Monitor" icon="gauge-high" href="/concepts/product/monitor">
    The always-on rule that evaluates your production sessions automatically.
  </Card>

  <Card title="Tracing Agent Operations" icon="sitemap" href="/sdk/tutorials/tracing-agent-operations">
    Collect traces with the Galtea SDK's `@trace` decorator.
  </Card>

  <Card title="Trace" icon="timeline" href="/concepts/product/version/session/trace">
    What a Trace record is and which properties it holds.
  </Card>
</CardGroup>
