Skip to content

Commit a3c8aef

Browse files
fix(core): include llm_output in streaming LLMResult
Fixes #34057 Previously, streaming mode did not include the `llm_output` field in the `LLMResult` object passed to `on_llm_end` callbacks. This broke integrations like Langfuse that rely on this field to extract metadata such as model name. This commit ensures that `llm_output` is always present in streaming mode by passing an empty dict `{}` in all streaming methods (`stream` and `astream`) for both `BaseLLM` and `BaseChatModel`. Changes: - Updated `BaseLLM.stream()` to include `llm_output={}` in LLMResult - Updated `BaseLLM.astream()` to include `llm_output={}` in LLMResult - Updated `BaseChatModel.stream()` to include `llm_output={}` in LLMResult - Updated `BaseChatModel.astream()` to include `llm_output={}` in LLMResult - Added test to verify `llm_output` is present in streaming callbacks
1 parent 2f67f9d commit a3c8aef

File tree

3 files changed

+46
-4
lines changed

3 files changed

+46
-4
lines changed

libs/core/langchain_core/language_models/chat_models.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,7 @@ def stream(
580580
run_manager.on_llm_error(err, response=LLMResult(generations=[]))
581581
raise err
582582

583-
run_manager.on_llm_end(LLMResult(generations=[[generation]]))
583+
run_manager.on_llm_end(LLMResult(generations=[[generation]], llm_output={}))
584584

585585
@override
586586
async def astream(
@@ -709,7 +709,7 @@ async def astream(
709709
raise err
710710

711711
await run_manager.on_llm_end(
712-
LLMResult(generations=[[generation]]),
712+
LLMResult(generations=[[generation]], llm_output={}),
713713
)
714714

715715
# --- Custom methods ---

libs/core/langchain_core/language_models/llms.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -564,7 +564,7 @@ def stream(
564564
run_manager.on_llm_error(err, response=LLMResult(generations=[]))
565565
raise err
566566

567-
run_manager.on_llm_end(LLMResult(generations=[[generation]]))
567+
run_manager.on_llm_end(LLMResult(generations=[[generation]], llm_output={}))
568568

569569
@override
570570
async def astream(
@@ -635,7 +635,7 @@ async def astream(
635635
await run_manager.on_llm_error(err, response=LLMResult(generations=[]))
636636
raise err
637637

638-
await run_manager.on_llm_end(LLMResult(generations=[[generation]]))
638+
await run_manager.on_llm_end(LLMResult(generations=[[generation]], llm_output={}))
639639

640640
# --- Custom methods ---
641641

libs/core/tests/unit_tests/fake/test_fake_chat_model.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,3 +253,45 @@ def test_fake_messages_list_chat_model_sleep_delay() -> None:
253253
elapsed = time.time() - start
254254

255255
assert elapsed >= sleep_time
256+
257+
258+
def test_stream_llm_result_contains_llm_output() -> None:
259+
"""Test that streaming mode includes llm_output in LLMResult."""
260+
from langchain_core.callbacks.base import BaseCallbackHandler
261+
from langchain_core.outputs import LLMResult
262+
263+
class LLMResultCaptureHandler(BaseCallbackHandler):
264+
"""Callback handler that captures LLMResult from on_llm_end."""
265+
266+
def __init__(self) -> None:
267+
self.llm_results: list[LLMResult] = []
268+
269+
@override
270+
def on_llm_end(
271+
self,
272+
response: LLMResult,
273+
*,
274+
run_id: UUID,
275+
parent_run_id: UUID | None = None,
276+
**kwargs: Any,
277+
) -> None:
278+
"""Capture the LLMResult."""
279+
self.llm_results.append(response)
280+
281+
model = GenericFakeChatModel(messages=cycle([AIMessage(content="hello world")]))
282+
handler = LLMResultCaptureHandler()
283+
284+
# Consume the stream to trigger on_llm_end
285+
chunks = list(model.stream("test", config={"callbacks": [handler]}))
286+
287+
# Verify we got chunks
288+
assert len(chunks) > 0
289+
290+
# Verify on_llm_end was called
291+
assert len(handler.llm_results) == 1
292+
293+
# Verify llm_output field exists in the LLMResult
294+
llm_result = handler.llm_results[0]
295+
assert hasattr(llm_result, "llm_output")
296+
assert llm_result.llm_output is not None
297+
assert isinstance(llm_result.llm_output, dict)

0 commit comments

Comments
 (0)