diff --git a/dspy/adapters/types/tool.py b/dspy/adapters/types/tool.py index 1fa01690d7..a1d98a0129 100644 --- a/dspy/adapters/types/tool.py +++ b/dspy/adapters/types/tool.py @@ -168,6 +168,17 @@ def _run_async_in_sync(self, coroutine): except RuntimeError: return asyncio.run(coroutine) + if loop and loop.is_running(): + try: + import nest_asyncio + + nest_asyncio.apply() + except ImportError: + raise ImportError( + "nest_asyncio is required to call async tools from within a running event loop. " + "Install it with: pip install nest-asyncio" + ) + return loop.run_until_complete(coroutine) @with_callbacks diff --git a/dspy/utils/syncify.py b/dspy/utils/syncify.py index 01aee61cee..9e1c1ff0a7 100644 --- a/dspy/utils/syncify.py +++ b/dspy/utils/syncify.py @@ -14,10 +14,15 @@ def run_async(coro): loop = None if loop and loop.is_running(): - # If we're in a running event loop (e.g., Jupyter), use asyncio.create_task and run until done - import nest_asyncio - - nest_asyncio.apply() + try: + import nest_asyncio + + nest_asyncio.apply() + except ImportError: + raise ImportError( + "nest_asyncio is required to call async functions from within a running event loop. " + "Install it with: pip install nest-asyncio" + ) return asyncio.get_event_loop().run_until_complete(coro) else: return asyncio.run(coro) diff --git a/pyproject.toml b/pyproject.toml index ce72e6a9f0..7348c18191 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,6 +57,7 @@ dev = [ "build>=1.0.3", "litellm>=1.64.0; sys_platform == 'win32' or python_version == '3.14'", "litellm[proxy]>=1.64.0; sys_platform != 'win32' and python_version < '3.14'", # Remove 3.14 condition once uvloop supports + "nest-asyncio>=1.6.0", ] test_extras = [ "mcp; python_version >= '3.10'", diff --git a/tests/adapters/test_tool.py b/tests/adapters/test_tool.py index 876ff5fc79..ce5f10f652 100644 --- a/tests/adapters/test_tool.py +++ b/tests/adapters/test_tool.py @@ -393,6 +393,16 @@ def test_async_tool_call_in_sync_mode(): assert result == "hello 1" +@pytest.mark.asyncio +@pytest.mark.filterwarnings("ignore::DeprecationWarning:pytest_asyncio") +async def test_async_tool_call_from_running_event_loop(): + tool = Tool(async_dummy_function) + + with dspy.context(allow_tool_async_sync_conversion=True): + result = tool(x=42, y="test") + assert result == "test 42" + + TOOL_CALL_TEST_CASES = [ ([], {"tool_calls": []}), ( @@ -542,8 +552,6 @@ def test_tool_convert_input_schema_to_tool_args_lang_chain(): } - - def test_tool_call_execute(): def get_weather(city: str) -> str: return f"The weather in {city} is sunny" diff --git a/uv.lock b/uv.lock index ff1bb26ce9..b3a0473523 100644 --- a/uv.lock +++ b/uv.lock @@ -762,6 +762,7 @@ dev = [ { name = "datamodel-code-generator" }, { name = "litellm" }, { name = "litellm", extra = ["proxy"], marker = "python_full_version < '3.14' and sys_platform != 'win32'" }, + { name = "nest-asyncio" }, { name = "pillow" }, { name = "pre-commit" }, { name = "pytest" }, @@ -799,7 +800,7 @@ requires-dist = [ { name = "datamodel-code-generator", marker = "extra == 'dev'", specifier = ">=0.26.3" }, { name = "datasets", marker = "extra == 'test-extras'", specifier = ">=2.14.6" }, { name = "diskcache", specifier = ">=5.6.0" }, - { name = "gepa", extras = ["dspy"], specifier = "==0.0.18" }, + { name = "gepa", extras = ["dspy"], specifier = "==0.0.22" }, { name = "json-repair", specifier = ">=0.30.0" }, { name = "langchain-core", marker = "extra == 'langchain'" }, { name = "langchain-core", marker = "extra == 'test-extras'" }, @@ -808,6 +809,7 @@ requires-dist = [ { name = "litellm", extras = ["proxy"], marker = "python_full_version < '3.14' and sys_platform != 'win32' and extra == 'dev'", specifier = ">=1.64.0" }, { name = "mcp", marker = "python_full_version >= '3.10' and extra == 'mcp'" }, { name = "mcp", marker = "python_full_version >= '3.10' and extra == 'test-extras'" }, + { name = "nest-asyncio", marker = "extra == 'dev'", specifier = ">=1.6.0" }, { name = "numpy", specifier = ">=1.26.0" }, { name = "openai", specifier = ">=0.28.1" }, { name = "optuna", specifier = ">=3.4.0" }, @@ -1012,11 +1014,11 @@ wheels = [ [[package]] name = "gepa" -version = "0.0.18" +version = "0.0.22" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2e/c1/81235474f37ceb2f9c82e49c34a0eab8ceb6e641a3f79d4ffa34f29febf1/gepa-0.0.18.tar.gz", hash = "sha256:368f3bab9b155a3e9e43e739bd244e6eccad4d40cb3a005d23c3f7407c0ef00a", size = 103385, upload-time = "2025-10-25T02:34:25.535Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/be/9a4c31c65c4910e73bd4a316df99347681bce4f2ef6d10877cc1ed8d57da/gepa-0.0.22.tar.gz", hash = "sha256:0b13a644efd2af52186e456eaad2ae28fcf88c6f796a9c999910c587863d4315", size = 116337, upload-time = "2025-11-10T21:39:27.044Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/52/9f2a82b4c8910fc12d43a2c27c30d1253b9cdc0d0ef65fd613c9104e9ef8/gepa-0.0.18-py3-none-any.whl", hash = "sha256:0ca0ddf05610cdd6b1d37a778e0963069dbba1c3075a188ac15236847d4ad2b7", size = 112168, upload-time = "2025-10-25T02:34:24.315Z" }, + { url = "https://files.pythonhosted.org/packages/b3/0e/d41272b28f2163f5bf840b508cbaf4a0bc01eaeaa6e5d447558d1050eb3b/gepa-0.0.22-py3-none-any.whl", hash = "sha256:ff57ee0442399a5cc62d01411935476bc80cfa8f1c318bed82e3db4c2c834665", size = 119666, upload-time = "2025-11-10T21:39:26.028Z" }, ] [[package]] @@ -1866,6 +1868,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, ] +[[package]] +name = "nest-asyncio" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418, upload-time = "2024-01-21T14:25:19.227Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" }, +] + [[package]] name = "nodeenv" version = "1.9.1"