Skip to content

Add GENERATION span attributes for streaming (semconv) - #2689

Open
connectsudhindra-gif wants to merge 3 commits into
truera:mainfrom
connectsudhindra-gif:add-streaming-generation-span-attrs
Open

Add GENERATION span attributes for streaming (semconv)#2689
connectsudhindra-gif wants to merge 3 commits into
truera:mainfrom
connectsudhindra-gif:add-streaming-generation-span-attrs

Conversation

@connectsudhindra-gif

Copy link
Copy Markdown
Contributor

Summary

Part of #2442 (streaming LLM instrumentation). This is PR 1 of several small, independently-revertable PRs toward that issue, split so each piece can land (or be reverted) on its own.

Adds the four new GENERATION span attributes the issue asks for, as a pure additive semconv change with no behavior change:

  • ai.observability.generation.is_streaming
  • ai.observability.generation.time_to_first_token_ms
  • ai.observability.generation.tokens_per_second
  • ai.observability.generation.chunks_received

Nothing populates these yet — that's the next PR (wiring them up for the OpenAI provider's stream=True path, extending the existing chunk-handling in OpenAIEndpoint._handle_response).

Scope of the overall issue

Issue #2442 bundles several genuinely separate efforts: semconv attributes (this PR), OpenAI streaming instrumentation, LangChain stream()/astream() instrumentation, a generic async-generator pattern, mid-stream/partial-output evaluation for guardrails, and a dashboard latency-view UI change. Splitting these into separate PRs keeps each one small, reviewable, and safe to revert independently rather than landing (or blocking on) one large, multi-part change.

Test plan

  • ruff check / ruff format --check (pinned 0.5.5) pass.
  • Verified the new attribute keys resolve correctly, e.g. SpanAttributes.GENERATION.IS_STREAMING == "ai.observability.generation.is_streaming".
  • No existing tests reference trulens.otel.semconv.trace.SpanAttributes.GENERATION (confirmed via repo-wide search), and this package isn't covered by the tests/unit/static API golden-snapshot tests, so nothing needed regenerating.

@joshreini1 joshreini1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The semconv additions don't align with the official OTel GenAI semantic conventions for streaming instrumentation. The spec defines gen_ai.request.stream (boolean) and gen_ai.response.time_to_first_chunk (double, seconds) as the canonical streaming attributes, and recommends Histogram metrics (not span attributes) for per-chunk latency and throughput. This PR adds custom ai.observability.generation.* attributes with wrong units (milliseconds vs seconds) and derived metrics (tokens_per_second, chunks_received) that conflict with the spec's instrumentation model. Request changes: 3 blockers.


base = BASE_SCOPE + ".generation"

IS_STREAMING = base + ".is_streaming"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace IS_STREAMING with the official gen_ai.request.stream attribute (boolean) already defined in the OTel GenAI spec. The spec explicitly defines this as the standard way to flag streaming requests on spans and events. Duplicating it under the ai.observability.* namespace creates confusion and breaks interoperability with OTel-native tooling.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

IS_STREAMING = base + ".is_streaming"
"""Whether this generation call used streaming (e.g. `stream=True`)."""

TIME_TO_FIRST_TOKEN_MS = base + ".time_to_first_token_ms"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change TIME_TO_FIRST_TOKEN_MS to align with the official gen_ai.response.time_to_first_chunk attribute (double, unit=seconds, not milliseconds). The OTel GenAI spec defines this attribute for both span attributes and the gen_ai.client.inference.operation.details event. Using milliseconds breaks interoperability and makes this incompatible with the standard Histogram metric gen_ai.client.operation.time_to_first_chunk (which also uses seconds with explicit bucket boundaries).

"""Milliseconds between issuing the request and receiving the first
streamed chunk. Only set when `IS_STREAMING` is `True`."""

TOKENS_PER_SECOND = base + ".tokens_per_second"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Drop TOKENS_PER_SECOND and CHUNKS_RECEIVED — these are derived metrics that conflict with the OTel GenAI instrumentation model. The spec expects tokens/sec to be computed from gen_ai.client.token.usage + gen_ai.client.operation.duration, and inter-chunk latency to be reported as Histogram metrics (gen_ai.client.operation.time_per_output_chunk), not as span attributes. Span attributes should capture request/response metadata, not aggregated performance metrics. If TruLens needs these for internal evaluation, compute them from the official attributes rather than storing redundant custom ones.

@@ -425,6 +425,22 @@ class GENERATION:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should streaming metadata live on a gen_ai.client.inference.operation.details event (lines 101-105) rather than as span attributes? The OTel GenAI spec defines that event for capturing per-request metadata including gen_ai.response.time_to_first_chunk and gen_ai.request.stream. The GenAIEvents class already exists in this file (lines 95-114) and includes the event name constant. If these will be populated on the next PR's OpenAI instrumentation, clarify whether they'll be set as span attributes, event attributes, or both.

= added 2 commits August 17, 2026 21:28
First step toward streaming instrumentation support (truera#2442). Adds the
attribute keys the issue asks for as a pure, additive semconv change
with no behavior change, so it can land and be reverted independently
of the instrumentation work that will populate them:

- ai.observability.generation.is_streaming
- ai.observability.generation.time_to_first_token_ms
- ai.observability.generation.tokens_per_second
- ai.observability.generation.chunks_received

Follow-up PRs will wire these up for the OpenAI provider's stream=True
path and add an example notebook; LangChain streaming, the generic
async-generator pattern, mid-stream guardrail evaluation, and the
dashboard latency view are each separate, larger efforts tracked
against truera#2442 but out of scope here.
…ument scope of internal-only ones

Reviewer flagged 3 blockers: the custom ai.observability.generation.*
streaming attributes don't align with the OTel GenAI spec's canonical
gen_ai.request.stream (bool) / gen_ai.response.time_to_first_chunk
(double, seconds), and the spec models per-chunk throughput as
Histogram metrics rather than span attributes.

- Added GenAIAttributes.REQUEST.STREAM (gen_ai.request.stream) and a
  new GenAIAttributes.RESPONSE class with TIME_TO_FIRST_CHUNK
  (gen_ai.response.time_to_first_chunk), matching this file's existing
  pattern of emitting gen_ai.* attributes alongside the
  ai.observability.* ones for interoperability. These are the
  canonical counterparts to IS_STREAMING and TIME_TO_FIRST_TOKEN_MS.
- TOKENS_PER_SECOND and CHUNKS_RECEIVED have no canonical counterpart:
  the spec wants these as Histogram metrics
  (gen_ai.client.operation.time_per_output_chunk), and this codebase
  has no OTel Metrics/MeterProvider pipeline to emit them through yet.
  Documented them as TruLens-internal (ai.observability.* only) rather
  than silently leaving them looking like they're meant to be
  canonical semconv.

Still additive/no-behavior-change, consistent with this PR's original
scope -- nothing populates any of these attributes yet.
@connectsudhindra-gif
connectsudhindra-gif force-pushed the add-streaming-generation-span-attrs branch from c3cfd37 to 1880a70 Compare August 18, 2026 02:54
@dosubot dosubot Bot added size:M This PR changes 30-99 lines, ignoring generated files. and removed size:S This PR changes 10-29 lines, ignoring generated files. labels Aug 18, 2026
…uplicate them

Reviewer's inline comments (not just the summary) explicitly objected
to keeping ai.observability.generation.is_streaming and
.time_to_first_token_ms alongside the new canonical gen_ai.* ones:
"Duplicating it under the ai.observability.* namespace creates
confusion and breaks interoperability with OTel-native tooling."

Removed GENERATION.IS_STREAMING and GENERATION.TIME_TO_FIRST_TOKEN_MS
entirely. GenAIAttributes.REQUEST.STREAM and
GenAIAttributes.RESPONSE.TIME_TO_FIRST_CHUNK (from the previous
commit) are now the sole representations. This is a breaking rename
for the stacked instrumentation PRs (truera#2690/truera#2691/truera#2692), which are
being updated to match in the same pass.

TOKENS_PER_SECOND and CHUNKS_RECEIVED are intentionally left as-is
(TruLens-internal, documented as such) rather than dropped per the
reviewer's other comment -- replying on the review thread to explain
why, since dropping them removes already-shipped, tested
functionality rather than just renaming a key.
connectsudhindra-gif pushed a commit to connectsudhindra-gif/trulens that referenced this pull request Aug 18, 2026
…tream/TTFT

SpanAttributes.GENERATION.IS_STREAMING and .TIME_TO_FIRST_TOKEN_MS no
longer exist (removed upstream per review feedback). Switch to
GenAIAttributes.REQUEST.STREAM (bool) and
GenAIAttributes.RESPONSE.TIME_TO_FIRST_CHUNK (seconds, not ms -- drop
the *1000 conversion). CHUNKS_RECEIVED/TOKENS_PER_SECOND are unaffected,
still TruLens-internal.
connectsudhindra-gif pushed a commit to connectsudhindra-gif/trulens that referenced this pull request Aug 18, 2026
_instrument_stream_span_attributes divided completion_tokens by
elapsed duration with only a `duration_s > 0` guard, which lets a
near-instant stream produce a misleadingly huge tokens/sec figure.
Added a _MIN_TOKENS_PER_SECOND_DURATION_S floor below which
TOKENS_PER_SECOND is simply not recorded.

Updated the affected test to fake the clock (real synchronous mock
consumption is faster than the new duration floor).

(The TTFT docstring clarification from the original version of this
commit is dropped: TIME_TO_FIRST_TOKEN_MS no longer exists -- it was
replaced by the canonical GenAIAttributes.RESPONSE.TIME_TO_FIRST_CHUNK
per truera#2689's review feedback.)
connectsudhindra-gif pushed a commit to connectsudhindra-gif/trulens that referenced this pull request Aug 18, 2026
SpanAttributes.GENERATION.IS_STREAMING and .TIME_TO_FIRST_TOKEN_MS no
longer exist. Switch the notebook's streaming-metrics cell to
GenAIAttributes.REQUEST.STREAM / .RESPONSE.TIME_TO_FIRST_CHUNK.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants