feat(langfuse): migrate the sdk callback to langfuse v4 (#36741)

* feat(langfuse): migrate the sdk callback to langfuse v4

Replace the v2 trace()/generation()/span() calls with SDK v4 observations exported over OpenTelemetry, with one isolated tracer provider per Langfuse credential set, a discarding exporter for mock mode, and v4 trace and observation id normalization. Keeps the session-header trace provenance logic from main so each call under a session alias still gets its own trace

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(langfuse): drop the always-true prompt client check now that v4 get_prompt is non-optional

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(langfuse): isolate the e2e sync test from cached clients and log the real sdk major

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* refactor(langfuse): type the slack trace-url lookup and drop dead v2 test shims

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(slack): cover the langfuse trace url built from the logger host

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* build(docker): pin langfuse to the locked 4.15.2 in the pip image

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(langfuse): hash all-zero trace and observation ids instead of passing them through

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* feat(langfuse): honour caller generation ids and assert v4 OTLP exports in legacy tests

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>

* refactor(langfuse): read the sdk version header from package metadata

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(langfuse): propagate trace_metadata as trace-level attributes in v4

v2 wrote trace(metadata=...) onto the trace object. In v4 the trace only
carries what the observations propagate, so a continuation request with
update_trace_keys=["trace_metadata"] updated the generation's metadata
while the trace kept its stale values. Coerce each entry to the SDK's
string limit and hand it to propagate_attributes(metadata=...).

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(langfuse): propagate interrupts raised during deferred client teardown

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(langfuse): honor ssl_verify=False and SSL_VERIFY on the v4 OTLP exporter

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(langfuse): fall back to the default CA when the configured bundle path is missing

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(langfuse): renew the client when eviction lands before the callback lease

The cache can evict a logger between handing it to the callback and the callback taking its
lease. Such a lease now hands back a fresh client acquired through the same parameters, so that
callback exports through a live tracer provider instead of one teardown already shut down.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(langfuse): emit litellm_call_id and response_id as generation metadata

v2 put the provider response id inside the generation id. v4 observation ids are 16 hex chars derived from that string, so the ids move to generation metadata to keep generations searchable by response id

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* refactor(langfuse): read the response id through a typed protocol

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(langfuse): do not claim trace root when continuing an existing trace

Langfuse derives a trace's name and I/O from any observation flagged
langfuse.internal.as_root, so a request carrying existing_trace_id
renamed the trace to the generation name and replaced the trace input
and output on every continuation. v2 only updated the keys listed in
update_trace_keys. Continuations now export as plain children of the
remote parent and keep the explicit langfuse.trace.* attributes for the
fields they do want changed.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(langfuse): iterate lease renewal instead of recursing, monkeypatch update_trace_keys flag in tests

The recursive lease fallback tripped tests/code_coverage_tests/recursive_detector.py; the renewal
candidates are now walked with itertools.chain. The six update_trace_keys tests set the litellm
global through pytest monkeypatch so the TQ008 budget stays within its ceiling

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(langfuse): retry raised OTLP exports and honor LANGFUSE_TIMEOUT

The OTLP http exporter only retries 429 and 5xx; a connect or read timeout
propagates and BatchSpanProcessor drops the batch. Wrap the exporter in
RetryingSpanExporter (three backoff retries, as the v2 consumer did) and
build it on every path so the default and private-CA deployments share the
same channel, timeout and retry behaviour

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(langfuse): sample on a hash of the full trace id and tolerate bad LANGFUSE_SAMPLE_RATE

TraceIdRatioBased reads the low 64 bits of the trace id. litellm trace ids are
UUIDs, whose variant bits sit at the top of that word, so every fractional rate
up to 0.5 dropped all traces. A SHA-256 of the full id gives an unbiased,
deterministic decision. Values outside [0, 1] or non numeric now warn and export
everything instead of raising during callback construction, which surfaced as a
500 on the first request of each worker

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(langfuse): put the Langfuse trace link back into Slack alerts

The proxy registers LangfusePromptManagement for callbacks: ["langfuse"], so the alert helper never saw the literal "langfuse" string and returned before looking up the trace id, and the prompt management logger never stored the trace id it got back from log_event_on_langfuse. Recognize LangFuseLogger instances in the callback list, record the returned trace id in the shared service trace id cache, and skip the link when no trace id arrives

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* chore(deps): relock langfuse 4.15.2 and opentelemetry 1.33.1 on current main

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* chore(langfuse): mark the deliberate blind except in client teardown for the strict ruff gate

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* refactor(langfuse): pass the resource attributes mapping straight to Resource.create

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(langfuse): warn about ignored UPSTREAM_LANGFUSE_* on the shared client init path too

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(langfuse): normalise the OTLP export path so a trailing host slash never yields a double slash

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(langfuse): nest guardrail and grounding spans under the generation

Langfuse v4 derives the trace name and I/O from every observation marked as_root, and the one with the latest start time wins. Guardrail and grounding spans used to claim root next to the generation, so a post_call guardrail could replace the model's request and response on the trace with its own. Only the generation claims root now; the sibling spans become its children

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(langfuse): rebuild the cached bundle when mock mode or sample rate changes

The SDK keys resource bundles on the public key alone, so a bundle built with the discarding exporter for LANGFUSE_MOCK, or with an earlier LANGFUSE_SAMPLE_RATE, was handed back to a client that asked for a live exporter or a different rate. Compare both when deciding whether the cached bundle is still valid

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(langfuse): keep trace_public true when a guardrail span is exported

Langfuse folds langfuse.trace.public across every observation in the trace and reads a missing attribute as false, so a guardrail child span without the flag turned a trace_public: true request private on Langfuse Cloud. Child spans now repeat the generation's value

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* refactor(langfuse): emit observations as plain OTel spans, keep the SDK for prompts and auth

The callback now owns an isolated TracerProvider and OTLP exporter and builds generation and child spans with public OpenTelemetry APIs plus the LangfuseOtelSpanAttributes constants. Caller trace ids, generation ids, parent observation ids and historical start and end times are honoured through the OTel id generator, remote SpanContext and explicit span timestamps, so no private Langfuse SDK tracing handle is used any more. The Langfuse client stays only for get_prompt and auth_check

This also resolves the gauntlet findings on the previous draft: fresh traces start from an empty context so caller application spans are never stamped, the Slack trace link is read from the request logging state instead of constructing a logger per alert, a truthy non-mapping trace_metadata is serialized instead of raising, trace_input and trace_output land on the root generation, discarding a cached client is done under the lock, and the prompt cache no longer leaks a task manager because the client cache no longer tears down shared providers

Fixtures under tests/logging_callback_tests lose the SDK-private langfuse.internal.as_root marker; every other exported attribute is unchanged

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(langfuse): hand the SDK client a validated sample rate so an unusable LANGFUSE_SAMPLE_RATE no longer breaks the callback

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(langfuse): gate the SDK version before importing the OTel module in prompt management

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(langfuse): flush every export channel on proxy shutdown and use the callback's host in Slack trace links

The shutdown hook imported litellm.utils.langFuseLogger, a global the callback registry never assigns, so a graceful restart dropped the spans still queued in the batch processors. Shutdown now calls flush_langfuse_tracing, which force-flushes every acquired channel. The Slack alert link falls back to the registered LangFuseLogger's langfuse_host when the request carries no dynamic host, and the export endpoint tests pin that scheme-relative or absolute LANGFUSE_OTEL_TRACES_EXPORT_PATH values stay on the configured host

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(langfuse): store resolved credentials on LangfusePromptManagement

The Slack alert trace link reads langfuse_host from every registered LangFuseLogger. Prompt management subclasses it without calling the parent constructor, so it never set the attribute and the alerting handler crashed before posting

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(langfuse): flush every export channel concurrently under one shutdown deadline

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(langfuse): flush export channels on daemon threads so a stuck channel cannot hold up interpreter exit

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(langfuse): own the tracer config and drop the SDK client for prompts and auth

The callback's TracerProvider now sets its sampler, span limits and id generator explicitly so unrelated OTEL_* variables no longer change what Langfuse receives, and trace metadata is written once on the trace instead of folded into the generation, which kept input and output under the attribute cap. Spans are emitted under the langfuse-sdk scope so Langfuse renders them natively, the batch processor queues 100k spans and honors LANGFUSE_FLUSH_AT, and the proxy shutdown flush runs off the event loop with a 10s deadline and logs a miss.

Prompts, auth_check and the project id now go through LangfuseAPI directly with a litellm-owned TTL cache, so no Langfuse() client is built and a host application's client on the same public key is left alone. Dead attributes, the unreachable exporter branch and the export list are cleaned up, and the client-budget eviction behavior is documented.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* refactor(langfuse): export OTLP spans and fetch prompts through litellm's HTTPHandler instead of a private requests session

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(langfuse): gate the SDK version before importing the tracing module and retire unheld export channels

An installed v2 SDK used to fail inside the langfuse_sdk import and surface as "Langfuse not installed"; the version check now runs first so v2 users get the upgrade message, and only PackageNotFoundError means the package is missing

Export channels are now leased per credential set: acquire adds a holder, LangFuseLogger.stop (called by DynamicLoggingCache on expiry) releases one, and a channel with no holders is flushed and shut down after a 60 s grace, so rotating key or team credentials no longer grows one batch thread per credential set for the life of the process

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(langfuse): end the generation when a child span fails, take the client slot last, keep prompt cache keys structured

Generation spans now end in a finally block so a bad guardrail or provider entry cannot strand the trace. The logger acquires its export channel and REST client before counting a client slot and releases the channel synchronously if the REST client fails to build, so retries after a bad config do not exhaust the budget. LANGFUSE_TIMEOUT accepts decimals for the REST client like it already did for OTLP export. The prompt cache keys on (name, version, label) so a missing label and the literal label None stay apart

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(langfuse): claim the cache entry before releasing its slot and channel hold on eviction

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(langfuse): coerce generation names, keep v2 release, timeout and retry defaults, refresh stale prompts off the loop

A non-string metadata generation_name reached the OTLP encoder and took the whole batch down; it is now exported as its text and the exporter drops only the span the encoder rejects. LANGFUSE_RELEASE falls back to the deploy platform's commit variable again, the export deadline is back to the v2 default of 20 s and LANGFUSE_MAX_RETRIES sizes the retry ladder. An expired prompt is served at once while one background thread refreshes it, a re-acquired export channel cancels the pending retire timer, and flush reports delivery rather than a drained queue

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(proxy): assert the current Langfuse shutdown flush warning

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(langfuse): keep host OTel resource out, carry big metadata ints, tolerate bad flush and TTL env, stamp trace I/O under a parent

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(langfuse): name a malformed prompt cache TTL before the SDK import, keep metadata ints JSON safe, retry every 5xx export

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(langfuse): name the auth check failure, split a 413 export, wire LANGFUSE_DEBUG, stamp error output under a parent, send the ingestion version header

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(langfuse): honor LANGFUSE_DEBUG on the callbacks path, cap retry backoff, name the auth failure status and body

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(langfuse): cap LANGFUSE_MAX_RETRIES at 1000 so an absurd value cannot stall callback init

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* refactor(langfuse): fold 413 halving into bounded rounds instead of recursion

The code-quality recursive-function gate flagged LangfuseSpanExporter.export. A batch of n spans settles within n.bit_length() halving rounds, so the split is a reduce over a frozen round state with the same posts, logs and results. The TTL gate test now asserts the gate returns without raising

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(langfuse): truncate a single oversized span like v2 instead of dropping it, no retries on REST auth and project lookups

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(langfuse): write the metadata truncation marker under a flattened key so Langfuse keeps it

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(langfuse): patch the HTTPHandler export path and sync the metadata fixture and lease registry with the v4 callback

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* refactor(langfuse): give the 413 split helpers a single explicit return path

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(langfuse): url-encode prompt names and fetch cold prompts without client retries

A cold get_prompt runs inline on the event loop; the generated v4 client's default two retries slept through
Retry-After (up to 60 s per attempt) and held the loop. The wrapper also passed the raw name into
api/public/v2/prompts/{name}, so 'what?' fetched prompt 'what' and folder names left the route. Quote the
name with safe='' like the v4 SDK's own get_prompt and pass max_retries=0 like the projects.get calls

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(langfuse): retry a cold prompt miss once and drop upstream headers from prompt errors

A cold prompt fetch makes one immediate second attempt after a 5xx or a
transport failure, as the v2 client did, still with the generated client's
sleeping retries and Retry-After handling off so the event loop never stalls.
A failed fetch raises LangfusePromptError carrying only the status and body,
so the proxy no longer forwards Langfuse's response headers to its client

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(langfuse): stub the logger in the health auth_check test instead of dialing a closed port

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(langfuse): integration test for OTLP v4 delivery and prompt fetch through a real proxy

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* build(docker): keep the pip image's langfuse and otel pins on the v2 line its litellm 1.83.0 wheel expects

The image validates the published PyPI artifact, whose langfuse callback still
reads langfuse.version, so the 4.15.2 pin broke that callback. The pins move
together with the next LITELLM_VERSION bump. Also rewords the trace_version
precedence test docstring: v2 carried two version fields, v4 has one per span

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
yucheng-berri 2026-09-24 23:22:56 -07:00 • committed by GitHub
parent e106dbd8ba
commit e319bf270c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
45 changed files with 6159 additions and 3025 deletions

View file

@ -598,6 +598,7 @@ FIREWORKS_AI_DEFAULT_CACHE_READ_RATE_RATIO: Final = 0.5
#### Logging callback constants ####
REDACTED_BY_LITELM_STRING: Final = "REDACTED_BY_LITELM"
MAX_LANGFUSE_INITIALIZED_CLIENTS: Final = int(os.getenv("MAX_LANGFUSE_INITIALIZED_CLIENTS", 50))
LANGFUSE_SHUTDOWN_FLUSH_TIMEOUT_MILLIS: Final = 10_000
# Backpressure + lifetime bounds for the /v1/messages streaming relay (see
# BaseAnthropicMessagesStreamingIterator.async_sse_wrapper). The relay queue is
# bounded so a slow client throttles the upstream pump instead of letting it

View file

@ -3,9 +3,11 @@ Utils used for slack alerting
"""
import asyncio
from collections.abc import Callable
from typing import TYPE_CHECKING, Any, Final
import litellm
from litellm.integrations.custom_logger import CustomLogger
from litellm.proxy._types import AlertType
from litellm.secret_managers.main import get_secret
@ -66,25 +68,27 @@ async def _add_langfuse_trace_id_to_alert(
-> trace_id
-> litellm_call_id
"""
if "langfuse" not in litellm.logging_callback_manager._get_all_callbacks():
from litellm.integrations.langfuse.langfuse import LangFuseLogger, resolve_langfuse_host
callbacks: Final[list[CustomLogger | Callable[..., object] | str]] = (
litellm.logging_callback_manager._get_all_callbacks()
)
if not any(callback == "langfuse" or isinstance(callback, LangFuseLogger) for callback in callbacks):
return None
#########################################################
# Only run if langfuse is added as a callback
#########################################################
if request_data is not None and request_data.get("litellm_logging_obj", None) is not None:
trace_id: str | None = None
litellm_logging_obj: Final[Logging] = request_data["litellm_logging_obj"]
if request_data is None or request_data.get("litellm_logging_obj", None) is None:
return None
for _ in range(3):
trace_id = litellm_logging_obj._get_trace_id(service_name="langfuse")
if trace_id is not None:
break
await asyncio.sleep(3) # wait 3s before retrying for trace id
#########################################################
langfuse_object: Final = litellm_logging_obj._get_callback_object(service_name="langfuse")
if langfuse_object is not None:
base_url: Final = langfuse_object.Langfuse.base_url
return f"{base_url}/trace/{trace_id}"
litellm_logging_obj: Final[Logging] = request_data["litellm_logging_obj"]
instance_host: Final = next(
(callback.langfuse_host for callback in callbacks if isinstance(callback, LangFuseLogger)), None
)
host: Final = resolve_langfuse_host(
litellm_logging_obj.standard_callback_dynamic_params.get("langfuse_host") or instance_host
)
for _ in range(3):
if (trace_id := litellm_logging_obj._get_trace_id(service_name="langfuse")) is not None:
return f"{host}/trace/{trace_id}"
await asyncio.sleep(3) # wait 3s before retrying for trace id
return None

View file

@ -1,14 +1,14 @@
#### What this does ####
# On success, logs events to Langfuse
import inspect
import os
import re
import traceback
from collections.abc import Callable, Iterable, Mapping, Sequence
from datetime import datetime
from functools import lru_cache
from importlib.metadata import PackageNotFoundError, version
from types import MappingProxyType
from typing import TYPE_CHECKING, Any, Final, Literal, Protocol, cast
from typing import TYPE_CHECKING, Any, Final, Literal, Protocol, cast, runtime_checkable
from packaging.version import Version
@ -45,13 +45,13 @@ from litellm.types.utils import (
)
if TYPE_CHECKING:
from langfuse.client import Langfuse, StatefulTraceClient
from litellm.integrations.langfuse.langfuse_sdk import LangfuseApiClient, LangfuseObservation, LangfuseTracing
from litellm.litellm_core_utils.litellm_logging import DynamicLoggingCache
else:
DynamicLoggingCache = Any
StatefulTraceClient = Any
Langfuse = Any
LangfuseApiClient = Any
LangfuseObservation = Any
LangfuseTracing = Any
_DENIED_STEERING_KEYS: Final = frozenset({"headers", "endpoint", "caching_groups", "previous_models"})
@ -142,6 +142,20 @@ def _logging_id(start_time: datetime | None, response_obj: object) -> str | None
return litellm.utils.get_logging_id(start_time, response_obj)
@runtime_checkable
class _ResponseWithId(Protocol):
"""Response payloads (ModelResponse and friends, or a plain dict) expose their provider id via ``get``."""
def get(self, key: Literal["id"], default: None = None, /) -> object: ...
def _lookup_ids(litellm_call_id: str | None, response_obj: object) -> Mapping[str, str]:
"""v2 carried the response id inside the generation id; v4 hashes ids to 16 hex chars, so they ride in metadata."""
response_id: Final[object] = response_obj.get("id") if isinstance(response_obj, _ResponseWithId) else None
ids: Final[tuple[tuple[str, object], ...]] = (("litellm_call_id", litellm_call_id), ("response_id", response_id))
return MappingProxyType({key: str(value) for key, value in ids if value is not None})
def _as_steering_flag(value: object) -> bool:
"""A string ``str_to_bool`` does not recognise falls back to its truthiness."""
if isinstance(value, str):
@ -158,6 +172,68 @@ def _as_steering_key_sequence(value: object) -> tuple[str, ...]:
return ()
MINIMUM_LANGFUSE_VERSION: Final = "4.7"
UNSUPPORTED_LANGFUSE_VERSION: Final = "5"
PROMPT_CACHE_TTL_ENV: Final = "LANGFUSE_PROMPT_CACHE_DEFAULT_TTL_SECONDS"
def installed_langfuse_version() -> str:
"""Only ``importlib.metadata`` reads correctly on every major.
``langfuse.version`` was removed in v4, ``langfuse.__version__`` does not
exist in v3, and in v2 it reports a different value from the distribution
that is actually installed.
"""
return version("langfuse")
def raise_if_unsupported_langfuse_version(installed_version: str) -> None:
"""Fail at logger construction rather than dropping every event at request time.
v4 moved the callback onto OpenTelemetry, so on an older SDK the import of
`LangfuseOtelSpanAttributes` raises inside the per-request handler and the
broad except there turns it into silent total data loss.
"""
installed: Final = Version(installed_version)
# compare majors, not versions: "5.0.0rc1" sorts below "5" but is just as unsupported
if Version(MINIMUM_LANGFUSE_VERSION) <= installed and installed.major < Version(UNSUPPORTED_LANGFUSE_VERSION).major:
return
raise ImportError(
f"\033[91mlitellm requires langfuse>={MINIMUM_LANGFUSE_VERSION},<{UNSUPPORTED_LANGFUSE_VERSION} for the "
f"'langfuse' callback, but {installed_version} is installed. Run "
f"'pip install \"langfuse>={MINIMUM_LANGFUSE_VERSION},<{UNSUPPORTED_LANGFUSE_VERSION}\"' to upgrade, or use "
f"the 'langfuse_otel' callback, which does not depend on the langfuse SDK\033[0m"
)
def whole_number(raw: str) -> int | None:
try:
return int(raw)
except ValueError:
return None
def raise_if_unusable_prompt_cache_ttl() -> None:
"""The v4 SDK runs ``int()`` on this variable while it is being imported, so a value that is not a whole
number has to be named here, before that import fails with a bare ``ValueError`` on every request."""
raw: Final = os.environ.get(PROMPT_CACHE_TTL_ENV)
if raw is None or whole_number(raw) is not None:
return
raise ValueError(f"\033[91m{PROMPT_CACHE_TTL_ENV}={raw!r} must be a whole number of seconds\033[0m")
def _optional_str(value: object) -> str | None:
"""v4 sets attribute values raw; a non-string version would be dropped by the server."""
return str(value) if value is not None else None
def _trace_public_flag(value: object) -> bool | None:
"""``trace_public`` reaches here as a bool from metadata or a string from a ``langfuse_*`` header."""
if value is None:
return None
return _as_steering_flag(value)
def resolve_langfuse_credentials(
langfuse_public_key=None,
langfuse_secret=None,
@ -172,9 +248,29 @@ def resolve_langfuse_credentials(
secret_key = langfuse_secret or langfuse_secret_key or os.getenv("LANGFUSE_SECRET_KEY")
public_key = langfuse_public_key or os.getenv("LANGFUSE_PUBLIC_KEY")
resolved_host: Final = langfuse_host or os.getenv("LANGFUSE_HOST", "https://cloud.langfuse.com")
return public_key, secret_key, resolve_langfuse_host(langfuse_host)
return public_key, secret_key, resolved_host
def resolve_langfuse_host(langfuse_host: object = None) -> str:
"""The Langfuse base URL for ``langfuse_host`` with the env fallbacks, always carrying a scheme."""
resolved: Final = str(
langfuse_host or os.getenv("LANGFUSE_HOST") or os.getenv("LANGFUSE_BASE_URL") or "https://cloud.langfuse.com"
)
return resolved if resolved.startswith(("http://", "https://")) else f"http://{resolved}"
def warn_if_upstream_langfuse_configured() -> None:
if os.getenv("UPSTREAM_LANGFUSE_SECRET_KEY") is None:
return
verbose_logger.warning(
"UPSTREAM_LANGFUSE_* is no longer supported: the langfuse callback moved to SDK v4, "
"which has no second ingestion client. The values are ignored."
)
def parse_langfuse_debug(raw_value: str | None) -> bool:
"""Parse the LANGFUSE_DEBUG value into the boolean flag the langfuse client expects."""
return raw_value is not None and raw_value.strip().lower() in ("true", "1")
@lru_cache(maxsize=8)
@ -199,29 +295,29 @@ class LangFuseLogger:
allow_env_credentials: bool = True,
):
try:
import langfuse
from langfuse import Langfuse
except Exception as e:
self.langfuse_sdk_version: str = installed_langfuse_version()
except PackageNotFoundError as e:
raise Exception(
f"\033[91mLangfuse not installed, try running 'pip install langfuse' to fix this error: {e}\n{traceback.format_exc()}\033[0m"
)
f"\033[91mLangfuse not installed, try running 'pip install langfuse' to fix this error: {e}\033[0m"
) from e
raise_if_unsupported_langfuse_version(self.langfuse_sdk_version)
raise_if_unusable_prompt_cache_ttl()
from litellm.integrations.langfuse.langfuse_sdk import configured_release
self.public_key, self.secret_key, self.langfuse_host = resolve_langfuse_credentials(
langfuse_public_key=langfuse_public_key,
langfuse_secret=langfuse_secret,
langfuse_host=langfuse_host,
allow_env_credentials=allow_env_credentials,
)
if not (self.langfuse_host.startswith("http://") or self.langfuse_host.startswith("https://")):
# add http:// if unset, assume communicating over private network - e.g. render
self.langfuse_host = "http://" + self.langfuse_host
_env_override: Final = str(langfuse_environment).strip() if langfuse_environment is not None else None
if _env_override:
validate_langfuse_environment_value(_env_override)
self.langfuse_environment: str | None = _env_override
else:
self.langfuse_environment = self.resolve_deployment_environment()
self.langfuse_release = os.getenv("LANGFUSE_RELEASE")
self.langfuse_debug = os.getenv("LANGFUSE_DEBUG")
self.langfuse_release = configured_release()
self.langfuse_debug = parse_langfuse_debug(os.getenv("LANGFUSE_DEBUG"))
self.langfuse_flush_interval = LangFuseLogger._get_langfuse_flush_interval(flush_interval)
if should_use_langfuse_mock():
@ -232,22 +328,9 @@ class LangFuseLogger:
self.langfuse_client = self._http_handler.client
self.is_mock_mode = False
parameters: Final = {
"public_key": self.public_key,
"secret_key": self.secret_key,
"host": self.langfuse_host,
"release": self.langfuse_release,
"debug": self.langfuse_debug,
"flush_interval": self.langfuse_flush_interval, # flush interval in seconds
"httpx_client": self.langfuse_client,
}
self.langfuse_sdk_version: str = langfuse.version.__version__
if "environment" in inspect.signature(Langfuse.__init__).parameters:
parameters["environment"] = self.langfuse_environment
if Version(self.langfuse_sdk_version) >= Version("2.6.0"):
parameters["sdk_integration"] = "litellm"
self.Langfuse: Langfuse = self.safe_init_langfuse_client(parameters)
self.api_client: LangfuseApiClient
self.tracing: LangfuseTracing
self.api_client, self.tracing = self.safe_init_langfuse_client()
# set the current langfuse project id in the environ
# this is used by Alerting to link to the correct project
@ -256,49 +339,62 @@ class LangFuseLogger:
verbose_logger.debug("Langfuse Mock: Using mock project ID")
else:
try:
project_id = self.Langfuse.client.projects.get().data[0].id
os.environ["LANGFUSE_PROJECT_ID"] = project_id
project_id: Final = self.api_client.project_id()
if project_id is not None:
os.environ["LANGFUSE_PROJECT_ID"] = project_id
except Exception:
project_id = None
verbose_logger.debug("Langfuse project id unavailable, alerting links will omit it")
if os.getenv("UPSTREAM_LANGFUSE_SECRET_KEY") is not None:
upstream_langfuse_debug_env: Final = os.getenv("UPSTREAM_LANGFUSE_DEBUG")
upstream_langfuse_debug: Final = (
str_to_bool(upstream_langfuse_debug_env) if upstream_langfuse_debug_env is not None else None
)
self.upstream_langfuse_secret_key = os.getenv("UPSTREAM_LANGFUSE_SECRET_KEY")
self.upstream_langfuse_public_key = os.getenv("UPSTREAM_LANGFUSE_PUBLIC_KEY")
self.upstream_langfuse_host = os.getenv("UPSTREAM_LANGFUSE_HOST")
self.upstream_langfuse_release = os.getenv("UPSTREAM_LANGFUSE_RELEASE")
self.upstream_langfuse_debug = upstream_langfuse_debug_env
self.upstream_langfuse = Langfuse(
public_key=self.upstream_langfuse_public_key,
secret_key=self.upstream_langfuse_secret_key,
host=self.upstream_langfuse_host,
release=self.upstream_langfuse_release,
debug=(upstream_langfuse_debug if upstream_langfuse_debug is not None else False),
)
else:
self.upstream_langfuse = None
warn_if_upstream_langfuse_configured()
def safe_init_langfuse_client(self, parameters: dict) -> Langfuse:
def safe_init_langfuse_client(self) -> "tuple[LangfuseApiClient, LangfuseTracing]":
"""Build the REST client and export channel while the process is under its logger budget.
The budget dates from the SDK client, which started a consumer thread per instance and once
pinned a CPU at 100% when many were built; it still bounds the number of per-key loggers.
"""
Safely init a langfuse client if the number of initialized clients is less than the max
Note:
- Langfuse initializes 1 thread everytime a client is initialized.
- We've had an incident in the past where we reached 100% cpu utilization because Langfuse was initialized several times.
"""
from langfuse import Langfuse
if litellm.initialized_langfuse_clients >= MAX_LANGFUSE_INITIALIZED_CLIENTS:
raise Exception(
f"Max langfuse clients reached: {litellm.initialized_langfuse_clients} is greater than {MAX_LANGFUSE_INITIALIZED_CLIENTS}"
)
langfuse_client: Final = Langfuse(**parameters)
from litellm.integrations.langfuse.langfuse_sdk import (
acquire_langfuse_tracing,
build_langfuse_client,
release_langfuse_tracing,
)
tracing: Final = acquire_langfuse_tracing(
public_key=str(self.public_key),
secret_key=str(self.secret_key),
base_url=self.langfuse_host,
environment=self.langfuse_environment,
release=self.langfuse_release,
flush_interval=self.langfuse_flush_interval,
mock_mode=self.is_mock_mode,
)
try:
api_client: Final = build_langfuse_client(
public_key=self.public_key,
secret_key=self.secret_key,
base_url=self.langfuse_host,
httpx_client=self.langfuse_client,
)
except Exception:
release_langfuse_tracing(tracing, grace_seconds=0.0)
raise
litellm.initialized_langfuse_clients += 1
verbose_logger.debug("Created langfuse client number %s", litellm.initialized_langfuse_clients)
return langfuse_client
return api_client, tracing
def flush(self) -> None:
"""Push every queued observation to Langfuse before the process goes away."""
self.tracing.flush()
def stop(self) -> None:
"""Give the export channel back; ``DynamicLoggingCache`` calls this when a per-key logger expires."""
from litellm.integrations.langfuse.langfuse_sdk import release_langfuse_tracing
release_langfuse_tracing(self.tracing)
@staticmethod
def add_metadata_from_header(litellm_params: dict, metadata: dict) -> dict[str, object]:
@ -349,7 +445,7 @@ class LangFuseLogger:
user_id: str | None = None,
level: str = "DEFAULT",
status_message: str | None = None,
) -> dict:
) -> LangfuseLoggedEvent:
"""
Logs a success or error event on Langfuse
"""
@ -411,10 +507,10 @@ class LangFuseLogger:
verbose_logger.debug("Langfuse Layer Logging - final response object: %s", response_obj)
verbose_logger.info("Langfuse Layer Logging - logging success")
return {"trace_id": trace_id, "generation_id": generation_id}
return LangfuseLoggedEvent(trace_id=trace_id, generation_id=generation_id)
except Exception as e:
verbose_logger.exception("Langfuse Layer Error(): Exception occured - %s", e)
return {"trace_id": None, "generation_id": None}
return LangfuseLoggedEvent(trace_id=None, generation_id=None)
def _get_langfuse_input_output_content(
self,
@ -518,18 +614,14 @@ class LangFuseLogger:
level: str,
litellm_call_id: str | None,
) -> tuple:
verbose_logger.debug("Langfuse Layer Logging - logging to langfuse v2")
verbose_logger.debug("Langfuse Layer Logging - logging to langfuse via sdk v%s", self.langfuse_sdk_version)
try:
standard_logging_object: Final[StandardLoggingPayload | None] = cast(
StandardLoggingPayload | None,
kwargs.get("standard_logging_object", None),
)
tags = (
self._get_langfuse_tags(standard_logging_object=standard_logging_object)
if self._supports_tags()
else []
)
tags = self._get_langfuse_tags(standard_logging_object=standard_logging_object)
allowlisted_metadata: Final[StandardLoggingMetadata | Mapping[str, object]] = (
standard_logging_object["metadata"] if standard_logging_object is not None else _NO_METADATA
@ -581,17 +673,17 @@ class LangFuseLogger:
# This allows continuing an existing trace while still returning the correct trace_id
if existing_trace_id is not None:
trace_id = existing_trace_id
resolved_trace_id: Final = (
call_trace_id: Final = (
litellm_call_id or trace_id
if existing_trace_id is None
and _is_session_header_trace(trace_id, session_id, litellm_params.get("proxy_server_request"))
else trace_id
)
if resolved_trace_id != trace_id:
if call_trace_id != trace_id:
verbose_logger.debug(
"Langfuse: trace_id %s came from a session header; using call id %s so each call gets its own trace",
trace_id,
resolved_trace_id,
call_trace_id,
)
requested_trace_keys: Final = _as_steering_key_sequence(clean_metadata.pop("update_trace_keys", ()))
update_trace_keys: Final = (
@ -647,7 +739,7 @@ class LangFuseLogger:
trace_params["output"] = masked_output if not mask_output else "redacted-by-litellm"
else: # don't overwrite an existing trace
trace_params = {
"id": resolved_trace_id,
"id": call_trace_id,
"name": trace_name,
"session_id": session_id,
"input": masked_input if not mask_input else "redacted-by-litellm",
@ -659,10 +751,7 @@ class LangFuseLogger:
for key in list(filter(lambda key: key.startswith("trace_"), clean_metadata.keys())):
trace_params[key.replace("trace_", "")] = clean_metadata.pop(key, None)
if level == "ERROR":
trace_params["status_message"] = masked_output
else:
trace_params["output"] = masked_output if not mask_output else "redacted-by-litellm"
trace_params["output"] = masked_output if not mask_output else "redacted-by-litellm"
if debug is True or (isinstance(debug, str) and debug.lower() == "true"):
debug_metadata: Final = {
@ -697,17 +786,16 @@ class LangFuseLogger:
("api_base", api_base, bool(api_base)),
("vertex_location", vertex_location, bool(vertex_location)),
("aws_region_name", aws_region_name, bool(aws_region_name)),
("cache_hit", kwargs.get("cache_hit") or False, self._supports_tags() and "cache_hit" in kwargs),
("cache_hit", kwargs.get("cache_hit") or False, "cache_hit" in kwargs),
)
enrichments: Final[Mapping[str, object]] = {
key: value for key, value, include in candidate_enrichments if include
}
if self._supports_tags():
if "cache_hit" in kwargs and kwargs["cache_hit"] is None:
kwargs["cache_hit"] = False # rebind-ok: pre-existing normalization other integrations rely on
if existing_trace_id is None:
trace_params.update({"tags": tags})
if "cache_hit" in kwargs and kwargs["cache_hit"] is None:
kwargs["cache_hit"] = False # rebind-ok: pre-existing normalization other integrations rely on
if existing_trace_id is None:
trace_params.update({"tags": tags})
proxy_server_request: Final = litellm_params.get("proxy_server_request", None)
if proxy_server_request:
@ -721,17 +809,6 @@ class LangFuseLogger:
if key.lower() not in _REDACTED_PROXY_HEADERS:
clean_headers[key] = value
trace: Final[StatefulTraceClient] = self.Langfuse.trace(**trace_params)
# Log provider specific information as a span
log_provider_specific_information_as_span(trace, enrichments)
# Log guardrail information as a span
self._log_guardrail_information_as_span(
trace=trace,
standard_logging_object=standard_logging_object,
)
generation_id = None
usage = None
usage_details = None
@ -753,7 +830,7 @@ class LangFuseLogger:
usage = {
"prompt_tokens": prompt_tokens,
"completion_tokens": completion_tokens,
"total_cost": cost if self._supports_costs() else None,
"total_cost": cost,
}
# According to langfuse documentation: "the input value must be reduced by the number of cache_read_input_tokens"
input_tokens: Final = prompt_tokens - cache_read_input_tokens
@ -765,15 +842,15 @@ class LangFuseLogger:
cache_read_input_tokens=cache_read_input_tokens,
)
generation_name = clean_metadata.pop("generation_name", None)
if generation_name is None:
# if `generation_name` is None, use sensible default values
# If using litellm proxy user `key_alias` if not None
# If `key_alias` is None, just log `litellm-{call_type}` as the generation name
_user_api_key_alias: Final = cast(str | None, clean_metadata.get("user_api_key_alias", None))
generation_name = f"litellm-{cast(str, kwargs.get('call_type', 'completion'))}"
if _user_api_key_alias is not None:
generation_name = f"litellm:{_user_api_key_alias}"
requested_generation_name: Final = clean_metadata.pop("generation_name", None)
_user_api_key_alias: Final = cast(str | None, clean_metadata.get("user_api_key_alias", None))
generation_name: Final = (
str(requested_generation_name)
if requested_generation_name is not None
else f"litellm:{_user_api_key_alias}"
if _user_api_key_alias is not None
else f"litellm-{cast(str, kwargs.get('call_type', 'completion'))}"
)
if response_obj is not None:
system_fingerprint = getattr(response_obj, "system_fingerprint", None)
@ -789,53 +866,97 @@ class LangFuseLogger:
generation_params = {
"name": generation_name,
"id": clean_metadata.pop("generation_id", generation_id),
"start_time": start_time,
"end_time": end_time,
"model": model_name,
"model_parameters": optional_params,
"input": masked_input if not mask_input else "redacted-by-litellm",
"output": masked_output if not mask_output else "redacted-by-litellm",
"usage": usage,
"usage_details": usage_details,
"metadata": {
**log_requester_metadata(redact_user_api_key_info(metadata=allowlisted_metadata)),
"cost_details": {"total": cost} # mutable-ok: langfuse serializes this payload
if usage is not None and isinstance(cost, (int, float))
else None,
"metadata": { # mutable-ok: langfuse serializes this payload, a proxy is not json-encodable
**log_requester_metadata(redact_user_api_key_info(metadata=allowlisted_metadata)), # pyright: ignore[reportArgumentType] # TypedDict in, plain metadata dict out
**enrichments,
**_lookup_ids(litellm_call_id, response_obj),
},
"level": level,
"version": clean_metadata.pop("version", None),
"version": _optional_str(clean_metadata.pop("version", None)),
}
parent_observation_id: Final = metadata.get("parent_observation_id", None)
if parent_observation_id is not None:
generation_params["parent_observation_id"] = parent_observation_id
if self._supports_prompt():
generation_params = _add_prompt_to_generation_params(
generation_params=generation_params,
clean_metadata=clean_metadata,
prompt_management_metadata=prompt_management_metadata,
langfuse_client=self.Langfuse,
)
generation_params = _add_prompt_to_generation_params(
generation_params=generation_params,
clean_metadata=clean_metadata,
prompt_management_metadata=prompt_management_metadata,
langfuse_client=self.api_client,
)
if masked_output is not None and isinstance(masked_output, str) and level == "ERROR":
generation_params["status_message"] = masked_output
if self._supports_completion_start_time():
generation_params["completion_start_time"] = kwargs.get("completion_start_time", None)
# langfuse ships in the proxy-runtime extra, so this module must import cleanly without it
from litellm.integrations.langfuse.langfuse_sdk import (
observation_attributes,
resolve_observation_id,
resolve_trace_id,
start_generation,
trace_attributes,
)
generation_client: Final = trace.generation(**generation_params)
resolved_trace_id: Final = resolve_trace_id(call_trace_id) # pyright: ignore[reportArgumentType] # metadata value, str or None at runtime
continued_trace: Final = existing_trace_id is not None
generation_is_trace_root: Final = not continued_trace and parent_observation_id is None
trace_public: Final = _trace_public_flag(trace_params.get("public"))
trace_input: Final = trace_params.get("input")
trace_output: Final = trace_params.get("output")
trace_level_attributes: Final = trace_attributes(
name=trace_params.get("name"),
user_id=trace_params.get("user_id"),
session_id=trace_params.get("session_id"),
version=trace_params.get("version"),
release=trace_params.get("release"),
tags=trace_params.get("tags"),
metadata=trace_params.get("metadata"),
public=trace_public,
input=None if generation_is_trace_root and trace_input == generation_params["input"] else trace_input,
output=None
if generation_is_trace_root and trace_output == generation_params["output"]
else trace_output,
)
generation_attributes: Final = observation_attributes(
observation_type="generation",
input=generation_params["input"],
output=generation_params["output"],
metadata=generation_params["metadata"],
level=level,
status_message=generation_params.get("status_message"),
version=generation_params["version"],
model=model_name,
model_parameters=optional_params,
usage_details=usage_details,
cost_details=generation_params["cost_details"],
completion_start_time=kwargs.get("completion_start_time", None),
prompt=generation_params.get("prompt"),
)
generation: Final = start_generation(
tracing=self.tracing,
trace_id=resolved_trace_id,
parent_observation_id=resolve_observation_id(parent_observation_id), # pyright: ignore[reportArgumentType] # metadata value, str or None at runtime
existing_trace=continued_trace,
observation_id=resolve_observation_id(generation_params["id"]),
name=generation_params["name"], # pyright: ignore[reportArgumentType] # always the str set a few lines up
start_time=start_time,
public=trace_public,
attributes=MappingProxyType({**generation_attributes, **trace_level_attributes}),
)
try:
log_provider_specific_information_as_span(
tracing=self.tracing, parent=generation, enrichments=enrichments
)
self._log_guardrail_information_as_span(
tracing=self.tracing, parent=generation, standard_logging_object=standard_logging_object
)
finally:
generation.end(end_time)
# Return the trace_id we set (which should be litellm_call_id when no explicit trace_id provided)
# We explicitly set trace_id in trace_params["id"], so langfuse should use it
# Verify langfuse accepted our trace_id; if it differs, log a warning but still return our intended value
# to match expected test behavior
if hasattr(generation_client, "trace_id") and generation_client.trace_id:
if generation_client.trace_id != resolved_trace_id:
verbose_logger.warning(
"Langfuse trace_id mismatch: set %s, but langfuse returned %s. Using our intended trace_id for consistency.",
resolved_trace_id,
generation_client.trace_id,
)
return resolved_trace_id, generation_id
# log_event_on_langfuse tuple-unpacks this and re-wraps it in the dict callers cache.
# The observation id is the requested generation_id after resolve_observation_id.
return resolved_trace_id, generation.id
except Exception:
verbose_logger.error("Langfuse Layer Error - %s", traceback.format_exc())
return None, None
@ -904,27 +1025,11 @@ class LangFuseLogger:
_cache_key = _hidden_params.get("cache_key", None)
if _cache_key is None and litellm.cache is not None:
# fallback to using "preset_cache_key"
_preset_cache_key: Final = litellm.cache._get_preset_cache_key_from_kwargs(**kwargs)
_preset_cache_key: Final = litellm.cache._get_preset_cache_key_from_kwargs(**kwargs) # pyright: ignore[reportPrivateUsage] # kwargs-ok: no public preset-cache-key accessor
_cache_key = _preset_cache_key
tags.append(f"cache_key:{_cache_key}")
return tags
def _supports_tags(self):
"""Check if current langfuse version supports tags"""
return Version(self.langfuse_sdk_version) >= Version("2.6.3")
def _supports_prompt(self):
"""Check if current langfuse version supports prompt"""
return Version(self.langfuse_sdk_version) >= Version("2.7.3")
def _supports_costs(self):
"""Check if current langfuse version supports costs"""
return Version(self.langfuse_sdk_version) >= Version("2.7.3")
def _supports_completion_start_time(self):
"""Check if current langfuse version supports completion start time"""
return Version(self.langfuse_sdk_version) >= Version("2.7.3")
@staticmethod
def _apply_masking_function(data: object, masking_function: Callable[[object], object]) -> object:
"""
@ -973,23 +1078,24 @@ class LangFuseLogger:
@staticmethod
def _get_langfuse_flush_interval(flush_interval: int) -> int:
"""
Get the langfuse flush interval to initialize the Langfuse client
Reads `LANGFUSE_FLUSH_INTERVAL` from the environment variable.
If not set, uses the flush interval passed in as an argument.
Args:
flush_interval: The flush interval to use if LANGFUSE_FLUSH_INTERVAL is not set
Returns:
[int] The flush interval to use to initialize the Langfuse client
"""
return int(os.getenv("LANGFUSE_FLUSH_INTERVAL") or flush_interval)
"""``LANGFUSE_FLUSH_INTERVAL`` in whole seconds above 0 (the export scheduler's delay), else ``flush_interval``."""
raw: Final = os.getenv("LANGFUSE_FLUSH_INTERVAL")
if not raw:
return flush_interval
parsed: Final = int(raw) if raw.strip().isdigit() else None
if parsed is None or parsed <= 0:
verbose_logger.warning(
"LANGFUSE_FLUSH_INTERVAL=%r is not a whole number of seconds above 0; flushing every %d s",
raw,
flush_interval,
)
return flush_interval
return parsed
def _log_guardrail_information_as_span(
self,
trace: StatefulTraceClient,
tracing: "LangfuseTracing",
parent: "LangfuseObservation",
standard_logging_object: StandardLoggingPayload | None,
):
"""
@ -1011,6 +1117,8 @@ class LangFuseLogger:
)
return
from litellm.integrations.langfuse.langfuse_sdk import observation_attributes, start_child_span
for guardrail_entry in guardrail_information:
if not isinstance(guardrail_entry, dict):
verbose_logger.debug(
@ -1019,30 +1127,35 @@ class LangFuseLogger:
)
continue
span = trace.span(
span = start_child_span(
tracing=tracing,
parent=parent,
name="guardrail",
input=guardrail_entry.get("guardrail_request", None),
output=guardrail_entry.get("guardrail_response", None),
metadata={
"guardrail_name": guardrail_entry.get("guardrail_name", None),
"guardrail_mode": guardrail_entry.get("guardrail_mode", None),
"guardrail_masked_entity_count": guardrail_entry.get("masked_entity_count", None),
},
start_time=guardrail_entry.get("start_time", None),
end_time=guardrail_entry.get("end_time", None),
attributes=observation_attributes(
observation_type="span",
input=guardrail_entry.get("guardrail_request", None),
output=guardrail_entry.get("guardrail_response", None),
metadata=MappingProxyType(
{
"guardrail_name": guardrail_entry.get("guardrail_name", None),
"guardrail_mode": guardrail_entry.get("guardrail_mode", None),
"guardrail_masked_entity_count": guardrail_entry.get("masked_entity_count", None),
}
),
),
)
verbose_logger.debug("Logged guardrail information as span: %s", span)
span.end()
span.end(guardrail_entry.get("end_time", None))
def _add_prompt_to_generation_params(
generation_params: dict,
clean_metadata: dict,
prompt_management_metadata: StandardLoggingPromptManagementMetadata | None,
langfuse_client: object,
langfuse_client: "LangfuseApiClient",
) -> dict:
from langfuse import Langfuse
from langfuse.model import (
ChatPromptClient,
Prompt_Chat,
@ -1050,8 +1163,6 @@ def _add_prompt_to_generation_params(
TextPromptClient,
)
langfuse_client = cast(Langfuse, langfuse_client)
user_prompt: Final = clean_metadata.pop("prompt", None)
if user_prompt is None and prompt_management_metadata is None:
pass
@ -1075,7 +1186,7 @@ def _add_prompt_to_generation_params(
if "labels" in prompt_text_params and "tags" in prompt_text_params:
_data["labels"] = user_prompt.get("labels", []) or []
_data["tags"] = user_prompt.get("tags", []) or []
_prompt_obj = Prompt_Text(**_data)
_prompt_obj = Prompt_Text(**_data) # pyright: ignore[reportArgumentType] # kwargs-ok: shape mirrors the pydantic model, values from the user's prompt dict
generation_params["prompt"] = TextPromptClient(prompt=_prompt_obj)
elif isinstance(user_prompt["prompt"], list):
@ -1090,7 +1201,7 @@ def _add_prompt_to_generation_params(
_data["labels"] = user_prompt.get("labels", []) or []
_data["tags"] = user_prompt.get("tags", []) or []
_prompt_obj = Prompt_Chat(**_data)
_prompt_obj = Prompt_Chat(**_data) # pyright: ignore[reportArgumentType] # kwargs-ok: shape mirrors the pydantic model, values from the user's prompt dict
generation_params["prompt"] = ChatPromptClient(prompt=_prompt_obj)
else:
@ -1110,21 +1221,14 @@ def _add_prompt_to_generation_params(
def log_provider_specific_information_as_span(
trace,
clean_metadata: Mapping[str, Any],
*,
tracing: "LangfuseTracing",
parent: "LangfuseObservation",
enrichments: Mapping[str, Any],
):
"""
Logs provider-specific information as spans.
"""Logs provider-specific information as spans under the generation."""
Parameters:
trace: The tracing object used to log spans.
clean_metadata: A dictionary containing metadata to be logged.
Returns:
None
"""
_hidden_params: Final[Mapping[str, object] | None] = clean_metadata.get("hidden_params", None)
_hidden_params: Final[Mapping[str, object] | None] = enrichments.get("hidden_params", None)
if _hidden_params is None:
return
@ -1135,22 +1239,27 @@ def log_provider_specific_information_as_span(
for elem in vertex_ai_grounding_metadata:
if isinstance(elem, dict):
for key, value in elem.items():
trace.span(
name=key,
input=value,
)
_end_grounding_span(tracing=tracing, parent=parent, name=key, value=value)
else:
trace.span(
name="vertex_ai_grounding_metadata",
input=elem,
)
_end_grounding_span(tracing=tracing, parent=parent, name="vertex_ai_grounding_metadata", value=elem)
else:
trace.span(
name="vertex_ai_grounding_metadata",
input=vertex_ai_grounding_metadata,
_end_grounding_span(
tracing=tracing, parent=parent, name="vertex_ai_grounding_metadata", value=vertex_ai_grounding_metadata
)
def _end_grounding_span(*, tracing: "LangfuseTracing", parent: "LangfuseObservation", name: str, value: object) -> None:
from litellm.integrations.langfuse.langfuse_sdk import observation_attributes, start_child_span
start_child_span(
tracing=tracing,
parent=parent,
name=name,
start_time=None,
attributes=observation_attributes(observation_type="span", input=value),
).end()
def log_requester_metadata(clean_metadata: Mapping[str, Any]):
returned_metadata: Final = {}
requester_metadata: Final = clean_metadata.get("requester_metadata") or {}

View file

@ -2,16 +2,14 @@
Call Hook for LiteLLM Proxy which allows Langfuse prompt management.
"""
import inspect
import os
from functools import lru_cache
from typing import TYPE_CHECKING, Any, Final, Literal, TypeAlias, cast
from packaging.version import Version
from litellm.integrations.custom_logger import CustomLogger
from litellm.integrations.prompt_management_base import PromptManagementClient
from litellm.litellm_core_utils.asyncify import run_async_function
from litellm.llms.custom_httpx.http_handler import HTTPHandler
from litellm.types.integrations.langfuse import LangfuseLoggedEvent
from litellm.types.llms.openai import AllMessageValues, ChatCompletionSystemMessage
from litellm.types.prompts.init_prompts import PromptSpec
from litellm.types.utils import StandardCallbackDynamicParams, StandardLoggingPayload
@ -19,17 +17,27 @@ from litellm.types.utils import StandardCallbackDynamicParams, StandardLoggingPa
from ...litellm_core_utils.specialty_caches.dynamic_logging_cache import (
DynamicLoggingCache,
)
from ...litellm_core_utils.specialty_caches.service_trace_id_cache import in_memory_trace_id_cache
from ..prompt_management_base import PromptManagementBase
from .langfuse import LangFuseLogger, resolve_langfuse_credentials
from .langfuse import (
LangFuseLogger,
installed_langfuse_version,
raise_if_unsupported_langfuse_version,
raise_if_unusable_prompt_cache_ttl,
resolve_langfuse_credentials,
warn_if_upstream_langfuse_configured,
)
from .langfuse_handler import LangFuseHandler
from .langfuse_mock_client import create_mock_langfuse_client, should_use_langfuse_mock
if TYPE_CHECKING:
from langfuse import Langfuse
from langfuse.client import ChatPromptClient, TextPromptClient
from langfuse.model import ChatPromptClient, TextPromptClient
from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj
LangfuseClass: TypeAlias = Langfuse
from .langfuse_sdk import LangfuseApiClient
LangfuseClass: TypeAlias = LangfuseApiClient
PROMPT_CLIENT = TextPromptClient | ChatPromptClient
else:
@ -49,23 +57,24 @@ def langfuse_client_init(
allow_env_credentials: bool = True,
) -> LangfuseClass:
"""
Initialize Langfuse client with caching to prevent multiple initializations.
Initialize the Langfuse REST client with caching to prevent multiple initializations.
Args:
langfuse_public_key (str, optional): Public key for Langfuse. Defaults to None.
langfuse_secret (str, optional): Secret key for Langfuse. Defaults to None.
langfuse_host (str, optional): Host URL for Langfuse. Defaults to None.
flush_interval (int, optional): Flush interval in seconds. Defaults to 1.
flush_interval (int, optional): Kept in the signature so cached callers keep their cache key.
Returns:
Langfuse: Initialized Langfuse client instance
LangfuseApiClient: prompt, auth and project lookups for one credential set
Raises:
Exception: If langfuse package is not installed
"""
raise_if_unsupported_langfuse_version(installed_langfuse_version())
raise_if_unusable_prompt_cache_ttl()
try:
import langfuse
from langfuse import Langfuse
from .langfuse_sdk import build_langfuse_client
except Exception as e:
raise Exception(
f"\033[91mLangfuse not installed, try running 'pip install langfuse' to fix this error: {e}\n\033[0m"
@ -83,39 +92,22 @@ def langfuse_client_init(
# add http:// if unset, assume communicating over private network - e.g. render
langfuse_host = "http://" + langfuse_host
langfuse_release: Final = os.getenv("LANGFUSE_RELEASE")
langfuse_debug: Final = os.getenv("LANGFUSE_DEBUG")
warn_if_upstream_langfuse_configured()
parameters: Final = {
"public_key": public_key,
"secret_key": secret_key,
"host": langfuse_host,
"release": langfuse_release,
"debug": langfuse_debug,
"flush_interval": LangFuseLogger._get_langfuse_flush_interval(flush_interval), # flush interval in seconds
}
httpx_client: Final = create_mock_langfuse_client() if should_use_langfuse_mock() else HTTPHandler().client
return build_langfuse_client(
public_key=public_key,
secret_key=secret_key,
base_url=langfuse_host,
httpx_client=httpx_client,
)
if Version(langfuse.version.__version__) >= Version("2.6.0"):
parameters["sdk_integration"] = "litellm"
if Version(langfuse.version.__version__) >= Version("2.7.3"):
import httpx
import litellm
from ...llms.custom_httpx.http_handler import get_ssl_configuration
parameters["httpx_client"] = httpx.Client(
verify=get_ssl_configuration(),
cert=os.getenv("SSL_CERTIFICATE", litellm.ssl_certificate),
)
if "environment" in inspect.signature(Langfuse.__init__).parameters:
parameters["environment"] = LangFuseLogger.resolve_deployment_environment()
client: Final = Langfuse(**parameters)
return client
def _remember_trace_id(litellm_call_id: object, logged: LangfuseLoggedEvent) -> None:
trace_id: Final = logged["trace_id"]
if not isinstance(litellm_call_id, str) or trace_id is None:
return
in_memory_trace_id_cache.set_cache(litellm_call_id=litellm_call_id, service_name="langfuse", trace_id=trace_id)
class LangfusePromptManagement(LangFuseLogger, PromptManagementBase, CustomLogger):
@ -126,15 +118,33 @@ class LangfusePromptManagement(LangFuseLogger, PromptManagementBase, CustomLogge
langfuse_host=None,
flush_interval=1,
):
import langfuse
self.langfuse_sdk_version = langfuse.version.__version__
self.Langfuse = langfuse_client_init(
self.langfuse_sdk_version = installed_langfuse_version()
raise_if_unsupported_langfuse_version(self.langfuse_sdk_version)
raise_if_unusable_prompt_cache_ttl()
from .langfuse_sdk import acquire_langfuse_tracing, configured_release
self.api_client = langfuse_client_init(
langfuse_public_key=langfuse_public_key,
langfuse_secret=langfuse_secret,
langfuse_host=langfuse_host,
flush_interval=flush_interval,
)
self.public_key, self.secret_key, self.langfuse_host = resolve_langfuse_credentials(
langfuse_public_key=langfuse_public_key,
langfuse_secret=langfuse_secret,
langfuse_host=langfuse_host,
)
self.tracing = acquire_langfuse_tracing(
public_key=str(self.public_key),
secret_key=str(self.secret_key),
base_url=self.langfuse_host,
environment=LangFuseLogger.resolve_deployment_environment(),
release=configured_release(),
flush_interval=LangFuseLogger._get_langfuse_flush_interval(flush_interval), # pyright: ignore[reportPrivateUsage] # shared env-fallback helper, not part of the logger's API
mock_mode=should_use_langfuse_mock(),
)
@property
def integration_name(self):
@ -228,11 +238,8 @@ class LangfusePromptManagement(LangFuseLogger, PromptManagementBase, CustomLogge
langfuse_host=dynamic_callback_params.get("langfuse_host"),
allow_env_credentials=dynamic_callback_params.get("langfuse_host") is None,
)
langfuse_prompt_client: Final = self._get_prompt_from_id(
langfuse_prompt_id=prompt_id,
langfuse_client=langfuse_client,
)
return langfuse_prompt_client is not None
self._get_prompt_from_id(langfuse_prompt_id=prompt_id, langfuse_client=langfuse_client)
return True
def _compile_prompt_helper(
self,
@ -311,13 +318,14 @@ class LangfusePromptManagement(LangFuseLogger, PromptManagementBase, CustomLogge
standard_callback_dynamic_params=standard_callback_dynamic_params,
in_memory_dynamic_logger_cache=in_memory_dynamic_logger_cache,
)
langfuse_logger_to_use.log_event_on_langfuse(
logged: Final = langfuse_logger_to_use.log_event_on_langfuse(
kwargs=kwargs,
response_obj=response_obj,
start_time=start_time,
end_time=end_time,
user_id=kwargs.get("user", None),
)
_remember_trace_id(litellm_call_id=kwargs.get("litellm_call_id"), logged=logged)
except Exception as e:
from litellm._logging import verbose_logger
@ -339,7 +347,7 @@ class LangfusePromptManagement(LangFuseLogger, PromptManagementBase, CustomLogge
status_message = str(kwargs.get("exception", "Unknown error"))
if standard_logging_object is not None:
status_message = standard_logging_object.get("error_str", None) or status_message
langfuse_logger_to_use.log_event_on_langfuse(
logged: Final = langfuse_logger_to_use.log_event_on_langfuse(
start_time=start_time,
end_time=end_time,
response_obj=None,
@ -348,6 +356,7 @@ class LangfusePromptManagement(LangFuseLogger, PromptManagementBase, CustomLogge
level="ERROR",
kwargs=kwargs,
)
_remember_trace_id(litellm_call_id=kwargs.get("litellm_call_id"), logged=logged)
except Exception as e:
from litellm._logging import verbose_logger

File diff suppressed because it is too large Load diff

View file

@ -36,7 +36,7 @@ from litellm._logging import (
)
from litellm._uuid import uuid
from litellm.batches.batch_utils import _handle_completed_batch, batch_cost_is_final
from litellm.caching.caching import DualCache, InMemoryCache
from litellm.caching.caching import DualCache
from litellm.caching.caching_handler import LLMCachingHandler
from litellm.constants import (
DEFAULT_MOCK_RESPONSE_COMPLETION_TOKEN_COUNT,
@ -221,6 +221,7 @@ from .initialize_dynamic_callback_params import (
initialize_standard_callback_dynamic_params as _initialize_standard_callback_dynamic_params,
)
from .specialty_caches.dynamic_logging_cache import DynamicLoggingCache
from .specialty_caches.service_trace_id_cache import in_memory_trace_id_cache
if TYPE_CHECKING:
from mcp.types import CallToolResult, EmbeddedResource, ImageContent, TextContent
@ -349,21 +350,6 @@ last_fetched_at_keys: Final = None
####
class ServiceTraceIDCache:
def __init__(self) -> None:
self.cache = InMemoryCache()
def get_cache(self, litellm_call_id: str, service_name: str) -> str | None:
key_name: Final = f"{service_name}:{litellm_call_id}"
response: Final = self.cache.get_cache(key=key_name)
return response
def set_cache(self, litellm_call_id: str, service_name: str, trace_id: str) -> None:
key_name: Final = f"{service_name}:{litellm_call_id}"
self.cache.set_cache(key=key_name, value=trace_id)
in_memory_trace_id_cache: Final = ServiceTraceIDCache()
in_memory_dynamic_logger_cache: Final = DynamicLoggingCache()
# Cached lazy import for PrometheusLogger
@ -3979,40 +3965,6 @@ class Logging(LiteLLMLoggingBaseClass):
return trace_id
def _get_callback_object(self, service_name: Literal["langfuse"]) -> Any | None:
"""
Return dynamic callback object.
Meant to solve issue when doing key-based/team-based logging
"""
global langFuseLogger
if service_name == "langfuse":
if langFuseLogger is None or (
(
self.standard_callback_dynamic_params.get("langfuse_public_key") is not None
and self.standard_callback_dynamic_params.get("langfuse_public_key") != langFuseLogger.public_key
)
or (
self.standard_callback_dynamic_params.get("langfuse_public_key") is not None
and self.standard_callback_dynamic_params.get("langfuse_public_key") != langFuseLogger.public_key
)
or (
self.standard_callback_dynamic_params.get("langfuse_host") is not None
and self.standard_callback_dynamic_params.get("langfuse_host") != langFuseLogger.langfuse_host
)
):
return LangFuseLogger(
langfuse_public_key=self.standard_callback_dynamic_params.get("langfuse_public_key"),
langfuse_secret=self.standard_callback_dynamic_params.get("langfuse_secret")
or self.standard_callback_dynamic_params.get("langfuse_secret_key"),
langfuse_host=self.standard_callback_dynamic_params.get("langfuse_host"),
allow_env_credentials=self.standard_callback_dynamic_params.get("langfuse_host") is None,
)
return langFuseLogger
return None
def handle_sync_success_callbacks_for_async_calls(
self,
result: Any,

View file

@ -1,10 +1,8 @@
"""
This is a cache for LangfuseLoggers.
Langfuse Python SDK initializes a thread for each client.
This ensures we do
1. Proper cleanup of Langfuse initialized clients.
1. Release the initialized-client slot a LangfuseLogger holds when it expires.
2. Re-use created langfuse clients.
"""
@ -21,45 +19,34 @@ from ...caching import InMemoryCache
class LangfuseInMemoryCache(InMemoryCache):
"""
Ensures we do proper cleanup of Langfuse initialized clients.
Decrements ``litellm.initialized_langfuse_clients`` when a LangFuseLogger entry expires.
Langfuse Python SDK initializes a thread for each client, we need to call Langfuse.shutdown() to properly cleanup.
This ensures we do proper cleanup of Langfuse initialized clients.
The counter is a soft budget: loggers built concurrently for one credential set before the
first lands in the cache each take a slot, and only the cached one gives it back on expiry.
The logger's ``stop()`` below hands its shared export channel back
(https://github.com/BerriAI/litellm/issues/11169).
"""
def _remove_key(self, key: str) -> None:
"""
Override _remove_key in InMemoryCache to ensure we do proper cleanup of Langfuse initialized clients.
LangfuseLoggers consume threads when initalized, this shuts them down when they are expired
Relevant Issue: https://github.com/BerriAI/litellm/issues/11169
"""
from litellm.integrations.langfuse.langfuse import LangFuseLogger
if isinstance(self.cache_dict[key], LangFuseLogger):
_created_langfuse_logger: Final[LangFuseLogger] = self.cache_dict[key]
#########################################################
# Clean up Langfuse initialized clients
#########################################################
evicted: Final = self.cache_dict.pop(key, None)
self.ttl_dict.pop(key, None)
if evicted is None:
return
if isinstance(evicted, LangFuseLogger):
litellm.initialized_langfuse_clients -= 1
_created_langfuse_logger.Langfuse.flush()
_created_langfuse_logger.Langfuse.shutdown()
# Loggers with a periodic flush task (e.g. NewRelicMetricsLogger) expose
# stop() so eviction actually ends the task instead of leaking it.
_evicted_stop: Final = getattr(self.cache_dict[key], "stop", None)
if callable(_evicted_stop):
try:
_evicted_stop()
except Exception: # noqa: BLE001 # a failing stop() must not block eviction
verbose_logger.debug("DynamicLoggingCache: stop() raised during eviction", exc_info=True)
#########################################################
# Call parent class to remove key from cache
#########################################################
return super()._remove_key(key)
_evicted_stop: Final = getattr(evicted, "stop", None)
if not callable(_evicted_stop):
return
try:
_evicted_stop()
except Exception: # noqa: BLE001 # a failing stop() must not block eviction
verbose_logger.debug("DynamicLoggingCache: stop() raised during eviction", exc_info=True)
class DynamicLoggingCache:

View file

@ -0,0 +1,20 @@
from typing import Final
from ...caching import InMemoryCache
class ServiceTraceIDCache:
def __init__(self) -> None:
self.cache = InMemoryCache()
def get_cache(self, litellm_call_id: str, service_name: str) -> str | None:
key_name: Final = f"{service_name}:{litellm_call_id}"
response: Final = self.cache.get_cache(key=key_name)
return response
def set_cache(self, litellm_call_id: str, service_name: str, trace_id: str) -> None:
key_name: Final = f"{service_name}:{litellm_call_id}"
self.cache.set_cache(key=key_name, value=trace_id)
in_memory_trace_id_cache: Final = ServiceTraceIDCache()

View file

@ -395,7 +395,9 @@ async def health_services_endpoint(
from litellm.integrations.langfuse.langfuse import LangFuseLogger
langfuse_logger: Final = LangFuseLogger()
langfuse_logger.Langfuse.auth_check()
auth_failure: Final = langfuse_logger.api_client.auth_check()
if auth_failure is not None:
raise ValueError(f"langfuse auth_check failed: {auth_failure.reason}")
_ = litellm.completion(
model="openai/litellm-mock-response-model",
messages=[{"role": "user", "content": "Hey, how's it going?"}],

View file

@ -68,6 +68,7 @@ from litellm.constants import (
DEFAULT_SHARED_HEALTH_CHECK_LOCK_TTL,
DEFAULT_SHARED_HEALTH_CHECK_TTL,
DEFAULT_SLACK_ALERTING_THRESHOLD,
LANGFUSE_SHUTDOWN_FLUSH_TIMEOUT_MILLIS,
LITELLM_EMBEDDING_PROVIDERS_SUPPORTING_INPUT_ARRAY_OF_TOKENS,
LITELLM_SETTINGS_SAFE_DB_OVERRIDES,
LITELLM_UI_ALLOW_HEADERS,
@ -1122,17 +1123,21 @@ async def proxy_shutdown_event(worker_heartbeat: ProxyWorkerHeartbeat | None = N
if shutdown_billing_metrics_recorder is not None:
shutdown_billing_metrics_recorder()
# flush remaining langfuse logs
if "langfuse" in litellm.success_callback:
if "litellm.integrations.langfuse.langfuse_sdk" in sys.modules:
try:
# flush langfuse logs on shutdow
from litellm.utils import langFuseLogger
from litellm.integrations.langfuse.langfuse_sdk import flush_langfuse_tracing
if langFuseLogger is not None:
langFuseLogger.Langfuse.flush()
except Exception:
# [DO NOT BLOCK shutdown events for this]
pass
flushed: Final = await asyncio.to_thread(flush_langfuse_tracing, LANGFUSE_SHUTDOWN_FLUSH_TIMEOUT_MILLIS)
if flushed:
verbose_proxy_logger.info("Langfuse export channels flushed")
else:
verbose_proxy_logger.warning(
"Langfuse shutdown flush incomplete: a channel did not finish within %dms or a batch was rejected "
"(see the export errors above); remaining spans are left to the background exporter",
LANGFUSE_SHUTDOWN_FLUSH_TIMEOUT_MILLIS,
)
except Exception as e: # noqa: BLE001 # shutdown must continue even if the flush fails
verbose_proxy_logger.exception("Error flushing Langfuse export channels on shutdown: %s", e)
## RESET CUSTOM VARIABLES ##
cleanup_router_config_variables()

View file

@ -14,3 +14,8 @@ class LangfuseUsageDetails(TypedDict):
total: int | None
cache_creation_input_tokens: int | None
cache_read_input_tokens: int | None
class LangfuseLoggedEvent(TypedDict):
trace_id: ReadOnly[str | None]
generation_id: ReadOnly[str | None]

View file

@ -171,11 +171,11 @@ proxy-runtime = [
"anthropic[vertex]>=0.84.0,<1.0",
"grpcio==1.78.0",
"prometheus-client>=0.20.0,<1.0",
"langfuse>=2.59.7,<3.0",
"opentelemetry-api==1.28.0",
"opentelemetry-sdk==1.28.0",
"opentelemetry-exporter-otlp==1.28.0",
"opentelemetry-instrumentation-fastapi==0.49b0",
"langfuse>=4.7,<5.0",
"opentelemetry-api==1.33.1",
"opentelemetry-sdk==1.33.1",
"opentelemetry-exporter-otlp==1.33.1",
"opentelemetry-instrumentation-fastapi==0.54b1",
"ddtrace>=4.8.2,<5.0",
"sentry-sdk>=2.21.0,<3.0",
"mangum>=0.17.0,<1.0",
@ -222,11 +222,11 @@ dev = [
"types-PyYAML==6.0.12.20250915",
"botocore-stubs==1.43.14",
"types-boto3[bedrock,bedrock-agent,bedrock-runtime,kms,s3,sagemaker-runtime,sts]==1.43.30",
"opentelemetry-api==1.28.0",
"opentelemetry-sdk==1.28.0",
"opentelemetry-exporter-otlp==1.28.0",
"opentelemetry-instrumentation-fastapi==0.49b0",
"langfuse==2.59.7",
"opentelemetry-api==1.33.1",
"opentelemetry-sdk==1.33.1",
"opentelemetry-exporter-otlp==1.33.1",
"opentelemetry-instrumentation-fastapi==0.54b1",
"langfuse>=4.7,<5.0",
"fastapi-offline==1.7.6",
"fakeredis==2.34.1",
"pytest-rerunfailures==15.1",
@ -249,10 +249,10 @@ proxy-dev = [
"prisma==0.11.0",
"hypercorn==0.17.3",
"prometheus-client==0.20.0",
"opentelemetry-api==1.28.0",
"opentelemetry-sdk==1.28.0",
"opentelemetry-exporter-otlp==1.28.0",
"opentelemetry-instrumentation-fastapi==0.49b0",
"opentelemetry-api==1.33.1",
"opentelemetry-sdk==1.33.1",
"opentelemetry-exporter-otlp==1.33.1",
"opentelemetry-instrumentation-fastapi==0.54b1",
"azure-identity==1.25.2",
"a2a-sdk==1.1.0",
]
@ -272,7 +272,7 @@ ci = [
"lunary==1.4.36; python_version == '3.10'",
"lunary==1.4.37; python_version >= '3.11'",
"logfire==4.6.0",
"traceloop-sdk==0.33.12",
"traceloop-sdk==0.34.0",
"detect-secrets==1.5.0",
"PyGithub==2.8.1",
"aiodynamo==24.7",

View file

@ -0,0 +1,270 @@
import base64
import json
import time
import uuid
from collections.abc import Sequence
from pathlib import Path
from typing import Final
import yaml
from integration._support.client import Gateway, eventually
from integration._support.process import owned_proxy
from integration._support.wire import Reply, Request, Wire, wire_server
from opentelemetry.proto.collector.trace.v1.trace_service_pb2 import ExportTraceServiceRequest
from opentelemetry.proto.common.v1.common_pb2 import KeyValue
from opentelemetry.proto.trace.v1.trace_pb2 import Span
from pydantic import BaseModel, TypeAdapter
PUBLIC_KEY: Final = "pk-lf-integration"
SECRET_KEY: Final = "sk-lf-integration"
PROJECTS_PATH: Final = "/api/public/projects"
TRACES_PATH: Final = "/api/public/otel/v1/traces"
PROMPTS_PATH: Final = "/api/public/v2/prompts/"
_PROXY_CONFIG: Final = TypeAdapter(dict[str, object])
_SETTINGS: Final = TypeAdapter(dict[str, object])
class _ProviderBody(BaseModel):
messages: list[object]
def _completion(text: str) -> Reply:
return Reply(
body=json.dumps(
{
"id": "chatcmpl-" + text,
"object": "chat.completion",
"created": 1,
"model": "gpt-4o-mini",
"choices": [{"index": 0, "message": {"role": "assistant", "content": text}, "finish_reason": "stop"}],
"usage": {"prompt_tokens": 11, "completion_tokens": 4, "total_tokens": 15},
}
).encode()
)
def _projects() -> Reply:
return Reply(body=json.dumps({"data": [{"id": "integration-project", "name": "integration"}]}).encode())
def _text_prompt(name: str) -> Reply:
return Reply(
body=json.dumps(
{
"type": "text",
"name": name,
"version": 1,
"prompt": "Say {{word}}",
"config": {},
"labels": ["production"],
"tags": [],
}
).encode()
)
def _langfuse_config(tmp_path: Path) -> Path:
config: Final = _PROXY_CONFIG.validate_python(
yaml.safe_load(Path("tests/integration/proxy_config.yaml").read_text())
)
settings: Final = {
**_SETTINGS.validate_python(config["litellm_settings"]),
"success_callback": ["langfuse"],
"failure_callback": ["langfuse"],
}
path: Final = tmp_path / "langfuse.yaml"
path.write_text(yaml.safe_dump({**config, "litellm_settings": settings}))
return path
def _langfuse_environment(langfuse: Wire) -> dict[str, str]:
return {
"LANGFUSE_HOST": langfuse.url,
"LANGFUSE_PUBLIC_KEY": PUBLIC_KEY,
"LANGFUSE_SECRET_KEY": SECRET_KEY,
"LANGFUSE_FLUSH_INTERVAL": "1",
}
def _attribute(entries: Sequence[KeyValue], key: str) -> str | list[str] | None:
for entry in entries:
if entry.key != key:
continue
if entry.value.HasField("array_value"):
return [item.string_value for item in entry.value.array_value.values]
return entry.value.string_value
return None
def _spans(batches: Sequence[Request]) -> tuple[Span, ...]:
return tuple(
span
for batch in batches
if batch.target == TRACES_PATH and batch.headers.get("content-type") == "application/x-protobuf"
for resource_spans in ExportTraceServiceRequest.FromString(batch.body).resource_spans
for scope_spans in resource_spans.scope_spans
for span in scope_spans.spans
)
def test_langfuse_callback_delivers_the_generation_over_otlp_v4_with_the_caller_trace_fields(
gateway: Gateway, tmp_path: Path
) -> None:
marker: Final = "langfuse" + uuid.uuid4().hex
trace_id: Final = uuid.uuid4().hex
provider_secret: Final = "synthetic-provider-secret-" + marker
def upstream(request: Request) -> Reply:
assert request.headers["authorization"] == f"Bearer {provider_secret}"
return _completion(marker + "-answer")
def langfuse(request: Request) -> Reply:
if request.method == "GET" and request.target.startswith(PROJECTS_PATH):
return _projects()
return Reply(body=b"", content_type="application/x-protobuf")
with (
wire_server(upstream) as provider,
wire_server(langfuse) as destination,
owned_proxy(
gateway, tmp_path, _langfuse_environment(destination), config=_langfuse_config(tmp_path)
) as candidate,
candidate.scenario() as scenario,
):
model: Final = scenario.model(api_base=provider.url + "/v1", api_key=provider_secret)
response: Final = candidate.request(
"POST",
"/v1/chat/completions",
{
"model": model,
"messages": [{"role": "user", "content": marker + "-question"}],
"metadata": {
"trace_id": trace_id,
"trace_name": marker + "-trace",
"generation_name": marker,
"trace_user_id": marker + "-user",
"session_id": marker + "-session",
"tags": [marker],
},
"cache": {"no-cache": True},
},
)
assert response.status_code == 200, response.text
received: Final[list[Request]] = [] # mutable-ok: drain() consumes the queue, later polls keep earlier ones
def exported() -> tuple[Span, ...]:
received.extend(destination.drain())
return tuple(span for span in _spans(received) if span.name == marker)
spans: Final = eventually(exported, lambda values: len(values) == 1, seconds=20)
span: Final = spans[0]
posts: Final = tuple(request for request in received if request.method == "POST")
assert {request.target for request in posts} == {TRACES_PATH}, [request.target for request in received]
basic: Final = "Basic " + base64.b64encode(f"{PUBLIC_KEY}:{SECRET_KEY}".encode()).decode()
for request in posts:
assert request.headers["authorization"] == basic
assert request.headers["content-type"] == "application/x-protobuf"
assert request.headers["x-langfuse-ingestion-version"] == "4"
assert provider_secret.encode() not in request.body
assert candidate.key.encode() not in request.body
assert span.trace_id.hex() == trace_id
assert span.parent_span_id == b""
attributes: Final = span.attributes
assert _attribute(attributes, "langfuse.observation.type") == "generation"
assert _attribute(attributes, "langfuse.trace.name") == marker + "-trace"
assert _attribute(attributes, "user.id") == marker + "-user"
assert _attribute(attributes, "session.id") == marker + "-session"
assert marker in (_attribute(attributes, "langfuse.trace.tags") or ())
assert _attribute(attributes, "langfuse.observation.model.name") == "openai/gpt-4o-mini"
assert json.loads(str(_attribute(attributes, "langfuse.observation.usage_details"))) == {
"input": 11,
"output": 4,
"total": 15,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 0,
}
assert marker + "-question" in str(_attribute(attributes, "langfuse.observation.input"))
assert marker + "-answer" in str(_attribute(attributes, "langfuse.observation.output"))
assert (
_attribute(attributes, "langfuse.observation.metadata.litellm_call_id")
== response.headers["x-litellm-call-id"]
)
def test_prompt_fetch_encodes_the_name_retries_a_5xx_once_and_keeps_langfuse_headers_off_the_client(
gateway: Gateway, tmp_path: Path
) -> None:
marker: Final = "prompt" + uuid.uuid4().hex
leak: Final = "leak-" + marker
flaky_prompt: Final = f"{marker}/what?"
encoded_flaky_prompt: Final = f"{marker}%2Fwhat%3F"
missing_prompt: Final = marker + "-missing"
seen_prompt_gets: Final[list[str]] = [] # mutable-ok: the double counts attempts across requests
def upstream(request: Request) -> Reply:
return _completion(marker + "-answer")
def langfuse(request: Request) -> Reply:
if request.method == "GET" and request.target.startswith(PROJECTS_PATH):
return _projects()
if request.method == "POST":
return Reply(body=b"", content_type="application/x-protobuf")
assert request.target.startswith(PROMPTS_PATH), request.target
assert request.headers["authorization"].startswith("Basic ")
if request.target.startswith(PROMPTS_PATH + encoded_flaky_prompt):
prior: Final = sum(1 for seen in seen_prompt_gets if seen.startswith(PROMPTS_PATH + encoded_flaky_prompt))
seen_prompt_gets.append(request.target)
if prior == 0:
return Reply(status=503, body=b'{"message":"try later"}', headers={"retry-after": "30"})
return _text_prompt(flaky_prompt)
seen_prompt_gets.append(request.target)
return Reply(
status=404,
body=b'{"message":"Prompt not found","error":"LangfuseNotFoundError"}',
headers={"set-cookie": f"session={leak}; Path=/", "x-upstream-internal": leak, "server": leak},
)
with (
wire_server(upstream) as provider,
wire_server(langfuse) as destination,
owned_proxy(
gateway, tmp_path, _langfuse_environment(destination), config=_langfuse_config(tmp_path)
) as candidate,
candidate.scenario() as scenario,
):
flaky: Final = scenario.model(
model="langfuse/gpt-4o-mini", prompt_id=flaky_prompt, api_base=provider.url + "/v1", api_key="synthetic"
)
missing: Final = scenario.model(
model="langfuse/gpt-4o-mini", prompt_id=missing_prompt, api_base=provider.url + "/v1", api_key="synthetic"
)
started: Final = time.monotonic()
response: Final = candidate.request(
"POST",
"/v1/chat/completions",
{"model": flaky, "messages": [{"role": "user", "content": marker}], "prompt_variables": {"word": marker}},
)
elapsed: Final = time.monotonic() - started
assert response.status_code == 200, response.text
assert elapsed < 5, f"a retried cold prompt miss took {elapsed:.1f}s"
attempts: Final = tuple(
target for target in seen_prompt_gets if target.startswith(PROMPTS_PATH + encoded_flaky_prompt)
)
assert len(attempts) == 2, seen_prompt_gets
assert all(target.split("?", 1)[0] == PROMPTS_PATH + encoded_flaky_prompt for target in attempts), attempts
sent: Final = _ProviderBody.model_validate_json(provider.drain()[-1].body).messages
assert any("Say " + marker in json.dumps(message) for message in sent), sent
failure: Final = candidate.request(
"POST",
"/v1/chat/completions",
{"model": missing, "messages": [{"role": "user", "content": marker}], "prompt_variables": {"word": marker}},
)
assert failure.status_code == 404, failure.text
assert "Prompt not found" in failure.text
assert leak not in failure.text
assert leak not in json.dumps(dict(failure.headers))
assert "set-cookie" not in failure.headers and "x-upstream-internal" not in failure.headers
assert sum(1 for target in seen_prompt_gets if target.startswith(PROMPTS_PATH + missing_prompt)) == 1

View file

@ -842,6 +842,7 @@ def test_logging_trace_id(langfuse_trace_id, langfuse_existing_trace_id):
"""
- Unit test for `_get_trace_id` function in Logging obj
"""
from litellm.integrations.langfuse.langfuse_sdk import resolve_trace_id
from litellm.litellm_core_utils.litellm_logging import Logging
litellm.success_callback = ["langfuse"]
@ -874,24 +875,18 @@ def test_logging_trace_id(langfuse_trace_id, langfuse_existing_trace_id):
time.sleep(3)
assert litellm_logging_obj._get_trace_id(service_name="langfuse") is not None
## if existing_trace_id exists
# langfuse addresses a trace by a 32-hex id, so the id litellm reports back is the
# resolved form of whichever source won; that is what the alerting deep link needs
if langfuse_existing_trace_id is not None:
assert (
litellm_logging_obj._get_trace_id(service_name="langfuse")
== langfuse_existing_trace_id
)
## if trace_id exists
expected_source = langfuse_existing_trace_id
elif langfuse_trace_id is not None:
assert (
litellm_logging_obj._get_trace_id(service_name="langfuse")
== langfuse_trace_id
)
## if no trace_id or existing_trace_id is provided, use litellm_trace_id
expected_source = langfuse_trace_id
else:
assert (
litellm_logging_obj._get_trace_id(service_name="langfuse")
== litellm_logging_obj.litellm_trace_id
)
expected_source = litellm_logging_obj.litellm_trace_id
assert litellm_logging_obj._get_trace_id(service_name="langfuse") == resolve_trace_id(
expected_source
)
def test_convert_model_response_object():

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,38 @@
{
"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.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,31 @@
{
"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.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,38 @@
{
"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.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,62 @@
{
"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.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.input": {
"messages": [
{
"role": "user",
"content": "Hello!"
}
]
},
"langfuse.trace.metadata.test_key": "test_value",
"langfuse.trace.name": "test_trace_name",
"langfuse.trace.output": {
"content": "Hello! How can I assist you today?",
"role": "assistant",
"tool_calls": null,
"function_call": null,
"provider_specific_fields": null
},
"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,31 @@
{
"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.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,33 @@
{
"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.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,42 @@
{
"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.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,42 @@
{
"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.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,29 @@
{
"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.observation.cost_details": {
"total": 3.5e-05
},
{
"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": 3.5e-05,
"additional_headers": {},
"litellm_overhead_time_ms": null,
"batch_models": null,
"litellm_model_name": "vertex_ai/gemini-3-flash-preview",
"usage_object": null
},
"litellm_response_cost": 3.5e-05,
"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-3-flash-preview",
"modelParameters": {},
"usage": {
"input": 10,
"output": 10,
"unit": "TOKENS",
"totalCost": 3.5e-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: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-3-flash-preview",
"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,38 @@
{
"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.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,38 @@
{
"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.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,38 @@
{
"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.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,38 @@
{
"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.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,38 @@
{
"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.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,38 @@
{
"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.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,38 @@
{
"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.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,38 @@
{
"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.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,38 @@
{
"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.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,6 +1,3 @@
import sys
from types import ModuleType, SimpleNamespace
import litellm
from litellm.integrations.langfuse.langfuse import resolve_langfuse_credentials
from litellm.integrations.langfuse.langfuse_handler import LangFuseHandler
@ -51,37 +48,29 @@ def test_resolve_langfuse_credentials_keeps_env_for_global_config(monkeypatch):
assert host == "https://admin-configured.example"
def test_upstream_langfuse_debug_env_is_passed(monkeypatch):
def test_upstream_langfuse_env_only_warns_and_opens_no_second_channel(monkeypatch, caplog):
"""UPSTREAM_LANGFUSE_* configured a second v2 ingestion client. v4 has one export channel per
credential set, so the values are ignored with a startup warning and never build anything."""
from litellm.integrations.langfuse import langfuse_sdk
from litellm.integrations.langfuse.langfuse import LangFuseLogger
class FakeLangfuse:
instances = []
def __init__(self, **kwargs):
self.kwargs = kwargs
FakeLangfuse.instances.append(self)
fake_langfuse_module = ModuleType("langfuse")
fake_langfuse_module.Langfuse = FakeLangfuse
fake_langfuse_module.version = SimpleNamespace(__version__="2.6.0")
monkeypatch.setitem(sys.modules, "langfuse", fake_langfuse_module)
monkeypatch.setattr(litellm, "initialized_langfuse_clients", 0)
monkeypatch.setattr(langfuse_sdk, "_TRACING", {})
monkeypatch.setenv("LANGFUSE_MOCK", "true")
monkeypatch.setenv("UPSTREAM_LANGFUSE_SECRET_KEY", "upstream-secret")
monkeypatch.setenv("UPSTREAM_LANGFUSE_PUBLIC_KEY", "upstream-public")
monkeypatch.setenv("UPSTREAM_LANGFUSE_HOST", "https://upstream.example")
monkeypatch.setenv("UPSTREAM_LANGFUSE_RELEASE", "release")
monkeypatch.setenv("UPSTREAM_LANGFUSE_DEBUG", "true")
logger = LangFuseLogger(
langfuse_public_key="public",
langfuse_secret="secret",
langfuse_host="https://langfuse.example",
)
with caplog.at_level("WARNING", logger="LiteLLM"):
logger = LangFuseLogger(
langfuse_public_key="public",
langfuse_secret="secret",
langfuse_host="https://langfuse.example",
)
assert logger.upstream_langfuse_debug == "true"
assert FakeLangfuse.instances[-1].kwargs["debug"] is True
assert any("UPSTREAM_LANGFUSE_* is no longer supported" in record.getMessage() for record in caplog.records)
assert [lease.tracing for lease in langfuse_sdk._TRACING.values()] == [logger.tracing]
assert all(key.public_key == "public" for key in langfuse_sdk._TRACING)
def test_langfuse_handler_accepts_secret_key_alias(monkeypatch):

View file

@ -1,166 +1,140 @@
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 = "litellm.llms.custom_httpx.http_handler.HTTPHandler.post"
LANGFUSE_EXPORT_PATH: Final = "/api/public/otel/v1/traces"
_PER_RUN_ATTRIBUTES: Final = frozenset(
{
"langfuse.observation.completion_start_time",
"langfuse.observation.metadata.applied_guardrails",
"langfuse.observation.metadata.cache_hit",
"langfuse.observation.metadata.hidden_params",
"langfuse.observation.metadata.litellm_call_id",
"langfuse.observation.metadata.litellm_response_cost",
"langfuse.observation.metadata.requester_metadata",
"langfuse.observation.metadata.response_id",
"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:
url: str = call.args[0] if call.args else call.kwargs["url"]
assert url.endswith(LANGFUSE_EXPORT_PATH), 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": span["parent_span_id"],
"attributes": {key: value for key, value in sorted(attributes.items()) if key not in _PER_RUN_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 +142,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 +161,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 +185,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 +208,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 +263,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 +291,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 +341,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 +364,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 +386,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 +423,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 +479,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 +493,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 +520,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

@ -306,35 +306,63 @@ def test_get_langfuse_flush_interval():
def test_langfuse_e2e_sync(monkeypatch):
from litellm import completion
import litellm
import respx
import httpx
"""A sync completion must reach langfuse over the wire, not just build a span.
v4 exports OTLP over ``requests`` rather than the v2 ingestion endpoint over
httpx, so this stands up a real receiver and asserts langfuse posted to it.
"""
import threading
import time
from http.server import BaseHTTPRequestHandler, HTTPServer
litellm.disable_aiohttp_transport = (
True # since this uses respx, we need to set use_aiohttp_transport to False
)
import litellm
from litellm import completion
from litellm.integrations.langfuse.langfuse import LangFuseLogger
from litellm.integrations.langfuse.langfuse_prompt_management import langfuse_client_init
from litellm.litellm_core_utils import litellm_logging
litellm._turn_on_debug()
received_paths = []
class _Receiver(BaseHTTPRequestHandler):
def do_POST(self):
received_paths.append(self.path)
self.rfile.read(int(self.headers.get("Content-Length") or 0))
self.send_response(200)
self.send_header("Content-Length", "0")
self.end_headers()
def log_message(self, *args):
pass
server = HTTPServer(("127.0.0.1", 0), _Receiver)
threading.Thread(target=server.serve_forever, daemon=True).start()
monkeypatch.setenv("LANGFUSE_HOST", f"http://127.0.0.1:{server.server_port}")
monkeypatch.setenv("LANGFUSE_PUBLIC_KEY", "pk-e2e-sync")
monkeypatch.setenv("LANGFUSE_SECRET_KEY", "sk-e2e-sync")
monkeypatch.setattr(litellm, "success_callback", ["langfuse"])
monkeypatch.setattr(litellm_logging, "langFuseLogger", None)
monkeypatch.setattr(litellm_logging, "in_memory_dynamic_logger_cache", DynamicLoggingCache())
monkeypatch.setattr(litellm_logging, "_in_memory_loggers", [])
langfuse_client_init.cache_clear()
with respx.mock:
# Mock Langfuse
# Mock any Langfuse endpoint
langfuse_mock = respx.post(
"https://*.cloud.langfuse.com/api/public/ingestion"
).mock(return_value=httpx.Response(200))
try:
completion(
model="openai/my-fake-endpoint",
messages=[{"role": "user", "content": "hello from litellm"}],
stream=False,
mock_response="Hello from litellm 2",
)
for logger in litellm.logging_callback_manager._get_all_callbacks():
if isinstance(logger, LangFuseLogger):
logger.flush()
deadline = time.time() + 10
while not received_paths and time.time() < deadline:
time.sleep(0.1)
finally:
server.shutdown()
time.sleep(3)
assert langfuse_mock.called
assert received_paths, "langfuse exported nothing"
assert all(path.endswith("/api/public/otel/v1/traces") for path in received_paths)
def test_get_chat_content_for_langfuse():

View file

@ -1,15 +1,9 @@
import json
from typing import Optional
from unittest.mock import MagicMock
from unittest.mock import AsyncMock, MagicMock
import pytest
# Adds the grandparent directory to sys.path to allow importing project modules
import litellm
from litellm.integrations.langfuse.langfuse_prompt_management import (
LangfusePromptManagement,
)
from litellm.integrations.SlackAlerting.utils import _add_langfuse_trace_id_to_alert
from litellm.litellm_core_utils.logging_callback_manager import LoggingCallbackManager
@ -34,3 +28,95 @@ async def test_langfuse_not_initialized_returns_none_early():
# Verify the litellm_logging_obj was never accessed (early return)
request_data["litellm_logging_obj"].assert_not_called()
@pytest.mark.asyncio
async def test_langfuse_trace_url_uses_the_request_host_without_building_a_logger(monkeypatch):
"""Key-scoped callbacks point at their own Langfuse host; the alert link follows it.
The lookup must not construct a LangFuseLogger per alert, or an alert storm
exhausts the initialized-client ceiling and takes the callback down with it.
"""
monkeypatch.setattr(litellm, "success_callback", ["langfuse"])
monkeypatch.setattr(litellm, "initialized_langfuse_clients", 0)
logging_obj = MagicMock()
logging_obj._get_trace_id.return_value = "abc123"
logging_obj.standard_callback_dynamic_params = {"langfuse_host": "http://127.0.0.1:1"}
result = await _add_langfuse_trace_id_to_alert({"litellm_logging_obj": logging_obj})
assert result == "http://127.0.0.1:1/trace/abc123"
assert litellm.initialized_langfuse_clients == 0
@pytest.mark.asyncio
async def test_langfuse_trace_url_falls_back_to_the_env_host(monkeypatch):
monkeypatch.setattr(litellm, "success_callback", ["langfuse"])
monkeypatch.setenv("LANGFUSE_HOST", "langfuse.internal:3000")
logging_obj = MagicMock()
logging_obj._get_trace_id.return_value = "abc123"
logging_obj.standard_callback_dynamic_params = {}
assert await _add_langfuse_trace_id_to_alert({"litellm_logging_obj": logging_obj}) == (
"http://langfuse.internal:3000/trace/abc123"
)
@pytest.mark.asyncio
async def test_langfuse_trace_url_when_callback_registered_as_logger_instance(monkeypatch):
from litellm.integrations.langfuse.langfuse import LangFuseLogger
logger = LangFuseLogger(
langfuse_public_key="pk-slack-instance",
langfuse_secret="sk-slack-instance",
langfuse_host="http://127.0.0.1:1",
)
monkeypatch.setattr(litellm, "success_callback", [logger])
monkeypatch.setattr(litellm, "failure_callback", [])
monkeypatch.setattr(litellm, "_async_success_callback", [])
monkeypatch.setattr(litellm, "_async_failure_callback", [])
monkeypatch.setattr(litellm, "callbacks", [])
monkeypatch.setenv("LANGFUSE_HOST", "http://env-host.invalid")
logging_obj = MagicMock()
logging_obj._get_trace_id.return_value = "trace-from-instance"
logging_obj.standard_callback_dynamic_params = {}
result = await _add_langfuse_trace_id_to_alert({"litellm_logging_obj": logging_obj})
assert result == "http://127.0.0.1:1/trace/trace-from-instance"
@pytest.mark.asyncio
async def test_langfuse_trace_url_when_prompt_management_is_the_registered_callback(monkeypatch):
"""Prompt management registers a LangFuseLogger subclass; the alert must read its host, not crash."""
from litellm.integrations.langfuse.langfuse_prompt_management import LangfusePromptManagement
prompt_callback = LangfusePromptManagement(
langfuse_public_key="pk-slack-prompt",
langfuse_secret="sk-slack-prompt",
langfuse_host="http://127.0.0.1:2",
)
monkeypatch.setattr(litellm, "success_callback", ["langfuse"])
monkeypatch.setattr(litellm, "failure_callback", [])
monkeypatch.setattr(litellm, "_async_success_callback", [])
monkeypatch.setattr(litellm, "_async_failure_callback", [])
monkeypatch.setattr(litellm, "callbacks", [prompt_callback])
monkeypatch.setenv("LANGFUSE_HOST", "http://env-host.invalid")
logging_obj = MagicMock()
logging_obj._get_trace_id.return_value = "trace-from-prompt-callback"
logging_obj.standard_callback_dynamic_params = {}
result = await _add_langfuse_trace_id_to_alert({"litellm_logging_obj": logging_obj})
assert result == "http://127.0.0.1:2/trace/trace-from-prompt-callback"
@pytest.mark.asyncio
async def test_langfuse_trace_url_absent_when_trace_id_never_arrives(monkeypatch):
monkeypatch.setattr(litellm, "success_callback", ["langfuse"])
monkeypatch.setattr("litellm.integrations.SlackAlerting.utils.asyncio.sleep", AsyncMock())
logging_obj = MagicMock()
logging_obj._get_trace_id.return_value = None
logging_obj.standard_callback_dynamic_params = {"langfuse_host": "http://127.0.0.1:1"}
assert await _add_langfuse_trace_id_to_alert({"litellm_logging_obj": logging_obj}) is None

View file

@ -1,9 +1,13 @@
from types import MappingProxyType
import sys
from datetime import datetime, timezone
from typing import Final
from unittest.mock import MagicMock, patch
import pytest
# langfuse_client_init imports this lazily; cache it before any test mocks
# sys.modules["langfuse"], or a single-file run dies on the real import
import litellm.integrations.langfuse.langfuse_sdk # noqa: F401
from litellm.integrations.langfuse.langfuse_prompt_management import (
LangfusePromptManagement,
langfuse_client_init,
@ -17,9 +21,7 @@ class TestLangfusePromptManagement:
# This also prevents test-ordering issues when earlier tests remove sys.modules["langfuse"].
self._mock_langfuse = MagicMock()
self._mock_langfuse.version.__version__ = "3.0.0"
self._langfuse_patcher = patch.dict(
"sys.modules", {"langfuse": self._mock_langfuse}
)
self._langfuse_patcher = patch.dict("sys.modules", {"langfuse": self._mock_langfuse})
self._langfuse_patcher.start()
def teardown_method(self):
@ -31,9 +33,7 @@ class TestLangfusePromptManagement:
patch.object(
langfuse_prompt_management, "should_run_prompt_management"
) as mock_should_run_prompt_management,
patch.object(
langfuse_prompt_management, "_get_prompt_from_id"
) as mock_get_prompt_from_id,
patch.object(langfuse_prompt_management, "_get_prompt_from_id") as mock_get_prompt_from_id,
):
mock_should_run_prompt_management.return_value = True
langfuse_prompt_management.get_chat_completion_prompt(
@ -51,9 +51,7 @@ class TestLangfusePromptManagement:
def test_log_failure_event_runs_async_logger(self):
langfuse_prompt_management = LangfusePromptManagement()
with patch(
"litellm.integrations.langfuse.langfuse_prompt_management.run_async_function"
) as mock_run_async:
with patch("litellm.integrations.langfuse.langfuse_prompt_management.run_async_function") as mock_run_async:
kwargs = {"standard_callback_dynamic_params": {}}
start_time, end_time = 1, 2
@ -65,10 +63,7 @@ class TestLangfusePromptManagement:
)
mock_run_async.assert_called_once()
assert (
mock_run_async.call_args[0][0]
== langfuse_prompt_management.async_log_failure_event
)
assert mock_run_async.call_args[0][0] == langfuse_prompt_management.async_log_failure_event
def test_langfuse_client_init_passes_dedicated_httpx_client(self):
import httpx
@ -76,35 +71,28 @@ class TestLangfusePromptManagement:
from litellm.llms.custom_httpx.http_handler import _get_httpx_client
shared_client = _get_httpx_client().client
mock_langfuse_class = MagicMock()
built = MagicMock()
with (
patch(
"litellm.integrations.langfuse.langfuse_prompt_management.resolve_langfuse_credentials",
return_value=("pk-1234", "sk-1234", "https://localhost"),
),
patch(
"litellm.integrations.langfuse.langfuse_prompt_management.LangFuseLogger._get_langfuse_flush_interval",
return_value=1,
),
patch.dict("sys.modules", {"langfuse": self._mock_langfuse}),
"litellm.integrations.langfuse.langfuse_sdk.build_langfuse_client", built
), # test-quality-ok: the REST client is built where langfuse_client_init resolves it; the transport it gets is the behavior under test
patch(
"litellm.llms.custom_httpx.http_handler.get_ssl_configuration",
return_value=False,
) as mock_get_ssl,
):
self._mock_langfuse.Langfuse = mock_langfuse_class
langfuse_client_init(
langfuse_public_key="pk-1234",
langfuse_secret="sk-1234",
langfuse_host="https://localhost",
)
mock_langfuse_class.assert_called_once()
call_kwargs = mock_langfuse_class.call_args[1]
assert "httpx_client" in call_kwargs
passed_client = call_kwargs["httpx_client"]
built.assert_called_once()
passed_client = built.call_args.kwargs["httpx_client"]
assert isinstance(passed_client, httpx.Client)
assert passed_client is not shared_client
mock_get_ssl.assert_called_once()
@ -112,28 +100,181 @@ class TestLangfusePromptManagement:
langfuse_client_init.cache_clear()
class _RecordingLangfuseForEnv:
last_environment: str | None = None
def __init__(self, *, environment: str | None = None, **parameters: object) -> None: # kwargs-ok: records only environment out of whatever langfuse_client_init forwards
type(self).last_environment = environment
@pytest.mark.parametrize(
("env_value", "expected"),
(("Production", "default"), ("production ", "production"), ("prod", "prod")),
)
def test_langfuse_client_init_resolves_deployment_environment(monkeypatch, env_value, expected):
mock_langfuse_module: Final = MagicMock()
mock_langfuse_module.version.__version__ = "2.60.0"
mock_langfuse_module.Langfuse = _RecordingLangfuseForEnv
def test_prompt_management_logger_exports_the_resolved_deployment_environment(monkeypatch, env_value, expected):
from langfuse import LangfuseOtelSpanAttributes
monkeypatch.setenv("LANGFUSE_PUBLIC_KEY", "pk-test")
monkeypatch.setenv("LANGFUSE_SECRET_KEY", "sk-test")
monkeypatch.setenv("LANGFUSE_HOST", "http://127.0.0.1:1")
monkeypatch.setenv("LANGFUSE_MOCK", "true")
monkeypatch.setenv("LANGFUSE_TRACING_ENVIRONMENT", env_value)
langfuse_client_init.cache_clear()
logger = LangfusePromptManagement()
langfuse_client_init.cache_clear()
assert logger.tracing.provider.resource.attributes[LangfuseOtelSpanAttributes.ENVIRONMENT] == expected
def test_langfuse_client_init_warns_that_upstream_langfuse_is_ignored(monkeypatch, caplog):
"""The YAML `callbacks: ["langfuse"]` path builds its client here, not through LangFuseLogger.__init__,
so an operator who still sets UPSTREAM_LANGFUSE_* must get the same startup warning on this path."""
monkeypatch.setenv("LANGFUSE_PUBLIC_KEY", "pk-test")
monkeypatch.setenv("LANGFUSE_SECRET_KEY", "sk-test")
monkeypatch.setenv("LANGFUSE_HOST", "https://test.langfuse.com")
monkeypatch.setenv("LANGFUSE_TRACING_ENVIRONMENT", env_value)
monkeypatch.setattr(_RecordingLangfuseForEnv, "last_environment", None)
with patch.dict("sys.modules", MappingProxyType({"langfuse": mock_langfuse_module})):
monkeypatch.setenv("UPSTREAM_LANGFUSE_SECRET_KEY", "sk-upstream")
monkeypatch.setenv("UPSTREAM_LANGFUSE_HOST", "https://upstream.example")
with caplog.at_level("WARNING", logger="LiteLLM"):
langfuse_client_init.cache_clear()
langfuse_client_init()
langfuse_client_init.cache_clear()
assert _RecordingLangfuseForEnv.last_environment == expected
assert any("UPSTREAM_LANGFUSE_* is no longer supported" in record.getMessage() for record in caplog.records)
def test_langfuse_client_init_mock_mode_makes_no_network_calls(monkeypatch):
"""LANGFUSE_MOCK promises full execution without egress.
The registry maps the "langfuse" callback to LangfusePromptManagement, so
this logger is the one the standard proxy path emits observations through;
they travel over litellm's own OTLP exporter, which the httpx mock cannot see.
"""
import threading
from http.server import BaseHTTPRequestHandler, HTTPServer
import litellm
received = []
class _Receiver(BaseHTTPRequestHandler):
def do_POST(self):
received.append(self.path)
self.rfile.read(int(self.headers.get("Content-Length") or 0))
self.send_response(200)
self.send_header("Content-Length", "0")
self.end_headers()
def log_message(self, *args):
pass
server = HTTPServer(("127.0.0.1", 0), _Receiver)
threading.Thread(target=server.serve_forever, daemon=True).start()
monkeypatch.setenv("LANGFUSE_MOCK", "true")
monkeypatch.setenv("LANGFUSE_HOST", f"http://127.0.0.1:{server.server_port}")
monkeypatch.setenv("LANGFUSE_PUBLIC_KEY", "pk-pm-mock-egress")
monkeypatch.setenv("LANGFUSE_SECRET_KEY", "sk-pm-mock-egress")
langfuse_client_init.cache_clear()
now: Final = datetime.now(timezone.utc)
try:
logger = LangfusePromptManagement()
logged = logger.log_event_on_langfuse(
kwargs={
"litellm_call_id": "call-pm-mock-egress",
"call_type": "completion",
"litellm_params": {"metadata": {"trace_id": "a" * 32}},
"messages": [{"role": "user", "content": "hi"}],
"optional_params": {},
},
response_obj=litellm.ModelResponse(choices=[{"message": {"role": "assistant", "content": "ok"}}]),
start_time=now,
end_time=now,
)
logger.flush()
finally:
server.shutdown()
langfuse_client_init.cache_clear()
assert logged["trace_id"] == "a" * 32
assert received == [], f"LANGFUSE_MOCK still sent spans to the configured host: {received}"
def test_langfuse_debug_reaches_the_export_channel_through_the_registered_callback(monkeypatch):
"""The registry maps ``langfuse`` to this class, whose constructor never runs ``LangFuseLogger.__init__``,
so wiring ``LANGFUSE_DEBUG`` only there left the flag a no-op on the YAML callback path."""
import logging
from litellm.integrations.langfuse.langfuse_sdk import release_langfuse_tracing
monkeypatch.setenv("LANGFUSE_MOCK", "true")
monkeypatch.setenv("LANGFUSE_HOST", "http://127.0.0.1:1")
monkeypatch.setenv("LANGFUSE_PUBLIC_KEY", "pk-pm-debug-wire")
monkeypatch.setenv("LANGFUSE_SECRET_KEY", "sk-pm-debug-wire")
monkeypatch.setenv("LANGFUSE_DEBUG", "true")
langfuse_client_init.cache_clear()
langfuse_logger: Final = logging.getLogger("langfuse")
level_before: Final = langfuse_logger.level
langfuse_logger.setLevel(logging.WARNING)
try:
logger = LangfusePromptManagement()
assert langfuse_logger.level == logging.DEBUG
release_langfuse_tracing(logger.tracing, grace_seconds=0.0)
finally:
langfuse_logger.setLevel(level_before)
langfuse_client_init.cache_clear()
@pytest.mark.asyncio
async def test_async_log_failure_event_records_trace_id_for_alerting(monkeypatch):
from litellm.integrations.langfuse.langfuse_sdk import resolve_trace_id
from litellm.litellm_core_utils.specialty_caches.service_trace_id_cache import in_memory_trace_id_cache
monkeypatch.setenv("LANGFUSE_MOCK", "true")
monkeypatch.setenv("LANGFUSE_HOST", "http://127.0.0.1:1")
monkeypatch.setenv("LANGFUSE_PUBLIC_KEY", "pk-pm-trace-cache")
monkeypatch.setenv("LANGFUSE_SECRET_KEY", "sk-pm-trace-cache")
langfuse_client_init.cache_clear()
call_id: Final = "call-trace-cache-1"
now: Final = datetime.now(timezone.utc)
kwargs: Final = {
"litellm_call_id": call_id,
"model": "gpt-5.4",
"messages": [{"role": "user", "content": "hi"}],
"litellm_params": {"metadata": {"trace_id": "alert-trace-1"}},
"optional_params": {},
"standard_callback_dynamic_params": {},
"exception": RuntimeError("provider down"),
}
try:
await LangfusePromptManagement().async_log_failure_event(
kwargs=kwargs, response_obj=None, start_time=now, end_time=now
)
finally:
langfuse_client_init.cache_clear()
assert in_memory_trace_id_cache.get_cache(litellm_call_id=call_id, service_name="langfuse") == resolve_trace_id(
"alert-trace-1"
)
def test_old_sdk_fails_with_the_upgrade_message_before_the_otel_module_is_imported(monkeypatch):
"""On a v2 install `langfuse_sdk` itself fails to import, so the version gate must run first."""
import litellm.integrations.langfuse.langfuse_prompt_management as pm_module
monkeypatch.setattr(pm_module, "installed_langfuse_version", lambda: "2.59.7")
monkeypatch.setitem(sys.modules, "litellm.integrations.langfuse.langfuse_sdk", None)
with pytest.raises(ImportError) as raised:
LangfusePromptManagement(
langfuse_public_key="pk-old", langfuse_secret="sk-old", langfuse_host="http://127.0.0.1:1"
)
assert "2.59.7" in str(raised.value)
assert "langfuse_otel" in str(raised.value)
@pytest.mark.parametrize("raw", ["abc", "2.5"], ids=["text", "fraction"])
def test_prompt_cache_ttl_typo_is_named_instead_of_reported_as_not_installed(monkeypatch, raw):
"""The v4 SDK runs ``int()`` on this variable at import, and ``langfuse_client_init`` wraps any import
failure as "Langfuse not installed", so the gate has to run before that import."""
monkeypatch.setenv("LANGFUSE_PROMPT_CACHE_DEFAULT_TTL_SECONDS", raw)
monkeypatch.setitem(sys.modules, "litellm.integrations.langfuse.langfuse_sdk", None)
langfuse_client_init.cache_clear()
with pytest.raises(ValueError, match="LANGFUSE_PROMPT_CACHE_DEFAULT_TTL_SECONDS") as raised:
langfuse_client_init(langfuse_public_key="pk-ttl", langfuse_secret="sk-ttl", langfuse_host="http://127.0.0.1:1")
assert "not installed" not in str(raised.value)
assert repr(raw) in str(raised.value)

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -24,10 +24,7 @@ class TestLangfuseInMemoryCache:
# Create a mock LangFuseLogger class
class MockLangFuseLogger:
def __init__(self):
self.Langfuse = MagicMock()
self.Langfuse.flush = MagicMock()
self.Langfuse.shutdown = MagicMock()
pass
mock_logger = MockLangFuseLogger()
@ -50,29 +47,72 @@ class TestLangfuseInMemoryCache:
assert litellm.initialized_langfuse_clients == initial_count - 1
@patch("litellm.initialized_langfuse_clients", 3)
def test_langfuse_client_shutdown_called_on_eviction(self):
"""Test that langfuse client shutdown is called to close the thread."""
def test_evicted_logger_releases_its_hold_on_the_shared_export_channel(self):
"""Export channels are shared per credential set: eviction gives this logger's hold back
while a sibling logger keeps exporting, and the channel is retired once the last hold goes."""
from litellm.integrations.langfuse.langfuse import LangFuseLogger
from litellm.integrations.langfuse.langfuse_sdk import acquire_langfuse_tracing, release_langfuse_tracing
# Create a mock LangFuseLogger class
class MockLangFuseLogger:
def __init__(self):
self.Langfuse = MagicMock()
self.Langfuse.flush = MagicMock()
self.Langfuse.shutdown = MagicMock()
def acquire():
return acquire_langfuse_tracing(
public_key="pk-eviction-test",
secret_key="sk",
base_url="http://127.0.0.1:1",
environment=None,
release=None,
flush_interval=1.0,
mock_mode=True,
)
mock_logger = MockLangFuseLogger()
logger = LangFuseLogger.__new__(LangFuseLogger)
logger.api_client = MagicMock()
logger.api_client.get_prompt.return_value = "prompt-after-eviction"
logger.tracing = acquire()
sibling = acquire()
self.cache.cache_dict["test_key"] = logger
self.cache.ttl_dict["test_key"] = time.time() + 100
# Patch the LangFuseLogger import to return our mock class
with patch(
"litellm.integrations.langfuse.langfuse.LangFuseLogger", MockLangFuseLogger
):
# Add the mock logger to cache
self.cache.cache_dict["test_key"] = mock_logger
self.cache.ttl_dict["test_key"] = time.time() + 100
self.cache._remove_key("test_key")
# Remove the key (this should trigger cleanup)
self.cache._remove_key("test_key")
assert litellm.initialized_langfuse_clients == 2
assert logger.api_client.get_prompt("greeting") == "prompt-after-eviction"
with sibling.tracer.start_as_current_span("still-open"):
pass
assert sibling.flush(1000) is True
# Verify flush and shutdown were called
mock_logger.Langfuse.flush.assert_called_once()
mock_logger.Langfuse.shutdown.assert_called_once()
release_langfuse_tracing(sibling, grace_seconds=0.0)
assert acquire() is not logger.tracing, "eviction did not release the evicted logger's hold"
@patch("litellm.initialized_langfuse_clients", 3)
def test_second_evictor_of_the_same_entry_releases_nothing(self):
"""Two callers can expire the same entry at once (a request thread and the reaper). Only the one that
claims the entry may give its slot and channel hold back, or a sibling logger loses its channel."""
from litellm.integrations.langfuse.langfuse import LangFuseLogger
from litellm.integrations.langfuse.langfuse_sdk import acquire_langfuse_tracing, release_langfuse_tracing
def acquire():
return acquire_langfuse_tracing(
public_key="pk-double-eviction-test",
secret_key="sk",
base_url="http://127.0.0.1:1",
environment=None,
release=None,
flush_interval=1.0,
mock_mode=True,
)
logger = LangFuseLogger.__new__(LangFuseLogger)
logger.api_client = MagicMock()
logger.tracing = acquire()
sibling = acquire()
self.cache.cache_dict["test_key"] = logger
self.cache.ttl_dict["test_key"] = time.time() + 100
self.cache._remove_key("test_key")
self.cache._remove_key("test_key")
assert litellm.initialized_langfuse_clients == 2
assert "test_key" not in self.cache.cache_dict and "test_key" not in self.cache.ttl_dict
assert acquire() is sibling, "the second evictor took the sibling logger's hold on the channel"
release_langfuse_tracing(sibling)
release_langfuse_tracing(sibling, grace_seconds=0.0)

View file

@ -4307,3 +4307,22 @@ async def test_health_services_endpoint_pointfive_blocks_non_admin(monkeypatch,
assert str(raised.value.code) == "403"
logger_class.assert_not_called()
@pytest.mark.asyncio
async def test_health_services_endpoint_langfuse_missing_keys_errors(monkeypatch):
"""v2 raised out of ``auth_check`` and the endpoint printed the server's answer; the v4 check
returns the failure as a value, and the endpoint has to error with that reason rather than a
generic credentials message that reads the same for an outage and a bad key."""
import litellm.integrations.langfuse.langfuse as langfuse_module
from litellm.integrations.langfuse.langfuse_sdk import AuthCheckFailure
logger_class = MagicMock()
logger_class.return_value.api_client.auth_check.return_value = AuthCheckFailure(
"connection refused by lf.internal.example"
)
monkeypatch.setattr(langfuse_module, "LangFuseLogger", logger_class)
with pytest.raises(ProxyException, match="auth_check failed") as raised:
await health_services_endpoint(service="langfuse")
assert "connection refused by lf.internal.example" in str(raised.value.message)

View file

@ -81,35 +81,29 @@ class TestCallbackManagementEndpoints:
# Setup test client
client = TestClient(app)
# Initialize Langfuse logger and add to callbacks
with patch("litellm.integrations.langfuse.langfuse.Langfuse") as mock_langfuse:
# Mock the Langfuse client initialization
mock_langfuse_client = MagicMock()
mock_langfuse.return_value = mock_langfuse_client
# Add string representation to callback lists (this is how the system typically works)
litellm.success_callback.append("langfuse")
litellm._async_success_callback.append("langfuse")
# Add string representation to callback lists (this is how the system typically works)
litellm.success_callback.append("langfuse")
litellm._async_success_callback.append("langfuse")
# Make request to list callbacks endpoint
response = client.get(
"/callbacks/list", headers={"Authorization": "Bearer sk-1234"}
)
# Make request to list callbacks endpoint
response = client.get(
"/callbacks/list", headers={"Authorization": "Bearer sk-1234"}
)
# Verify response
assert response.status_code == 200
# Verify response
assert response.status_code == 200
response_data = response.json()
response_data = response.json()
# Verify langfuse appears in success callbacks
assert "langfuse" in response_data["success"]
assert response_data["failure"] == []
assert response_data["success_and_failure"] == []
# Verify langfuse appears in success callbacks
assert "langfuse" in response_data["success"]
assert response_data["failure"] == []
assert response_data["success_and_failure"] == []
# Verify the response structure is correct
assert isinstance(response_data["success"], list)
assert isinstance(response_data["failure"], list)
assert isinstance(response_data["success_and_failure"], list)
# Verify the response structure is correct
assert isinstance(response_data["success"], list)
assert isinstance(response_data["failure"], list)
assert isinstance(response_data["success_and_failure"], list)
def test_alist_callbacks_with_datadog_logger(self):
"""Test /callbacks/list endpoint with DataDog logger configuration"""

View file

@ -132,6 +132,62 @@ async def test_proxy_shutdown_event_disconnects_prisma_and_resets(monkeypatch):
}
@pytest.mark.asyncio
async def test_proxy_shutdown_flushes_every_langfuse_export_channel(monkeypatch):
"""A generation finished just before a graceful restart is still queued in its batch
processor, so shutdown must flush every acquired export channel."""
from litellm.integrations.langfuse import langfuse_sdk
flushed = MagicMock(return_value=True)
monkeypatch.setattr(langfuse_sdk, "flush_langfuse_tracing", flushed)
monkeypatch.setattr(ps, "prisma_client", None, raising=False)
monkeypatch.setattr(ps, "jwt_handler", MagicMock(close=AsyncMock()), raising=False)
monkeypatch.setattr(ps, "db_writer_client", None, raising=False)
import litellm
monkeypatch.setattr(litellm, "cache", None, raising=False)
monkeypatch.setattr(litellm, "success_callback", [], raising=False)
await proxy_shutdown_event()
assert flushed.call_count == 1
@pytest.mark.asyncio
async def test_proxy_shutdown_flushes_langfuse_off_the_event_loop_and_logs_a_timeout(monkeypatch, caplog):
"""The flush blocks on OTLP exports for up to its deadline, so it must run on a worker thread
with the shutdown deadline, and a channel that misses it is reported instead of ignored."""
import threading
from litellm.constants import LANGFUSE_SHUTDOWN_FLUSH_TIMEOUT_MILLIS
from litellm.integrations.langfuse import langfuse_sdk
ran_on = MagicMock()
def flushed(timeout_millis: int) -> bool:
ran_on(threading.current_thread(), timeout_millis)
return False
monkeypatch.setattr(langfuse_sdk, "flush_langfuse_tracing", flushed)
monkeypatch.setattr(ps, "prisma_client", None, raising=False)
monkeypatch.setattr(ps, "jwt_handler", MagicMock(close=AsyncMock()), raising=False)
monkeypatch.setattr(ps, "db_writer_client", None, raising=False)
import litellm
monkeypatch.setattr(litellm, "cache", None, raising=False)
monkeypatch.setattr(litellm, "success_callback", [], raising=False)
with caplog.at_level("WARNING", logger="LiteLLM Proxy"):
await proxy_shutdown_event()
(flush_thread, timeout_millis), _ = ran_on.call_args
assert flush_thread is not threading.main_thread()
assert timeout_millis == LANGFUSE_SHUTDOWN_FLUSH_TIMEOUT_MILLIS
assert any("Langfuse shutdown flush incomplete" in record.getMessage() for record in caplog.records)
@pytest.mark.asyncio
async def test_proxy_shutdown_drains_gateway_requests_before_disconnecting(monkeypatch):
"""

301
uv.lock generated
View file

@ -4256,21 +4256,22 @@ wheels = [
[[package]]
name = "langfuse"
version = "2.59.7"
version = "4.15.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
{ name = "backoff" },
{ name = "httpx" },
{ name = "idna" },
{ name = "opentelemetry-api" },
{ name = "opentelemetry-exporter-otlp-proto-http" },
{ name = "opentelemetry-sdk" },
{ name = "packaging" },
{ name = "pydantic" },
{ name = "requests" },
{ name = "typing-extensions" },
{ name = "wrapt" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d5/0e/8390bd3a4ad92ecb1ba0462ec8b7c7d328b2e2f31ae0e734bf2f50dbdc96/langfuse-2.59.7.tar.gz", hash = "sha256:f631981705177bf53d030d191397da9b864b99729a7273448afed10d76f78e23", size = 146608, upload-time = "2025-03-03T16:30:59.926Z" }
sdist = { url = "https://files.pythonhosted.org/packages/97/30/6a64dcf84de2f2eb4d03adbfd22cc7bdc95ce67e3e56cd6288405087fb8a/langfuse-4.15.2.tar.gz", hash = "sha256:7f818f38cc22daba88fdcec62d2addcee4e18d1af4529978b6d07501e86b6946", size = 391727, upload-time = "2026-09-09T16:01:25.73Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b7/f3/420518b9003c997cdcb0a86473bf0c111181578a95565823c333cb58eb7b/langfuse-2.59.7-py3-none-any.whl", hash = "sha256:2c6890f5b842257173eb54d08f2890c7fd7617859a48b3914ef73f13a6514473", size = 260468, upload-time = "2025-03-03T16:30:57.426Z" },
{ url = "https://files.pythonhosted.org/packages/02/de/e59da18cb5ca9cb8515a254199bd96ace8cf918788d13ded66aa2693e007/langfuse-4.15.2-py3-none-any.whl", hash = "sha256:98c27a3c06e18c4497045f2d4decce2c716ef11cb215cf5b27bbea6ee0877115", size = 705824, upload-time = "2026-09-09T16:01:23.696Z" },
]
[[package]]
@ -4794,7 +4795,7 @@ requires-dist = [
{ name = "jinja2", specifier = ">=3.1.6,<4.0" },
{ name = "jsonschema", specifier = ">=4.0.0,<5.0" },
{ name = "keyring", marker = "extra == 'cli'", specifier = ">=25.6.0,<26.0" },
{ name = "langfuse", marker = "extra == 'proxy-runtime'", specifier = ">=2.59.7,<3.0" },
{ name = "langfuse", marker = "extra == 'proxy-runtime'", specifier = ">=4.7,<5.0" },
{ name = "litellm-enterprise", marker = "extra == 'proxy'", editable = "enterprise" },
{ name = "litellm-proxy-extras", marker = "extra == 'proxy'", editable = "litellm-proxy-extras" },
{ name = "llm-sandbox", marker = "extra == 'proxy-runtime'", specifier = ">=0.3.39,<1.0" },
@ -4806,10 +4807,10 @@ requires-dist = [
{ name = "numpydoc", marker = "extra == 'utils'", specifier = ">=1.8.0,<2.0" },
{ name = "nvidia-riva-client", marker = "extra == 'stt-nvidia-riva'", specifier = ">=2.15.0" },
{ name = "openai", specifier = ">=2.20.0,<3.0.0" },
{ name = "opentelemetry-api", marker = "extra == 'proxy-runtime'", specifier = "==1.28.0" },
{ name = "opentelemetry-exporter-otlp", marker = "extra == 'proxy-runtime'", specifier = "==1.28.0" },
{ name = "opentelemetry-instrumentation-fastapi", marker = "extra == 'proxy-runtime'", specifier = "==0.49b0" },
{ name = "opentelemetry-sdk", marker = "extra == 'proxy-runtime'", specifier = "==1.28.0" },
{ name = "opentelemetry-api", marker = "extra == 'proxy-runtime'", specifier = "==1.33.1" },
{ name = "opentelemetry-exporter-otlp", marker = "extra == 'proxy-runtime'", specifier = "==1.33.1" },
{ name = "opentelemetry-instrumentation-fastapi", marker = "extra == 'proxy-runtime'", specifier = "==0.54b1" },
{ name = "opentelemetry-sdk", marker = "extra == 'proxy-runtime'", specifier = "==1.33.1" },
{ name = "orjson", marker = "extra == 'proxy'", specifier = ">=3.11.6,<4.0" },
{ name = "packaging", specifier = ">=24.0" },
{ name = "polars", marker = "extra == 'proxy'", specifier = ">=1.38.1,<2.0" },
@ -4889,7 +4890,7 @@ ci = [
{ name = "pytest-codspeed", specifier = "==4.3.0" },
{ name = "pytest-retry", specifier = "==1.7.0" },
{ name = "tenacity", specifier = "==8.5.0" },
{ name = "traceloop-sdk", specifier = "==0.33.12" },
{ name = "traceloop-sdk", specifier = "==0.34.0" },
]
dev = [
{ name = "basedpyright", specifier = "==1.39.7" },
@ -4899,14 +4900,14 @@ dev = [
{ name = "fastapi-offline", specifier = "==1.7.6" },
{ name = "hypothesis", specifier = "==6.165.10" },
{ name = "keyring", specifier = "==25.7.0" },
{ name = "langfuse", specifier = "==2.59.7" },
{ name = "langfuse", specifier = ">=4.7,<5.0" },
{ name = "mypy", specifier = "==1.20.1" },
{ name = "numpy", specifier = ">=1.26.0,<3.0" },
{ name = "openapi-core", specifier = "==0.22.0" },
{ name = "opentelemetry-api", specifier = "==1.28.0" },
{ name = "opentelemetry-exporter-otlp", specifier = "==1.28.0" },
{ name = "opentelemetry-instrumentation-fastapi", specifier = "==0.49b0" },
{ name = "opentelemetry-sdk", specifier = "==1.28.0" },
{ name = "opentelemetry-api", specifier = "==1.33.1" },
{ name = "opentelemetry-exporter-otlp", specifier = "==1.33.1" },
{ name = "opentelemetry-instrumentation-fastapi", specifier = "==0.54b1" },
{ name = "opentelemetry-sdk", specifier = "==1.33.1" },
{ name = "parameterized", specifier = "==0.9.0" },
{ name = "psycopg", specifier = "==3.3.3" },
{ name = "psycopg-binary", specifier = "==3.3.3" },
@ -4949,10 +4950,10 @@ proxy-dev = [
{ name = "a2a-sdk", specifier = "==1.1.0" },
{ name = "azure-identity", specifier = "==1.25.2" },
{ name = "hypercorn", specifier = "==0.17.3" },
{ name = "opentelemetry-api", specifier = "==1.28.0" },
{ name = "opentelemetry-exporter-otlp", specifier = "==1.28.0" },
{ name = "opentelemetry-instrumentation-fastapi", specifier = "==0.49b0" },
{ name = "opentelemetry-sdk", specifier = "==1.28.0" },
{ name = "opentelemetry-api", specifier = "==1.33.1" },
{ name = "opentelemetry-exporter-otlp", specifier = "==1.33.1" },
{ name = "opentelemetry-instrumentation-fastapi", specifier = "==0.54b1" },
{ name = "opentelemetry-sdk", specifier = "==1.33.1" },
{ name = "prisma", specifier = "==0.11.0" },
{ name = "prometheus-client", specifier = "==0.20.0" },
]
@ -6152,45 +6153,45 @@ wheels = [
[[package]]
name = "opentelemetry-api"
version = "1.28.0"
version = "1.33.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "deprecated" },
{ name = "importlib-metadata" },
]
sdist = { url = "https://files.pythonhosted.org/packages/79/36/260eaea0f74fdd0c0d8f22ed3a3031109ea1c85531f94f4fde266c29e29a/opentelemetry_api-1.28.0.tar.gz", hash = "sha256:578610bcb8aa5cdcb11169d136cc752958548fb6ccffb0969c1036b0ee9e5353", size = 62803, upload-time = "2024-11-05T19:14:45.497Z" }
sdist = { url = "https://files.pythonhosted.org/packages/9a/8d/1f5a45fbcb9a7d87809d460f09dc3399e3fbd31d7f3e14888345e9d29951/opentelemetry_api-1.33.1.tar.gz", hash = "sha256:1c6055fc0a2d3f23a50c7e17e16ef75ad489345fd3df1f8b8af7c0bbf8a109e8", size = 65002, upload-time = "2025-05-16T18:52:41.146Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/22/e4/3b25d8b856791c04d8a62b1257b5fc09dc41a057800db06885af8ddcdce1/opentelemetry_api-1.28.0-py3-none-any.whl", hash = "sha256:8457cd2c59ea1bd0988560f021656cecd254ad7ef6be4ba09dbefeca2409ce52", size = 64314, upload-time = "2024-11-05T19:14:21.659Z" },
{ url = "https://files.pythonhosted.org/packages/05/44/4c45a34def3506122ae61ad684139f0bbc4e00c39555d4f7e20e0e001c8a/opentelemetry_api-1.33.1-py3-none-any.whl", hash = "sha256:4db83ebcf7ea93e64637ec6ee6fabee45c5cbe4abd9cf3da95c43828ddb50b83", size = 65771, upload-time = "2025-05-16T18:52:17.419Z" },
]
[[package]]
name = "opentelemetry-exporter-otlp"
version = "1.28.0"
version = "1.33.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-exporter-otlp-proto-grpc" },
{ name = "opentelemetry-exporter-otlp-proto-http" },
]
sdist = { url = "https://files.pythonhosted.org/packages/eb/16/14e3fc163930ea68f0980a4cdd4ae5796e60aeb898965990e13263d64baf/opentelemetry_exporter_otlp-1.28.0.tar.gz", hash = "sha256:31ae7495831681dd3da34ac457f6970f147465ae4b9aae3a888d7a581c7cd868", size = 6170, upload-time = "2024-11-05T19:14:47.349Z" }
sdist = { url = "https://files.pythonhosted.org/packages/b1/3f/c8ad4f1c3aaadcea2b0f1b4d7970e7b7898c145699769a789f3435143f69/opentelemetry_exporter_otlp-1.33.1.tar.gz", hash = "sha256:4d050311ea9486e3994575aa237e32932aad58330a31fba24fdba5c0d531cf04", size = 6189, upload-time = "2025-05-16T18:52:43.176Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c2/82/3f521b3c1f2a411ed60a24a8c9f486c1beeaf8c6c55337c87d3ae1642151/opentelemetry_exporter_otlp-1.28.0-py3-none-any.whl", hash = "sha256:1fd02d70f2c1b7ac5579c81e78de4594b188d3317c8ceb69e8b53900fb7b40fd", size = 7024, upload-time = "2024-11-05T19:14:24.534Z" },
{ url = "https://files.pythonhosted.org/packages/4d/32/b9add70dd4e845654fc9fcd1401a705477743880be6c3e62acb1ad0d8662/opentelemetry_exporter_otlp-1.33.1-py3-none-any.whl", hash = "sha256:9bcf1def35b880b55a49e31ebd63910edac14b294fd2ab884953c4deaff5b300", size = 7045, upload-time = "2025-05-16T18:52:21.022Z" },
]
[[package]]
name = "opentelemetry-exporter-otlp-proto-common"
version = "1.28.0"
version = "1.33.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-proto" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c2/8d/5d411084ac441052f4c9bae03a1aec65ae5d16b439fea7b9c5ac3842c013/opentelemetry_exporter_otlp_proto_common-1.28.0.tar.gz", hash = "sha256:5fa0419b0c8e291180b0fc8430a20dd44a3f3236f8e0827992145914f273ec4f", size = 18505, upload-time = "2024-11-05T19:14:48.204Z" }
sdist = { url = "https://files.pythonhosted.org/packages/7a/18/a1ec9dcb6713a48b4bdd10f1c1e4d5d2489d3912b80d2bcc059a9a842836/opentelemetry_exporter_otlp_proto_common-1.33.1.tar.gz", hash = "sha256:c57b3fa2d0595a21c4ed586f74f948d259d9949b58258f11edb398f246bec131", size = 20828, upload-time = "2025-05-16T18:52:43.795Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e1/72/3c44aabc74db325aaba09361b6a0d80f6d601f0ff86ecea8ee655c9538fc/opentelemetry_exporter_otlp_proto_common-1.28.0-py3-none-any.whl", hash = "sha256:467e6437d24e020156dffecece8c0a4471a8a60f6a34afeda7386df31a092410", size = 18403, upload-time = "2024-11-05T19:14:25.798Z" },
{ url = "https://files.pythonhosted.org/packages/09/52/9bcb17e2c29c1194a28e521b9d3f2ced09028934c3c52a8205884c94b2df/opentelemetry_exporter_otlp_proto_common-1.33.1-py3-none-any.whl", hash = "sha256:b81c1de1ad349785e601d02715b2d29d6818aed2c809c20219f3d1f20b038c36", size = 18839, upload-time = "2025-05-16T18:52:22.447Z" },
]
[[package]]
name = "opentelemetry-exporter-otlp-proto-grpc"
version = "1.28.0"
version = "1.33.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "deprecated" },
@ -6201,14 +6202,14 @@ dependencies = [
{ name = "opentelemetry-proto" },
{ name = "opentelemetry-sdk" },
]
sdist = { url = "https://files.pythonhosted.org/packages/43/4d/f215162e58041afb4bdf5dbd0d8faf0b7fc9bf7b3d3fc0e44e06f9e7e869/opentelemetry_exporter_otlp_proto_grpc-1.28.0.tar.gz", hash = "sha256:47a11c19dc7f4289e220108e113b7de90d59791cb4c37fc29f69a6a56f2c3735", size = 26237, upload-time = "2024-11-05T19:14:49.026Z" }
sdist = { url = "https://files.pythonhosted.org/packages/d8/5f/75ef5a2a917bd0e6e7b83d3fb04c99236ee958f6352ba3019ea9109ae1a6/opentelemetry_exporter_otlp_proto_grpc-1.33.1.tar.gz", hash = "sha256:345696af8dc19785fac268c8063f3dc3d5e274c774b308c634f39d9c21955728", size = 22556, upload-time = "2025-05-16T18:52:44.76Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1d/b5/afabc8106abc0f9cfeecf5b3e682622b3e04bba1d9b967dbfcd91b9c4ebe/opentelemetry_exporter_otlp_proto_grpc-1.28.0-py3-none-any.whl", hash = "sha256:edbdc53e7783f88d4535db5807cb91bd7b1ec9e9b9cdbfee14cd378f29a3b328", size = 18532, upload-time = "2024-11-05T19:14:26.853Z" },
{ url = "https://files.pythonhosted.org/packages/ba/ec/6047e230bb6d092c304511315b13893b1c9d9260044dd1228c9d48b6ae0e/opentelemetry_exporter_otlp_proto_grpc-1.33.1-py3-none-any.whl", hash = "sha256:7e8da32c7552b756e75b4f9e9c768a61eb47dee60b6550b37af541858d669ce1", size = 18591, upload-time = "2025-05-16T18:52:23.772Z" },
]
[[package]]
name = "opentelemetry-exporter-otlp-proto-http"
version = "1.28.0"
version = "1.33.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "deprecated" },
@ -6219,14 +6220,14 @@ dependencies = [
{ name = "opentelemetry-sdk" },
{ name = "requests" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f1/2a/555f2845928086cd51aa6941c7a546470805b68ed631ec139ce7d841763d/opentelemetry_exporter_otlp_proto_http-1.28.0.tar.gz", hash = "sha256:d83a9a03a8367ead577f02a64127d827c79567de91560029688dd5cfd0152a8e", size = 15051, upload-time = "2024-11-05T19:14:49.813Z" }
sdist = { url = "https://files.pythonhosted.org/packages/60/48/e4314ac0ed2ad043c07693d08c9c4bf5633857f5b72f2fefc64fd2b114f6/opentelemetry_exporter_otlp_proto_http-1.33.1.tar.gz", hash = "sha256:46622d964a441acb46f463ebdc26929d9dec9efb2e54ef06acdc7305e8593c38", size = 15353, upload-time = "2025-05-16T18:52:45.522Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b2/ce/80d5adabbf7ab4a0ca7b5e0f4039b24d273be370c3ba85fc05b13794411c/opentelemetry_exporter_otlp_proto_http-1.28.0-py3-none-any.whl", hash = "sha256:e8f3f7961b747edb6b44d51de4901a61e9c01d50debd747b120a08c4996c7e7b", size = 17228, upload-time = "2024-11-05T19:14:28.613Z" },
{ url = "https://files.pythonhosted.org/packages/63/ba/5a4ad007588016fe37f8d36bf08f325fe684494cc1e88ca8fa064a4c8f57/opentelemetry_exporter_otlp_proto_http-1.33.1-py3-none-any.whl", hash = "sha256:ebd6c523b89a2ecba0549adb92537cc2bf647b4ee61afbbd5a4c6535aa3da7cf", size = 17733, upload-time = "2025-05-16T18:52:25.137Z" },
]
[[package]]
name = "opentelemetry-instrumentation"
version = "0.49b0"
version = "0.54b1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
@ -6234,14 +6235,14 @@ dependencies = [
{ name = "packaging" },
{ name = "wrapt" },
]
sdist = { url = "https://files.pythonhosted.org/packages/de/6b/6c25b15063c92a011cf3f68375971e2c58a9c764690847edc97df2d94eeb/opentelemetry_instrumentation-0.49b0.tar.gz", hash = "sha256:398a93e0b9dc2d11cc8627e1761665c506fe08c6b2df252a2ab3ade53d751c46", size = 26478, upload-time = "2024-11-05T19:21:41.402Z" }
sdist = { url = "https://files.pythonhosted.org/packages/c3/fd/5756aea3fdc5651b572d8aef7d94d22a0a36e49c8b12fcb78cb905ba8896/opentelemetry_instrumentation-0.54b1.tar.gz", hash = "sha256:7658bf2ff914b02f246ec14779b66671508125c0e4227361e56b5ebf6cef0aec", size = 28436, upload-time = "2025-05-16T19:03:22.223Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/93/61/e0d21e958d6072ce25c4f5e26a1d22835fc86f80836660adf6badb6038ce/opentelemetry_instrumentation-0.49b0-py3-none-any.whl", hash = "sha256:68364d73a1ff40894574cbc6138c5f98674790cae1f3b0865e21cf702f24dcb3", size = 30694, upload-time = "2024-11-05T19:20:38.584Z" },
{ url = "https://files.pythonhosted.org/packages/f4/89/0790abc5d9c4fc74bd3e03cb87afe2c820b1d1a112a723c1163ef32453ee/opentelemetry_instrumentation-0.54b1-py3-none-any.whl", hash = "sha256:a4ae45f4a90c78d7006c51524f57cd5aa1231aef031eae905ee34d5423f5b198", size = 31019, upload-time = "2025-05-16T19:02:15.611Z" },
]
[[package]]
name = "opentelemetry-instrumentation-alephalpha"
version = "0.33.12"
version = "0.34.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
@ -6249,14 +6250,14 @@ dependencies = [
{ name = "opentelemetry-semantic-conventions" },
{ name = "opentelemetry-semantic-conventions-ai" },
]
sdist = { url = "https://files.pythonhosted.org/packages/47/32/15048d7773f6018abcd5b85f5c346b44fad8322031f6b4ea5a6c5ada304a/opentelemetry_instrumentation_alephalpha-0.33.12.tar.gz", hash = "sha256:b474ac634cd1e12b30c8863a925320a01043af8c0f46fd58288e587073d6ddec", size = 3727, upload-time = "2024-11-13T20:27:50.425Z" }
sdist = { url = "https://files.pythonhosted.org/packages/64/12/b962c7fd3d29bc4ffe70f41fab8054d0221ebfecc28a344aef6fc749be67/opentelemetry_instrumentation_alephalpha-0.34.0.tar.gz", hash = "sha256:ed6647505963d53aed63b0b2ca84c989ca94ccc215ad19355a7de33e0b10f0ac", size = 3688, upload-time = "2024-12-12T21:02:01.771Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d3/77/e483e2fa14fddc87324b242d59992cfbd2d590563352aa044d11e1d200ed/opentelemetry_instrumentation_alephalpha-0.33.12-py3-none-any.whl", hash = "sha256:b3c7e3dd99121f5c52d7c7a3a82dd2d7a9ba7360f63ac6fcbdca187f58756e16", size = 5116, upload-time = "2024-11-13T20:27:12.818Z" },
{ url = "https://files.pythonhosted.org/packages/ef/1b/d37c9af6319ad64b182f77aec1154f5fab25b9123c9e04fe1a6d19e19e7e/opentelemetry_instrumentation_alephalpha-0.34.0-py3-none-any.whl", hash = "sha256:4e05e1b12edf30597e3cb6163d2e63f938fd3b061a3251940ac12783d1103ce6", size = 5101, upload-time = "2024-12-12T21:01:12.317Z" },
]
[[package]]
name = "opentelemetry-instrumentation-anthropic"
version = "0.33.12"
version = "0.34.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
@ -6264,14 +6265,14 @@ dependencies = [
{ name = "opentelemetry-semantic-conventions" },
{ name = "opentelemetry-semantic-conventions-ai" },
]
sdist = { url = "https://files.pythonhosted.org/packages/40/0a/cba0a6ac1832e3002158b5a9451268aebfe0150c7b8355068d1f2cea148b/opentelemetry_instrumentation_anthropic-0.33.12.tar.gz", hash = "sha256:0bc1fd9d4cf2feec4fe9f80c0bdfcbfab33ed9cf0edea850b6c198a8679b01ff", size = 8711, upload-time = "2024-11-13T20:27:52.005Z" }
sdist = { url = "https://files.pythonhosted.org/packages/d1/56/57bbdb8907e14793d9831b220a6561a29033204acc60ce2ebc6387d29ad5/opentelemetry_instrumentation_anthropic-0.34.0.tar.gz", hash = "sha256:ab4336723de8cc3327aeacfab6e2fa085101f92614a402ee2822f8fb557ba7a6", size = 8693, upload-time = "2024-12-12T21:02:02.731Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ba/46/ba2dc8d18b04acae3d34facd8fe1e5e0cdc9fe64292d45eca9d1d4a8a298/opentelemetry_instrumentation_anthropic-0.33.12-py3-none-any.whl", hash = "sha256:b31618d12a429045db14ed982a142a25df0f0f1dbf03d756e8d597f25b9a053d", size = 11024, upload-time = "2024-11-13T20:27:14.622Z" },
{ url = "https://files.pythonhosted.org/packages/5c/8e/ef2782ecd3e2b03fb792f42ade5fea3c549ba28e6ebefdcf95a4c14412df/opentelemetry_instrumentation_anthropic-0.34.0-py3-none-any.whl", hash = "sha256:8fc397802033636eb74967ffc6a85344e575ea615b5de502386b0a004b07ba68", size = 11005, upload-time = "2024-12-12T21:01:13.846Z" },
]
[[package]]
name = "opentelemetry-instrumentation-asgi"
version = "0.49b0"
version = "0.54b1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "asgiref" },
@ -6280,14 +6281,14 @@ dependencies = [
{ name = "opentelemetry-semantic-conventions" },
{ name = "opentelemetry-util-http" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e8/55/693c3d0938ba5fead5c3aa4ac7022a992b4ff99a8e9979800d0feb843ff4/opentelemetry_instrumentation_asgi-0.49b0.tar.gz", hash = "sha256:959fd9b1345c92f20c6ef1d42f92ef6a76b3c3083fbc4104d59da6859b15b083", size = 24117, upload-time = "2024-11-05T19:21:46.769Z" }
sdist = { url = "https://files.pythonhosted.org/packages/20/f7/a3377f9771947f4d3d59c96841d3909274f446c030dbe8e4af871695ddee/opentelemetry_instrumentation_asgi-0.54b1.tar.gz", hash = "sha256:ab4df9776b5f6d56a78413c2e8bbe44c90694c67c844a1297865dc1bd926ed3c", size = 24230, upload-time = "2025-05-16T19:03:30.234Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2c/0b/7900c782a1dfaa584588d724bc3bbdf8405a32497537dd96b3fcbf8461b9/opentelemetry_instrumentation_asgi-0.49b0-py3-none-any.whl", hash = "sha256:722a90856457c81956c88f35a6db606cc7db3231046b708aae2ddde065723dbe", size = 16326, upload-time = "2024-11-05T19:20:46.176Z" },
{ url = "https://files.pythonhosted.org/packages/20/24/7a6f0ae79cae49927f528ecee2db55a5bddd87b550e310ce03451eae7491/opentelemetry_instrumentation_asgi-0.54b1-py3-none-any.whl", hash = "sha256:84674e822b89af563b283a5283c2ebb9ed585d1b80a1c27fb3ac20b562e9f9fc", size = 16338, upload-time = "2025-05-16T19:02:22.808Z" },
]
[[package]]
name = "opentelemetry-instrumentation-bedrock"
version = "0.33.12"
version = "0.34.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anthropic" },
@ -6296,14 +6297,14 @@ dependencies = [
{ name = "opentelemetry-semantic-conventions" },
{ name = "opentelemetry-semantic-conventions-ai" },
]
sdist = { url = "https://files.pythonhosted.org/packages/95/5a/346c17fca4dd929ce6be8cf402cd3580bb6e4da42ca8eadd2b6f2b4907e4/opentelemetry_instrumentation_bedrock-0.33.12.tar.gz", hash = "sha256:6f5a3f7044edff020d62b3e94f0ea543da4e5c23b7cdb72642692952843b0003", size = 7690, upload-time = "2024-11-13T20:27:53.497Z" }
sdist = { url = "https://files.pythonhosted.org/packages/aa/79/c384051d3e234ffb5f995ecb2245aef54083dc4919258601d9449c8c47bd/opentelemetry_instrumentation_bedrock-0.34.0.tar.gz", hash = "sha256:07f0ed84fa6d9e93c8cefee48ce171c59961c44708fcc11ec21fc1fbcdfb314d", size = 7695, upload-time = "2024-12-12T21:02:04.602Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/04/d93857519edd693e72e6d9ba08a6f0feda2ca21a08e3bc02cbfe242495f6/opentelemetry_instrumentation_bedrock-0.33.12-py3-none-any.whl", hash = "sha256:f9749898c52643d5027b45ac92bf4d3fd39b83adfaf68705a0ed9b4f04b8afae", size = 8982, upload-time = "2024-11-13T20:27:15.935Z" },
{ url = "https://files.pythonhosted.org/packages/56/2c/6d3e353d69407b308a254713728a613651bbe34138956f4f6b0104a5cc0a/opentelemetry_instrumentation_bedrock-0.34.0-py3-none-any.whl", hash = "sha256:1e521e33721e0fbcde2c2cb7cf788e2b8926063846800db777678be583bf1420", size = 8966, upload-time = "2024-12-12T21:01:16.457Z" },
]
[[package]]
name = "opentelemetry-instrumentation-chromadb"
version = "0.33.12"
version = "0.34.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
@ -6311,14 +6312,14 @@ dependencies = [
{ name = "opentelemetry-semantic-conventions" },
{ name = "opentelemetry-semantic-conventions-ai" },
]
sdist = { url = "https://files.pythonhosted.org/packages/62/05/ae78dd08c30203009815b35bce9b524458d73174e7dc4924a431e7b0b65b/opentelemetry_instrumentation_chromadb-0.33.12.tar.gz", hash = "sha256:eb4c591d398963504f82c20879030ea3694f10065ee62450da761c9b6e1792e7", size = 4598, upload-time = "2024-11-13T20:27:54.38Z" }
sdist = { url = "https://files.pythonhosted.org/packages/29/8e/0846e9c8846eee6f782767a1ee2f760ed5ca53cc95035189706c63027d58/opentelemetry_instrumentation_chromadb-0.34.0.tar.gz", hash = "sha256:ed0b4842db9bd35a0cff138d88d84d63a1529038ac11cf37eeba1dd294d4a2e8", size = 4596, upload-time = "2024-12-12T21:02:06.825Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cc/2f/3fabec28e538fc0671c5c149e979e6f36f823078aa8c43ab0f69392185e1/opentelemetry_instrumentation_chromadb-0.33.12-py3-none-any.whl", hash = "sha256:2413426c3bf1f3714a95318e934f090fa778ab7b3d7bdd2cc8ee068cda216a06", size = 6322, upload-time = "2024-11-13T20:27:18.789Z" },
{ url = "https://files.pythonhosted.org/packages/9a/b6/132c1cdd8dea4f0e4e1cab910dabdacd9802fb3a8e802e0c825bf6e9691f/opentelemetry_instrumentation_chromadb-0.34.0-py3-none-any.whl", hash = "sha256:d95df8285405a23b82c3b6d0c1b7c439ec86793d21b3a23e51965853d3e9c4a6", size = 6303, upload-time = "2024-12-12T21:01:17.711Z" },
]
[[package]]
name = "opentelemetry-instrumentation-cohere"
version = "0.33.12"
version = "0.34.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
@ -6326,14 +6327,14 @@ dependencies = [
{ name = "opentelemetry-semantic-conventions" },
{ name = "opentelemetry-semantic-conventions-ai" },
]
sdist = { url = "https://files.pythonhosted.org/packages/fe/96/f9cfc4f27c20deabfca237cb26734f060525b43e6993f753fad4ee0eded1/opentelemetry_instrumentation_cohere-0.33.12.tar.gz", hash = "sha256:4ea626d096fdf4c64e04a63b437e36f72a4341f818034ee6dc73ba1dba9ab341", size = 4235, upload-time = "2024-11-13T20:27:55.358Z" }
sdist = { url = "https://files.pythonhosted.org/packages/b2/bc/d38c64d0e0f92fb8b8bcde241024dd5d4810c2fbe379fdfbbbb32dee957f/opentelemetry_instrumentation_cohere-0.34.0.tar.gz", hash = "sha256:80e27c6f86a73a2c0e89aa3c9ca1a37ff58a01b4c0eb7f249d7ae66568730477", size = 4227, upload-time = "2024-12-12T21:02:08.177Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/bf/08/dce2b7926ace0204ce7946563348e1ff755873e387833484791e4ed391c8/opentelemetry_instrumentation_cohere-0.33.12-py3-none-any.whl", hash = "sha256:3bee3f7f7105259c85145be8c3b68612421860c95ad170f4d03144a3b8c07418", size = 5589, upload-time = "2024-11-13T20:27:21.317Z" },
{ url = "https://files.pythonhosted.org/packages/e3/bb/5efa301486ad236777d15b515158224cb17ca4e1f138e1480ce8a9d5c369/opentelemetry_instrumentation_cohere-0.34.0-py3-none-any.whl", hash = "sha256:6238c84948d809ea5feb1ce603de2c8f9d72d7b8286d9f9115edf33b91202011", size = 5576, upload-time = "2024-12-12T21:01:20.273Z" },
]
[[package]]
name = "opentelemetry-instrumentation-fastapi"
version = "0.49b0"
version = "0.54b1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
@ -6342,14 +6343,14 @@ dependencies = [
{ name = "opentelemetry-semantic-conventions" },
{ name = "opentelemetry-util-http" },
]
sdist = { url = "https://files.pythonhosted.org/packages/fe/bf/8e6d2a4807360f2203192017eb4845f5628dbeaf0597adf3d141cc5c24e1/opentelemetry_instrumentation_fastapi-0.49b0.tar.gz", hash = "sha256:6d14935c41fd3e49328188b6a59dd4c37bd17a66b01c15b0c64afa9714a1f905", size = 19230, upload-time = "2024-11-05T19:21:59.361Z" }
sdist = { url = "https://files.pythonhosted.org/packages/98/3b/9a262cdc1a4defef0e52afebdde3e8add658cc6f922e39e9dcee0da98349/opentelemetry_instrumentation_fastapi-0.54b1.tar.gz", hash = "sha256:1fcad19cef0db7092339b571a59e6f3045c9b58b7fd4670183f7addc459d78df", size = 19325, upload-time = "2025-05-16T19:03:45.359Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b1/f4/0895b9410c10abf987c90dee1b7688a8f2214a284fe15e575648f6a1473a/opentelemetry_instrumentation_fastapi-0.49b0-py3-none-any.whl", hash = "sha256:646e1b18523cbe6860ae9711eb2c7b9c85466c3c7697cd6b8fb5180d85d3fe6e", size = 12101, upload-time = "2024-11-05T19:21:01.805Z" },
{ url = "https://files.pythonhosted.org/packages/df/9c/6b2b0f9d6c5dea7528ae0bf4e461dd765b0ae35f13919cd452970bb0d0b3/opentelemetry_instrumentation_fastapi-0.54b1-py3-none-any.whl", hash = "sha256:fb247781cfa75fd09d3d8713c65e4a02bd1e869b00e2c322cc516d4b5429860c", size = 12125, upload-time = "2025-05-16T19:02:41.172Z" },
]
[[package]]
name = "opentelemetry-instrumentation-google-generativeai"
version = "0.33.12"
version = "0.34.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
@ -6357,14 +6358,14 @@ dependencies = [
{ name = "opentelemetry-semantic-conventions" },
{ name = "opentelemetry-semantic-conventions-ai" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b0/39/d33585303893fec6d4e828b794b8b26188658e3bd905098e568031eb0698/opentelemetry_instrumentation_google_generativeai-0.33.12.tar.gz", hash = "sha256:9d09cd39afecf70063733b3f2f15200b7dc28addfa6384947a9514557f18d64b", size = 4302, upload-time = "2024-11-13T20:27:56.179Z" }
sdist = { url = "https://files.pythonhosted.org/packages/30/c8/4620090d09b3d450ac7069ad84b366b34c2488df290c7fc0af6582178812/opentelemetry_instrumentation_google_generativeai-0.34.0.tar.gz", hash = "sha256:b0ecc9cb840277d4040277158c4d77a48c171a64ca556c54ddc1c5ce5105ebd8", size = 4288, upload-time = "2024-12-12T21:02:10.891Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/11/9a/622ca1552d05b5b948c1f0e78d8456464697118c63a58d4f5aa01c105d45/opentelemetry_instrumentation_google_generativeai-0.33.12-py3-none-any.whl", hash = "sha256:0dcd71c38331c47663d7ba6237ddfe02c14e3d1e3a47524c1437e3ee56cd0036", size = 5889, upload-time = "2024-11-13T20:27:22.37Z" },
{ url = "https://files.pythonhosted.org/packages/37/6e/e20b5fce0020a1f3de78227610a7d018764e9b26e8766c4f948493c2485e/opentelemetry_instrumentation_google_generativeai-0.34.0-py3-none-any.whl", hash = "sha256:eb42d8d48e3d13e03363932b69f424d27e8d9a53c8cbd23f190c4a294a881edc", size = 5879, upload-time = "2024-12-12T21:01:22.735Z" },
]
[[package]]
name = "opentelemetry-instrumentation-groq"
version = "0.33.12"
version = "0.34.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
@ -6372,14 +6373,14 @@ dependencies = [
{ name = "opentelemetry-semantic-conventions" },
{ name = "opentelemetry-semantic-conventions-ai" },
]
sdist = { url = "https://files.pythonhosted.org/packages/fa/39/71c87d595a312e2cfef83b006070e5d56c73895c59b596336a20aa43e79c/opentelemetry_instrumentation_groq-0.33.12.tar.gz", hash = "sha256:1460901e66c87b47198d639fb22ec25552281cdf7cafe13ae9605447661d6871", size = 5687, upload-time = "2024-11-13T20:28:00.703Z" }
sdist = { url = "https://files.pythonhosted.org/packages/af/1d/443944e52fc37a5e564525134dd86bee6a3f2db7be1c08f6459f056965ad/opentelemetry_instrumentation_groq-0.34.0.tar.gz", hash = "sha256:0c9162ce1a7b5b5a613dbf50f5f2ee8d5e6e175e0cc1758d53d71cb22c7aac1b", size = 5670, upload-time = "2024-12-12T21:02:12.256Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f7/24/631269741eabb0b028a313f15063871b28e700ba27feece154a3dd71f62d/opentelemetry_instrumentation_groq-0.33.12-py3-none-any.whl", hash = "sha256:4d239c73d689c046ab2c90a25b78d6c7406cef1e26f04633bc148464b66cc74c", size = 7270, upload-time = "2024-11-13T20:27:23.508Z" },
{ url = "https://files.pythonhosted.org/packages/a3/81/beb464fdd0d3f568b589b45629f74e0fb1a1e518a9df2f575bb68ea2096a/opentelemetry_instrumentation_groq-0.34.0-py3-none-any.whl", hash = "sha256:0f74c8b0df2984b27aadabebf3bed4443c0db45fbd851f956299207be12bb207", size = 7252, upload-time = "2024-12-12T21:01:24.069Z" },
]
[[package]]
name = "opentelemetry-instrumentation-haystack"
version = "0.33.12"
version = "0.34.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
@ -6387,14 +6388,14 @@ dependencies = [
{ name = "opentelemetry-semantic-conventions" },
{ name = "opentelemetry-semantic-conventions-ai" },
]
sdist = { url = "https://files.pythonhosted.org/packages/9c/06/067e4b2db2bc29d0a7e3a6cc8676d5f1971b0ecbaf7e5fa0c1e478e092af/opentelemetry_instrumentation_haystack-0.33.12.tar.gz", hash = "sha256:3d45df14aff1f2321066e55ecce632653d67c36249d3eaccbefa189f0daaba05", size = 4663, upload-time = "2024-11-13T20:28:01.551Z" }
sdist = { url = "https://files.pythonhosted.org/packages/c3/61/2aa5d850c1891fd99636a4ad724489ed792ac4aa560be75ab34af0ee26eb/opentelemetry_instrumentation_haystack-0.34.0.tar.gz", hash = "sha256:29739e9429a1a327dc72f743a0b37a3b7f26a742ac762791a75b1bc2f3ba43ff", size = 4645, upload-time = "2024-12-12T21:02:13.193Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/dc/ba/b8872dce7eb67bd6589d4cccfc97554851dad719067b24c7697a5ffef69a/opentelemetry_instrumentation_haystack-0.33.12-py3-none-any.whl", hash = "sha256:d2a3041a58e1027d8728e1a24430f7b01e8c27e9c04c22fa4204c590b12a95f6", size = 7513, upload-time = "2024-11-13T20:27:24.671Z" },
{ url = "https://files.pythonhosted.org/packages/58/15/682dfc4717e4ddbb668fdcb5a12a8b22a2f6c9402d78c26690528722e8e5/opentelemetry_instrumentation_haystack-0.34.0-py3-none-any.whl", hash = "sha256:2ae56f4abc7a2bafad7b2b3ec8e218edf2aa0daaa6570c692076c24682ee78ce", size = 7495, upload-time = "2024-12-12T21:01:25.723Z" },
]
[[package]]
name = "opentelemetry-instrumentation-lancedb"
version = "0.33.12"
version = "0.34.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
@ -6402,14 +6403,14 @@ dependencies = [
{ name = "opentelemetry-semantic-conventions" },
{ name = "opentelemetry-semantic-conventions-ai" },
]
sdist = { url = "https://files.pythonhosted.org/packages/38/52/16eef8e5c92627a82904f0112715ee95b2a6ee74ec958ec69f7db803a8d5/opentelemetry_instrumentation_lancedb-0.33.12.tar.gz", hash = "sha256:0aa9f6319374f532e2087949c15674f4d8036591ba70f91f9ce6996ea34508c3", size = 3198, upload-time = "2024-11-13T20:28:02.455Z" }
sdist = { url = "https://files.pythonhosted.org/packages/05/00/ad6383e2981308146e282da4d977ed61dd63321c6ac72751aa1c9eb26d74/opentelemetry_instrumentation_lancedb-0.34.0.tar.gz", hash = "sha256:5d081f36335d7b5dd3a8ae3b0fac0b895f4284941e3521f32332d3393b3b1178", size = 3185, upload-time = "2024-12-12T21:02:14.193Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/26/1d/218b74341471aa370999f7dbee09de44e32d8f822d69b6ca6d95f400be36/opentelemetry_instrumentation_lancedb-0.33.12-py3-none-any.whl", hash = "sha256:e1cdd55ef38d939d8af924478486e66c0cf65a7e5ac19c82f20ad5d06e682b9d", size = 4794, upload-time = "2024-11-13T20:27:25.825Z" },
{ url = "https://files.pythonhosted.org/packages/5b/65/db706f845a5ab861ee59feb6eb394843e27bf0f025bc1438e44a7af19f19/opentelemetry_instrumentation_lancedb-0.34.0-py3-none-any.whl", hash = "sha256:b8284453cb3d98fbe83bd286448eca4edbb779fc79ffc58bdb3a344137d82719", size = 4780, upload-time = "2024-12-12T21:01:26.96Z" },
]
[[package]]
name = "opentelemetry-instrumentation-langchain"
version = "0.33.12"
version = "0.34.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
@ -6417,14 +6418,14 @@ dependencies = [
{ name = "opentelemetry-semantic-conventions" },
{ name = "opentelemetry-semantic-conventions-ai" },
]
sdist = { url = "https://files.pythonhosted.org/packages/73/60/4fb638bc69cc63bbf7aad81a08650c99bd343a67f49c532261190e7ee7e4/opentelemetry_instrumentation_langchain-0.33.12.tar.gz", hash = "sha256:ff607742c76a1844211648415fa35da9eac22a42da2a9732c673bd13f2973994", size = 8518, upload-time = "2024-11-13T20:28:03.247Z" }
sdist = { url = "https://files.pythonhosted.org/packages/ce/b4/a8bafbc727a874eb26e788b1fe667db85c7dbcb6d685a6b9da07f6ba231b/opentelemetry_instrumentation_langchain-0.34.0.tar.gz", hash = "sha256:2a25bc07ff8719d30b9a01acf29305c7de5418683c14334ad7ddef4608222911", size = 8508, upload-time = "2024-12-12T21:02:15.138Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b1/fe/215a5b5b52360c94b2223f4dfb339665a60838d8f53edd37dd1c40897de4/opentelemetry_instrumentation_langchain-0.33.12-py3-none-any.whl", hash = "sha256:7406ab7116fa43343f53602f7b530f9bb1552e20ad750dbfc5aa1761c027c2d3", size = 9749, upload-time = "2024-11-13T20:27:27.623Z" },
{ url = "https://files.pythonhosted.org/packages/a1/3f/01f5d6e5fc3e34e068b6ad650bde73facb9748df74de305a33702ad06820/opentelemetry_instrumentation_langchain-0.34.0-py3-none-any.whl", hash = "sha256:373c69adcf18e9d37cd47d96fad78c57959c3f8af7034aff50553103fdbf0ba8", size = 9734, upload-time = "2024-12-12T21:01:28.244Z" },
]
[[package]]
name = "opentelemetry-instrumentation-llamaindex"
version = "0.33.12"
version = "0.34.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "inflection" },
@ -6433,27 +6434,27 @@ dependencies = [
{ name = "opentelemetry-semantic-conventions" },
{ name = "opentelemetry-semantic-conventions-ai" },
]
sdist = { url = "https://files.pythonhosted.org/packages/df/e7/9b9d43c7b78eea5ecf95b378ed4c12850f8745ff5b46ac5fa9042c89c941/opentelemetry_instrumentation_llamaindex-0.33.12.tar.gz", hash = "sha256:7a278dfe21fbba7dd1b8fe824c9baee0bfb3b4f7ccd71aae5f677412be45587e", size = 9285, upload-time = "2024-11-13T20:28:04.082Z" }
sdist = { url = "https://files.pythonhosted.org/packages/06/fe/b73490ee120672c81f78209a787feb1a5fbf19f2ec0657cf9b85277597ae/opentelemetry_instrumentation_llamaindex-0.34.0.tar.gz", hash = "sha256:f84eaa198873e856401fd8382f86d4f099e8edd712369579f4b74c24e0404933", size = 9274, upload-time = "2024-12-12T21:02:17.055Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d6/6a/aef813dff690cf06c62a86bf3f723ab1331b55f7c3dfd56690e93ac993df/opentelemetry_instrumentation_llamaindex-0.33.12-py3-none-any.whl", hash = "sha256:7f0d0700015f1e1576cf2de211a4062ae2d0ea899c298f4d7d44ce2a37226135", size = 16372, upload-time = "2024-11-13T20:27:28.724Z" },
{ url = "https://files.pythonhosted.org/packages/71/38/01d81a1bae3965031d612afe162a4fdee40d181b46d0f2aab7d2ac49d015/opentelemetry_instrumentation_llamaindex-0.34.0-py3-none-any.whl", hash = "sha256:0058a44a584ccb9046bed3d5da7bb64160c51d46f0a9946d2bb6517ffcd29fd0", size = 16354, upload-time = "2024-12-12T21:01:32.806Z" },
]
[[package]]
name = "opentelemetry-instrumentation-logging"
version = "0.49b0"
version = "0.54b1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
{ name = "opentelemetry-instrumentation" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c8/80/1d15f8afebc2b67ed47bfe45ee97c042808441586617d5aea8df1f1cbd96/opentelemetry_instrumentation_logging-0.49b0.tar.gz", hash = "sha256:d8058216b06c029785113a71428c6edbb3f0e3b9f69ee917050cb98cd8137fb2", size = 9731, upload-time = "2024-11-05T19:22:05.252Z" }
sdist = { url = "https://files.pythonhosted.org/packages/d9/5b/88ed39f22e8c6eb4f6192ab9a62adaa115579fcbcadb3f0241ee645eea56/opentelemetry_instrumentation_logging-0.54b1.tar.gz", hash = "sha256:893a3cbfda893b64ff71b81991894e2fd6a9267ba85bb6c251f51c0419fbe8fa", size = 9976, upload-time = "2025-05-16T19:03:49.976Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a7/c4/0eedcf9ccce07a64baa002fae7001d84f2c032cf5b2ff1a9438bff0479dd/opentelemetry_instrumentation_logging-0.49b0-py3-none-any.whl", hash = "sha256:9f9405d2f8e6fd756d49da979710f7b5ba1b95bd534467f176aae756102eed58", size = 12150, upload-time = "2024-11-05T19:21:07.826Z" },
{ url = "https://files.pythonhosted.org/packages/96/0c/b441fb30d860f25040eaed61e89d68f4d9ee31873159ed18cbc1b92eba56/opentelemetry_instrumentation_logging-0.54b1-py3-none-any.whl", hash = "sha256:01a4cec54348f13941707d857b850b0febf9d49f45d0fcf0673866e079d7357b", size = 12579, upload-time = "2025-05-16T19:02:49.039Z" },
]
[[package]]
name = "opentelemetry-instrumentation-marqo"
version = "0.33.12"
version = "0.34.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
@ -6461,14 +6462,14 @@ dependencies = [
{ name = "opentelemetry-semantic-conventions" },
{ name = "opentelemetry-semantic-conventions-ai" },
]
sdist = { url = "https://files.pythonhosted.org/packages/82/fb/775a1a5f9b9f641b3c7aa7ea8a7d83cbb97a4ddf86e1c5a4dd2a3c42af8d/opentelemetry_instrumentation_marqo-0.33.12.tar.gz", hash = "sha256:802def00b35033055618dc137f81895496bb449ff405580ff9414eeda139b89f", size = 3479, upload-time = "2024-11-13T20:28:04.892Z" }
sdist = { url = "https://files.pythonhosted.org/packages/e7/50/2585b0d337a15b7fe31ec0e7245c09154b7d3d7e7e172c3973d40ad313ee/opentelemetry_instrumentation_marqo-0.34.0.tar.gz", hash = "sha256:7bcc091b89717ac7b04c224dfc1429f200ba2b3e930d7a4de80bf9bc054fc0db", size = 3471, upload-time = "2024-12-12T21:02:17.979Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5b/b1/153592356d8cc6faab61f7e5c727ab3c382984cede3fbdc228872ba5ac7c/opentelemetry_instrumentation_marqo-0.33.12-py3-none-any.whl", hash = "sha256:6f939532f1f953a22eb2811dfdb439bb8bd813182d59d3444dcc4eca46d0805f", size = 5091, upload-time = "2024-11-13T20:27:31.133Z" },
{ url = "https://files.pythonhosted.org/packages/d4/51/9be4f5df62db6ff6e786933136541e3534656bb49a7d35324d96a5c07818/opentelemetry_instrumentation_marqo-0.34.0-py3-none-any.whl", hash = "sha256:dd342cfd4b70d4f65830708bc253397734d3da51d3773b677693e9007217e3ed", size = 5077, upload-time = "2024-12-12T21:01:35.643Z" },
]
[[package]]
name = "opentelemetry-instrumentation-milvus"
version = "0.33.12"
version = "0.34.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
@ -6476,14 +6477,14 @@ dependencies = [
{ name = "opentelemetry-semantic-conventions" },
{ name = "opentelemetry-semantic-conventions-ai" },
]
sdist = { url = "https://files.pythonhosted.org/packages/58/be/d86538d7b09c6ed77f137229ba418e402659e4aa9268ffff696e059ec8fb/opentelemetry_instrumentation_milvus-0.33.12.tar.gz", hash = "sha256:8720e8fd29ea3009dd0e5b8849b1d24657a5750d81f6c1af605e8aabe827f742", size = 3666, upload-time = "2024-11-13T20:28:06.004Z" }
sdist = { url = "https://files.pythonhosted.org/packages/3b/a8/18725e95cb5cf0d01c001698aa4b01198b5f205da4bb98acc8e0b0da1b6c/opentelemetry_instrumentation_milvus-0.34.0.tar.gz", hash = "sha256:6c19aa93c392f5c736390320b27e035761f36ead904277943d62ac6662d77f83", size = 3657, upload-time = "2024-12-12T21:02:18.849Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6d/22/0972d94433358624e8f956228763650a2366921d4659035b260aad9775aa/opentelemetry_instrumentation_milvus-0.33.12-py3-none-any.whl", hash = "sha256:608783fa555aded64606cfda64f4aa6ace5c2f9790b2b920e114ca229ab00915", size = 5311, upload-time = "2024-11-13T20:27:32.219Z" },
{ url = "https://files.pythonhosted.org/packages/7b/fb/685282b0e0339d629d4fdb03af7d2904461f20c128ae88fa148847e8664a/opentelemetry_instrumentation_milvus-0.34.0-py3-none-any.whl", hash = "sha256:4c587c6031bc82d78189b31f6acd4f36a62ce3ae1f2b18bc7fabb667912cc2d7", size = 5294, upload-time = "2024-12-12T21:01:38.115Z" },
]
[[package]]
name = "opentelemetry-instrumentation-mistralai"
version = "0.33.12"
version = "0.34.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
@ -6491,14 +6492,14 @@ dependencies = [
{ name = "opentelemetry-semantic-conventions" },
{ name = "opentelemetry-semantic-conventions-ai" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f6/78/5cab3468d3885cc391f67ef0a4a845abb085aa8d786aaa565b9cd9c6612f/opentelemetry_instrumentation_mistralai-0.33.12.tar.gz", hash = "sha256:c22f7006a56180ab6384e47b4e49bde8597833f73955e48ac323cdbe107f06ae", size = 4383, upload-time = "2024-11-13T20:28:09.179Z" }
sdist = { url = "https://files.pythonhosted.org/packages/49/0e/3d86aa6b5a31a20ecadbd4423e83255fd09d9648f431ccc786665e0f98be/opentelemetry_instrumentation_mistralai-0.34.0.tar.gz", hash = "sha256:7c81d8602a16b37d698002a7b06233095fe5c17ddf2f0b9d973b78255cdf7547", size = 4387, upload-time = "2024-12-12T21:02:19.71Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/12/c8/f47d404273e7130f4e4360f33a093d73f8fbbf232ef536a761baeb91027c/opentelemetry_instrumentation_mistralai-0.33.12-py3-none-any.whl", hash = "sha256:66c5961a33492aaf4420ed1ab8c63e533162a06366108c3398c461d05b2a1154", size = 5858, upload-time = "2024-11-13T20:27:33.251Z" },
{ url = "https://files.pythonhosted.org/packages/16/c8/5644b1a821b60a34bebc58f96367571bcdcdf5ab1522137e13ae3a936360/opentelemetry_instrumentation_mistralai-0.34.0-py3-none-any.whl", hash = "sha256:f682b8d4011124fa326308e8fc4ce9e9fdbacfc72fc77431682d2ef950e636d8", size = 5842, upload-time = "2024-12-12T21:01:39.204Z" },
]
[[package]]
name = "opentelemetry-instrumentation-ollama"
version = "0.33.12"
version = "0.34.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
@ -6506,14 +6507,14 @@ dependencies = [
{ name = "opentelemetry-semantic-conventions" },
{ name = "opentelemetry-semantic-conventions-ai" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d9/aa/e9c0f903b8ae750794688f82a00ac0b7ab00a42e57d7c279738b5a80a0ce/opentelemetry_instrumentation_ollama-0.33.12.tar.gz", hash = "sha256:4cd012503f8d692453645353231e216c756fc926bdd3142e8c97fc8e87cbe06f", size = 4512, upload-time = "2024-11-13T20:28:10.969Z" }
sdist = { url = "https://files.pythonhosted.org/packages/ff/f2/4c8e16bb5b13a85d86f6a3c515bee05051dcc08f8023dd201a69c9c0580f/opentelemetry_instrumentation_ollama-0.34.0.tar.gz", hash = "sha256:c9cabfac35945eb9b167f174a9fcafe82ec7c70ae1ba04d462486d2ef4c20f70", size = 4491, upload-time = "2024-12-12T21:02:20.656Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/30/58/5f11976bc5fde11390e709d96c757e1b21ff73faf88d5b0fd97ace56b061/opentelemetry_instrumentation_ollama-0.33.12-py3-none-any.whl", hash = "sha256:ed5313f45f5d46e17d93096eba91ed6338abf535cf2d2eca648d0e2d621e9d6b", size = 5847, upload-time = "2024-11-13T20:27:35.323Z" },
{ url = "https://files.pythonhosted.org/packages/16/3b/1547e92c76b9dd3097a98a67a84b0641ecbba3348f07d5825ecfa3433c7d/opentelemetry_instrumentation_ollama-0.34.0-py3-none-any.whl", hash = "sha256:17beea413c78be8510409aa4b5a5f909ba9e9d14799fd6372b16d84cefb21120", size = 5832, upload-time = "2024-12-12T21:01:40.792Z" },
]
[[package]]
name = "opentelemetry-instrumentation-openai"
version = "0.33.12"
version = "0.34.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
@ -6522,14 +6523,14 @@ dependencies = [
{ name = "opentelemetry-semantic-conventions-ai" },
{ name = "tiktoken" },
]
sdist = { url = "https://files.pythonhosted.org/packages/10/ec/2f9bb0a22ba916c10b2ef63ccde48f17348c49c2a651b8590a94076308e8/opentelemetry_instrumentation_openai-0.33.12.tar.gz", hash = "sha256:2c6dfd74d9d56ca393f9dbfc92883c7397d63408ff18b3d9a774ea1611a48ed9", size = 14631, upload-time = "2024-11-13T20:28:11.767Z" }
sdist = { url = "https://files.pythonhosted.org/packages/b2/9a/04bb865c14d44111ccde056ffa994d5c29ee604c286d6248b2365f53d676/opentelemetry_instrumentation_openai-0.34.0.tar.gz", hash = "sha256:67fabd6b178837c3d115296654a0daaebeeec763789e3f7ffd9a3db6117b354e", size = 14967, upload-time = "2024-12-12T21:02:22.935Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c5/21/c4a2b70e9f3487ba7123fde8c55090ff4a3f227477261fac1d0b73d6d349/opentelemetry_instrumentation_openai-0.33.12-py3-none-any.whl", hash = "sha256:d5d0c83a469dbf7ab97d1c482ce78f7ba23c00015b01bc8be43cdc0e5d7c497f", size = 22089, upload-time = "2024-11-13T20:27:36.375Z" },
{ url = "https://files.pythonhosted.org/packages/fe/08/6b3c0404d53a2ca913a98fecb4228be9238965cf2f9092acf5c3e960cba0/opentelemetry_instrumentation_openai-0.34.0-py3-none-any.whl", hash = "sha256:22e902b1b830ca53a0a94ec523880a4d39a210e4ec34d0ce76605b726eed1aab", size = 22597, upload-time = "2024-12-12T21:01:43.669Z" },
]
[[package]]
name = "opentelemetry-instrumentation-pinecone"
version = "0.33.12"
version = "0.34.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
@ -6537,14 +6538,14 @@ dependencies = [
{ name = "opentelemetry-semantic-conventions" },
{ name = "opentelemetry-semantic-conventions-ai" },
]
sdist = { url = "https://files.pythonhosted.org/packages/9a/2f/f9e0d1d5eb6f3eee791dbeac13185f4b77bbdce774a01011e03a5d8e2a71/opentelemetry_instrumentation_pinecone-0.33.12.tar.gz", hash = "sha256:92ed3221bddb061ebe7f50cd4804c76c9f5d019e2afb967858c1be62c1a3ebf2", size = 4651, upload-time = "2024-11-13T20:28:14.402Z" }
sdist = { url = "https://files.pythonhosted.org/packages/0e/a0/35791b83b78157f4bfb2f7d9c356a1f42a6ffc30f17f2e96210dabe090f7/opentelemetry_instrumentation_pinecone-0.34.0.tar.gz", hash = "sha256:573483686da9fd2be48c6de870b87515e479d6ee489ebc471d7c90e0de4106e1", size = 4649, upload-time = "2024-12-12T21:02:23.857Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/32/36/8458e916a2cca0378ac28e6d70347d5263160600fcb47b131f029bdd390a/opentelemetry_instrumentation_pinecone-0.33.12-py3-none-any.whl", hash = "sha256:8d6185bd2f5bf34f3983cad48bbaa86fc72925c02a07ab4a79eb805401556b19", size = 6377, upload-time = "2024-11-13T20:27:38.177Z" },
{ url = "https://files.pythonhosted.org/packages/9b/39/0f09e3de4fa72f17438a1ab7f2a8693a9c983a1e1ad6bf6e72c590616e4a/opentelemetry_instrumentation_pinecone-0.34.0-py3-none-any.whl", hash = "sha256:d81387e703bfd59ff03ed46acf0bf6a0c11cf4cdec16a440acbdea18987fda71", size = 6363, upload-time = "2024-12-12T21:01:46.926Z" },
]
[[package]]
name = "opentelemetry-instrumentation-qdrant"
version = "0.33.12"
version = "0.34.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
@ -6552,14 +6553,14 @@ dependencies = [
{ name = "opentelemetry-semantic-conventions" },
{ name = "opentelemetry-semantic-conventions-ai" },
]
sdist = { url = "https://files.pythonhosted.org/packages/1e/0a/216d28c48dc8b9e37094c54cd1e3ef9a43609fc25b2eeb8bd939f0026047/opentelemetry_instrumentation_qdrant-0.33.12.tar.gz", hash = "sha256:ba34c6863c652f27ae28b9922b25d77617ece4a2233ad0c9c1ebd257605853a5", size = 3988, upload-time = "2024-11-13T20:28:18.929Z" }
sdist = { url = "https://files.pythonhosted.org/packages/da/03/bbf02439ba6c6077ac814957695846bc44edc4e631fce8f0cbb792aa5572/opentelemetry_instrumentation_qdrant-0.34.0.tar.gz", hash = "sha256:8d569b2d7ac70bbf7e75abe5f572ff9576fa175660d1ecdc98f60c6ea1d7010b", size = 3977, upload-time = "2024-12-12T21:02:24.795Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d2/dc/caa6f4951c84ecb11594ab8545a8692c8a166d618a67687e7f07694dcd97/opentelemetry_instrumentation_qdrant-0.33.12-py3-none-any.whl", hash = "sha256:e759fe49c67092197eaa547570d67e15f30675bfdc772839d0456d535297d4c0", size = 6317, upload-time = "2024-11-13T20:27:39.638Z" },
{ url = "https://files.pythonhosted.org/packages/2f/fe/1797190a4a6b81b50a5c83615590929a21775bd4cd8a855102703243fd51/opentelemetry_instrumentation_qdrant-0.34.0-py3-none-any.whl", hash = "sha256:34ef85e62f3039a2b61a68c6decdb9e5d05ab9f7303d08fc010d6d5ec8f144e0", size = 6302, upload-time = "2024-12-12T21:01:48.042Z" },
]
[[package]]
name = "opentelemetry-instrumentation-replicate"
version = "0.33.12"
version = "0.34.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
@ -6567,14 +6568,14 @@ dependencies = [
{ name = "opentelemetry-semantic-conventions" },
{ name = "opentelemetry-semantic-conventions-ai" },
]
sdist = { url = "https://files.pythonhosted.org/packages/89/1e/b4260513c9526b2113772e4bf10bee714555abfb6e7cf69f86326e5607d5/opentelemetry_instrumentation_replicate-0.33.12.tar.gz", hash = "sha256:5dafad1a7a20ba762f689f30c4f76bcb3817b617adb7da3288ac545d15a14565", size = 3767, upload-time = "2024-11-13T20:28:19.766Z" }
sdist = { url = "https://files.pythonhosted.org/packages/0e/98/a36a16df6876396962071d8fbc6f0dc1c81bc1fdcb324e72871683b83e51/opentelemetry_instrumentation_replicate-0.34.0.tar.gz", hash = "sha256:124796ff8593cd211bfa05773f70e8f087a8c0522a544be39bed212a95c8dec3", size = 3767, upload-time = "2024-12-12T21:02:25.678Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4d/6d/20dab686dce1ce491c97ea4a0feec86b9a2207891a2627b1f4a670d109b5/opentelemetry_instrumentation_replicate-0.33.12-py3-none-any.whl", hash = "sha256:dc527f470080248a57b738b63ed29eae2d82ef68a100fe6e3548f5de7677f2ed", size = 5189, upload-time = "2024-11-13T20:27:40.792Z" },
{ url = "https://files.pythonhosted.org/packages/69/4b/cb70ab819ec045c2e494deea99a542e4819c9d1c5f09ec99d6dffacb00ad/opentelemetry_instrumentation_replicate-0.34.0-py3-none-any.whl", hash = "sha256:c5f3d712702f3cbcfde619d08e83b1c2fd70e4ad36190d68575d576e27370c4d", size = 5175, upload-time = "2024-12-12T21:01:49.409Z" },
]
[[package]]
name = "opentelemetry-instrumentation-requests"
version = "0.49b0"
version = "0.54b1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
@ -6582,14 +6583,14 @@ dependencies = [
{ name = "opentelemetry-semantic-conventions" },
{ name = "opentelemetry-util-http" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c1/16/c71196d8f4cac30b6936c77567ae769f44ac97227255627f5277d825277d/opentelemetry_instrumentation_requests-0.49b0.tar.gz", hash = "sha256:b75a282b3641547272dc7d2fdc0dd68269d0c1e685e4d17579b7fbd34c19b6bb", size = 14123, upload-time = "2024-11-05T19:22:14.128Z" }
sdist = { url = "https://files.pythonhosted.org/packages/e7/45/116da84930d3dc2f5cdd876283ca96e9b96547bccee7eaa0bd01ce6bf046/opentelemetry_instrumentation_requests-0.54b1.tar.gz", hash = "sha256:3eca5d697c5564af04c6a1dd23b6a3ffbaf11e64887c6051655cee03998f4654", size = 15148, upload-time = "2025-05-16T19:04:00.488Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/79/33/4b8a4a839401290c44c65a8ca926a60a86c5ee3ecdcf54de4575c288b5ac/opentelemetry_instrumentation_requests-0.49b0-py3-none-any.whl", hash = "sha256:bb39803359e226b8eb0d4c8aaba6fd8a883a7f869fc331ff861743173b33d26d", size = 12368, upload-time = "2024-11-05T19:21:22.387Z" },
{ url = "https://files.pythonhosted.org/packages/2b/b1/6e33d2c3d3cc9e3ae20a9a77625ec81a509a0e5d7fa87e09e7f879468990/opentelemetry_instrumentation_requests-0.54b1-py3-none-any.whl", hash = "sha256:a0c4cd5d946224f336d6bd73cdabdecc6f80d5c39208f84eb96eb15f16cd41a0", size = 12968, upload-time = "2025-05-16T19:03:03.131Z" },
]
[[package]]
name = "opentelemetry-instrumentation-sagemaker"
version = "0.33.12"
version = "0.34.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
@ -6597,14 +6598,14 @@ dependencies = [
{ name = "opentelemetry-semantic-conventions" },
{ name = "opentelemetry-semantic-conventions-ai" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c2/97/2ddbceba0f95f9b28e7ed75ee2d38214ea1e7d071585d9afea35d0e71619/opentelemetry_instrumentation_sagemaker-0.33.12.tar.gz", hash = "sha256:286bb0e7765967212e111274ca523084d8105a3f18d1dfc90873bca60f6ad766", size = 4508, upload-time = "2024-11-13T20:28:21.829Z" }
sdist = { url = "https://files.pythonhosted.org/packages/e6/98/fc4a33b8800a4a774c50a4b3da83a95359b89b3744c259b2ebbafc3b2f3a/opentelemetry_instrumentation_sagemaker-0.34.0.tar.gz", hash = "sha256:b7c2be5ba9ea4f4b9705705cddc1c3474cf4cb4e6db9fdf6968ad97ec8e6f1df", size = 4506, upload-time = "2024-12-12T21:02:26.652Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c3/f3/21896275fb1b4954082c4b95277d8ce66d6e947f1c153b0f01182e9a852f/opentelemetry_instrumentation_sagemaker-0.33.12-py3-none-any.whl", hash = "sha256:da72e78a094106c3ce48e2410665016f161211976c577e92b4624dfbbc54e47e", size = 6296, upload-time = "2024-11-13T20:27:41.815Z" },
{ url = "https://files.pythonhosted.org/packages/89/c2/b60f211e51b3c8346073dde33e7053ba1027b943da50d34ec6f00afe7d78/opentelemetry_instrumentation_sagemaker-0.34.0-py3-none-any.whl", hash = "sha256:ed7a50a5a863bfc81bc792fd3bc7b33bbf0af9e279b6e527c79e93034deda1a0", size = 6282, upload-time = "2024-12-12T21:01:50.527Z" },
]
[[package]]
name = "opentelemetry-instrumentation-sqlalchemy"
version = "0.49b0"
version = "0.54b1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
@ -6613,28 +6614,28 @@ dependencies = [
{ name = "packaging" },
{ name = "wrapt" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a0/a7/24f6cce3808ae1802dd1b60d752fbab877db5655198929cf4ee8ea416923/opentelemetry_instrumentation_sqlalchemy-0.49b0.tar.gz", hash = "sha256:32658e520fc8b35823c722f5d8831d3a410b76dd2724adb2887befc041ddef04", size = 13194, upload-time = "2024-11-05T19:22:14.92Z" }
sdist = { url = "https://files.pythonhosted.org/packages/ac/33/78a25ae4233d42058bb0b363ba4fea7d7210e53c24e5e31f16d5cf6cf957/opentelemetry_instrumentation_sqlalchemy-0.54b1.tar.gz", hash = "sha256:97839acf1c9b96ded857fca57a09b86a56cf8d9eb6d706b7ceaee9352a460e03", size = 14620, upload-time = "2025-05-16T19:04:01.215Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ec/6b/a1a3685fed593282999cdc374ece15efbd56f8d774bd368bf7ff2cf5923c/opentelemetry_instrumentation_sqlalchemy-0.49b0-py3-none-any.whl", hash = "sha256:d854052d2b02cd0562e5628a514c8153fceada7f585137e173165dfd0a46ef6a", size = 13358, upload-time = "2024-11-05T19:21:23.654Z" },
{ url = "https://files.pythonhosted.org/packages/c7/2b/1c954885815614ef5c1e8c7bbf57a5275e64cd6fb5946b65e17162a34037/opentelemetry_instrumentation_sqlalchemy-0.54b1-py3-none-any.whl", hash = "sha256:d2ca5edb4c7ecef120d51aad6793b7da1cc80207ccfd31c437ee18f098e7c4c4", size = 14169, upload-time = "2025-05-16T19:03:04.119Z" },
]
[[package]]
name = "opentelemetry-instrumentation-threading"
version = "0.49b0"
version = "0.54b1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
{ name = "opentelemetry-instrumentation" },
{ name = "wrapt" },
]
sdist = { url = "https://files.pythonhosted.org/packages/80/88/b19f064ebf1650a7291cb7fcb623129997a7d8af603ffe7cd1907fe469ba/opentelemetry_instrumentation_threading-0.49b0.tar.gz", hash = "sha256:b65ec668a3ee73fccb1432edf52556f374cb9d9e5b160a6da3a6f67890adf444", size = 8283, upload-time = "2024-11-05T19:22:18.778Z" }
sdist = { url = "https://files.pythonhosted.org/packages/a0/bd/561245292e7cc78ac7a0a75537873aea87440cb9493d41371421b3308c2b/opentelemetry_instrumentation_threading-0.54b1.tar.gz", hash = "sha256:3a081085b59675baf7bd93126a681903e6304a5f283df5eaecdd44bcb66df578", size = 8774, upload-time = "2025-05-16T19:04:04.482Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e1/77/cf262caae1a8903bbe9c379dc6908fddc9f7bbd5c51866d7c7fbae2edb70/opentelemetry_instrumentation_threading-0.49b0-py3-none-any.whl", hash = "sha256:47a49931a2244c2b17db985c512e6c922328b891ff2b64d37b0cd3bd00fd00a9", size = 9072, upload-time = "2024-11-05T19:21:29.564Z" },
{ url = "https://files.pythonhosted.org/packages/81/10/d87ec07d69546adaad525ba5d40d27324a45cba29097d9854a53d9af5047/opentelemetry_instrumentation_threading-0.54b1-py3-none-any.whl", hash = "sha256:bc229e6cd3f2b29fafe0a8dd3141f452e16fcb4906bca4fbf52609f99fb1eb42", size = 9314, upload-time = "2025-05-16T19:03:09.527Z" },
]
[[package]]
name = "opentelemetry-instrumentation-together"
version = "0.33.12"
version = "0.34.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
@ -6642,14 +6643,14 @@ dependencies = [
{ name = "opentelemetry-semantic-conventions" },
{ name = "opentelemetry-semantic-conventions-ai" },
]
sdist = { url = "https://files.pythonhosted.org/packages/7f/5f/63ee7efc3de97e12eadc927ac4079caef454ee41f8e608bf7a83734024a9/opentelemetry_instrumentation_together-0.33.12.tar.gz", hash = "sha256:4ac8676560e93492bdd0540d67672424e26f1eb9a41a266d5248eb09b00dc4d2", size = 3907, upload-time = "2024-11-13T20:28:22.971Z" }
sdist = { url = "https://files.pythonhosted.org/packages/1c/b5/f306f884fd775aff4195ca1d8c7a1426829cd7060ff50b293be23e1ad869/opentelemetry_instrumentation_together-0.34.0.tar.gz", hash = "sha256:f8968d2aaae123e556e9bd7ce9213f40888a180a8014382bb738cff0bc8de8a1", size = 3907, upload-time = "2024-12-12T21:02:28.939Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e4/6a/56f3a5abea0a3086d24a32398231e0a578c01b7fad174cd69cdd644fb87d/opentelemetry_instrumentation_together-0.33.12-py3-none-any.whl", hash = "sha256:6a1941e3d02b1505bd79a1ef3540d1fe15bf4f61c72cc162445d25d8715386b3", size = 5284, upload-time = "2024-11-13T20:27:42.798Z" },
{ url = "https://files.pythonhosted.org/packages/78/82/32bc20923c9ecd4495a01bc2dcabd377e4fd82c9cf334ed3ad3a81afaf02/opentelemetry_instrumentation_together-0.34.0-py3-none-any.whl", hash = "sha256:9b5069c3a294c161d8ad638a6d234484a2c600f77260902fb8e15afdd8dfdd33", size = 5267, upload-time = "2024-12-12T21:01:51.576Z" },
]
[[package]]
name = "opentelemetry-instrumentation-transformers"
version = "0.33.12"
version = "0.34.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
@ -6657,14 +6658,14 @@ dependencies = [
{ name = "opentelemetry-semantic-conventions" },
{ name = "opentelemetry-semantic-conventions-ai" },
]
sdist = { url = "https://files.pythonhosted.org/packages/3f/25/f73bfae73a466b0ee206168a0ec2212db694132f0af75ff7bf8d7da74488/opentelemetry_instrumentation_transformers-0.33.12.tar.gz", hash = "sha256:d7b9c0d4bd71b834a79c2522455799feb7e76148e1dd371408e9907e847e8d6a", size = 3714, upload-time = "2024-11-13T20:28:24.399Z" }
sdist = { url = "https://files.pythonhosted.org/packages/02/54/1ab4fb5409cf6c48f7b0c0a48b39cbea70b4083d994719ba0975ba9a9580/opentelemetry_instrumentation_transformers-0.34.0.tar.gz", hash = "sha256:586b146509a90900486039850f5f3d63256c7f1546e1a897912ba454aa14e5af", size = 3714, upload-time = "2024-12-12T21:02:29.913Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/12/ef/4305bdf6af7161c2b38d5341b25bb2a0817f8ba40bf370bb6eeb131a223c/opentelemetry_instrumentation_transformers-0.33.12-py3-none-any.whl", hash = "sha256:14c3f3831a892ae38f8bb85240c195ed95e8fa996f60930e2e4f00bb73073036", size = 5255, upload-time = "2024-11-13T20:27:43.888Z" },
{ url = "https://files.pythonhosted.org/packages/25/e9/081aeb69bf4170a5d88de48db11837cce136649b35304ab0ac7164fcc06a/opentelemetry_instrumentation_transformers-0.34.0-py3-none-any.whl", hash = "sha256:984cf5e0f4ef31662382019e3a18edf821f8ce3c20d53aeea68cee5718aad752", size = 5241, upload-time = "2024-12-12T21:01:53.001Z" },
]
[[package]]
name = "opentelemetry-instrumentation-urllib3"
version = "0.49b0"
version = "0.54b1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
@ -6673,14 +6674,14 @@ dependencies = [
{ name = "opentelemetry-util-http" },
{ name = "wrapt" },
]
sdist = { url = "https://files.pythonhosted.org/packages/fb/fd/79fa96e997a9ba9f90dd6fd9bd20c67db8b965dea035e54b864665a2508d/opentelemetry_instrumentation_urllib3-0.49b0.tar.gz", hash = "sha256:33db59eafc80877c225467bf71dfe098874dd7f4463a4f12c61fb7dbcd3b4e31", size = 15432, upload-time = "2024-11-05T19:22:23.261Z" }
sdist = { url = "https://files.pythonhosted.org/packages/ed/6f/76a46806cd21002cac1bfd087f5e4674b195ab31ab44c773ca534b6bb546/opentelemetry_instrumentation_urllib3-0.54b1.tar.gz", hash = "sha256:0d30ba3b230e4100cfadaad29174bf7bceac70e812e4f5204e681e4b55a74cd9", size = 15697, upload-time = "2025-05-16T19:04:07.709Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/82/56/6339b51038142ffacc33821d1cf9a3cf91d9c9166088a5c7d862d40000bb/opentelemetry_instrumentation_urllib3-0.49b0-py3-none-any.whl", hash = "sha256:672855f033e608c857353b6e098551f70088664fbec227f4ea5d90463d602adc", size = 12847, upload-time = "2024-11-05T19:21:34.14Z" },
{ url = "https://files.pythonhosted.org/packages/ff/7a/d75bec41edb6deaf1d2859bab66a84c8ba03e822e7eafdb245da205e53f6/opentelemetry_instrumentation_urllib3-0.54b1-py3-none-any.whl", hash = "sha256:e87958c297ddd36d30e1c9069f34a9690e845e4ccc2662dd80e99ed976d4c03e", size = 13123, upload-time = "2025-05-16T19:03:14.053Z" },
]
[[package]]
name = "opentelemetry-instrumentation-vertexai"
version = "0.33.12"
version = "0.34.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
@ -6688,14 +6689,14 @@ dependencies = [
{ name = "opentelemetry-semantic-conventions" },
{ name = "opentelemetry-semantic-conventions-ai" },
]
sdist = { url = "https://files.pythonhosted.org/packages/70/99/355c73ba6fb1679f32caa5579d9956dd3e0d40fa2205b41932694bd54696/opentelemetry_instrumentation_vertexai-0.33.12.tar.gz", hash = "sha256:a4ff534f24d4e1caecc621bea1ad19905bafc8ebf2fd1506e9eb1ae8f2a7831a", size = 4356, upload-time = "2024-11-13T20:28:25.514Z" }
sdist = { url = "https://files.pythonhosted.org/packages/f0/63/37f55389efdffcb167ec6e5cdb6b01cfeaaab01becac80d300aff547c2f5/opentelemetry_instrumentation_vertexai-0.34.0.tar.gz", hash = "sha256:4db963d487a4c26875c50dfeddfb589d998cc46b3cb89dc9a3f1083352b9e607", size = 4343, upload-time = "2024-12-12T21:02:32.037Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5c/e6/04cb7853674e4412d929d7c3172b073b402b3ee8f049e57dce042bdd5a2d/opentelemetry_instrumentation_vertexai-0.33.12-py3-none-any.whl", hash = "sha256:cf61cdc08bb6cb4dcbb0b59d1d0432cc1b0b7bee8fa25c69ae4886cf048204c4", size = 5789, upload-time = "2024-11-13T20:27:45.036Z" },
{ url = "https://files.pythonhosted.org/packages/4a/11/6dbf0defdfeeeaf4bb2037732bedbe020e048157642519cd022b33af84e6/opentelemetry_instrumentation_vertexai-0.34.0-py3-none-any.whl", hash = "sha256:d9206a65a416159597676ac60d1331abdc3844e98982126c155e1cacd939d395", size = 5773, upload-time = "2024-12-12T21:01:55.753Z" },
]
[[package]]
name = "opentelemetry-instrumentation-watsonx"
version = "0.33.12"
version = "0.34.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
@ -6703,14 +6704,14 @@ dependencies = [
{ name = "opentelemetry-semantic-conventions" },
{ name = "opentelemetry-semantic-conventions-ai" },
]
sdist = { url = "https://files.pythonhosted.org/packages/5b/f4/13359f1ef849d87010e494f503ce0d90c0b37f25b3cdf1ce58f0eaf0aa0b/opentelemetry_instrumentation_watsonx-0.33.12.tar.gz", hash = "sha256:98d537e3e9a919eab87f1f5f487679dd642d0742032635001b974c2154cedc0b", size = 6552, upload-time = "2024-11-13T20:28:26.346Z" }
sdist = { url = "https://files.pythonhosted.org/packages/2e/ed/78fafee5b64f728c5d8958f0fa558148674fb878b5585873e7d76708fa18/opentelemetry_instrumentation_watsonx-0.34.0.tar.gz", hash = "sha256:149a2ec1c6aa476c6258d7f00fc7951220ea8cc23be9a7a1273009377b9df0a4", size = 6552, upload-time = "2024-12-12T21:02:32.962Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a3/f6/74c9e14dc3324a9e5fb9a31c1ae29f92d25afe7930d1430dc55923867547/opentelemetry_instrumentation_watsonx-0.33.12-py3-none-any.whl", hash = "sha256:76bde9b15ca9be9fa6124b7e09606203103b77e7fa05227b8c9145fd2a782102", size = 7457, upload-time = "2024-11-13T20:27:46.861Z" },
{ url = "https://files.pythonhosted.org/packages/9c/ef/4b2189eda9ed49f4ea69e6b102351944d7e17ab90bfa6ff451ee20c1c97d/opentelemetry_instrumentation_watsonx-0.34.0-py3-none-any.whl", hash = "sha256:85d352880c8abccba92c728cbea7cab455a4acb454d43ed0037b6afecdb3a90c", size = 7442, upload-time = "2024-12-12T21:01:58.071Z" },
]
[[package]]
name = "opentelemetry-instrumentation-weaviate"
version = "0.33.12"
version = "0.34.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
@ -6718,48 +6719,48 @@ dependencies = [
{ name = "opentelemetry-semantic-conventions" },
{ name = "opentelemetry-semantic-conventions-ai" },
]
sdist = { url = "https://files.pythonhosted.org/packages/1d/50/38b46f295c4f28301d6aea15aeddcbc9550bb51a005da95045310549191f/opentelemetry_instrumentation_weaviate-0.33.12.tar.gz", hash = "sha256:1d14949e2123e5a2bd0eb149d8281713b33623d3f09f7aa587d4fca130d11b70", size = 4635, upload-time = "2024-11-13T20:28:27.189Z" }
sdist = { url = "https://files.pythonhosted.org/packages/68/47/9f0fc2310ef155edd22ae8ee3444e76d91a100a1579b40d034d85d2b0806/opentelemetry_instrumentation_weaviate-0.34.0.tar.gz", hash = "sha256:b69294e0b6b2fc5b90cd389c1a2bc75d18ed09f075ab589a61a0bcbe049ef9db", size = 4654, upload-time = "2024-12-12T21:02:34.344Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/28/e7/16d9a936d716af84546045fa62e593079069e14c667d049602b6e19d6e31/opentelemetry_instrumentation_weaviate-0.33.12-py3-none-any.whl", hash = "sha256:afa500e59bd7059495c6190decb1dd57dc620e17181c9543bd91e26afce74dcd", size = 6428, upload-time = "2024-11-13T20:27:48.71Z" },
{ url = "https://files.pythonhosted.org/packages/6a/9f/f55c020a3619d31dd39d32e376d8d5f8f6322f82b1c27acd5666503d6643/opentelemetry_instrumentation_weaviate-0.34.0-py3-none-any.whl", hash = "sha256:79eaa9be4393702d7b3cc938f3d01d82371d4a236326b01819002bac3f118194", size = 6410, upload-time = "2024-12-12T21:02:00.604Z" },
]
[[package]]
name = "opentelemetry-proto"
version = "1.28.0"
version = "1.33.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "protobuf" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c9/63/ac4cef4d30ea0ca1d2153ad2fc62d91d1cf3b89b0e4e5cbd61a8c567885f/opentelemetry_proto-1.28.0.tar.gz", hash = "sha256:4a45728dfefa33f7908b828b9b7c9f2c6de42a05d5ec7b285662ddae71c4c870", size = 34331, upload-time = "2024-11-05T19:14:59.503Z" }
sdist = { url = "https://files.pythonhosted.org/packages/f6/dc/791f3d60a1ad8235930de23eea735ae1084be1c6f96fdadf38710662a7e5/opentelemetry_proto-1.33.1.tar.gz", hash = "sha256:9627b0a5c90753bf3920c398908307063e4458b287bb890e5c1d6fa11ad50b68", size = 34363, upload-time = "2025-05-16T18:52:52.141Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/86/94/c0b43d16e1d96ee1e699373aa59f14a3aa2e7126af3f11d6adc5dcc531cd/opentelemetry_proto-1.28.0-py3-none-any.whl", hash = "sha256:d5ad31b997846543b8e15504657d9a8cf1ad3c71dcbbb6c4799b1ab29e38f7f9", size = 55832, upload-time = "2024-11-05T19:14:40.446Z" },
{ url = "https://files.pythonhosted.org/packages/c4/29/48609f4c875c2b6c80930073c82dd1cafd36b6782244c01394007b528960/opentelemetry_proto-1.33.1-py3-none-any.whl", hash = "sha256:243d285d9f29663fc7ea91a7171fcc1ccbbfff43b48df0774fd64a37d98eda70", size = 55854, upload-time = "2025-05-16T18:52:36.269Z" },
]
[[package]]
name = "opentelemetry-sdk"
version = "1.28.0"
version = "1.33.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
{ name = "opentelemetry-semantic-conventions" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/0c/5b/a509ccab93eacc6044591d5ec437d8266e76f893d0389bbf7e5592c7da32/opentelemetry_sdk-1.28.0.tar.gz", hash = "sha256:41d5420b2e3fb7716ff4981b510d551eff1fc60eb5a95cf7335b31166812a893", size = 156155, upload-time = "2024-11-05T19:15:00.451Z" }
sdist = { url = "https://files.pythonhosted.org/packages/67/12/909b98a7d9b110cce4b28d49b2e311797cffdce180371f35eba13a72dd00/opentelemetry_sdk-1.33.1.tar.gz", hash = "sha256:85b9fcf7c3d23506fbc9692fd210b8b025a1920535feec50bd54ce203d57a531", size = 161885, upload-time = "2025-05-16T18:52:52.832Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c3/fe/c8decbebb5660529f1d6ba65e50a45b1294022dfcba2968fc9c8697c42b2/opentelemetry_sdk-1.28.0-py3-none-any.whl", hash = "sha256:4b37da81d7fad67f6683c4420288c97f4ed0d988845d5886435f428ec4b8429a", size = 118692, upload-time = "2024-11-05T19:14:41.669Z" },
{ url = "https://files.pythonhosted.org/packages/df/8e/ae2d0742041e0bd7fe0d2dcc5e7cce51dcf7d3961a26072d5b43cc8fa2a7/opentelemetry_sdk-1.33.1-py3-none-any.whl", hash = "sha256:19ea73d9a01be29cacaa5d6c8ce0adc0b7f7b4d58cc52f923e4413609f670112", size = 118950, upload-time = "2025-05-16T18:52:37.297Z" },
]
[[package]]
name = "opentelemetry-semantic-conventions"
version = "0.49b0"
version = "0.54b1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "deprecated" },
{ name = "opentelemetry-api" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ee/c8/433b0e54143f8c9369f5c4a7a83e73eec7eb2ee7d0b7e81a9243e78c8e80/opentelemetry_semantic_conventions-0.49b0.tar.gz", hash = "sha256:dbc7b28339e5390b6b28e022835f9bac4e134a80ebf640848306d3c5192557e8", size = 95227, upload-time = "2024-11-05T19:15:01.443Z" }
sdist = { url = "https://files.pythonhosted.org/packages/5b/2c/d7990fc1ffc82889d466e7cd680788ace44a26789809924813b164344393/opentelemetry_semantic_conventions-0.54b1.tar.gz", hash = "sha256:d1cecedae15d19bdaafca1e56b29a66aa286f50b5d08f036a145c7f3e9ef9cee", size = 118642, upload-time = "2025-05-16T18:52:53.962Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/25/05/20104df4ef07d3bf5c3fd6bcc796ef70ab4ea4309378a9ba57bc4b4d01fa/opentelemetry_semantic_conventions-0.49b0-py3-none-any.whl", hash = "sha256:0458117f6ead0b12e3221813e3e511d85698c31901cac84682052adb9c17c7cd", size = 159214, upload-time = "2024-11-05T19:14:43.047Z" },
{ url = "https://files.pythonhosted.org/packages/0a/80/08b1698c52ff76d96ba440bf15edc2f4bc0a279868778928e947c1004bdd/opentelemetry_semantic_conventions-0.54b1-py3-none-any.whl", hash = "sha256:29dab644a7e435b58d3a3918b58c333c92686236b30f7891d5e51f02933ca60d", size = 194938, upload-time = "2025-05-16T18:52:38.796Z" },
]
[[package]]
@ -6773,11 +6774,11 @@ wheels = [
[[package]]
name = "opentelemetry-util-http"
version = "0.49b0"
version = "0.54b1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a3/99/377ef446928808211b127b9ab31c348bc465c8da4514ebeec6e4a3de3d21/opentelemetry_util_http-0.49b0.tar.gz", hash = "sha256:02928496afcffd58a7c15baf99d2cedae9b8325a8ac52b0d0877b2e8f936dd1b", size = 7863, upload-time = "2024-11-05T19:22:26.973Z" }
sdist = { url = "https://files.pythonhosted.org/packages/a8/9f/1d8a1d1f34b9f62f2b940b388bf07b8167a8067e70870055bd05db354e5c/opentelemetry_util_http-0.54b1.tar.gz", hash = "sha256:f0b66868c19fbaf9c9d4e11f4a7599fa15d5ea50b884967a26ccd9d72c7c9d15", size = 8044, upload-time = "2025-05-16T19:04:10.79Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/66/0e/ab0a89b315d0bacdd355a345bb69b20c50fc1f0804b52b56fe1c35a60e68/opentelemetry_util_http-0.49b0-py3-none-any.whl", hash = "sha256:8661bbd6aea1839badc44de067ec9c15c05eab05f729f496c856c50a1203caf1", size = 6945, upload-time = "2024-11-05T19:21:37.81Z" },
{ url = "https://files.pythonhosted.org/packages/a4/ef/c5aa08abca6894792beed4c0405e85205b35b8e73d653571c9ff13a8e34e/opentelemetry_util_http-0.54b1-py3-none-any.whl", hash = "sha256:b1c91883f980344a1c3c486cffd47ae5c9c1dd7323f9cbe9fdb7cadb401c87c9", size = 7301, upload-time = "2025-05-16T19:03:18.18Z" },
]
[[package]]
@ -9860,7 +9861,7 @@ wheels = [
[[package]]
name = "traceloop-sdk"
version = "0.33.12"
version = "0.34.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aiohttp" },
@ -9906,9 +9907,9 @@ dependencies = [
{ name = "pydantic" },
{ name = "tenacity" },
]
sdist = { url = "https://files.pythonhosted.org/packages/7e/0d/d7d413e9fe907a8abc33e6f93044484d158722b5ca0bfe22e1ef9ad4e729/traceloop_sdk-0.33.12.tar.gz", hash = "sha256:999ae50b1e5773b2802a8b3e8585c3826b7867bba032a88b6f30ec2727225dda", size = 19768, upload-time = "2024-11-13T20:29:26.67Z" }
sdist = { url = "https://files.pythonhosted.org/packages/0a/b1/fd7360d97c651098da505e95600e067a7eedb1b78635b2f1d23545ee4a46/traceloop_sdk-0.34.0.tar.gz", hash = "sha256:4aa26003dfa2e417f73728bd847284a12d6da43a946dd588603a0966e753b3e6", size = 19808, upload-time = "2024-12-12T21:03:41.647Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ce/13/53c2ab6ac27804769314554a062e0651a44db2360be47e21cf0a29d202ee/traceloop_sdk-0.33.12-py3-none-any.whl", hash = "sha256:d47a474afbf4a68ff38a702dbaca7b17d2d4f0b0e14dc2f1560b6bdd3859ac75", size = 25932, upload-time = "2024-11-13T20:29:25.174Z" },
{ url = "https://files.pythonhosted.org/packages/c5/e8/c89cc77c272312930cc263c45fbd2a648536e93358611bf03dba6f176a0b/traceloop_sdk-0.34.0-py3-none-any.whl", hash = "sha256:1cc3e5be9dd2765212feaa5655e1f43ddc66739585d78d9c81134428a2a7d927", size = 25944, upload-time = "2024-12-12T21:03:39.565Z" },
]
[[package]]