Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions dspy/utils/asyncify.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import TYPE_CHECKING, Any, Awaitable, Callable
from typing import TYPE_CHECKING, Any, Awaitable, Callable, Union, overload

import asyncer
from anyio import CapacityLimiter
Expand Down Expand Up @@ -27,22 +27,30 @@ def get_limiter():
return _limiter


def asyncify(program: "Module") -> Callable[[Any, Any], Awaitable[Any]]:
@overload
def asyncify(program: "Module") -> Callable[..., Awaitable[Any]]: ...
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot can we use generic typing to keep the function signature?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! Updated to use ParamSpec and TypeVar to preserve function signatures. Now when you pass a typed callable, the asyncified version maintains the exact parameter specification and return type: Callable[P, T]Callable[P, Awaitable[T]]. For DSPy Modules, it falls back to Callable[..., Awaitable[T]].

Changes in commit 4b759b3.



@overload
def asyncify(program: Callable[..., Any]) -> Callable[..., Awaitable[Any]]: ...


def asyncify(program: Union["Module", Callable[..., Any]]) -> Callable[..., Awaitable[Any]]:
"""
Wraps a DSPy program so that it can be called asynchronously. This is useful for running a
Wraps a DSPy program or callable so that it can be called asynchronously. This is useful for running a
program in parallel with another task (e.g., another DSPy program).

This implementation propagates the current thread's configuration context to the worker thread.

Args:
program: The DSPy program to be wrapped for asynchronous execution.
program: The DSPy program or callable to be wrapped for asynchronous execution.

Returns:
An async function: An async function that, when awaited, runs the program in a worker thread.
The current thread's configuration context is inherited for each call.
"""

async def async_program(*args, **kwargs) -> Any:
async def async_program(*args: Any, **kwargs: Any) -> Any:
# Capture the current overrides at call-time.
from dspy.dsp.utils.settings import thread_local_overrides

Expand Down
21 changes: 21 additions & 0 deletions tests/utils/test_asyncify.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,24 @@ async def verify_asyncify(capacity: int, number_of_tasks: int, wait: float = 0.5
await verify_asyncify(4, 10)
await verify_asyncify(8, 15)
await verify_asyncify(8, 30)


@pytest.mark.anyio
async def test_asyncify_with_dspy_module():
"""Test that asyncify works with DSPy modules and can be type-checked."""

class SimpleModule(dspy.Module):
def forward(self, x: int) -> int:
return x * 2

module = SimpleModule()
async_module = dspy.asyncify(module)

# Test with positional argument
result = await async_module(5)
assert result == 10, "Asyncified module should return correct result"

# Test with keyword argument
result = await async_module(x=7)
assert result == 14, "Asyncified module should work with keyword arguments"