From 9a1747753d7e12a0288380bac373198d4c7400b9 Mon Sep 17 00:00:00 2001 From: Zach Groves Date: Tue, 31 Mar 2026 16:10:10 -0400 Subject: [PATCH] fix(llmobs): skip downstream OpenAI span check for litellm_proxy/ models Models with the litellm_proxy/ prefix route through a proxy and never invoke the OpenAI integration directly, so the downstream span check was incorrectly suppressing LLMObs span submission for these requests. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- ddtrace/llmobs/_integrations/litellm.py | 3 ++ ...oxy-model-llmobs-fix-a1b2c3d4e5f6a7b8.yaml | 7 +++++ tests/contrib/litellm/test_litellm_llmobs.py | 29 +++++++++++++++++++ 3 files changed, 39 insertions(+) create mode 100644 releasenotes/notes/litellm-proxy-model-llmobs-fix-a1b2c3d4e5f6a7b8.yaml diff --git a/ddtrace/llmobs/_integrations/litellm.py b/ddtrace/llmobs/_integrations/litellm.py index 77ea27315b3..73ae0ae4674 100644 --- a/ddtrace/llmobs/_integrations/litellm.py +++ b/ddtrace/llmobs/_integrations/litellm.py @@ -158,6 +158,9 @@ def _has_downstream_openai_span(self, kwargs: dict[str, Any], model: Optional[st """ stream = kwargs.get("stream", False) model_lower = model.lower() if model else "" + # litellm_proxy/ requests route through a proxy — the OpenAI integration never fires for these + if model_lower.startswith("litellm_proxy/"): + return False # best effort attempt to check if Open AI or Azure since model_provider is unknown until request completes is_openai_model = any(prefix in model_lower for prefix in ("gpt", "openai", "azure")) return is_openai_model and not stream and LLMObs._integration_is_enabled("openai") diff --git a/releasenotes/notes/litellm-proxy-model-llmobs-fix-a1b2c3d4e5f6a7b8.yaml b/releasenotes/notes/litellm-proxy-model-llmobs-fix-a1b2c3d4e5f6a7b8.yaml new file mode 100644 index 00000000000..2b417c4b958 --- /dev/null +++ b/releasenotes/notes/litellm-proxy-model-llmobs-fix-a1b2c3d4e5f6a7b8.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + litellm: Fix missing LLMObs spans when routing through a litellm proxy. + Models with the ``litellm_proxy/`` prefix were incorrectly suppressed by + the OpenAI downstream span check, resulting in empty or missing LLMObs + spans. Proxy requests are now always handled by the litellm integration. diff --git a/tests/contrib/litellm/test_litellm_llmobs.py b/tests/contrib/litellm/test_litellm_llmobs.py index b0a1763e9a5..524c2e3c6c8 100644 --- a/tests/contrib/litellm/test_litellm_llmobs.py +++ b/tests/contrib/litellm/test_litellm_llmobs.py @@ -611,6 +611,35 @@ def test_completion_with_reasoning(self, litellm, request_vcr, llmobs_events, te assert event_metrics["reasoning_output_tokens"] == 15 +@pytest.mark.parametrize( + "model,stream,openai_enabled,expected", + [ + # litellm_proxy/ prefix always suppresses downstream check regardless of model name or OpenAI enabled + ("litellm_proxy/azure-gpt-5-nano", False, True, False), + ("litellm_proxy/gpt-4o", False, True, False), + ("litellm_proxy/openai/gpt-4", False, True, False), + # normal OpenAI/Azure models with OpenAI integration enabled (non-streamed) should return True + ("gpt-4o", False, True, True), + ("azure/gpt-4", False, True, True), + ("openai/gpt-4", False, True, True), + # streaming disables downstream check + ("gpt-4o", True, True, False), + # OpenAI integration disabled disables downstream check + ("gpt-4o", False, False, False), + # non-OpenAI models are unaffected + ("anthropic/claude-3", False, True, False), + ], +) +def test_has_downstream_openai_span(model, stream, openai_enabled, expected): + from ddtrace import config + from ddtrace.llmobs._integrations import LiteLLMIntegration + + integration = LiteLLMIntegration(integration_config=config.litellm) + kwargs = {"stream": stream} + with mock.patch("ddtrace.llmobs._integrations.litellm.LLMObs._integration_is_enabled", return_value=openai_enabled): + assert integration._has_downstream_openai_span(kwargs, model) is expected + + def test_enable_llmobs_after_litellm_was_imported(run_python_code_in_subprocess): """ Test that LLMObs.enable() logs a warning if litellm is imported before LLMObs.enable() is called.