Class OtlpLLMCallExporter
java.lang.Object
com.tnsai.quality.observability.otlp.OtlpLLMCallExporter
- All Implemented Interfaces:
com.tnsai.llm.observability.LLMCallPublisher
public final class OtlpLLMCallExporter
extends Object
implements com.tnsai.llm.observability.LLMCallPublisher
LLMCallPublisher that exports each captured call as an
OpenTelemetry span plus a triplet of GenAI metrics
(token-usage counter, cost histogram, latency histogram).
Span / attribute names follow the OpenTelemetry GenAI semantic conventions so the wire is drop-in for Langfuse, LangWatch, Phoenix, Honeycomb, Tempo, Loki, and any other OTLP collector — consumers don't need a TnsAI-specific dashboard.
Wire shape
Per call: one CLIENT span (kind = SpanKind.CLIENT) named
chat <model> (or chat_stream <model> for streaming
calls), populated with the standard GenAI attribute set plus a
gen_ai.usage.cost_usd extension and TnsAI's correlation fields
(tenant, session, agent, role) drawn from EventContext.
Per call: three metric records keyed by gen_ai.system +
gen_ai.request.model:
gen_ai.client.token.usage— long counter, partitioned bygen_ai.token.type∈ {input,output,cached,reasoning}. Components are emitted only when their token count is > 0.gen_ai.client.cost.usd— double histogram, billed total in USD. Always emitted (zero recorded for free / failed calls).gen_ai.client.operation.duration— double histogram in milliseconds. Always emitted.
Failures
Failed calls produce a span withStatusCode.ERROR, the error
class set as error.type, and (when present) the HTTP status
mapped to http.response.status_code. Metrics still record
(token counts may be zero; cost typically zero).
Retroactive timing
The framework's capture wrapper (CapturingLLMClient) hands us
a finished call. We bracket the span with the recorded
startedAt / completedAt timestamps so the duration on
the dashboard matches the captured elapsed even though the span was
created post-hoc.
Thread safety
The OTel API is thread-safe; this exporter holds only the meter's pre-built instruments and the tracer reference. Many concurrentpublish(com.tnsai.llm.observability.LLMCallLog) calls are safe.
Composition
For PII scrubbing or sampling, wrap this exporter with theRedactingLLMCallPublisher /
SamplingLLMCallPublisher
decorators — same module. Order: redaction inside, sampling
outside, so dropped events skip the redactor cost entirely.- Since:
- 0.10.1 (TNS-374)
-
Nested Class Summary
Nested Classes -
Field Summary
Fields inherited from interface com.tnsai.llm.observability.LLMCallPublisher
NOOP -
Constructor Summary
ConstructorsConstructorDescriptionOtlpLLMCallExporter(io.opentelemetry.api.trace.Tracer tracer, io.opentelemetry.api.metrics.Meter meter) Build an exporter from a tracer + meter. -
Method Summary
Modifier and TypeMethodDescriptionstatic OtlpLLMCallExporter.Builderbuilder()voidpublish(com.tnsai.llm.observability.LLMCallLog call)
-
Constructor Details
-
OtlpLLMCallExporter
public OtlpLLMCallExporter(io.opentelemetry.api.trace.Tracer tracer, io.opentelemetry.api.metrics.Meter meter) Build an exporter from a tracer + meter. Prefer the static factorybuilder()unless you already hold both handles.
-
-
Method Details
-
builder
-
publish
public void publish(com.tnsai.llm.observability.LLMCallLog call) - Specified by:
publishin interfacecom.tnsai.llm.observability.LLMCallPublisher
-