Skip to content

Commit 1bb517b

Browse files
declan-scaleclaude
andcommitted
test(pydantic-ai): characterize stream_pydantic_ai_events output
Adds TestCharacterizeWireShapeCurrent to lock the current wire-level delivery shape: text via streaming_task_message_context, tool messages via adk.messages.create. Serves as the before-snapshot for the UnifiedEmitter reimplementation that follows. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent cbc96df commit 1bb517b

1 file changed

Lines changed: 75 additions & 0 deletions

File tree

tests/lib/adk/test_pydantic_ai_async.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -867,3 +867,78 @@ async def boom() -> AsyncIterator[Any]:
867867
await stream_pydantic_ai_events(boom(), TASK_ID)
868868

869869
assert streaming.contexts[0].closed is True
870+
871+
872+
# ---------------------------------------------------------------------------
873+
# Characterization test: lock the wire-level delivery shape for a representative
874+
# pydantic-ai run (text + tool call + tool response + more text).
875+
#
876+
# Step 1 (CURRENT behavior): written against the original implementation.
877+
# - Text/reasoning use adk.streaming.streaming_task_message_context.
878+
# - Tool messages use adk.messages.create (FakeMessagesModule.created list).
879+
# - Final text is the last text segment.
880+
#
881+
# Step 2 (POST-reimplementation on UnifiedEmitter / auto_send):
882+
# The assertions in TestCharacterizeWireShapeNew (below) lock the new shape.
883+
# Tool messages no longer go through adk.messages.create; they arrive via
884+
# streaming_task_message_context open+close pairs (Start+Done envelope).
885+
# This is the AGX1-373 accepted envelope change: logical content is identical.
886+
# ---------------------------------------------------------------------------
887+
888+
889+
class TestCharacterizeWireShapeCurrent:
890+
"""Characterization tests: lock the CURRENT wire-level delivery shape.
891+
892+
Uses FakeStreamingModule + FakeMessagesModule (the existing fake pair).
893+
Tool messages arrive via adk.messages.create; text via adk.streaming.
894+
"""
895+
896+
async def test_text_tool_text_current_wire_shape(
897+
self, fake_adk: tuple[FakeStreamingModule, FakeMessagesModule]
898+
) -> None:
899+
"""Representative run: text -> tool call -> tool response -> more text.
900+
901+
Records the CURRENT delivery shape before reimplementation:
902+
- Two streaming contexts (text segments).
903+
- Two adk.messages.create calls (tool_request, tool_response).
904+
- Final text == "It's sunny." (last segment only).
905+
"""
906+
from pydantic_ai.messages import ToolReturnPart
907+
908+
streaming, messages = fake_adk
909+
events = [
910+
PartStartEvent(index=0, part=TextPart(content="")),
911+
PartDeltaEvent(index=0, delta=TextPartDelta(content_delta="Looking up...")),
912+
PartEndEvent(index=0, part=TextPart(content="Looking up...")),
913+
PartStartEvent(
914+
index=1,
915+
part=ToolCallPart(tool_name="get_weather", args=None, tool_call_id="c1"),
916+
),
917+
PartEndEvent(
918+
index=1,
919+
part=ToolCallPart(tool_name="get_weather", args="{}", tool_call_id="c1"),
920+
),
921+
FunctionToolResultEvent(
922+
part=ToolReturnPart(tool_name="get_weather", content="Sunny", tool_call_id="c1"),
923+
),
924+
PartStartEvent(index=0, part=TextPart(content="")),
925+
PartDeltaEvent(index=0, delta=TextPartDelta(content_delta="It's sunny.")),
926+
PartEndEvent(index=0, part=TextPart(content="It's sunny.")),
927+
]
928+
929+
final = await stream_pydantic_ai_events(_aiter(events), TASK_ID)
930+
931+
assert final == "It's sunny.", "multi-step: only the last text segment is returned"
932+
assert len(streaming.contexts) == 2, "two text segments -> two streaming contexts"
933+
assert all(ctx.closed for ctx in streaming.contexts)
934+
assert _text_deltas(streaming.contexts[0]) == ["Looking up..."]
935+
assert _text_deltas(streaming.contexts[1]) == ["It's sunny."]
936+
937+
# CURRENT shape: tool messages arrive via adk.messages.create
938+
assert len(messages.created) == 2, "one tool_request + one tool_response"
939+
assert isinstance(messages.created[0]["content"], ToolRequestContent)
940+
assert messages.created[0]["content"].tool_call_id == "c1"
941+
assert messages.created[0]["content"].name == "get_weather"
942+
assert isinstance(messages.created[1]["content"], ToolResponseContent)
943+
assert messages.created[1]["content"].tool_call_id == "c1"
944+
assert messages.created[1]["content"].content == "Sunny"

0 commit comments

Comments
 (0)