Skip to content

fix: merge websearch tool params - #32162

Merged
krrish-berri-2 merged 7 commits into
litellm_internal_stagingfrom
litellm_websearch_interception_params
Jul 5, 2026
Merged

fix: merge websearch tool params#32162
krrish-berri-2 merged 7 commits into
litellm_internal_stagingfrom
litellm_websearch_interception_params

Conversation

@krrish-berri-2

@krrish-berri-2 krrish-berri-2 commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Relevant issues

Linear ticket

Pre-Submission checklist

  • I have added meaningful tests
  • My PR passes all CI/CD checks (e.g., lint, format, unit tests)
  • 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

Delays in PR merge?

If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review)

Screenshots / Proof of Fix

I reproduced this against a DB-backed proxy with websearch_interception enabled and no search_tools in config. The search tool existed only in LiteLLM_SearchToolsTable, created through the UI, with search_provider=exa_ai and a stored API key. EXA_API_KEY was unset in the proxy process

The pre-fix /v1/messages request failed by falling back to Perplexity because llm_router.search_tools did not include the DB/UI tool:

WebSearchInterception: Search tool 'my-perplexity-search' not found in router, falling back to first available or perplexity
WebSearchInterception: No search tools configured in router, using default provider 'perplexity'
Search failed: ... PERPLEXITYAI_API_KEY is not set

After this fix, proxy search-tool sync builds the router list as config tools plus DB tools, with DB tools taking precedence on duplicate search_tool_name. The same DB-backed /v1/messages request succeeds:

curl -sS http://localhost:4062/v1/messages \
  -H 'Authorization: Bearer sk-1234' \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "gpt-5-mini-db-websearch-qa",
    "max_tokens": 512,
    "messages": [{"role": "user", "content": "db ui exa merged router websearch interception fixed"}],
    "tools": [{"type": "web_search_20250305", "name": "web_search", "max_uses": 1}]
  }' | jq '{stop_reason, error, content_types: ([.content[]?.type] // null)}'
{
  "stop_reason": "end_turn",
  "error": null,
  "content_types": ["text"]
}

Relevant proxy log lines:

Loading 1 search tool(s) into router (0 from config, 1 from database)
Successfully loaded 1 search tool(s) into router
WebSearchInterception: Found search tool 'my-perplexity-search' from router with provider 'exa_ai'
litellm.asearch(query='db ui exa merged router websearch interception fixed ...', search_provider='exa_ai', REDACTED')
Search call - provider: exa_ai
WebSearchInterception: Search completed for 'db ui exa merged router websearch interception fixed ...'

Type

Bug Fix
Test

Changes

Proxy search-tool sync now updates llm_router.search_tools to the merged view of config search tools plus DB/UI search tools. DB tools take precedence when the same search_tool_name exists in both places, matching the /search_tools/list behavior. Empty config plus empty DB syncs now skip the router update instead of clearing existing search tools

Websearch interception continues to select from llm_router.search_tools, but now preserves and forwards the selected tool's full litellm_params into litellm.asearch, with search_provider kept as the explicit argument and remaining non-null params forwarded as kwargs

Websearch interception now runs the same key and team search-tool authorization checks as the direct /search route before forwarding stored search-tool credentials to litellm.asearch. The check is applied to the normal agentic-loop path and the /v1/messages short-circuit path

Added focused regression coverage for router-backed UI-style search tools passing all params, DB/config search-tool merging, startup router sync, and key/team search-tool allowlist enforcement before litellm.asearch is called

Local checks run:

python -m pytest tests/test_litellm/integrations/websearch_interception/test_websearch_interception_handler.py tests/test_litellm/integrations/websearch_interception/test_websearch_short_circuit.py tests/test_litellm/integrations/websearch_interception/test_websearch_native_blocks.py tests/test_litellm/proxy/proxy_server/test_proxy_config.py -q
python -m ruff check litellm/integrations/websearch_interception/handler.py litellm/llms/anthropic/experimental_pass_through/messages/handler.py litellm/proxy/proxy_server.py tests/test_litellm/integrations/websearch_interception/test_websearch_interception_handler.py tests/test_litellm/proxy/proxy_server/test_proxy_config.py
python -m ruff format --check litellm/integrations/websearch_interception/handler.py litellm/llms/anthropic/experimental_pass_through/messages/handler.py litellm/proxy/proxy_server.py tests/test_litellm/integrations/websearch_interception/test_websearch_interception_handler.py tests/test_litellm/proxy/proxy_server/test_proxy_config.py
python scripts/ruff_strict_gate.py --base origin/litellm_internal_staging
git diff --staged --check

make pre-commit was retried, but uv sync --group proxy-dev failed while building grpcio==1.78.0 with an incompatible macOS x86_64 wheel. Because the sync did not complete, earlier runs also failed the later Prisma generation step with missing prisma package metadata

Replacement for fork-head PR #32157. This PR uses an upstream branch in BerriAI/litellm

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@krrish-berri-2
krrish-berri-2 marked this pull request as ready for review July 5, 2026 00:23
@greptile-apps

greptile-apps Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes websearch tool parameter forwarding and router synchronization for DB-backed proxy deployments. Search tools created via the UI were not being merged with config-defined tools in the router, so web-search requests fell back to Perplexity even when a different provider was configured in the database. The fix also properly forwards the full litellm_params from the selected search tool to litellm.asearch and applies key/team authorization checks before forwarding stored credentials.

  • Search-tool sync (proxy_server.py): _init_search_tools_in_db now reads config tools via get_config_state() and merges them with DB tools, with DB tools taking precedence on name collisions.
  • Credential forwarding (handler.py): _execute_search now builds search_kwargs from the selected tool's litellm_params (excluding search_provider and None values) and passes them through to litellm.asearch.
  • Authorization (handler.py): New _authorize_search_tool performs the same can_key_call_search_tool / can_team_call_search_tool checks as the direct /search route before any stored credentials are used.

Confidence Score: 5/5

Safe to merge — the fix is narrow and well-tested, the merge logic is straightforward, and the new authorization layer follows established proxy helper patterns.

All three changes (tool merging, credential forwarding, auth checks) have focused unit tests using mocks, no real network calls are introduced, and the get_config_state() call correctly avoids any I/O side effects. The only finding is a redundant branch that does not affect behavior.

No files require special attention.

Important Files Changed

Filename Overview
litellm/integrations/websearch_interception/handler.py Refactored _execute_search to forward full litellm_params, added _authorize_search_tool for key/team permission checks, and extracted selection logic into _select_search_tool_from_list/_select_search_tool_from_router. Logic is sound; the redundant if kwargs is None: guard in try_short_circuit_search can be simplified.
litellm/llms/anthropic/experimental_pass_through/messages/handler.py Threads kwargs (merged with metadata) into _try_websearch_short_circuit so auth context and litellm_params are available to the short-circuit path. Change is minimal and correct.
litellm/proxy/proxy_server.py Replaced the DB-only router update with a merged config+DB list via the new _merge_config_and_db_search_tools static method. Uses get_config_state() (read-only, no I/O) to retrieve config tools. Logic is correct and the merge semantics (DB wins on duplicates) match the documented behavior.
tests/test_litellm/integrations/websearch_interception/test_websearch_interception_handler.py Adds focused regression tests for litellm_params forwarding, key allowlist enforcement, and team allowlist enforcement. Tests use mocks and monkeypatching correctly; no real network calls introduced.
tests/test_litellm/proxy/proxy_server/test_proxy_config.py Adds three new tests covering the merge logic (superset, duplicate/DB-wins, and end-to-end startup sync). Pre-existing tests reformatted for line length only — assertions are unchanged.

Reviews (4): Last reviewed commit: "fix: preserve search tools on empty sync" | Re-trigger Greptile

Comment thread litellm/proxy/proxy_server.py Outdated
Comment thread litellm/proxy/proxy_server.py Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d6fefff3db

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread litellm/proxy/proxy_server.py Outdated
Comment on lines +6402 to +6403
except Exception as e:
verbose_proxy_logger.debug(f"Could not get config-defined search tools: {e}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve config search tools when config reload fails

In the DB-enabled sync path (add_deployment -> _init_non_llm_objects_in_db), if get_config() raises here, config_search_tools stays empty and the later update_router_search_tools(...) call overwrites llm_router.search_tools with only DB tools, or an empty list when the DB has none. For proxies that already loaded search tools from config, a transient remote config read failure or missing config file during this periodic sync will evict the working config-defined search tools instead of preserving the last known router state.

Useful? React with 👍 / 👎.

Comment thread litellm/integrations/websearch_interception/handler.py
@veria-ai

veria-ai Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

PR overview

All previously flagged issues have been addressed. No open security concerns remain on this pull request.

Security review

No open security issues remain on this pull request.

Fixed/addressed: 1 · PR risk: 0/10

@codecov

codecov Bot commented Jul 5, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 84.41558% with 12 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...llm/integrations/websearch_interception/handler.py 80.95% 12 Missing ⚠️

📢 Thoughts on this report? Let us know!

@krrish-berri-2

Copy link
Copy Markdown
Contributor Author

@greptileai

@krrish-berri-2

Copy link
Copy Markdown
Contributor Author

@greptile review

@krrish-berri-2

Copy link
Copy Markdown
Contributor Author

@greptileai review

Comment on lines 179 to 186
try:
search_result_text, structured = await self._execute_search(query)
if kwargs is None:
search_result_text, structured = await self._execute_search(query)
else:
search_result_text, structured = await self._execute_search(query, kwargs=kwargs)
except Exception as e:
verbose_logger.error(f"WebSearchInterception: Short-circuit search failed: {e}")
search_result_text, structured = f"Search failed: {e}", None

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.

P1 Auth exception swallowed in short-circuit path

_execute_search can now raise ProxyException from _authorize_search_tool when a key or team lacks permission for a search tool. That exception is a subclass of Exception and therefore falls into the existing catch-all block, which converts it into a "Search failed: ..." text string. The client receives a 200 OK with a failed search message instead of a proper authorization error — and the proxy logs the rejection at error level rather than surfacing it to the caller. The agentic-loop path (gathered search_tasks) lets the exception propagate normally, so there is already an inconsistency between the two paths.

@krrish-berri-2

Copy link
Copy Markdown
Contributor Author

bugbot please review?

@mateo-berri mateo-berri left a comment

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.

Left a qq

Also, I'm seeing: FAILED tests/test_litellm/proxy/common_utils/test_key_rotation_e2e.py::TestDeprecatedKeyLookupDbE2E::test_deprecated_key_grace_period_cache_hit_path - httpx.ConnectError: All connection attempts failed. Deterministic failure or flake?


async def _execute_search(self, query: str) -> Tuple[str, Optional[SearchResponse]]:
async def _execute_search(
self, query: str, kwargs: Optional[dict[str, Any]] = None

@mateo-berri mateo-berri Jul 5, 2026

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'm noticing many introduced kwargs. Are these necessary? Can we just introduce named params?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yes - this fixes the issue where litellm params were not being passed through. since litellm params can be a broad dict, this is appropriate imo

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Left a qq

Also, I'm seeing: FAILED tests/test_litellm/proxy/common_utils/test_key_rotation_e2e.py::TestDeprecatedKeyLookupDbE2E::test_deprecated_key_grace_period_cache_hit_path - httpx.ConnectError: All connection attempts failed. Deterministic failure or flake?


this seems like flake, since it's unrelated to the change here

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 see the issue _build_chat_completion_request_patch brings it in as kwargs.

if the tests pass, LGTM

@mateo-berri mateo-berri left a comment

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.

LGTM; thanks!

@krrish-berri-2
krrish-berri-2 merged commit 2967bc9 into litellm_internal_staging Jul 5, 2026
126 of 128 checks passed
@krrish-berri-2
krrish-berri-2 deleted the litellm_websearch_interception_params branch July 5, 2026 02:24
EkkoG pushed a commit to EkkoG/litellm that referenced this pull request Jul 7, 2026
* fix: pass websearch tool params

* fix: load db websearch tool params

* fix: merge search tools in proxy

* fix: satisfy websearch lint budget

* fix: enforce websearch tool auth

* fix: preserve search tools on empty sync

* chore: rerun circleci
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.

3 participants