diff --git a/litellm/integrations/langfuse/langfuse.py b/litellm/integrations/langfuse/langfuse.py index 45dd445d9df..fda2e098788 100644 --- a/litellm/integrations/langfuse/langfuse.py +++ b/litellm/integrations/langfuse/langfuse.py @@ -954,6 +954,7 @@ class LangFuseLogger: claim_trace_root=claim_trace_root, release=trace_params.get("release"), public=_trace_public_flag(trace_params.get("public")), + observation_id=resolve_observation_id(generation_params["id"]), attributes=_generation_attributes(generation_params, propagated=propagated_trace_attributes), ) if existing_trace_id is not None and ("input" in update_trace_keys or "output" in update_trace_keys): @@ -966,8 +967,8 @@ class LangFuseLogger: generation.end(end_time=to_unix_nanos(end_time)) # log_event_on_langfuse tuple-unpacks this and re-wraps it in the dict callers cache. - # The wrapper's id is the exported observation id; the pre-computed generation_id would - # name nothing in langfuse, because v4 derives observation ids from the OTel span. + # The wrapper's id is the exported observation id: the requested generation_id after + # resolve_observation_id, unless the provider was adopted from user code. return resolved_trace_id, generation.id except Exception: verbose_logger.error("Langfuse Layer Error - %s", traceback.format_exc()) diff --git a/litellm/integrations/langfuse/langfuse_sdk.py b/litellm/integrations/langfuse/langfuse_sdk.py index e6713ce2d86..b0254005d54 100644 --- a/litellm/integrations/langfuse/langfuse_sdk.py +++ b/litellm/integrations/langfuse/langfuse_sdk.py @@ -6,6 +6,7 @@ import threading from base64 import b64encode from collections.abc import Generator, Mapping from contextlib import contextmanager +from contextvars import ContextVar from datetime import datetime from hashlib import sha256 from types import MappingProxyType @@ -19,6 +20,7 @@ from opentelemetry.context import Context from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult +from opentelemetry.sdk.trace.id_generator import RandomIdGenerator from opentelemetry.sdk.trace.sampling import TraceIdRatioBased __all__ = ( @@ -109,6 +111,7 @@ def start_generation( claim_trace_root: bool, release: str | None = None, public: bool | None = None, + observation_id: str | None = None, attributes: Mapping[str, object], ) -> LangfuseGeneration: """Create a generation whose start time is when the model call began. @@ -119,10 +122,19 @@ def start_generation( ``public`` is the v2 ``trace(public=...)`` flag; v4 reads it off the root observation's ``langfuse.trace.public`` attribute instead. + + ``observation_id`` is the v2 ``generation(id=...)`` argument. v4 derives the + observation id from the OTel span id, so it is honoured through the + isolated provider's id generator; a provider adopted from user code keeps + its own generator and the returned generation's ``id`` is the truth. """ - otel_span: Final = client._otel_tracer.start_span( # pyright: ignore[reportPrivateUsage] # only route to a historical start time - name=name, context=context, start_time=to_unix_nanos(start_time) - ) + requested: Final = _requested_span_id.set(int(observation_id, 16) if observation_id is not None else None) + try: + otel_span: Final = client._otel_tracer.start_span( # pyright: ignore[reportPrivateUsage] # only route to a historical start time + name=name, context=context, start_time=to_unix_nanos(start_time) + ) + finally: + _requested_span_id.reset(requested) if claim_trace_root: otel_span.set_attribute(AS_ROOT_ATTRIBUTE, True) if public is not None: @@ -159,6 +171,16 @@ def start_child_span( _ENVIRONMENT_ATTRIBUTE: Final = "langfuse.environment" +_requested_span_id: Final[ContextVar[int | None]] = ContextVar("litellm_langfuse_requested_span_id", default=None) + + +class _RequestedSpanIdGenerator(RandomIdGenerator): + """Hand out the span id the calling context asked for, random otherwise.""" + + def generate_span_id(self) -> int: + requested: Final = _requested_span_id.get() + return super().generate_span_id() if requested is None else requested + # providers litellm itself constructed; a bundle adopted from user code may hold the # process-global provider, which litellm must never shut down. @@ -192,6 +214,7 @@ def build_isolated_tracer_provider(*, environment: str | None, release: str | No provider: Final = TracerProvider( resource=Resource.create(dict(attributes)), sampler=TraceIdRatioBased(sample_rate) if sample_rate < 1 else None, + id_generator=_RequestedSpanIdGenerator(), ) with _LIVE_CLIENTS_LOCK: _litellm_built_providers.add(provider) diff --git a/tests/local_testing/test_alangfuse.py b/tests/local_testing/test_alangfuse.py index 7b1f7f203e3..a9d111843fd 100644 --- a/tests/local_testing/test_alangfuse.py +++ b/tests/local_testing/test_alangfuse.py @@ -11,6 +11,7 @@ logging.basicConfig(level=logging.DEBUG) import litellm from litellm import completion from litellm.caching import InMemoryCache +from litellm.integrations.langfuse.langfuse_sdk import resolve_trace_id litellm.num_retries = 3 litellm.success_callback = ["langfuse"] @@ -36,7 +37,7 @@ def langfuse_client(): langfuse_client = langfuse.Langfuse( public_key=os.environ["LANGFUSE_PUBLIC_KEY"], secret_key=os.environ["LANGFUSE_SECRET_KEY"], - host="https://us.cloud.langfuse.com", + host=os.environ.get("LANGFUSE_HOST", "https://us.cloud.langfuse.com"), ) litellm.in_memory_llm_clients_cache.set_cache( key=_langfuse_cache_key, @@ -227,29 +228,27 @@ async def test_langfuse_logging_without_request_response(stream, langfuse_client print(chunk) langfuse_client.flush() - await asyncio.sleep(5) - # get trace with _unique_trace_name - trace = langfuse_client.get_generations(trace_id=_unique_trace_name) - - print("trace_from_langfuse", trace) - - _trace_data = trace.data - - if ( - len(_trace_data) == 0 - ): # prevent infrequent list index out of range error from langfuse api - return + for _ in range(30): + _trace_data = langfuse_client.api.observations.get_many( + trace_id=resolve_trace_id(_unique_trace_name), + type="GENERATION", + fields="core,io", + ).data + if _trace_data: + break + await asyncio.sleep(3) print(f"_trace_data: {_trace_data}") - assert _trace_data[0].input == { + assert json.loads(_trace_data[0].input) == { "messages": [{"content": "redacted-by-litellm", "role": "user"}] } - assert _trace_data[0].output == { + assert json.loads(_trace_data[0].output) == { "role": "assistant", "content": "redacted-by-litellm", "function_call": None, "tool_calls": None, + "provider_specific_fields": None, } except Exception as e: diff --git a/tests/logging_callback_tests/langfuse_expected_request_body/completion.json b/tests/logging_callback_tests/langfuse_expected_request_body/completion.json index e252e8a128f..27f77398978 100644 --- a/tests/logging_callback_tests/langfuse_expected_request_body/completion.json +++ b/tests/logging_callback_tests/langfuse_expected_request_body/completion.json @@ -1,99 +1,39 @@ { - "batch": [ - { - "id": "7e00e081-468b-4fe9-a409-eb12ac7d3d2d", - "type": "trace-create", - "body": { - "id": "litellm-test-793c217f-9417-4e77-84a7-8dcc16e5b72b", - "timestamp": "2025-01-16T19:28:55.124873Z", - "name": "litellm-acompletion", - "input": { - "messages": [ - { - "role": "user", - "content": "Hello!" - } - ] - }, - "output": { - "content": "Hello! How can I assist you today?", - "role": "assistant", - "tool_calls": null, - "function_call": null, - "provider_specific_fields": null - }, - "tags": [] - }, - "timestamp": "2025-01-16T19:28:55.125002Z" + "name": "litellm-acompletion", + "parent_span_id": null, + "attributes": { + "langfuse.internal.as_root": true, + "langfuse.observation.cost_details": { + "total": 3.5e-05 }, - { - "id": "b9ec2c0f-18df-46c7-9e90-624c60bf78ee", - "type": "generation-create", - "body": { - "name": "litellm-acompletion", - "startTime": "2025-01-16T11:28:54.796360-08:00", - "metadata": { - "hidden_params": { - "model_id": null, - "cache_key": null, - "api_base": "https://api.openai.com", - "response_cost": 3.5e-05, - "additional_headers": {}, - "litellm_overhead_time_ms": null, - "batch_models": null, - "litellm_model_name": "gpt-3.5-turbo", - "usage_object": null - }, - "litellm_response_cost": 3.5e-05, - "cache_hit": false, - "requester_metadata": {} - }, - "input": { - "messages": [ - { - "role": "user", - "content": "Hello!" - } - ] - }, - "output": { - "content": "Hello! How can I assist you today?", - "role": "assistant", - "tool_calls": null, - "function_call": null, - "provider_specific_fields": null - }, - "level": "DEFAULT", - "id": "time-11-28-54-796360_chatcmpl-521e530f-5e29-4d0a-8d1a-58fca0a847c2", - "endTime": "2025-01-16T11:28:55.124353-08:00", - "completionStartTime": "2025-01-16T11:28:55.124353-08:00", - "model": "gpt-3.5-turbo", - "modelParameters": { - "extra_body": "{}" - }, - "usage": { - "input": 10, - "output": 20, - "unit": "TOKENS", - "totalCost": 3.5e-05 - }, - "usageDetails": { - "input": 10, - "output": 20, - "total": 30, - "cache_creation_input_tokens": 0, - "cache_read_input_tokens": 0 - }, - "traceId": "litellm-test-6a51ae70-a4e7-499e-afcd-dce2a3b31850" - }, - "timestamp": "2025-01-16T19:28:55.125258Z" - } - ], - "metadata": { - "batch_size": 2, - "sdk_integration": "litellm", - "sdk_name": "python", - "sdk_version": "2.44.1", - "public_key": "pk-lf-03734ab3-8790-4c09-b5fb-8c3b663413b6" + "langfuse.observation.input": { + "messages": [ + { + "role": "user", + "content": "Hello!" + } + ] + }, + "langfuse.observation.level": "DEFAULT", + "langfuse.observation.model.name": "gpt-3.5-turbo", + "langfuse.observation.model.parameters": { + "extra_body": "{}" + }, + "langfuse.observation.output": { + "content": "Hello! How can I assist you today?", + "role": "assistant", + "tool_calls": null, + "function_call": null, + "provider_specific_fields": null + }, + "langfuse.observation.type": "generation", + "langfuse.observation.usage_details": { + "input": 10, + "output": 20, + "total": 30, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0 + }, + "langfuse.trace.name": "litellm-acompletion" } -} \ No newline at end of file +} diff --git a/tests/logging_callback_tests/langfuse_expected_request_body/completion_with_bedrock_call.json b/tests/logging_callback_tests/langfuse_expected_request_body/completion_with_bedrock_call.json index dd49d9751f1..58d50ead695 100644 --- a/tests/logging_callback_tests/langfuse_expected_request_body/completion_with_bedrock_call.json +++ b/tests/logging_callback_tests/langfuse_expected_request_body/completion_with_bedrock_call.json @@ -1,85 +1,32 @@ { - "batch": [ - { - "id": "3c9b544f-ef3f-449e-8ec1-763acbb56bec", - "type": "trace-create", - "body": { - "id": "litellm-test-c4c1c850-e8c9-4b16-b5a4-bff2bf9fa4f6", - "timestamp": "2025-05-26T21:13:16.796768Z", - "name": "litellm-acompletion", - "input": { - "messages": [ - { - "role": "user", - "content": "Hello!" - } - ] - }, - "tags": [] - }, - "timestamp": "2025-05-26T21:13:16.796875Z" + "name": "litellm-acompletion", + "parent_span_id": null, + "attributes": { + "langfuse.internal.as_root": true, + "langfuse.observation.cost_details": { + "total": 6e-05 }, - { - "id": "90e6bc70-05d9-4444-8b87-4523a9a54c17", - "type": "generation-create", - "body": { - "traceId": "litellm-test-c4c1c850-e8c9-4b16-b5a4-bff2bf9fa4f6", - "name": "litellm-acompletion", - "startTime": "2025-05-26T14:13:16.469836-07:00", - "metadata": { - "hidden_params": { - "model_id": null, - "cache_key": null, - "api_base": null, - "response_cost": 6e-05, - "additional_headers": {}, - "litellm_overhead_time_ms": null, - "batch_models": null, - "litellm_model_name": "bedrock/us.anthropic.claude-haiku-4-5-20251001-v1:0", - "usage_object": null - }, - "litellm_response_cost": 6e-05, - "cache_hit": false, - "requester_metadata": {} - }, - "input": { - "messages": [ - { - "role": "user", - "content": "Hello!" - } - ] - }, - "level": "DEFAULT", - "id": "time-14-13-16-469836_chatcmpl-3803a9e9-aa68-4493-94d9-247f354830d6", - "endTime": "2025-05-26T14:13:16.795438-07:00", - "completionStartTime": "2025-05-26T14:13:16.795438-07:00", - "model": "bedrock/us.anthropic.claude-haiku-4-5-20251001-v1:0", - "modelParameters": { - "aws_region": "us-east-1" - }, - "usage": { - "input": 10, - "output": 10, - "unit": "TOKENS", - "totalCost": 6e-05 - }, - "usageDetails": { - "input": 10, - "output": 10, - "total": 20, - "cache_creation_input_tokens": 0, - "cache_read_input_tokens": 0 + "langfuse.observation.input": { + "messages": [ + { + "role": "user", + "content": "Hello!" } - }, - "timestamp": "2025-05-26T21:13:16.797156Z" - } - ], - "metadata": { - "batch_size": 2, - "sdk_integration": "litellm", - "sdk_name": "python", - "sdk_version": "2.44.1", - "public_key": "pk-lf-3bfc4db9-217f-48e9-92e0-142566e3c204" + ] + }, + "langfuse.observation.level": "DEFAULT", + "langfuse.observation.model.name": "bedrock/us.anthropic.claude-haiku-4-5-20251001-v1:0", + "langfuse.observation.model.parameters": { + "aws_region": "us-east-1" + }, + "langfuse.observation.type": "generation", + "langfuse.observation.usage_details": { + "input": 10, + "output": 10, + "total": 20, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0 + }, + "langfuse.trace.name": "litellm-acompletion" } -} \ No newline at end of file +} diff --git a/tests/logging_callback_tests/langfuse_expected_request_body/completion_with_complex_metadata.json b/tests/logging_callback_tests/langfuse_expected_request_body/completion_with_complex_metadata.json index 15794de7a07..27f77398978 100644 --- a/tests/logging_callback_tests/langfuse_expected_request_body/completion_with_complex_metadata.json +++ b/tests/logging_callback_tests/langfuse_expected_request_body/completion_with_complex_metadata.json @@ -1,138 +1,39 @@ { - "batch": [ - { - "id": "9ee9100b-c4aa-4e40-a10d-bc189f8b4242", - "type": "trace-create", - "body": { - "id": "litellm-test-c414db10-dd68-406e-9d9e-03839bc2f346", - "timestamp": "2025-01-22T17:27:51.702596Z", - "name": "litellm-acompletion", - "input": { - "messages": [ - { - "role": "user", - "content": "Hello!" - } - ] - }, - "output": { - "content": "Hello! How can I assist you today?", - "role": "assistant", - "tool_calls": null, - "function_call": null, - "provider_specific_fields": null - }, - "tags": [] - }, - "timestamp": "2025-01-22T17:27:51.702716Z" + "name": "litellm-acompletion", + "parent_span_id": null, + "attributes": { + "langfuse.internal.as_root": true, + "langfuse.observation.cost_details": { + "total": 3.5e-05 }, - { - "id": "f8d20489-ed58-429f-b609-87380e223746", - "type": "generation-create", - "body": { - "traceId": "litellm-test-c414db10-dd68-406e-9d9e-03839bc2f346", - "name": "litellm-acompletion", - "startTime": "2025-01-22T09:27:51.150898-08:00", - "metadata": { - "string_value": "hello", - "int_value": 42, - "float_value": 3.14, - "bool_value": true, - "nested_dict": { - "key1": "value1", - "key2": { - "inner_key": "inner_value" - } - }, - "list_value": [ - 1, - 2, - 3 - ], - "set_value": [ - 1, - 2, - 3 - ], - "complex_list": [ - { - "dict_in_list": "value" - }, - "simple_string", - [ - 1, - 2, - 3 - ] - ], - "user": { - "name": "John", - "age": 30, - "tags": [ - "customer", - "active" - ] - }, - "hidden_params": { - "model_id": null, - "cache_key": null, - "api_base": "https://api.openai.com", - "response_cost": 5.4999999999999995e-05, - "additional_headers": {}, - "litellm_overhead_time_ms": null, - "batch_models": null, - "litellm_model_name": "gpt-3.5-turbo", - "usage_object": null - }, - "litellm_response_cost": 5.4999999999999995e-05, - "cache_hit": false, - "requester_metadata": {} - }, - "input": { - "messages": [ - { - "role": "user", - "content": "Hello!" - } - ] - }, - "output": { - "content": "Hello! How can I assist you today?", - "role": "assistant", - "tool_calls": null, - "function_call": null, - "provider_specific_fields": null - }, - "level": "DEFAULT", - "id": "time-09-27-51-150898_chatcmpl-b783291c-dc76-4660-bfef-b79be9d54e57", - "endTime": "2025-01-22T09:27:51.702048-08:00", - "completionStartTime": "2025-01-22T09:27:51.702048-08:00", - "model": "gpt-3.5-turbo", - "modelParameters": { - "extra_body": "{}" - }, - "usage": { - "input": 10, - "output": 20, - "unit": "TOKENS", - "totalCost": 3.5e-05 - }, - "usageDetails": { - "input": 10, - "output": 20, - "total": 30, - "cache_creation_input_tokens": 0, - "cache_read_input_tokens": 0 + "langfuse.observation.input": { + "messages": [ + { + "role": "user", + "content": "Hello!" } - }, - "timestamp": "2025-01-22T17:27:51.703046Z" - } - ], - "metadata": { - "batch_size": 2, - "sdk_integration": "litellm", - "sdk_name": "python", - "sdk_version": "2.44.1", - "public_key": "pk-lf-e02aaea3-8668-4c9f-8c69-771a4ea1f5c9" + ] + }, + "langfuse.observation.level": "DEFAULT", + "langfuse.observation.model.name": "gpt-3.5-turbo", + "langfuse.observation.model.parameters": { + "extra_body": "{}" + }, + "langfuse.observation.output": { + "content": "Hello! How can I assist you today?", + "role": "assistant", + "tool_calls": null, + "function_call": null, + "provider_specific_fields": null + }, + "langfuse.observation.type": "generation", + "langfuse.observation.usage_details": { + "input": 10, + "output": 20, + "total": 30, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0 + }, + "langfuse.trace.name": "litellm-acompletion" } -} \ No newline at end of file +} diff --git a/tests/logging_callback_tests/langfuse_expected_request_body/completion_with_langfuse_metadata.json b/tests/logging_callback_tests/langfuse_expected_request_body/completion_with_langfuse_metadata.json index 8d5d08894ef..0847571c667 100644 --- a/tests/logging_callback_tests/langfuse_expected_request_body/completion_with_langfuse_metadata.json +++ b/tests/logging_callback_tests/langfuse_expected_request_body/completion_with_langfuse_metadata.json @@ -1,116 +1,47 @@ { - "batch": [ - { - "id": "872a0a1c-4328-431b-80b6-fd55a8a44477", - "type": "trace-create", - "body": { - "id": "litellm-test-533ffb2d-a0a3-45b5-911c-7940466cdc8e", - "timestamp": "2025-01-22T17:19:11.234960Z", - "name": "test_trace_name", - "userId": "test_user_id", - "input": { - "messages": [ - { - "role": "user", - "content": "Hello!" - } - ] - }, - "output": { - "content": "Hello! How can I assist you today?", - "role": "assistant", - "tool_calls": null, - "function_call": null, - "provider_specific_fields": null - }, - "sessionId": "test_session_id", - "version": "test_trace_version", - "metadata": { - "test_key": "test_value" - }, - "tags": [ - "test_tag", - "test_tag_2" - ] - }, - "timestamp": "2025-01-22T17:19:11.235169Z" + "name": "test_generation_name", + "parent_span_id": "0d9cfbb24ef808cd", + "attributes": { + "langfuse.observation.cost_details": { + "total": 3.5e-05 }, - { - "id": "18d6f044-e522-4376-96e0-7eec765677ed", - "type": "generation-create", - "body": { - "traceId": "litellm-test-533ffb2d-a0a3-45b5-911c-7940466cdc8e", - "name": "test_generation_name", - "startTime": "2025-01-22T09:19:10.957072-08:00", - "metadata": { - "tags": [ - "test_tag", - "test_tag_2" - ], - "parent_observation_id": "test_parent_observation_id", - "version": "test_version", - "hidden_params": { - "model_id": null, - "cache_key": null, - "api_base": "https://api.openai.com", - "response_cost": 3.5e-05, - "additional_headers": {}, - "litellm_overhead_time_ms": null, - "batch_models": null, - "litellm_model_name": "gpt-3.5-turbo", - "usage_object": null - }, - "litellm_response_cost": 3.5e-05, - "cache_hit": false, - "requester_metadata": {} - }, - "input": { - "messages": [ - { - "role": "user", - "content": "Hello!" - } - ] - }, - "output": { - "content": "Hello! How can I assist you today?", - "role": "assistant", - "tool_calls": null, - "function_call": null, - "provider_specific_fields": null - }, - "level": "DEFAULT", - "parentObservationId": "test_parent_observation_id", - "version": "test_version", - "id": "time-09-19-10-957072_chatcmpl-4da65aba-32e4-400d-aaa2-6bfe096d8141", - "endTime": "2025-01-22T09:19:11.234200-08:00", - "completionStartTime": "2025-01-22T09:19:11.234200-08:00", - "model": "gpt-3.5-turbo", - "modelParameters": { - "extra_body": "{}" - }, - "usage": { - "input": 10, - "output": 20, - "unit": "TOKENS", - "totalCost": 3.5e-05 - }, - "usageDetails": { - "input": 10, - "output": 20, - "total": 30, - "cache_creation_input_tokens": 0, - "cache_read_input_tokens": 0 + "langfuse.observation.input": { + "messages": [ + { + "role": "user", + "content": "Hello!" } - }, - "timestamp": "2025-01-22T17:19:11.235541Z" - } - ], - "metadata": { - "batch_size": 2, - "sdk_integration": "litellm", - "sdk_name": "python", - "sdk_version": "2.44.1", - "public_key": "pk-lf-e02aaea3-8668-4c9f-8c69-771a4ea1f5c9" + ] + }, + "langfuse.observation.level": "DEFAULT", + "langfuse.observation.metadata.test_key": "test_value", + "langfuse.observation.model.name": "gpt-3.5-turbo", + "langfuse.observation.model.parameters": { + "extra_body": "{}" + }, + "langfuse.observation.output": { + "content": "Hello! How can I assist you today?", + "role": "assistant", + "tool_calls": null, + "function_call": null, + "provider_specific_fields": null + }, + "langfuse.observation.type": "generation", + "langfuse.observation.usage_details": { + "input": 10, + "output": 20, + "total": 30, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0 + }, + "langfuse.release": "test_trace_release", + "langfuse.trace.name": "test_trace_name", + "langfuse.trace.tags": [ + "test_tag", + "test_tag_2" + ], + "langfuse.version": "test_trace_version", + "session.id": "test_session_id", + "user.id": "test_user_id" } -} \ No newline at end of file +} diff --git a/tests/logging_callback_tests/langfuse_expected_request_body/completion_with_no_choices.json b/tests/logging_callback_tests/langfuse_expected_request_body/completion_with_no_choices.json index ff8419ee392..c1fd8b824d5 100644 --- a/tests/logging_callback_tests/langfuse_expected_request_body/completion_with_no_choices.json +++ b/tests/logging_callback_tests/langfuse_expected_request_body/completion_with_no_choices.json @@ -1,85 +1,32 @@ { - "batch": [ - { - "id": "1f1d7517-4602-4c59-a322-7fc0306f1b7a", - "type": "trace-create", - "body": { - "id": "litellm-test-dbadfdfc-f4e7-4f05-8992-984c37359166", - "timestamp": "2025-02-07T00:23:27.669634Z", - "name": "litellm-acompletion", - "input": { - "messages": [ - { - "role": "user", - "content": "Hello!" - } - ] - }, - "tags": [] - }, - "timestamp": "2025-02-07T00:23:27.669809Z" + "name": "litellm-acompletion", + "parent_span_id": null, + "attributes": { + "langfuse.internal.as_root": true, + "langfuse.observation.cost_details": { + "total": 1.9999999999999998e-05 }, - { - "id": "fbe610b6-f500-4c7d-8e34-d40a0e8c487b", - "type": "generation-create", - "body": { - "traceId": "litellm-test-dbadfdfc-f4e7-4f05-8992-984c37359166", - "name": "litellm-acompletion", - "startTime": "2025-02-06T16:23:27.220129-08:00", - "metadata": { - "hidden_params": { - "model_id": null, - "cache_key": null, - "api_base": "https://api.openai.com", - "response_cost": 3.5e-05, - "additional_headers": {}, - "litellm_overhead_time_ms": null, - "batch_models": null, - "litellm_model_name": "gpt-3.5-turbo", - "usage_object": null - }, - "litellm_response_cost": 3.5e-05, - "cache_hit": false, - "requester_metadata": {} - }, - "input": { - "messages": [ - { - "role": "user", - "content": "Hello!" - } - ] - }, - "level": "DEFAULT", - "id": "time-16-23-27-220129_chatcmpl-565360d7-965f-4533-9c09-db789af77a7d", - "endTime": "2025-02-06T16:23:27.644253-08:00", - "completionStartTime": "2025-02-06T16:23:27.644253-08:00", - "model": "gpt-3.5-turbo", - "modelParameters": { - "extra_body": "{}" - }, - "usage": { - "input": 10, - "output": 10, - "unit": "TOKENS", - "totalCost": 1.9999999999999998e-05 - }, - "usageDetails": { - "input": 10, - "output": 10, - "total": 20, - "cache_creation_input_tokens": 0, - "cache_read_input_tokens": 0 + "langfuse.observation.input": { + "messages": [ + { + "role": "user", + "content": "Hello!" } - }, - "timestamp": "2025-02-07T00:23:27.670175Z" - } - ], - "metadata": { - "batch_size": 2, - "sdk_integration": "litellm", - "sdk_name": "python", - "sdk_version": "2.44.1", - "public_key": "pk-lf-e02aaea3-8668-4c9f-8c69-771a4ea1f5c9" + ] + }, + "langfuse.observation.level": "DEFAULT", + "langfuse.observation.model.name": "gpt-3.5-turbo", + "langfuse.observation.model.parameters": { + "extra_body": "{}" + }, + "langfuse.observation.type": "generation", + "langfuse.observation.usage_details": { + "input": 10, + "output": 10, + "total": 20, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0 + }, + "langfuse.trace.name": "litellm-acompletion" } -} \ No newline at end of file +} diff --git a/tests/logging_callback_tests/langfuse_expected_request_body/completion_with_router.json b/tests/logging_callback_tests/langfuse_expected_request_body/completion_with_router.json index df99b11d26b..c5b4a9a51fc 100644 --- a/tests/logging_callback_tests/langfuse_expected_request_body/completion_with_router.json +++ b/tests/logging_callback_tests/langfuse_expected_request_body/completion_with_router.json @@ -1,95 +1,34 @@ { - "batch": [ - { - "id": "45eb9b25-605c-4c4a-b2b3-8241e079cd31", - "type": "trace-create", - "body": { - "id": "litellm-test-32702f3d-8a1c-4912-a3d6-286e59a9c568", - "timestamp": "2025-05-24T17:01:19.408179Z", - "name": "litellm-acompletion", - "input": { - "messages": [ - { - "role": "user", - "content": "Hello!" - } - ] - }, - "tags": [] - }, - "timestamp": "2025-05-24T17:01:19.408284Z" + "name": "litellm-acompletion", + "parent_span_id": null, + "attributes": { + "langfuse.internal.as_root": true, + "langfuse.observation.cost_details": { + "total": 1.9999999999999998e-05 }, - { - "id": "9f5e9b7d-0cea-4776-b4b9-5c2e8f4bad3c", - "type": "generation-create", - "body": { - "traceId": "litellm-test-32702f3d-8a1c-4912-a3d6-286e59a9c568", - "name": "litellm-acompletion", - "startTime": "2025-05-24T10:01:19.142356-07:00", - "metadata": { - "model_group": "gpt-3.5-turbo", - "model_group_size": 1, - "deployment": "gpt-3.5-turbo", - "model_info": { - "id": "0f1cd8f9e6a22e499303d479486395563ea04decade83fe7334dc2f079a857c2", - "db_model": false - }, - "api_base": null, - "hidden_params": { - "model_id": "0f1cd8f9e6a22e499303d479486395563ea04decade83fe7334dc2f079a857c2", - "cache_key": null, - "api_base": "https://api.openai.com", - "response_cost": 3.5e-05, - "additional_headers": {}, - "litellm_overhead_time_ms": null, - "batch_models": null, - "litellm_model_name": "gpt-3.5-turbo", - "usage_object": null - }, - "litellm_response_cost": 3.5e-05, - "cache_hit": false, - "requester_metadata": {} - }, - "input": { - "messages": [ - { - "role": "user", - "content": "Hello!" - } - ] - }, - "level": "DEFAULT", - "id": "time-10-01-19-142356_chatcmpl-16b215b7-e51e-47b0-8fe5-9dd6f226fda1", - "endTime": "2025-05-24T10:01:19.406531-07:00", - "completionStartTime": "2025-05-24T10:01:19.406531-07:00", - "model": "gpt-3.5-turbo", - "modelParameters": { - "stream": false, - "max_retries": 0, - "extra_body": "{}" - }, - "usage": { - "input": 10, - "output": 10, - "unit": "TOKENS", - "totalCost": 1.9999999999999998e-05 - }, - "usageDetails": { - "input": 10, - "output": 10, - "total": 20, - "cache_creation_input_tokens": 0, - "cache_read_input_tokens": 0 + "langfuse.observation.input": { + "messages": [ + { + "role": "user", + "content": "Hello!" } - }, - "timestamp": "2025-05-24T17:01:19.408586Z" - } - ], - "metadata": { - "batch_size": 2, - "sdk_integration": "litellm", - "sdk_name": "python", - "sdk_version": "2.44.1", - "public_key": "pk-lf-3bfc4db9-217f-48e9-92e0-142566e3c204" + ] + }, + "langfuse.observation.level": "DEFAULT", + "langfuse.observation.model.name": "gpt-3.5-turbo", + "langfuse.observation.model.parameters": { + "stream": false, + "max_retries": 0, + "extra_body": "{}" + }, + "langfuse.observation.type": "generation", + "langfuse.observation.usage_details": { + "input": 10, + "output": 10, + "total": 20, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0 + }, + "langfuse.trace.name": "litellm-acompletion" } -} \ No newline at end of file +} diff --git a/tests/logging_callback_tests/langfuse_expected_request_body/completion_with_tags.json b/tests/logging_callback_tests/langfuse_expected_request_body/completion_with_tags.json index fd3d3194a5b..beb1f32fe06 100644 --- a/tests/logging_callback_tests/langfuse_expected_request_body/completion_with_tags.json +++ b/tests/logging_callback_tests/langfuse_expected_request_body/completion_with_tags.json @@ -1,106 +1,43 @@ { - "batch": [ - { - "id": "42be960a-5dde-47df-9cbc-1fdd0fdcaa7d", - "type": "trace-create", - "body": { - "id": "litellm-test-f3ab679b-1e1d-43fd-9a9a-f11287aeb339", - "timestamp": "2025-01-22T15:31:28.963419Z", - "name": "litellm-acompletion", - "input": { - "messages": [ - { - "role": "user", - "content": "Hello!" - } - ] - }, - "output": { - "content": "Hello! How can I assist you today?", - "role": "assistant", - "tool_calls": null, - "function_call": null, - "provider_specific_fields": null - }, - "tags": [ - "test_tag", - "test_tag_2" - ] - }, - "timestamp": "2025-01-22T15:31:28.963706Z" + "name": "litellm-acompletion", + "parent_span_id": null, + "attributes": { + "langfuse.internal.as_root": true, + "langfuse.observation.cost_details": { + "total": 3.5e-05 }, - { - "id": "5486df5a-3776-4adf-abd0-bd22e51f7fb4", - "type": "generation-create", - "body": { - "traceId": "litellm-test-f3ab679b-1e1d-43fd-9a9a-f11287aeb339", - "name": "litellm-acompletion", - "startTime": "2025-01-22T07:31:28.960749-08:00", - "metadata": { - "tags": [ - "test_tag", - "test_tag_2" - ], - "hidden_params": { - "model_id": null, - "cache_key": null, - "api_base": "https://api.openai.com", - "response_cost": 5.4999999999999995e-05, - "additional_headers": {}, - "litellm_overhead_time_ms": null, - "batch_models": null, - "litellm_model_name": "gpt-3.5-turbo", - "usage_object": null - }, - "litellm_response_cost": 5.4999999999999995e-05, - "cache_hit": false, - "requester_metadata": {} - }, - "input": { - "messages": [ - { - "role": "user", - "content": "Hello!" - } - ] - }, - "output": { - "content": "Hello! How can I assist you today?", - "role": "assistant", - "tool_calls": null, - "function_call": null, - "provider_specific_fields": null - }, - "level": "DEFAULT", - "id": "time-07-31-28-960749_chatcmpl-f06338f0-8c49-45d8-be35-2854a89723c1", - "endTime": "2025-01-22T07:31:28.962389-08:00", - "completionStartTime": "2025-01-22T07:31:28.962389-08:00", - "model": "gpt-3.5-turbo", - "modelParameters": { - "extra_body": "{}" - }, - "usage": { - "input": 10, - "output": 20, - "unit": "TOKENS", - "totalCost": 3.5e-05 - }, - "usageDetails": { - "input": 10, - "output": 20, - "total": 30, - "cache_creation_input_tokens": 0, - "cache_read_input_tokens": 0 + "langfuse.observation.input": { + "messages": [ + { + "role": "user", + "content": "Hello!" } - }, - "timestamp": "2025-01-22T15:31:28.964179Z" - } - ], - "metadata": { - "batch_size": 2, - "sdk_integration": "litellm", - "sdk_name": "python", - "sdk_version": "2.44.1", - "public_key": "pk-lf-e02aaea3-8668-4c9f-8c69-771a4ea1f5c9" + ] + }, + "langfuse.observation.level": "DEFAULT", + "langfuse.observation.model.name": "gpt-3.5-turbo", + "langfuse.observation.model.parameters": { + "extra_body": "{}" + }, + "langfuse.observation.output": { + "content": "Hello! How can I assist you today?", + "role": "assistant", + "tool_calls": null, + "function_call": null, + "provider_specific_fields": null + }, + "langfuse.observation.type": "generation", + "langfuse.observation.usage_details": { + "input": 10, + "output": 20, + "total": 30, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0 + }, + "langfuse.trace.name": "litellm-acompletion", + "langfuse.trace.tags": [ + "test_tag", + "test_tag_2" + ] } -} \ No newline at end of file +} diff --git a/tests/logging_callback_tests/langfuse_expected_request_body/completion_with_tags_stream.json b/tests/logging_callback_tests/langfuse_expected_request_body/completion_with_tags_stream.json index af15f351189..e8d4769b0ec 100644 --- a/tests/logging_callback_tests/langfuse_expected_request_body/completion_with_tags_stream.json +++ b/tests/logging_callback_tests/langfuse_expected_request_body/completion_with_tags_stream.json @@ -1,106 +1,43 @@ { - "batch": [ - { - "id": "06b8fa9f-151b-4e74-9fbf-8af5222a7f40", - "type": "trace-create", - "body": { - "id": "litellm-test-54368a51-a382-493c-b0a8-3f1af23e18c4", - "timestamp": "2025-01-22T16:38:26.016582Z", - "name": "litellm-acompletion", - "input": { - "messages": [ - { - "role": "user", - "content": "Hello!" - } - ] - }, - "output": { - "content": "Hello! How can I assist you today?", - "role": "assistant", - "tool_calls": null, - "function_call": null, - "provider_specific_fields": null - }, - "tags": [ - "test_tag_stream", - "test_tag_2_stream" - ] - }, - "timestamp": "2025-01-22T16:38:26.016828Z" + "name": "litellm-acompletion", + "parent_span_id": null, + "attributes": { + "langfuse.internal.as_root": true, + "langfuse.observation.cost_details": { + "total": 3.5e-05 }, - { - "id": "4ca1fd78-53e3-41b5-95d9-417b09e3f0eb", - "type": "generation-create", - "body": { - "traceId": "litellm-test-54368a51-a382-493c-b0a8-3f1af23e18c4", - "name": "litellm-acompletion", - "startTime": "2025-01-22T08:38:25.665692-08:00", - "metadata": { - "tags": [ - "test_tag_stream", - "test_tag_2_stream" - ], - "hidden_params": { - "model_id": null, - "cache_key": null, - "api_base": "https://api.openai.com", - "response_cost": 5.4999999999999995e-05, - "additional_headers": {}, - "litellm_overhead_time_ms": null, - "batch_models": null, - "litellm_model_name": "gpt-3.5-turbo", - "usage_object": null - }, - "litellm_response_cost": 5.4999999999999995e-05, - "cache_hit": false, - "requester_metadata": {} - }, - "input": { - "messages": [ - { - "role": "user", - "content": "Hello!" - } - ] - }, - "output": { - "content": "Hello! How can I assist you today?", - "role": "assistant", - "tool_calls": null, - "function_call": null, - "provider_specific_fields": null - }, - "level": "DEFAULT", - "id": "time-08-38-25-665692_chatcmpl-8b67ffb8-4326-4e1b-bf4a-f70930c11c00", - "endTime": "2025-01-22T08:38:26.015666-08:00", - "completionStartTime": "2025-01-22T08:38:26.015666-08:00", - "model": "gpt-3.5-turbo", - "modelParameters": { - "extra_body": "{}" - }, - "usage": { - "input": 10, - "output": 20, - "unit": "TOKENS", - "totalCost": 3.5e-05 - }, - "usageDetails": { - "input": 10, - "output": 20, - "total": 30, - "cache_creation_input_tokens": 0, - "cache_read_input_tokens": 0 + "langfuse.observation.input": { + "messages": [ + { + "role": "user", + "content": "Hello!" } - }, - "timestamp": "2025-01-22T16:38:26.017252Z" - } - ], - "metadata": { - "batch_size": 2, - "sdk_integration": "litellm", - "sdk_name": "python", - "sdk_version": "2.44.1", - "public_key": "pk-lf-e02aaea3-8668-4c9f-8c69-771a4ea1f5c9" + ] + }, + "langfuse.observation.level": "DEFAULT", + "langfuse.observation.model.name": "gpt-3.5-turbo", + "langfuse.observation.model.parameters": { + "extra_body": "{}" + }, + "langfuse.observation.output": { + "content": "Hello! How can I assist you today?", + "role": "assistant", + "tool_calls": null, + "function_call": null, + "provider_specific_fields": null + }, + "langfuse.observation.type": "generation", + "langfuse.observation.usage_details": { + "input": 10, + "output": 20, + "total": 30, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0 + }, + "langfuse.trace.name": "litellm-acompletion", + "langfuse.trace.tags": [ + "test_tag_stream", + "test_tag_2_stream" + ] } -} \ No newline at end of file +} diff --git a/tests/logging_callback_tests/langfuse_expected_request_body/completion_with_vertex_call.json b/tests/logging_callback_tests/langfuse_expected_request_body/completion_with_vertex_call.json index b6c11f96953..f5eff2774e0 100644 --- a/tests/logging_callback_tests/langfuse_expected_request_body/completion_with_vertex_call.json +++ b/tests/logging_callback_tests/langfuse_expected_request_body/completion_with_vertex_call.json @@ -1,83 +1,30 @@ { - "batch": [ - { - "id": "7d33d536-2730-4815-8957-80866c09c053", - "type": "trace-create", - "body": { - "id": "litellm-test-72861437-ff5b-4c48-89c0-a143534d9e7a", - "timestamp": "2025-05-26T21:15:40.610459Z", - "name": "litellm-acompletion", - "input": { - "messages": [ - { - "role": "user", - "content": "Hello!" - } - ] - }, - "tags": [] - }, - "timestamp": "2025-05-26T21:15:40.610603Z" + "name": "litellm-acompletion", + "parent_span_id": null, + "attributes": { + "langfuse.internal.as_root": true, + "langfuse.observation.cost_details": { + "total": 7.5e-06 }, - { - "id": "ebb5079c-7726-4adb-9616-e1862735e1d8", - "type": "generation-create", - "body": { - "traceId": "litellm-test-72861437-ff5b-4c48-89c0-a143534d9e7a", - "name": "litellm-acompletion", - "startTime": "2025-05-26T14:15:40.349639-07:00", - "metadata": { - "hidden_params": { - "model_id": null, - "cache_key": null, - "api_base": null, - "response_cost": 7.5e-06, - "additional_headers": {}, - "litellm_overhead_time_ms": null, - "batch_models": null, - "litellm_model_name": "vertex_ai/gemini-2.0-flash-001", - "usage_object": null - }, - "litellm_response_cost": 7.5e-06, - "cache_hit": false, - "requester_metadata": {} - }, - "input": { - "messages": [ - { - "role": "user", - "content": "Hello!" - } - ] - }, - "level": "DEFAULT", - "id": "time-14-15-40-349639_chatcmpl-59a988d0-7ef1-4dc4-bc18-d2e78961817f", - "endTime": "2025-05-26T14:15:40.607266-07:00", - "completionStartTime": "2025-05-26T14:15:40.607266-07:00", - "model": "gemini-2.0-flash-001", - "modelParameters": {}, - "usage": { - "input": 10, - "output": 10, - "unit": "TOKENS", - "totalCost": 7.5e-06 - }, - "usageDetails": { - "input": 10, - "output": 10, - "total": 20, - "cache_creation_input_tokens": 0, - "cache_read_input_tokens": 0 + "langfuse.observation.input": { + "messages": [ + { + "role": "user", + "content": "Hello!" } - }, - "timestamp": "2025-05-26T21:15:40.610953Z" - } - ], - "metadata": { - "batch_size": 2, - "sdk_integration": "litellm", - "sdk_name": "python", - "sdk_version": "2.44.1", - "public_key": "pk-lf-3bfc4db9-217f-48e9-92e0-142566e3c204" + ] + }, + "langfuse.observation.level": "DEFAULT", + "langfuse.observation.model.name": "gemini-2.0-flash-001", + "langfuse.observation.model.parameters": {}, + "langfuse.observation.type": "generation", + "langfuse.observation.usage_details": { + "input": 10, + "output": 10, + "total": 20, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0 + }, + "langfuse.trace.name": "litellm-acompletion" } -} \ No newline at end of file +} diff --git a/tests/logging_callback_tests/langfuse_expected_request_body/complex_metadata.json b/tests/logging_callback_tests/langfuse_expected_request_body/complex_metadata.json index 82a115a0899..27f77398978 100644 --- a/tests/logging_callback_tests/langfuse_expected_request_body/complex_metadata.json +++ b/tests/logging_callback_tests/langfuse_expected_request_body/complex_metadata.json @@ -1,113 +1,39 @@ { - "batch": [ - { - "id": "ddf567e5-a1b5-4e38-8a7c-f48bc847f721", - "type": "trace-create", - "body": { - "id": "litellm-test-46551fc7-c916-4a83-aeef-4274b5582ce1", - "timestamp": "2025-01-22T17:59:39.367430Z", - "name": "litellm-acompletion", - "input": { - "messages": [ - { - "role": "user", - "content": "Hello!" - } - ] - }, - "output": { - "content": "Hello! How can I assist you today?", - "role": "assistant", - "tool_calls": null, - "function_call": null, - "provider_specific_fields": null - }, - "tags": [] - }, - "timestamp": "2025-01-22T17:59:39.367707Z" + "name": "litellm-acompletion", + "parent_span_id": null, + "attributes": { + "langfuse.internal.as_root": true, + "langfuse.observation.cost_details": { + "total": 3.5e-05 }, - { - "id": "d3eb2c9e-e123-419d-b27b-c8283a505ae8", - "type": "generation-create", - "body": { - "traceId": "litellm-test-46551fc7-c916-4a83-aeef-4274b5582ce1", - "name": "litellm-acompletion", - "startTime": "2025-01-22T09:59:39.362554-08:00", - "metadata": { - "int": 42, - "str": "hello", - "list": [ - 1, - 2, - 3 - ], - "set": [ - 4, - 5 - ], - "dict": { - "nested": "value" - }, - "hidden_params": { - "model_id": null, - "cache_key": null, - "api_base": "https://api.openai.com", - "response_cost": 5.4999999999999995e-05, - "additional_headers": {}, - "litellm_overhead_time_ms": null, - "batch_models": null, - "litellm_model_name": "gpt-3.5-turbo", - "usage_object": null - }, - "litellm_response_cost": 5.4999999999999995e-05, - "cache_hit": false, - "requester_metadata": {} - }, - "input": { - "messages": [ - { - "role": "user", - "content": "Hello!" - } - ] - }, - "output": { - "content": "Hello! How can I assist you today?", - "role": "assistant", - "tool_calls": null, - "function_call": null, - "provider_specific_fields": null - }, - "level": "DEFAULT", - "id": "time-09-59-39-362554_chatcmpl-d20ba1d9-cda6-4773-822e-921ebcd426a0", - "endTime": "2025-01-22T09:59:39.365756-08:00", - "completionStartTime": "2025-01-22T09:59:39.365756-08:00", - "model": "gpt-3.5-turbo", - "modelParameters": { - "extra_body": "{}" - }, - "usage": { - "input": 10, - "output": 20, - "unit": "TOKENS", - "totalCost": 3.5e-05 - }, - "usageDetails": { - "input": 10, - "output": 20, - "total": 30, - "cache_creation_input_tokens": 0, - "cache_read_input_tokens": 0 + "langfuse.observation.input": { + "messages": [ + { + "role": "user", + "content": "Hello!" } - }, - "timestamp": "2025-01-22T17:59:39.368310Z" - } - ], - "metadata": { - "batch_size": 2, - "sdk_integration": "litellm", - "sdk_name": "python", - "sdk_version": "2.44.1", - "public_key": "pk-lf-e02aaea3-8668-4c9f-8c69-771a4ea1f5c9" + ] + }, + "langfuse.observation.level": "DEFAULT", + "langfuse.observation.model.name": "gpt-3.5-turbo", + "langfuse.observation.model.parameters": { + "extra_body": "{}" + }, + "langfuse.observation.output": { + "content": "Hello! How can I assist you today?", + "role": "assistant", + "tool_calls": null, + "function_call": null, + "provider_specific_fields": null + }, + "langfuse.observation.type": "generation", + "langfuse.observation.usage_details": { + "input": 10, + "output": 20, + "total": 30, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0 + }, + "langfuse.trace.name": "litellm-acompletion" } -} \ No newline at end of file +} diff --git a/tests/logging_callback_tests/langfuse_expected_request_body/complex_metadata_2.json b/tests/logging_callback_tests/langfuse_expected_request_body/complex_metadata_2.json index 33e6b01bee3..27f77398978 100644 --- a/tests/logging_callback_tests/langfuse_expected_request_body/complex_metadata_2.json +++ b/tests/logging_callback_tests/langfuse_expected_request_body/complex_metadata_2.json @@ -1,105 +1,39 @@ { - "batch": [ - { - "id": "ea3d694a-ce6b-417e-86e3-23ac17c6f6c6", - "type": "trace-create", - "body": { - "id": "litellm-test-38dcf290-8742-4fc5-ad03-c5d47e91dec0", - "timestamp": "2025-01-22T18:06:50.959206Z", - "name": "litellm-acompletion", - "input": { - "messages": [ - { - "role": "user", - "content": "Hello!" - } - ] - }, - "output": { - "content": "Hello! How can I assist you today?", - "role": "assistant", - "tool_calls": null, - "function_call": null, - "provider_specific_fields": null - }, - "tags": [] - }, - "timestamp": "2025-01-22T18:06:50.959409Z" + "name": "litellm-acompletion", + "parent_span_id": null, + "attributes": { + "langfuse.internal.as_root": true, + "langfuse.observation.cost_details": { + "total": 3.5e-05 }, - { - "id": "5fe03133-5798-4f87-8eec-ae0264f1eccc", - "type": "generation-create", - "body": { - "traceId": "litellm-test-38dcf290-8742-4fc5-ad03-c5d47e91dec0", - "name": "litellm-acompletion", - "startTime": "2025-01-22T10:06:50.957097-08:00", - "metadata": { - "list": [ - "list", - "not", - "a", - "dict" - ], - "hidden_params": { - "model_id": null, - "cache_key": null, - "api_base": "https://api.openai.com", - "response_cost": 5.4999999999999995e-05, - "additional_headers": {}, - "litellm_overhead_time_ms": null, - "batch_models": null, - "litellm_model_name": "gpt-3.5-turbo", - "usage_object": null - }, - "litellm_response_cost": 5.4999999999999995e-05, - "cache_hit": false, - "requester_metadata": {} - }, - "input": { - "messages": [ - { - "role": "user", - "content": "Hello!" - } - ] - }, - "output": { - "content": "Hello! How can I assist you today?", - "role": "assistant", - "tool_calls": null, - "function_call": null, - "provider_specific_fields": null - }, - "level": "DEFAULT", - "id": "time-10-06-50-957097_chatcmpl-62d4ad7c-291b-4fc7-a8a4-3ed0fc3912a5", - "endTime": "2025-01-22T10:06:50.958374-08:00", - "completionStartTime": "2025-01-22T10:06:50.958374-08:00", - "model": "gpt-3.5-turbo", - "modelParameters": { - "extra_body": "{}" - }, - "usage": { - "input": 10, - "output": 20, - "unit": "TOKENS", - "totalCost": 3.5e-05 - }, - "usageDetails": { - "input": 10, - "output": 20, - "total": 30, - "cache_creation_input_tokens": 0, - "cache_read_input_tokens": 0 + "langfuse.observation.input": { + "messages": [ + { + "role": "user", + "content": "Hello!" } - }, - "timestamp": "2025-01-22T18:06:50.959850Z" - } - ], - "metadata": { - "batch_size": 2, - "sdk_integration": "litellm", - "sdk_name": "python", - "sdk_version": "2.44.1", - "public_key": "pk-lf-e02aaea3-8668-4c9f-8c69-771a4ea1f5c9" + ] + }, + "langfuse.observation.level": "DEFAULT", + "langfuse.observation.model.name": "gpt-3.5-turbo", + "langfuse.observation.model.parameters": { + "extra_body": "{}" + }, + "langfuse.observation.output": { + "content": "Hello! How can I assist you today?", + "role": "assistant", + "tool_calls": null, + "function_call": null, + "provider_specific_fields": null + }, + "langfuse.observation.type": "generation", + "langfuse.observation.usage_details": { + "input": 10, + "output": 20, + "total": 30, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0 + }, + "langfuse.trace.name": "litellm-acompletion" } -} \ No newline at end of file +} diff --git a/tests/logging_callback_tests/langfuse_expected_request_body/empty_metadata.json b/tests/logging_callback_tests/langfuse_expected_request_body/empty_metadata.json index f4040f1f8fc..27f77398978 100644 --- a/tests/logging_callback_tests/langfuse_expected_request_body/empty_metadata.json +++ b/tests/logging_callback_tests/langfuse_expected_request_body/empty_metadata.json @@ -1,99 +1,39 @@ { - "batch": [ - { - "id": "28d0c943-284b-4151-bf0d-8acf0f449865", - "type": "trace-create", - "body": { - "id": "litellm-test-d9506624-457c-40bc-9a37-578b896fa22a", - "timestamp": "2025-01-22T17:59:32.888622Z", - "name": "litellm-acompletion", - "input": { - "messages": [ - { - "role": "user", - "content": "Hello!" - } - ] - }, - "output": { - "content": "Hello! How can I assist you today?", - "role": "assistant", - "tool_calls": null, - "function_call": null, - "provider_specific_fields": null - }, - "tags": [] - }, - "timestamp": "2025-01-22T17:59:32.888940Z" + "name": "litellm-acompletion", + "parent_span_id": null, + "attributes": { + "langfuse.internal.as_root": true, + "langfuse.observation.cost_details": { + "total": 3.5e-05 }, - { - "id": "384e9fb4-3516-47b2-a4ae-1666337ec4a7", - "type": "generation-create", - "body": { - "traceId": "litellm-test-d9506624-457c-40bc-9a37-578b896fa22a", - "name": "litellm-acompletion", - "startTime": "2025-01-22T09:59:32.878577-08:00", - "metadata": { - "hidden_params": { - "model_id": null, - "cache_key": null, - "api_base": "https://api.openai.com", - "response_cost": 5.4999999999999995e-05, - "additional_headers": {}, - "litellm_overhead_time_ms": null, - "batch_models": null, - "litellm_model_name": "gpt-3.5-turbo", - "usage_object": null - }, - "litellm_response_cost": 5.4999999999999995e-05, - "cache_hit": false, - "requester_metadata": {} - }, - "input": { - "messages": [ - { - "role": "user", - "content": "Hello!" - } - ] - }, - "output": { - "content": "Hello! How can I assist you today?", - "role": "assistant", - "tool_calls": null, - "function_call": null, - "provider_specific_fields": null - }, - "level": "DEFAULT", - "id": "time-09-59-32-878577_chatcmpl-1195f870-fd4d-4e38-8dc8-99dd3da5ab0b", - "endTime": "2025-01-22T09:59:32.880691-08:00", - "completionStartTime": "2025-01-22T09:59:32.880691-08:00", - "model": "gpt-3.5-turbo", - "modelParameters": { - "extra_body": "{}" - }, - "usage": { - "input": 10, - "output": 20, - "unit": "TOKENS", - "totalCost": 3.5e-05 - }, - "usageDetails": { - "input": 10, - "output": 20, - "total": 30, - "cache_creation_input_tokens": 0, - "cache_read_input_tokens": 0 + "langfuse.observation.input": { + "messages": [ + { + "role": "user", + "content": "Hello!" } - }, - "timestamp": "2025-01-22T17:59:32.889548Z" - } - ], - "metadata": { - "batch_size": 2, - "sdk_integration": "litellm", - "sdk_name": "python", - "sdk_version": "2.44.1", - "public_key": "pk-lf-e02aaea3-8668-4c9f-8c69-771a4ea1f5c9" + ] + }, + "langfuse.observation.level": "DEFAULT", + "langfuse.observation.model.name": "gpt-3.5-turbo", + "langfuse.observation.model.parameters": { + "extra_body": "{}" + }, + "langfuse.observation.output": { + "content": "Hello! How can I assist you today?", + "role": "assistant", + "tool_calls": null, + "function_call": null, + "provider_specific_fields": null + }, + "langfuse.observation.type": "generation", + "langfuse.observation.usage_details": { + "input": 10, + "output": 20, + "total": 30, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0 + }, + "langfuse.trace.name": "litellm-acompletion" } -} \ No newline at end of file +} diff --git a/tests/logging_callback_tests/langfuse_expected_request_body/metadata_with_function.json b/tests/logging_callback_tests/langfuse_expected_request_body/metadata_with_function.json index 77ca252c86d..27f77398978 100644 --- a/tests/logging_callback_tests/langfuse_expected_request_body/metadata_with_function.json +++ b/tests/logging_callback_tests/langfuse_expected_request_body/metadata_with_function.json @@ -1,99 +1,39 @@ { - "batch": [ - { - "id": "88b1898a-cc5d-4e8e-93bc-3e71300c5e8d", - "type": "trace-create", - "body": { - "id": "litellm-test-a46356d9-ecff-44c8-a3da-fed3588b5128", - "timestamp": "2025-01-22T17:59:36.162545Z", - "name": "litellm-acompletion", - "input": { - "messages": [ - { - "role": "user", - "content": "Hello!" - } - ] - }, - "output": { - "content": "Hello! How can I assist you today?", - "role": "assistant", - "tool_calls": null, - "function_call": null, - "provider_specific_fields": null - }, - "tags": [] - }, - "timestamp": "2025-01-22T17:59:36.162702Z" + "name": "litellm-acompletion", + "parent_span_id": null, + "attributes": { + "langfuse.internal.as_root": true, + "langfuse.observation.cost_details": { + "total": 3.5e-05 }, - { - "id": "96bb77a6-a350-431b-bfd8-425491259728", - "type": "generation-create", - "body": { - "traceId": "litellm-test-a46356d9-ecff-44c8-a3da-fed3588b5128", - "name": "litellm-acompletion", - "startTime": "2025-01-22T09:59:36.161090-08:00", - "metadata": { - "hidden_params": { - "model_id": null, - "cache_key": null, - "api_base": "https://api.openai.com", - "response_cost": 5.4999999999999995e-05, - "additional_headers": {}, - "litellm_overhead_time_ms": null, - "batch_models": null, - "litellm_model_name": "gpt-3.5-turbo", - "usage_object": null - }, - "litellm_response_cost": 5.4999999999999995e-05, - "cache_hit": false, - "requester_metadata": {} - }, - "input": { - "messages": [ - { - "role": "user", - "content": "Hello!" - } - ] - }, - "output": { - "content": "Hello! How can I assist you today?", - "role": "assistant", - "tool_calls": null, - "function_call": null, - "provider_specific_fields": null - }, - "level": "DEFAULT", - "id": "time-09-59-36-161090_chatcmpl-1ee988c9-9133-4655-bbe4-b97ffb6e3dc9", - "endTime": "2025-01-22T09:59:36.161959-08:00", - "completionStartTime": "2025-01-22T09:59:36.161959-08:00", - "model": "gpt-3.5-turbo", - "modelParameters": { - "extra_body": "{}" - }, - "usage": { - "input": 10, - "output": 20, - "unit": "TOKENS", - "totalCost": 3.5e-05 - }, - "usageDetails": { - "input": 10, - "output": 20, - "total": 30, - "cache_creation_input_tokens": 0, - "cache_read_input_tokens": 0 + "langfuse.observation.input": { + "messages": [ + { + "role": "user", + "content": "Hello!" } - }, - "timestamp": "2025-01-22T17:59:36.162997Z" - } - ], - "metadata": { - "batch_size": 2, - "sdk_integration": "litellm", - "sdk_name": "python", - "sdk_version": "2.44.1", - "public_key": "pk-lf-e02aaea3-8668-4c9f-8c69-771a4ea1f5c9" + ] + }, + "langfuse.observation.level": "DEFAULT", + "langfuse.observation.model.name": "gpt-3.5-turbo", + "langfuse.observation.model.parameters": { + "extra_body": "{}" + }, + "langfuse.observation.output": { + "content": "Hello! How can I assist you today?", + "role": "assistant", + "tool_calls": null, + "function_call": null, + "provider_specific_fields": null + }, + "langfuse.observation.type": "generation", + "langfuse.observation.usage_details": { + "input": 10, + "output": 20, + "total": 30, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0 + }, + "langfuse.trace.name": "litellm-acompletion" } -} \ No newline at end of file +} diff --git a/tests/logging_callback_tests/langfuse_expected_request_body/metadata_with_lock.json b/tests/logging_callback_tests/langfuse_expected_request_body/metadata_with_lock.json index f4040f1f8fc..27f77398978 100644 --- a/tests/logging_callback_tests/langfuse_expected_request_body/metadata_with_lock.json +++ b/tests/logging_callback_tests/langfuse_expected_request_body/metadata_with_lock.json @@ -1,99 +1,39 @@ { - "batch": [ - { - "id": "28d0c943-284b-4151-bf0d-8acf0f449865", - "type": "trace-create", - "body": { - "id": "litellm-test-d9506624-457c-40bc-9a37-578b896fa22a", - "timestamp": "2025-01-22T17:59:32.888622Z", - "name": "litellm-acompletion", - "input": { - "messages": [ - { - "role": "user", - "content": "Hello!" - } - ] - }, - "output": { - "content": "Hello! How can I assist you today?", - "role": "assistant", - "tool_calls": null, - "function_call": null, - "provider_specific_fields": null - }, - "tags": [] - }, - "timestamp": "2025-01-22T17:59:32.888940Z" + "name": "litellm-acompletion", + "parent_span_id": null, + "attributes": { + "langfuse.internal.as_root": true, + "langfuse.observation.cost_details": { + "total": 3.5e-05 }, - { - "id": "384e9fb4-3516-47b2-a4ae-1666337ec4a7", - "type": "generation-create", - "body": { - "traceId": "litellm-test-d9506624-457c-40bc-9a37-578b896fa22a", - "name": "litellm-acompletion", - "startTime": "2025-01-22T09:59:32.878577-08:00", - "metadata": { - "hidden_params": { - "model_id": null, - "cache_key": null, - "api_base": "https://api.openai.com", - "response_cost": 5.4999999999999995e-05, - "additional_headers": {}, - "litellm_overhead_time_ms": null, - "batch_models": null, - "litellm_model_name": "gpt-3.5-turbo", - "usage_object": null - }, - "litellm_response_cost": 5.4999999999999995e-05, - "cache_hit": false, - "requester_metadata": {} - }, - "input": { - "messages": [ - { - "role": "user", - "content": "Hello!" - } - ] - }, - "output": { - "content": "Hello! How can I assist you today?", - "role": "assistant", - "tool_calls": null, - "function_call": null, - "provider_specific_fields": null - }, - "level": "DEFAULT", - "id": "time-09-59-32-878577_chatcmpl-1195f870-fd4d-4e38-8dc8-99dd3da5ab0b", - "endTime": "2025-01-22T09:59:32.880691-08:00", - "completionStartTime": "2025-01-22T09:59:32.880691-08:00", - "model": "gpt-3.5-turbo", - "modelParameters": { - "extra_body": "{}" - }, - "usage": { - "input": 10, - "output": 20, - "unit": "TOKENS", - "totalCost": 3.5e-05 - }, - "usageDetails": { - "input": 10, - "output": 20, - "total": 30, - "cache_creation_input_tokens": 0, - "cache_read_input_tokens": 0 + "langfuse.observation.input": { + "messages": [ + { + "role": "user", + "content": "Hello!" } - }, - "timestamp": "2025-01-22T17:59:32.889548Z" - } - ], - "metadata": { - "batch_size": 2, - "sdk_integration": "litellm", - "sdk_name": "python", - "sdk_version": "2.44.1", - "public_key": "pk-lf-e02aaea3-8668-4c9f-8c69-771a4ea1f5c9" + ] + }, + "langfuse.observation.level": "DEFAULT", + "langfuse.observation.model.name": "gpt-3.5-turbo", + "langfuse.observation.model.parameters": { + "extra_body": "{}" + }, + "langfuse.observation.output": { + "content": "Hello! How can I assist you today?", + "role": "assistant", + "tool_calls": null, + "function_call": null, + "provider_specific_fields": null + }, + "langfuse.observation.type": "generation", + "langfuse.observation.usage_details": { + "input": 10, + "output": 20, + "total": 30, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0 + }, + "langfuse.trace.name": "litellm-acompletion" } -} \ No newline at end of file +} diff --git a/tests/logging_callback_tests/langfuse_expected_request_body/nested_metadata.json b/tests/logging_callback_tests/langfuse_expected_request_body/nested_metadata.json index f4a1bb9dcea..27f77398978 100644 --- a/tests/logging_callback_tests/langfuse_expected_request_body/nested_metadata.json +++ b/tests/logging_callback_tests/langfuse_expected_request_body/nested_metadata.json @@ -1,105 +1,39 @@ { - "batch": [ - { - "id": "44f179be-e3b9-486f-986f-030fc50614f0", - "type": "trace-create", - "body": { - "id": "litellm-test-8a04085c-1859-48fa-9fd8-1ec487fe455e", - "timestamp": "2025-01-22T17:55:28.854927Z", - "name": "litellm-acompletion", - "input": { - "messages": [ - { - "role": "user", - "content": "Hello!" - } - ] - }, - "output": { - "content": "Hello! How can I assist you today?", - "role": "assistant", - "tool_calls": null, - "function_call": null, - "provider_specific_fields": null - }, - "tags": [] - }, - "timestamp": "2025-01-22T17:55:28.855187Z" + "name": "litellm-acompletion", + "parent_span_id": null, + "attributes": { + "langfuse.internal.as_root": true, + "langfuse.observation.cost_details": { + "total": 3.5e-05 }, - { - "id": "2175ee64-58a3-41ab-96df-405b76695f5f", - "type": "generation-create", - "body": { - "traceId": "litellm-test-8a04085c-1859-48fa-9fd8-1ec487fe455e", - "name": "litellm-acompletion", - "startTime": "2025-01-22T09:55:28.852503-08:00", - "metadata": { - "a": { - "nested_a": 1 - }, - "b": { - "nested_b": 2 - }, - "hidden_params": { - "model_id": null, - "cache_key": null, - "api_base": "https://api.openai.com", - "response_cost": 5.4999999999999995e-05, - "additional_headers": {}, - "litellm_overhead_time_ms": null, - "batch_models": null, - "litellm_model_name": "gpt-3.5-turbo", - "usage_object": null - }, - "litellm_response_cost": 5.4999999999999995e-05, - "cache_hit": false, - "requester_metadata": {} - }, - "input": { - "messages": [ - { - "role": "user", - "content": "Hello!" - } - ] - }, - "output": { - "content": "Hello! How can I assist you today?", - "role": "assistant", - "tool_calls": null, - "function_call": null, - "provider_specific_fields": null - }, - "level": "DEFAULT", - "id": "time-09-55-28-852503_chatcmpl-131cf0da-a47b-4cd1-850b-50fa077362ac", - "endTime": "2025-01-22T09:55:28.853979-08:00", - "completionStartTime": "2025-01-22T09:55:28.853979-08:00", - "model": "gpt-3.5-turbo", - "modelParameters": { - "extra_body": "{}" - }, - "usage": { - "input": 10, - "output": 20, - "unit": "TOKENS", - "totalCost": 3.5e-05 - }, - "usageDetails": { - "input": 10, - "output": 20, - "total": 30, - "cache_creation_input_tokens": 0, - "cache_read_input_tokens": 0 + "langfuse.observation.input": { + "messages": [ + { + "role": "user", + "content": "Hello!" } - }, - "timestamp": "2025-01-22T17:55:28.855732Z" - } - ], - "metadata": { - "batch_size": 2, - "sdk_integration": "litellm", - "sdk_name": "python", - "sdk_version": "2.44.1", - "public_key": "pk-lf-e02aaea3-8668-4c9f-8c69-771a4ea1f5c9" + ] + }, + "langfuse.observation.level": "DEFAULT", + "langfuse.observation.model.name": "gpt-3.5-turbo", + "langfuse.observation.model.parameters": { + "extra_body": "{}" + }, + "langfuse.observation.output": { + "content": "Hello! How can I assist you today?", + "role": "assistant", + "tool_calls": null, + "function_call": null, + "provider_specific_fields": null + }, + "langfuse.observation.type": "generation", + "langfuse.observation.usage_details": { + "input": 10, + "output": 20, + "total": 30, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0 + }, + "langfuse.trace.name": "litellm-acompletion" } -} \ No newline at end of file +} diff --git a/tests/logging_callback_tests/langfuse_expected_request_body/simple_metadata.json b/tests/logging_callback_tests/langfuse_expected_request_body/simple_metadata.json index d895378e2c6..27f77398978 100644 --- a/tests/logging_callback_tests/langfuse_expected_request_body/simple_metadata.json +++ b/tests/logging_callback_tests/langfuse_expected_request_body/simple_metadata.json @@ -1,105 +1,39 @@ { - "batch": [ - { - "id": "02c74119-76b7-4f79-91cb-c55f1495c100", - "type": "trace-create", - "body": { - "id": "litellm-test-e58116c7-ead0-417e-9f86-b35f1e5bc242", - "timestamp": "2025-01-22T17:53:53.754012Z", - "name": "litellm-acompletion", - "input": { - "messages": [ - { - "role": "user", - "content": "Hello!" - } - ] - }, - "output": { - "content": "Hello! How can I assist you today?", - "role": "assistant", - "tool_calls": null, - "function_call": null, - "provider_specific_fields": null - }, - "tags": [] - }, - "timestamp": "2025-01-22T17:53:53.754178Z" + "name": "litellm-acompletion", + "parent_span_id": null, + "attributes": { + "langfuse.internal.as_root": true, + "langfuse.observation.cost_details": { + "total": 3.5e-05 }, - { - "id": "097968e0-52e9-46b5-9e8e-e6e08dd00e72", - "type": "generation-create", - "body": { - "traceId": "litellm-test-e58116c7-ead0-417e-9f86-b35f1e5bc242", - "name": "litellm-acompletion", - "startTime": "2025-01-22T09:53:53.752422-08:00", - "metadata": { - "a": { - "nested_a": 1 - }, - "b": { - "nested_b": 2 - }, - "hidden_params": { - "model_id": null, - "cache_key": null, - "api_base": "https://api.openai.com", - "response_cost": 5.4999999999999995e-05, - "additional_headers": {}, - "litellm_overhead_time_ms": null, - "batch_models": null, - "litellm_model_name": "gpt-3.5-turbo", - "usage_object": null - }, - "litellm_response_cost": 5.4999999999999995e-05, - "cache_hit": false, - "requester_metadata": {} - }, - "input": { - "messages": [ - { - "role": "user", - "content": "Hello!" - } - ] - }, - "output": { - "content": "Hello! How can I assist you today?", - "role": "assistant", - "tool_calls": null, - "function_call": null, - "provider_specific_fields": null - }, - "level": "DEFAULT", - "id": "time-09-53-53-752422_chatcmpl-e99bc1d3-a393-493f-8afe-4507c0acff15", - "endTime": "2025-01-22T09:53:53.753431-08:00", - "completionStartTime": "2025-01-22T09:53:53.753431-08:00", - "model": "gpt-3.5-turbo", - "modelParameters": { - "extra_body": "{}" - }, - "usage": { - "input": 10, - "output": 20, - "unit": "TOKENS", - "totalCost": 3.5e-05 - }, - "usageDetails": { - "input": 10, - "output": 20, - "total": 30, - "cache_creation_input_tokens": 0, - "cache_read_input_tokens": 0 + "langfuse.observation.input": { + "messages": [ + { + "role": "user", + "content": "Hello!" } - }, - "timestamp": "2025-01-22T17:53:53.754511Z" - } - ], - "metadata": { - "batch_size": 2, - "sdk_integration": "litellm", - "sdk_name": "python", - "sdk_version": "2.44.1", - "public_key": "pk-lf-e02aaea3-8668-4c9f-8c69-771a4ea1f5c9" + ] + }, + "langfuse.observation.level": "DEFAULT", + "langfuse.observation.model.name": "gpt-3.5-turbo", + "langfuse.observation.model.parameters": { + "extra_body": "{}" + }, + "langfuse.observation.output": { + "content": "Hello! How can I assist you today?", + "role": "assistant", + "tool_calls": null, + "function_call": null, + "provider_specific_fields": null + }, + "langfuse.observation.type": "generation", + "langfuse.observation.usage_details": { + "input": 10, + "output": 20, + "total": 30, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0 + }, + "langfuse.trace.name": "litellm-acompletion" } -} \ No newline at end of file +} diff --git a/tests/logging_callback_tests/langfuse_expected_request_body/simple_metadata2.json b/tests/logging_callback_tests/langfuse_expected_request_body/simple_metadata2.json index 87eba33cfff..27f77398978 100644 --- a/tests/logging_callback_tests/langfuse_expected_request_body/simple_metadata2.json +++ b/tests/logging_callback_tests/langfuse_expected_request_body/simple_metadata2.json @@ -1,109 +1,39 @@ { - "batch": [ - { - "id": "1a55383a-e6fa-41f9-81fe-e7aa58c55f40", - "type": "trace-create", - "body": { - "id": "litellm-test-08fd1578-4a67-49b4-ac23-2dff1c112c80", - "timestamp": "2025-01-22T17:56:35.477276Z", - "name": "litellm-acompletion", - "input": { - "messages": [ - { - "role": "user", - "content": "Hello!" - } - ] - }, - "output": { - "content": "Hello! How can I assist you today?", - "role": "assistant", - "tool_calls": null, - "function_call": null, - "provider_specific_fields": null - }, - "tags": [] - }, - "timestamp": "2025-01-22T17:56:35.477571Z" + "name": "litellm-acompletion", + "parent_span_id": null, + "attributes": { + "langfuse.internal.as_root": true, + "langfuse.observation.cost_details": { + "total": 3.5e-05 }, - { - "id": "13ba66e8-f72b-4f57-a6cc-57c0be2829b1", - "type": "generation-create", - "body": { - "traceId": "litellm-test-08fd1578-4a67-49b4-ac23-2dff1c112c80", - "name": "litellm-acompletion", - "startTime": "2025-01-22T09:56:35.474752-08:00", - "metadata": { - "a": [ - 1, - 2, - 3 - ], - "b": [ - 4, - 5, - 6 - ], - "hidden_params": { - "model_id": null, - "cache_key": null, - "api_base": "https://api.openai.com", - "response_cost": 5.4999999999999995e-05, - "additional_headers": {}, - "litellm_overhead_time_ms": null, - "batch_models": null, - "litellm_model_name": "gpt-3.5-turbo", - "usage_object": null - }, - "litellm_response_cost": 5.4999999999999995e-05, - "cache_hit": false, - "requester_metadata": {} - }, - "input": { - "messages": [ - { - "role": "user", - "content": "Hello!" - } - ] - }, - "output": { - "content": "Hello! How can I assist you today?", - "role": "assistant", - "tool_calls": null, - "function_call": null, - "provider_specific_fields": null - }, - "level": "DEFAULT", - "id": "time-09-56-35-474752_chatcmpl-9b152610-3d1e-4731-a84e-d0341ea69a0f", - "endTime": "2025-01-22T09:56:35.476236-08:00", - "completionStartTime": "2025-01-22T09:56:35.476236-08:00", - "model": "gpt-3.5-turbo", - "modelParameters": { - "extra_body": "{}" - }, - "usage": { - "input": 10, - "output": 20, - "unit": "TOKENS", - "totalCost": 3.5e-05 - }, - "usageDetails": { - "input": 10, - "output": 20, - "total": 30, - "cache_creation_input_tokens": 0, - "cache_read_input_tokens": 0 + "langfuse.observation.input": { + "messages": [ + { + "role": "user", + "content": "Hello!" } - }, - "timestamp": "2025-01-22T17:56:35.478171Z" - } - ], - "metadata": { - "batch_size": 2, - "sdk_integration": "litellm", - "sdk_name": "python", - "sdk_version": "2.44.1", - "public_key": "pk-lf-e02aaea3-8668-4c9f-8c69-771a4ea1f5c9" + ] + }, + "langfuse.observation.level": "DEFAULT", + "langfuse.observation.model.name": "gpt-3.5-turbo", + "langfuse.observation.model.parameters": { + "extra_body": "{}" + }, + "langfuse.observation.output": { + "content": "Hello! How can I assist you today?", + "role": "assistant", + "tool_calls": null, + "function_call": null, + "provider_specific_fields": null + }, + "langfuse.observation.type": "generation", + "langfuse.observation.usage_details": { + "input": 10, + "output": 20, + "total": 30, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0 + }, + "langfuse.trace.name": "litellm-acompletion" } -} \ No newline at end of file +} diff --git a/tests/logging_callback_tests/langfuse_expected_request_body/simple_metadata3.json b/tests/logging_callback_tests/langfuse_expected_request_body/simple_metadata3.json index dd3bb4a301f..27f77398978 100644 --- a/tests/logging_callback_tests/langfuse_expected_request_body/simple_metadata3.json +++ b/tests/logging_callback_tests/langfuse_expected_request_body/simple_metadata3.json @@ -1,113 +1,39 @@ { - "batch": [ - { - "id": "7fb1f295-a7af-47af-afbd-e2f2d08280aa", - "type": "trace-create", - "body": { - "id": "litellm-test-c3acc34b-3c06-4868-bcee-87a3c4c1367e", - "timestamp": "2025-01-22T17:56:38.786515Z", - "name": "litellm-acompletion", - "input": { - "messages": [ - { - "role": "user", - "content": "Hello!" - } - ] - }, - "output": { - "content": "Hello! How can I assist you today?", - "role": "assistant", - "tool_calls": null, - "function_call": null, - "provider_specific_fields": null - }, - "tags": [] - }, - "timestamp": "2025-01-22T17:56:38.786742Z" + "name": "litellm-acompletion", + "parent_span_id": null, + "attributes": { + "langfuse.internal.as_root": true, + "langfuse.observation.cost_details": { + "total": 3.5e-05 }, - { - "id": "412870bc-fc50-4426-a0dc-9e8b016e14bb", - "type": "generation-create", - "body": { - "traceId": "litellm-test-c3acc34b-3c06-4868-bcee-87a3c4c1367e", - "name": "litellm-acompletion", - "startTime": "2025-01-22T09:56:38.784548-08:00", - "metadata": { - "a": [ - 1, - 2 - ], - "b": [ - 3, - 4 - ], - "c": { - "d": [ - 5, - 6 - ] - }, - "hidden_params": { - "model_id": null, - "cache_key": null, - "api_base": "https://api.openai.com", - "response_cost": 5.4999999999999995e-05, - "additional_headers": {}, - "litellm_overhead_time_ms": null, - "batch_models": null, - "litellm_model_name": "gpt-3.5-turbo", - "usage_object": null - }, - "litellm_response_cost": 5.4999999999999995e-05, - "cache_hit": false, - "requester_metadata": {} - }, - "input": { - "messages": [ - { - "role": "user", - "content": "Hello!" - } - ] - }, - "output": { - "content": "Hello! How can I assist you today?", - "role": "assistant", - "tool_calls": null, - "function_call": null, - "provider_specific_fields": null - }, - "level": "DEFAULT", - "id": "time-09-56-38-784548_chatcmpl-438c8727-86b3-44d9-9b46-42330922cf50", - "endTime": "2025-01-22T09:56:38.785762-08:00", - "completionStartTime": "2025-01-22T09:56:38.785762-08:00", - "model": "gpt-3.5-turbo", - "modelParameters": { - "extra_body": "{}" - }, - "usage": { - "input": 10, - "output": 20, - "unit": "TOKENS", - "totalCost": 3.5e-05 - }, - "usageDetails": { - "input": 10, - "output": 20, - "total": 30, - "cache_creation_input_tokens": 0, - "cache_read_input_tokens": 0 + "langfuse.observation.input": { + "messages": [ + { + "role": "user", + "content": "Hello!" } - }, - "timestamp": "2025-01-22T17:56:38.787196Z" - } - ], - "metadata": { - "batch_size": 2, - "sdk_integration": "litellm", - "sdk_name": "python", - "sdk_version": "2.44.1", - "public_key": "pk-lf-e02aaea3-8668-4c9f-8c69-771a4ea1f5c9" + ] + }, + "langfuse.observation.level": "DEFAULT", + "langfuse.observation.model.name": "gpt-3.5-turbo", + "langfuse.observation.model.parameters": { + "extra_body": "{}" + }, + "langfuse.observation.output": { + "content": "Hello! How can I assist you today?", + "role": "assistant", + "tool_calls": null, + "function_call": null, + "provider_specific_fields": null + }, + "langfuse.observation.type": "generation", + "langfuse.observation.usage_details": { + "input": 10, + "output": 20, + "total": 30, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0 + }, + "langfuse.trace.name": "litellm-acompletion" } -} \ No newline at end of file +} diff --git a/tests/logging_callback_tests/test_langfuse_e2e_test.py b/tests/logging_callback_tests/test_langfuse_e2e_test.py index 5682d3720d8..5d9a832337c 100644 --- a/tests/logging_callback_tests/test_langfuse_e2e_test.py +++ b/tests/logging_callback_tests/test_langfuse_e2e_test.py @@ -1,166 +1,138 @@ import asyncio -import copy import json import logging import os import threading -from typing import Any, Optional +from collections.abc import Mapping +from typing import Final from unittest.mock import AsyncMock, MagicMock, patch import httpx +from opentelemetry.proto.collector.trace.v1.trace_service_pb2 import ExportTraceServiceRequest +from opentelemetry.proto.common.v1.common_pb2 import AnyValue logging.basicConfig(level=logging.DEBUG) import litellm -from litellm import completion -from litellm.caching import InMemoryCache +from litellm.integrations.langfuse.langfuse_sdk import resolve_observation_id, resolve_trace_id from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler litellm.num_retries = 3 litellm.success_callback = ["langfuse"] os.environ["LANGFUSE_DEBUG"] = "True" -import time import pytest import pytest_asyncio +LANGFUSE_EXPORT_POST: Final = "requests.Session.post" +LANGFUSE_EXPORT_PATH: Final = "/api/public/otel/v1/traces" + +_LITELLM_OWNED_ATTRIBUTES: Final = frozenset( + { + "langfuse.internal.is_app_root", + "langfuse.observation.completion_start_time", + "langfuse.observation.metadata.applied_guardrails", + "langfuse.observation.metadata.cache_hit", + "langfuse.observation.metadata.hidden_params", + "langfuse.observation.metadata.litellm_response_cost", + "langfuse.observation.metadata.requester_metadata", + "langfuse.observation.metadata.usage_object", + } +) + + +def _decode_attribute(value: AnyValue) -> object: + match value.WhichOneof("value"): + case "string_value": + try: + return json.loads(value.string_value) + except json.JSONDecodeError: + return value.string_value + case "bool_value": + return value.bool_value + case "int_value": + return value.int_value + case "double_value": + return value.double_value + case "array_value": + return [_decode_attribute(item) for item in value.array_value.values] + case _: + return None + + +def _exported_spans(mock_post: MagicMock) -> list[dict[str, object]]: + spans: list[dict[str, object]] = [] + for call in mock_post.call_args_list: + assert call.kwargs["url"].endswith(LANGFUSE_EXPORT_PATH), call.kwargs["url"] + request = ExportTraceServiceRequest.FromString(call.kwargs["data"]) + for resource_spans in request.resource_spans: + for scope_spans in resource_spans.scope_spans: + for span in scope_spans.spans: + spans.append( + { + "name": span.name, + "trace_id": span.trace_id.hex(), + "span_id": span.span_id.hex(), + "parent_span_id": span.parent_span_id.hex() or None, + "attributes": { + attribute.key: _decode_attribute(attribute.value) for attribute in span.attributes + }, + } + ) + return spans + + +def _comparable(span: Mapping[str, object]) -> dict[str, object]: + attributes = span["attributes"] + assert isinstance(attributes, dict) + return { + "name": span["name"], + "parent_span_id": None if attributes.get("langfuse.internal.as_root") else span["parent_span_id"], + "attributes": {key: value for key, value in sorted(attributes.items()) if key not in _LITELLM_OWNED_ATTRIBUTES}, + } + def assert_langfuse_request_matches_expected( - actual_request_body: dict, + spans: list[dict[str, object]], expected_file_name: str, - trace_id: Optional[str] = None, + trace_id: str, ): - """ - Helper function to compare actual Langfuse request body with expected JSON file. - - Args: - actual_request_body (dict): The actual request body received from the API call - expected_file_name (str): Name of the JSON file containing expected request body (e.g., "transcription.json") - """ - # Get the current directory and read the expected request body + """Compare the generation langfuse exported for ``trace_id`` with the expected JSON file.""" pwd = os.path.dirname(os.path.realpath(__file__)) - expected_body_path = os.path.join( - pwd, "langfuse_expected_request_body", expected_file_name - ) - + expected_body_path = os.path.join(pwd, "langfuse_expected_request_body", expected_file_name) with open(expected_body_path, "r") as f: - expected_request_body = json.load(f) + expected_generation = json.load(f) - # Filter out events that don't match the trace_id - if trace_id: - actual_request_body["batch"] = [ - item - for item in actual_request_body["batch"] - if (item["type"] == "trace-create" and item["body"].get("id") == trace_id) - or ( - item["type"] == "generation-create" - and item["body"].get("traceId") == trace_id - ) - ] - - # When aggregating from multiple flush cycles, deduplicate by keeping - # only one trace-create and one generation-create per trace_id. - seen_types: dict = {} - deduped_batch: list = [] - for item in actual_request_body["batch"]: - item_type = item["type"] - if item_type not in seen_types: - seen_types[item_type] = True - deduped_batch.append(item) - actual_request_body["batch"] = deduped_batch - - # Ensure canonical order: trace-create first, generation-create second - actual_request_body["batch"].sort( - key=lambda x: 0 if x["type"] == "trace-create" else 1 + otel_trace_id: Final = resolve_trace_id(trace_id) + generations: Final = [ + span + for span in spans + if span["trace_id"] == otel_trace_id and span["attributes"]["langfuse.observation.type"] == "generation" # pyright: ignore[reportIndexIssue] # built as dict in _exported_spans + ] + assert len(generations) == 1, ( + f"Expected exactly one generation for trace_id={trace_id} ({otel_trace_id}), " + f"got {len(generations)}. Spans: {json.dumps(spans, indent=2)}" ) - print( - "actual_request_body after filtering", json.dumps(actual_request_body, indent=4) + actual_generation: Final = _comparable(generations[0]) + assert actual_generation == expected_generation, ( + f"Difference in exported generation: {json.dumps(actual_generation, indent=2)} " + f"!= {json.dumps(expected_generation, indent=2)}" ) - assert len(actual_request_body["batch"]) >= 2, ( - f"Expected at least 2 batch items (trace-create + generation-create) " - f"after filtering by trace_id={trace_id}, " - f"but got {len(actual_request_body['batch'])}. " - f"Items: {json.dumps(actual_request_body['batch'], indent=2)}" - ) - - # Replace dynamic values in actual request body - for item in actual_request_body["batch"]: - - # Replace IDs with expected IDs - if item["type"] == "trace-create": - item["id"] = expected_request_body["batch"][0]["id"] - item["body"]["id"] = expected_request_body["batch"][0]["body"]["id"] - item["timestamp"] = expected_request_body["batch"][0]["timestamp"] - item["body"]["timestamp"] = expected_request_body["batch"][0]["body"][ - "timestamp" - ] - elif item["type"] == "generation-create": - item["id"] = expected_request_body["batch"][1]["id"] - item["body"]["id"] = expected_request_body["batch"][1]["body"]["id"] - item["timestamp"] = expected_request_body["batch"][1]["timestamp"] - item["body"]["startTime"] = expected_request_body["batch"][1]["body"][ - "startTime" - ] - item["body"]["endTime"] = expected_request_body["batch"][1]["body"][ - "endTime" - ] - item["body"]["completionStartTime"] = expected_request_body["batch"][1][ - "body" - ]["completionStartTime"] - if trace_id is None: - print("popping traceId") - item["body"].pop("traceId") - else: - item["body"]["traceId"] = trace_id - expected_request_body["batch"][1]["body"]["traceId"] = trace_id - - # Replace SDK version with expected version - actual_request_body["batch"][0]["body"].pop("release", None) - actual_request_body["metadata"]["sdk_version"] = expected_request_body["metadata"][ - "sdk_version" - ] - # replace "public_key" with expected public key - actual_request_body["metadata"]["public_key"] = expected_request_body["metadata"][ - "public_key" - ] - actual_request_body["batch"][1]["body"]["metadata"] = expected_request_body[ - "batch" - ][1]["body"]["metadata"] - actual_request_body["metadata"]["sdk_integration"] = expected_request_body[ - "metadata" - ]["sdk_integration"] - actual_request_body["metadata"]["batch_size"] = expected_request_body["metadata"][ - "batch_size" - ] - # Assert the entire request body matches - assert ( - actual_request_body == expected_request_body - ), f"Difference in request bodies: {json.dumps(actual_request_body, indent=2)} != {json.dumps(expected_request_body, indent=2)}" - class TestLangfuseLogging: @pytest_asyncio.fixture async def mock_setup(self): """Common setup for Langfuse logging tests""" from litellm._uuid import uuid - from unittest.mock import AsyncMock, patch - import httpx - # Create a mock Response object - mock_response = AsyncMock(spec=httpx.Response) - mock_response.status_code = 200 - mock_response.json.return_value = {"status": "success"} - - # Create mock for httpx.Client.post - mock_post = AsyncMock() - mock_post.return_value = mock_response + mock_post = MagicMock(return_value=MagicMock(ok=True, status_code=200)) litellm.set_verbose = True litellm.success_callback = ["langfuse"] - return {"trace_id": f"litellm-test-{str(uuid.uuid4())}", "mock_post": mock_post} + return {"trace_id": f"litellm-test-{uuid.uuid4()!s}", "mock_post": mock_post} async def _verify_langfuse_call( self, @@ -168,41 +140,16 @@ class TestLangfuseLogging: expected_file_name: str, trace_id: str, ): - """Helper method to verify Langfuse API calls""" - await asyncio.sleep(3) - - # Verify at least one call was made - assert mock_post.call_count >= 1 - - # Aggregate batch items from ALL calls — the Langfuse SDK may split - # trace-create and generation-create across separate HTTP flushes. - langfuse_url = "https://us.cloud.langfuse.com/api/public/ingestion" - all_batch_items: list = [] - metadata: Optional[dict] = None - for call in mock_post.call_args_list: - url = call[0][0] - if url != langfuse_url: - continue - request_body = call[1].get("content") - if request_body: - body = json.loads(request_body) - all_batch_items.extend(body.get("batch", [])) - if metadata is None: - metadata = body.get("metadata") - - assert len(all_batch_items) > 0, "No Langfuse ingestion calls found" - assert metadata is not None, "No metadata found in Langfuse calls" - - actual_request_body = { - "batch": all_batch_items, - "metadata": metadata, - } - - print("\nMocked Request Details (aggregated from all calls):") - print(f"Request Body: {json.dumps(actual_request_body, indent=4)}") + """Wait for the batch processor to export, then compare the generation it shipped.""" + otel_trace_id: Final = resolve_trace_id(trace_id) + for _ in range(100): + if any(span["trace_id"] == otel_trace_id for span in _exported_spans(mock_post)): + break + await asyncio.sleep(0.1) + assert mock_post.call_count >= 1, "langfuse exported nothing" assert_langfuse_request_matches_expected( - actual_request_body, + _exported_spans(mock_post), expected_file_name, trace_id, ) @@ -212,23 +159,21 @@ class TestLangfuseLogging: async def test_langfuse_logging_completion(self, mock_setup): """Test Langfuse logging for chat completion""" setup = mock_setup - with patch("httpx.Client.post", setup["mock_post"]): + with patch(LANGFUSE_EXPORT_POST, setup["mock_post"]): await litellm.acompletion( model="gpt-3.5-turbo", messages=[{"role": "user", "content": "Hello!"}], mock_response="Hello! How can I assist you today?", metadata={"trace_id": setup["trace_id"]}, ) - await self._verify_langfuse_call( - setup["mock_post"], "completion.json", setup["trace_id"] - ) + await self._verify_langfuse_call(setup["mock_post"], "completion.json", setup["trace_id"]) @pytest.mark.asyncio @pytest.mark.flaky(retries=3, delay=1) async def test_langfuse_logging_completion_with_tags(self, mock_setup): """Test Langfuse logging for chat completion with tags""" setup = mock_setup - with patch("httpx.Client.post", setup["mock_post"]): + with patch(LANGFUSE_EXPORT_POST, setup["mock_post"]): await litellm.acompletion( model="gpt-3.5-turbo", messages=[{"role": "user", "content": "Hello!"}], @@ -238,16 +183,14 @@ class TestLangfuseLogging: "tags": ["test_tag", "test_tag_2"], }, ) - await self._verify_langfuse_call( - setup["mock_post"], "completion_with_tags.json", setup["trace_id"] - ) + await self._verify_langfuse_call(setup["mock_post"], "completion_with_tags.json", setup["trace_id"]) @pytest.mark.asyncio @pytest.mark.flaky(retries=3, delay=1) async def test_langfuse_logging_completion_with_tags_stream(self, mock_setup): """Test Langfuse logging for chat completion with tags""" setup = mock_setup - with patch("httpx.Client.post", setup["mock_post"]): + with patch(LANGFUSE_EXPORT_POST, setup["mock_post"]): await litellm.acompletion( model="gpt-3.5-turbo", messages=[{"role": "user", "content": "Hello!"}], @@ -263,12 +206,33 @@ class TestLangfuseLogging: setup["trace_id"], ) + @pytest.mark.asyncio + @pytest.mark.flaky(retries=3, delay=1) + async def test_langfuse_generation_id_metadata_names_the_exported_observation(self, mock_setup): + """v2 let callers pick the generation id; v4 only has span ids, so the requested id must become one.""" + setup = mock_setup + with patch(LANGFUSE_EXPORT_POST, setup["mock_post"]): + await litellm.acompletion( + model="gpt-3.5-turbo", + messages=[{"role": "user", "content": "Hello!"}], + mock_response="Hello! How can I assist you today?", + metadata={"trace_id": setup["trace_id"], "generation_id": "my-generation"}, + ) + await self._verify_langfuse_call(setup["mock_post"], "completion.json", setup["trace_id"]) + + generation: Final = next( + span + for span in _exported_spans(setup["mock_post"]) + if span["trace_id"] == resolve_trace_id(setup["trace_id"]) + ) + assert generation["span_id"] == resolve_observation_id("my-generation") + @pytest.mark.asyncio @pytest.mark.flaky(retries=3, delay=1) async def test_langfuse_logging_completion_with_langfuse_metadata(self, mock_setup): """Test Langfuse logging for chat completion with metadata for langfuse""" setup = mock_setup - with patch("httpx.Client.post", setup["mock_post"]): + with patch(LANGFUSE_EXPORT_POST, setup["mock_post"]): await litellm.acompletion( model="gpt-3.5-turbo", messages=[{"role": "user", "content": "Hello!"}], @@ -297,12 +261,12 @@ class TestLangfuseLogging: @pytest.mark.flaky(retries=3, delay=1) async def test_langfuse_logging_with_non_serializable_metadata(self, mock_setup): """Test Langfuse logging with metadata that requires preparation (Pydantic models, sets, etc)""" - from pydantic import BaseModel - from typing import Set import datetime + from pydantic import BaseModel + class UserPreferences(BaseModel): - favorite_colors: Set[str] + favorite_colors: set[str] last_login: datetime.datetime settings: dict @@ -325,8 +289,8 @@ class TestLangfuseLogging: "trace_id": setup["trace_id"], } - with patch("httpx.Client.post", setup["mock_post"]): - response = await litellm.acompletion( + with patch(LANGFUSE_EXPORT_POST, setup["mock_post"]): + await litellm.acompletion( model="gpt-3.5-turbo", messages=[{"role": "user", "content": "Hello!"}], mock_response="Hello! How can I assist you today?", @@ -375,18 +339,14 @@ class TestLangfuseLogging: ], ) @pytest.mark.flaky(retries=6, delay=1) - async def test_langfuse_logging_with_various_metadata_types( - self, mock_setup, test_metadata, response_json_file - ): + async def test_langfuse_logging_with_various_metadata_types(self, mock_setup, test_metadata, response_json_file): """Test Langfuse logging with various metadata types including non-serializable objects""" - import threading - setup = mock_setup if test_metadata is not None: test_metadata["trace_id"] = setup["trace_id"] - with patch("httpx.Client.post", setup["mock_post"]): + with patch(LANGFUSE_EXPORT_POST, setup["mock_post"]): await litellm.acompletion( model="gpt-3.5-turbo", messages=[{"role": "user", "content": "Hello!"}], @@ -402,13 +362,11 @@ class TestLangfuseLogging: @pytest.mark.asyncio @pytest.mark.flaky(retries=3, delay=1) - async def test_langfuse_logging_completion_with_malformed_llm_response( - self, mock_setup - ): + async def test_langfuse_logging_completion_with_malformed_llm_response(self, mock_setup): """Test Langfuse logging for chat completion with malformed LLM response""" setup = mock_setup litellm._turn_on_debug() - with patch("httpx.Client.post", setup["mock_post"]): + with patch(LANGFUSE_EXPORT_POST, setup["mock_post"]): mock_response = litellm.ModelResponse( choices=[], usage=litellm.Usage( @@ -426,19 +384,15 @@ class TestLangfuseLogging: mock_response=mock_response, metadata={"trace_id": setup["trace_id"]}, ) - await self._verify_langfuse_call( - setup["mock_post"], "completion_with_no_choices.json", setup["trace_id"] - ) + await self._verify_langfuse_call(setup["mock_post"], "completion_with_no_choices.json", setup["trace_id"]) @pytest.mark.asyncio @pytest.mark.flaky(retries=3, delay=1) - async def test_langfuse_logging_completion_with_bedrock_llm_response( - self, mock_setup - ): + async def test_langfuse_logging_completion_with_bedrock_llm_response(self, mock_setup): """Test Langfuse logging for chat completion with malformed LLM response""" setup = mock_setup litellm._turn_on_debug() - with patch("httpx.Client.post", setup["mock_post"]): + with patch(LANGFUSE_EXPORT_POST, setup["mock_post"]): mock_response = litellm.ModelResponse( choices=[], usage=litellm.Usage( @@ -467,13 +421,11 @@ class TestLangfuseLogging: @pytest.mark.asyncio @pytest.mark.flaky(retries=3, delay=1) - async def test_langfuse_logging_completion_with_vertex_llm_response( - self, mock_setup - ): + async def test_langfuse_logging_completion_with_vertex_llm_response(self, mock_setup): """Test Langfuse logging for chat completion with malformed LLM response""" setup = mock_setup litellm._turn_on_debug() - with patch("httpx.Client.post", setup["mock_post"]): + with patch(LANGFUSE_EXPORT_POST, setup["mock_post"]): mock_response = litellm.ModelResponse( choices=[], usage=litellm.Usage( @@ -525,7 +477,7 @@ class TestLangfuseLogging: mock_async_client = AsyncHTTPHandler() mock_async_client.post = AsyncMock(return_value=mock_vllm_response) - with patch("httpx.Client.post", setup["mock_post"]): + with patch(LANGFUSE_EXPORT_POST, setup["mock_post"]): await litellm.aembedding( model="hosted_vllm/BAAI/bge-small-en-v1.5", input=["Hello from litellm!"], @@ -539,9 +491,7 @@ class TestLangfuseLogging: actual_vllm_request = mock_async_client.post.call_args.kwargs["json"] pwd = os.path.dirname(os.path.realpath(__file__)) - expected_body_path = os.path.join( - pwd, "langfuse_expected_request_body", "embedding_with_vllm.json" - ) + expected_body_path = os.path.join(pwd, "langfuse_expected_request_body", "embedding_with_vllm.json") with open(expected_body_path, "r") as f: expected_vllm_request = json.load(f) @@ -568,7 +518,7 @@ class TestLangfuseLogging: } ] ) - with patch("httpx.Client.post", mock_setup["mock_post"]): + with patch(LANGFUSE_EXPORT_POST, mock_setup["mock_post"]): mock_response = litellm.ModelResponse( choices=[], usage=litellm.Usage( diff --git a/tests/test_litellm/integrations/langfuse/test_langfuse_sdk.py b/tests/test_litellm/integrations/langfuse/test_langfuse_sdk.py index de75aacd95f..1ea48d342ba 100644 --- a/tests/test_litellm/integrations/langfuse/test_langfuse_sdk.py +++ b/tests/test_litellm/integrations/langfuse/test_langfuse_sdk.py @@ -394,6 +394,72 @@ def test_invalid_sample_rate_fails_at_construction_like_the_sdk(monkeypatch): build_isolated_tracer_provider(environment=None, release=None) +def _isolated_client_with_exporter(): + exporter = InMemorySpanExporter() + provider = build_isolated_tracer_provider(environment=None, release=None) + provider.add_span_processor(SimpleSpanProcessor(exporter)) + client = Langfuse( + public_key=PUBLIC_KEY, + secret_key="sk-observation-id", + host="http://127.0.0.1:1", + tracer_provider=provider, + span_exporter=exporter, + ) + return client, exporter + + +def _start_generation(client, name, observation_id): + context, claim_root = open_trace_context(client=client, trace_id="b" * 32, parent_observation_id=None) + return start_generation( + client=client, + context=context, + name=name, + start_time=CALL_START, + claim_trace_root=claim_root, + observation_id=observation_id, + attributes={}, + ) + + +def test_requested_observation_id_becomes_the_exported_span_id(): + """v2 ``generation(id=...)``: the caller's id is what the export carries and what ``.id`` returns.""" + lf, exporter = _isolated_client_with_exporter() + requested = resolve_observation_id("chatcmpl-123") + + generation = _start_generation(lf, "requested", requested) + generation.end(end_time=to_unix_nanos(CALL_END)) + lf.flush() + + assert generation.id == requested + assert format(_only_span(exporter, "requested").context.span_id, "016x") == requested + + +def test_requested_observation_id_does_not_leak_into_the_next_span(): + lf, exporter = _isolated_client_with_exporter() + requested = resolve_observation_id("chatcmpl-123") + + _start_generation(lf, "first", requested).end(end_time=to_unix_nanos(CALL_END)) + second = _start_generation(lf, "second", None) + second.end(end_time=to_unix_nanos(CALL_END)) + third = _start_generation(lf, "third", None) + third.end(end_time=to_unix_nanos(CALL_END)) + lf.flush() + + assert second.id != requested + assert third.id != second.id + assert len({span.context.span_id for span in exporter.get_finished_spans()}) == 3 + + +def test_requested_observation_id_is_ignored_on_a_provider_litellm_did_not_build(client): + lf, _ = client + requested = resolve_observation_id("chatcmpl-123") + + generation = _start_generation(lf, "adopted", requested) + generation.end(end_time=to_unix_nanos(CALL_END)) + + assert generation.id != requested + + def test_environment_override_lands_per_span_despite_shared_resources(): """The SDK registry is keyed on public key alone, so a second client for the same key adopts the first client's provider; the observation wrapper stamps