From ccb3afb3e23d4b2cea3338facc83b086317debbe Mon Sep 17 00:00:00 2001 From: Aman Sachan Date: Wed, 13 May 2026 02:06:55 +0000 Subject: [PATCH] fix(langfuse): guard against None standard_callback_dynamic_params Fix AttributeError: 'NoneType' object has no attribute 'get' when Langfuse is configured via environment variables (no per-request dynamic params). In _dynamic_langfuse_credentials_are_passed() and get_dynamic_langfuse_logging_config(), standard_callback_dynamic_params can be None (not {}). Calling .get() directly on None raises AttributeError. Fix: use params = standard_callback_dynamic_params or {} before calling .get(). Added regression tests for both functions to ensure None is handled correctly. Fixes: BerriAI/litellm#25940 --- .../integrations/langfuse/langfuse_handler.py | 20 ++++++----- .../test_langfuse_dynamic_credentials.py | 33 +++++++++++++++++++ 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/litellm/integrations/langfuse/langfuse_handler.py b/litellm/integrations/langfuse/langfuse_handler.py index 4a809726424..b2510cee44c 100644 --- a/litellm/integrations/langfuse/langfuse_handler.py +++ b/litellm/integrations/langfuse/langfuse_handler.py @@ -140,13 +140,15 @@ class LangFuseHandler: If no dynamic parameters are provided, it uses the `globalLangfuseLogger` values """ # only use dynamic params if langfuse credentials are passed dynamically + # Guard against None to handle the case where no per-request params are passed + params = standard_callback_dynamic_params or {} return LangfuseLoggingConfig( - langfuse_secret=standard_callback_dynamic_params.get("langfuse_secret") - or standard_callback_dynamic_params.get("langfuse_secret_key"), - langfuse_public_key=standard_callback_dynamic_params.get( + langfuse_secret=params.get("langfuse_secret") + or params.get("langfuse_secret_key"), + langfuse_public_key=params.get( "langfuse_public_key" ), - langfuse_host=standard_callback_dynamic_params.get("langfuse_host"), + langfuse_host=params.get("langfuse_host"), ) @staticmethod @@ -159,12 +161,14 @@ class LangFuseHandler: Returns: bool: True if the dynamic langfuse credentials are passed, False otherwise """ + # Guard against None to handle the case where no per-request params are passed (credentials come from env vars) + params = standard_callback_dynamic_params or {} if ( - standard_callback_dynamic_params.get("langfuse_host") is not None - or standard_callback_dynamic_params.get("langfuse_public_key") is not None - or standard_callback_dynamic_params.get("langfuse_secret") is not None - or standard_callback_dynamic_params.get("langfuse_secret_key") is not None + params.get("langfuse_host") is not None + or params.get("langfuse_public_key") is not None + or params.get("langfuse_secret") is not None + or params.get("langfuse_secret_key") is not None ): return True return False diff --git a/tests/logging_callback_tests/test_langfuse_dynamic_credentials.py b/tests/logging_callback_tests/test_langfuse_dynamic_credentials.py index 1b198623381..e7b3adf5224 100644 --- a/tests/logging_callback_tests/test_langfuse_dynamic_credentials.py +++ b/tests/logging_callback_tests/test_langfuse_dynamic_credentials.py @@ -127,3 +127,36 @@ def test_langfuse_handler_accepts_secret_key_alias(monkeypatch): assert captured["allow_env_credentials"] is False assert captured["cached_service_name"] == "langfuse" assert captured["cached_logging_obj"] is logger + + +def test_dynamic_langfuse_credentials_are_passed_handles_none(monkeypatch): + """Regression test: _dynamic_langfuse_credentials_are_passed must not crash when standard_callback_dynamic_params is None. + + When Langfuse is configured via environment variables (no per-request dynamic params), + standard_callback_dynamic_params is None, not {}. The function must not raise + AttributeError: 'NoneType' object has no attribute 'get'. + """ + monkeypatch.setenv("LANGFUSE_PUBLIC_KEY", "pk-test") + monkeypatch.setenv("LANGFUSE_SECRET_KEY", "sk-test") + monkeypatch.setenv("LANGFUSE_HOST", "https://langfuse.example") + + # Should return False (no dynamic params passed), not raise AttributeError + result = LangFuseHandler._dynamic_langfuse_credentials_are_passed(None) + assert result is False + + +def test_get_dynamic_langfuse_logging_config_handles_none(monkeypatch): + """Regression test: get_dynamic_langfuse_logging_config must not crash when standard_callback_dynamic_params is None. + + When Langfuse is configured via environment variables, standard_callback_dynamic_params is None. + The function should return a config with None values (relying on env var fallback downstream). + """ + monkeypatch.setenv("LANGFUSE_PUBLIC_KEY", "pk-test") + monkeypatch.setenv("LANGFUSE_SECRET_KEY", "sk-test") + monkeypatch.setenv("LANGFUSE_HOST", "https://langfuse.example") + + # Should not raise AttributeError + config = LangFuseHandler.get_dynamic_langfuse_logging_config(None) + assert config.langfuse_public_key is None + assert config.langfuse_secret is None + assert config.langfuse_host is None