diff --git a/litellm/proxy/relay_endpoints/endpoints.py b/litellm/proxy/relay_endpoints/endpoints.py index 1d96ce7ff8b..76c32201c6b 100644 --- a/litellm/proxy/relay_endpoints/endpoints.py +++ b/litellm/proxy/relay_endpoints/endpoints.py @@ -3,6 +3,7 @@ from pathlib import Path import yaml from fastapi import APIRouter, Depends, HTTPException +from pydantic import ValidationError from litellm.proxy.auth.user_api_key_auth import user_api_key_auth from litellm.types.proxy.relay_endpoints import RelayManagedConfigResponse @@ -26,7 +27,7 @@ def _load_managed_config(path: Path) -> RelayManagedConfigResponse: except yaml.YAMLError as e: raise HTTPException( status_code=500, - detail={"error": f"Failed to parse Relay settings at {path}: {e}"}, + detail={"error": f"Failed to parse Relay settings ({RELAY_SETTINGS_PATH_ENV}): {e}"}, ) if parsed is None: @@ -34,9 +35,17 @@ def _load_managed_config(path: Path) -> RelayManagedConfigResponse: if not isinstance(parsed, dict): raise HTTPException( status_code=500, - detail={"error": f"Relay settings at {path} must be a YAML mapping, got {type(parsed).__name__}"}, + detail={ + "error": f"Relay settings ({RELAY_SETTINGS_PATH_ENV}) must be a YAML mapping, got {type(parsed).__name__}" + }, + ) + try: + return RelayManagedConfigResponse.model_validate(parsed) + except ValidationError as e: + raise HTTPException( + status_code=500, + detail={"error": f"Relay settings ({RELAY_SETTINGS_PATH_ENV}) failed schema validation: {e}"}, ) - return RelayManagedConfigResponse.model_validate(parsed) @router.get( diff --git a/litellm/types/proxy/relay_endpoints.py b/litellm/types/proxy/relay_endpoints.py index 169db8e3afd..f6007c150d4 100644 --- a/litellm/types/proxy/relay_endpoints.py +++ b/litellm/types/proxy/relay_endpoints.py @@ -1,28 +1,26 @@ -from typing import Dict, Optional - from pydantic import BaseModel, Field, JsonValue class RelayClaudeCodeConfig(BaseModel): channel: str = Field(default="pinned") - version: Optional[str] = Field(default=None) + version: str | None = Field(default=None) registry: str = Field(default="npm") package: str = Field(default="@anthropic-ai/claude-code") - model: Optional[str] = Field(default=None) - managed_settings: Dict[str, JsonValue] = Field(default_factory=dict) + model: str | None = Field(default=None) + managed_settings: dict[str, JsonValue] = Field(default_factory=dict) class RelayCodexConfig(BaseModel): channel: str = Field(default="pinned") - version: Optional[str] = Field(default=None) + version: str | None = Field(default=None) registry: str = Field(default="npm") package: str = Field(default="@openai/codex") - model: Optional[str] = Field(default=None) + model: str | None = Field(default=None) class RelayManagedConfigResponse(BaseModel): claude_code: RelayClaudeCodeConfig = Field(default_factory=RelayClaudeCodeConfig) codex: RelayCodexConfig = Field(default_factory=RelayCodexConfig) - policy_version: Optional[int] = Field(default=None) - updated_by: Optional[str] = Field(default=None) - updated_at: Optional[str] = Field(default=None) + policy_version: int | None = Field(default=None) + updated_by: str | None = Field(default=None) + updated_at: str | None = Field(default=None) diff --git a/tests/test_litellm/proxy/relay_endpoints/test_endpoints.py b/tests/test_litellm/proxy/relay_endpoints/test_endpoints.py index 446ba260471..a3dfead25b6 100644 --- a/tests/test_litellm/proxy/relay_endpoints/test_endpoints.py +++ b/tests/test_litellm/proxy/relay_endpoints/test_endpoints.py @@ -109,6 +109,16 @@ def test_load_managed_config_rejects_invalid_yaml(tmp_path: Path) -> None: assert exc_info.value.status_code == 500 +def test_load_managed_config_rejects_wrong_field_type(tmp_path: Path) -> None: + path = tmp_path / "relay_settings.yaml" + path.write_text("policy_version: not-an-integer\n") + + with pytest.raises(HTTPException) as exc_info: + _load_managed_config(path) + + assert exc_info.value.status_code == 500 + + def test_load_managed_config_preserves_nested_managed_settings(tmp_path: Path) -> None: path = tmp_path / "relay_settings.yaml" path.write_text(