fix: reject unsupported web search before LiteLLM translation

This commit is contained in:
redshift
2026-09-29 16:48:19 +08:00
parent bee9115bae
commit d58e7b9ab0
2 changed files with 51 additions and 3 deletions
+26
View File
@@ -507,6 +507,32 @@ async def dispatch_anthropic_messages(
model_suffix = adapt_request(body) if adapt_request else ""
# LiteLLM turns Anthropic's server-side web_search tool into the OpenAI
# `web_search_options` parameter. Generic OpenAI-compatible chat endpoints
# (including those serving Claude through a proxy) may reject that field.
# Only a provider with an explicit adaptation (e.g. Venice's model suffix)
# can preserve search semantics; do not silently remove the tool and return
# an answer that never searched. Native /v1/messages providers bypass this
# dispatcher and receive the original tool unchanged.
tools = body.get("tools")
if provider_prefix == "openai/" and isinstance(tools, list) and any(
isinstance(tool, dict)
and (
(
isinstance(tool.get("type"), str)
and tool["type"].startswith("web_search")
)
or tool.get("name") == "web_search"
)
for tool in tools
):
raise UpstreamError(
"This upstream does not support Anthropic web search through "
"OpenAI-compatible /v1/messages translation",
status_code=400,
code="UNSUPPORTED_WEB_SEARCH",
)
# Convention: `model.id` is the canonical upstream model name;
# `forwarded_model_id` is the public alias the internal API exposes
# and echoes back to the client.
+25 -3
View File
@@ -123,13 +123,35 @@ async def test_requests_without_web_search_are_untouched() -> None:
@pytest.mark.asyncio
async def test_other_providers_keep_their_existing_behaviour() -> None:
"""The base hook is a no-op, so no non-Venice upstream changes shape."""
async def test_generic_openai_upstream_rejects_untranslatable_web_search() -> None:
"""Do not let LiteLLM send unsupported web_search_options to a generic API."""
provider = BaseUpstreamProvider(base_url="http://test", api_key="k")
with pytest.raises(UpstreamError) as excinfo:
await _dispatch(provider, _body(tools=[WEB_SEARCH_TOOL, FUNCTION_TOOL]))
assert excinfo.value.status_code == 400
assert excinfo.value.code == "UNSUPPORTED_WEB_SEARCH"
@pytest.mark.asyncio
async def test_generic_openai_upstream_still_accepts_function_tools() -> None:
provider = BaseUpstreamProvider(base_url="http://test", api_key="k")
kwargs = await _dispatch(provider, _body(tools=[FUNCTION_TOOL]))
assert kwargs["model"] == "openai/deepseek-v4-flash-0731"
assert kwargs["tools"] == [FUNCTION_TOOL]
@pytest.mark.asyncio
async def test_non_openai_adapter_can_still_handle_search_tool() -> None:
provider = BaseUpstreamProvider(
base_url="https://openrouter.ai/api/v1", api_key="k"
)
kwargs = await _dispatch(provider, _body(tools=[WEB_SEARCH_TOOL]))
assert kwargs["model"] == "openai/deepseek-v4-flash-0731"
assert kwargs["tools"] == [WEB_SEARCH_TOOL]