feat(langfuse): honour caller generation ids and assert v4 OTLP exports in legacy tests
Some checks are pending
ai-gateway image / ai-gateway release image (push) Waiting to run
LiteLLM Rust / rust-lint (push) Waiting to run
LiteLLM Rust / rust-test (push) Waiting to run
Terraform Modules / fmt, validate, test (aws) (push) Waiting to run
Terraform Modules / fmt, validate, test (gcp) (push) Waiting to run
Terraform Provider / gofmt, vet, build, test (push) Waiting to run
Terraform Provider / Provider endpoints vs proxy OpenAPI schema (push) Waiting to run

v2 accepted generation(id=...). v4 derives the observation id from the OTel
span id, so the isolated tracer provider now carries an id generator that
hands out the id start_generation asked for through a context variable, and
the callback passes the resolved generation_id metadata into it.

The legacy e2e suite patched httpx.Client.post and compared v2 ingestion
batches; it now patches requests.Session.post, decodes the OTLP protobuf
and compares the exported generation against regenerated fixtures. The
local readback test replaces the removed get_generations() with
api.observations.get_many() and polls Langfuse Cloud instead of sleeping.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
yucheng 2026-09-14 23:12:03 +00:00
parent 124ac5e8dd
commit 09dfd7e50d
23 changed files with 872 additions and 2003 deletions

View file

@ -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())

View file

@ -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)

View file

@ -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:

View file

@ -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"
}
}
}

View file

@ -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"
}
}
}

View file

@ -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"
}
}
}

View file

@ -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"
}
}
}

View file

@ -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"
}
}
}

View file

@ -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"
}
}
}

View file

@ -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"
]
}
}
}

View file

@ -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"
]
}
}
}

View file

@ -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"
}
}
}

View file

@ -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"
}
}
}

View file

@ -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"
}
}
}

View file

@ -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"
}
}
}

View file

@ -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"
}
}
}

View file

@ -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"
}
}
}

View file

@ -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"
}
}
}

View file

@ -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"
}
}
}

View file

@ -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"
}
}
}

View file

@ -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"
}
}
}

View file

@ -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(

View file

@ -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