This commit is contained in:
Chau Vu 2026-10-18 04:26:52 +00:00 • committed by GitHub
commit 32be08fc31
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 96 additions and 1 deletions

View file

@ -339,18 +339,23 @@ class LiteLLMRoutes(enum.Enum):
"/responses",
"/v1/responses",
"/openai/v1/responses",
"/openai/responses",
"/responses/{response_id}",
"/v1/responses/{response_id}",
"/openai/v1/responses/{response_id}",
"/openai/responses/{response_id}",
"/responses/{response_id}/input_items",
"/v1/responses/{response_id}/input_items",
"/openai/v1/responses/{response_id}/input_items",
"/openai/responses/{response_id}/input_items",
"/responses/{response_id}/cancel",
"/v1/responses/{response_id}/cancel",
"/openai/v1/responses/{response_id}/cancel",
"/openai/responses/{response_id}/cancel",
"/responses/input_tokens",
"/v1/responses/input_tokens",
"/openai/v1/responses/input_tokens",
"/openai/responses/input_tokens",
# vector stores
"/vector_stores",
"/v1/vector_stores",

View file

@ -38,6 +38,7 @@ _MANAGED_MODEL_ROUTES: Final = frozenset(
) | frozenset(
(
"/openai/v1/responses",
"/openai/responses",
"/v2/rerank",
"/claude_code_gateway/v1/messages",
"/claude_code_gateway/v1/messages/count_tokens",

View file

@ -921,6 +921,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}"),

View file

@ -31,7 +31,7 @@ class CustomOpenAPISpec:
"/openai/deployments/{model}/embeddings",
]
RESPONSES_API_PATHS = ["/v1/responses", "/responses", "/openai/v1/responses"]
RESPONSES_API_PATHS = ["/v1/responses", "/responses", "/openai/v1/responses", "/openai/responses"]
@staticmethod
def _as_object(node: JsonValue) -> JsonObject:

View file

@ -260,6 +260,12 @@ async def _resolve_cursor_model_variant_before_auth(request: Request) -> None:
tags=["responses"],
responses=RESPONSES_API_CREATE_RESPONSE_SCHEMAS,
)
@router.post(
"/openai/responses",
dependencies=[Depends(user_api_key_auth)],
tags=["responses"],
responses=RESPONSES_API_CREATE_RESPONSE_SCHEMAS,
)
async def responses_api(
request: Request,
fastapi_response: Response,
@ -736,6 +742,12 @@ async def cursor_chat_completions(
tags=["responses"],
responses=RESPONSES_API_RESPONSE_SCHEMAS,
)
@router.get(
"/openai/responses/{response_id}",
dependencies=[Depends(user_api_key_auth)],
tags=["responses"],
responses=RESPONSES_API_RESPONSE_SCHEMAS,
)
async def get_response(
response_id: str,
request: Request,
@ -1113,6 +1125,12 @@ async def _bill_finished_stream(
tags=["responses"],
responses=DELETE_RESPONSE_SCHEMAS,
)
@router.delete(
"/openai/responses/{response_id}",
dependencies=[Depends(user_api_key_auth)],
tags=["responses"],
responses=DELETE_RESPONSE_SCHEMAS,
)
async def delete_response(
response_id: str,
request: Request,
@ -1229,6 +1247,12 @@ async def delete_response(
tags=["responses"],
responses=RESPONSE_ITEM_LIST_SCHEMAS,
)
@router.get(
"/openai/responses/{response_id}/input_items",
dependencies=[Depends(user_api_key_auth)],
tags=["responses"],
responses=RESPONSE_ITEM_LIST_SCHEMAS,
)
async def get_response_input_items(
response_id: str,
request: Request,
@ -1297,6 +1321,11 @@ async def get_response_input_items(
dependencies=[Depends(user_api_key_auth)],
tags=["responses"],
)
@router.post(
"/openai/responses/compact",
dependencies=[Depends(user_api_key_auth)],
tags=["responses"],
)
async def compact_response(
request: Request,
fastapi_response: Response,
@ -1540,6 +1569,11 @@ def _responses_input_as_token_count_messages(
dependencies=(_user_api_key_auth_dep,),
tags=_RESPONSES_TAGS,
)
@router.post(
"/openai/responses/input_tokens",
dependencies=(_user_api_key_auth_dep,),
tags=_RESPONSES_TAGS,
)
async def responses_input_tokens(
request: Request,
token_counter: _TokenCounter = _token_counter_dep,
@ -1632,6 +1666,11 @@ async def responses_input_tokens(
dependencies=[Depends(user_api_key_auth)],
tags=["responses"],
)
@router.post(
"/openai/responses/{response_id}/cancel",
dependencies=[Depends(user_api_key_auth)],
tags=["responses"],
)
async def cancel_response(
response_id: str,
request: Request,

View file

@ -236,6 +236,7 @@ _UNBILLED_ROUTES: Final[frozenset[str]] = frozenset(
"/responses/input_tokens",
"/v1/responses/input_tokens",
"/openai/v1/responses/input_tokens",
"/openai/responses/input_tokens",
}
)
_TOKEN_COUNTING_SEGMENTS: Final[frozenset[str]] = frozenset({"count_tokens", "count-tokens"})

View file

@ -1057,12 +1057,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],

View file

@ -0,0 +1,43 @@
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():
"""Verify that Azure-style /openai/responses endpoints are registered on the router."""
routes = {getattr(r, "path", None) for r in router.routes}
expected_routes = [
"/openai/responses",
"/openai/responses/{response_id}",
"/openai/responses/{response_id}/input_items",
"/openai/responses/compact",
"/openai/responses/input_tokens",
"/openai/responses/{response_id}/cancel",
]
for expected in expected_routes:
assert expected in routes, f"Route {expected} missing from router.routes!"
assert "/openai/responses" in CustomOpenAPISpec.RESPONSES_API_PATHS
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"