Skip to content

Commit db84cf5

Browse files
declan-scaleclaude
andcommitted
test(langgraph): 4 conformance fixtures (text, tool, reasoning, multi-step)
Registers LangGraph-specific conformance fixtures with the shared harness conformance runner. Documents the AGX1-377 behavior (tool requests are Full events, not Start+Done). Span derivation is deterministic for all 4 fixtures. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 14846f4 commit db84cf5

1 file changed

Lines changed: 206 additions & 0 deletions

File tree

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
"""LangGraph conformance fixtures for the cross-channel span-derivation test.
2+
3+
Registers 4 LangGraph event sequences as conformance fixtures:
4+
- text-only: a plain text response (no tool calls)
5+
- single-tool: one tool call + response
6+
- reasoning: a reasoning block + text
7+
- multi-step: two turns with tool calls
8+
9+
AGX1-377 note: LangGraph emits tool requests as ``StreamTaskMessageFull``
10+
(from "updates" events), NOT Start+Delta+Done like pydantic-ai. The SpanDeriver
11+
does not produce tool spans from Full events today; that gap is tracked in
12+
AGX1-373. The fixtures here document the current behavior and will be updated
13+
when AGX1-373 resolves.
14+
"""
15+
16+
from __future__ import annotations
17+
18+
import pytest
19+
20+
from agentex.types.text_content import TextContent
21+
from agentex.types.reasoning_content import ReasoningContent
22+
from agentex.types.task_message_delta import TextDelta
23+
from agentex.types.task_message_update import (
24+
StreamTaskMessageDone,
25+
StreamTaskMessageFull,
26+
StreamTaskMessageDelta,
27+
StreamTaskMessageStart,
28+
)
29+
from agentex.types.tool_request_content import ToolRequestContent
30+
from agentex.types.tool_response_content import ToolResponseContent
31+
from agentex.types.reasoning_content_delta import ReasoningContentDelta
32+
33+
from .runner import Fixture, register, derive_all
34+
35+
# ---------------------------------------------------------------------------
36+
# Fixtures
37+
# ---------------------------------------------------------------------------
38+
39+
_TEXT_ONLY = Fixture(
40+
name="langgraph-text-only",
41+
events=[
42+
StreamTaskMessageStart(
43+
type="start",
44+
index=0,
45+
content=TextContent(type="text", author="agent", content=""),
46+
),
47+
StreamTaskMessageDelta(
48+
type="delta",
49+
index=0,
50+
delta=TextDelta(type="text", text_delta="Hello from LangGraph!"),
51+
),
52+
StreamTaskMessageDone(type="done", index=0),
53+
],
54+
)
55+
56+
_SINGLE_TOOL = Fixture(
57+
name="langgraph-single-tool",
58+
events=[
59+
# LangGraph tool request is a Full event (AGX1-377)
60+
StreamTaskMessageFull(
61+
type="full",
62+
index=0,
63+
content=ToolRequestContent(
64+
type="tool_request",
65+
author="agent",
66+
tool_call_id="call_1",
67+
name="get_weather",
68+
arguments={"city": "Paris"},
69+
),
70+
),
71+
StreamTaskMessageFull(
72+
type="full",
73+
index=1,
74+
content=ToolResponseContent(
75+
type="tool_response",
76+
author="agent",
77+
tool_call_id="call_1",
78+
name="get_weather",
79+
content="Sunny, 72F",
80+
),
81+
),
82+
StreamTaskMessageStart(
83+
type="start",
84+
index=2,
85+
content=TextContent(type="text", author="agent", content=""),
86+
),
87+
StreamTaskMessageDelta(
88+
type="delta",
89+
index=2,
90+
delta=TextDelta(type="text", text_delta="The weather in Paris is sunny, 72F."),
91+
),
92+
StreamTaskMessageDone(type="done", index=2),
93+
],
94+
)
95+
96+
_REASONING = Fixture(
97+
name="langgraph-reasoning",
98+
events=[
99+
StreamTaskMessageStart(
100+
type="start",
101+
index=0,
102+
content=ReasoningContent(
103+
type="reasoning",
104+
author="agent",
105+
summary=[],
106+
content=[],
107+
style="active",
108+
),
109+
),
110+
StreamTaskMessageDelta(
111+
type="delta",
112+
index=0,
113+
delta=ReasoningContentDelta(
114+
type="reasoning_content",
115+
content_index=0,
116+
content_delta="Thinking about this...",
117+
),
118+
),
119+
StreamTaskMessageDone(type="done", index=0),
120+
StreamTaskMessageStart(
121+
type="start",
122+
index=1,
123+
content=TextContent(type="text", author="agent", content=""),
124+
),
125+
StreamTaskMessageDelta(
126+
type="delta",
127+
index=1,
128+
delta=TextDelta(type="text", text_delta="The answer is 42."),
129+
),
130+
StreamTaskMessageDone(type="done", index=1),
131+
],
132+
)
133+
134+
_MULTI_STEP = Fixture(
135+
name="langgraph-multi-step",
136+
events=[
137+
# Turn 1: text + tool call
138+
StreamTaskMessageStart(
139+
type="start",
140+
index=0,
141+
content=TextContent(type="text", author="agent", content=""),
142+
),
143+
StreamTaskMessageDelta(
144+
type="delta",
145+
index=0,
146+
delta=TextDelta(type="text", text_delta="Let me search for that."),
147+
),
148+
StreamTaskMessageDone(type="done", index=0),
149+
# Tool request (Full — AGX1-377)
150+
StreamTaskMessageFull(
151+
type="full",
152+
index=1,
153+
content=ToolRequestContent(
154+
type="tool_request",
155+
author="agent",
156+
tool_call_id="call_2",
157+
name="search",
158+
arguments={"query": "langgraph"},
159+
),
160+
),
161+
StreamTaskMessageFull(
162+
type="full",
163+
index=2,
164+
content=ToolResponseContent(
165+
type="tool_response",
166+
author="agent",
167+
tool_call_id="call_2",
168+
name="search",
169+
content="LangGraph is a framework for...",
170+
),
171+
),
172+
# Turn 2: final text
173+
StreamTaskMessageStart(
174+
type="start",
175+
index=3,
176+
content=TextContent(type="text", author="agent", content=""),
177+
),
178+
StreamTaskMessageDelta(
179+
type="delta",
180+
index=3,
181+
delta=TextDelta(type="text", text_delta="Based on my research, LangGraph is..."),
182+
),
183+
StreamTaskMessageDone(type="done", index=3),
184+
],
185+
)
186+
187+
_LANGGRAPH_FIXTURES = [_TEXT_ONLY, _SINGLE_TOOL, _REASONING, _MULTI_STEP]
188+
189+
for _fixture in _LANGGRAPH_FIXTURES:
190+
register(_fixture)
191+
192+
193+
# ---------------------------------------------------------------------------
194+
# Tests
195+
# ---------------------------------------------------------------------------
196+
197+
198+
@pytest.mark.parametrize("fixture", _LANGGRAPH_FIXTURES, ids=lambda f: f.name)
199+
def test_langgraph_span_derivation_is_deterministic(fixture: Fixture):
200+
"""Exercises the cross-channel guarantee: yield and auto-send observe the
201+
same event stream, so span derivation must be deterministic/idempotent.
202+
203+
Deriving twice over the same events yields identical signals (the property
204+
that makes yield vs auto-send equivalent, since both observe the same stream).
205+
"""
206+
assert derive_all(fixture.events) == derive_all(fixture.events)

0 commit comments

Comments
 (0)