From c42c2792d8b23e9b84abe25faaf02feb3cf7594c Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Thu, 7 May 2026 10:44:29 +0200 Subject: [PATCH] fix(langfuse): support LANGFUSE_BASE_URL env var --- litellm/integrations/langfuse/langfuse.py | 6 ++- .../integrations/langfuse/langfuse_otel.py | 13 +++++-- litellm/proxy/_types.py | 1 + tests/local_testing/test_custom_logger.py | 1 + .../integrations/test_langfuse.py | 24 +++++++++++- .../integrations/test_langfuse_otel.py | 37 ++++++++++++++++++- 6 files changed, 73 insertions(+), 9 deletions(-) diff --git a/litellm/integrations/langfuse/langfuse.py b/litellm/integrations/langfuse/langfuse.py index 0efc7d66876..6d8b1968344 100644 --- a/litellm/integrations/langfuse/langfuse.py +++ b/litellm/integrations/langfuse/langfuse.py @@ -106,8 +106,10 @@ def resolve_langfuse_credentials( ) public_key = langfuse_public_key or os.getenv("LANGFUSE_PUBLIC_KEY") - resolved_host = langfuse_host or os.getenv( - "LANGFUSE_HOST", "https://cloud.langfuse.com" + resolved_host = ( + langfuse_host + or os.getenv("LANGFUSE_BASE_URL") + or os.getenv("LANGFUSE_HOST", "https://cloud.langfuse.com") ) return public_key, secret_key, resolved_host diff --git a/litellm/integrations/langfuse/langfuse_otel.py b/litellm/integrations/langfuse/langfuse_otel.py index b96ec72b04e..2cc1812b671 100644 --- a/litellm/integrations/langfuse/langfuse_otel.py +++ b/litellm/integrations/langfuse/langfuse_otel.py @@ -260,9 +260,14 @@ class LangfuseOtelLogger(OpenTelemetry): Returned in the following order of precedence: 1. LANGFUSE_OTEL_HOST - 2. LANGFUSE_HOST + 2. LANGFUSE_BASE_URL + 3. LANGFUSE_HOST """ - return os.environ.get("LANGFUSE_OTEL_HOST") or os.environ.get("LANGFUSE_HOST") + return ( + os.environ.get("LANGFUSE_OTEL_HOST") + or os.environ.get("LANGFUSE_BASE_URL") + or os.environ.get("LANGFUSE_HOST") + ) def _create_open_telemetry_config_from_langfuse_env(self) -> OpenTelemetryConfig: """ @@ -311,7 +316,9 @@ class LangfuseOtelLogger(OpenTelemetry): Environment Variables: LANGFUSE_PUBLIC_KEY: Required. Langfuse public key for authentication. LANGFUSE_SECRET_KEY: Required. Langfuse secret key for authentication. - LANGFUSE_HOST: Optional. Custom Langfuse host URL. Defaults to US cloud. + LANGFUSE_OTEL_HOST: Optional. Custom Langfuse OTEL host URL. + LANGFUSE_BASE_URL: Optional. Custom Langfuse base URL. + LANGFUSE_HOST: Optional. Deprecated custom Langfuse host URL. Defaults to US cloud. Returns: OpenTelemetryConfig: A Pydantic model containing Langfuse OTEL configuration. diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 2c976479798..92c6e7cee43 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -3324,6 +3324,7 @@ class AllCallbacks(LiteLLMPydanticObjectBase): litellm_callback_params=[ "LANGFUSE_PUBLIC_KEY", "LANGFUSE_SECRET_KEY", + "LANGFUSE_BASE_URL", "LANGFUSE_HOST", ], ) diff --git a/tests/local_testing/test_custom_logger.py b/tests/local_testing/test_custom_logger.py index 6af2ff7e964..931826ae7fb 100644 --- a/tests/local_testing/test_custom_logger.py +++ b/tests/local_testing/test_custom_logger.py @@ -108,6 +108,7 @@ def test_get_callback_env_vars(): assert env_vars == [ "LANGFUSE_PUBLIC_KEY", "LANGFUSE_SECRET_KEY", + "LANGFUSE_BASE_URL", "LANGFUSE_HOST", ] diff --git a/tests/test_litellm/integrations/test_langfuse.py b/tests/test_litellm/integrations/test_langfuse.py index 98e5d1f6dd4..97544da86c3 100644 --- a/tests/test_litellm/integrations/test_langfuse.py +++ b/tests/test_litellm/integrations/test_langfuse.py @@ -10,15 +10,35 @@ import pytest import litellm from litellm.integrations.langfuse import langfuse as langfuse_module -from litellm.integrations.langfuse.langfuse import LangFuseLogger +from litellm.integrations.langfuse.langfuse import ( + LangFuseLogger, + resolve_langfuse_credentials, +) sys.path.insert(0, os.path.abspath("../..")) -from litellm.integrations.langfuse.langfuse import LangFuseLogger # Import LangfuseUsageDetails directly from the module where it's defined from litellm.types.integrations.langfuse import * +def test_resolve_langfuse_credentials_prefers_base_url_over_host(): + with patch.dict( + os.environ, + { + "LANGFUSE_PUBLIC_KEY": "test-public-key", + "LANGFUSE_SECRET_KEY": "test-secret-key", + "LANGFUSE_BASE_URL": "https://base-url.langfuse.com", + "LANGFUSE_HOST": "https://deprecated-host.langfuse.com", + }, + clear=False, + ): + public_key, secret_key, host = resolve_langfuse_credentials() + + assert public_key == "test-public-key" + assert secret_key == "test-secret-key" + assert host == "https://base-url.langfuse.com" + + class TestLangfuseUsageDetails(unittest.TestCase): def setUp(self): # Save global Langfuse client counter to restore after test diff --git a/tests/test_litellm/integrations/test_langfuse_otel.py b/tests/test_litellm/integrations/test_langfuse_otel.py index 44853d9dce5..c9cddc96251 100644 --- a/tests/test_litellm/integrations/test_langfuse_otel.py +++ b/tests/test_litellm/integrations/test_langfuse_otel.py @@ -14,6 +14,7 @@ class TestLangfuseOtelIntegration: """Test that config is created correctly with required environment variables.""" # Clean environment of any Langfuse-related variables env_vars_to_clean = [ + "LANGFUSE_BASE_URL", "LANGFUSE_HOST", "OTEL_EXPORTER_OTLP_ENDPOINT", "OTEL_EXPORTER_OTLP_HEADERS", @@ -79,6 +80,21 @@ class TestLangfuseOtelIntegration: # Endpoint assertion removed as side effect is gone assert isinstance(config, OpenTelemetryConfig) + def test_get_langfuse_otel_config_with_base_url(self): + """Test config with LANGFUSE_BASE_URL.""" + with patch.dict( + os.environ, + { + "LANGFUSE_PUBLIC_KEY": "test_public_key", + "LANGFUSE_SECRET_KEY": "test_secret_key", + "LANGFUSE_BASE_URL": "https://base-url.langfuse.com", + }, + clear=False, + ): + config = LangfuseOtelLogger.get_langfuse_otel_config() + assert isinstance(config, OpenTelemetryConfig) + assert config.endpoint == "https://base-url.langfuse.com/api/public/otel" + def test_get_langfuse_otel_config_with_host_no_protocol(self): """Test config with custom host without protocol.""" with patch.dict( @@ -393,12 +409,13 @@ class TestLangfuseOtelIntegration: assert result == {} def test_get_langfuse_otel_config_with_otel_host_priority(self): - """LANGFUSE_OTEL_HOST should take priority over LANGFUSE_HOST.""" + """LANGFUSE_OTEL_HOST should take priority over LANGFUSE_BASE_URL and LANGFUSE_HOST.""" with patch.dict( os.environ, { "LANGFUSE_PUBLIC_KEY": "test_public_key", "LANGFUSE_SECRET_KEY": "test_secret_key", + "LANGFUSE_BASE_URL": "https://base-url.langfuse.com", "LANGFUSE_HOST": "https://should-not-be-used.com", "LANGFUSE_OTEL_HOST": "https://otel-host.com", }, @@ -406,7 +423,23 @@ class TestLangfuseOtelIntegration: ): config = LangfuseOtelLogger.get_langfuse_otel_config() assert isinstance(config, OpenTelemetryConfig) - # Endpoint assertion removed as side effect is gone + assert config.endpoint == "https://otel-host.com/api/public/otel" + + def test_get_langfuse_otel_config_with_base_url_priority_over_host(self): + """LANGFUSE_BASE_URL should take priority over deprecated LANGFUSE_HOST.""" + with patch.dict( + os.environ, + { + "LANGFUSE_PUBLIC_KEY": "test_public_key", + "LANGFUSE_SECRET_KEY": "test_secret_key", + "LANGFUSE_BASE_URL": "https://base-url.langfuse.com", + "LANGFUSE_HOST": "https://deprecated-host.langfuse.com", + }, + clear=False, + ): + config = LangfuseOtelLogger.get_langfuse_otel_config() + assert isinstance(config, OpenTelemetryConfig) + assert config.endpoint == "https://base-url.langfuse.com/api/public/otel" class TestLangfuseOtelResponsesAPI: