From 4064e59aa1c18099c53866880ff8ff5f4e9a1d0c Mon Sep 17 00:00:00 2001 From: Chau Vu / CPF-FAMILY <58875435+chauvuusvn@users.noreply.github.com> Date: Thu, 8 Oct 2026 21:47:06 +0700 Subject: [PATCH] fix(proxy): add /openai/responses to API_ROUTE_TO_CALL_TYPES and Prometheus route normalization - Map /openai/responses routes in API_ROUTE_TO_CALL_TYPES for tool allowlist and guardrail validation - Add regex normalization patterns for /openai/responses/{response_id} in normalize_request_route - Expand test suite in tests/proxy_unit_tests/test_openai_responses_routes.py --- litellm/proxy/auth/auth_utils.py | 3 +++ litellm/types/utils.py | 3 +++ .../test_openai_responses_routes.py | 16 ++++++++++++++++ 3 files changed, 22 insertions(+) diff --git a/litellm/proxy/auth/auth_utils.py b/litellm/proxy/auth/auth_utils.py index 2bd10b90101..9e603af01dd 100644 --- a/litellm/proxy/auth/auth_utils.py +++ b/litellm/proxy/auth/auth_utils.py @@ -857,6 +857,9 @@ def normalize_request_route(route: str) -> str: (r"^(/(?:openai/)?v1/responses)/([^/]+)(/input_items)$", r"\1/{response_id}\3"), (r"^(/(?:openai/)?v1/responses)/([^/]+)(/cancel)$", r"\1/{response_id}\3"), (r"^(/(?:openai/)?v1/responses)/([^/]+)$", r"\1/{response_id}"), + (r"^(/openai/responses)/([^/]+)(/input_items)$", r"\1/{response_id}\3"), + (r"^(/openai/responses)/([^/]+)(/cancel)$", r"\1/{response_id}\3"), + (r"^(/openai/responses)/([^/]+)$", r"\1/{response_id}"), (r"^(/responses)/([^/]+)(/input_items)$", r"\1/{response_id}\3"), (r"^(/responses)/([^/]+)(/cancel)$", r"\1/{response_id}\3"), (r"^(/responses)/([^/]+)$", r"\1/{response_id}"), diff --git a/litellm/types/utils.py b/litellm/types/utils.py index a99c22e9479..7ebbe04b197 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -1038,12 +1038,15 @@ API_ROUTE_TO_CALL_TYPES: Final[Mapping[str, Sequence[CallTypes]]] = { "/responses": (CallTypes.aresponses, CallTypes.responses), "/v1/responses": (CallTypes.aresponses, CallTypes.responses), "/openai/v1/responses": (CallTypes.aresponses, CallTypes.responses), + "/openai/responses": (CallTypes.aresponses, CallTypes.responses), "/responses/{response_id}": (CallTypes.aresponses, CallTypes.responses), "/v1/responses/{response_id}": (CallTypes.aresponses, CallTypes.responses), "/openai/v1/responses/{response_id}": (CallTypes.aresponses, CallTypes.responses), + "/openai/responses/{response_id}": (CallTypes.aresponses, CallTypes.responses), "/responses/{response_id}/input_items": (CallTypes.alist_input_items,), "/v1/responses/{response_id}/input_items": (CallTypes.alist_input_items,), "/openai/v1/responses/{response_id}/input_items": (CallTypes.alist_input_items,), + "/openai/responses/{response_id}/input_items": (CallTypes.alist_input_items,), # Realtime API "/realtime": [CallTypes.arealtime], "/v1/realtime": [CallTypes.arealtime], diff --git a/tests/proxy_unit_tests/test_openai_responses_routes.py b/tests/proxy_unit_tests/test_openai_responses_routes.py index dc83a1fb257..21c7865a7fb 100644 --- a/tests/proxy_unit_tests/test_openai_responses_routes.py +++ b/tests/proxy_unit_tests/test_openai_responses_routes.py @@ -1,8 +1,10 @@ from litellm.proxy._types import LiteLLMRoutes from litellm.proxy.agent_endpoints.auth.managed_authorization import _MANAGED_MODEL_ROUTES +from litellm.proxy.auth.auth_utils import normalize_request_route from litellm.proxy.common_utils.custom_openapi_spec import CustomOpenAPISpec from litellm.proxy.response_api_endpoints.endpoints import router from litellm.proxy.spend_tracking.budget_reservation import _UNBILLED_ROUTES +from litellm.types.utils import API_ROUTE_TO_CALL_TYPES, CallTypes def test_openai_responses_routes_registered(): @@ -25,3 +27,17 @@ def test_openai_responses_routes_registered(): assert "/openai/responses" in _MANAGED_MODEL_ROUTES assert "/openai/responses/input_tokens" in _UNBILLED_ROUTES assert "/openai/responses" in LiteLLMRoutes.openai_routes.value + + +def test_openai_responses_api_route_to_call_types(): + """Verify that /openai/responses mappings exist in API_ROUTE_TO_CALL_TYPES for guardrails/tools.""" + assert API_ROUTE_TO_CALL_TYPES.get("/openai/responses") == (CallTypes.aresponses, CallTypes.responses) + assert API_ROUTE_TO_CALL_TYPES.get("/openai/responses/{response_id}") == (CallTypes.aresponses, CallTypes.responses) + assert API_ROUTE_TO_CALL_TYPES.get("/openai/responses/{response_id}/input_items") == (CallTypes.alist_input_items,) + + +def test_openai_responses_metrics_normalization(): + """Verify that dynamic response IDs are normalized to prevent Prometheus high-cardinality metrics.""" + assert normalize_request_route("/openai/responses/resp-123456") == "/openai/responses/{response_id}" + assert normalize_request_route("/openai/responses/resp-123456/cancel") == "/openai/responses/{response_id}/cancel" + assert normalize_request_route("/openai/responses/resp-123456/input_items") == "/openai/responses/{response_id}/input_items"