Python: normalize single Anthropic tools#6903
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes the Anthropic Python provider’s handling of ChatOptions["tools"] when callers pass a single tool (either a FunctionTool/decorated function or a single dict spec), aligning it with the framework-wide tools surface and other providers.
Changes:
- Normalize
toolsvia sharednormalize_tools()before applying Anthropic-specific tool mapping. - Add regression tests for a single
FunctionToolinput and a singledicttool specification.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| python/packages/anthropic/agent_framework_anthropic/_chat_client.py | Uses shared normalize_tools() so single-tool inputs don’t break or get misinterpreted during Anthropic tool preparation. |
| python/packages/anthropic/tests/test_anthropic_client.py | Adds regression coverage to ensure single-tool inputs produce the same Anthropic request payload shape as one-item lists. |
| ) -> None: | ||
| """Test passing through a single dict tool.""" | ||
| client = create_test_anthropic_client(mock_anthropic_client) | ||
| chat_options = ChatOptions(tools={"type": "custom", "name": "custom_tool", "description": "A custom tool"}) # type: ignore[arg-type] |
There was a problem hiding this comment.
Addressed in d9e11e6 by removing the remaining unnecessary # type: ignore[arg-type] from the single mapping tool regression test.
Validation run locally:
uv run pytest packages/anthropic/tests/test_anthropic_client.py -quv run ruff check packages/anthropic/tests/test_anthropic_client.pygit diff --check -- python/packages/anthropic/tests/test_anthropic_client.py
Thanks Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
|
@copilot-pull-request-reviewer I addressed the review feedback in d9e11e6 by removing the unnecessary Local validation completed:
Could you please re-review when you get a chance? |
Motivation & Context
What Problem This Solves
The Python Anthropic provider accepted
options["tools"]at the provider boundary, but then treated that value as though it were always an iterable sequence during request preparation. That is narrower than the framework-levelChatOptions.toolscontract, where a caller can naturally pass either one tool-like value or a sequence of tool-like values.This is a real triggerable path, not just a typing edge case. A caller can pass a single decorated callable,
FunctionTool, or dict tool specification directly throughChatOptions(tools=tool). That shape should be normalized before Anthropic-specific request preparation begins, so the provider maps the same logical single tool the same way whether it arrives astools=toolortools=[tool].Direct iteration created two different single-tool failure modes:
FunctionToolor decorated callable could fail before the Anthropic payload was built, because the provider attempted to iterate the tool object itself.dicttool could be silently misread, because Python dict iteration yields keys. The provider could therefore see"type","name", and"description"as separate tool items instead of preserving the dictionary as one Anthropic tool specification.The bug therefore sits at the framework-to-provider boundary. Core normalization should decide whether the caller supplied one tool or many; the Anthropic provider should then perform Anthropic-specific conversion on already-normalized tool entries.
Changes
This PR normalizes
options["tools"]with the shared corenormalize_tools()helper before Anthropic-specific tool handling runs.That keeps the responsibility split clean:
The production change is intentionally small:
_prepare_tools_for_anthropic(...)now iterates overnormalize_tools(tools)instead of iterating overtoolsdirectly. In practical terms,tools=<single tool>now reaches the same Anthropic preparation logic astools=[<single tool>].Evidence
Focused Anthropic regression coverage now exercises the two single-tool shapes that exposed the bug:
The tests verify that these inputs prepare one Anthropic tool entry with the expected name/type fields. That proves the provider no longer treats a single tool object as the iterable container, and no longer splits a single dict tool into mapping keys.
The reviewer follow-up also removed unnecessary
type: ignore[arg-type]comments from the new single-tool tests, so the tests now exercise the typedChatOptions.toolssurface directly instead of hiding the accepted input shape behind ignores.This evidence is intentionally scoped to the PR diff, focused Anthropic regression tests, reviewer feedback, and visible PR checks. It does not claim a full repository CI or repository-wide typecheck run.
Possible call chain / impact
Before this change, a representative failing path was:
After this change, the path is:
The compatibility impact should be narrow and positive. Existing callers that already pass a list or other supported sequence continue through the same Anthropic conversion logic after normalization. The newly fixed callers are the ones using the broader framework API shape with a single tool, which now behaves like the equivalent one-item sequence for the covered request-preparation fields.
This is a localized Anthropic provider compatibility fix, not a broad tool-calling rewrite or a change to Anthropic request semantics.
Description & Review Guide
What are the major changes?
normalize_tools()before Anthropic-specific tool conversion.What is the impact of these changes?
What do you want reviewers to focus on?
Related Issue
Fixes #6901
Contribution Checklist
breaking changelabel (or add "[BREAKING]" to the title prefix, before or after any language prefix) - a workflow keeps the label and title prefix in sync automatically.