Skip to content

fix(tools): support positional-only parameters in beta_tool - #1912

Open
azrabano23 wants to merge 1 commit into
anthropics:mainfrom
azrabano23:fix-1911-beta-tool-positional-only
Open

fix(tools): support positional-only parameters in beta_tool#1912
azrabano23 wants to merge 1 commit into
anthropics:mainfrom
azrabano23:fix-1911-beta-tool-positional-only

Conversation

@azrabano23

Copy link
Copy Markdown

What was wrong

@beta_tool / @beta_async_tool on a function with a positional-only parameter produced an invalid schema and an uncallable tool, without any error at decoration time:

@beta_tool
def lookup(user_id: int, /, field: str = "name") -> str:
    """Look up a field on a user record."""
    return f"{user_id}:{field}"

lookup.input_schema
# {"maxItems": 2, "minItems": 1, "prefixItems": [...], "type": "array"}
lookup.call({"user_id": 7, "field": "email"})
# ValueError: Invalid arguments for function lookup
#   (2 validation errors: missing_positional_only_argument, unexpected_keyword_argument)

def collect(*values: int) produced {"items": {"type": "integer"}, "type": "array"} the same way. InputSchema requires type: "object", so both schemas are rejected by the API, and the positional-only tool could never be invoked by the tool runner even with a hand-written input_schema=.

Root cause

_create_schema_from_function delegates to pydantic's GenerateJsonSchema.arguments_schema, which renders the positional (array) form whenever a signature has positional-only parameters or *args and no keyword-only ones, so the docstring hook in kw_arguments_schema never runs for those signatures. call() then forwards the input object purely by keyword, which a positional-only parameter cannot accept.

The fix

Tool inputs are JSON objects passed by name, so:

  • CustomGenerateJsonSchema.arguments_schema is overridden to render positional-only parameters as ordinary named properties. The schema for lookup above is now identical to the one for def lookup(user_id: int, field: str = "name").
  • BetaFunctionTool.call / BetaAsyncFunctionTool.call route positional-only values back into positional slots (_split_input). Missing or unexpected values are still left to validate_call, so the existing ValueError("Invalid arguments for function ...") behaviour is unchanged.
  • *args, which a JSON object cannot represent, now raises a TypeError at decoration time. This is the one behavioural change for existing code: such a tool previously constructed successfully but carried an array schema the API cannot accept.

signature() failures (builtins, some callables) fall back to the previous behaviour, and the lazily-entered @asynccontextmanager path is unaffected since its wrapper only takes **kwargs.

Verification

CPU only, no API calls. From the repo root with a uv venv (Python 3.12, pydantic 2.13.5):

python -m pytest tests/lib/tools -q -o addopts="--tb=short -p tests._alias_httpx"
# 431 passed, 1 skipped, 1 xfailed
ruff check src/anthropic/lib/tools/_beta_functions.py tests/lib/tools/test_functions.py   # All checks passed
ruff format --check src/anthropic/lib/tools/_beta_functions.py tests/lib/tools/test_functions.py
pyright src/anthropic/lib/tools/_beta_functions.py tests/lib/tools/test_functions.py     # 0 errors

The four new tests (test_positional_only_parameters, test_positional_only_with_keyword_only_parameters, test_async_positional_only_parameters, test_var_positional_parameter_raises) fail on main and pass with this change.

Fixes #1911

🤖 Generated with Claude Code

`@beta_tool` / `@beta_async_tool` on a function with positional-only
parameters (`def f(a: int, /, b: str)`) produced an `input_schema` of
`type: "array"` and a tool whose `.call()` always failed. pydantic's
`GenerateJsonSchema.arguments_schema` renders the positional (array) form
whenever a signature has positional-only parameters or `*args` and no
keyword-only ones, so the docstring hook in `kw_arguments_schema` was never
reached, and `call()` forwarded the input purely by keyword, which a
positional-only parameter cannot accept. `InputSchema` requires
`type: "object"`, so the generated schema was invalid for the API as well.

Tool inputs are JSON objects passed by name, so:

- `arguments_schema` is overridden to render positional-only parameters as
  ordinary named properties, giving the same object schema as a
  keyword-capable signature;
- `call()` routes positional-only values back into positional slots via
  `_split_input`, leaving missing or unexpected values to `validate_call`
  so the existing `ValueError("Invalid arguments ...")` path is unchanged;
- `*args`, which a JSON object cannot represent, now raises a `TypeError`
  at decoration time instead of silently producing an array schema.

Fixes anthropics#1911

Signed-off-by: Azra Bano <azrabano.work@gmail.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@azrabano23
azrabano23 requested a review from a team as a code owner September 4, 2026 20:20
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.

beta_tool generates a type: array input_schema and an uncallable tool for positional-only parameters (and *args)

1 participant