From f415ff45e1813fbac7cb094c7044bc2060c294f3 Mon Sep 17 00:00:00 2001 From: shivam Date: Wed, 30 Sep 2026 20:59:22 +0000 Subject: [PATCH] fix(proxy): allow promptless image edits and default search models Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm/proxy/route_llm_request.py | 1 - litellm/proxy/search_endpoints/endpoints.py | 4 +++- .../proxy/search_endpoints/test_endpoints.py | 23 +++++++++++++++++++ .../proxy/test_route_llm_request.py | 4 ++-- 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/litellm/proxy/route_llm_request.py b/litellm/proxy/route_llm_request.py index b87b15d62ce..a36f1e0947a 100644 --- a/litellm/proxy/route_llm_request.py +++ b/litellm/proxy/route_llm_request.py @@ -171,7 +171,6 @@ REQUIRED_BODY_PARAMS_BY_ROUTE: Final[Mapping[str, tuple[str, ...]]] = { "atranscription": ("file",), "arerank": ("query", "documents"), "acompact_responses": ("input",), - "aimage_edit": ("image", "prompt"), "anthropic_messages": ("messages", "max_tokens"), "agenerate_content": ("contents",), "aocr": ("document",), diff --git a/litellm/proxy/search_endpoints/endpoints.py b/litellm/proxy/search_endpoints/endpoints.py index 9515b00bc97..9cc76024770 100644 --- a/litellm/proxy/search_endpoints/endpoints.py +++ b/litellm/proxy/search_endpoints/endpoints.py @@ -135,7 +135,9 @@ async def search( if search_tool_name is not None: data["search_tool_name"] = search_tool_name - if not data.get("search_tool_name") and not data.get("model"): + if not ( + data.get("search_tool_name") or data.get("model") or general_settings.get("completion_model") or user_model + ): raise ProxyMissingRequiredParamError(route="/search", param="search_tool_name") if "search_tool_name" in data and data["search_tool_name"]: diff --git a/tests/test_litellm/proxy/search_endpoints/test_endpoints.py b/tests/test_litellm/proxy/search_endpoints/test_endpoints.py index adfacf83683..b55d444b828 100644 --- a/tests/test_litellm/proxy/search_endpoints/test_endpoints.py +++ b/tests/test_litellm/proxy/search_endpoints/test_endpoints.py @@ -3,7 +3,9 @@ from unittest.mock import AsyncMock, MagicMock import orjson import pytest +from litellm.proxy import proxy_server from litellm.proxy._types import UserAPIKeyAuth +from litellm.proxy.common_request_processing import ProxyBaseLLMRequestProcessing from litellm.proxy.route_llm_request import ProxyMissingRequiredParamError from litellm.proxy.search_endpoints.endpoints import search @@ -27,3 +29,24 @@ async def test_search_without_search_tool_name_or_model_is_a_400(body): assert exc_info.value.code == "400" assert exc_info.value.param == "search_tool_name" assert exc_info.value.message == "/search: Missing required parameter: 'search_tool_name'." + + +@pytest.mark.asyncio +@pytest.mark.parametrize("default_source", ["cli_model", "completion_model"]) +async def test_search_with_only_a_query_falls_back_to_the_proxy_default_model(monkeypatch, default_source): + if default_source == "cli_model": + monkeypatch.setattr(proxy_server, "user_model", "perplexity-search") + else: + monkeypatch.setitem(proxy_server.general_settings, "completion_model", "perplexity-search") + search_result = {"object": "search", "results": []} + monkeypatch.setattr( + ProxyBaseLLMRequestProcessing, "base_process_llm_request", AsyncMock(return_value=search_result) + ) + + response = await search( + request=_json_request({"query": "litellm"}), + fastapi_response=MagicMock(), + user_api_key_dict=UserAPIKeyAuth(api_key="sk-test"), + ) + + assert response == search_result, response diff --git a/tests/test_litellm/proxy/test_route_llm_request.py b/tests/test_litellm/proxy/test_route_llm_request.py index 889143b869f..7e2c2ab3aac 100644 --- a/tests/test_litellm/proxy/test_route_llm_request.py +++ b/tests/test_litellm/proxy/test_route_llm_request.py @@ -1052,7 +1052,6 @@ async def test_route_request_override_enable_tag_filtering_beats_body_value(): ("atranscription", "file", "/audio/transcriptions"), ("arerank", "query", "/rerank"), ("acompact_responses", "input", "/responses/compact"), - ("aimage_edit", "image", "/images/edits"), ("anthropic_messages", "messages", "anthropic_messages"), ("agenerate_content", "contents", "agenerate_content"), ("aocr", "document", "/ocr"), @@ -1130,7 +1129,6 @@ def test_raise_if_required_body_param_missing_names_first_missing_batch_param(da "route_type, data, param", [ ("arerank", {"model": "rerank-model", "query": "hi"}, "documents"), - ("aimage_edit", {"model": "gpt-image-1", "image": b"png"}, "prompt"), ("anthropic_messages", {"model": "claude", "messages": []}, "max_tokens"), ("avideo_extension", {"model": "sora-2", "prompt": "longer"}, "seconds"), ("avideo_create_character", {"name": "hero"}, "video"), @@ -1164,6 +1162,8 @@ def test_raise_if_required_body_param_missing_names_each_missing_param(route_typ ("aresponses", {"model": "gpt-4o", "input": []}), ("arerank", {"model": "rerank-model", "query": "hi", "documents": ["hello"]}), ("aimage_edit", {"model": "gpt-image-1", "image": b"png", "prompt": "a hat"}), + ("aimage_edit", {"model": "stability.stable-image-remove-background-v1:0", "image": b"png"}), + ("aimage_edit", {"model": "stability.stable-style-transfer-v1:0", "init_image": b"png"}), ("anthropic_messages", {"model": "claude", "messages": [], "max_tokens": 16}), ("avideo_extension", {"model": "sora-2", "prompt": "longer", "seconds": "4"}), ("acreate_eval", {"data_source_config": {"type": "custom"}, "testing_criteria": []}),