From e002d6afe87b5fa62b851ec39bbc3ad3733d80a5 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Tue, 10 Feb 2026 15:16:18 -0800 Subject: [PATCH] addressing comments --- .../pass_through_endpoints.py | 28 +++++++++++----- .../test_pass_through_endpoints.py | 33 +++++++++++++++++++ 2 files changed, 52 insertions(+), 9 deletions(-) diff --git a/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py b/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py index b87ff58db1..a7b60c8b18 100644 --- a/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py +++ b/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py @@ -2205,7 +2205,10 @@ def _get_pass_through_endpoints_from_config() -> List[PassThroughGenericEndpoint """ Get pass-through endpoints defined in the config file. These are read-only and cannot be edited via the UI. + Malformed endpoints are logged and skipped; they do not crash the function. """ + from pydantic import ValidationError + from litellm.proxy.proxy_server import config_passthrough_endpoints if config_passthrough_endpoints is None or len(config_passthrough_endpoints) == 0: @@ -2213,15 +2216,22 @@ def _get_pass_through_endpoints_from_config() -> List[PassThroughGenericEndpoint returned_endpoints: List[PassThroughGenericEndpoint] = [] for endpoint in config_passthrough_endpoints: - if isinstance(endpoint, dict): - endpoint_dict = dict(endpoint) - endpoint_dict["is_from_config"] = True - returned_endpoints.append(PassThroughGenericEndpoint(**endpoint_dict)) - elif isinstance(endpoint, PassThroughGenericEndpoint): - # Create a copy with is_from_config=True - endpoint_dict = endpoint.model_dump() - endpoint_dict["is_from_config"] = True - returned_endpoints.append(PassThroughGenericEndpoint(**endpoint_dict)) + try: + if isinstance(endpoint, dict): + endpoint_dict = dict(endpoint) + endpoint_dict["is_from_config"] = True + returned_endpoints.append(PassThroughGenericEndpoint(**endpoint_dict)) + elif isinstance(endpoint, PassThroughGenericEndpoint): + # Create a copy with is_from_config=True + endpoint_dict = endpoint.model_dump() + endpoint_dict["is_from_config"] = True + returned_endpoints.append(PassThroughGenericEndpoint(**endpoint_dict)) + except ValidationError as e: + verbose_proxy_logger.warning( + "Skipping malformed pass-through endpoint from config: %s", + e, + exc_info=False, + ) return returned_endpoints diff --git a/tests/test_litellm/proxy/pass_through_endpoints/test_pass_through_endpoints.py b/tests/test_litellm/proxy/pass_through_endpoints/test_pass_through_endpoints.py index a64fef1c4b..e50e10352e 100644 --- a/tests/test_litellm/proxy/pass_through_endpoints/test_pass_through_endpoints.py +++ b/tests/test_litellm/proxy/pass_through_endpoints/test_pass_through_endpoints.py @@ -1410,6 +1410,39 @@ async def test_get_pass_through_endpoints_includes_config_and_db(): assert by_path["/v1/rerank"].target == "https://db-override.com/v1/rerank" +def test_get_pass_through_endpoints_from_config_skips_malformed(): + """ + Test that _get_pass_through_endpoints_from_config skips malformed endpoints + and returns only valid ones, without raising. + """ + from litellm.proxy.pass_through_endpoints.pass_through_endpoints import ( + _get_pass_through_endpoints_from_config, + ) + + # Mix of valid and malformed config endpoints + config_passthrough_endpoints = [ + {"path": "/valid/1", "target": "https://valid1.example.com"}, + {}, # Missing required path and target + {"path": "/missing-target"}, # Missing required target + {"target": "https://example.com"}, # Missing required path + {"path": "/valid/2", "target": "https://valid2.example.com", "headers": {}}, + ] + + with patch( + "litellm.proxy.proxy_server.config_passthrough_endpoints", + config_passthrough_endpoints, + ): + result = _get_pass_through_endpoints_from_config() + + # Only the 2 valid endpoints should be returned + assert len(result) == 2 + paths = {ep.path for ep in result} + assert "/valid/1" in paths + assert "/valid/2" in paths + for ep in result: + assert ep.is_from_config is True + + @pytest.mark.asyncio async def test_delete_pass_through_endpoint_empty_list(): """