From 7e727a1947a9301f8cc2e4964cfd1870dc5bb3f3 Mon Sep 17 00:00:00 2001 From: Cosimo Faeti Date: Tue, 20 Oct 2026 12:29:02 +0200 Subject: [PATCH 1/2] fix(passthrough): stop logging Langfuse pass-through calls as LLM requests The success handler meant to skip /langfuse/* pass-through calls, but is_langfuse_route() matched `/langfuse/` against the target URL path (LANGFUSE_HOST/api/public/...), which never contains it. Every call was logged as an LLM generation (model "unknown", cost 0). Tag the Langfuse route with custom_llm_provider="langfuse" and skip on that in is_langfuse_route(). Fixes #44030 Co-Authored-By: Claude Opus 5.5 --- .../pass_through_endpoints/success_handler.py | 6 ++- .../vertex_ai_endpoints/langfuse_endpoints.py | 3 ++ .../test_pass_through_endpoints.py | 43 +++++++++++++++++++ .../test_langfuse_passthrough_security.py | 29 +++++++++++++ 4 files changed, 79 insertions(+), 2 deletions(-) diff --git a/litellm/proxy/pass_through_endpoints/success_handler.py b/litellm/proxy/pass_through_endpoints/success_handler.py index 9c6c3111209..838fc732e50 100644 --- a/litellm/proxy/pass_through_endpoints/success_handler.py +++ b/litellm/proxy/pass_through_endpoints/success_handler.py @@ -461,7 +461,7 @@ class PassThroughEndpointLogging: **kwargs, ) return - elif self.is_langfuse_route(url_route): + elif self.is_langfuse_route(url_route, custom_llm_provider): # Don't log langfuse pass-through requests return elif self.is_transcribe_route(custom_llm_provider) and TranscribePassthroughLoggingHandler.is_priced_job_start( @@ -568,7 +568,9 @@ class PassThroughEndpointLogging: def is_openrouter_decisions_route(self, url_route: str, custom_llm_provider: str | None) -> bool: return custom_llm_provider == "openrouter" and urlparse(url_route).path.endswith("/alpha/decisions") - def is_langfuse_route(self, url_route: str): + def is_langfuse_route(self, url_route: str, custom_llm_provider: str | None = None): + if custom_llm_provider == "langfuse": + return True parsed_url: Final = urlparse(url_route) for route in self.TRACKED_LANGFUSE_ROUTES: if route in parsed_url.path: diff --git a/litellm/proxy/vertex_ai_endpoints/langfuse_endpoints.py b/litellm/proxy/vertex_ai_endpoints/langfuse_endpoints.py index 724fb128a9a..35b0dc93660 100644 --- a/litellm/proxy/vertex_ai_endpoints/langfuse_endpoints.py +++ b/litellm/proxy/vertex_ai_endpoints/langfuse_endpoints.py @@ -223,6 +223,9 @@ async def langfuse_proxy_route( target=target_url, custom_headers=target_headers, query_params=dict(request.query_params), + # tags the request so the success handler skips logging it as an LLM call; + # the target URL path (LANGFUSE_HOST/api/public/...) has no `/langfuse/` to match on + custom_llm_provider="langfuse", ) # dynamically construct pass-through endpoint based on incoming path received_value: Final = await endpoint_func( request, diff --git a/tests/unit/proxy/pass_through_endpoints/test_pass_through_endpoints.py b/tests/unit/proxy/pass_through_endpoints/test_pass_through_endpoints.py index f0baceecdaa..6c334e3b037 100644 --- a/tests/unit/proxy/pass_through_endpoints/test_pass_through_endpoints.py +++ b/tests/unit/proxy/pass_through_endpoints/test_pass_through_endpoints.py @@ -530,6 +530,49 @@ def test_is_langfuse_route(): assert handler.is_langfuse_route("") is False +def test_is_langfuse_route_matches_langfuse_provider_on_target_url(): + """ + The /langfuse/* proxy route forwards to LANGFUSE_HOST/api/public/..., so the + target URL never contains `/langfuse/`; the route is identified by its provider. + """ + handler = PassThroughEndpointLogging() + + assert handler.is_langfuse_route("https://cloud.langfuse.com/api/public/scores", "langfuse") is True + assert handler.is_langfuse_route("https://langfuse.internal/api/public/otel/v1/traces", "langfuse") is True + assert handler.is_langfuse_route("https://cloud.langfuse.com/api/public/scores") is False + assert handler.is_langfuse_route("https://api.openai.com/v1/chat/completions", "openai") is False + + +@pytest.mark.asyncio +async def test_pass_through_async_success_handler_skips_langfuse_provider(): + log_dispatch = AsyncMock() + handler = PassThroughEndpointLogging(log_dispatch=log_dispatch) + logging_obj = MagicMock() + logging_obj.model_call_details = {} + target_url = "https://cloud.langfuse.com/api/public/scores" + + result = await handler.pass_through_async_success_handler( + httpx_response=httpx.Response(200, request=httpx.Request("POST", target_url)), + response_body={"id": "score-1"}, + logging_obj=logging_obj, + url_route=target_url, + result="", + start_time=datetime.now(), + end_time=datetime.now(), + cache_hit=False, + request_body={}, + passthrough_logging_payload=PassthroughStandardLoggingPayload( + url=target_url, + request_body={}, + request_method="POST", + ), + custom_llm_provider="langfuse", + ) + + assert result is None + log_dispatch.assert_not_called() + + def test_is_vertex_route_ignores_plain_predict_path_segment(): """ Regression for LIT-4527: a custom (non-Vertex) passthrough URL whose path diff --git a/tests/unit/proxy/test_langfuse_passthrough_security.py b/tests/unit/proxy/test_langfuse_passthrough_security.py index 5ef3c38c09d..98967ca562e 100644 --- a/tests/unit/proxy/test_langfuse_passthrough_security.py +++ b/tests/unit/proxy/test_langfuse_passthrough_security.py @@ -1,3 +1,4 @@ +import base64 import socket import pytest @@ -100,3 +101,31 @@ def test_dynamic_langfuse_proxy_target_preserves_host_header_for_http(monkeypatc assert target_url == "http://8.8.8.8/api/public/projects" assert headers["Host"] == "langfuse.example" + + +@pytest.mark.asyncio +async def test_langfuse_proxy_route_tags_pass_through_as_langfuse_provider(monkeypatch): + """The provider tag is what lets the success handler skip logging these calls as LLM requests.""" + from unittest.mock import AsyncMock, MagicMock + + from litellm.proxy.vertex_ai_endpoints import langfuse_endpoints + + monkeypatch.setenv("LANGFUSE_HOST", "https://langfuse.example.com") + monkeypatch.setenv("LANGFUSE_PUBLIC_KEY", "global-public") + monkeypatch.setenv("LANGFUSE_SECRET_KEY", "global-secret") + monkeypatch.setattr(langfuse_endpoints, "user_api_key_auth", AsyncMock(return_value=MagicMock())) + monkeypatch.setattr(langfuse_endpoints, "get_dynamic_logging_metadata", MagicMock(return_value=None)) + endpoint_func = AsyncMock(return_value={"ok": True}) + create_route = MagicMock(return_value=endpoint_func) + monkeypatch.setattr(langfuse_endpoints, "create_pass_through_route", create_route) + + request = MagicMock() + request.headers = {"Authorization": "Basic " + base64.b64encode(b"x:sk-1234").decode()} + request.query_params = {} + + await langfuse_endpoints.langfuse_proxy_route( + endpoint="api/public/scores", request=request, fastapi_response=MagicMock() + ) + + assert create_route.call_args.kwargs["custom_llm_provider"] == "langfuse" + assert create_route.call_args.kwargs["target"] == "https://langfuse.example.com/api/public/scores" From 4963ce552a51bc8b90e1820e27712c4a1b58616c Mon Sep 17 00:00:00 2001 From: Cosimo Faeti Date: Tue, 20 Oct 2026 12:50:26 +0200 Subject: [PATCH 2/2] test(passthrough): use a non-default key in the Langfuse route test check_no_publicly_known_master_key rejects the sk-1234 prefix. Co-Authored-By: Claude Opus 5.5 --- tests/unit/proxy/test_langfuse_passthrough_security.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/proxy/test_langfuse_passthrough_security.py b/tests/unit/proxy/test_langfuse_passthrough_security.py index 98967ca562e..9d9ee69f81d 100644 --- a/tests/unit/proxy/test_langfuse_passthrough_security.py +++ b/tests/unit/proxy/test_langfuse_passthrough_security.py @@ -120,7 +120,7 @@ async def test_langfuse_proxy_route_tags_pass_through_as_langfuse_provider(monke monkeypatch.setattr(langfuse_endpoints, "create_pass_through_route", create_route) request = MagicMock() - request.headers = {"Authorization": "Basic " + base64.b64encode(b"x:sk-1234").decode()} + request.headers = {"Authorization": "Basic " + base64.b64encode(b"x:sk-virtual-key").decode()} request.query_params = {} await langfuse_endpoints.langfuse_proxy_route(