fix: revert langfuse dep bump and add DeprecationWarning test

The langfuse>=4.7 dep bump caused an unsatisfiable dependency resolution:
langfuse>=4.7 requires opentelemetry-sdk>=1.33.1, but litellm pins
opentelemetry-sdk==1.28.0. The dep change is not needed because:

- langfuse_otel (the recommended callback) sends spans via pure OTLP
  and imports zero langfuse SDK symbols
- The legacy langfuse callback (being deprecated) continues to work
  with the existing langfuse>=2.59.7,<3.0 pin

Also adds test_langfuse_legacy_callback_emits_deprecation_warning to
tests/test_service_logger_otel.py to cover the new DeprecationWarning
added to LangFuseLogger.__init__ in the previous commit.
This commit is contained in:
Vedant-2-6 2026-08-31 20:00:58 +05:30
parent e1f359c621
commit d58d1d5554
2 changed files with 50 additions and 17 deletions

View file

@ -146,7 +146,7 @@ proxy-runtime = [
"anthropic[vertex]>=0.84.0,<1.0",
"grpcio==1.78.0",
"prometheus-client>=0.20.0,<1.0",
"langfuse>=4.7,<5",
"langfuse>=2.59.7,<3.0",
"opentelemetry-api==1.28.0",
"opentelemetry-sdk==1.28.0",
"opentelemetry-exporter-otlp==1.28.0",
@ -195,7 +195,7 @@ dev = [
"opentelemetry-sdk==1.28.0",
"opentelemetry-exporter-otlp==1.28.0",
"opentelemetry-instrumentation-fastapi==0.49b0",
"langfuse>=4.7,<5",
"langfuse==2.59.7",
"fastapi-offline==1.7.6",
"fakeredis==2.34.1",
"pytest-rerunfailures==15.1",

View file

@ -25,9 +25,7 @@ class TestServiceLoggerOTEL(unittest.IsolatedAsyncioTestCase):
@patch("litellm.integrations.opentelemetry.OpenTelemetry._init_tracing")
@patch("litellm.integrations.opentelemetry.OpenTelemetry._init_metrics")
@patch("litellm.integrations.opentelemetry.OpenTelemetry._init_logs")
async def test_langfuse_otel_ignores_service_logs(
self, mock_logs, mock_metrics, mock_tracing
):
async def test_langfuse_otel_ignores_service_logs(self, mock_logs, mock_metrics, mock_tracing):
"""
Test that LangfuseOtelLogger overrides the service logging hooks with 'pass'.
"""
@ -46,9 +44,7 @@ class TestServiceLoggerOTEL(unittest.IsolatedAsyncioTestCase):
@patch("litellm.integrations.opentelemetry.OpenTelemetry._init_tracing")
@patch("litellm.integrations.opentelemetry.OpenTelemetry._init_metrics")
@patch("litellm.integrations.opentelemetry.OpenTelemetry._init_logs")
async def test_langfuse_otel_does_not_create_proxy_request_span(
self, mock_logs, mock_metrics, mock_tracing
):
async def test_langfuse_otel_does_not_create_proxy_request_span(self, mock_logs, mock_metrics, mock_tracing):
"""
Test that LangfuseOtelLogger returns None for create_litellm_proxy_request_started_span.
@ -73,9 +69,7 @@ class TestServiceLoggerOTEL(unittest.IsolatedAsyncioTestCase):
@patch("litellm.integrations.opentelemetry.OpenTelemetry._init_tracing")
@patch("litellm.integrations.opentelemetry.OpenTelemetry._init_metrics")
@patch("litellm.integrations.opentelemetry.OpenTelemetry._init_logs")
async def test_service_logging_shadowing_fix(
self, mock_logs, mock_metrics, mock_tracing
):
async def test_service_logging_shadowing_fix(self, mock_logs, mock_metrics, mock_tracing):
"""
Test the architectural fix: multiple OTEL loggers should receive logs independently.
"""
@ -89,9 +83,7 @@ class TestServiceLoggerOTEL(unittest.IsolatedAsyncioTestCase):
service_logging = ServiceLogging()
# 3. Mock the base OpenTelemetry hook
with patch.object(
OpenTelemetry, "async_service_success_hook", new_callable=AsyncMock
) as mock_base_hook:
with patch.object(OpenTelemetry, "async_service_success_hook", new_callable=AsyncMock) as mock_base_hook:
# Trigger a service event
await service_logging.async_service_success_hook(
service=ServiceTypes.DB,
@ -112,9 +104,7 @@ class TestServiceLoggerOTEL(unittest.IsolatedAsyncioTestCase):
@patch("litellm.integrations.opentelemetry.OpenTelemetry._init_tracing")
@patch("litellm.integrations.opentelemetry.OpenTelemetry._init_metrics")
@patch("litellm.integrations.opentelemetry.OpenTelemetry._init_logs")
async def test_langfuse_otel_env_config_includes_v4_ingestion_header(
self, mock_logs, mock_metrics, mock_tracing
):
async def test_langfuse_otel_env_config_includes_v4_ingestion_header(self, mock_logs, mock_metrics, mock_tracing):
logger = LangfuseOtelLogger()
headers = OpenTelemetry._get_headers_dictionary(logger.config.headers)
@ -147,6 +137,49 @@ class TestServiceLoggerOTEL(unittest.IsolatedAsyncioTestCase):
)
self.assertTrue(headers["Authorization"].startswith("Basic "))
def test_langfuse_legacy_callback_emits_deprecation_warning(self):
"""
Test that instantiating LangFuseLogger emits a DeprecationWarning
directing users to switch to the langfuse_otel callback.
"""
import warnings
from unittest.mock import MagicMock, patch
mock_langfuse_module = MagicMock()
mock_langfuse_module.Langfuse = MagicMock()
with patch.dict(
"sys.modules",
{
"langfuse": mock_langfuse_module,
"langfuse.client": MagicMock(),
},
):
with warnings.catch_warnings(record=True) as caught:
warnings.simplefilter("always")
from litellm.integrations.langfuse.langfuse import LangFuseLogger
with patch(
"litellm.integrations.langfuse.langfuse.resolve_langfuse_credentials",
return_value=("pk", "sk", "https://cloud.langfuse.com"),
):
LangFuseLogger(
langfuse_public_key="pk",
langfuse_secret="sk",
langfuse_host="https://cloud.langfuse.com",
allow_env_credentials=False,
)
deprecation_warnings = [w for w in caught if issubclass(w.category, DeprecationWarning)]
self.assertTrue(
len(deprecation_warnings) >= 1,
"Expected at least one DeprecationWarning to be emitted",
)
self.assertIn(
"langfuse_otel",
str(deprecation_warnings[0].message),
)
if __name__ == "__main__":
unittest.main()