Skip to content

Commit 1d83504

Browse files
committed
feat(async): integrate nest-asyncio for running async tools in event loops
- Added nest-asyncio as a dependency to allow calling async tools from within a running event loop. - Updated the Tool class to apply nest-asyncio when the event loop is already running. - Enhanced syncify utility to raise an ImportError if nest-asyncio is not installed when needed. - Added a new test case to verify async tool calls from a running event loop. This change improves compatibility with environments like Jupyter notebooks where event loops are already active. Signed-off-by: TomuHirata <[email protected]>
1 parent fe03ead commit 1d83504

File tree

5 files changed

+46
-10
lines changed

5 files changed

+46
-10
lines changed

dspy/adapters/types/tool.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,17 @@ def _run_async_in_sync(self, coroutine):
168168
except RuntimeError:
169169
return asyncio.run(coroutine)
170170

171+
if loop and loop.is_running():
172+
try:
173+
import nest_asyncio
174+
175+
nest_asyncio.apply()
176+
except ImportError:
177+
raise ImportError(
178+
"nest_asyncio is required to call async tools from within a running event loop. "
179+
"Install it with: pip install nest-asyncio"
180+
)
181+
171182
return loop.run_until_complete(coroutine)
172183

173184
@with_callbacks

dspy/utils/syncify.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,15 @@ def run_async(coro):
1414
loop = None
1515

1616
if loop and loop.is_running():
17-
# If we're in a running event loop (e.g., Jupyter), use asyncio.create_task and run until done
18-
import nest_asyncio
19-
20-
nest_asyncio.apply()
17+
try:
18+
import nest_asyncio
19+
20+
nest_asyncio.apply()
21+
except ImportError:
22+
raise ImportError(
23+
"nest_asyncio is required to call async functions from within a running event loop. "
24+
"Install it with: pip install nest-asyncio"
25+
)
2126
return asyncio.get_event_loop().run_until_complete(coro)
2227
else:
2328
return asyncio.run(coro)

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ dev = [
5757
"build>=1.0.3",
5858
"litellm>=1.64.0; sys_platform == 'win32' or python_version == '3.14'",
5959
"litellm[proxy]>=1.64.0; sys_platform != 'win32' and python_version < '3.14'", # Remove 3.14 condition once uvloop supports
60+
"nest-asyncio>=1.6.0",
6061
]
6162
test_extras = [
6263
"mcp; python_version >= '3.10'",

tests/adapters/test_tool.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,16 @@ def test_async_tool_call_in_sync_mode():
393393
assert result == "hello 1"
394394

395395

396+
@pytest.mark.asyncio
397+
@pytest.mark.filterwarnings("ignore::DeprecationWarning:pytest_asyncio")
398+
async def test_async_tool_call_from_running_event_loop():
399+
tool = Tool(async_dummy_function)
400+
401+
with dspy.context(allow_tool_async_sync_conversion=True):
402+
result = tool(x=42, y="test")
403+
assert result == "test 42"
404+
405+
396406
TOOL_CALL_TEST_CASES = [
397407
([], {"tool_calls": []}),
398408
(
@@ -542,8 +552,6 @@ def test_tool_convert_input_schema_to_tool_args_lang_chain():
542552
}
543553

544554

545-
546-
547555
def test_tool_call_execute():
548556
def get_weather(city: str) -> str:
549557
return f"The weather in {city} is sunny"

uv.lock

Lines changed: 15 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)