- Create production sessions from real traffic. Uncorrelated spans become production Sessions that Monitors evaluate automatically. Spans that name a test case become a non-production session instead, which no Monitor scores. See Monitor Real User Traffic via OpenTelemetry.
- Enrich existing Galtea Traces. Spans correlated to a Trace become Span records on it. Spans of a Trace that already exists explains how a span names its Trace.
Endpoint and authentication
Configure your OTel exporter to send traces to the Galtea collector. Every request must carry anAuthorization: Bearer <Galtea-API-key> header — without it, the gateway returns 401 Unauthorized before the request reaches the collector.
All examples below use the Galtea platform hostname: otel.platform.prod-main.galtea.ai.
Using a dedicated tenant deployment?
Using a dedicated tenant deployment?
If your organization has a private Galtea deployment, replace the hostname with your tenant-specific endpoint:Your Galtea account team will provide the exact hostname during onboarding.
Configure your exporter
- Environment variables
- Python code
- OTel Collector
The OpenTelemetry SDK reads endpoint and headers from environment variables. Use shell substitution to expand The same
${GALTEA_API_KEY} before the value is stored in OTEL_EXPORTER_OTLP_TRACES_HEADERS:- HTTP (port 4318)
- gRPC (port 4317)
${GALTEA_API_KEY} syntax works in a docker-compose.yml environment: block, but only when GALTEA_API_KEY is set in the shell that runs Compose or in a .env file Compose loads. Docker Compose substitutes ${...} from those sources when it renders the config. It does not read another entry in the same environment: block. If the variable is unset there, the exporter receives the literal string ${GALTEA_API_KEY} and the gateway returns 401 Unauthorized.Use
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT (the per-signal variant), not OTEL_EXPORTER_OTLP_ENDPOINT. The base variable auto-appends /v1/traces to the URL, but the Galtea collector expects the /otel/traces path.Verify your key and endpoint
A quick way to confirm the header is being applied correctly is to POST an empty payload and inspect the status code:How span content maps to Span records
When a span becomes a Span record — whether it enriches an existing trace or is assembled into a production conversation — its input and output are filled per field, using the first source that provides a value:galtea.span.*explicit attributes — full control, always wins.gen_ai.*GenAI semantic conventions — the OpenTelemetry standard for LLM telemetry.- Recognized managed-platform span shapes — platform-specific formats Galtea knows.
- A configured span-source rule —
otelInputSourceRuleorotelOutputSourceRuleon the Version, the Product, or the organization, which reads the field out of a named non-LLM span. See Recover the turn input from a non-LLM span.
{} or [] never becomes the turn input. A configured rule that fires on no span of the turn leaves that field empty; it never falls back. See Monitor real user traffic.
If you set no attributes, spans are still ingested. A span type Galtea does not recognize is also ingested: Galtea normalizes the value to a known span type and keeps your original label in the span’s metadata, under the key galtea.span.original_type.
The span type follows its own order, because only galtea.span.type is a Galtea attribute:
galtea.span.type— always wins.- The span kind your platform already declares — MLflow’s
mlflow.spanType, or a span shape that names the kind by itself. gen_ai.operation.name— the OpenTelemetry GenAI operation, such aschatorembeddings.SPAN— when nothing above names a kind.
RETRIEVER span becomes a RETRIEVER span, which is what Galtea reads retrieval context from. A vendor word Galtea spells differently still maps, so MLflow’s LLM becomes GENERATION.
These keys had different names before version 5.0.0, and Galtea still reads the old ones. See
Dataset, Trace, and Span Renames if your collector already
sends them.
- Galtea span attributes
- OTel GenAI conventions
- Managed platforms
span.name→namestartTimeUnixNano/endTimeUnixNano→startTime/endTime+latencyMs(computed)span.status→error(fallback whengaltea.span.erroris not set; only forSTATUS_CODE_ERROR)parentSpanId→parentTraceId(parent-child hierarchy)
metadata field, so no data is lost.
Token usage and cost
Galtea reads the token counts your spans report and adds them up per turn. Send them with the OpenTelemetry GenAI keys:
A turn’s
inputTokens, outputTokens and tokens are the sum over every span of that turn, so a turn that makes three LLM calls reports the three calls added together. A span that reports no usage adds nothing. Send no usage at all and the turn keeps what Galtea already had: nothing when no Model is linked to the Version, or the estimate Galtea derived from the turn text when one is.
Galtea also recognizes the usage a managed platform reports in its own format. MLflow Tracing’s mlflow.chat.tokenUsage works with no Galtea attribute. When a span carries both, the gen_ai.usage.* keys win, field by field.
Cost is derived from those counts and the per-token price of the Model linked to the Version. Link a Model with a known price and the turn’s cost is filled. With no Model, or with no price for it, cost stays empty rather than zero.
A reported count replaces one Galtea estimated earlier: your own figure is the accurate one. A cost you reported yourself is kept, and the new counts do not replace it: Galtea recomputes only a cost it derived from the counts and the Model price, never a different figure you set. Spans of a turn may arrive in several export batches, and each batch raises the turn’s total instead of replacing it. Re-sending the same spans changes nothing.
Retrieval context (RAG): to record what your system retrieved, type a span as
RETRIEVER and put the retrieved data in its output (for example via galtea.span.output). This is how Galtea reads retrieval context: it is derived from your RETRIEVER spans, and the output has no required shape.Attributes that route a conversation
The keys above, exceptgaltea.trace.id, fill a Span record. The keys below decide where Galtea stores the spans.
Galtea stores each conversation under a Product and one of its Versions. Your telemetry answers three questions:
- Which product? A resource attribute names it.
- Which version of that product? A resource attribute names it, or describes it so that Galtea finds or creates it.
- Which conversation? A span attribute groups spans into one Session.
A span that names an existing Trace skips the three questions. That is the usual case when Galtea calls your endpoint during Direct Inference. See Spans of a Trace that already exists. Every other span needs all three answers, even when its conversation key joins a session that already exists.
1. Which product
Send one of these on the resource. Skip this step when you sendgaltea.version.id, because a Version ID already names its product.
When the resource carries both,
galtea.product.id wins. A galtea.product.id that is empty or not text is rejected, and Galtea does not try the key values.
2. Which version
Send one of these on the resource. Galtea uses the first one present, in the order of the table. When that one fails, Galtea does not try the next one.
With facts, Galtea finds the Version of the product that holds exactly these facts, or creates it, with the rules of Get or Create Version. The same facts reuse one Version, whatever the order of the attributes. One changed value creates a new Version, so a new release needs no change in Galtea. A fact value is text or an integer.
A fact key such as
model or model_name also links a model to a Version that Galtea creates, with the rules of Model facts. So galtea.version.fact.model can link the model, and a version attribute such as app.agent.model never does.
3. Which conversation
Set a conversation key names the managed platforms whose key Galtea derives. A span that carries no key waits for a later batch of its trace that does, up to a size limit. Past that limit, Galtea rejects it at once. See Batched Exporters and the Conversation Key.
Spans of a Trace that already exists
A Trace already exists when Galtea calls your endpoint during Direct Inference, or when your code created it with the SDK, the CLI or the API. Its Session already has its Version, and a Direct Inference session also has its Test Case. Your spans only need to name the Trace. Galtea then ignores the product, version, conversation and test case attributes.
Galtea tries the trace ID first, then
galtea.trace.id. A galtea.trace.id that names no Trace your API key can see is rejected, and Galtea does not route the span by product, version or conversation. A span with neither goes through the three questions, unless its trace ID matches a Direct Inference turn that was removed.
What Galtea rejects
Galtea never guesses where your data belongs. It rejects the spans of one resource, and the rest of the payload still ingests, in these cases:- No version. The resource carries no
galtea.version.id, nogaltea.version.fact.*attribute, and no attribute of the version attribute list in force for its product: the product’s own list, or else the organization list. - No product. The resource carries
galtea.version.fact.*attributes or listed version attributes, and nogaltea.product.id, and its product key attributes match no single product of your organization. That means no product holds those key values, or two products with keys of the same size do. A resource that routes by version attributes gets this reason only when it carries an attribute of the organization list. Otherwise the reason is no version. - Invalid facts. A value is not text or an integer, a key after the
galtea.version.fact.prefix is empty, allgaltea.version.fact.*values are blank, or the facts have more than 32 keys or more than 2000 bytes in all. For version attributes, a blank or null value counts as absent instead. - Unknown version or product. The Version or the product does not exist, or your API key cannot see it. With
galtea.product.id, this also covers an API key whose role cannot create a Version, when no Version holds the facts yet. When the key values found the product, Galtea reports that case as no product.
- The test case does not exist, or it belongs to another product than the Version.
- Two spans of one conversation name different test case IDs, or two resources do.
- The conversation names a test case, and its key already holds another session on that Version: a production session, or one linked to another test case.
- The conversation is new, and the payload already opened 100 new conversations, the default limit.
- The session is closed, and the product’s Closed Session Inference Creation Policy is
REJECT. Only the spans of the new turn are rejected.
galtea.trace.id names a live Trace. It does not assemble that span into a new session.
The export response counts the rejected spans in partialSuccess.rejectedSpans. Its partialSuccess.errorMessage names each reason that applied, so your collector log says what to change.
Monitor Real User Traffic via OpenTelemetry walks through the full setup, step by step.
Learn More
Monitor Real User Traffic via OpenTelemetry
Turn your live traffic’s traces into production sessions that Monitors evaluate.
W3C Trace Context Propagation
Attach OTel spans to the test runs Galtea triggers via Direct Inference.