mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-23 05:34:41 +00:00
Merge 4064e59aa1 into f370235bb4
This commit is contained in:
commit
32be08fc31
8 changed files with 96 additions and 1 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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}"),
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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"})
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
|
|
|
|||
43
tests/proxy_unit_tests/test_openai_responses_routes.py
Normal file
43
tests/proxy_unit_tests/test_openai_responses_routes.py
Normal 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"
|
||||
Loading…
Add table
Reference in a new issue