diff --git a/docs/docs/learn/programming/tools.md b/docs/docs/learn/programming/tools.md index 757421de01..0beded0620 100644 --- a/docs/docs/learn/programming/tools.md +++ b/docs/docs/learn/programming/tools.md @@ -213,6 +213,52 @@ the screenshot below: Native function calling automatically detects model support using `litellm.supports_function_calling()`. If the model doesn't support native function calling, DSPy will fall back to manual text-based parsing even when `use_native_function_calling=True` is set. +## Async Tools + +DSPy tools support both synchronous and asynchronous functions. When working with async tools, you have two options: + +### Using `acall` for Async Tools + +The recommended approach is to use `acall` when working with async tools: + +```python +import asyncio +import dspy + +async def async_weather(city: str) -> str: + """Get weather information asynchronously.""" + await asyncio.sleep(0.1) # Simulate async API call + return f"The weather in {city} is sunny" + +tool = dspy.Tool(async_weather) + +# Use acall for async tools +result = await tool.acall(city="New York") +print(result) +``` + +### Running Async Tools in Sync Mode + +If you need to call an async tool from synchronous code, you can enable automatic conversion using the `allow_tool_async_sync_conversion` setting: + +```python +import asyncio +import dspy + +async def async_weather(city: str) -> str: + """Get weather information asynchronously.""" + await asyncio.sleep(0.1) + return f"The weather in {city} is sunny" + +tool = dspy.Tool(async_weather) + +# Enable async-to-sync conversion +with dspy.context(allow_tool_async_sync_conversion=True): + # Now you can use __call__ on async tools + result = tool(city="New York") + print(result) +``` + ## Best Practices ### 1. Tool Function Design diff --git a/docs/docs/tutorials/async/index.md b/docs/docs/tutorials/async/index.md index 089036b68e..288364964a 100644 --- a/docs/docs/tutorials/async/index.md +++ b/docs/docs/tutorials/async/index.md @@ -99,6 +99,33 @@ async def main(): asyncio.run(main()) ``` +#### Using Async Tools in Synchronous Contexts + +If you need to call an async tool from synchronous code, you can enable automatic async-to-sync conversion: + +```python +import dspy + +async def async_tool(x: int) -> int: + """An async tool that doubles a number.""" + await asyncio.sleep(0.1) + return x * 2 + +tool = dspy.Tool(async_tool) + +# Option 1: Use context manager for temporary conversion +with dspy.context(allow_tool_async_sync_conversion=True): + result = tool(x=5) # Works in sync context + print(result) # 10 + +# Option 2: Configure globally +dspy.configure(allow_tool_async_sync_conversion=True) +result = tool(x=5) # Now works everywhere +print(result) # 10 +``` + +For more details on async tools, see the [Tools documentation](../../learn/programming/tools.md#async-tools). + Note: When using `dspy.ReAct` with tools, calling `acall()` on the ReAct instance will automatically execute all tools asynchronously using their `acall()` methods. diff --git a/docs/docs/tutorials/yahoo_finance_react/index.md b/docs/docs/tutorials/yahoo_finance_react/index.md index de55f60aa9..1c6f0838bc 100644 --- a/docs/docs/tutorials/yahoo_finance_react/index.md +++ b/docs/docs/tutorials/yahoo_finance_react/index.md @@ -23,7 +23,7 @@ import yfinance as yf # Configure DSPy lm = dspy.LM(model='openai/gpt-4o-mini') -dspy.configure(lm=lm) +dspy.configure(lm=lm, allow_tool_async_sync_conversion=True) # Convert LangChain Yahoo Finance tool to DSPy yahoo_finance_tool = YahooFinanceNewsTool() @@ -152,6 +152,10 @@ When you run the agent with a query like "What's the latest news about Apple?", Analysis: Given the current price of Apple (AAPL) at $196.58 and the slight increase of 0.48%, it appears that the stock is performing steadily in the market. However, the inability to access the latest news means that any significant developments that could influence investor sentiment and stock price are unknown. Investors should keep an eye on upcoming announcements or market trends that could impact Apple's performance, especially in comparison to other tech stocks like Microsoft (MSFT), which is also showing a positive trend. ``` +## Working with Async Tools + +Many Langchain tools use async operations for better performance. For details on async tools, see the [Tools documentation](../../learn/programming/tools.md#async-tools). + ## Key Benefits - **Tool Integration**: Seamlessly combine LangChain tools with DSPy ReAct diff --git a/dspy/adapters/types/tool.py b/dspy/adapters/types/tool.py index 1fa01690d7..16f2cb5805 100644 --- a/dspy/adapters/types/tool.py +++ b/dspy/adapters/types/tool.py @@ -179,8 +179,9 @@ def __call__(self, **kwargs): return self._run_async_in_sync(result) else: raise ValueError( - "You are calling `__call__` on an async tool, please use `acall` instead or set " - "`allow_async=True` to run the async tool in sync mode." + "You are calling `__call__` on an async tool, please use `acall` instead or enable " + "async-to-sync conversion with `dspy.configure(allow_tool_async_sync_conversion=True)` " + "or `with dspy.context(allow_tool_async_sync_conversion=True):`." ) return result diff --git a/tests/adapters/test_tool.py b/tests/adapters/test_tool.py index 876ff5fc79..cfcffe0947 100644 --- a/tests/adapters/test_tool.py +++ b/tests/adapters/test_tool.py @@ -385,7 +385,7 @@ async def test_async_concurrent_calls(): def test_async_tool_call_in_sync_mode(): tool = Tool(async_dummy_function) with dspy.context(allow_tool_async_sync_conversion=False): - with pytest.raises(ValueError): + with pytest.raises(ValueError, match=r".*acall.*allow_tool_async_sync_conversion.*"): result = tool(x=1, y="hello") with dspy.context(allow_tool_async_sync_conversion=True):