Merge pull request #41723 from BerriAI/litellm_/circleci-specific-sha-0cf414

fix(proxy): forward every method on the typesafe pass-through route
This commit is contained in:
yuneng-jiang 2026-09-17 18:57:06 -07:00 committed by GitHub
commit 34718f0da6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 278 additions and 6 deletions

View file

@ -20374,6 +20374,50 @@
}
},
"/typesafe/{endpoint}": {
"delete": {
"description": "[Docs](https://docs.litellm.ai/docs/pass_through/typesafe)",
"operationId": "typesafe_proxy_route_typesafe__endpoint__delete",
"parameters": [
{
"in": "path",
"name": "endpoint",
"required": true,
"schema": {
"title": "Endpoint",
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"security": [
{
"APIKeyHeader": []
}
],
"summary": "Typesafe Proxy Route",
"tags": [
"llm_passthrough"
]
},
"get": {
"description": "[Docs](https://docs.litellm.ai/docs/pass_through/typesafe)",
"operationId": "typesafe_proxy_route_typesafe__endpoint__get",
@ -20418,6 +20462,50 @@
"llm_passthrough"
]
},
"patch": {
"description": "[Docs](https://docs.litellm.ai/docs/pass_through/typesafe)",
"operationId": "typesafe_proxy_route_typesafe__endpoint__patch",
"parameters": [
{
"in": "path",
"name": "endpoint",
"required": true,
"schema": {
"title": "Endpoint",
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"security": [
{
"APIKeyHeader": []
}
],
"summary": "Typesafe Proxy Route",
"tags": [
"llm_passthrough"
]
},
"post": {
"description": "[Docs](https://docs.litellm.ai/docs/pass_through/typesafe)",
"operationId": "typesafe_proxy_route_typesafe__endpoint__post",
@ -20461,6 +20549,50 @@
"tags": [
"llm_passthrough"
]
},
"put": {
"description": "[Docs](https://docs.litellm.ai/docs/pass_through/typesafe)",
"operationId": "typesafe_proxy_route_typesafe__endpoint__put",
"parameters": [
{
"in": "path",
"name": "endpoint",
"required": true,
"schema": {
"title": "Endpoint",
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"security": [
{
"APIKeyHeader": []
}
],
"summary": "Typesafe Proxy Route",
"tags": [
"llm_passthrough"
]
}
},
"/vertex_ai/discovery/{endpoint}": {

View file

@ -527,7 +527,7 @@ async def mistral_proxy_route(
@router.api_route(
"/typesafe/{endpoint:path}",
methods=["GET", "POST"], # mutable-ok: FastAPI route metadata requires a list
methods=["GET", "POST", "PUT", "DELETE", "PATCH"], # mutable-ok: FastAPI route metadata requires a list
tags=["TypeSafe AI Pass-through", "pass-through"], # mutable-ok: FastAPI route metadata requires a list
)
async def typesafe_proxy_route(

View file

@ -1360,8 +1360,7 @@ def test_models_by_provider():
or v["litellm_provider"] == "bedrock_converse"
):
continue
elif v.get("mode") == "search":
# Skip search providers as they don't have traditional models
elif v.get("mode") in ("search", "evaluation"):
continue
else:
providers.add(v["litellm_provider"])

View file

@ -6149,6 +6149,42 @@ class TestTypeSafePassthroughRoute:
request.json = AsyncMock(return_value=body)
return request
@pytest.fixture
def client(self, monkeypatch: pytest.MonkeyPatch) -> Iterator[TestClient]:
from litellm.proxy.proxy_server import app
monkeypatch.setenv("TYPESAFE_API_KEY", "typesafe-test-key")
monkeypatch.setenv("TYPESAFE_API_BASE", "https://typesafe.example/base")
monkeypatch.delenv("SERVER_ROOT_PATH", raising=False)
monkeypatch.setattr(litellm, "disable_aiohttp_transport", True)
litellm.in_memory_llm_clients_cache.flush_cache()
monkeypatch.setitem(app.dependency_overrides, user_api_key_auth, lambda: UserAPIKeyAuth(api_key="sk-virtual"))
yield TestClient(app)
@pytest.mark.parametrize(
"method, body",
[
("GET", None),
("POST", {"state": "x"}),
("PUT", {"state": "x"}),
("DELETE", None),
("PATCH", {"state": "x"}),
],
)
def test_forwards_every_method_and_body_upstream(
self, client: TestClient, method: str, body: dict[str, str] | None
) -> None:
with respx.mock(assert_all_called=True) as upstream:
route = upstream.request(method, "https://typesafe.example/base/v1/systemone").mock(
return_value=httpx.Response(200, json={"id": "upstream_123"})
)
response = client.request(method, "/typesafe/v1/systemone", json=body)
assert (response.status_code, response.json()) == (200, {"id": "upstream_123"})
sent: Final = route.calls.last.request
assert sent.headers["authorization"] == "Bearer typesafe-test-key"
assert json.loads(sent.content or b"{}") == (body or {})
@pytest.mark.asyncio
async def test_forwards_target_auth_headers_provider_and_query(self, monkeypatch):
monkeypatch.setenv("TYPESAFE_API_KEY", "typesafe-test-key")

View file

@ -16488,16 +16488,28 @@ export interface paths {
* @description [Docs](https://docs.litellm.ai/docs/pass_through/typesafe)
*/
get: operations["typesafe_proxy_route_typesafe__endpoint__get"];
put?: never;
/**
* Typesafe Proxy Route
* @description [Docs](https://docs.litellm.ai/docs/pass_through/typesafe)
*/
put: operations["typesafe_proxy_route_typesafe__endpoint__put"];
/**
* Typesafe Proxy Route
* @description [Docs](https://docs.litellm.ai/docs/pass_through/typesafe)
*/
post: operations["typesafe_proxy_route_typesafe__endpoint__post"];
delete?: never;
/**
* Typesafe Proxy Route
* @description [Docs](https://docs.litellm.ai/docs/pass_through/typesafe)
*/
delete: operations["typesafe_proxy_route_typesafe__endpoint__delete"];
options?: never;
head?: never;
patch?: never;
/**
* Typesafe Proxy Route
* @description [Docs](https://docs.litellm.ai/docs/pass_through/typesafe)
*/
patch: operations["typesafe_proxy_route_typesafe__endpoint__patch"];
trace?: never;
};
"/update/default_team_settings": {
@ -61695,6 +61707,37 @@ export interface operations {
};
};
};
typesafe_proxy_route_typesafe__endpoint__put: {
parameters: {
query?: never;
header?: never;
path: {
endpoint: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": unknown;
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
typesafe_proxy_route_typesafe__endpoint__post: {
parameters: {
query?: never;
@ -61726,6 +61769,68 @@ export interface operations {
};
};
};
typesafe_proxy_route_typesafe__endpoint__delete: {
parameters: {
query?: never;
header?: never;
path: {
endpoint: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": unknown;
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
typesafe_proxy_route_typesafe__endpoint__patch: {
parameters: {
query?: never;
header?: never;
path: {
endpoint: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": unknown;
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
update_default_team_settings_update_default_team_settings_patch: {
parameters: {
query?: never;