From 7e352a6ef30ba5a5985b853918ae7e3f35d08bc5 Mon Sep 17 00:00:00 2001 From: YR Chen Date: Wed, 26 Nov 2025 16:31:53 +0800 Subject: [PATCH] Avoid unwanted exception chaining when running async tool in sync context Running async tool under sync context (require `settings.allow_tool_async_sync_conversion`) is handled by `Tool._run_async_in_sync`. If there's no running event loop, we used to run the coroutine directly when handling the `RuntimeError` raised by `asyncio.get_running_loop()`. This resulted in exception chaining, where the error raised by the tool will be chained with the `RuntimeError` raised by `asyncio`. Since the result will be passed to the LM as tool call result, such behavior is regarded as unnecessary leak of implementation detail, and will diverge the result of running with `async` and in sync. Move the real call outside of the `except` block will resolve the problem. ## Before ``` Execution error in retrieve: Traceback (most recent call last): File ".venv\Lib\site-packages\dspy\adapters\types\tool.py", line 167, in _run_async_in_sync loop = asyncio.get_running_loop() RuntimeError: no running event loop During handling of the above exception, another exception occurred: Traceback (most recent call last): File ".venv\Lib\site-packages\dspy\predict\react.py", line 111, in forward trajectory[f"observation_{idx}"] = self.tools[pred.next_tool_name](**pred.next_tool_args) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^ File ".venv\Lib\site-packages\dspy\utils\callback.py", line 326, in sync_wrapper return fn(instance, *args, **kwargs) File ".venv\Lib\site-packages\dspy\adapters\types\tool.py", line 176, in __call__ result = self.func(**parsed_kwargs) File "src\retrieval\tool.py", line 169, in retrieve result = run_request(kwargs) ~~~~~~~~~~~^^^^^^^^ ValueError: Invalid request ``` ## After ``` Execution error in retrieve: Traceback (most recent call last): File ".venv\Lib\site-packages\dspy\predict\react.py", line 111, in forward trajectory[f"observation_{idx}"] = self.tools[pred.next_tool_name](**pred.next_tool_args) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^ File ".venv\Lib\site-packages\dspy\utils\callback.py", line 326, in sync_wrapper return fn(instance, *args, **kwargs) File ".venv\Lib\site-packages\dspy\adapters\types\tool.py", line 176, in __call__ result = self.func(**parsed_kwargs) File "src\retrieval\tool.py", line 169, in retrieve result = run_request(kwargs) ~~~~~~~~~~~^^^^^^^^ ValueError: Invalid request ``` --- dspy/adapters/types/tool.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/dspy/adapters/types/tool.py b/dspy/adapters/types/tool.py index 16f2cb5805..e6614837da 100644 --- a/dspy/adapters/types/tool.py +++ b/dspy/adapters/types/tool.py @@ -166,8 +166,11 @@ def _run_async_in_sync(self, coroutine): try: loop = asyncio.get_running_loop() except RuntimeError: - return asyncio.run(coroutine) + # Run the coroutine outside of "except" block to avoid propagation + loop = None + if loop is None: + return asyncio.run(coroutine) return loop.run_until_complete(coroutine) @with_callbacks