fix(agents): accept sync tool handlers on invoke and native graphs - #134
andrewklatzke wants to merge 2 commits into
Conversation
Graph handoff tools stay synchronous so routing can record the selected edge. Awaiting every tool result raised before that record happened. Co-authored-by: Cursor <cursoragent@cursor.com>
jeffdupont
left a comment
There was a problem hiding this comment.
GA 1.0 review of 5fb2560 against main. pytest tests/test_handler.py in langchain-agents: 122 passed. With handler.py put back to main, three of the new tests fail with TypeError: object str can't be used in 'await' expression (test_sync_handler_returns_string, test_handoff_handler_records_destination, test_invoke_and_stream_run_sync_handoff), so they catch the bug. The fix matches the rule in tracking.wrap_tool_handlers (tracking.py:102). JS doesn't need a counterpart: langchain-agents/src/handler.ts:301 awaits the result, and awaiting a plain value is fine in JS.
Two things to settle before merge. Both are the same bug on the native-graph path, which this PR doesn't touch:
langchain-agents/native_graph.py:82still doesres = await fn(kwargs). That path reads_opts["tool_handlers"]directly (:161), withoutwrap_tool_handlers, so a sync user handler reaches it unwrapped. Reproduced:_build_node_tools(node, {"weather": lambda a: "sunny"}), then calling the built tool, raisesTypeError: object str can't be used in 'await' expression. It needs the same three lines and a test.openai-agents/native_graph.py:70has the same code (res = await handler(args), withtool_handlersalso read straight from_opts). I haven't run a probe there; that one is from reading the code. AIC-3507 should cover both, either here or in a linked PR.
Smaller notes are inline: one shared helper for calling a handler, documenting the sync/async contract for 1.0, and the scope of the description.
LangChain and OpenAI native graphs read tool_handlers without wrap_tool_handlers, so sync handlers raised TypeError on await. Match the handler/tracking isawaitable rule and document the sync/async contract. Co-authored-by: Cursor <cursoragent@cursor.com>
Thanks for the review — addressed in 100ca1f:
|
|
Integration tests run locally as welll |



Summary
__handoff_*tools stay synchronous so routing records the selected edge instead of raising when the result is awaited._build_agent_tools(), so both routes pick up the same behavior.inspect.isawaitablerule is applied on both native-graph paths (langchain-agentsandopenai-agents), which readtool_handlerswithoutwrap_tool_handlers.Fixes AIC-3507.
Test plan
__handoff_*handler records and returns its destination_build_node_toolsaccepts a sync lambda (Jeff's repro)_build_node_toolsaccepts sync / async / sync-returning-awaitabletest_handler+ bothtest_native_graph)langchain-agents,native-graph-langchain,graph,openai-agents— success exit 0 + valid output JSON; failure exit 1 + clean error