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 by gen_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 with StatusCode.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 concurrent publish(com.tnsai.llm.observability.LLMCallLog) calls are safe.

Composition

For PII scrubbing or sampling, wrap this exporter with the RedactingLLMCallPublisher / SamplingLLMCallPublisher decorators — same module. Order: redaction inside, sampling outside, so dropped events skip the redactor cost entirely.
Since:
0.10.1 (TNS-374)
  • 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 factory builder() unless you already hold both handles.
  • Method Details

    • builder

      public static OtlpLLMCallExporter.Builder builder()
    • publish

      public void publish(com.tnsai.llm.observability.LLMCallLog call)
      Specified by:
      publish in interface com.tnsai.llm.observability.LLMCallPublisher