fix: Langfuse otel handle
This commit is contained in:
parent
951e3284ed
commit
4d86236cd6
@ -8,9 +8,8 @@ from litellm.integrations.arize import _utils
|
||||
from litellm.integrations.langfuse.langfuse_otel_attributes import (
|
||||
LangfuseLLMObsOTELAttributes,
|
||||
)
|
||||
from litellm.integrations.opentelemetry import OpenTelemetry
|
||||
from litellm.integrations.opentelemetry import OpenTelemetry, OpenTelemetryConfig
|
||||
from litellm.types.integrations.langfuse_otel import (
|
||||
LangfuseOtelConfig,
|
||||
LangfuseSpanAttributes,
|
||||
)
|
||||
from litellm.types.utils import StandardCallbackDynamicParams
|
||||
@ -313,7 +312,7 @@ class LangfuseOtelLogger(OpenTelemetry):
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_langfuse_otel_config() -> LangfuseOtelConfig:
|
||||
def get_langfuse_otel_config() -> "OpenTelemetryConfig":
|
||||
"""
|
||||
Retrieves the Langfuse OpenTelemetry configuration based on environment variables.
|
||||
|
||||
@ -323,7 +322,7 @@ class LangfuseOtelLogger(OpenTelemetry):
|
||||
LANGFUSE_HOST: Optional. Custom Langfuse host URL. Defaults to US cloud.
|
||||
|
||||
Returns:
|
||||
LangfuseOtelConfig: A Pydantic model containing Langfuse OTEL configuration.
|
||||
OpenTelemetryConfig: A Pydantic model containing Langfuse OTEL configuration.
|
||||
|
||||
Raises:
|
||||
ValueError: If required keys are missing.
|
||||
@ -359,8 +358,10 @@ class LangfuseOtelLogger(OpenTelemetry):
|
||||
# os.environ["OTEL_EXPORTER_OTLP_ENDPOINT"] = endpoint
|
||||
# os.environ["OTEL_EXPORTER_OTLP_HEADERS"] = otlp_auth_headers
|
||||
|
||||
return LangfuseOtelConfig(
|
||||
otlp_auth_headers=otlp_auth_headers, protocol="otlp_http"
|
||||
return OpenTelemetryConfig(
|
||||
exporter="otlp_http",
|
||||
endpoint=endpoint,
|
||||
headers=otlp_auth_headers,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
||||
@ -665,7 +665,10 @@ class OpenTelemetry(CustomLogger):
|
||||
kwargs, response_obj, start_time, end_time, span
|
||||
)
|
||||
# Ensure proxy-request parent span is annotated with the actual operation kind
|
||||
if parent_span is not None and parent_span.name == LITELLM_PROXY_REQUEST_SPAN_NAME:
|
||||
if (
|
||||
parent_span is not None
|
||||
and parent_span.name == LITELLM_PROXY_REQUEST_SPAN_NAME
|
||||
):
|
||||
self.set_attributes(parent_span, kwargs, response_obj)
|
||||
else:
|
||||
# Do not create primary span (keep hierarchy shallow when parent exists)
|
||||
@ -994,10 +997,13 @@ class OpenTelemetry(CustomLogger):
|
||||
# TODO: Refactor to use the proper OTEL Logs API instead of directly creating SDK LogRecords
|
||||
|
||||
from opentelemetry._logs import SeverityNumber, get_logger, get_logger_provider
|
||||
|
||||
try:
|
||||
from opentelemetry.sdk._logs import LogRecord as SdkLogRecord # type: ignore[attr-defined] # OTEL < 1.39.0
|
||||
except ImportError:
|
||||
from opentelemetry.sdk._logs._internal import LogRecord as SdkLogRecord # OTEL >= 1.39.0
|
||||
from opentelemetry.sdk._logs._internal import (
|
||||
LogRecord as SdkLogRecord,
|
||||
) # OTEL >= 1.39.0
|
||||
|
||||
otel_logger = get_logger(LITELLM_LOGGER_NAME)
|
||||
|
||||
@ -1708,6 +1714,7 @@ class OpenTelemetry(CustomLogger):
|
||||
|
||||
def set_raw_request_attributes(self, span: Span, kwargs, response_obj):
|
||||
try:
|
||||
self.set_attributes(span, kwargs, response_obj)
|
||||
kwargs.get("optional_params", {})
|
||||
litellm_params = kwargs.get("litellm_params", {}) or {}
|
||||
custom_llm_provider = litellm_params.get("custom_llm_provider", "Unknown")
|
||||
|
||||
@ -1,8 +1,34 @@
|
||||
from typing import Dict, Optional
|
||||
|
||||
from litellm.secret_managers.main import get_secret_str
|
||||
from litellm.types.utils import StandardCallbackDynamicParams
|
||||
|
||||
# Hardcoded list of supported callback params to avoid runtime inspection issues with TypedDict
|
||||
_supported_callback_params = [
|
||||
"langfuse_public_key",
|
||||
"langfuse_secret",
|
||||
"langfuse_secret_key",
|
||||
"langfuse_host",
|
||||
"langfuse_prompt_version",
|
||||
"gcs_bucket_name",
|
||||
"gcs_path_service_account",
|
||||
"langsmith_api_key",
|
||||
"langsmith_project",
|
||||
"langsmith_base_url",
|
||||
"langsmith_sampling_rate",
|
||||
"langsmith_tenant_id",
|
||||
"humanloop_api_key",
|
||||
"arize_api_key",
|
||||
"arize_space_key",
|
||||
"arize_space_id",
|
||||
"posthog_api_key",
|
||||
"posthog_host",
|
||||
"braintrust_api_key",
|
||||
"braintrust_project",
|
||||
"braintrust_host",
|
||||
"slack_webhook_url",
|
||||
"lunary_public_key",
|
||||
]
|
||||
|
||||
|
||||
def initialize_standard_callback_dynamic_params(
|
||||
kwargs: Optional[Dict] = None,
|
||||
@ -15,13 +41,10 @@ def initialize_standard_callback_dynamic_params(
|
||||
|
||||
standard_callback_dynamic_params = StandardCallbackDynamicParams()
|
||||
if kwargs:
|
||||
_supported_callback_params = (
|
||||
StandardCallbackDynamicParams.__annotations__.keys()
|
||||
)
|
||||
|
||||
# 1. Check top-level kwargs
|
||||
for param in _supported_callback_params:
|
||||
if param in kwargs:
|
||||
_param_value = kwargs.pop(param)
|
||||
_param_value = kwargs.get(param)
|
||||
if (
|
||||
_param_value is not None
|
||||
and isinstance(_param_value, str)
|
||||
@ -30,4 +53,22 @@ def initialize_standard_callback_dynamic_params(
|
||||
_param_value = get_secret_str(secret_name=_param_value)
|
||||
standard_callback_dynamic_params[param] = _param_value # type: ignore
|
||||
|
||||
# 2. Fallback: check "metadata" or "litellm_params" -> "metadata"
|
||||
metadata = (kwargs.get("metadata") or {}).copy()
|
||||
litellm_params = kwargs.get("litellm_params") or {}
|
||||
if isinstance(litellm_params, dict):
|
||||
metadata.update(litellm_params.get("metadata") or {})
|
||||
|
||||
if isinstance(metadata, dict):
|
||||
for param in _supported_callback_params:
|
||||
if param not in standard_callback_dynamic_params and param in metadata:
|
||||
_param_value = metadata.get(param)
|
||||
if (
|
||||
_param_value is not None
|
||||
and isinstance(_param_value, str)
|
||||
and "os.environ/" in _param_value
|
||||
):
|
||||
_param_value = get_secret_str(secret_name=_param_value)
|
||||
standard_callback_dynamic_params[param] = _param_value # type: ignore
|
||||
|
||||
return standard_callback_dynamic_params
|
||||
|
||||
52
tests/logging_callback_tests/test_dynamic_otel_keys.py
Normal file
52
tests/logging_callback_tests/test_dynamic_otel_keys.py
Normal file
@ -0,0 +1,52 @@
|
||||
import sys
|
||||
import os
|
||||
|
||||
sys.path.insert(0, os.path.abspath("../.."))
|
||||
|
||||
from litellm.litellm_core_utils.initialize_dynamic_callback_params import (
|
||||
initialize_standard_callback_dynamic_params,
|
||||
)
|
||||
|
||||
|
||||
def test_dynamic_key_extraction_from_metadata():
|
||||
"""
|
||||
Test extraction of langfuse keys from metadata in kwargs.
|
||||
This simulates a Proxy request where keys are passed in metadata.
|
||||
"""
|
||||
kwargs = {
|
||||
"metadata": {
|
||||
"langfuse_public_key": "pk-test",
|
||||
"langfuse_secret_key": "sk-test",
|
||||
"langfuse_host": "https://test.langfuse.com",
|
||||
}
|
||||
}
|
||||
|
||||
params = initialize_standard_callback_dynamic_params(kwargs)
|
||||
|
||||
assert params.get("langfuse_public_key") == "pk-test"
|
||||
assert params.get("langfuse_secret_key") == "sk-test"
|
||||
assert params.get("langfuse_host") == "https://test.langfuse.com"
|
||||
|
||||
|
||||
def test_dynamic_key_extraction_from_litellm_params_metadata():
|
||||
"""
|
||||
Test extraction of langfuse keys from litellm_params.metadata.
|
||||
"""
|
||||
kwargs = {
|
||||
"litellm_params": {
|
||||
"metadata": {
|
||||
"langfuse_public_key": "pk-litellm",
|
||||
"langfuse_secret_key": "sk-litellm",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
params = initialize_standard_callback_dynamic_params(kwargs)
|
||||
|
||||
assert params.get("langfuse_public_key") == "pk-litellm"
|
||||
assert params.get("langfuse_secret_key") == "sk-litellm"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_dynamic_key_extraction_from_metadata()
|
||||
test_dynamic_key_extraction_from_litellm_params_metadata()
|
||||
Loading…
Reference in New Issue
Block a user