diff --git a/litellm/integrations/opentelemetry.py b/litellm/integrations/opentelemetry.py index 0d0e309034a..3a0d10ffbd3 100644 --- a/litellm/integrations/opentelemetry.py +++ b/litellm/integrations/opentelemetry.py @@ -2578,6 +2578,11 @@ class OpenTelemetry(OTELGenAISemconvMixin, CustomLogger): value=optional_params.get("user"), ) + # Some responses (MCP CallToolResult, DecisionsResponse, list) have no + # .get(). They carry no LLM response fields, so treat them as None. + if not callable(getattr(response_obj, "get", None)): + response_obj = None # rebind-ok: no .get() means no LLM response fields + # The unique identifier for the LLM call. # Completions have a provider response ID (e.g. "chatcmpl-xxx"), # but Embeddings and Image-gen responses do not. Fall back to diff --git a/tests/unit/integrations/test_opentelemetry.py b/tests/unit/integrations/test_opentelemetry.py index f0fca94c370..479b63aa0c7 100644 --- a/tests/unit/integrations/test_opentelemetry.py +++ b/tests/unit/integrations/test_opentelemetry.py @@ -4195,6 +4195,38 @@ class TestResponseIdFallback(unittest.TestCase): mock_span.set_attribute.assert_any_call("litellm.call_id", call_id) + def test_litellm_call_id_set_when_response_obj_has_no_get(self): + """Responses without .get() (MCP CallToolResult, DecisionsResponse, list) + must not abort set_attributes before litellm.call_id is set.""" + from pydantic import BaseModel + + class NoGetResponse(BaseModel): + id: str = "dec-1" + + call_id = "my-litellm-call-uuid-789" + for response_obj in (NoGetResponse(), ["tool output"]): + with self.subTest(response_type=type(response_obj).__name__): + otel = OpenTelemetry() + mock_span = MagicMock() + kwargs = { + "model": "my-model", + "optional_params": {}, + "litellm_params": {}, + "standard_logging_object": { + "id": "litellm-call-id-1", + "litellm_call_id": call_id, + "call_type": "call_mcp_tool", + "metadata": {}, + }, + } + + otel.set_attributes(mock_span, kwargs, response_obj) + + mock_span.set_attribute.assert_any_call("litellm.call_id", call_id) + mock_span.set_attribute.assert_any_call( + "gen_ai.response.id", "litellm-call-id-1" + ) + class TestOpenTelemetryResponsesAPI(unittest.TestCase): """