镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

fix(llms): coerce dict tool_search config instead of silently disabling it - #8738

Open
梦想陆地的鱼 (heyseere) wants to merge 2 commits into
crewAIInc:mainfrom
heyseere:fix/anthropic-tool-search-dict
Open

梦想陆地的鱼 (heyseere) wants to merge 2 commits into
crewAIInc:mainfrom
heyseere:fix/anthropic-tool-search-dict

Conversation

@heyseere

@heyseere 梦想陆地的鱼 (heyseere) commented Oct 28, 2026 •

Copy link
Copy Markdown

Related issue

Fixes #8734

Summary

The Anthropic pre-validator discarded any tool_search value that was not True or an AnthropicToolSearchConfig instance — including plain dicts, which is exactly what YAML/JSON agent configs produce when the llm: block is passed through LLM(**value). Result: tool_search: {type: regex} silently disabled tool search instead of coercing.

This keeps special-casing only True (default variant) and False/None (disabled) and lets the pydantic field (annotated AnthropicToolSearchConfig | None) coerce and validate dict inputs, so invalid values now fail loudly instead of silently disabling.

Two behavior changes beyond the headline dict case, both intended:

  • An empty dict (tool_search: {}) now enables tool search with the default AnthropicToolSearchConfig() (bm25 variant) — previously it was silently dropped to None. Reading it as "present with defaults" matches the field's coercion semantics.
  • Any other non-True/False/None value (e.g. a string, int, or list) now reaches the pydantic field and fails validation at construction — previously it was also silently dropped to None. Same fail-loudly rationale: a typo like tool_search: regeex should surface, not silently disable the feature.

Verification

  • Tests added or updated for the changed behavior
  • Relevant tests and quality checks pass locally

lib/crewai/tests/llms/anthropic/test_anthropic.py (114 passed): tests cover dict coercion, tool_search=False → None, and loud failure on an invalid dict — asserted directly as ValidationError on a directly-constructed AnthropicCompletion, with a supplementary case pinning the LLM factory path (ImportError with the ValidationError as __cause__). ruff check clean on changed files.

Disclosure: this PR was produced with AI assistance (ZCode agent) and human-reviewed — please apply the llm-generated label.

Additional context

None

From ZCode


Note

Low Risk
Narrow change to Anthropic LLM config normalization; improves correctness for agent YAML and makes misconfiguration visible instead of silent.

Overview
Fixes Anthropic tool_search handling when the value comes from YAML/JSON as a dict (e.g. {type: regex}). The pre-validator in AnthropicCompletion._normalize_anthropic_fields no longer strips dicts and other non-boolean inputs to None; it only maps tool_search=True to a default AnthropicToolSearchConfig() and tool_search=False to None, leaving everything else for the Pydantic field to coerce or validate.

YAML agent configs with tool_search: {type: regex} now enable tool search instead of silently turning it off. Invalid configs raise ValidationError at construction (surfacing as ImportError via the LLM factory), and tool_search=False is explicitly tested to clear the feature.

Tests in test_anthropic.py cover dict coercion, explicit disable, and loud failure on invalid type values.

Reviewed by Cursor Bugbot for commit f17e9d2. Bugbot is set up for automated code reviews on this repo. Configure here.

@strix-security

strix-security Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Strix Security Review

Warning

This pull request has 85 commits after the last Strix review (58c63bf). Strix has not reviewed these changes.
Automatic review on push is off for this repository. To review the latest changes, tag @strix-security in a comment, or turn on re-review on push.

✅ No open security findings.

PR summary: This PR fixes Anthropic LLM config handling so a `tool_search` value supplied as a dict (as YAML/JSON agent configs produce) is coerced into `AnthropicToolSearchConfig` by the pydantic field instead of being silently discarded; only `True` (default variant) and `False`/`None` (disabled) remain special-cased. It also adds tests for dict coercion, `False` disabling, and failure on invalid dicts.

Review summary

No security issues found. The change narrows a pre-validator so only True and False are special-cased and all other tool_search values reach pydantic for coercion/validation. The value is operator-supplied LLM configuration, and its only consumer restricts output to a fixed Literal["regex", "bm25"] mapped to two constant tool-type strings, so no attacker-controlled data reaches the Anthropic request body, and invalid values now fail loudly rather than silently disabling the feature.

Review 1 on this PR · 58c63bf · requested Oct 28 16:06 UTC when the PR was opened


Reviewed by Strix
Re-run review · Configure security review settings

@heyseere
梦想陆地的鱼 (heyseere) force-pushed the fix/anthropic-tool-search-dict branch 2 times, most recently from 7b044b0 to 1d9939b Compare October 29, 2026 15:42
@heyseere

Copy link
Copy Markdown
Author

Friendly ping for a review when you have a moment. All bot checks are green and the PR is mergeable; happy to adjust anything.

@heyseere
梦想陆地的鱼 (heyseere) force-pushed the fix/anthropic-tool-search-dict branch 2 times, most recently from 9a249c9 to 5b2d371 Compare November 2, 2026 16:52

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.

I read the diff in lib/crewai/src/crewai/llms/providers/anthropic/completion.py (_normalize_anthropic_fields) and the new tests in test_anthropic.py, plus AnthropicToolSearchConfig. I did not run the test suite.

  1. test_tool_search_invalid_dict_raises expects ImportError matching "tool_search", but AnthropicToolSearchConfig is a plain Pydantic model with type: Literal["regex", "bm25"], so a dict {"type": "bogus"} should fail validation with a ValidationError, not an ImportError. The only ImportError in completion.py I found is the module-level anthropic SDK import, whose message does not mention tool_search. Can you confirm this test passes for the validation reason, rather than passing or failing for an unrelated import path? If the intent is "fail loudly", asserting the validation error type would pin that down.

  2. An empty dict now enables tool search: tool_search={} is not True or False, so it passes through to the field and coerces to AnthropicToolSearchConfig() with the default type "bm25". Before this change it was dropped to None. Is that flip intended? A config carrying tool_search: {} arguably means "present with defaults", but it is a behavior change worth calling out in the PR description.

  3. The same pass-through now applies to other non-dict values: a string such as "regex", or an int or list, previously became None and now reaches Pydantic validation and errors. That seems consistent with "fail loudly", but it is broader than the dict case in the PR description. Is erroring on those inputs intended, or should only dicts and config instances be coerced while other types are still dropped?

@heyseere

Copy link
Copy Markdown
Author

Thanks for the careful read — took all three points back to the code and the test run:

1. ImportError vs ValidationError — the test does pass for the validation reason, but via one hop I should have pointed at: LLM(model="anthropic/…", tool_search={"type": "bogus"}) goes through the native-provider factory path in llm.py, which wraps any construction failure in ImportError("Error importing native provider: …") with the pydantic ValidationError as __cause__ — the wrapped message embeds the validation dump (1 validation error for AnthropicCompletion / tool_search.type / Input should be 'regex' or 'bm25'), which is what match="tool_search" hits. So it's failing on the validation error itself, not an unrelated import path. That said, you're right that asserting the wrapper's ImportError is indirect and brittle — I'll change the test to construct AnthropicCompletion directly and assert ValidationError.

2. tool_search={} — intended: an empty dict passes the True/False shortcuts through to the pydantic field and coerces to AnthropicToolSearchConfig() with the default bm25 variant, i.e. "present with defaults". Agreed the flip from "dropped to None" is a behavior change worth stating explicitly — I'll add it to the PR description.

3. Non-dict values — also intended, same fail-loudly rationale: previously a typo like tool_search="regeex" silently disabled the feature; now it surfaces at construction. If maintainers would rather keep the old minimal surface (only dicts and config instances coerced, everything else dropped), that's a small change — happy to go either way.

@heyseere
梦想陆地的鱼 (heyseere) force-pushed the fix/anthropic-tool-search-dict branch 3 times, most recently from 75a2ff1 to 57648ca Compare November 8, 2026 06:49
…class

Assert the invalid-dict tool_search failure as a pydantic ValidationError on
AnthropicCompletion itself, instead of relying on the LLM factory wrapping it
into an ImportError whose message happens to embed the pydantic dump (an
implementation detail of llm.py, not the contract under test). Also assert the
error location is tool_search.type.

Add a companion case pinning the factory path via the exception cause chain:
LLM(...) raises ImportError with __cause__ set to the ValidationError, replacing
the previous fragile message string match.

Both tests fail against the pre-PR behavior (invalid dicts silently became None),
confirmed by reverting completion.py to 86dca29.

From ZCode

This branch has not been deployed

No deployments
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] Anthropic tool_search given as a dict is silently disabled

2 participants