diff --git a/autogen/fast_depends/core/model.py b/autogen/fast_depends/core/model.py index fdc0a41cc256..7bdedc4a84c4 100644 --- a/autogen/fast_depends/core/model.py +++ b/autogen/fast_depends/core/model.py @@ -219,6 +219,15 @@ def _solve( if (v := kwargs.pop(arg, Parameter.empty)) is not Parameter.empty: kw[arg] = v + # Positional parameters can also be supplied by name (e.g. when an LLM + # tool call delivers all arguments as keyword args). Pull them out + # before assigning the remainder to ``var_keyword_arg`` so they don't + # get swallowed into ``**kwargs``. + if self.var_keyword_arg is not None: + for arg in self.positional_args: + if (v := kwargs.pop(arg, Parameter.empty)) is not Parameter.empty: + kw[arg] = v + if self.var_keyword_arg is not None: kw[self.var_keyword_arg] = kwargs else: diff --git a/test/fast_depends/async/test_cast.py b/test/fast_depends/async/test_cast.py index 1b0cf6ccaf45..3b4c89aa9ccb 100644 --- a/test/fast_depends/async/test_cast.py +++ b/test/fast_depends/async/test_cast.py @@ -157,6 +157,21 @@ async def simple_func( assert await simple_func(1.0, 2.0, 3, b=3.0, key=1.0) == (1, (2.0, 3.0), 3, {"key": 1}) +@pytest.mark.anyio +async def test_positional_args_passed_by_name_with_var_keyword(): + """Regression for ag2 #1790 — async path.""" + + @inject + async def simple_func( + arg1: str, + arg2: str, + **kwargs: dict[str, str], + ): + return arg1, arg2, kwargs + + assert await simple_func(arg1="x", arg2="y", extra="z") == ("x", "y", {"extra": "z"}) + + @pytest.mark.anyio async def test_args_kwargs_2(): @inject diff --git a/test/fast_depends/sync/test_cast.py b/test/fast_depends/sync/test_cast.py index 55f3f0cc1949..153844aa3013 100644 --- a/test/fast_depends/sync/test_cast.py +++ b/test/fast_depends/sync/test_cast.py @@ -157,6 +157,25 @@ def simple_func( assert simple_func(1.0, 2.0, 3, b=3.0, key=1.0) == (1, (2.0, 3.0), 3, {"key": 1}) +def test_positional_args_passed_by_name_with_var_keyword(): + """Regression for ag2 #1790. + + When a function has positional-or-keyword params and a ``**kwargs``, all + arguments may arrive as keyword args (e.g. from an LLM tool call). The + positional names must not be swept into ``**kwargs``. + """ + + @inject + def simple_func( + arg1: str, + arg2: str, + **kwargs: dict[str, str], + ): + return arg1, arg2, kwargs + + assert simple_func(arg1="x", arg2="y", extra="z") == ("x", "y", {"extra": "z"}) + + def test_args_kwargs_2(): @inject def simple_func(