Skip to content

WebSearch cannot express max_uses, and lets mutually exclusive domain lists through #291

Description

@saarnilauri

src/Tools/DTO/WebSearch.php carries exactly two fields, allowedDomains and disallowedDomains. The file is unchanged since the 0.1.0 release. Two consequences, one additive and one that needs a decision.

1. max_uses cannot be expressed, so providers hardcode it

ai-provider-for-anthropic hardcodes it at src/Models/AnthropicTextGenerationModel.php:588-597 (trunk):

if ($webSearch) {
    $tools[] = array_filter([
        'type' => 'web_search_20250305',
        'name' => 'web_search',
        'max_uses' => 1,
        'allowed_domains' => $webSearch->getAllowedDomains(),
        'blocked_domains' => $webSearch->getDisallowedDomains(),
    ]);
}

Every grounded request performs at most one search and a caller cannot change it. Per Anthropic's web search documentation, "Simple factual queries typically use 1-3 searches; comparative or multientity research can use 10 or more", and exceeding the cap yields a max_uses_exceeded error inside the result block rather than a failed request. So a cap of 1 quietly truncates the research a comparative question needs, and the caller sees a thinner answer rather than an error.

This is not a provider oversight: maxUses and userLocation appear nowhere in src/ (case-insensitive grep), so there is nothing for a provider to read.

Suggested: an optional $maxUses constructor parameter with getMaxUses(): ?int, null meaning "provider default", rejecting values below 1. Purely additive — no existing caller changes, and the key is omitted from toArray() when unset so serialisation round-trips unchanged.

2. Mutually exclusive domain lists are not rejected

Anthropic's documentation is explicit: "Provide allowed_domains or blocked_domains, not both. If a request includes both, the API returns a 400 error."

Nothing prevents it. WebSearch::__construct() accepts both lists, and the array_filter above only drops empty arrays — so a WebSearch carrying both populated lists produces a request with both keys and a guaranteed 400.

I am raising this rather than patching it because the fix has a real design question attached. Mutual exclusivity is currently an Anthropic constraint, not a universal one, so enforcing it in a provider-agnostic DTO encodes one vendor's rule into shared code. The options as I see them:

  1. Throw from WebSearch::__construct() when both lists are non-empty. Fails at the point of the mistake, but it is a breaking change: 11 tests in tests/unit/Tools/DTO/WebSearchTest.php construct WebSearch with both lists populated (testCreateWithBothAllowedAndDisallowedDomains, testWithDuplicateDomains, testWithEmptyStringsInArrays, testWithSingleDomainInEachList, testWithManyDomains, testMultipleInstances, testWithCommonDomainPatterns, testToArrayWithBothDomainLists, testFromArrayWithBothDomainLists, testArrayRoundTrip, testArrayRoundTripWithSpecialCharacters). If the test suite does it that freely, callers will too.
  2. Guard in the Anthropic provider, where the constraint actually lives, leaving the DTO permissive.
  3. Document the exclusivity on the DTO and leave enforcement to providers.

I lean towards 2, and towards 1 only if maintainers consider the combination meaningless in general rather than merely unsupported by one vendor.

Offer

I have the maxUses half implemented against trunk with unit tests, PHPCS and PHPStan clean and PHP 7.4 compatible. Happy to open that PR immediately if the shape above is acceptable. I would rather have the domain-list half settled here before writing it.

Related: WordPress/ai-provider-for-anthropic#41 reaches the same hardcoded max_uses from the usage-reporting angle.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions