diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index e4eb4a06c8..27d7a2ad81 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -1871,11 +1871,9 @@ class AddTeamCallback(LiteLLMPydanticObjectBase): raise ValueError( f"Invalid callback variable: {key}. Must be one of {valid_keys}" ) - if not isinstance(value, str): - callback_vars[key] = str(value) - value = callback_vars[key] + callback_vars[key] = str(value) validate_no_callback_env_reference( - key, value, source="key/team callback metadata" + key, callback_vars[key], source="key/team callback metadata" ) return values diff --git a/litellm/proxy/litellm_pre_call_utils.py b/litellm/proxy/litellm_pre_call_utils.py index c3e3c7716a..ceadf82379 100644 --- a/litellm/proxy/litellm_pre_call_utils.py +++ b/litellm/proxy/litellm_pre_call_utils.py @@ -233,6 +233,20 @@ def convert_key_logging_metadata_to_callback( return team_callback_settings_obj +def _get_validated_callback_metadata( + item: dict, *, source: str +) -> Optional[AddTeamCallback]: + try: + return AddTeamCallback(**item) + except ValueError as e: + verbose_proxy_logger.warning( + "Ignoring invalid %s callback metadata: %s", + source, + _sanitize_for_log(str(e)), + ) + return None + + class KeyAndTeamLoggingSettings: """ Helper class to get the dynamic logging settings for the key and team @@ -272,8 +286,11 @@ def _get_dynamic_logging_metadata( ######################################################################################### if key_dynamic_logging_settings is not None: for item in key_dynamic_logging_settings: + callback = _get_validated_callback_metadata(item=item, source="key-level") + if callback is None: + continue callback_settings_obj = convert_key_logging_metadata_to_callback( - data=AddTeamCallback(**item), + data=callback, team_callback_settings_obj=callback_settings_obj, ) ######################################################################################### @@ -281,8 +298,11 @@ def _get_dynamic_logging_metadata( ######################################################################################### elif team_dynamic_logging_settings is not None: for item in team_dynamic_logging_settings: + callback = _get_validated_callback_metadata(item=item, source="team-level") + if callback is None: + continue callback_settings_obj = convert_key_logging_metadata_to_callback( - data=AddTeamCallback(**item), + data=callback, team_callback_settings_obj=callback_settings_obj, ) ######################################################################################### diff --git a/tests/proxy_unit_tests/test_proxy_utils.py b/tests/proxy_unit_tests/test_proxy_utils.py index 1fa3a021d7..3332e77f2d 100644 --- a/tests/proxy_unit_tests/test_proxy_utils.py +++ b/tests/proxy_unit_tests/test_proxy_utils.py @@ -309,10 +309,15 @@ def test_dynamic_logging_metadata_key_and_team_metadata(callback_vars): assert "os.environ" not in var -def test_dynamic_logging_metadata_rejects_env_references_from_key_metadata( +def test_dynamic_logging_metadata_ignores_env_references_from_key_metadata( monkeypatch, ): monkeypatch.setenv("LANGFUSE_SECRET_KEY_TEMP", "server-side-secret") + monkeypatch.setattr( + litellm.utils, + "get_secret", + lambda *args, **kwargs: pytest.fail("get_secret should not be called"), + ) from litellm.proxy.proxy_server import ProxyConfig proxy_config = ProxyConfig() @@ -332,13 +337,11 @@ def test_dynamic_logging_metadata_rejects_env_references_from_key_metadata( team_metadata={}, ) - with pytest.raises(ValueError) as exc_info: - _get_dynamic_logging_metadata( - user_api_key_dict=user_api_key_dict, proxy_config=proxy_config - ) + callbacks = _get_dynamic_logging_metadata( + user_api_key_dict=user_api_key_dict, proxy_config=proxy_config + ) - assert "os.environ/" in str(exc_info.value) - assert "server-side-secret" not in str(exc_info.value) + assert callbacks is None @pytest.mark.parametrize( diff --git a/tests/test_litellm/proxy/test_litellm_pre_call_utils.py b/tests/test_litellm/proxy/test_litellm_pre_call_utils.py index d971a884ee..543be7948f 100644 --- a/tests/test_litellm/proxy/test_litellm_pre_call_utils.py +++ b/tests/test_litellm/proxy/test_litellm_pre_call_utils.py @@ -10,7 +10,7 @@ from fastapi import Request from starlette.datastructures import Headers import litellm -from litellm.proxy._types import TeamCallbackMetadata, UserAPIKeyAuth +from litellm.proxy._types import AddTeamCallback, TeamCallbackMetadata, UserAPIKeyAuth from litellm.proxy.litellm_pre_call_utils import ( KeyAndTeamLoggingSettings, LiteLLMProxyRequestSetup, @@ -1274,10 +1274,28 @@ def test_get_dynamic_logging_metadata_with_arize_team_logging(): assert result.callback_vars["arize_space_id"] == "test_arize_space_id" -def test_get_dynamic_logging_metadata_rejects_env_reference_from_key_metadata( +def test_add_team_callback_rejects_env_reference(): + with pytest.raises(ValueError) as exc_info: + AddTeamCallback( + callback_name="langfuse", + callback_type="success", + callback_vars={ + "langfuse_secret_key": "os.environ/LANGFUSE_SECRET_KEY_TEMP" + }, + ) + + assert "os.environ/" in str(exc_info.value) + + +def test_get_dynamic_logging_metadata_ignores_env_reference_from_key_metadata( monkeypatch, ): monkeypatch.setenv("LANGFUSE_SECRET_KEY_TEMP", "server-side-secret") + monkeypatch.setattr( + litellm.utils, + "get_secret", + lambda *args, **kwargs: pytest.fail("get_secret should not be called"), + ) user_api_key_dict = UserAPIKeyAuth( api_key="test-key", metadata={ @@ -1294,13 +1312,11 @@ def test_get_dynamic_logging_metadata_rejects_env_reference_from_key_metadata( team_metadata={}, ) - with pytest.raises(ValueError) as exc_info: - _get_dynamic_logging_metadata( - user_api_key_dict=user_api_key_dict, proxy_config=MagicMock() - ) + result = _get_dynamic_logging_metadata( + user_api_key_dict=user_api_key_dict, proxy_config=MagicMock() + ) - assert "os.environ/" in str(exc_info.value) - assert "server-side-secret" not in str(exc_info.value) + assert result is None def test_get_num_retries_from_request():