Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import asyncio
import functools
import inspect
import warnings
from textwrap import dedent
from typing import Any, Callable, Sequence
from typing import Any, Callable, Dict, List, Sequence

from pydantic import BaseModel
from typing_extensions import Self
Expand Down Expand Up @@ -103,29 +104,43 @@ def __init__(
super().__init__(args_model, return_type, func_name, description, strict)

async def run(self, args: BaseModel, cancellation_token: CancellationToken) -> Any:
kwargs = {}
pos_args: List[Any] = []
kwargs: Dict[str, Any] = {}

for name, param in self._signature.parameters.items():
if name == "cancellation_token":
val = cancellation_token
elif hasattr(args, name):
val = getattr(args, name)
else:
continue

for name in self._signature.parameters.keys():
if hasattr(args, name):
kwargs[name] = getattr(args, name)
if param.kind == inspect.Parameter.POSITIONAL_ONLY:
pos_args.append(val)
else:
kwargs[name] = val

if asyncio.iscoroutinefunction(self._func):
if self._has_cancellation_support:
result = await self._func(**kwargs, cancellation_token=cancellation_token)
else:
result = await self._func(**kwargs)
result = await self._func(*pos_args, **kwargs)
else:
if self._has_cancellation_support:
result = await asyncio.get_event_loop().run_in_executor(
None,
functools.partial(
self._func,
*pos_args,
**kwargs,
cancellation_token=cancellation_token,
),
)
else:
future = asyncio.get_event_loop().run_in_executor(None, functools.partial(self._func, **kwargs))
future = asyncio.get_event_loop().run_in_executor(
None,
functools.partial(
self._func,
*pos_args,
**kwargs,
),
)
cancellation_token.link_future(future)
result = await future

Expand Down
44 changes: 44 additions & 0 deletions python/packages/autogen-core/tests/test_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -589,3 +589,47 @@ async def test_func_tool_with_dataclass_conversion_failure() -> None:

with pytest.raises(ValidationError, match="Field required"):
await tool.run_json(test_input, CancellationToken())


def _scale_sync(value: int, /, *, factor: int = 2) -> int:
return value * factor


async def _scale_async(value: int, /, factor: int = 3) -> int:
return value * factor


def _cancel_sync(x: int, cancellation_token: CancellationToken, /) -> int:
return x + 1


async def _cancel_async(x: int, cancellation_token: CancellationToken, /) -> int:
return x + 2


@pytest.mark.asyncio
async def test_func_tool_positional_only_sync() -> None:
tool = FunctionTool(_scale_sync, description="Scale value sync.")
result = await tool.run_json({"value": 4, "factor": 5}, CancellationToken())
assert result == 20


@pytest.mark.asyncio
async def test_func_tool_positional_only_async() -> None:
tool = FunctionTool(_scale_async, description="Scale value async.")
result = await tool.run_json({"value": 4}, CancellationToken())
assert result == 12


@pytest.mark.asyncio
async def test_func_tool_positional_only_cancellation_token_sync() -> None:
tool = FunctionTool(_cancel_sync, description="Cancel token sync.")
result = await tool.run_json({"x": 10}, CancellationToken())
assert result == 11


@pytest.mark.asyncio
async def test_func_tool_positional_only_cancellation_token_async() -> None:
tool = FunctionTool(_cancel_async, description="Cancel token async.")
result = await tool.run_json({"x": 10}, CancellationToken())
assert result == 12