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

This commit is contained in:
Yucheng Zhu 2026-09-01 00:39:12 -07:00
parent 8ae072b501
commit b08598534c
16 changed files with 1753 additions and 597 deletions

View file

@ -32,10 +32,10 @@ RUN uv venv --python python && \
"anthropic[vertex]==0.84.0" \
"grpcio==1.78.0" \
"prometheus-client==0.20.0" \
"langfuse==2.59.7" \
"opentelemetry-api==1.28.0" \
"opentelemetry-sdk==1.28.0" \
"opentelemetry-exporter-otlp==1.28.0" \
"langfuse>=4.7,<5.0" \
"opentelemetry-api==1.33.1" \
"opentelemetry-sdk==1.33.1" \
"opentelemetry-exporter-otlp==1.33.1" \
"ddtrace==4.11.0" \
"sentry-sdk==2.21.0" \
"mangum==0.17.0" \

View file

@ -84,7 +84,8 @@ async def _add_langfuse_trace_id_to_alert(
#########################################################
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}"
base_url: Final = getattr(langfuse_object, "langfuse_host", None)
if base_url is not None:
return f"{base_url}/trace/{trace_id}"
return None

View file

@ -1,11 +1,11 @@
#### What this does ####
# On success, logs events to Langfuse
import inspect
import os
import traceback
from collections.abc import Callable, Iterable, Mapping
from datetime import datetime
from functools import lru_cache
from importlib.metadata import version
from types import MappingProxyType
from typing import TYPE_CHECKING, Any, Final, Literal, Protocol, cast
@ -44,12 +44,13 @@ from litellm.types.utils import (
)
if TYPE_CHECKING:
from langfuse.client import Langfuse, StatefulTraceClient
from langfuse import Langfuse
from opentelemetry.context import Context
from litellm.litellm_core_utils.litellm_logging import DynamicLoggingCache
else:
Context = Any
DynamicLoggingCache = Any
StatefulTraceClient = Any
Langfuse = Any
@ -119,6 +120,85 @@ def _as_steering_key_sequence(value: object) -> tuple[str, ...]:
return ()
MINIMUM_LANGFUSE_VERSION: Final = "4.7"
UNSUPPORTED_LANGFUSE_VERSION: Final = "5"
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
`propagate_attributes` raises inside the per-request handler and the broad
except there turns it into silent total data loss.
"""
if Version(MINIMUM_LANGFUSE_VERSION) <= Version(installed_version) < Version(UNSUPPORTED_LANGFUSE_VERSION):
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"
)
_PROPAGATED_TRACE_KEYS: Final = MappingProxyType(
{"name": "trace_name", "user_id": "user_id", "session_id": "session_id", "version": "version", "tags": "tags"}
)
_GENERATION_ONLY_KEYS: Final = frozenset(
{"id", "start_time", "end_time", "parent_observation_id", "usage", "name", "version"}
)
def _trace_attributes_for_propagation(trace_params: Mapping[str, object]) -> Mapping[str, object]:
"""Trace-level fields in v4 are propagated onto the observations, not set on a trace object."""
return MappingProxyType(
{
propagated: trace_params[key]
for key, propagated in _PROPAGATED_TRACE_KEYS.items()
if trace_params.get(key) is not 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 _generation_attributes(
generation_params: Mapping[str, object], *, propagated: Mapping[str, object]
) -> Mapping[str, object]:
"""Drop what the v4 wrapper cannot take: ids it generates, and timings set on the span itself.
``usage`` is the v2 shape that v4 replaced with ``usage_details``, which the
caller already builds alongside it.
v4 has one ``version`` for a trace and its observations, so the trace's
propagated value covers the generation; a continued trace propagates none
and the generation keeps its own, as it did in v2.
"""
keep_version: Final = "version" not in propagated and generation_params.get("version") is not None
return MappingProxyType(
{
key: value
for key, value in generation_params.items()
if key not in _GENERATION_ONLY_KEYS or (key == "version" and keep_version)
}
)
def resolve_langfuse_credentials(
langfuse_public_key=None,
langfuse_secret=None,
@ -133,11 +213,18 @@ 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")
resolved_host: Final = (
langfuse_host or os.getenv("LANGFUSE_HOST") or os.getenv("LANGFUSE_BASE_URL") or "https://cloud.langfuse.com"
)
return public_key, secret_key, resolved_host
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)
def _warn_invalid_deployment_environment(raw_value: str, error: str) -> None:
verbose_logger.warning(
@ -160,12 +247,14 @@ class LangFuseLogger:
allow_env_credentials: bool = True,
):
try:
import langfuse
from langfuse import Langfuse
except Exception 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"
)
self.langfuse_sdk_version: str = installed_langfuse_version()
raise_if_unsupported_langfuse_version(self.langfuse_sdk_version)
self.public_key, self.secret_key, self.langfuse_host = resolve_langfuse_credentials(
langfuse_public_key=langfuse_public_key,
langfuse_secret=langfuse_secret,
@ -182,7 +271,7 @@ class LangFuseLogger:
else:
self.langfuse_environment = self.resolve_deployment_environment()
self.langfuse_release = os.getenv("LANGFUSE_RELEASE")
self.langfuse_debug = os.getenv("LANGFUSE_DEBUG")
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():
@ -196,18 +285,13 @@ class LangFuseLogger:
parameters: Final = {
"public_key": self.public_key,
"secret_key": self.secret_key,
"host": self.langfuse_host,
"base_url": 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"
parameters["environment"] = self.langfuse_environment
self.Langfuse: Langfuse = self.safe_init_langfuse_client(parameters)
# set the current langfuse project id in the environ
@ -217,30 +301,17 @@ class LangFuseLogger:
verbose_logger.debug("Langfuse Mock: Using mock project ID")
else:
try:
project_id = self.Langfuse.client.projects.get().data[0].id
project_id: Final = self.Langfuse.api.projects.get().data[0].id
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
self.upstream_langfuse_debug = os.getenv("UPSTREAM_LANGFUSE_DEBUG")
def safe_init_langfuse_client(self, parameters: dict) -> Langfuse:
"""
@ -256,7 +327,27 @@ class LangFuseLogger:
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 (
DiscardingSpanExporter,
build_isolated_tracer_provider,
evict_stale_langfuse_resources,
register_langfuse_client,
)
evict_stale_langfuse_resources(
public_key=parameters.get("public_key"),
secret_key=parameters.get("secret_key"),
base_url=parameters.get("base_url"),
)
langfuse_client: Final = Langfuse(
**parameters,
tracer_provider=build_isolated_tracer_provider(
environment=parameters.get("environment"),
release=parameters.get("release"),
),
span_exporter=DiscardingSpanExporter() if self.is_mock_mode else None,
)
register_langfuse_client(langfuse_client)
litellm.initialized_langfuse_clients += 1
verbose_logger.debug("Created langfuse client number %s", litellm.initialized_langfuse_clients)
return langfuse_client
@ -357,33 +448,20 @@ class LangFuseLogger:
verbose_logger.debug("OUTPUT IN LANGFUSE: %s; original: %s", output, response_obj)
trace_id = None
generation_id = None
if self._is_langfuse_v2():
trace_id, generation_id = self._log_langfuse_v2(
user_id=user_id,
metadata=metadata,
litellm_params=litellm_params,
output=output,
start_time=start_time,
end_time=end_time,
kwargs=kwargs,
optional_params=optional_params,
input=input,
response_obj=response_obj,
level=level,
litellm_call_id=litellm_call_id,
)
elif response_obj is not None:
self._log_langfuse_v1(
user_id=user_id,
metadata=metadata,
output=output,
start_time=start_time,
end_time=end_time,
kwargs=kwargs,
optional_params=optional_params,
input=input,
response_obj=response_obj,
)
trace_id, generation_id = self._log_langfuse_v2(
user_id=user_id,
metadata=metadata,
litellm_params=litellm_params,
output=output,
start_time=start_time,
end_time=end_time,
kwargs=kwargs,
optional_params=optional_params,
input=input,
response_obj=response_obj,
level=level,
litellm_call_id=litellm_call_id,
)
verbose_logger.debug("Langfuse Layer Logging - final response object: %s", response_obj)
verbose_logger.info("Langfuse Layer Logging - logging success")
@ -479,58 +557,6 @@ class LangFuseLogger:
This approach does not impact latency and runs in the background
"""
def _is_langfuse_v2(self):
import langfuse
return Version(langfuse.version.__version__) >= Version("2.0.0")
def _log_langfuse_v1(
self,
user_id,
metadata,
output,
start_time,
end_time,
kwargs,
optional_params,
input,
response_obj,
):
from langfuse.model import CreateGeneration, CreateTrace
verbose_logger.warning(
"Please upgrade langfuse to v2.0.0 or higher: https://github.com/langfuse/langfuse-python/releases/tag/v2.0.1"
)
trace: Final = self.Langfuse.trace(
CreateTrace(
name=metadata.get("generation_name", "litellm-completion"),
input=input,
output=output,
userId=user_id,
)
)
custom_llm_provider: Final = cast(str | None, kwargs.get("custom_llm_provider"))
model_name: Final = reconstruct_model_name(kwargs.get("model", ""), custom_llm_provider, metadata)
trace.generation(
CreateGeneration(
name=metadata.get("generation_name", "litellm-completion"),
startTime=start_time,
endTime=end_time,
model=model_name,
modelParameters=optional_params,
prompt=input,
completion=output,
usage={
"prompt_tokens": response_obj.usage.prompt_tokens,
"completion_tokens": response_obj.usage.completion_tokens,
},
metadata=metadata,
)
)
def _log_langfuse_v2(
self,
user_id: str | None,
@ -737,17 +763,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
@ -769,7 +784,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
@ -813,8 +828,12 @@ class LangFuseLogger:
"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
**(trace_params.get("metadata") or {}),
**log_requester_metadata(redact_user_api_key_info(metadata=allowlisted_metadata)), # pyright: ignore[reportArgumentType] # TypedDict in, plain metadata dict out
**enrichments,
},
"level": level,
@ -835,23 +854,48 @@ class LangFuseLogger:
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)
generation_params["completion_start_time"] = kwargs.get("completion_start_time", None)
generation_client: Final = trace.generation(**generation_params)
# langfuse ships in the proxy-runtime extra, so this module must import cleanly without it
from litellm.integrations.langfuse.langfuse_sdk import (
open_trace_context,
propagate_attributes,
resolve_observation_id,
resolve_trace_id,
start_generation,
to_unix_nanos,
)
# 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 != trace_id:
verbose_logger.warning(
"Langfuse trace_id mismatch: set %s, but langfuse returned %s. Using our intended trace_id for consistency.",
trace_id,
generation_client.trace_id,
)
return trace_id, generation_id
resolved_trace_id: Final = resolve_trace_id(trace_id) # pyright: ignore[reportArgumentType] # metadata value, str or None at runtime
propagated_trace_attributes: Final = _trace_attributes_for_propagation(trace_params)
with propagate_attributes(**propagated_trace_attributes): # pyright: ignore[reportArgumentType] # kwargs-ok: keys fixed by _PROPAGATED_TRACE_KEYS, values are the SDK's own trace fields
trace_context, claim_trace_root = open_trace_context(
client=self.Langfuse,
trace_id=resolved_trace_id,
parent_observation_id=resolve_observation_id(parent_observation_id), # pyright: ignore[reportArgumentType] # metadata value, str or None at runtime
)
log_provider_specific_information_as_span(
client=self.Langfuse, context=trace_context, enrichments=enrichments
)
self._log_guardrail_information_as_span(
client=self.Langfuse,
context=trace_context,
standard_logging_object=standard_logging_object,
)
start_generation(
client=self.Langfuse,
context=trace_context,
name=generation_params["name"], # pyright: ignore[reportArgumentType] # always the str set a few lines up
start_time=start_time,
claim_trace_root=claim_trace_root,
release=trace_params.get("release"),
public=_trace_public_flag(trace_params.get("public")),
attributes=_generation_attributes(generation_params, propagated=propagated_trace_attributes),
).end(end_time=to_unix_nanos(end_time))
# log_event_on_langfuse tuple-unpacks this and re-wraps it in the dict callers cache
return resolved_trace_id, generation_id
except Exception:
verbose_logger.error("Langfuse Layer Error - %s", traceback.format_exc())
return None, None
@ -920,7 +964,7 @@ 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
@ -933,14 +977,6 @@ class LangFuseLogger:
"""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:
"""
@ -1005,7 +1041,8 @@ class LangFuseLogger:
def _log_guardrail_information_as_span(
self,
trace: StatefulTraceClient,
client: "Langfuse",
context: "Context",
standard_logging_object: StandardLoggingPayload | None,
):
"""
@ -1027,6 +1064,8 @@ class LangFuseLogger:
)
return
from litellm.integrations.langfuse.langfuse_sdk import start_child_span, to_unix_nanos
for guardrail_entry in guardrail_information:
if not isinstance(guardrail_entry, dict):
verbose_logger.debug(
@ -1035,21 +1074,24 @@ class LangFuseLogger:
)
continue
span = trace.span(
span = start_child_span(
client=client,
context=context,
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={ # mutable-ok: langfuse serializes this payload, a proxy is not json-encodable
"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),
},
},
)
verbose_logger.debug("Logged guardrail information as span: %s", span)
span.end()
span.end(end_time=to_unix_nanos(guardrail_entry.get("end_time", None)))
def _add_prompt_to_generation_params(
@ -1091,7 +1133,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):
@ -1106,7 +1148,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:
@ -1126,21 +1168,23 @@ def _add_prompt_to_generation_params(
def log_provider_specific_information_as_span(
trace,
clean_metadata: Mapping[str, Any],
*,
client: "Langfuse",
context: "Context",
enrichments: Mapping[str, Any],
):
"""
Logs provider-specific information as spans.
Parameters:
trace: The tracing object used to log spans.
clean_metadata: A dictionary containing metadata to be logged.
enrichments: The litellm-computed fields on the emitted payload.
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
@ -1151,22 +1195,30 @@ 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(client=client, context=context, name=key, value=value)
else:
trace.span(
name="vertex_ai_grounding_metadata",
input=elem,
)
_end_grounding_span(client=client, context=context, name="vertex_ai_grounding_metadata", value=elem)
else:
trace.span(
_end_grounding_span(
client=client,
context=context,
name="vertex_ai_grounding_metadata",
input=vertex_ai_grounding_metadata,
value=vertex_ai_grounding_metadata,
)
def _end_grounding_span(*, client: "Langfuse", context: "Context", name: str, value: object) -> None:
from litellm.integrations.langfuse.langfuse_sdk import start_child_span
start_child_span(
client=client,
context=context,
name=name,
start_time=None,
attributes={"input": value}, # mutable-ok: langfuse serializes this payload
).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,13 +2,10 @@
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
@ -20,12 +17,19 @@ from ...litellm_core_utils.specialty_caches.dynamic_logging_cache import (
DynamicLoggingCache,
)
from ..prompt_management_base import PromptManagementBase
from .langfuse import LangFuseLogger, resolve_langfuse_credentials
from .langfuse import (
LangFuseLogger,
installed_langfuse_version,
parse_langfuse_debug,
raise_if_unsupported_langfuse_version,
resolve_langfuse_credentials,
)
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
@ -64,7 +68,6 @@ def langfuse_client_init(
Exception: If langfuse package is not installed
"""
try:
import langfuse
from langfuse import Langfuse
except Exception as e:
raise Exception(
@ -84,36 +87,51 @@ def langfuse_client_init(
langfuse_host = "http://" + langfuse_host
langfuse_release: Final = os.getenv("LANGFUSE_RELEASE")
langfuse_debug: Final = os.getenv("LANGFUSE_DEBUG")
langfuse_debug: Final = parse_langfuse_debug(os.getenv("LANGFUSE_DEBUG"))
parameters: Final = {
"public_key": public_key,
"secret_key": secret_key,
"host": langfuse_host,
"base_url": langfuse_host,
"release": langfuse_release,
"debug": langfuse_debug,
"flush_interval": LangFuseLogger._get_langfuse_flush_interval(flush_interval), # flush interval in seconds
"flush_interval": LangFuseLogger._get_langfuse_flush_interval(flush_interval), # pyright: ignore[reportPrivateUsage] # shared env-fallback helper, not part of the logger's API
}
if Version(langfuse.version.__version__) >= Version("2.6.0"):
parameters["sdk_integration"] = "litellm"
raise_if_unsupported_langfuse_version(installed_langfuse_version())
if Version(langfuse.version.__version__) >= Version("2.7.3"):
import httpx
import httpx
import litellm
import litellm
from ...llms.custom_httpx.http_handler import get_ssl_configuration
from ...llms.custom_httpx.http_handler import get_ssl_configuration
parameters["httpx_client"] = httpx.Client(
is_mock_mode: Final = should_use_langfuse_mock()
parameters["httpx_client"] = (
create_mock_langfuse_client()
if is_mock_mode
else 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()
parameters["environment"] = LangFuseLogger.resolve_deployment_environment()
client: Final = Langfuse(**parameters)
from .langfuse_sdk import (
DiscardingSpanExporter,
build_isolated_tracer_provider,
evict_stale_langfuse_resources,
register_langfuse_client,
)
evict_stale_langfuse_resources(public_key=public_key, secret_key=secret_key, base_url=langfuse_host)
client: Final = Langfuse(
**parameters, # pyright: ignore[reportArgumentType] # kwargs-ok: dict mirrors the typed ctor, values resolved above
tracer_provider=build_isolated_tracer_provider(environment=parameters["environment"], release=langfuse_release),
span_exporter=DiscardingSpanExporter() if is_mock_mode else None,
)
register_langfuse_client(client)
return client
@ -126,9 +144,8 @@ class LangfusePromptManagement(LangFuseLogger, PromptManagementBase, CustomLogge
langfuse_host=None,
flush_interval=1,
):
import langfuse
self.langfuse_sdk_version = langfuse.version.__version__
self.langfuse_sdk_version = installed_langfuse_version()
self.Langfuse = langfuse_client_init(
langfuse_public_key=langfuse_public_key,
langfuse_secret=langfuse_secret,

View file

@ -0,0 +1,279 @@
from __future__ import annotations
import re
import threading
from collections.abc import Mapping
from datetime import datetime
from hashlib import sha256
from types import MappingProxyType
from typing import Final
from weakref import WeakKeyDictionary, WeakSet
import opentelemetry.trace as otel_trace
from langfuse import Langfuse, LangfuseGeneration, LangfuseSpan, propagate_attributes
from langfuse._client.resource_manager import LangfuseResourceManager
from opentelemetry.context import Context
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
__all__ = (
"AS_ROOT_ATTRIBUTE",
"PUBLIC_ATTRIBUTE",
"RELEASE_ATTRIBUTE",
"DiscardingSpanExporter",
"build_isolated_tracer_provider",
"evict_stale_langfuse_resources",
"open_trace_context",
"propagate_attributes",
"register_langfuse_client",
"resolve_observation_id",
"resolve_trace_id",
"shutdown_langfuse_client",
"start_child_span",
"start_generation",
"to_unix_nanos",
)
AS_ROOT_ATTRIBUTE: Final = "langfuse.internal.as_root"
PUBLIC_ATTRIBUTE: Final = "langfuse.trace.public"
RELEASE_ATTRIBUTE: Final = "langfuse.release"
_TRACE_ID_PATTERN: Final = re.compile(r"^[0-9a-f]{32}$")
_OBSERVATION_ID_PATTERN: Final = re.compile(r"^[0-9a-f]{16}$")
def to_unix_nanos(value: datetime | float | None) -> int | None:
"""Langfuse v4 takes OTel timestamps, which are integer nanoseconds since the epoch.
Guardrail entries carry unix seconds as floats rather than datetimes, so both
shapes have to convert; the v2 SDK accepted either through a pydantic model.
"""
if value is None:
return None
seconds: Final = value.timestamp() if isinstance(value, datetime) else float(value)
return int(seconds * 1_000_000_000)
def resolve_trace_id(trace_id: str | None) -> str:
"""Map litellm's trace id onto the 32 lowercase hex characters v4 requires.
Anything else raises inside the SDK rather than being ignored, so a plain
uuid is dash-stripped and any other identifier is hashed deterministically,
which keeps repeat calls with the same id on the same trace.
"""
normalized: Final = trace_id.lower().replace("-", "") if trace_id else ""
if _TRACE_ID_PATTERN.match(normalized):
return normalized
return Langfuse.create_trace_id(seed=trace_id) if trace_id else Langfuse.create_trace_id()
def resolve_observation_id(observation_id: str | None) -> str | None:
"""Same for a caller-supplied parent, which v4 requires to be 16 hex characters."""
normalized: Final = observation_id.lower().replace("-", "") if observation_id else ""
if not normalized:
return None
if _OBSERVATION_ID_PATTERN.match(normalized):
return normalized
return sha256(normalized.encode("utf-8")).digest()[:8].hex()
def open_trace_context(
*,
client: Langfuse,
trace_id: str,
parent_observation_id: str | None,
) -> tuple[Context, bool]:
"""Build the OTel context that places new observations inside ``trace_id``.
Returns the context plus whether the caller must claim trace root. Langfuse
fabricates a random parent span id when no real parent is supplied, so the
observation is a child of something that will never be exported; the public
SDK path compensates by marking the span as root and this path must do the
same.
"""
remote_parent: Final = client._create_remote_parent_span( # pyright: ignore[reportPrivateUsage] # no public equivalent in v4
trace_id=trace_id, parent_span_id=parent_observation_id
)
return otel_trace.set_span_in_context(remote_parent), parent_observation_id is None
def start_generation(
*,
client: Langfuse,
context: Context,
name: str,
start_time: datetime | float | None,
claim_trace_root: bool,
release: str | None = None,
public: bool | None = None,
attributes: Mapping[str, object],
) -> LangfuseGeneration:
"""Create a generation whose start time is when the model call began.
No public v4 API accepts a historical start time, so this drives the SDK's
own OTel tracer, which does. Langfuse documents this route for backdated
ingestion.
``public`` is the v2 ``trace(public=...)`` flag; v4 reads it off the root
observation's ``langfuse.trace.public`` attribute instead.
"""
otel_span: Final = client._otel_tracer.start_span( # pyright: ignore[reportPrivateUsage] # only route to a historical start time
name=name, context=context, start_time=to_unix_nanos(start_time)
)
if claim_trace_root:
otel_span.set_attribute(AS_ROOT_ATTRIBUTE, True)
if public is not None:
otel_span.set_attribute(PUBLIC_ATTRIBUTE, public)
generation: Final = LangfuseGeneration(otel_span=otel_span, langfuse_client=client, **attributes) # pyright: ignore[reportArgumentType] # kwargs-ok: callback-built params, v2 accepted the same shapes
if release is not None:
# after the wrapper, which stamps the client-wide release and would otherwise
# overwrite the release this request asked for
otel_span.set_attribute(RELEASE_ATTRIBUTE, release)
return generation
def start_child_span(
*,
client: Langfuse,
context: Context,
name: str,
start_time: datetime | float | None,
attributes: Mapping[str, object],
) -> LangfuseSpan:
"""Create a sibling observation inside the same trace, keeping its own window."""
otel_span: Final = client._otel_tracer.start_span( # pyright: ignore[reportPrivateUsage] # only route to a historical start time
name=name, context=context, start_time=to_unix_nanos(start_time)
)
return LangfuseSpan(otel_span=otel_span, langfuse_client=client, **attributes) # pyright: ignore[reportArgumentType] # kwargs-ok: callback-built params, v2 accepted the same shapes
_ENVIRONMENT_ATTRIBUTE: Final = "langfuse.environment"
_RELEASE_ATTRIBUTE: Final = "langfuse.release"
def build_isolated_tracer_provider(*, environment: str | None, release: str | None) -> TracerProvider:
"""Give the langfuse client a provider of its own instead of the process-wide one.
v4 is built on OpenTelemetry and otherwise either claims the global tracer
provider, which silently disables litellm's own exporters, or attaches its
processor to litellm's, which sends litellm spans to every langfuse project
and langfuse spans to every other litellm destination.
The resource is rebuilt here because langfuse only applies ``environment``
and ``release`` when it constructs the provider itself.
"""
attributes: Final = MappingProxyType(
{
key: value
for key, value in ((_ENVIRONMENT_ATTRIBUTE, environment), (_RELEASE_ATTRIBUTE, release))
if value is not None
}
)
return TracerProvider(resource=Resource.create(dict(attributes)))
class DiscardingSpanExporter(SpanExporter):
"""Accept and drop every span, for mock mode.
The mock intercepts the httpx client langfuse used to take, but v4 ships
observations through its own OTLP exporter, so without this the "no network
calls" contract silently sends real traces to the configured host.
"""
def export(self, spans: object) -> SpanExportResult:
return SpanExportResult.SUCCESS
def shutdown(self) -> None:
return None
def force_flush(self, timeout_millis: int = 30_000) -> bool:
return True
def evict_stale_langfuse_resources(*, public_key: str | None, secret_key: str | None, base_url: str | None) -> None:
"""Drop a cached client whose credentials no longer match the ones being requested.
langfuse keys its client registry on the public key alone, so a rotated
secret or a moved host silently keeps exporting with the original values.
Only the one stale entry is removed; the SDK's own reset would shut down
every other tenant in the process.
"""
if not public_key:
return
with LangfuseResourceManager._lock: # pyright: ignore[reportPrivateUsage] # registry has no public accessor
cached: Final = LangfuseResourceManager._instances.get(public_key) # pyright: ignore[reportPrivateUsage] # registry has no public accessor
if cached is None:
return
if getattr(cached, "secret_key", None) == secret_key and getattr(cached, "base_url", None) == base_url:
return
LangfuseResourceManager._instances.pop(public_key, None) # pyright: ignore[reportPrivateUsage] # registry has no public accessor
_LIVE_CLIENTS_LOCK: Final = threading.Lock()
# litellm clients still using each SDK resource bundle; the bundle is torn down with the last one.
# Both sides are weak so a throwaway client (a health probe, an alerting lookup) that is simply
# garbage-collected stops holding the bundle open rather than inflating a counter forever.
_live_clients: Final[WeakKeyDictionary[LangfuseResourceManager, WeakSet]] = WeakKeyDictionary()
def register_langfuse_client(client: Langfuse) -> None:
"""Track the client against the SDK resources it ended up with.
langfuse keys its resources on the public key alone, so a second client for
the same key (a per-key ``langfuse_environment`` override, a team whose
callback_vars repeat the global credentials) is handed the first client's
tracer provider and export thread rather than its own. Only the last live
client may shut those down; see ``shutdown_langfuse_client``.
"""
resources: Final = getattr(client, "_resources", None)
if resources is None:
return
with _LIVE_CLIENTS_LOCK:
holders = _live_clients.get(resources)
if holders is None:
holders = WeakSet()
_live_clients[resources] = holders
holders.add(client)
def _release_langfuse_resources(resources: LangfuseResourceManager, client: Langfuse) -> bool:
"""Drop the client's claim; True when no other live client still uses ``resources``."""
with _LIVE_CLIENTS_LOCK:
holders: Final = _live_clients.get(resources)
if holders is None:
return True
holders.discard(client)
if len(holders) > 0:
return False
_live_clients.pop(resources, None)
return True
def shutdown_langfuse_client(client: Langfuse) -> None:
"""Release everything the client owns, which the SDK's own shutdown does not.
``Langfuse.shutdown`` joins the score and media consumers but leaves the
tracer provider's export thread running and leaves the client in the
registry, so a later request for the same key gets a dead client back.
A client that shares its resources with another live client only flushes:
shutting the shared provider down here would silence the other client for
the rest of its life, as it did before the reference count existed.
"""
resources: Final = getattr(client, "_resources", None)
client.flush()
if resources is None:
client.shutdown()
return
if not _release_langfuse_resources(resources, client):
return
client.shutdown()
provider: Final = getattr(resources, "tracer_provider", None)
if provider is not None and not isinstance(provider, otel_trace.ProxyTracerProvider):
provider.shutdown()
public_key: Final = getattr(resources, "public_key", None)
if public_key is None:
return
with LangfuseResourceManager._lock: # pyright: ignore[reportPrivateUsage] # registry has no public accessor
if LangfuseResourceManager._instances.get(public_key) is resources: # pyright: ignore[reportPrivateUsage] # registry has no public accessor
LangfuseResourceManager._instances.pop(public_key, None) # pyright: ignore[reportPrivateUsage] # registry has no public accessor

View file

@ -43,9 +43,12 @@ class LangfuseInMemoryCache(InMemoryCache):
#########################################################
# Clean up Langfuse initialized clients
#########################################################
from litellm.integrations.langfuse.langfuse_sdk import (
shutdown_langfuse_client,
)
litellm.initialized_langfuse_clients -= 1
_created_langfuse_logger.Langfuse.flush()
_created_langfuse_logger.Langfuse.shutdown()
shutdown_langfuse_client(_created_langfuse_logger.Langfuse)
# Loggers with a periodic flush task (e.g. NewRelicMetricsLogger) expose
# stop() so eviction actually ends the task instead of leaking it.

View file

@ -355,7 +355,10 @@ async def health_services_endpoint(
from litellm.integrations.langfuse.langfuse import LangFuseLogger
langfuse_logger: Final = LangFuseLogger()
langfuse_logger.Langfuse.auth_check()
if langfuse_logger.Langfuse.auth_check() is False:
raise ValueError(
"langfuse auth_check failed - verify LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY are set correctly"
)
_ = litellm.completion(
model="openai/litellm-mock-response-model",
messages=[{"role": "user", "content": "Hey, how's it going?"}],

View file

@ -151,11 +151,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",
@ -196,11 +196,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",
@ -221,10 +221,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",
]
@ -244,7 +244,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

@ -914,6 +914,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"]
@ -946,24 +947,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

@ -51,7 +51,13 @@ 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_is_read_without_building_a_client(monkeypatch):
"""The UPSTREAM_LANGFUSE_* values are recorded, and nothing consumes them.
A client was built here and never referenced. On v4 that means a second
exporter, its own threads, and an entry in the SDK's per-key registry, so it
is no longer constructed.
"""
from litellm.integrations.langfuse.langfuse import LangFuseLogger
class FakeLangfuse:
@ -81,7 +87,10 @@ def test_upstream_langfuse_debug_env_is_passed(monkeypatch):
)
assert logger.upstream_langfuse_debug == "true"
assert FakeLangfuse.instances[-1].kwargs["debug"] is True
assert logger.upstream_langfuse_public_key == "upstream-public"
assert logger.upstream_langfuse_host == "https://upstream.example"
assert logger.upstream_langfuse_release == "release"
assert not hasattr(logger, "upstream_langfuse")
def test_langfuse_handler_accepts_secret_key_alias(monkeypatch):

View file

@ -306,35 +306,58 @@ 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
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, "_langfuse_logger_cache", {}, raising=False)
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.Langfuse.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,9 +1,13 @@
from datetime import datetime, timezone
from types import MappingProxyType
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,
@ -137,3 +141,66 @@ def test_langfuse_client_init_resolves_deployment_environment(monkeypatch, env_v
langfuse_client_init()
langfuse_client_init.cache_clear()
assert _RecordingLangfuseForEnv.last_environment == expected
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 client is the one the standard proxy path emits observations through;
v4 ships them over its own OTLP exporter, which the httpx mock cannot see.
"""
import threading
import time
from http.server import BaseHTTPRequestHandler, HTTPServer
from langfuse._client.resource_manager import LangfuseResourceManager
from litellm.integrations.langfuse.langfuse_sdk import (
open_trace_context,
start_generation,
to_unix_nanos,
)
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")
LangfuseResourceManager._instances.pop("pk-pm-mock-egress", None)
langfuse_client_init.cache_clear()
try:
client = langfuse_client_init()
context, claim_root = open_trace_context(client=client, trace_id="a" * 32, parent_observation_id=None)
now = datetime.now(timezone.utc)
start_generation(
client=client,
context=context,
name="pm-mock-gen",
start_time=now,
claim_trace_root=claim_root,
attributes={},
).end(end_time=to_unix_nanos(now))
client.flush()
time.sleep(1)
finally:
server.shutdown()
langfuse_client_init.cache_clear()
LangfuseResourceManager._instances.pop("pk-pm-mock-egress", None)
assert received == [], f"LANGFUSE_MOCK still sent spans to the configured host: {received}"

View file

@ -0,0 +1,475 @@
"""Covers the v4 observation plumbing: historical timestamps and id normalisation.
The timestamp assertions are the regression guard for the migration: v4 has no
public API for an observation start time, so a callback running after the model
call would otherwise record its own duration instead of the call's.
"""
import json
from datetime import datetime, timedelta, timezone
import opentelemetry.trace as otel_trace
import pytest
from langfuse import Langfuse
from langfuse._client.resource_manager import LangfuseResourceManager
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter
from litellm.integrations.langfuse.langfuse import (
MINIMUM_LANGFUSE_VERSION,
raise_if_unsupported_langfuse_version,
installed_langfuse_version,
)
from litellm.integrations.langfuse.langfuse_sdk import (
AS_ROOT_ATTRIBUTE,
PUBLIC_ATTRIBUTE,
RELEASE_ATTRIBUTE,
build_isolated_tracer_provider,
evict_stale_langfuse_resources,
open_trace_context,
register_langfuse_client,
resolve_observation_id,
resolve_trace_id,
shutdown_langfuse_client,
start_child_span,
start_generation,
to_unix_nanos,
)
CALL_START = datetime(2024, 3, 1, 12, 0, 0, tzinfo=timezone.utc)
FIRST_TOKEN = CALL_START + timedelta(seconds=5)
CALL_END = CALL_START + timedelta(seconds=20)
@pytest.fixture(name="client")
def _client():
LangfuseResourceManager._instances.pop("pk-obs-test", None)
exporter = InMemorySpanExporter()
provider = TracerProvider()
provider.add_span_processor(SimpleSpanProcessor(exporter))
client = Langfuse(
public_key="pk-obs-test",
secret_key="sk-obs-test",
host="http://127.0.0.1:1",
tracer_provider=provider,
span_exporter=exporter,
)
yield client, exporter
LangfuseResourceManager._instances.pop("pk-obs-test", None)
def _only_span(exporter, name):
return next(s for s in exporter.get_finished_spans() if s.name == name)
def test_generation_records_the_model_call_window_not_the_callback(client):
lf, exporter = client
context, claim_root = open_trace_context(client=lf, trace_id="a" * 32, parent_observation_id=None)
start_generation(
client=lf,
context=context,
name="gen",
start_time=CALL_START,
claim_trace_root=claim_root,
attributes={"completion_start_time": FIRST_TOKEN},
).end(end_time=to_unix_nanos(CALL_END))
lf.flush()
span = _only_span(exporter, "gen")
assert span.start_time == to_unix_nanos(CALL_START)
assert span.end_time == to_unix_nanos(CALL_END)
assert (span.end_time - span.start_time) == 20 * 1_000_000_000
assert json.loads(span.attributes["langfuse.observation.completion_start_time"]) == FIRST_TOKEN.isoformat().replace(
"+00:00", "Z"
)
@pytest.mark.parametrize(
"supplied",
[1709294400.5, datetime(2024, 3, 1, 12, 0, 0, 500000, tzinfo=timezone.utc)],
ids=["unix-seconds-float", "datetime"],
)
def test_timestamps_accept_both_shapes_guardrails_and_callbacks_use(supplied):
"""Guardrail entries carry unix seconds as floats, the callback carries datetimes."""
assert to_unix_nanos(supplied) == 1709294400500000000
def test_guardrail_span_with_float_timestamps_does_not_break_the_generation(client):
"""A guardrail entry must not take the whole event down with it."""
lf, exporter = client
context, claim_root = open_trace_context(client=lf, trace_id="9" * 32, parent_observation_id=None)
guardrail_start = 1709294400.0
start_child_span(client=lf, context=context, name="guardrail", start_time=guardrail_start, attributes={}).end(
end_time=to_unix_nanos(guardrail_start + 2)
)
start_generation(
client=lf, context=context, name="gen", start_time=CALL_START, claim_trace_root=claim_root, attributes={}
).end(end_time=to_unix_nanos(CALL_END))
lf.flush()
guardrail = _only_span(exporter, "guardrail")
assert (guardrail.end_time - guardrail.start_time) == 2 * 1_000_000_000
assert _only_span(exporter, "gen") is not None
def test_generation_claims_trace_root_only_without_a_real_parent(client):
lf, exporter = client
context, claim_root = open_trace_context(client=lf, trace_id="b" * 32, parent_observation_id=None)
assert claim_root is True
start_generation(
client=lf, context=context, name="root-gen", start_time=CALL_START, claim_trace_root=claim_root, attributes={}
).end()
parented_context, parented_claim = open_trace_context(client=lf, trace_id="b" * 32, parent_observation_id="c" * 16)
assert parented_claim is False
start_generation(
client=lf,
context=parented_context,
name="child-gen",
start_time=CALL_START,
claim_trace_root=parented_claim,
attributes={},
).end()
lf.flush()
assert _only_span(exporter, "root-gen").attributes.get(AS_ROOT_ATTRIBUTE) is True
assert _only_span(exporter, "child-gen").attributes.get(AS_ROOT_ATTRIBUTE) is None
def test_child_span_keeps_its_own_window_and_stays_a_sibling(client):
lf, exporter = client
context, claim_root = open_trace_context(client=lf, trace_id="d" * 32, parent_observation_id=None)
guardrail_start = CALL_START + timedelta(seconds=1)
start_child_span(client=lf, context=context, name="guardrail", start_time=guardrail_start, attributes={}).end(
end_time=to_unix_nanos(guardrail_start + timedelta(seconds=2))
)
start_generation(
client=lf, context=context, name="gen", start_time=CALL_START, claim_trace_root=claim_root, attributes={}
).end(end_time=to_unix_nanos(CALL_END))
lf.flush()
guardrail = _only_span(exporter, "guardrail")
generation = _only_span(exporter, "gen")
assert (guardrail.end_time - guardrail.start_time) == 2 * 1_000_000_000
assert guardrail.parent.span_id == generation.parent.span_id
assert guardrail.context.trace_id == generation.context.trace_id
def test_release_is_carried_on_the_root_observation(client):
lf, exporter = client
context, claim_root = open_trace_context(client=lf, trace_id="e" * 32, parent_observation_id=None)
start_generation(
client=lf,
context=context,
name="gen",
start_time=CALL_START,
claim_trace_root=claim_root,
release="v1.2.3",
attributes={},
).end()
lf.flush()
assert _only_span(exporter, "gen").attributes[RELEASE_ATTRIBUTE] == "v1.2.3"
@pytest.mark.parametrize("public", [True, False], ids=["public", "private"])
def test_trace_public_flag_lands_on_the_root_observation(client, public):
"""v2 took ``public`` on ``trace()``; v4 reads ``langfuse.trace.public`` off the root observation."""
lf, exporter = client
context, claim_root = open_trace_context(client=lf, trace_id="a" * 32, parent_observation_id=None)
start_generation(
client=lf,
context=context,
name="gen",
start_time=CALL_START,
claim_trace_root=claim_root,
public=public,
attributes={},
).end()
lf.flush()
assert _only_span(exporter, "gen").attributes[PUBLIC_ATTRIBUTE] is public
def test_trace_public_flag_is_absent_when_not_requested(client):
lf, exporter = client
context, claim_root = open_trace_context(client=lf, trace_id="b" * 32, parent_observation_id=None)
start_generation(
client=lf, context=context, name="gen", start_time=CALL_START, claim_trace_root=claim_root, attributes={}
).end()
lf.flush()
assert PUBLIC_ATTRIBUTE not in _only_span(exporter, "gen").attributes
def test_request_release_beats_the_client_wide_release(monkeypatch):
"""A client configured with its own release must not overwrite trace_release."""
monkeypatch.setenv("LANGFUSE_RELEASE", "client-wide-release")
LangfuseResourceManager._instances.pop("pk-release-test", None)
exporter = InMemorySpanExporter()
provider = TracerProvider()
provider.add_span_processor(SimpleSpanProcessor(exporter))
lf = Langfuse(
public_key="pk-release-test",
secret_key="sk-release-test",
host="http://127.0.0.1:1",
release="client-wide-release",
tracer_provider=provider,
span_exporter=exporter,
)
context, claim_root = open_trace_context(client=lf, trace_id="f" * 32, parent_observation_id=None)
start_generation(
client=lf,
context=context,
name="gen",
start_time=CALL_START,
claim_trace_root=claim_root,
release="per-request-release",
attributes={},
).end()
lf.flush()
LangfuseResourceManager._instances.pop("pk-release-test", None)
assert _only_span(exporter, "gen").attributes[RELEASE_ATTRIBUTE] == "per-request-release"
@pytest.mark.parametrize(
"supplied, expected",
[
("0123456789abcdef0123456789abcdef", "0123456789abcdef0123456789abcdef"),
("0123456789ABCDEF0123456789ABCDEF", "0123456789abcdef0123456789abcdef"),
("3fe0c940-b69a-de3b-a77c-06102505349a", "3fe0c940b69ade3ba77c06102505349a"),
],
ids=["already-hex", "uppercase-hex", "uuid-with-dashes"],
)
def test_trace_id_passes_through_when_it_is_already_usable(supplied, expected):
assert resolve_trace_id(supplied) == expected
def test_arbitrary_trace_id_is_hashed_deterministically():
first = resolve_trace_id("order-4471")
assert first == resolve_trace_id("order-4471")
assert len(first) == 32 and first == first.lower()
assert first != resolve_trace_id("order-4472")
def test_missing_trace_id_still_yields_a_valid_trace_id():
generated = resolve_trace_id(None)
assert len(generated) == 32
assert int(generated, 16) >= 0
@pytest.mark.parametrize(
"supplied, expected",
[
("0123456789abcdef", "0123456789abcdef"),
(None, None),
("", None),
],
ids=["already-hex", "none", "empty"],
)
def test_observation_id_normalisation(supplied, expected):
assert resolve_observation_id(supplied) == expected
def test_arbitrary_observation_id_is_hashed_to_a_span_id():
resolved = resolve_observation_id("my-parent-observation")
assert len(resolved) == 16
assert resolved == resolve_observation_id("my-parent-observation")
PUBLIC_KEY = "pk-lifecycle-test"
@pytest.fixture(autouse=True)
def _clean_registry():
LangfuseResourceManager._instances.pop(PUBLIC_KEY, None)
yield
LangfuseResourceManager._instances.pop(PUBLIC_KEY, None)
def _lifecycle_client(secret_key="sk-original", host="http://127.0.0.1:1"):
return Langfuse(
public_key=PUBLIC_KEY,
secret_key=secret_key,
host=host,
tracer_provider=build_isolated_tracer_provider(environment=None, release=None),
)
@pytest.mark.parametrize("unsupported", ["2.59.7", "3.15.0", "5.0.0"], ids=["v2", "v3", "v5"])
def test_unsupported_sdk_fails_loudly_rather_than_dropping_every_event(unsupported):
with pytest.raises(ImportError) as raised:
raise_if_unsupported_langfuse_version(unsupported)
assert unsupported in str(raised.value)
assert MINIMUM_LANGFUSE_VERSION in str(raised.value)
def test_supported_sdk_is_accepted():
assert raise_if_unsupported_langfuse_version(installed_langfuse_version()) is None
def test_isolated_provider_carries_environment_and_release():
provider = build_isolated_tracer_provider(environment="staging", release="v9")
attributes = provider.resource.attributes
assert attributes["langfuse.environment"] == "staging"
assert attributes["langfuse.release"] == "v9"
def test_client_does_not_take_over_the_process_tracer_provider():
# the global provider can only be set once per process, so assert it is left
# alone rather than assuming this test is the one that installed it
provider_before = otel_trace.get_tracer_provider()
client = _lifecycle_client()
assert otel_trace.get_tracer_provider() is provider_before
assert client._resources.tracer_provider is not provider_before
active = getattr(provider_before, "_active_span_processor", None)
if active is not None:
assert not any("Langfuse" in type(processor).__name__ for processor in active._span_processors)
def test_rotated_credentials_replace_the_cached_client():
original = _lifecycle_client(secret_key="sk-original", host="http://127.0.0.1:1")
original_resources = original._resources
evict_stale_langfuse_resources(public_key=PUBLIC_KEY, secret_key="sk-rotated", base_url="http://127.0.0.1:2")
rotated = _lifecycle_client(secret_key="sk-rotated", host="http://127.0.0.1:2")
assert rotated._resources is not original_resources
assert rotated._resources.secret_key == "sk-rotated"
assert rotated._resources.base_url == "http://127.0.0.1:2"
def test_unchanged_credentials_keep_the_cached_client():
original = _lifecycle_client()
evict_stale_langfuse_resources(public_key=PUBLIC_KEY, secret_key="sk-original", base_url="http://127.0.0.1:1")
assert LangfuseResourceManager._instances.get(PUBLIC_KEY) is original._resources
def test_eviction_flushes_queued_observations_before_tearing_down():
"""An observation already ended when the cache evicts must still reach langfuse."""
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.sdk.trace.export.in_memory_span_exporter import (
InMemorySpanExporter,
)
from litellm.integrations.langfuse.langfuse_sdk import (
open_trace_context,
start_generation,
)
exporter = InMemorySpanExporter()
provider = TracerProvider()
# a long delay keeps the span queued, so only the shutdown can flush it
provider.add_span_processor(BatchSpanProcessor(exporter, schedule_delay_millis=600000))
client = Langfuse(
public_key=PUBLIC_KEY,
secret_key="sk-original",
host="http://127.0.0.1:1",
tracer_provider=provider,
span_exporter=exporter,
)
context, claim_root = open_trace_context(client=client, trace_id="a" * 32, parent_observation_id=None)
start_generation(
client=client, context=context, name="in-flight", start_time=None, claim_trace_root=claim_root, attributes={}
).end()
assert exporter.get_finished_spans() == ()
shutdown_langfuse_client(client)
assert any(span.name == "in-flight" for span in exporter.get_finished_spans())
def test_shutdown_deregisters_so_a_later_client_is_not_a_corpse():
client = _lifecycle_client()
resources = client._resources
shutdown_langfuse_client(client)
assert LangfuseResourceManager._instances.get(PUBLIC_KEY) is not resources
def _shared_resources_pair():
"""The SDK hands a second client on the same public key the first client's resources."""
exporter = InMemorySpanExporter()
provider = TracerProvider()
provider.add_span_processor(SimpleSpanProcessor(exporter))
first = Langfuse(
public_key=PUBLIC_KEY,
secret_key="sk-original",
host="http://127.0.0.1:1",
tracer_provider=provider,
span_exporter=exporter,
)
second = Langfuse(
public_key=PUBLIC_KEY,
secret_key="sk-original",
host="http://127.0.0.1:1",
tracer_provider=build_isolated_tracer_provider(environment="per-key-override", release=None),
)
assert second._resources is first._resources
register_langfuse_client(first)
register_langfuse_client(second)
return first, second, exporter
def _exports(client, exporter, name):
client.start_observation(name=name).end()
client.flush()
return any(span.name == name for span in exporter.get_finished_spans())
def test_garbage_collected_throwaway_clients_do_not_hold_shared_resources_open():
"""A health probe or alerting lookup builds a client it never shuts down.
Once such a client is garbage collected it must stop counting, or the last
managed client's shutdown would skip the teardown forever.
"""
import gc
first, second, exporter = _shared_resources_pair()
throwaway = Langfuse(public_key=PUBLIC_KEY, secret_key="sk-original", host="http://127.0.0.1:1")
register_langfuse_client(throwaway)
shutdown_langfuse_client(second)
del throwaway
gc.collect()
shutdown_langfuse_client(first)
assert not _exports(first, exporter, "after-managed-teardown")
assert LangfuseResourceManager._instances.get(PUBLIC_KEY) is not first._resources
def test_evicting_a_client_that_shares_resources_keeps_the_other_exporting():
"""A per-key ``langfuse_environment`` override is a second client on the global key.
When the cache evicts it, the global logger must keep exporting.
"""
first, second, exporter = _shared_resources_pair()
shutdown_langfuse_client(second)
assert _exports(first, exporter, "after-sibling-eviction")
assert LangfuseResourceManager._instances.get(PUBLIC_KEY) is first._resources
def test_last_client_on_shared_resources_tears_them_down():
first, second, exporter = _shared_resources_pair()
shutdown_langfuse_client(second)
shutdown_langfuse_client(first)
assert not _exports(first, exporter, "after-last-eviction")
assert LangfuseResourceManager._instances.get(PUBLIC_KEY) is not first._resources
def test_shutdown_of_a_stale_client_does_not_deregister_the_live_one():
stale = _lifecycle_client(secret_key="sk-original", host="http://127.0.0.1:1")
stale_resources = stale._resources
evict_stale_langfuse_resources(public_key=PUBLIC_KEY, secret_key="sk-rotated", base_url="http://127.0.0.1:2")
live = _lifecycle_client(secret_key="sk-rotated", host="http://127.0.0.1:2")
shutdown_langfuse_client(stale)
assert stale_resources is not live._resources
assert LangfuseResourceManager._instances.get(PUBLIC_KEY) is live._resources

View file

@ -1,6 +1,5 @@
import datetime
import json
import sys
import types
import unittest
from typing import Final, Optional
@ -11,6 +10,7 @@ import pytest
import litellm
from litellm.integrations.langfuse import langfuse as langfuse_module
from litellm.integrations.langfuse.langfuse import LangFuseLogger
from litellm.integrations.langfuse.langfuse_sdk import resolve_trace_id
# Import LangfuseUsageDetails directly from the module where it's defined
@ -56,28 +56,23 @@ class TestLangfuseUsageDetails(unittest.TestCase):
self.mock_langfuse_client.trace.side_effect = _trace_side_effect
# Mock the langfuse module that's imported locally in methods
self.langfuse_module_patcher = patch.dict(
"sys.modules", {"langfuse": MagicMock()}
)
self.mock_langfuse_module = self.langfuse_module_patcher.start()
# Create a mock for the langfuse module with version
self.mock_langfuse = MagicMock()
self.mock_langfuse.version = MagicMock()
self.mock_langfuse.version.__version__ = (
"3.0.0" # Set a version that supports all features
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
from opentelemetry.sdk.trace.export.in_memory_span_exporter import (
InMemorySpanExporter,
)
# Mock the Langfuse class
self.mock_langfuse_class = MagicMock()
self.mock_langfuse_class.return_value = self.mock_langfuse_client
self.span_exporter = InMemorySpanExporter()
self.real_provider = TracerProvider()
self.real_provider.add_span_processor(SimpleSpanProcessor(self.span_exporter))
# Set up the sys.modules['langfuse'] mock
sys.modules["langfuse"] = self.mock_langfuse
sys.modules["langfuse"].Langfuse = self.mock_langfuse_class
# the real SDK is installed; inject the client instead of replacing the module,
# so the v4 imports under test resolve normally
import langfuse as _langfuse_module
# Create a fresh logger instance for each test
self.real_langfuse_class = _langfuse_module.Langfuse
# no patching: the host above is unreachable, so a real client is cheap to build
# and each test swaps in the client it wants
self.logger = LangFuseLogger()
# Explicitly set the Langfuse client to our mock
@ -113,9 +108,7 @@ class TestLangfuseUsageDetails(unittest.TestCase):
)
# Bind the method to the instance
self.logger.log_event_on_langfuse = types.MethodType(
log_event_on_langfuse, self.logger
)
self.logger.log_event_on_langfuse = types.MethodType(log_event_on_langfuse, self.logger)
# Make sure _is_langfuse_v2 returns True
def mock_is_langfuse_v2(self):
@ -135,7 +128,37 @@ class TestLangfuseUsageDetails(unittest.TestCase):
litellm.initialized_langfuse_clients = self._original_langfuse_clients_count
self.env_patcher.stop()
self.langfuse_module_patcher.stop() # patch.dict automatically restores sys.modules
def use_real_langfuse_client(self):
"""Point the logger at a real v4 client whose spans land in memory."""
from langfuse._client.resource_manager import LangfuseResourceManager
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
from opentelemetry.sdk.trace.export.in_memory_span_exporter import (
InMemorySpanExporter,
)
self.span_exporter = InMemorySpanExporter()
self.real_provider = TracerProvider()
LangfuseResourceManager._instances.pop("pk-unit-test", None)
self.logger.Langfuse = self.real_langfuse_class(
public_key="pk-unit-test",
secret_key="sk-unit-test",
host="http://127.0.0.1:1",
tracer_provider=self.real_provider,
span_exporter=self.span_exporter,
)
return self.logger.Langfuse
def exported_generation(self):
self.logger.Langfuse.flush()
spans = [s for s in self.span_exporter.get_finished_spans()]
assert spans, "no spans were exported"
return spans[-1]
@staticmethod
def span_trace_id(span):
return format(span.context.trace_id, "032x")
def test_langfuse_usage_details_type(self):
"""Test that LangfuseUsageDetails TypedDict is properly defined with the correct fields"""
@ -278,9 +301,7 @@ class TestLangfuseUsageDetails(unittest.TestCase):
self.mock_langfuse_trace.span.return_value = mock_span
self.mock_langfuse_trace.generation.return_value = self.mock_langfuse_generation
# Ensure trace returns our mock
self.mock_langfuse_client.trace.return_value = self.mock_langfuse_trace
self.logger.Langfuse = self.mock_langfuse_client
self.use_real_langfuse_client()
with (
patch(
@ -338,29 +359,12 @@ class TestLangfuseUsageDetails(unittest.TestCase):
except Exception as e:
self.fail(f"_log_langfuse_v2 raised an exception: {e}")
# Verify that trace was called first
self.mock_langfuse_client.trace.assert_called()
# Check the arguments passed to the mocked langfuse generation call
self.mock_langfuse_trace.generation.assert_called_once()
call_args, call_kwargs = self.mock_langfuse_trace.generation.call_args
# Inspect the usage and usage_details dictionaries
usage_arg = call_kwargs.get("usage")
usage_details_arg = call_kwargs.get("usage_details")
self.assertIsNotNone(usage_arg)
self.assertIsNotNone(usage_details_arg)
# Verify that None values were converted to 0
self.assertEqual(usage_arg["prompt_tokens"], 0)
self.assertEqual(usage_arg["completion_tokens"], 0)
self.assertEqual(usage_details_arg["input"], 0)
self.assertEqual(usage_details_arg["output"], 0)
self.assertEqual(usage_details_arg["total"], 0)
self.assertEqual(usage_details_arg["cache_creation_input_tokens"], 0)
self.assertEqual(usage_details_arg["cache_read_input_tokens"], 0)
usage_details = json.loads(self.exported_generation().attributes["langfuse.observation.usage_details"])
assert usage_details["input"] == 0
assert usage_details["output"] == 0
assert usage_details["total"] == 0
assert usage_details["cache_creation_input_tokens"] == 0
assert usage_details["cache_read_input_tokens"] == 0
mock_add_prompt_params.assert_called_once()
@ -413,7 +417,7 @@ class TestLangfuseUsageDetails(unittest.TestCase):
def test_log_langfuse_v2_uses_standard_trace_id_when_available(self):
payload = self._build_standard_logging_payload(trace_id="std-trace-id")
kwargs = self._build_langfuse_kwargs(payload)
self.last_trace_kwargs = {}
self.use_real_langfuse_client()
with patch(
"litellm.integrations.langfuse.langfuse._add_prompt_to_generation_params",
@ -435,12 +439,12 @@ class TestLangfuseUsageDetails(unittest.TestCase):
litellm_call_id="call-id-xyz",
)
assert self.last_trace_kwargs.get("id") == "std-trace-id"
assert self.span_trace_id(self.exported_generation()) == resolve_trace_id("std-trace-id")
def test_log_langfuse_v2_defaults_to_call_id_without_standard_trace_id(self):
payload = self._build_standard_logging_payload()
kwargs = self._build_langfuse_kwargs(payload)
self.last_trace_kwargs = {}
self.use_real_langfuse_client()
with patch(
"litellm.integrations.langfuse.langfuse._add_prompt_to_generation_params",
@ -462,7 +466,7 @@ class TestLangfuseUsageDetails(unittest.TestCase):
litellm_call_id="call-id-xyz",
)
assert self.last_trace_kwargs.get("id") == "call-id-xyz"
assert self.span_trace_id(self.exported_generation()) == resolve_trace_id("call-id-xyz")
def test_log_langfuse_v2_uses_litellm_trace_id_fallback_over_call_id(self):
"""
@ -474,7 +478,7 @@ class TestLangfuseUsageDetails(unittest.TestCase):
payload = self._build_standard_logging_payload() # no trace_id
kwargs = self._build_langfuse_kwargs(payload)
kwargs["litellm_trace_id"] = "trace-id-from-kwargs"
self.last_trace_kwargs = {}
self.use_real_langfuse_client()
with patch(
"litellm.integrations.langfuse.langfuse._add_prompt_to_generation_params",
@ -497,7 +501,7 @@ class TestLangfuseUsageDetails(unittest.TestCase):
)
# litellm_trace_id should be preferred over litellm_call_id
assert self.last_trace_kwargs.get("id") == "trace-id-from-kwargs"
assert self.span_trace_id(self.exported_generation()) == resolve_trace_id("trace-id-from-kwargs")
CANARY = "sk-lf-canary-SECRET-d4e5f6"
@ -527,14 +531,41 @@ class TestLangfuseUsageDetails(unittest.TestCase):
}
def _emitted_payload_text(self):
"""Every blob this logger handed to the langfuse SDK, as one searchable string."""
"""Every attribute this logger exported to langfuse, as one searchable string."""
import json
blobs = [self.last_trace_kwargs]
if self.mock_langfuse_trace.generation.call_args is not None:
blobs.append(self.mock_langfuse_trace.generation.call_args.kwargs)
blobs.extend(call.kwargs for call in self.mock_langfuse_trace.span.call_args_list)
return json.dumps(blobs, default=repr)
self.logger.Langfuse.flush()
return json.dumps(
[dict(span.attributes or {}) for span in self.span_exporter.get_finished_spans()],
default=repr,
)
def exported_generation_metadata(self):
"""The generation's metadata as langfuse receives it, one attribute per key.
v4 serializes each value onto the span, so they are decoded back here to
keep these assertions about what litellm emitted rather than about the
SDK's wire encoding.
"""
import json
prefix = "langfuse.observation.metadata."
def decoded(raw):
try:
return json.loads(raw)
except (TypeError, ValueError):
return raw
return {
key[len(prefix) :]: decoded(value)
for key, value in (self.exported_generation().attributes or {}).items()
if key.startswith(prefix)
}
def exported_spans_named(self, name):
self.logger.Langfuse.flush()
return [span for span in self.span_exporter.get_finished_spans() if span.name == name]
def _drive_with_canary(self, extra_metadata=None, hidden_params=None):
metadata = {**self._canary_request_metadata(), **(extra_metadata or {})}
@ -542,9 +573,7 @@ class TestLangfuseUsageDetails(unittest.TestCase):
if hidden_params is not None:
payload["hidden_params"] = hidden_params
kwargs = {**self._build_langfuse_kwargs(payload), "response_cost": 0.25}
self.last_trace_kwargs = {}
self.mock_langfuse_trace.generation.reset_mock()
self.mock_langfuse_trace.span.reset_mock()
self.use_real_langfuse_client()
with patch(
"litellm.integrations.langfuse.langfuse._add_prompt_to_generation_params",
@ -565,7 +594,7 @@ class TestLangfuseUsageDetails(unittest.TestCase):
level="INFO",
litellm_call_id="canary-call-id",
)
return self.mock_langfuse_trace.generation.call_args.kwargs["metadata"]
return self.exported_generation_metadata()
def test_team_callback_credentials_never_reach_langfuse(self):
"""
@ -589,9 +618,8 @@ class TestLangfuseUsageDetails(unittest.TestCase):
debug_langfuse dumps request metadata into the trace as a second emit site.
It must be sourced from the allowlisted payload too.
"""
self._drive_with_canary(extra_metadata={"debug_langfuse": True})
dumped = self._drive_with_canary(extra_metadata={"debug_langfuse": True})["metadata_passed_to_litellm"]
dumped = self.last_trace_kwargs["metadata"]["metadata_passed_to_litellm"]
assert "user_api_key_auth" not in dumped
assert self.CANARY not in self._emitted_payload_text()
@ -616,7 +644,10 @@ class TestLangfuseUsageDetails(unittest.TestCase):
"""
self._drive_with_canary(hidden_params={"vertex_ai_grounding_metadata": ["ground-a", "ground-b"]})
span_inputs = [call.kwargs.get("input") for call in self.mock_langfuse_trace.span.call_args_list]
span_inputs = [
span.attributes.get("langfuse.observation.input")
for span in self.exported_spans_named("vertex_ai_grounding_metadata")
]
assert span_inputs == ["ground-a", "ground-b"]
assert self.CANARY not in self._emitted_payload_text()
@ -625,9 +656,7 @@ class TestLangfuseUsageDetails(unittest.TestCase):
Request metadata never reaches the blob, so a caller naming user_api_key_alias
cannot have their value emitted in place of the proxy-resolved one.
"""
generation_metadata = self._drive_with_canary(
extra_metadata={"user_api_key_alias": "spoofed-by-caller"}
)
generation_metadata = self._drive_with_canary(extra_metadata={"user_api_key_alias": "spoofed-by-caller"})
assert generation_metadata["user_api_key_alias"] == "canary-alias"
@ -642,7 +671,7 @@ class TestLangfuseUsageDetails(unittest.TestCase):
payload["metadata"]["requester_metadata"] = {"litellm_response_cost": "caller-value", "api_base": "caller"}
kwargs = {**self._build_langfuse_kwargs(payload), "response_cost": 0.25}
metadata = self._canary_request_metadata()
self.mock_langfuse_trace.generation.reset_mock()
self.use_real_langfuse_client()
with patch(
"litellm.integrations.langfuse.langfuse._add_prompt_to_generation_params",
@ -664,7 +693,7 @@ class TestLangfuseUsageDetails(unittest.TestCase):
litellm_call_id="canary-call-id",
)
generation_metadata = self.mock_langfuse_trace.generation.call_args.kwargs["metadata"]
generation_metadata = self.exported_generation_metadata()
assert generation_metadata["litellm_response_cost"] == 0.25
assert generation_metadata["api_base"] == "https://real-api-base"
@ -732,8 +761,9 @@ class TestLangfuseUsageDetails(unittest.TestCase):
"""
self._drive_with_canary()
assert self.last_trace_kwargs.get("session_id") == "canary-session"
assert self.last_trace_kwargs.get("name") == "canary-trace"
generation = self.exported_generation()
assert generation.attributes["session.id"] == "canary-session"
assert generation.attributes["langfuse.trace.name"] == "canary-trace"
def test_failure_trace_survives_a_missing_standard_logging_object(self):
"""
@ -752,8 +782,7 @@ class TestLangfuseUsageDetails(unittest.TestCase):
"messages": [],
"litellm_trace_id": "trace-id-failure",
}
self.last_trace_kwargs = {}
self.mock_langfuse_trace.generation.reset_mock()
self.use_real_langfuse_client()
with patch(
"litellm.integrations.langfuse.langfuse._add_prompt_to_generation_params",
@ -777,9 +806,12 @@ class TestLangfuseUsageDetails(unittest.TestCase):
import json
assert trace_id == "trace-id-failure"
assert self.last_trace_kwargs.get("id") == "trace-id-failure"
generation_metadata = self.mock_langfuse_trace.generation.call_args.kwargs["metadata"]
# Must use litellm_trace_id, not litellm_call_id. v4 addresses a trace by a
# 32-hex id, so the callback returns the resolved form, which is what makes
# the alerting deep link point at a trace langfuse can actually open
assert trace_id == resolve_trace_id("trace-id-failure")
assert self.span_trace_id(self.exported_generation()) == trace_id
generation_metadata = self.exported_generation_metadata()
assert "user_api_key_auth" not in generation_metadata
assert self.CANARY not in self._emitted_payload_text()
assert "first_custom" not in generation_metadata
@ -796,7 +828,7 @@ class TestLangfuseUsageDetails(unittest.TestCase):
"""
payload = self._build_standard_logging_payload(trace_id="std-trace-123")
kwargs = self._build_langfuse_kwargs(payload)
self.last_trace_kwargs = {}
self.use_real_langfuse_client()
with patch(
"litellm.integrations.langfuse.langfuse._add_prompt_to_generation_params",
@ -819,9 +851,9 @@ class TestLangfuseUsageDetails(unittest.TestCase):
)
# session_id should be set for Langfuse session grouping
assert self.last_trace_kwargs.get("session_id") == "my-session-abc"
assert self.exported_generation().attributes["session.id"] == "my-session-abc"
# trace_id should remain the standard trace_id, NOT the session_id
assert self.last_trace_kwargs.get("id") == "std-trace-123"
assert self.span_trace_id(self.exported_generation()) == resolve_trace_id("std-trace-123")
def test_log_langfuse_v2_session_id_preserved_for_error_level(self):
"""
@ -831,7 +863,7 @@ class TestLangfuseUsageDetails(unittest.TestCase):
"""
payload = self._build_standard_logging_payload(trace_id="std-trace-err")
kwargs = self._build_langfuse_kwargs(payload)
self.last_trace_kwargs = {}
self.use_real_langfuse_client()
with patch(
"litellm.integrations.langfuse.langfuse._add_prompt_to_generation_params",
@ -854,11 +886,11 @@ class TestLangfuseUsageDetails(unittest.TestCase):
)
# session_id must be preserved even for ERROR level logs
assert self.last_trace_kwargs.get("session_id") == "error-session-xyz"
assert self.exported_generation().attributes["session.id"] == "error-session-xyz"
# trace_id should be the standard trace_id, not the session_id
assert self.last_trace_kwargs.get("id") == "std-trace-err"
assert self.span_trace_id(self.exported_generation()) == resolve_trace_id("std-trace-err")
# status_message should be set for error traces
assert self.last_trace_kwargs.get("status_message") is not None
assert self.exported_generation().attributes["langfuse.observation.level"] == "ERROR"
def test_log_langfuse_v2_explicit_trace_id_takes_priority_over_session_id(self):
"""
@ -867,7 +899,7 @@ class TestLangfuseUsageDetails(unittest.TestCase):
"""
payload = self._build_standard_logging_payload()
kwargs = self._build_langfuse_kwargs(payload)
self.last_trace_kwargs = {}
self.use_real_langfuse_client()
with patch(
"litellm.integrations.langfuse.langfuse._add_prompt_to_generation_params",
@ -898,9 +930,9 @@ class TestLangfuseUsageDetails(unittest.TestCase):
)
# Explicit trace_id must take priority
assert self.last_trace_kwargs.get("id") == "explicit-trace-id-777"
assert self.span_trace_id(self.exported_generation()) == resolve_trace_id("explicit-trace-id-777")
# session_id must still be set for session grouping
assert self.last_trace_kwargs.get("session_id") == "session-999"
assert self.exported_generation().attributes["session.id"] == "session-999"
def test_failure_handler_langfuse_kwargs_excludes_original_response():
@ -948,12 +980,8 @@ def test_failure_handler_langfuse_kwargs_excludes_original_response():
try:
# Mock LangFuseHandler to return our capturing mock logger
with patch(
"litellm.litellm_core_utils.litellm_logging.LangFuseHandler"
) as mock_handler_class:
mock_handler_class.get_langfuse_logger_for_request.return_value = (
mock_langfuse_logger
)
with patch("litellm.litellm_core_utils.litellm_logging.LangFuseHandler") as mock_handler_class:
mock_handler_class.get_langfuse_logger_for_request.return_value = mock_langfuse_logger
# Call the actual failure_handler
test_exception = Exception("TestError: model not found")
@ -965,23 +993,19 @@ def test_failure_handler_langfuse_kwargs_excludes_original_response():
)
# Verify log_event_on_langfuse was actually called
assert (
mock_langfuse_logger.log_event_on_langfuse.called
), "log_event_on_langfuse was not called"
assert mock_langfuse_logger.log_event_on_langfuse.called, "log_event_on_langfuse was not called"
# Verify original_response is NOT in the kwargs passed to Langfuse
langfuse_kwargs = captured_kwargs.get("kwargs", {})
assert (
"original_response" not in langfuse_kwargs
), "original_response should be excluded from kwargs passed to Langfuse"
assert "original_response" not in langfuse_kwargs, (
"original_response should be excluded from kwargs passed to Langfuse"
)
# Verify session_id metadata is preserved in the kwargs
langfuse_metadata = langfuse_kwargs.get("litellm_params", {}).get(
"metadata", {}
langfuse_metadata = langfuse_kwargs.get("litellm_params", {}).get("metadata", {})
assert langfuse_metadata.get("session_id") == "test-session-failure", (
"session_id should be preserved in kwargs passed to Langfuse"
)
assert (
langfuse_metadata.get("session_id") == "test-session-failure"
), "session_id should be preserved in kwargs passed to Langfuse"
# Verify level is ERROR
assert captured_kwargs.get("level") == "ERROR"
@ -1023,9 +1047,7 @@ async def test_async_log_failure_event_logs_to_langfuse():
"generation_id": "mock-gen",
}
with patch(
"litellm.integrations.langfuse.langfuse_prompt_management.LangFuseHandler"
) as mock_handler:
with patch("litellm.integrations.langfuse.langfuse_prompt_management.LangFuseHandler") as mock_handler:
mock_handler.get_langfuse_logger_for_request.return_value = mock_logger
kwargs = {
@ -1050,9 +1072,7 @@ async def test_async_log_failure_event_logs_to_langfuse():
)
# Verify log_event_on_langfuse was called
assert (
mock_logger.log_event_on_langfuse.called
), "log_event_on_langfuse was not called for failure event"
assert mock_logger.log_event_on_langfuse.called, "log_event_on_langfuse was not called for failure event"
call_kwargs = mock_logger.log_event_on_langfuse.call_args[1]
assert call_kwargs["level"] == "ERROR"
assert call_kwargs["status_message"] == "API error: model not found"
@ -1092,9 +1112,7 @@ async def test_async_log_failure_event_works_without_standard_logging_object():
"generation_id": "mock-gen",
}
with patch(
"litellm.integrations.langfuse.langfuse_prompt_management.LangFuseHandler"
) as mock_handler:
with patch("litellm.integrations.langfuse.langfuse_prompt_management.LangFuseHandler") as mock_handler:
mock_handler.get_langfuse_logger_for_request.return_value = mock_logger
kwargs = {
@ -1125,6 +1143,64 @@ async def test_async_log_failure_event_works_without_standard_logging_object():
assert "InternalServerError" in call_kwargs["status_message"]
def test_mock_mode_makes_no_network_calls(monkeypatch):
"""LANGFUSE_MOCK promises full execution without egress.
The mock intercepts httpx, but v4 ships observations over its own OTLP
exporter, so nothing stops a real request to the configured host without an
exporter that drops them.
"""
import threading
import time
from http.server import BaseHTTPRequestHandler, HTTPServer
from langfuse._client.resource_manager import LangfuseResourceManager
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-mock-egress")
monkeypatch.setenv("LANGFUSE_SECRET_KEY", "sk-mock-egress")
LangfuseResourceManager._instances.pop("pk-mock-egress", None)
try:
logger = LangFuseLogger()
assert logger.is_mock_mode is True
now = datetime.datetime.now()
logger.log_event_on_langfuse(
kwargs={
"call_type": "completion",
"litellm_params": {"metadata": {}, "proxy_server_request": {"headers": {}}},
"messages": [{"role": "user", "content": "hi"}],
"optional_params": {},
},
response_obj=litellm.ModelResponse(choices=[{"message": {"role": "assistant", "content": "yo"}}]),
start_time=now,
end_time=now,
)
logger.Langfuse.flush()
time.sleep(1)
finally:
server.shutdown()
LangfuseResourceManager._instances.pop("pk-mock-egress", None)
assert received == [], f"mock mode sent real requests: {received}"
def test_max_langfuse_clients_limit():
"""
Test that the max langfuse clients limit is respected when initializing multiple clients
@ -1184,14 +1260,6 @@ class _RecordingLangfuse:
self.client = MagicMock()
class _RecordingLangfuseWithoutEnvironment:
last_parameters: Optional[dict] = None
def __init__(self, **parameters):
type(self).last_parameters = parameters
self.client = MagicMock()
def _build_langfuse_logger(monkeypatch) -> LangFuseLogger:
monkeypatch.setenv("LANGFUSE_MOCK", "false")
monkeypatch.setattr(litellm, "initialized_langfuse_clients", 0)
@ -1232,19 +1300,6 @@ def test_langfuse_environment_falls_back_to_deployment_env_var(monkeypatch):
assert _RecordingLangfuse.last_parameters["environment"] == "deployment-wide"
def test_langfuse_environment_omitted_for_old_sdk_versions(monkeypatch):
monkeypatch.setenv("LANGFUSE_MOCK", "false")
monkeypatch.setattr(litellm, "initialized_langfuse_clients", 0)
with patch("langfuse.Langfuse", _RecordingLangfuseWithoutEnvironment):
LangFuseLogger(
langfuse_public_key="pk-env",
langfuse_secret="sk-env",
langfuse_host="https://test.langfuse.com",
langfuse_environment="staging",
)
assert "environment" not in _RecordingLangfuseWithoutEnvironment.last_parameters
def test_dynamic_langfuse_environment_triggers_dynamic_logger():
from litellm.integrations.langfuse.langfuse_handler import LangFuseHandler
from litellm.types.utils import StandardCallbackDynamicParams
@ -1307,71 +1362,110 @@ def test_langfuse_logger_reuses_the_shared_cached_client(monkeypatch):
_LANGFUSE_REDACTED = "redacted-by-litellm"
def _steering_logger() -> LangFuseLogger:
"""``__new__`` skips the SDK and network setup in ``__init__``."""
def _steering_logger():
"""``__new__`` skips the network setup in ``__init__``; spans land in memory."""
from langfuse import Langfuse
from langfuse._client.resource_manager import LangfuseResourceManager
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export.in_memory_span_exporter import (
InMemorySpanExporter,
)
from litellm.integrations.langfuse.langfuse import installed_langfuse_version
exporter = InMemorySpanExporter()
LangfuseResourceManager._instances.pop("pk-steering-test", None)
logger = LangFuseLogger.__new__(LangFuseLogger)
logger.Langfuse = MagicMock()
logger.langfuse_sdk_version = "2.60.0"
return logger
logger.Langfuse = Langfuse(
public_key="pk-steering-test",
secret_key="sk-steering-test",
host="http://127.0.0.1:1",
tracer_provider=TracerProvider(),
span_exporter=exporter,
)
logger.langfuse_sdk_version = installed_langfuse_version()
return logger, exporter
def _emit(logger: LangFuseLogger, *, metadata=None, headers=None):
"""``log_event_on_langfuse`` is the entry point that folds ``langfuse_*`` headers into metadata."""
def _emit(rig, *, metadata=None, headers=None):
"""``log_event_on_langfuse`` is the entry point that folds ``langfuse_*`` headers into metadata.
v4 has no trace object, so the trace-level fields are captured where the
callback hands them to propagation, and the observation fields are read back
off the span langfuse actually exported.
"""
from litellm.integrations.langfuse import langfuse as langfuse_module
logger, exporter = rig
exporter.clear()
captured_trace_params = {}
propagate_for_real = langfuse_module._trace_attributes_for_propagation
def capture(trace_params):
captured_trace_params.update(trace_params)
return propagate_for_real(trace_params)
now = datetime.datetime.now()
response_obj = litellm.ModelResponse(
choices=[{"message": {"role": "assistant", "content": "the-output"}}]
)
logger.log_event_on_langfuse(
kwargs={
"call_type": "completion",
"litellm_params": {
"metadata": dict(metadata or {}),
"proxy_server_request": {"headers": dict(headers or {})},
response_obj = litellm.ModelResponse(choices=[{"message": {"role": "assistant", "content": "the-output"}}])
with patch.object( # test-quality-ok: v4 has no trace object to read back; the propagation call is the only observable trace-level boundary
langfuse_module, "_trace_attributes_for_propagation", capture
):
logger.log_event_on_langfuse(
kwargs={
"call_type": "completion",
"litellm_params": {
"metadata": dict(metadata or {}),
"proxy_server_request": {"headers": dict(headers or {})},
},
"messages": [{"role": "user", "content": "the-input"}],
"optional_params": {},
},
"messages": [{"role": "user", "content": "the-input"}],
"optional_params": {},
},
response_obj=response_obj,
start_time=now,
end_time=now,
)
return (
logger.Langfuse.trace.call_args.kwargs,
logger.Langfuse.trace.return_value.generation.call_args.kwargs,
)
response_obj=response_obj,
start_time=now,
end_time=now,
)
logger.Langfuse.flush()
prefix = "langfuse.observation."
span = exporter.get_finished_spans()[-1]
generation_params = {
key[len(prefix) :]: value
for key, value in (span.attributes or {}).items()
if key.startswith(prefix) and not key.startswith(prefix + "metadata.")
}
return captured_trace_params, generation_params, span
def test_mask_input_header_false_keeps_the_prompt():
logger = _steering_logger()
rig = _steering_logger()
trace_params, generation_params = _emit(logger, headers={"langfuse_mask_input": "false"})
trace_params, generation_params, _ = _emit(rig, headers={"langfuse_mask_input": "false"})
assert trace_params["input"] == {"messages": [{"role": "user", "content": "the-input"}]}
assert generation_params["input"] == {"messages": [{"role": "user", "content": "the-input"}]}
assert json.loads(generation_params["input"]) == {"messages": [{"role": "user", "content": "the-input"}]}
def test_mask_input_header_true_redacts_the_prompt():
logger = _steering_logger()
rig = _steering_logger()
trace_params, generation_params = _emit(logger, headers={"langfuse_mask_input": "true"})
trace_params, generation_params, _ = _emit(rig, headers={"langfuse_mask_input": "true"})
assert trace_params["input"] == _LANGFUSE_REDACTED
assert generation_params["input"] == _LANGFUSE_REDACTED
def test_mask_output_header_false_keeps_the_completion():
logger = _steering_logger()
rig = _steering_logger()
trace_params, generation_params = _emit(logger, headers={"langfuse_mask_output": "false"})
trace_params, generation_params, _ = _emit(rig, headers={"langfuse_mask_output": "false"})
assert trace_params["output"] != _LANGFUSE_REDACTED
assert generation_params["output"] != _LANGFUSE_REDACTED
def test_mask_output_header_true_redacts_the_completion():
logger = _steering_logger()
rig = _steering_logger()
trace_params, generation_params = _emit(logger, headers={"langfuse_mask_output": "true"})
trace_params, generation_params, _ = _emit(rig, headers={"langfuse_mask_output": "true"})
assert trace_params["output"] == _LANGFUSE_REDACTED
assert generation_params["output"] == _LANGFUSE_REDACTED
@ -1387,20 +1481,20 @@ def test_mask_output_header_true_redacts_the_completion():
],
)
def test_mask_input_from_the_request_body_is_unchanged(mask_input, expect_redacted):
logger = _steering_logger()
rig = _steering_logger()
trace_params, _ = _emit(logger, metadata={"mask_input": mask_input})
trace_params, _, _ = _emit(rig, metadata={"mask_input": mask_input})
assert (trace_params["input"] == _LANGFUSE_REDACTED) is expect_redacted
@pytest.mark.parametrize("flag", [True, "true"])
def test_update_trace_keys_header_applies_every_key_when_enabled(flag):
logger = _steering_logger()
rig = _steering_logger()
with patch.object(litellm, "langfuse_enable_update_trace_keys", flag):
trace_params, _ = _emit(
logger,
trace_params, _, span = _emit(
rig,
headers={
"langfuse_existing_trace_id": "trace-1",
"langfuse_update_trace_keys": "trace_release, trace_tail",
@ -1411,6 +1505,9 @@ def test_update_trace_keys_header_applies_every_key_when_enabled(flag):
assert trace_params["release"] == "v1.2.3"
assert trace_params["tail"] == "last"
# v4 models release, so it reaches langfuse; a key it does not model cannot
assert span.attributes["langfuse.release"] == "v1.2.3"
assert not [key for key in span.attributes if key.endswith("tail")]
def test_update_trace_keys_is_off_by_default():
@ -1419,10 +1516,10 @@ def test_update_trace_keys_is_off_by_default():
user_api_key_auth and have the resolved auth object, including team callback
credentials, serialized onto the trace. It stays inert until an operator opts in.
"""
logger = _steering_logger()
rig = _steering_logger()
trace_params, _ = _emit(
logger,
trace_params, _, span = _emit(
rig,
metadata={
"existing_trace_id": "trace-1",
"update_trace_keys": ["user_api_key_auth", "trace_release"],
@ -1434,25 +1531,26 @@ def test_update_trace_keys_is_off_by_default():
assert "user_api_key_auth" not in trace_params
assert "release" not in trace_params
assert "sk-canary" not in json.dumps(trace_params, default=repr)
assert "sk-canary" not in json.dumps(dict(span.attributes or {}), default=repr)
def test_update_trace_keys_input_and_output_are_gated_too():
logger = _steering_logger()
rig = _steering_logger()
off, _ = _emit(logger, metadata={"existing_trace_id": "trace-1", "update_trace_keys": ["input", "output"]})
off, _, _ = _emit(rig, metadata={"existing_trace_id": "trace-1", "update_trace_keys": ["input", "output"]})
with patch.object(litellm, "langfuse_enable_update_trace_keys", True):
on, _ = _emit(logger, metadata={"existing_trace_id": "trace-1", "update_trace_keys": ["input", "output"]})
on, _, _ = _emit(rig, metadata={"existing_trace_id": "trace-1", "update_trace_keys": ["input", "output"]})
assert "input" not in off and "output" not in off
assert "input" in on and "output" in on
def test_update_trace_keys_from_the_request_body_list_applies_when_enabled():
logger = _steering_logger()
rig = _steering_logger()
with patch.object(litellm, "langfuse_enable_update_trace_keys", True):
trace_params, _ = _emit(
logger,
trace_params, _, span = _emit(
rig,
metadata={
"existing_trace_id": "trace-1",
"update_trace_keys": ["trace_release"],
@ -1461,13 +1559,14 @@ def test_update_trace_keys_from_the_request_body_list_applies_when_enabled():
)
assert trace_params["release"] == "v1.2.3"
assert span.attributes["langfuse.release"] == "v1.2.3"
def test_update_trace_keys_matches_whole_keys_not_substrings():
logger = _steering_logger()
rig = _steering_logger()
trace_params, _ = _emit(
logger,
trace_params, _, _ = _emit(
rig,
headers={"langfuse_existing_trace_id": "trace-1", "langfuse_update_trace_keys": "my_input"},
)
@ -1554,3 +1653,119 @@ def test_langfuse_deployment_environment_fallback_never_raises(monkeypatch, env_
langfuse_host="https://test.langfuse.com",
)
assert logger.langfuse_environment == expected
def test_continued_trace_keeps_the_generation_version():
"""v2 set ``version`` on the generation even when the trace was not being updated."""
rig = _steering_logger()
captured_trace_params, _, span = _emit(rig, metadata={"existing_trace_id": "b" * 32, "version": "gen-7"})
assert "version" not in captured_trace_params
assert span.attributes["langfuse.version"] == "gen-7"
def test_new_trace_version_takes_precedence_over_the_generation_version():
"""v4 has one ``version`` for the trace and its root observation; ``trace_version`` wins as in v2."""
rig = _steering_logger()
captured_trace_params, _, span = _emit(rig, metadata={"trace_version": "trace-1", "version": "gen-7"})
assert captured_trace_params["version"] == "trace-1"
assert span.attributes["langfuse.version"] == "trace-1"
def test_log_event_returns_the_v2_dict_shape_for_the_alerting_trace_id_cache():
"""litellm_logging only caches the langfuse trace id off a dict with a ``trace_id`` key.
Slack alerting builds its trace URL from that cache, so a different return
shape silently breaks alert links.
"""
rig = _steering_logger()
logger, _ = rig
returned = logger.log_event_on_langfuse(
kwargs={
"call_type": "completion",
"litellm_params": {"metadata": {"trace_id": "c" * 32}},
"messages": [{"role": "user", "content": "the-input"}],
"optional_params": {},
},
response_obj=litellm.ModelResponse(choices=[{"message": {"role": "assistant", "content": "the-output"}}]),
start_time=datetime.datetime.now(),
end_time=datetime.datetime.now(),
)
assert isinstance(returned, dict)
assert returned["trace_id"] == "c" * 32
assert returned["generation_id"]
def test_parse_langfuse_debug_only_enables_on_true_strings():
"""v4 treats any truthy value as debug=on, so the raw env string "false" would enable debug."""
assert langfuse_module.parse_langfuse_debug("true") is True
assert langfuse_module.parse_langfuse_debug("True") is True
assert langfuse_module.parse_langfuse_debug("1") is True
assert langfuse_module.parse_langfuse_debug("false") is False
assert langfuse_module.parse_langfuse_debug("False") is False
assert langfuse_module.parse_langfuse_debug("") is False
assert langfuse_module.parse_langfuse_debug(None) is False
def test_langfuse_debug_env_string_false_stays_off(monkeypatch):
"""LANGFUSE_DEBUG=false must not reach the v4 client as a truthy string.
The v4 client does ``if debug:`` and then mutates root logging via
``logging.basicConfig``, so the unparsed string "false" turns debug ON.
"""
from langfuse._client.resource_manager import LangfuseResourceManager
monkeypatch.setenv("LANGFUSE_PUBLIC_KEY", "pk-debug-parse-test")
monkeypatch.setenv("LANGFUSE_SECRET_KEY", "sk-debug-parse-test")
monkeypatch.setenv("LANGFUSE_MOCK", "true")
monkeypatch.setenv("LANGFUSE_DEBUG", "false")
monkeypatch.setattr(litellm, "initialized_langfuse_clients", litellm.initialized_langfuse_clients)
logger = LangFuseLogger()
try:
assert logger.langfuse_debug is False
finally:
LangfuseResourceManager._instances.pop("pk-debug-parse-test", None)
def test_explicit_langfuse_host_beats_the_v4_base_url_env(monkeypatch):
"""Per-key/per-team ``langfuse_host`` must win over LANGFUSE_BASE_URL.
v4 resolves ``base_url or $LANGFUSE_BASE_URL or host``, so passing the
resolved host as ``host=`` lets a stray env var silently redirect every
tenant's traces to one server.
"""
from langfuse._client.resource_manager import LangfuseResourceManager
monkeypatch.setenv("LANGFUSE_PUBLIC_KEY", "pk-base-url-test")
monkeypatch.setenv("LANGFUSE_SECRET_KEY", "sk-base-url-test")
monkeypatch.setenv("LANGFUSE_MOCK", "true")
monkeypatch.setenv("LANGFUSE_BASE_URL", "https://elsewhere.example")
monkeypatch.setattr(litellm, "initialized_langfuse_clients", litellm.initialized_langfuse_clients)
logger = LangFuseLogger(langfuse_host="https://good.example")
try:
assert logger.Langfuse._base_url == "https://good.example"
finally:
LangfuseResourceManager._instances.pop("pk-base-url-test", None)
def test_resolve_credentials_falls_back_to_langfuse_base_url(monkeypatch):
"""v4's canonical env var works when LANGFUSE_HOST is unset, but never beats it."""
monkeypatch.setenv("LANGFUSE_BASE_URL", "https://from-base-url.example")
monkeypatch.delenv("LANGFUSE_HOST", raising=False)
_, _, host = langfuse_module.resolve_langfuse_credentials()
assert host == "https://from-base-url.example"
monkeypatch.setenv("LANGFUSE_HOST", "https://from-host.example")
_, _, host = langfuse_module.resolve_langfuse_credentials()
assert host == "https://from-host.example"
_, _, host = langfuse_module.resolve_langfuse_credentials(langfuse_host="https://explicit.example")
assert host == "https://explicit.example"

View file

@ -2928,3 +2928,19 @@ def test_test_model_connection_accepts_image_edit_mode(monkeypatch):
assert response.status_code == 200, response.text
assert response.json()["status"] == "success"
@pytest.mark.asyncio
async def test_health_services_endpoint_langfuse_missing_keys_errors(monkeypatch):
"""A disabled v4 client returns False from auth_check instead of raising.
v2 raised out of ``auth_check`` on missing keys, so the endpoint errored;
the endpoint must not report success when the return value says the check
failed.
"""
for key in ("LANGFUSE_PUBLIC_KEY", "LANGFUSE_SECRET_KEY", "LANGFUSE_HOST", "LANGFUSE_BASE_URL", "LANGFUSE_MOCK"):
monkeypatch.delenv(key, raising=False)
monkeypatch.setattr(litellm, "initialized_langfuse_clients", litellm.initialized_langfuse_clients)
with pytest.raises(ProxyException, match="auth_check failed"):
await health_services_endpoint(service="langfuse")

309
uv.lock generated
View file

@ -10,7 +10,7 @@ resolution-markers = [
]
[options]
exclude-newer = "2026-08-29T17:58:57.633306Z"
exclude-newer = "2026-08-29T07:41:48.685791Z"
exclude-newer-span = "P3D"
[manifest]
@ -4122,21 +4122,22 @@ wheels = [
[[package]]
name = "langfuse"
version = "2.59.7"
version = "4.15.1"
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/c2/51/ed5569bc2dcc8fe767e3b315207a7511ad23d00d811f02bbf0d6f80bf906/langfuse-4.15.1.tar.gz", hash = "sha256:70cb47529a6ba78383c4f2a197eb3d3ab9d39529c9e6b568d392150c1f40dbb9", size = 432353, upload-time = "2026-08-28T07:55:15.894Z" }
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/2a/f9/7160bafcfe9797575359a9e77f810cf26f57f55a7c5ceda602fd9e353ddf/langfuse-4.15.1-py3-none-any.whl", hash = "sha256:795760693a62895157b6f5d6c80f1ad524fd12380995f06f82fc3a627b88c8d4", size = 789929, upload-time = "2026-08-28T07:55:14.034Z" },
]
[[package]]
@ -4266,7 +4267,7 @@ wheels = [
[[package]]
name = "litellm"
version = "1.101.0"
version = "1.100.0"
source = { editable = "." }
dependencies = [
{ name = "aiohttp" },
@ -4526,7 +4527,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" },
@ -4538,10 +4539,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 = "polars", marker = "extra == 'proxy'", specifier = ">=1.38.1,<2.0" },
{ name = "prisma", marker = "extra == 'extra-proxy'", specifier = ">=0.11.0,<1.0" },
@ -4607,7 +4608,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" },
@ -4616,12 +4617,12 @@ dev = [
{ name = "fakeredis", specifier = "==2.34.1" },
{ name = "fastapi-offline", specifier = "==1.7.6" },
{ name = "keyring", specifier = "==25.7.0" },
{ name = "langfuse", specifier = "==2.59.7" },
{ name = "langfuse", specifier = ">=4.7,<5.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" },
@ -4659,22 +4660,22 @@ 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" },
]
[[package]]
name = "litellm-enterprise"
version = "0.1.63"
version = "0.1.62"
source = { editable = "enterprise" }
[[package]]
name = "litellm-proxy-extras"
version = "0.4.92"
version = "0.4.91"
source = { editable = "litellm-proxy-extras" }
[[package]]
@ -5791,45 +5792,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" },
@ -5840,14 +5841,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" },
@ -5858,14 +5859,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" },
@ -5873,14 +5874,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" },
@ -5888,14 +5889,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" },
@ -5903,14 +5904,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" },
@ -5919,14 +5920,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" },
@ -5935,14 +5936,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" },
@ -5950,14 +5951,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" },
@ -5965,14 +5966,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" },
@ -5981,14 +5982,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" },
@ -5996,14 +5997,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" },
@ -6011,14 +6012,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" },
@ -6026,14 +6027,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" },
@ -6041,14 +6042,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" },
@ -6056,14 +6057,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" },
@ -6072,27 +6073,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" },
@ -6100,14 +6101,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" },
@ -6115,14 +6116,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" },
@ -6130,14 +6131,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" },
@ -6145,14 +6146,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" },
@ -6161,14 +6162,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" },
@ -6176,14 +6177,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" },
@ -6191,14 +6192,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" },
@ -6206,14 +6207,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" },
@ -6221,14 +6222,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" },
@ -6236,14 +6237,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" },
@ -6252,28 +6253,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" },
@ -6281,14 +6282,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" },
@ -6296,14 +6297,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" },
@ -6312,14 +6313,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" },
@ -6327,14 +6328,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" },
@ -6342,14 +6343,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" },
@ -6357,48 +6358,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]]
@ -6412,11 +6413,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]]
@ -9470,7 +9471,7 @@ wheels = [
[[package]]
name = "traceloop-sdk"
version = "0.33.12"
version = "0.34.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aiohttp" },
@ -9516,9 +9517,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]]