Skip to content

[opentelemetry-instrumentation-genai-langchain] Record streaming telemetry for the LangChain instrumentation - #482

Merged
lmolkova merged 5 commits into
open-telemetry:mainfrom
sfc-gh-zeningchen:langchain-streaming-telemetry
Aug 28, 2026
Merged

lmolkova merged 5 commits into
open-telemetry:mainfrom
sfc-gh-zeningchen:langchain-streaming-telemetry

Conversation

@sfc-gh-zeningchen

Copy link
Copy Markdown
Contributor

Description

Streamed LangChain calls never set gen_ai.request.stream or
gen_ai.response.time_to_first_chunk, and never recorded the
time_to_first_chunk / time_per_output_chunk histograms added in #269,
because the shared stream wrappers are the only producers of those values and a
callback-based instrumentation has no stream to wrap. This adds
GenAIInvocation.record_stream_chunk() to opentelemetry-util-genai as the
entry point for reporting chunk arrival without a stream, and implements
on_llm_new_token to call it once per output chunk. The capability lives in the
util rather than the instrumentation, per the direction on #8.

Fixes #481

Type of change

  • Bug fix (non-breaking change which fixes an issue)

How has this been tested?

New tests/test_streaming.py in the langchain package covers .stream(),
.astream(), a non-streamed .invoke() asserting no stream attributes or
metric points appear, and a parametrized case separating LangChain's
end-of-stream marker from a genuinely content-free chunk. These patch the
model's _stream / _astream rather than replaying a cassette, because the
assertions are about chunk timing, which a cassette does not reproduce. Two
tests in test_handler_metrics.py cover record_stream_chunk() directly.

  • util-genai suite: 332 passed
  • langchain suite: 239 passed
  • Both matrix legs: verified at the declared floor (langchain==0.3.21,
    langchain-core 0.3.86, where chunk_position does not exist) and at
    latest (langchain==1.3.16, langchain-core 1.6.0)
  • tox -e precommit (ruff, ruff-format) and pyright clean

Checklist

  • Followed the style guidelines of this project
  • Changelog updated if the change requires an entry
  • Unit tests added
  • Documentation updated

A streamed call was indistinguishable from a synchronous one. The
callback-based handler never routes chunks through util-genai's stream
wrappers, so gen_ai.request.stream and gen_ai.response.time_to_first_chunk
were never set and the gen_ai.client.operation.time_to_first_chunk and
time_per_output_chunk histograms never recorded a point.

The wrappers own that bookkeeping for instrumentations that receive an SDK
stream to proxy, which a callback handler never does. Add
GenAIInvocation.record_stream_chunk() as the public entry point for
reporting chunk arrival without a stream, and implement on_llm_new_token
to call it once per output chunk.

Streaming is inferred from a token arriving rather than from the stream
invocation param, which LangChain derives from the model's streaming
field and leaves False for a plain .stream() call. LangChain's contentless
end-of-stream marker is skipped so it does not contribute a spurious
near-zero inter-chunk gap.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds support for streaming timing telemetry to the callback-based LangChain instrumentation by introducing a util-level API (GenAIInvocation.record_stream_chunk()) that can be invoked from token callbacks, enabling gen_ai.request.stream, gen_ai.response.time_to_first_chunk, and the streaming timing histograms to be emitted for LangChain .stream() / .astream().

Changes:

  • Add GenAIInvocation.record_stream_chunk() in opentelemetry-util-genai to record per-chunk arrival timing without wrapping an SDK stream.
  • Implement on_llm_new_token in the LangChain callback handler to call record_stream_chunk() for each real output chunk (skipping LangChain’s end-of-stream marker).
  • Add unit tests in both util-genai and langchain packages to validate streamed vs non-streamed behavior and marker handling.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
util/opentelemetry-util-genai/src/opentelemetry/util/genai/_invocation.py Adds record_stream_chunk() to mark streaming and record per-chunk timing.
util/opentelemetry-util-genai/tests/test_handler_metrics.py Adds direct tests for record_stream_chunk() and its metric/span effects.
util/opentelemetry-util-genai/.changelog/461.added Towncrier fragment documenting the new util entry point.
instrumentation/opentelemetry-instrumentation-genai-langchain/src/opentelemetry/instrumentation/genai/langchain/utils.py Adds is_stream_end_marker() helper for filtering LangChain’s contentless terminal marker.
instrumentation/opentelemetry-instrumentation-genai-langchain/src/opentelemetry/instrumentation/genai/langchain/callback_handler.py Implements on_llm_new_token to report streaming chunk arrivals.
instrumentation/opentelemetry-instrumentation-genai-langchain/tests/test_streaming.py Adds streaming/non-streaming tests and an end-of-stream marker regression test.
instrumentation/opentelemetry-instrumentation-genai-langchain/.changelog/461.fixed Towncrier fragment documenting the LangChain streaming telemetry fix.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@opentelemetry-pr-dashboard

opentelemetry-pr-dashboard Bot commented Aug 25, 2026

Copy link
Copy Markdown

Pull request dashboard status

Merged · refreshed 2026-08-29 00:34 UTC

Status above doesn't look right?
  • Anything look wrong? Report it with what you expected; it helps us improve the dashboard.

sfc-gh-zeningchen and others added 3 commits August 25, 2026 17:00
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
on_llm_new_token calls GenAIInvocation.record_stream_chunk(), which this branch
adds to opentelemetry-util-genai and which the released 1.1b0 does not have.
The declared floor therefore understated the requirement: the oldest tox factor
installs only the instrumentation editable and resolves its declared deps from
PyPI at their floors, so py310-test-instrumentation-genai-langchain-oldest
would install util-genai 1.1b0 and raise AttributeError on the first streamed
token. A released instrumentation would fail the same way for anyone resolving
util-genai to the declared floor.

Declare the current unreleased dev version and install util-genai from the
workspace in the oldest requirements until it ships, matching what open-telemetry#269 did for
the anthropic and openai instrumentations. scripts/check_deps.py enforces both
halves of that pairing, and requires the editable install to be removed again
once the floor points at a release.

Also folds in the review follow-ups: rename the changelog fragments to the PR
number, add a streaming conformance scenario mirroring the anthropic and openai
packages, and restore the blank line ruff-format wants between
record_stream_chunk and _on_stream_chunk.
@linux-foundation-easycla

linux-foundation-easycla Bot commented Aug 26, 2026

Copy link
Copy Markdown

CLA Signed
The committers listed above are authorized under a signed CLA.

@sfc-gh-zeningchen
sfc-gh-zeningchen force-pushed the langchain-streaming-telemetry branch from b434163 to c78b4a2 Compare August 26, 2026 23:01

@lmolkova lmolkova left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thank you! Please add conformance tests with expected streaming metrics and it should be good to go.

@sfc-gh-zeningchen
sfc-gh-zeningchen force-pushed the langchain-streaming-telemetry branch 2 times, most recently from d50e850 to c295369 Compare August 27, 2026 01:24
@sfc-gh-zeningchen
sfc-gh-zeningchen force-pushed the langchain-streaming-telemetry branch from c295369 to 10dda48 Compare August 27, 2026 07:35
@lmolkova
lmolkova enabled auto-merge August 28, 2026 23:21
@lmolkova
lmolkova added this pull request to the merge queue Aug 28, 2026
Merged via the queue into open-telemetry:main with commit 2cf45cf Aug 28, 2026
66 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

langchain: streamed calls are not marked as streaming, thus no streaming timing signals emitted

3 participants