mirror of
https://github.com/hwchase17/langchain.git
synced 2026-07-12 19:31:24 +00:00
test(core): migrate streaming tests to stream_events(version='v3')
Rename test_stream_v2.py -> test_chat_model_v3_stream.py, replace all .stream_v2() / .astream_v2() call sites in tests with the new public API .stream_events(version="v3") / .astream_events(version="v3"), and update function names + docstrings to match.
This commit is contained in:
@@ -1561,16 +1561,17 @@ def test_invocation_params_passed_to_tracer_metadata() -> None:
|
||||
assert run.metadata == run.extra["metadata"]
|
||||
|
||||
|
||||
def test_stream_v2_invocation_params_passed_to_tracer_metadata() -> None:
|
||||
"""`stream_v2()` must preserve filtered invocation params for tracing."""
|
||||
def test_stream_events_v3_invocation_params_passed_to_tracer_metadata() -> None:
|
||||
"""`stream_events(version="v3")` must preserve filtered invocation params for tracing."""
|
||||
llm = FakeStreamingChatModelWithInvocationParams()
|
||||
collector = LangChainTracerRunCollector()
|
||||
|
||||
with collector.tracing_callback() as tracer:
|
||||
_ = llm.stream_v2(
|
||||
_ = llm.stream_events(
|
||||
[HumanMessage(content="Hello")],
|
||||
config={"callbacks": [tracer]},
|
||||
stop=["done"],
|
||||
version="v3",
|
||||
).output
|
||||
|
||||
assert len(collector.runs) == 1
|
||||
@@ -1581,16 +1582,17 @@ def test_stream_v2_invocation_params_passed_to_tracer_metadata() -> None:
|
||||
assert metadata["temperature"] == 0.7
|
||||
|
||||
|
||||
async def test_astream_v2_invocation_params_passed_to_tracer_metadata() -> None:
|
||||
"""`astream_v2()` must preserve filtered invocation params for tracing."""
|
||||
async def test_astream_events_v3_invocation_params_passed_to_tracer_metadata() -> None:
|
||||
"""`astream_events(version="v3")` must preserve filtered invocation params for tracing."""
|
||||
llm = FakeStreamingChatModelWithInvocationParams()
|
||||
collector = LangChainTracerRunCollector()
|
||||
|
||||
with collector.tracing_callback() as tracer:
|
||||
stream = await llm.astream_v2(
|
||||
stream = await llm.astream_events(
|
||||
[HumanMessage(content="Hello")],
|
||||
config={"callbacks": [tracer]},
|
||||
stop=["done"],
|
||||
version="v3",
|
||||
)
|
||||
_ = await stream
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Tests for BaseChatModel.stream_v2() / astream_v2()."""
|
||||
"""Tests for `BaseChatModel.stream_events(version="v3")` / `astream_events(version="v3")`."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -32,11 +32,11 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
class TestStreamV2Sync:
|
||||
"""Test BaseChatModel.stream_v2() with FakeListChatModel."""
|
||||
"""Test `BaseChatModel.stream_events(version="v3")` with `FakeListChatModel`."""
|
||||
|
||||
def test_stream_text(self) -> None:
|
||||
model = FakeListChatModel(responses=["Hello world!"])
|
||||
stream = model.stream_v2("test")
|
||||
stream = model.stream_events("test", version="v3")
|
||||
|
||||
assert isinstance(stream, ChatModelStream)
|
||||
deltas = list(stream.text)
|
||||
@@ -45,7 +45,7 @@ class TestStreamV2Sync:
|
||||
|
||||
def test_stream_output(self) -> None:
|
||||
model = FakeListChatModel(responses=["Hello!"])
|
||||
stream = model.stream_v2("test")
|
||||
stream = model.stream_events("test", version="v3")
|
||||
|
||||
msg = stream.output
|
||||
assert isinstance(msg.content, list)
|
||||
@@ -54,7 +54,7 @@ class TestStreamV2Sync:
|
||||
|
||||
def test_stream_usage_none_for_fake(self) -> None:
|
||||
model = FakeListChatModel(responses=["Hi"])
|
||||
stream = model.stream_v2("test")
|
||||
stream = model.stream_events("test", version="v3")
|
||||
# Drain
|
||||
for _ in stream.text:
|
||||
pass
|
||||
@@ -62,7 +62,7 @@ class TestStreamV2Sync:
|
||||
|
||||
def test_stream_raw_events(self) -> None:
|
||||
model = FakeListChatModel(responses=["ab"])
|
||||
stream = model.stream_v2("test")
|
||||
stream = model.stream_events("test", version="v3")
|
||||
|
||||
events = list(stream)
|
||||
event_types = [e.get("event") for e in events]
|
||||
@@ -72,12 +72,12 @@ class TestStreamV2Sync:
|
||||
|
||||
|
||||
class TestAstreamV2:
|
||||
"""Test BaseChatModel.astream_v2() with FakeListChatModel."""
|
||||
"""Test `BaseChatModel.astream_events(version="v3")` with `FakeListChatModel`."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_astream_text_await(self) -> None:
|
||||
model = FakeListChatModel(responses=["Hello!"])
|
||||
stream = await model.astream_v2("test")
|
||||
stream = await model.astream_events("test", version="v3")
|
||||
|
||||
assert isinstance(stream, AsyncChatModelStream)
|
||||
full = await stream.text
|
||||
@@ -86,7 +86,7 @@ class TestAstreamV2:
|
||||
@pytest.mark.asyncio
|
||||
async def test_astream_text_deltas(self) -> None:
|
||||
model = FakeListChatModel(responses=["Hi"])
|
||||
stream = await model.astream_v2("test")
|
||||
stream = await model.astream_events("test", version="v3")
|
||||
|
||||
deltas = [d async for d in stream.text]
|
||||
assert "".join(deltas) == "Hi"
|
||||
@@ -94,7 +94,7 @@ class TestAstreamV2:
|
||||
@pytest.mark.asyncio
|
||||
async def test_astream_await_output(self) -> None:
|
||||
model = FakeListChatModel(responses=["Hey"])
|
||||
stream = await model.astream_v2("test")
|
||||
stream = await model.astream_events("test", version="v3")
|
||||
|
||||
msg = await stream
|
||||
assert msg.content == [{"type": "text", "text": "Hey", "index": 0}]
|
||||
@@ -193,13 +193,13 @@ class _EmptyStreamModel(BaseChatModel):
|
||||
|
||||
|
||||
class TestCallbacks:
|
||||
"""Verify stream_v2 fires on_llm_end / on_llm_error callbacks."""
|
||||
"""Verify `stream_events(version="v3")` fires `on_llm_end` / `on_llm_error` callbacks."""
|
||||
|
||||
def test_stream_v2_defers_on_chat_model_start_until_consumed(self) -> None:
|
||||
def test_stream_events_v3_defers_on_chat_model_start_until_consumed(self) -> None:
|
||||
handler = _RecordingHandler()
|
||||
model = FakeListChatModel(responses=["done"], callbacks=[handler])
|
||||
|
||||
stream = model.stream_v2("test")
|
||||
stream = model.stream_events("test", version="v3")
|
||||
|
||||
assert handler.events == []
|
||||
|
||||
@@ -210,7 +210,7 @@ class TestCallbacks:
|
||||
def test_on_llm_end_fires_after_drain(self) -> None:
|
||||
handler = _RecordingHandler()
|
||||
model = FakeListChatModel(responses=["done"], callbacks=[handler])
|
||||
stream = model.stream_v2("test")
|
||||
stream = model.stream_events("test", version="v3")
|
||||
for _ in stream.text:
|
||||
pass
|
||||
_ = stream.output
|
||||
@@ -225,18 +225,18 @@ class TestCallbacks:
|
||||
async def test_on_llm_end_fires_async(self) -> None:
|
||||
handler = _AsyncRecordingHandler()
|
||||
model = FakeListChatModel(responses=["done"], callbacks=[handler])
|
||||
stream = await model.astream_v2("test")
|
||||
stream = await model.astream_events("test", version="v3")
|
||||
_ = await stream
|
||||
|
||||
assert "on_chat_model_start" in handler.events
|
||||
assert "on_llm_end" in handler.events
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_astream_v2_defers_on_chat_model_start_until_consumed(self) -> None:
|
||||
async def test_astream_events_v3_defers_on_chat_model_start_until_consumed(self) -> None:
|
||||
handler = _AsyncRecordingHandler()
|
||||
model = FakeListChatModel(responses=["done"], callbacks=[handler])
|
||||
|
||||
stream = await model.astream_v2("test")
|
||||
stream = await model.astream_events("test", version="v3")
|
||||
|
||||
assert handler.events == []
|
||||
|
||||
@@ -251,7 +251,7 @@ class TestCallbacks:
|
||||
"""
|
||||
handler = _RecordingHandler()
|
||||
model = FakeListChatModel(responses=["hello"], callbacks=[handler])
|
||||
stream = model.stream_v2("test")
|
||||
stream = model.stream_events("test", version="v3")
|
||||
_ = stream.output
|
||||
|
||||
response = handler.last_llm_end_response
|
||||
@@ -265,7 +265,7 @@ class TestCallbacks:
|
||||
async def test_on_llm_end_receives_assembled_message_async(self) -> None:
|
||||
handler = _AsyncRecordingHandler()
|
||||
model = FakeListChatModel(responses=["hello"], callbacks=[handler])
|
||||
stream = await model.astream_v2("test")
|
||||
stream = await model.astream_events("test", version="v3")
|
||||
_ = await stream
|
||||
|
||||
response = handler.last_llm_end_response
|
||||
@@ -277,7 +277,7 @@ class TestCallbacks:
|
||||
|
||||
def test_empty_stream_reports_error_without_finish_only_lifecycle(self) -> None:
|
||||
handler = _RecordingHandler()
|
||||
stream = _EmptyStreamModel(callbacks=[handler]).stream_v2("test")
|
||||
stream = _EmptyStreamModel(callbacks=[handler]).stream_events("test", version="v3")
|
||||
|
||||
with pytest.raises(ValueError, match="No generation chunks were returned"):
|
||||
list(stream)
|
||||
@@ -289,7 +289,7 @@ class TestCallbacks:
|
||||
@pytest.mark.asyncio
|
||||
async def test_empty_astream_reports_error(self) -> None:
|
||||
handler = _AsyncRecordingHandler()
|
||||
stream = await _EmptyStreamModel(callbacks=[handler]).astream_v2("test")
|
||||
stream = await _EmptyStreamModel(callbacks=[handler]).astream_events("test", version="v3")
|
||||
|
||||
with pytest.raises(ValueError, match="No generation chunks were returned"):
|
||||
await stream
|
||||
@@ -303,12 +303,12 @@ class TestCallbacks:
|
||||
|
||||
|
||||
class TestOnStreamEvent:
|
||||
"""`on_stream_event` must fire once per protocol event from stream_v2."""
|
||||
"""`on_stream_event` must fire once per protocol event from `stream_events(version="v3")`."""
|
||||
|
||||
def test_on_stream_event_fires_for_every_event_sync(self) -> None:
|
||||
handler = _RecordingHandler()
|
||||
model = FakeListChatModel(responses=["Hi"], callbacks=[handler])
|
||||
stream = model.stream_v2("test")
|
||||
stream = model.stream_events("test", version="v3")
|
||||
_ = stream.output
|
||||
|
||||
# Every event the stream sees should also reach the observer.
|
||||
@@ -322,7 +322,7 @@ class TestOnStreamEvent:
|
||||
async def test_on_stream_event_fires_for_every_event_async(self) -> None:
|
||||
handler = _AsyncRecordingHandler()
|
||||
model = FakeListChatModel(responses=["Hi"], callbacks=[handler])
|
||||
stream = await model.astream_v2("test")
|
||||
stream = await model.astream_events("test", version="v3")
|
||||
_ = await stream
|
||||
|
||||
event_types = [e["event"] for e in handler.stream_events]
|
||||
@@ -334,7 +334,7 @@ class TestOnStreamEvent:
|
||||
"""Stream events must all fire between on_chat_model_start and on_llm_end."""
|
||||
handler = _RecordingHandler()
|
||||
model = FakeListChatModel(responses=["Hi"], callbacks=[handler])
|
||||
stream = model.stream_v2("test")
|
||||
stream = model.stream_events("test", version="v3")
|
||||
_ = stream.output
|
||||
|
||||
# on_stream_event doesn't show up in `events` (different list), but
|
||||
@@ -346,10 +346,10 @@ class TestOnStreamEvent:
|
||||
|
||||
|
||||
class TestCancellation:
|
||||
"""Cancellation of `astream_v2` must propagate, not be swallowed."""
|
||||
"""Cancellation of `astream_events(version="v3")` must propagate, not be swallowed."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_astream_v2_cancellation_propagates(self) -> None:
|
||||
async def test_astream_events_v3_cancellation_propagates(self) -> None:
|
||||
"""Cancelling the producer task must raise CancelledError.
|
||||
|
||||
Regression test: the producer's `except BaseException` previously
|
||||
@@ -357,7 +357,7 @@ class TestCancellation:
|
||||
`on_llm_error` + `stream._fail` pair that never propagated.
|
||||
"""
|
||||
model = FakeListChatModel(responses=["abcdefghij"], sleep=0.05)
|
||||
stream = await model.astream_v2("test")
|
||||
stream = await model.astream_events("test", version="v3")
|
||||
aiter_ = stream.text.__aiter__()
|
||||
await aiter_.__anext__()
|
||||
task = stream._producer_task
|
||||
@@ -401,7 +401,7 @@ class _KwargRecordingModel(FakeListChatModel):
|
||||
|
||||
|
||||
class TestRunnableBindingForwarding:
|
||||
"""`RunnableBinding.stream_v2` must merge bound kwargs into the call.
|
||||
"""`RunnableBinding.stream_events(version="v3")` must merge bound kwargs into the call.
|
||||
|
||||
Without the explicit override on `RunnableBinding`, `__getattr__`
|
||||
forwards the call but drops `self.kwargs` — so tools bound via
|
||||
@@ -409,12 +409,12 @@ class TestRunnableBindingForwarding:
|
||||
ignored.
|
||||
"""
|
||||
|
||||
def test_bound_kwargs_reach_stream_v2(self) -> None:
|
||||
def test_bound_kwargs_reach_stream_events_v3(self) -> None:
|
||||
model = _KwargRecordingModel(responses=["hi"])
|
||||
model.received_kwargs = []
|
||||
bound = model.bind(my_marker="sentinel-42")
|
||||
|
||||
stream = bound.stream_v2("test")
|
||||
stream = bound.stream_events("test", version="v3")
|
||||
for _ in stream.text:
|
||||
pass
|
||||
|
||||
@@ -426,19 +426,19 @@ class TestRunnableBindingForwarding:
|
||||
model.received_kwargs = []
|
||||
bound = model.bind(my_marker="from-bind")
|
||||
|
||||
stream = bound.stream_v2("test", my_marker="from-call")
|
||||
stream = bound.stream_events("test", my_marker="from-call", version="v3")
|
||||
for _ in stream.text:
|
||||
pass
|
||||
|
||||
assert model.received_kwargs[0].get("my_marker") == "from-call"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_bound_kwargs_reach_astream_v2(self) -> None:
|
||||
async def test_bound_kwargs_reach_astream_events_v3(self) -> None:
|
||||
model = _KwargRecordingModel(responses=["hi"])
|
||||
model.received_kwargs = []
|
||||
bound = model.bind(my_marker="sentinel-async")
|
||||
|
||||
stream = await bound.astream_v2("test")
|
||||
stream = await bound.astream_events("test", version="v3")
|
||||
_ = await stream
|
||||
|
||||
assert len(model.received_kwargs) == 1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Tests for stream_v2 / astream_v2 and ChatModelStream."""
|
||||
"""Tests for `stream_events(version="v3")` / `astream_events(version="v3")` and `ChatModelStream`."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -301,20 +301,20 @@ class TestAsyncChatModelStream:
|
||||
|
||||
|
||||
class TestStreamV2:
|
||||
"""Test BaseChatModel.stream_v2() with FakeListChatModel."""
|
||||
"""Test `BaseChatModel.stream_events(version="v3")` with `FakeListChatModel`."""
|
||||
|
||||
def test_stream_v2_text(self) -> None:
|
||||
def test_stream_events_v3_text(self) -> None:
|
||||
model = FakeListChatModel(responses=["Hello world!"])
|
||||
stream = model.stream_v2("test")
|
||||
stream = model.stream_events("test", version="v3")
|
||||
|
||||
assert isinstance(stream, ChatModelStream)
|
||||
deltas = list(stream.text)
|
||||
assert "".join(deltas) == "Hello world!"
|
||||
assert stream.done
|
||||
|
||||
def test_stream_v2_usage(self) -> None:
|
||||
def test_stream_events_v3_usage(self) -> None:
|
||||
model = FakeListChatModel(responses=["Hi"])
|
||||
stream = model.stream_v2("test")
|
||||
stream = model.stream_events("test", version="v3")
|
||||
|
||||
# Drain stream
|
||||
for _ in stream.text:
|
||||
@@ -323,10 +323,10 @@ class TestStreamV2:
|
||||
assert stream.output.usage_metadata is None
|
||||
assert stream.done
|
||||
|
||||
def test_stream_v2_malformed_tool_args_produce_invalid_tool_call(self) -> None:
|
||||
def test_stream_events_v3_malformed_tool_args_produce_invalid_tool_call(self) -> None:
|
||||
"""End-to-end: malformed tool-call JSON becomes invalid_tool_calls."""
|
||||
model = _MalformedToolCallModel()
|
||||
stream = model.stream_v2("test")
|
||||
stream = model.stream_events("test", version="v3")
|
||||
msg = stream.output
|
||||
|
||||
assert msg.tool_calls == []
|
||||
@@ -336,10 +336,10 @@ class TestStreamV2:
|
||||
assert itc["args"] == '{"q": '
|
||||
assert itc["id"] == "call_1"
|
||||
|
||||
def test_stream_v2_translates_anthropic_server_tool_use_to_protocol(self) -> None:
|
||||
def test_stream_events_v3_translates_anthropic_server_tool_use_to_protocol(self) -> None:
|
||||
"""Phase E end-to-end: server_tool_use becomes server_tool_call in output."""
|
||||
model = _AnthropicStyleServerToolModel()
|
||||
stream = model.stream_v2("weather?")
|
||||
stream = model.stream_events("weather?", version="v3")
|
||||
msg = stream.output
|
||||
|
||||
assert isinstance(msg.content, list)
|
||||
@@ -353,21 +353,21 @@ class TestStreamV2:
|
||||
|
||||
|
||||
class TestAstreamV2:
|
||||
"""Test BaseChatModel.astream_v2() with FakeListChatModel."""
|
||||
"""Test `BaseChatModel.astream_events(version="v3")` with `FakeListChatModel`."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_astream_v2_text(self) -> None:
|
||||
async def test_astream_events_v3_text(self) -> None:
|
||||
model = FakeListChatModel(responses=["Hello!"])
|
||||
stream = await model.astream_v2("test")
|
||||
stream = await model.astream_events("test", version="v3")
|
||||
|
||||
assert isinstance(stream, AsyncChatModelStream)
|
||||
full = await stream.text
|
||||
assert full == "Hello!"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_astream_v2_deltas(self) -> None:
|
||||
async def test_astream_events_v3_deltas(self) -> None:
|
||||
model = FakeListChatModel(responses=["Hi"])
|
||||
stream = await model.astream_v2("test")
|
||||
stream = await model.astream_events("test", version="v3")
|
||||
|
||||
deltas = [d async for d in stream.text]
|
||||
assert "".join(deltas) == "Hi"
|
||||
@@ -690,33 +690,34 @@ class TestStructuredOutputKwargStripping:
|
||||
|
||||
`stream()` / `astream()` pop `ls_structured_output_format` and
|
||||
`structured_output_format` before forwarding kwargs to `_stream` —
|
||||
provider clients reject unknown kwargs. `stream_v2` / `astream_v2`
|
||||
must do the same, or `.with_structured_output().stream_v2()` breaks.
|
||||
provider clients reject unknown kwargs. `stream_events(version="v3")` /
|
||||
`astream_events(version="v3")` must do the same, or
|
||||
`.with_structured_output().stream_events(version="v3")` breaks.
|
||||
"""
|
||||
|
||||
def test_stream_v2_strips_ls_structured_output_format(self) -> None:
|
||||
def test_stream_events_v3_strips_ls_structured_output_format(self) -> None:
|
||||
model = _RecordingStreamModel()
|
||||
bound = model.bind(ls_structured_output_format={"schema": {"type": "object"}})
|
||||
stream = bound.stream_v2("test")
|
||||
stream = bound.stream_events("test", version="v3")
|
||||
_ = stream.output # drain
|
||||
recorded = _RecordingStreamModel.last_stream_kwargs
|
||||
assert "ls_structured_output_format" not in recorded
|
||||
assert "structured_output_format" not in recorded
|
||||
|
||||
def test_stream_v2_strips_structured_output_format(self) -> None:
|
||||
def test_stream_events_v3_strips_structured_output_format(self) -> None:
|
||||
model = _RecordingStreamModel()
|
||||
bound = model.bind(structured_output_format={"schema": {"type": "object"}})
|
||||
stream = bound.stream_v2("test")
|
||||
stream = bound.stream_events("test", version="v3")
|
||||
_ = stream.output
|
||||
recorded = _RecordingStreamModel.last_stream_kwargs
|
||||
assert "ls_structured_output_format" not in recorded
|
||||
assert "structured_output_format" not in recorded
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_astream_v2_strips_ls_structured_output_format(self) -> None:
|
||||
async def test_astream_events_v3_strips_ls_structured_output_format(self) -> None:
|
||||
model = _RecordingStreamModel()
|
||||
bound = model.bind(ls_structured_output_format={"schema": {"type": "object"}})
|
||||
stream = await bound.astream_v2("test")
|
||||
stream = await bound.astream_events("test", version="v3")
|
||||
_ = await stream
|
||||
assert (
|
||||
"ls_structured_output_format"
|
||||
@@ -727,10 +728,10 @@ class TestStructuredOutputKwargStripping:
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_astream_v2_strips_structured_output_format(self) -> None:
|
||||
async def test_astream_events_v3_strips_structured_output_format(self) -> None:
|
||||
model = _RecordingStreamModel()
|
||||
bound = model.bind(structured_output_format={"schema": {"type": "object"}})
|
||||
stream = await bound.astream_v2("test")
|
||||
stream = await bound.astream_events("test", version="v3")
|
||||
_ = await stream
|
||||
assert (
|
||||
"ls_structured_output_format"
|
||||
@@ -845,7 +846,7 @@ class TestAsyncStreamAclose:
|
||||
async def test_aclose_cancels_producer_task(self) -> None:
|
||||
gate = asyncio.Event()
|
||||
model = _GatedStreamModel(gate=gate)
|
||||
stream = await model.astream_v2("test")
|
||||
stream = await model.astream_events("test", version="v3")
|
||||
|
||||
# Pull the first delta so the producer enters the gated section.
|
||||
aiter_ = stream.text.__aiter__()
|
||||
@@ -863,7 +864,7 @@ class TestAsyncStreamAclose:
|
||||
async def test_aclose_is_idempotent(self) -> None:
|
||||
gate = asyncio.Event()
|
||||
model = _GatedStreamModel(gate=gate)
|
||||
stream = await model.astream_v2("test")
|
||||
stream = await model.astream_events("test", version="v3")
|
||||
aiter_ = stream.text.__aiter__()
|
||||
await aiter_.__anext__()
|
||||
|
||||
@@ -874,7 +875,7 @@ class TestAsyncStreamAclose:
|
||||
async def test_async_context_manager_closes_stream(self) -> None:
|
||||
gate = asyncio.Event()
|
||||
model = _GatedStreamModel(gate=gate)
|
||||
stream = await model.astream_v2("test")
|
||||
stream = await model.astream_events("test", version="v3")
|
||||
|
||||
async with stream as s:
|
||||
assert s is stream
|
||||
@@ -897,7 +898,7 @@ class TestAsyncStreamAclose:
|
||||
"""
|
||||
teardown_gate = asyncio.Event()
|
||||
model = _SlowTeardownModel(teardown_gate=teardown_gate)
|
||||
stream = await model.astream_v2("test")
|
||||
stream = await model.astream_events("test", version="v3")
|
||||
|
||||
# Prime the producer so it enters `_astream`'s forever-blocking
|
||||
# await.
|
||||
@@ -947,7 +948,7 @@ class TestAsyncStreamAclose:
|
||||
async def test_aclose_before_producer_starts_resolves_projections(self) -> None:
|
||||
"""Early-cancel path: `_produce` never runs.
|
||||
|
||||
If a consumer calls `astream_v2()` and immediately `aclose()`
|
||||
If a consumer calls `astream_events(version="v3")` and immediately `aclose()`
|
||||
(or `async with` exits before the loop schedules `_produce`),
|
||||
`task.cancel()` marks the task cancelled without ever invoking
|
||||
its body — so neither `stream.fail` nor `on_llm_error` fires.
|
||||
@@ -963,8 +964,8 @@ class TestAsyncStreamAclose:
|
||||
handler = RecordingHandler()
|
||||
gate = asyncio.Event()
|
||||
model = _GatedStreamModel(gate=gate)
|
||||
stream = await model.astream_v2("test", config={"callbacks": [handler]})
|
||||
# No yield to the event loop between `astream_v2` returning and
|
||||
stream = await model.astream_events("test", config={"callbacks": [handler]}, version="v3")
|
||||
# No yield to the event loop between `astream_events(version="v3")` returning and
|
||||
# `aclose()` — the producer task has been created but its body
|
||||
# has not executed.
|
||||
await stream.aclose()
|
||||
@@ -1003,7 +1004,7 @@ class TestAsyncStreamAclose:
|
||||
handler = RecordingHandler()
|
||||
gate = asyncio.Event()
|
||||
model = _GatedStreamModel(gate=gate)
|
||||
stream = await model.astream_v2("test", config={"callbacks": [handler]})
|
||||
stream = await model.astream_events("test", config={"callbacks": [handler]}, version="v3")
|
||||
|
||||
aiter_ = stream.text.__aiter__()
|
||||
await aiter_.__anext__()
|
||||
@@ -1043,7 +1044,7 @@ class TestAsyncStreamAclose:
|
||||
|
||||
handler = SlowEndHandler()
|
||||
model = FakeListChatModel(responses=["ok"])
|
||||
stream = await model.astream_v2("test", config={"callbacks": [handler]})
|
||||
stream = await model.astream_events("test", config={"callbacks": [handler]}, version="v3")
|
||||
|
||||
# Wait until the stream has assembled the message and the
|
||||
# slow on_llm_end handler has started running.
|
||||
@@ -1163,7 +1164,7 @@ class TestCacheHitV2Replay:
|
||||
class _ProviderMetadataStreamModel(BaseChatModel):
|
||||
"""Fake model that advertises `output_version="responses/v1"` in metadata.
|
||||
|
||||
Verifies `stream_v2` pins the assembled message's `output_version` to
|
||||
Verifies `stream_events(version="v3")` pins the assembled message's `output_version` to
|
||||
`"v1"` — the shape it actually produces — regardless of what the
|
||||
provider's chunk metadata claims.
|
||||
"""
|
||||
@@ -1199,11 +1200,11 @@ class _ProviderMetadataStreamModel(BaseChatModel):
|
||||
|
||||
|
||||
class TestOutputVersionPinning:
|
||||
"""`stream_v2().output` always serializes as v1 content blocks."""
|
||||
"""`stream_events(version="v3").output` always serializes as v1 content blocks."""
|
||||
|
||||
def test_output_version_pinned_to_v1(self) -> None:
|
||||
model = _ProviderMetadataStreamModel()
|
||||
stream = model.stream_v2("hi")
|
||||
stream = model.stream_events("hi", version="v3")
|
||||
msg = stream.output
|
||||
# Assembled message must claim `"v1"` even though the provider
|
||||
# chunk metadata advertised `"responses/v1"`.
|
||||
@@ -1325,7 +1326,7 @@ class TestBedrockConverseToolCallArgs:
|
||||
|
||||
def test_bedrock_tool_call_assembles_without_error(self) -> None:
|
||||
model = _BedrockConverseToolCallModel()
|
||||
stream = model.stream_v2("What's the weather in Boston?")
|
||||
stream = model.stream_events("What's the weather in Boston?", version="v3")
|
||||
# Drive the stream to completion — the raise would have surfaced here.
|
||||
events = list(stream)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""V1 parity tests: stream_v2() output must match model.stream() output.
|
||||
"""V1 parity tests: `stream_events(version="v3")` output must match `model.stream()` output.
|
||||
|
||||
These are the acceptance criteria for streaming v2 — if any test fails,
|
||||
v2 has a regression vs v1.
|
||||
These are the acceptance criteria for the v3 streaming API — if any test fails,
|
||||
v3 has a regression vs v1.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -128,8 +128,8 @@ def _collect_v1_message(model: BaseChatModel, input_text: str) -> AIMessage:
|
||||
|
||||
|
||||
def _collect_v2_message(model: BaseChatModel, input_text: str) -> AIMessage:
|
||||
"""Run model.stream_v2() and get .output."""
|
||||
stream = model.stream_v2(input_text)
|
||||
"""Run `model.stream_events(version="v3")` and get `.output`."""
|
||||
stream = model.stream_events(input_text, version="v3")
|
||||
return stream.output
|
||||
|
||||
|
||||
@@ -156,13 +156,13 @@ class TestV1ParityBasic:
|
||||
def test_empty_response(self) -> None:
|
||||
"""A truly empty stream is an error, matching `stream()` parity.
|
||||
|
||||
`stream_v2` distinguishes "producer emitted events but no terminal
|
||||
`message-finish`" (which is synthesized, for native-event providers
|
||||
`stream_events(version="v3")` distinguishes "producer emitted events but no
|
||||
terminal `message-finish`" (which is synthesized, for native-event providers
|
||||
that omit it) from "producer emitted nothing at all" (which fails
|
||||
with `ValueError`, same as `stream()`).
|
||||
"""
|
||||
model = FakeListChatModel(responses=[""])
|
||||
stream = model.stream_v2("test")
|
||||
stream = model.stream_events("test", version="v3")
|
||||
with pytest.raises(ValueError, match="No generation chunks"):
|
||||
_ = stream.output
|
||||
|
||||
@@ -179,7 +179,7 @@ class TestV1ParityBasic:
|
||||
|
||||
def test_text_deltas_reconstruct_content(self) -> None:
|
||||
model = FakeListChatModel(responses=["Hello!"])
|
||||
stream = model.stream_v2("test")
|
||||
stream = model.stream_events("test", version="v3")
|
||||
|
||||
deltas = list(stream.text)
|
||||
content = stream.output.content
|
||||
@@ -233,7 +233,7 @@ class TestV1ParityToolCalls:
|
||||
|
||||
def test_tool_calls_via_projection(self) -> None:
|
||||
model = self._make_model()
|
||||
stream = model.stream_v2("weather?")
|
||||
stream = model.stream_events("weather?", version="v3")
|
||||
finalized = stream.tool_calls.get()
|
||||
assert len(finalized) == 1
|
||||
assert finalized[0]["name"] == "get_weather"
|
||||
@@ -276,7 +276,7 @@ class TestV1ParityUsage:
|
||||
assert v1.usage_metadata["total_tokens"] == v2.usage_metadata["total_tokens"]
|
||||
|
||||
def test_usage_projection_matches(self) -> None:
|
||||
stream = self._make_model().stream_v2("hello")
|
||||
stream = self._make_model().stream_events("hello", version="v3")
|
||||
# Drain so usage is available
|
||||
for _ in stream.text:
|
||||
pass
|
||||
@@ -352,7 +352,7 @@ class TestV1ParityReasoning:
|
||||
assert types_in_order == ["reasoning", "text"]
|
||||
|
||||
def test_reasoning_projection(self) -> None:
|
||||
stream = self._make_model().stream_v2("think")
|
||||
stream = self._make_model().stream_events("think", version="v3")
|
||||
full_reasoning = str(stream.reasoning)
|
||||
assert full_reasoning == "Let me think. Done."
|
||||
|
||||
@@ -366,7 +366,7 @@ class TestV1ParityError:
|
||||
]
|
||||
model = _ScriptedChunkModel(scripted_chunks=chunks, raise_after=True)
|
||||
|
||||
stream = model.stream_v2("boom")
|
||||
stream = model.stream_events("boom", version="v3")
|
||||
# Drain first; error may surface here or at .output access.
|
||||
try:
|
||||
list(stream.text)
|
||||
@@ -382,7 +382,7 @@ class TestV1ParityError:
|
||||
]
|
||||
model = _ScriptedChunkModel(scripted_chunks=chunks, raise_after=True)
|
||||
|
||||
stream = await model.astream_v2("boom")
|
||||
stream = await model.astream_events("boom", version="v3")
|
||||
try:
|
||||
async for _ in stream.text:
|
||||
pass
|
||||
|
||||
Reference in New Issue
Block a user