Skip to content
EvalBook

OpenTelemetry

Sending OTLP/HTTP traces to a project, how GenAI semantic-convention attributes are mapped onto EvalBook spans, and what the adapter does with content, environments and schema versions.

A project accepts OpenTelemetry traces over OTLP/HTTP, in JSON or protobuf. The adapter is versioned: it is pinned to semantic conventions 1.37.0 and stamps every span it reads with otel-semconv-1.37.0+evalbook.1, so a later mapping can never silently reinterpret an earlier span. The producer's original attributes are kept verbatim; only content moves, into bodies, where the capture policy applies.

Status (25 October 2026). The traces component is built in the application (ingestion, explorer, settings, exports and deletions pages are live in the shell); its requirements EV-002 to EV-009 are not yet marked implemented in the requirements ledger and are being verified now. Nothing on this page is marked verified. The capability register lists OTLP ingestion as in progress and observation-only; it has not been run against a real collector.

The endpoint

POST /api/v1/otlp/v1/traces          # on the app's origin
Authorization: Bearer evb_…
Content-Type: application/x-protobuf    # or application/json
Content-Encoding: gzip                   # gzip, deflate and br are accepted

The same API key and scope as Ingestion: ingest, bound to the key's organization, project and environment. The body is decompressed under the 5 MB cap, and an export of more than 1,000 spans is 413; nothing is stored in either case. Any other media type is 415.

OTLP/JSON is read as the protobuf JSON mapping defines it: lowerCamelCase field names (snake_case accepted), hex trace and span ids, 64-bit integers as strings or numbers, enums as numbers or names. Unknown fields are ignored. A trace id must be 32 hex characters and a span id 16; an all-zero id is invalid.

How a span is mapped

OpenTelemetry EvalBook span
gen_ai.operation.name = chat, text_completion, generate_content kind: llm
embeddings / execute_tool / invoke_agent, create_agent embedding / tool / step
http.request.method (or http.method) http
no parent / anything else run / other
evalbook.kind, evalbook.purpose explicit overrides when they are valid values
gen_ai.provider.name (gen_ai.system before 1.37) model.provider
gen_ai.request.model, gen_ai.response.model model.requested, model.resolved
gen_ai.request.temperature, gen_ai.request.max_tokens model.temperature, model.max_tokens
gen_ai.usage.input_tokens / output_tokens (also prompt_tokens / completion_tokens) usage
gen_ai.usage.cache_read.input_tokens usage.cached_tokens
evalbook.ledger_ref, evalbook.cost.{amount,currency,source} usage.ledger_ref, usage.cost
gen_ai.tool.name, gen_ai.tool.call.id tool
status.code = 2, status.message, error.type or the exception event's exception.type status: error, error
deployment.environment.name (or deployment.environment) on the resource the environment

The OTLP span kind is kept as the attribute otel.span_kind. Resource attributes are stored beside the span's own (at most 128 each) and shown in the explorer under Resource (from OpenTelemetry). Arrays are flattened into indexed rows (at most 128 elements) and nested lists into dotted keys; a 64-bit integer beyond 2^53 is kept as its decimal text.

Content never stays an attribute

These keys are moved into the span's bodies, where the project's capture policy decides what is stored:

  • gen_ai.input.messages, gen_ai.system_instructions, gen_ai.tool.definitions, gen_ai.prompt, input.value → input
  • gen_ai.output.messages, gen_ai.completion, output.value → output
  • gen_ai.tool.call.arguments → tool arguments; gen_ai.tool.call.result → tool result
  • the events gen_ai.content.prompt, gen_ai.content.completion, gen_ai.{system,user,assistant,tool}.message and gen_ai.choice: their content moves to input or output and the event is kept with moved_to set

A content string that is JSON is parsed. Under metadata_only none of it is stored at all.

Environments

One export may carry resources from several environments. The key's binding wins: a resource naming an environment the key may not send to has its spans refused, listed on the receipt of the key's own environment. Otherwise each environment named gets its own receipt; a resource naming none lands in the key's environment, or default.

Idempotency and the answer

The batch id is otlp:<Idempotency-Key header> when you send one, else otlp:<sha256 of the bytes>, with the environment appended when an export splits across several. A resend of identical bytes answers the original receipts.

A successful export answers 200 with an ExportTraceServiceResponse. Refused rows appear in partialSuccess.rejectedSpans with an errorMessage; the JSON answer also carries EvalBook's receipts. 413, 415, 429 (with Retry-After) and 400 INVALID_OTLP refuse the whole export; for protobuf the error body is a google.rpc.Status.

Schema versions

The scope's schema URL, else the resource's, is stored as the span's source_schema and shown in the span drawer under Source schema. Spans declaring another revision are still read with the 1.37.0 mapping, their attributes kept as sent, and the receipt carries a SEMCONV_VERSION_DIFFERS warning.

Where this stands

The capability register lists otlp.traces.ingest as in progress and observation-only, against the OTLP specification and semantic conventions 1.37.0. Until it has been exercised against a real collector, treat the mapping above as the contract the adapter is built to, and check the receipts and the span drawer after your first export. See Capability register.