diff --git a/litellm/integrations/langfuse/langfuse.py b/litellm/integrations/langfuse/langfuse.py index 825f1b89e90..ee67228841a 100644 --- a/litellm/integrations/langfuse/langfuse.py +++ b/litellm/integrations/langfuse/langfuse.py @@ -638,9 +638,11 @@ class LangFuseLogger: elif response_obj is not None and isinstance(response_obj, litellm.ResponsesAPIResponse): input = prompt output = self._get_responses_api_content_for_langfuse(response_obj) - elif response_obj is not None and isinstance(response_obj, (DecisionsResponse, OpenAIDecisionResponse)): - input = prompt - output = response_obj.model_dump(mode="json", exclude={"usage"}) + elif isinstance(response_obj, (DecisionsResponse, OpenAIDecisionResponse)): + return ( # pyright: ignore[reportUnknownVariableType] # prompt is the caller's untyped dict, as in every branch + prompt, + response_obj.model_dump(mode="json", include={"answers"}), + ) elif ( kwargs.get("call_type") is not None and kwargs.get("call_type") == "_arealtime" diff --git a/tests/unit/integrations/langfuse/langfuse_expected_request_body/systemone.json b/tests/unit/integrations/langfuse/langfuse_expected_request_body/systemone.json new file mode 100644 index 00000000000..50e1ea18848 --- /dev/null +++ b/tests/unit/integrations/langfuse/langfuse_expected_request_body/systemone.json @@ -0,0 +1,38 @@ +{ + "name": "litellm-asystemone", + "parent_span_id": null, + "attributes": { + "langfuse.observation.cost_details": { + "total": 1.1886e-05 + }, + "langfuse.observation.input": { + "messages": [ + { + "content": "{\"state\": \"Customer writes: my payout has failed three days in a row.\", \"questions\": {\"urgent\": {\"type\": \"noul\", \"instructions\": \"Is this urgent?\"}}}", + "role": "user" + } + ] + }, + "langfuse.observation.level": "DEFAULT", + "langfuse.observation.metadata.api_base": "https://openrouter.ai/api/alpha/decisions", + "langfuse.observation.model.name": "typesafe/jev-1.13", + "langfuse.observation.model.parameters": {}, + "langfuse.observation.output": { + "answers": { + "urgent": { + "noul": 0.87, + "type": "noul" + } + } + }, + "langfuse.observation.type": "generation", + "langfuse.observation.usage_details": { + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0, + "input": 283, + "output": 20, + "total": 303 + }, + "langfuse.trace.name": "litellm-asystemone" + } +} diff --git a/tests/unit/integrations/langfuse/test_langfuse.py b/tests/unit/integrations/langfuse/test_langfuse.py index ecaecebf276..747ebd1cc1f 100644 --- a/tests/unit/integrations/langfuse/test_langfuse.py +++ b/tests/unit/integrations/langfuse/test_langfuse.py @@ -445,7 +445,7 @@ async def test_langfuse_logging_systemone( **({"id": response_id} if response_id is not None else {}), "model": "typesafe/jev-1.13", "answers": answers, - "usage": {"input_tokens": 283, "output_tokens": 20}, + "usage": {"input_tokens": 283, "output_tokens": 20, "cost": 1.1886e-05}, } ) await litellm.asystemone( @@ -457,23 +457,7 @@ async def test_langfuse_logging_systemone( ) await _flush_langfuse() assert upstream.call_count == 1 - generations: Final = tuple( - TypeAdapter(dict[str, object]).validate_python(span["attributes"]) - for span in _exported_spans(route) - if span["trace_id"] == resolve_trace_id(trace_id) - and TypeAdapter(dict[str, object]).validate_python(span["attributes"]).get("langfuse.observation.type") - == "generation" - ) - assert len(generations) == 1 - assert generations[0]["langfuse.observation.usage_details"] == { - "input": 283, - "output": 20, - "total": 303, - "cache_creation_input_tokens": 0, - "cache_read_input_tokens": 0, - } - output: Final = TypeAdapter(dict[str, object]).validate_python(generations[0]["langfuse.observation.output"]) - assert output["answers"] == answers + _expected_generation(route, "systemone.json", trace_id) @pytest.mark.asyncio