From 13b05af06e20b84158d41511efee690868dce288 Mon Sep 17 00:00:00 2001 From: yucheng Date: Sat, 19 Sep 2026 16:34:53 +0000 Subject: [PATCH] fix(proxy): validate responses input after prompt template expansion Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../proxy/response_api_endpoints/endpoints.py | 2 +- .../response_api_endpoints/test_endpoints.py | 68 ++++++++++++++++++- 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/litellm/proxy/response_api_endpoints/endpoints.py b/litellm/proxy/response_api_endpoints/endpoints.py index a680e445a2c..73ab7e5213f 100644 --- a/litellm/proxy/response_api_endpoints/endpoints.py +++ b/litellm/proxy/response_api_endpoints/endpoints.py @@ -281,7 +281,6 @@ async def responses_api( # instead of a polling ID that immediately fails in the background task. processor = ProxyBaseLLMRequestProcessing(data=data) try: - raise_if_required_body_param_missing(route_type="aresponses", data=data) data, _logging_obj = await processor.common_processing_pre_call_logic( request=request, general_settings=general_settings, @@ -298,6 +297,7 @@ async def responses_api( route_type="aresponses", llm_router=llm_router, ) + raise_if_required_body_param_missing(route_type="aresponses", data=data) except Exception as e: raise await processor._handle_llm_api_exception( e=e, diff --git a/tests/test_litellm/proxy/response_api_endpoints/test_endpoints.py b/tests/test_litellm/proxy/response_api_endpoints/test_endpoints.py index 751d4753608..f7abb209015 100644 --- a/tests/test_litellm/proxy/response_api_endpoints/test_endpoints.py +++ b/tests/test_litellm/proxy/response_api_endpoints/test_endpoints.py @@ -181,11 +181,77 @@ async def test_responses_api_background_polling_rejects_missing_input(): assert exc_info.value.code == "400" assert exc_info.value.param == "input" - processor.common_processing_pre_call_logic.assert_not_awaited() + processor.common_processing_pre_call_logic.assert_awaited_once() mock_background_streaming_task.assert_not_called() mock_create_initial_state.assert_not_awaited() +@pytest.mark.asyncio +async def test_responses_api_background_polling_accepts_input_from_prompt_template(): + from fastapi import Response as FastAPIResponse + from starlette.requests import Request + + from litellm.proxy._types import UserAPIKeyAuth + from litellm.proxy.response_api_endpoints.endpoints import responses_api + + processor = MagicMock() + processor.common_processing_pre_call_logic = AsyncMock( + return_value=({"model": "gpt-4o", "input": "hello from prompt"}, MagicMock()) + ) + initial_state = MagicMock() + + async def receive(): + return { + "type": "http.request", + "body": b'{"model":"gpt-4o","prompt_id":"greeting","background":true}', + "more_body": False, + } + + request = Request( + { + "type": "http", + "method": "POST", + "path": "/v1/responses", + "headers": [(b"content-type", b"application/json")], + }, + receive, + ) + + with ( + patch( # test-quality-ok: endpoint constructs the processor directly + "litellm.proxy.response_api_endpoints.endpoints.ProxyBaseLLMRequestProcessing", + return_value=processor, + ), + patch( # test-quality-ok: polling decision is imported inside the endpoint + "litellm.proxy.response_polling.polling_handler.should_use_polling_for_request", + return_value=True, + ), + patch( # test-quality-ok: background task is imported inside the endpoint + "litellm.proxy.response_polling.background_streaming.background_streaming_task", + new_callable=AsyncMock, + ), + patch( # test-quality-ok: avoid scheduling a background task in this unit test + "litellm.proxy.response_api_endpoints.endpoints.asyncio.create_task", + ), + patch( # test-quality-ok: polling handler is imported inside the endpoint + "litellm.proxy.response_polling.polling_handler.ResponsePollingHandler.create_initial_state", + new_callable=AsyncMock, + ) as mock_create_initial_state, + ): + mock_create_initial_state.return_value = initial_state + result = await responses_api( + request=request, + fastapi_response=FastAPIResponse(), + user_api_key_dict=UserAPIKeyAuth(api_key="sk-test"), + ) + + assert result is initial_state + processor.common_processing_pre_call_logic.assert_awaited_once() + mock_create_initial_state.assert_awaited_once() + request_data = mock_create_initial_state.await_args.kwargs["request_data"] + assert request_data["input"] == "hello from prompt" + + class TestResponsesAPIEndpoints(unittest.TestCase): @pytest.mark.asyncio @patch("litellm.proxy.proxy_server.llm_router")