Skip to content

fix(fast_depends): keep positional args out of **kwargs when passed by name#2807

Open
obchain wants to merge 1 commit into
ag2ai:mainfrom
obchain:fix/issue-1790
Open

fix(fast_depends): keep positional args out of **kwargs when passed by name#2807
obchain wants to merge 1 commit into
ag2ai:mainfrom
obchain:fix/issue-1790

Conversation

@obchain
Copy link
Copy Markdown
Contributor

@obchain obchain commented May 12, 2026

Why are these changes needed?

When a function has positional-or-keyword parameters alongside **kwargs and is invoked with all arguments supplied as keyword arguments — exactly what happens when an LLM tool call delivers every argument by name — the positional parameter names were swept into the **kwargs field and pydantic reported them as missing:

ValidationError: 2 validation errors for hello_world_v3
arg1
  Field required [type=missing, input_value={'kwargs': {'arg1': "Case... "location": "Italy"}'}}]
arg2
  Field required [type=missing, ...]

The root cause is in autogen/fast_depends/core/model.py::CallModel._solve. The existing loop only popped keyword_args out of the incoming kwargs dict before assigning the remainder to var_keyword_arg; positional_args were left in there and ended up nested inside the **kwargs value.

Fix

When the function has a **kwargs (self.var_keyword_arg is not None), pop the positional_args names from kwargs too, mirroring the existing pop loop for keyword_args. The subsequent positional-from-*args loop is unaffected: it only runs when args is supplied, which is mutually exclusive with passing the same name as a keyword argument.

Behaviour for purely positional invocation, for keyword-only params, for *args, and for functions without **kwargs is unchanged — the new pop loop is gated on var_keyword_arg is not None.

Repro

from typing import Annotated
from autogen.tools.dependency_injection import Field, inject_params

def hello_world_v3(
    arg1: Annotated[str, Field(description="Case Name")],
    arg2: Annotated[str, Field(description="Clue Name")],
    **kwargs: Annotated[dict, Field(description="extras")],
) -> str:
    return f"{arg1} | {arg2} | {kwargs}"

f = inject_params(hello_world_v3)
print(f(arg1="x", arg2="y", extra="z"))
# Before: ValidationError (arg1, arg2 missing)
# After: x | y | {'extra': 'z'}

Tests

uv run pytest test/fast_depends/ -q → 131 passed (was 129). New regression tests:

  • test/fast_depends/sync/test_cast.py::test_positional_args_passed_by_name_with_var_keyword
  • test/fast_depends/async/test_cast.py::test_positional_args_passed_by_name_with_var_keyword

Both invoke a function (arg1, arg2, **kwargs) with every arg by name and assert the positional values land in arg1/arg2 rather than inside kwargs.

Related issue number

Fixes #1790

Checks

  • I've included any doc changes needed (none — internal binding fix, no public-API change).
  • I've added tests corresponding to the changes introduced in this PR.
  • I've made sure all auto checks have passed.

AI assistance

  • I understand the changes in this PR and can explain them in my own words.
  • I have verified that the PR description accurately reflects the actual diff.
  • If AI assistance was used, I reviewed, tested, and validated the generated code/text before submitting.

…y name

When a function had positional-or-keyword parameters alongside a **kwargs and
all arguments were supplied as keyword arguments (e.g. an LLM tool call that
delivers every argument by name), the positional parameter names were not
pulled out of the kwargs dict before the rest was assigned to var_keyword_arg.
The whole input — including arg1, arg2 — was then placed inside the kwargs
field, and pydantic validation reported the positional fields as missing:

    Field required [type=missing, input_value={'kwargs': {'arg1': ...}}]

Pop the positional_arg names from kwargs first whenever var_keyword_arg is
set, mirroring the existing pop loop for keyword_args. The subsequent
positional-from-*args loop is unaffected: it only runs when args is supplied,
which is mutually exclusive with passing the same name as a keyword.

Add a regression test in both the sync and async test_cast suites.

Fixes ag2ai#1790
@codecov
Copy link
Copy Markdown

codecov Bot commented May 12, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

Files with missing lines Coverage Δ
autogen/fast_depends/core/model.py 96.41% <100.00%> (+0.06%) ⬆️

... and 38 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Tool call doesn't work when **kwargs in function arguments

1 participant