mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-31 06:56:59 +00:00
test(websearch): run the real search path and flatten the qualify helper tests
This commit is contained in:
parent
cca1943b6e
commit
df7155507a
2 changed files with 75 additions and 71 deletions
|
|
@ -11,12 +11,16 @@ from unittest.mock import AsyncMock, MagicMock, patch
|
|||
|
||||
import pytest
|
||||
|
||||
import litellm
|
||||
|
||||
from litellm.integrations.websearch_interception.handler import (
|
||||
WebSearchInterceptionLogger,
|
||||
)
|
||||
from litellm.types.integrations.custom_logger import (
|
||||
RESPONSES_AGENTIC_SURFACE,
|
||||
)
|
||||
from litellm.llms.base_llm.search.transformation import SearchResponse
|
||||
from litellm.proxy import proxy_server
|
||||
from litellm.types.utils import CallTypes, LlmProviders
|
||||
|
||||
|
||||
|
|
@ -253,10 +257,12 @@ async def test_build_responses_plan_produces_responses_input():
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_build_responses_plan_keeps_the_provider_on_a_sub_path_model():
|
||||
async def test_build_responses_plan_keeps_the_provider_on_a_sub_path_model(monkeypatch):
|
||||
"""#38829 on the Responses surface: a provider-stripped model whose remainder still holds a
|
||||
slash was left unqualified, so the follow-up could not resolve a provider."""
|
||||
logger = WebSearchInterceptionLogger(enabled_providers=[LlmProviders.BEDROCK])
|
||||
monkeypatch.setattr(proxy_server, "llm_router", None)
|
||||
monkeypatch.setattr(litellm, "asearch", AsyncMock(return_value=SearchResponse(object="search", results=[])))
|
||||
|
||||
tools_dict = {
|
||||
"tool_calls": [
|
||||
|
|
@ -274,28 +280,24 @@ async def test_build_responses_plan_keeps_the_provider_on_a_sub_path_model():
|
|||
"response_format": "responses",
|
||||
}
|
||||
|
||||
with patch.object(
|
||||
logger,
|
||||
"_execute_search",
|
||||
new=AsyncMock(return_value=("a search result", None)),
|
||||
):
|
||||
plan = await logger.async_build_responses_agentic_loop_plan(
|
||||
tools=tools_dict,
|
||||
model="mantle/anthropic.claude-sonnet-5",
|
||||
messages=[{"role": "user", "content": "What's the latest AI news?"}],
|
||||
response=_responses_output_with_web_search(),
|
||||
optional_params={"tools": [{"type": "function", "name": "litellm_web_search"}]},
|
||||
logging_obj=MagicMock(),
|
||||
stream=False,
|
||||
kwargs={
|
||||
"custom_llm_provider": "bedrock",
|
||||
"_agentic_loop_api_surface": RESPONSES_AGENTIC_SURFACE,
|
||||
},
|
||||
)
|
||||
plan = await logger.async_build_responses_agentic_loop_plan(
|
||||
tools=tools_dict,
|
||||
model="mantle/anthropic.claude-sonnet-5",
|
||||
messages=[{"role": "user", "content": "What's the latest AI news?"}],
|
||||
response=_responses_output_with_web_search(),
|
||||
optional_params={"tools": [{"type": "function", "name": "litellm_web_search"}]},
|
||||
logging_obj=MagicMock(),
|
||||
stream=False,
|
||||
kwargs={
|
||||
"custom_llm_provider": "bedrock",
|
||||
"_agentic_loop_api_surface": RESPONSES_AGENTIC_SURFACE,
|
||||
},
|
||||
)
|
||||
|
||||
patch_obj = plan.request_patch
|
||||
assert patch_obj is not None
|
||||
assert patch_obj.model == "bedrock/mantle/anthropic.claude-sonnet-5"
|
||||
litellm.asearch.assert_awaited_once()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
|
|||
|
|
@ -590,67 +590,69 @@ class TestProviderResponseHeadersInHiddenParams:
|
|||
assert "additional_headers" not in sibling._hidden_params
|
||||
|
||||
|
||||
class TestQualifyProviderStrippedModel:
|
||||
"""#38829: a model whose remainder still holds a slash must keep its provider prefix."""
|
||||
@pytest.mark.parametrize(
|
||||
"model,provider,expected",
|
||||
[
|
||||
("mantle/anthropic.claude-sonnet-5", "bedrock", "bedrock/mantle/anthropic.claude-sonnet-5"),
|
||||
("invoke/anthropic.claude-v2", "bedrock", "bedrock/invoke/anthropic.claude-v2"),
|
||||
("openai/gpt-4o", "openrouter", "openrouter/openai/gpt-4o"),
|
||||
("gpt-4o", "openai", "openai/gpt-4o"),
|
||||
("claude-sonnet-4-5", "anthropic", "anthropic/claude-sonnet-4-5"),
|
||||
],
|
||||
)
|
||||
def test_the_provider_prefix_is_restored(model, provider, expected):
|
||||
assert qualify_provider_stripped_model(model, provider) == expected
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"model,provider,expected",
|
||||
[
|
||||
("mantle/anthropic.claude-sonnet-5", "bedrock", "bedrock/mantle/anthropic.claude-sonnet-5"),
|
||||
("invoke/anthropic.claude-v2", "bedrock", "bedrock/invoke/anthropic.claude-v2"),
|
||||
("openai/gpt-4o", "openrouter", "openrouter/openai/gpt-4o"),
|
||||
("gpt-4o", "openai", "openai/gpt-4o"),
|
||||
("claude-sonnet-4-5", "anthropic", "anthropic/claude-sonnet-4-5"),
|
||||
],
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"model,provider",
|
||||
[
|
||||
("bedrock/mantle/anthropic.claude-sonnet-5", "bedrock"),
|
||||
("openai/gpt-4o", "openai"),
|
||||
],
|
||||
)
|
||||
def test_an_already_qualified_model_is_left_alone(model, provider):
|
||||
assert qualify_provider_stripped_model(model, provider) == model
|
||||
|
||||
|
||||
def test_a_provider_that_only_shares_a_prefix_is_still_qualified():
|
||||
assert qualify_provider_stripped_model("openai_like/foo", "openai") == "openai/openai_like/foo"
|
||||
|
||||
|
||||
def test_no_provider_leaves_the_model_untouched():
|
||||
assert qualify_provider_stripped_model("gpt-4o", "") == "gpt-4o"
|
||||
|
||||
|
||||
def test_a_cross_provider_patched_model_is_dispatched_as_the_hook_asked():
|
||||
assert qualify_agentic_followup_model("anthropic/claude-sonnet-4-5", "gpt-4o", "openai") == (
|
||||
"anthropic/claude-sonnet-4-5"
|
||||
)
|
||||
def test_the_provider_prefix_is_restored(self, model, provider, expected):
|
||||
assert qualify_provider_stripped_model(model, provider) == expected
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"model,provider",
|
||||
[
|
||||
("bedrock/mantle/anthropic.claude-sonnet-5", "bedrock"),
|
||||
("openai/gpt-4o", "openai"),
|
||||
],
|
||||
|
||||
def test_a_bare_patched_model_takes_the_request_provider():
|
||||
assert qualify_agentic_followup_model("gpt-4o-mini", "gpt-4o", "openai") == "openai/gpt-4o-mini"
|
||||
|
||||
|
||||
def test_a_sub_path_request_model_keeps_its_provider():
|
||||
assert qualify_agentic_followup_model(None, "mantle/anthropic.claude-sonnet-5", "bedrock") == (
|
||||
"bedrock/mantle/anthropic.claude-sonnet-5"
|
||||
)
|
||||
def test_an_already_qualified_model_is_left_alone(self, model, provider):
|
||||
assert qualify_provider_stripped_model(model, provider) == model
|
||||
|
||||
def test_a_provider_that_only_shares_a_prefix_is_still_qualified(self):
|
||||
assert qualify_provider_stripped_model("openai_like/foo", "openai") == "openai/openai_like/foo"
|
||||
|
||||
def test_no_provider_leaves_the_model_untouched(self):
|
||||
assert qualify_provider_stripped_model("gpt-4o", "") == "gpt-4o"
|
||||
|
||||
|
||||
class TestQualifyAgenticFollowUpModel:
|
||||
"""A hook that qualified its own patched model owns that choice, so a cross-provider
|
||||
follow-up must not be re-prefixed with the original request's provider."""
|
||||
def test_an_unpatched_ordinary_model_takes_the_request_provider():
|
||||
assert qualify_agentic_followup_model(None, "gpt-4o", "openai") == "openai/gpt-4o"
|
||||
|
||||
def test_a_cross_provider_patched_model_is_dispatched_as_the_hook_asked(self):
|
||||
assert qualify_agentic_followup_model("anthropic/claude-sonnet-4-5", "gpt-4o", "openai") == (
|
||||
"anthropic/claude-sonnet-4-5"
|
||||
)
|
||||
|
||||
def test_a_bare_patched_model_takes_the_request_provider(self):
|
||||
assert qualify_agentic_followup_model("gpt-4o-mini", "gpt-4o", "openai") == "openai/gpt-4o-mini"
|
||||
def test_a_patched_model_already_holding_the_request_provider_is_left_alone():
|
||||
assert qualify_agentic_followup_model("openai/gpt-4o", "gpt-4o", "openai") == "openai/gpt-4o"
|
||||
|
||||
def test_a_sub_path_request_model_keeps_its_provider(self):
|
||||
assert qualify_agentic_followup_model(None, "mantle/anthropic.claude-sonnet-5", "bedrock") == (
|
||||
"bedrock/mantle/anthropic.claude-sonnet-5"
|
||||
)
|
||||
|
||||
def test_an_unpatched_ordinary_model_takes_the_request_provider(self):
|
||||
assert qualify_agentic_followup_model(None, "gpt-4o", "openai") == "openai/gpt-4o"
|
||||
@pytest.mark.parametrize("patched", ["", None])
|
||||
def test_a_patch_without_a_model_falls_back_to_the_request_model(patched):
|
||||
assert qualify_agentic_followup_model(patched, "gpt-4o", "openai") == "openai/gpt-4o"
|
||||
|
||||
def test_a_patched_model_already_holding_the_request_provider_is_left_alone(self):
|
||||
assert qualify_agentic_followup_model("openai/gpt-4o", "gpt-4o", "openai") == "openai/gpt-4o"
|
||||
|
||||
@pytest.mark.parametrize("patched", ["", None])
|
||||
def test_a_patch_without_a_model_falls_back_to_the_request_model(self, patched):
|
||||
assert qualify_agentic_followup_model(patched, "gpt-4o", "openai") == "openai/gpt-4o"
|
||||
|
||||
def test_a_patched_sub_path_model_still_takes_the_request_provider(self):
|
||||
assert qualify_agentic_followup_model("mantle/anthropic.claude-sonnet-5", "gpt-4o", "bedrock") == (
|
||||
"bedrock/mantle/anthropic.claude-sonnet-5"
|
||||
)
|
||||
def test_a_patched_sub_path_model_still_takes_the_request_provider():
|
||||
assert qualify_agentic_followup_model("mantle/anthropic.claude-sonnet-5", "gpt-4o", "bedrock") == (
|
||||
"bedrock/mantle/anthropic.claude-sonnet-5"
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue