more testing

This commit is contained in:
Nick Hollon
2026-04-21 13:44:33 -04:00
parent 07c71ae632
commit be3a26b5de
3 changed files with 48 additions and 35 deletions

View File

@@ -1,6 +1,6 @@
"""Tests for the compat bridge (chunk-to-event conversion)."""
from typing import TYPE_CHECKING, cast
from typing import TYPE_CHECKING, Any, cast
import pytest
from langchain_tests.utils.stream_lifecycle import assert_valid_event_stream
@@ -205,8 +205,8 @@ def test_chunks_to_events_block_transitions_close_previous_block() -> None:
events = list(chunks_to_events(iter(chunks), message_id="msg-1"))
starts = [e for e in events if e["event"] == "content-block-start"]
finishes = [e for e in events if e["event"] == "content-block-finish"]
starts: list[Any] = [e for e in events if e["event"] == "content-block-start"]
finishes: list[Any] = [e for e in events if e["event"] == "content-block-finish"]
assert [s["content_block"]["type"] for s in starts] == [
"reasoning",
"reasoning",
@@ -223,9 +223,10 @@ def test_chunks_to_events_block_transitions_close_previous_block() -> None:
# Finish events must be interleaved with starts (no-interleave rule):
# block 0 finishes before block 1 starts, etc.
events_any: list[Any] = events
lifecycle = [
(e["event"], e["index"])
for e in events
for e in events_any
if e["event"] in ("content-block-start", "content-block-finish")
]
assert lifecycle == [
@@ -287,7 +288,9 @@ def test_chunks_to_events_tool_call_multichunk() -> None:
assert event_types[-1] == "message-finish"
# Exactly one block finalized, args parsed to a dict.
finish_events = [e for e in events if e["event"] == "content-block-finish"]
finish_events: list[Any] = [
e for e in events if e["event"] == "content-block-finish"
]
assert len(finish_events) == 1
finalized = cast("ToolCallBlock", finish_events[0]["content_block"])
assert finalized["type"] == "tool_call"
@@ -319,7 +322,9 @@ def test_chunks_to_events_invalid_tool_call_keeps_stop_reason() -> None:
events = list(chunks_to_events(iter(chunks), message_id="msg-bad"))
finish_events = [e for e in events if e["event"] == "content-block-finish"]
finish_events: list[Any] = [
e for e in events if e["event"] == "content-block-finish"
]
assert len(finish_events) == 1
assert finish_events[0]["content_block"]["type"] == "invalid_tool_call"
assert cast("MessageFinishData", events[-1])["reason"] == "stop"
@@ -345,7 +350,7 @@ def test_chunks_to_events_anthropic_server_tool_use_routes_through_translator()
]
events = list(chunks_to_events(iter(chunks)))
finish_blocks = [
finish_blocks: list[Any] = [
e["content_block"] for e in events if e["event"] == "content-block-finish"
]
block_types = [b.get("type") for b in finish_blocks]
@@ -365,7 +370,9 @@ def test_chunks_to_events_unregistered_provider_falls_back() -> None:
]
events = list(chunks_to_events(iter(chunks)))
finish_events = [e for e in events if e["event"] == "content-block-finish"]
finish_events: list[Any] = [
e for e in events if e["event"] == "content-block-finish"
]
assert [e["content_block"]["type"] for e in finish_events] == ["text"]
@@ -395,7 +402,7 @@ def test_chunks_to_events_no_provider_text_plus_tool_call() -> None:
]
events = list(chunks_to_events(iter(chunks)))
finish_blocks = [
finish_blocks: list[Any] = [
e["content_block"] for e in events if e["event"] == "content-block-finish"
]
types = [b.get("type") for b in finish_blocks]
@@ -416,7 +423,7 @@ def test_chunks_to_events_reasoning_in_additional_kwargs() -> None:
]
events = list(chunks_to_events(iter(chunks)))
finish_blocks = [
finish_blocks: list[Any] = [
e["content_block"] for e in events if e["event"] == "content-block-finish"
]
types = [b.get("type") for b in finish_blocks]
@@ -469,12 +476,12 @@ def test_message_to_events_reasoning_text_order() -> None:
)
events = list(message_to_events(msg))
starts = [e for e in events if e["event"] == "content-block-start"]
finishes = [e for e in events if e["event"] == "content-block-finish"]
starts: list[Any] = [e for e in events if e["event"] == "content-block-start"]
finishes: list[Any] = [e for e in events if e["event"] == "content-block-finish"]
assert [s["content_block"]["type"] for s in starts] == ["reasoning", "text"]
assert [f["content_block"]["type"] for f in finishes] == ["reasoning", "text"]
deltas = [e for e in events if e["event"] == "content-block-delta"]
deltas: list[Any] = [e for e in events if e["event"] == "content-block-delta"]
assert len(deltas) == 2
assert cast("ReasoningBlock", deltas[0]["content_block"])["reasoning"] == (
"think hard"
@@ -494,10 +501,10 @@ def test_message_to_events_tool_call_skips_delta_and_infers_tool_use() -> None:
# Finalized tool_call blocks carry no useful incremental text,
# so no content-block-delta is emitted.
deltas = [e for e in events if e["event"] == "content-block-delta"]
deltas: list[Any] = [e for e in events if e["event"] == "content-block-delta"]
assert deltas == []
finishes = [e for e in events if e["event"] == "content-block-finish"]
finishes: list[Any] = [e for e in events if e["event"] == "content-block-finish"]
assert len(finishes) == 1
tc = cast("ToolCallBlock", finishes[0]["content_block"])
assert tc["type"] == "tool_call"
@@ -526,7 +533,7 @@ def test_message_to_events_invalid_tool_calls_surfaced_from_field() -> None:
],
)
events = list(message_to_events(msg))
finishes = [e for e in events if e["event"] == "content-block-finish"]
finishes: list[Any] = [e for e in events if e["event"] == "content-block-finish"]
types = [f["content_block"]["type"] for f in finishes]
assert "invalid_tool_call" in types
@@ -608,7 +615,7 @@ def test_message_to_events_self_contained_start_strips_heavy_fields() -> None:
)
events = list(message_to_events(msg))
starts = [e for e in events if e["event"] == "content-block-start"]
starts: list[Any] = [e for e in events if e["event"] == "content-block-start"]
assert [s["content_block"]["type"] for s in starts] == [
"image",
"audio",
@@ -630,7 +637,7 @@ def test_message_to_events_self_contained_start_strips_heavy_fields() -> None:
assert ns_start["type"] == "non_standard"
assert ns_start["value"] == {}
finishes = [e for e in events if e["event"] == "content-block-finish"]
finishes: list[Any] = [e for e in events if e["event"] == "content-block-finish"]
assert finishes[0]["content_block"]["data"] == "A" * 1024
assert finishes[1]["content_block"]["data"] == "B" * 1024
assert finishes[1]["content_block"]["transcript"] == "hello"
@@ -653,7 +660,7 @@ def test_message_to_events_finalized_tool_call_start_strips_args() -> None:
)
events = list(message_to_events(msg))
starts = [e for e in events if e["event"] == "content-block-start"]
starts: list[Any] = [e for e in events if e["event"] == "content-block-start"]
assert len(starts) == 1
tc_start = starts[0]["content_block"]
assert tc_start["type"] == "tool_call"
@@ -661,7 +668,7 @@ def test_message_to_events_finalized_tool_call_start_strips_args() -> None:
assert tc_start["name"] == "search"
assert tc_start["args"] == {}
finishes = [e for e in events if e["event"] == "content-block-finish"]
finishes: list[Any] = [e for e in events if e["event"] == "content-block-finish"]
tc_finish = cast("ToolCallBlock", finishes[0]["content_block"])
assert tc_finish["args"] == {"q": "big payload " * 100}
@@ -742,7 +749,7 @@ def test_lifecycle_validator_openai_chat_completions_style() -> None:
events = list(chunks_to_events(iter(chunks), message_id="m"))
assert_valid_event_stream(events)
finishes = [e for e in events if e["event"] == "content-block-finish"]
finishes: list[Any] = [e for e in events if e["event"] == "content-block-finish"]
types = [f["content_block"]["type"] for f in finishes]
assert types == ["text", "tool_call"]
assert finishes[0]["content_block"]["text"] == "Hello there"
@@ -767,8 +774,8 @@ def test_lifecycle_validator_openai_responses_style() -> None:
events = list(chunks_to_events(iter(chunks), message_id="m"))
assert_valid_event_stream(events)
starts = [e for e in events if e["event"] == "content-block-start"]
finishes = [e for e in events if e["event"] == "content-block-finish"]
starts: list[Any] = [e for e in events if e["event"] == "content-block-start"]
finishes: list[Any] = [e for e in events if e["event"] == "content-block-finish"]
# Four distinct blocks: reasoning, text, reasoning, text
assert [s["content_block"]["type"] for s in starts] == [
"reasoning",
@@ -801,7 +808,7 @@ def test_lifecycle_validator_anthropic_style_text_and_thinking() -> None:
events = list(chunks_to_events(iter(chunks), message_id="m"))
assert_valid_event_stream(events)
finishes = [e for e in events if e["event"] == "content-block-finish"]
finishes: list[Any] = [e for e in events if e["event"] == "content-block-finish"]
assert [f["content_block"]["type"] for f in finishes] == ["reasoning", "text"]
assert finishes[0]["content_block"]["reasoning"] == "Let me think more"
assert finishes[1]["content_block"]["text"] == "The answer is 42."
@@ -846,7 +853,7 @@ def test_lifecycle_validator_anthropic_style_tool_use_after_text() -> None:
events = list(chunks_to_events(iter(chunks), message_id="m"))
assert_valid_event_stream(events)
finishes = [e for e in events if e["event"] == "content-block-finish"]
finishes: list[Any] = [e for e in events if e["event"] == "content-block-finish"]
assert [f["content_block"]["type"] for f in finishes] == ["text", "tool_call"]
assert finishes[1]["content_block"]["args"] == {"query": "42"}
assert finishes[1]["content_block"]["id"] == "toolu_1"
@@ -870,9 +877,9 @@ def test_lifecycle_validator_inline_image_block() -> None:
events = list(chunks_to_events(iter(chunks), message_id="m"))
assert_valid_event_stream(events)
starts = [e for e in events if e["event"] == "content-block-start"]
deltas = [e for e in events if e["event"] == "content-block-delta"]
finishes = [e for e in events if e["event"] == "content-block-finish"]
starts: list[Any] = [e for e in events if e["event"] == "content-block-start"]
deltas: list[Any] = [e for e in events if e["event"] == "content-block-delta"]
finishes: list[Any] = [e for e in events if e["event"] == "content-block-finish"]
assert [s["content_block"]["type"] for s in starts] == ["image"]
# Self-contained block: no delta, and start has heavy fields stripped.
assert deltas == []
@@ -902,7 +909,7 @@ def test_lifecycle_validator_invalid_tool_call_args() -> None:
events = list(chunks_to_events(iter(chunks), message_id="m"))
assert_valid_event_stream(events)
finishes = [e for e in events if e["event"] == "content-block-finish"]
finishes: list[Any] = [e for e in events if e["event"] == "content-block-finish"]
assert len(finishes) == 1
assert finishes[0]["content_block"]["type"] == "invalid_tool_call"

View File

@@ -2986,10 +2986,13 @@ def test_anthropic_stream_v2_lifecycle() -> None:
assert wire_indices == [0, 1, 2]
# Content accumulation reaches content-block-finish intact.
assert finishes[0]["content_block"]["reasoning"] == "Let me think."
assert finishes[1]["content_block"]["text"] == "The answer is 42."
assert finishes[2]["content_block"]["args"] == {"q": "weather"}
assert finishes[2]["content_block"]["name"] == "search"
reasoning_block = cast("dict[str, Any]", finishes[0]["content_block"])
text_block = cast("dict[str, Any]", finishes[1]["content_block"])
tool_block = cast("dict[str, Any]", finishes[2]["content_block"])
assert reasoning_block["reasoning"] == "Let me think."
assert text_block["text"] == "The answer is 42."
assert tool_block["args"] == {"q": "weather"}
assert tool_block["name"] == "search"
# message-finish carries the tool_use stop reason.
message_finish = stream_events[-1]

View File

@@ -1,7 +1,7 @@
from __future__ import annotations
import copy
from typing import Any
from typing import Any, cast
from unittest.mock import MagicMock, patch
import pytest
@@ -813,7 +813,10 @@ def test_responses_stream_v2_emits_reasoning_lifecycle() -> None:
)
# Finish events must carry the accumulated reasoning text.
reasoning_texts = [f["content_block"]["reasoning"] for f in reasoning_finishes]
reasoning_texts = [
cast("dict[str, Any]", f["content_block"])["reasoning"]
for f in reasoning_finishes
]
assert reasoning_texts == [
"reasoning block one",
"another reasoning block",