Skip to content

Commit cb03bbc

Browse files
declan-scaleclaude
andcommitted
fix(langgraph): reasoning Start needs non-null style + native async tool
- _langgraph_sync.py: reasoning StreamTaskMessageStart now sets style="active". The AgentEx server's StreamTaskMessageStartEntity rejects reasoning.style=None (enum), which killed the sync stream — breaking harness_langgraph and the pre-existing 030_langgraph that share this emitter. - temporal harness tools.py: give get_weather a native async coroutine so tools_node's `await tool.ainvoke(...)` runs on the workflow loop instead of LangChain's run_in_executor fallback (NotImplementedError in the deterministic Temporal workflow sandbox). - test_langgraph_sync.py: assert the reasoning Start carries a non-null style. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent bdd528b commit cb03bbc

3 files changed

Lines changed: 20 additions & 1 deletion

File tree

examples/tutorials/10_async/10_temporal/harness_langgraph/project/tools.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,22 @@ def get_weather(city: str) -> str:
1515
return f"The weather in {city} is sunny and 72°F"
1616

1717

18+
async def aget_weather(city: str) -> str:
19+
"""Native async tool entrypoint.
20+
21+
``tools_node`` runs inline in the Temporal workflow and invokes tools via
22+
``tool.ainvoke``. A sync-only tool forces LangChain to bridge through
23+
``run_in_executor`` (a thread pool), which the deterministic Temporal
24+
workflow event loop forbids (``NotImplementedError``). Providing a real
25+
coroutine keeps tool execution on the workflow loop.
26+
"""
27+
return get_weather(city)
28+
29+
1830
weather_tool = Tool(
1931
name="get_weather",
2032
func=get_weather,
33+
coroutine=aget_weather,
2134
description="Get the current weather for a city. Input should be a city name.",
2235
)
2336

src/agentex/lib/adk/_modules/_langgraph_sync.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,9 @@ async def convert_langgraph_to_agentex_events(
149149
yield StreamTaskMessageStart(
150150
type="start",
151151
index=message_index,
152-
content=ReasoningContent(type="reasoning", author="agent", summary=[], content=[]),
152+
content=ReasoningContent(
153+
type="reasoning", author="agent", summary=[], content=[], style="active"
154+
),
153155
)
154156
reasoning_streaming = True
155157
reasoning_content_index = 0

tests/lib/adk/test_langgraph_sync.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,10 @@ async def test_reasoning_block_start_wraps_reasoning_content(self):
101101
starts = [e for e in out if isinstance(e, StreamTaskMessageStart)]
102102
assert len(starts) == 1
103103
assert isinstance(starts[0].content, ReasoningContent), "reasoning Start must wrap ReasoningContent"
104+
# `style` must be a non-null MessageStyle: the AgentEx server's
105+
# StreamTaskMessageStartEntity rejects `reasoning.style=None` (enum), which
106+
# would kill the stream. Match the conformance fixture's canonical value.
107+
assert starts[0].content.style == "active", "reasoning Start must set a non-null style ('active')"
104108
# Pull content_delta inside the comprehension so the isinstance narrows the
105109
# delta union (narrowing would not survive a later attribute access).
106110
reasoning_delta_texts = [

0 commit comments

Comments
 (0)