Skip to content

[opentelemetry-instrumentation-genai-langchain] Record gen_ai.response.model on streamed spans - #505

Merged
lmolkova merged 7 commits into
open-telemetry:mainfrom
sfc-gh-zeningchen:langchain-streamed-response-model
Sep 2, 2026
Merged

[opentelemetry-instrumentation-genai-langchain] Record gen_ai.response.model on streamed spans#505
lmolkova merged 7 commits into
open-telemetry:mainfrom
sfc-gh-zeningchen:langchain-streamed-response-model

Conversation

@sfc-gh-zeningchen

@sfc-gh-zeningchen sfc-gh-zeningchen commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Description

gen_ai.response.model and gen_ai.response.id were absent from every
streamed chat span and present on every non-streamed one.

on_llm_end read both from LLMResult.llm_output. LangChain never populates
that for a streamed call: it assembles the final LLMResult from the merged
chunks alone. The values are still on the message's response_metadata, which
LangChain fills with the union of the generation's generation_info and
whatever the provider wrote onto the message, so reading that covers
langchain-openai (which reports the model through generation_info) and
langchain-anthropic (which writes it onto the message at message_start).

The precedence chain, generation then llm_output then the Responses API
served-model header, moves into one helper so it lives in one place instead of
being spread across the callback.

gen_ai.response.id stays absent for streamed Chat Completions and Anthropic
calls. Both providers send a response id on every chunk, but their LangChain
adapters do not surface it on the streaming path. Only the OpenAI Responses API
records one, and that path is covered here. message.id is not a substitute
because LangChain fills it with its own run id.

Fixes #503

Type of change

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

How has this been tested?

  • TestOnLlmEndStreamedResponse in tests/test_callback_handler.py covers
    the model and id read from a streamed generation, llm_output and the
    served-model header keeping precedence, both attributes staying unset when no
    source reports them, and message.id never being used as the response id.
  • test_chat_openai_streamed_response_model in tests/test_llm_call.py
    drives a real .stream(). The model is reported only in generation_info
    there, and LangChain is what copies it into response_metadata, so a
    hand-built LLMResult would skip that step and prove nothing.

Checklist

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

Copilot AI lite review requested due to automatic review settings August 31, 2026 02:33
@sfc-gh-zeningchen
sfc-gh-zeningchen requested a review from a team as a code owner August 31, 2026 02:33
Both attributes were absent from every streamed chat span and present on every
non-streamed one. on_llm_end read them from LLMResult.llm_output, which
LangChain never populates for a streamed call: it assembles the final
LLMResult from the merged chunks alone. The values are still on the message's
response_metadata, which LangChain fills with the union of the generation's
generation_info and whatever the provider wrote onto the message, so reading
that covers langchain-openai (which reports the model through generation_info)
and langchain-anthropic (which writes it onto the message) alike.

The precedence chain -- generation, then llm_output, then the Responses API
served-model header -- moves into one helper so it is stated in one place
instead of spread across the callback.

gen_ai.response.id stays absent for streamed OpenAI and Anthropic calls. Both
providers send a response id on every chunk, but their LangChain adapters drop
it on the streaming path; only the OpenAI Responses API records one. message.id
is not a substitute, since LangChain fills it with its own run id.

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

Fixes LangChain streamed chat spans missing gen_ai.response.model (and conditionally gen_ai.response.id) by extracting those fields from streamed-generation metadata and centralizing precedence logic.

Changes:

  • Add utilities to extract (response_model, response_id) from a streamed generation’s message.response_metadata and to resolve final precedence between generation, llm_output, and served-model headers.
  • Update the LangChain callback handler to use the new resolver so streamed and non-streamed paths consistently populate response_model_name / response_id when available.
  • Add regression tests covering streamed behavior via both unit-level on_llm_end inputs and a real .stream() flow with a patched provider stream.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
instrumentation/opentelemetry-instrumentation-genai-langchain/tests/test_llm_call.py Adds an end-to-end streamed ChatOpenAI test asserting gen_ai.response.model is present.
instrumentation/opentelemetry-instrumentation-genai-langchain/tests/test_callback_handler.py Adds focused unit tests for streamed on_llm_end extraction and precedence rules.
instrumentation/opentelemetry-instrumentation-genai-langchain/src/opentelemetry/instrumentation/genai/langchain/utils.py Introduces helper functions to extract/resolve response model/id across streamed and non-streamed results.
instrumentation/opentelemetry-instrumentation-genai-langchain/src/opentelemetry/instrumentation/genai/langchain/callback_handler.py Switches on_llm_end to use the new resolver and pulls fields from streamed generation metadata.
instrumentation/opentelemetry-instrumentation-genai-langchain/.changelog/505.fixed Adds a changelog fragment describing the streamed-span attribute fix.

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

sfc-gh-zeningchen and others added 2 commits August 30, 2026 19:38
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
gen_ai.response.id remains unset for streamed Chat Completions and Anthropic
calls, since those adapters do not surface the provider's id. The PR
description and commit message cover where it is recorded.
@opentelemetry-pr-dashboard

opentelemetry-pr-dashboard Bot commented Aug 31, 2026

Copy link
Copy Markdown

Pull request dashboard status

Merged · refreshed 2026-09-02 00:38 UTC

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

The InferenceStreamingScenario declared the missing attribute as an expected
violation, and the conformance harness fails when weaver stops reporting one,
so recording the model turned the scenario red. gen_ai.response.id keeps its
entry: the langchain-openai streaming path does not surface the id the provider
sends on every chunk.
@sfc-gh-zeningchen
sfc-gh-zeningchen force-pushed the langchain-streamed-response-model branch from dc32e87 to b2277f0 Compare August 31, 2026 06:13
LangChain merges a generation's generation_info into the message's
response_metadata and prefers the message where the two disagree, so reading
response_metadata covered every generation that went through .stream() or
_generate_with_cache(). A generation dispatched without that merge reported
nothing. Read generation_info after response_metadata, keeping the same
precedence LangChain applies.

Add a test for a generation that carries the model only in generation_info,
one that pins response_metadata winning when the two disagree, and an
.astream() counterpart to the streamed end-to-end test.
…sponse-model

# Conflicts:
#	instrumentation/opentelemetry-instrumentation-genai-langchain/tests/test_callback_handler.py
OpenAIResponsesInputImageScenario declared the missing attribute as an expected
violation, and the harness fails when weaver stops reporting one. The Responses
API records an id in the message's response_metadata, so recording the id from
there turned the scenario red. The streaming scenario keeps its entry, since
the Chat Completions streaming path still surfaces no id.
@lmolkova
lmolkova added this pull request to the merge queue Sep 2, 2026
Merged via the queue into open-telemetry:main with commit 79c2d34 Sep 2, 2026
70 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 spans are missing gen_ai.response.model

3 participants