Skip to content

fix(anthropic): place strict flag at tool level, not inside input_schema - #27542

Closed
Jwrede wants to merge 3 commits into
BerriAI:litellm_internal_stagingfrom
Jwrede:fix/anthropic-strict-tool-placement
Closed

fix(anthropic): place strict flag at tool level, not inside input_schema#27542
Jwrede wants to merge 3 commits into
BerriAI:litellm_internal_stagingfrom
Jwrede:fix/anthropic-strict-tool-placement

Conversation

@Jwrede

@Jwrede Jwrede commented May 9, 2026

Copy link
Copy Markdown

Relevant issues

Fixes #27490

Pre-Submission checklist

  • I have Added testing in the tests/test_litellm/ directory, Adding at least 1 test is a hard requirement
  • My PR passes all unit tests on make test-unit
  • My PR's scope is as isolated as possible, it only solves 1 specific problem
  • I have requested a Greptile review by commenting @greptileai and received a Confidence Score of at least 4/5 before requesting a maintainer review

Type

Bug Fix

Changes

Anthropic enforces strict tool use only when the flag is set at the tool top level (sibling of name/input_schema). LiteLLM was placing it inside input_schema where Anthropic silently ignores it, so callers thought strict was engaged but the model emitted unconstrained tool arguments.

The fix:

  • Remove strict from AnthropicInputSchema (not part of the JSON Schema object)
  • Add strict to AnthropicMessagesTool (where Anthropic expects it)
  • In _map_tool_helper, extract strict from either tool.function.strict (OpenAI canonical location) or parameters.strict (legacy placement) and set it on the tool object

Test plan

Added 4 tests in test_anthropic_chat_transformation.py:

  • strict: true on tool.function is placed at tool top level
  • strict: true inside parameters is moved to tool top level
  • No strict flag means the field is omitted entirely
  • strict: false is not forwarded to Anthropic

Screenshots / Proof of Fix

Before (on main) -- strict ends up nested inside input_schema, Anthropic ignores it:

{"name": "get_weather", "input_schema": {"type": "object", "strict": true, ...}, "type": "custom"}

After (this PR) -- strict is at the tool top level, Anthropic enforces it:

{"name": "get_weather", "input_schema": {"type": "object", ...}, "type": "custom", "strict": true}

yuneng-berri and others added 2 commits May 7, 2026 18:05
Anthropic enforces strict tool use only when `strict` is set at the tool
top level (sibling of name/input_schema). LiteLLM was placing it inside
input_schema where Anthropic silently ignores it, so callers thought
strict was engaged but the model emitted unconstrained tool arguments.

- Remove `strict` from AnthropicInputSchema (not part of the schema)
- Add `strict` to AnthropicMessagesTool (where Anthropic expects it)
- Extract strict from tool.function.strict (OpenAI canonical) or from
  parameters.strict (legacy), and set it on the tool object

Fixes BerriAI#27490
@Jwrede

Jwrede commented May 9, 2026

Copy link
Copy Markdown
Author

@greptileai

@greptile-apps

greptile-apps Bot commented May 9, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes the placement of the strict flag in Anthropic tool definitions — it was incorrectly nested inside input_schema where Anthropic silently ignores it, and is now correctly placed as a sibling of name and input_schema at the tool top level.

  • strict is removed from AnthropicInputSchema and added to AnthropicMessagesTool, matching Anthropic's actual API contract.
  • _map_tool_helper now reads strict from either tool.function.strict (OpenAI canonical) or parameters.strict (legacy location) and promotes it to the tool object only when True.
  • Four new unit tests cover all meaningful combinations: function-level strict, parameters-level strict, absent strict, and explicit strict=False.

Confidence Score: 4/5

Safe to merge; the fix is straightforward and well-tested, with one minor edge case in the fallback logic worth addressing.

The or-based fallback on line 679 can misread a deliberate strict=False at the function level if a stale strict=True exists inside parameters. In practice this combination is unlikely, but replacing or with an explicit is None guard would eliminate the ambiguity entirely. All other changes are clean and correct.

litellm/llms/anthropic/chat/transformation.py line 679 — the strict-extraction expression.

Important Files Changed

Filename Overview
litellm/llms/anthropic/chat/transformation.py Extracts strict from either tool.function.strict or parameters.strict and places it at the tool top level; minor or-based falsy evaluation could override an explicit strict=False in an edge case.
litellm/types/llms/anthropic.py Removes strict from AnthropicInputSchema (where it doesn't belong per Anthropic's spec) and adds it to AnthropicMessagesTool (correct location); clean and correct type change.
tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py Adds 4 unit tests covering function-level strict, parameters-level strict, no-strict omission, and strict=False omission; all are mocked and don't make real network calls, in line with the folder's test rules.

Reviews (1): Last reviewed commit: "fix(anthropic): place strict flag at too..." | Re-trigger Greptile

**input_schema_filtered
)

_strict = tool["function"].get("strict") or _input_schema.get("strict")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 or swallows an explicit strict=False at the function level

tool["function"].get("strict") or _input_schema.get("strict") uses short-circuit or, so if a caller explicitly sets strict=False on the function object the expression falls through to _input_schema.get("strict"). If the parameters dict still has a leftover strict=True (e.g., legacy placement that wasn't cleaned up by the caller), the result is True even though the caller explicitly opted out at the canonical location. Using is None chaining avoids this ambiguity.

Suggested change
_strict = tool["function"].get("strict") or _input_schema.get("strict")
_function_strict = tool["function"].get("strict")
_strict = _function_strict if _function_strict is not None else _input_schema.get("strict")

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code already uses the is not None pattern -- see lines 679-684. _function_strict is checked with if _function_strict is not None before falling back to _input_schema.get("strict"), so an explicit strict=False at the function level is preserved correctly.

When strict=False is set at the function level and strict=True remains
in parameters, the or operator treats False as falsy and falls through
to the parameters value. Use is-None chaining so the function-level
value always takes precedence when explicitly set.
@Jwrede

Jwrede commented May 16, 2026

Copy link
Copy Markdown
Author

Friendly ping @ishaan-jaff @krrish-berri-2 -- this fixes strict flag being silently ignored on Anthropic tools (placed inside input_schema where Anthropic drops it). Data correctness bug. Ready for review.

@github-actions

Copy link
Copy Markdown
Contributor

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.

@github-actions github-actions Bot added the stale label Aug 15, 2026
@github-actions github-actions Bot closed this Aug 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Anthropic strict tool use: strict flag forwarded to wrong location (inside input_schema instead of tool top level)

3 participants