Repository navigation
fix(agents): accept sync tool handlers on invoke and native graphs - #134
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 |
🤖 I have created a release *beep* *boop* --- <details><summary>launchdarkly-ai-server: 0.3.0</summary> ## [0.3.0](launchdarkly-ai-server-0.2.4...launchdarkly-ai-server-0.3.0) (2026-10-09) ### ⚠ BREAKING CHANGES * **evaluations:** take tools as a list of EvalTool, with inline definitions ([#105](#105)) * **evaluations:** rename Tool to EvalTool, freeze it, and make tools.get async * **evaluations:** take tools as a list of Tool ### Experimental * **evaluations:** let tools= override a variation's tools ([90b293c](90b293c)) * **evaluations:** Model resolution for remote AI config fetch ([#147](#147)) ([0b4df05](0b4df05)) * **evaluations:** rename Tool to EvalTool, freeze it, and make tools.get async ([4b8a819](4b8a819)) * **evaluations:** take tools as a list of EvalTool, with inline definitions ([#105](#105)) ([cad1547](cad1547)) * **evaluations:** take tools as a list of Tool ([0a92b13](0a92b13)) * **skills:** add experimental Agent Skills under `launchdarkly_ai_server.experimental.skills` ([dfb519c](dfb519c)) ### Features * **AIC-3495:** record which public helpers an application calls ([#145](#145)) ([19d2c2b](19d2c2b)) * **client:** warn about unrecognized `init_client` options instead of ignoring them silently ([dfb519c](dfb519c)) ### Bug Fixes * **client:** `init_client` adopts a client only after telemetry setup succeeds, and closes a client it built when setup fails ([dfb519c](dfb519c)) * **client:** `shutdown()` releases the global OpenTelemetry tracer provider the SDK registered, so a later `init_client` exports spans again ([dfb519c](dfb519c)) ### Documentation * **evaluations:** call the credential an API key, not a token ([7a238e5](7a238e5)) </details> <details><summary>launchdarkly-ai-python: 0.2.0</summary> ## [0.2.0](launchdarkly-ai-python-0.1.8...launchdarkly-ai-python-0.2.0) (2026-10-09) ### ⚠ BREAKING CHANGES * **evaluations:** take tools as a list of EvalTool, with inline definitions ([#105](#105)) * **evaluations:** rename Tool to EvalTool, freeze it, and make tools.get async * **evaluations:** take tools as a list of Tool ### Experimental * **evaluations:** rename Tool to EvalTool, freeze it, and make tools.get async ([4b8a819](4b8a819)) * **evaluations:** take tools as a list of EvalTool, with inline definitions ([#105](#105)) ([cad1547](cad1547)) * **evaluations:** take tools as a list of Tool ([0a92b13](0a92b13)) * **skills:** add experimental Agent Skills under `launchdarkly_ai_server.experimental.skills` ([dfb519c](dfb519c)) ### Features * **client:** warn about unrecognized `init_client` options instead of ignoring them silently ([dfb519c](dfb519c)) ### Bug Fixes * **client:** `init_client` adopts a client only after telemetry setup succeeds, and closes a client it built when setup fails ([dfb519c](dfb519c)) * **client:** `shutdown()` releases the global OpenTelemetry tracer provider the SDK registered, so a later `init_client` exports spans again ([dfb519c](dfb519c)) </details> <details><summary>launchdarkly-ai-claude-agents: 0.2.5</summary> ## [0.2.5](launchdarkly-ai-claude-agents-0.2.4...launchdarkly-ai-claude-agents-0.2.5) (2026-10-09) ### Features * **AIC-3495:** record which public helpers an application calls ([#145](#145)) ([19d2c2b](19d2c2b)) </details> <details><summary>launchdarkly-ai-claude-messages: 0.2.4</summary> ## [0.2.4](launchdarkly-ai-claude-messages-0.2.3...launchdarkly-ai-claude-messages-0.2.4) (2026-10-09) ### Features * **AIC-3495:** record which public helpers an application calls ([#145](#145)) ([19d2c2b](19d2c2b)) </details> <details><summary>launchdarkly-ai-openai-agents: 0.2.5</summary> ## [0.2.5](launchdarkly-ai-openai-agents-0.2.4...launchdarkly-ai-openai-agents-0.2.5) (2026-10-09) ### Features * **AIC-3495:** record which public helpers an application calls ([#145](#145)) ([19d2c2b](19d2c2b)) ### Bug Fixes * **agents:** accept sync tool handlers on invoke and native graphs ([#134](#134)) ([4ab5020](4ab5020)) </details> <details><summary>launchdarkly-ai-openai-messages: 0.2.4</summary> ## [0.2.4](launchdarkly-ai-openai-messages-0.2.3...launchdarkly-ai-openai-messages-0.2.4) (2026-10-09) ### Features * **AIC-3495:** record which public helpers an application calls ([#145](#145)) ([19d2c2b](19d2c2b)) </details> <details><summary>launchdarkly-ai-langchain-agents: 0.2.5</summary> ## [0.2.5](launchdarkly-ai-langchain-agents-0.2.4...launchdarkly-ai-langchain-agents-0.2.5) (2026-10-09) ### Features * **AIC-3495:** record which public helpers an application calls ([#145](#145)) ([19d2c2b](19d2c2b)) ### Bug Fixes * **agents:** accept sync tool handlers on invoke and native graphs ([#134](#134)) ([4ab5020](4ab5020)) * **AIC-3506:** strip tools from LangChain model constructor kwargs ([#133](#133)) ([77c4979](77c4979)) </details> <details><summary>launchdarkly-ai-langchain-messages: 0.2.4</summary> ## [0.2.4](launchdarkly-ai-langchain-messages-0.2.3...launchdarkly-ai-langchain-messages-0.2.4) (2026-10-09) ### Features * **AIC-3495:** record which public helpers an application calls ([#145](#145)) ([19d2c2b](19d2c2b)) ### Bug Fixes * **AIC-3506:** strip tools from LangChain model constructor kwargs ([#133](#133)) ([77c4979](77c4979)) </details> --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Overview** > **Release-only PR** (Release Please): bumps package versions, `__version__` strings, `.release-please-manifest.json`, and `CHANGELOG.md` entries—no application source changes in the diff. > > **`launchdarkly-ai-server` 0.3.0** and **`launchdarkly-ai-python` 0.2.0** document **breaking evaluation tool APIs**: `Tool` → frozen **`EvalTool`**, tools passed as a **list** (with inline definitions), and **`tools.get` is async**. Also ships experimental **evaluations** work (remote AI config model resolution, `tools=` override) and **`launchdarkly_ai_server.experimental.skills`**, plus **`init_client`/`shutdown`** telemetry lifecycle fixes and warnings for unrecognized `init_client` options. > > **Provider packages** (0.2.4–0.2.5) mainly add **SDK usage telemetry** for which public helpers apps call (**AIC-3495**), with **LangChain** fixes (strip `tools` from model ctor kwargs; accept **sync** tool handlers on invoke/graphs) and the same sync-handler fix on **OpenAI agents**. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit c2a6a78. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>



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