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→ inputgen_ai.output.messages,gen_ai.completion,output.value→ outputgen_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}.messageandgen_ai.choice: their content moves to input or output and the event is kept withmoved_toset
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.