mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-01 02:02:20 +00:00
fix(langfuse): honor LANGFUSE_DEBUG on the callbacks path, cap retry backoff, name the auth failure status and body
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
6b41d4ece1
commit
0b0e309b3b
4 changed files with 60 additions and 7 deletions
|
|
@ -302,7 +302,7 @@ class LangFuseLogger:
|
|||
) from e
|
||||
raise_if_unsupported_langfuse_version(self.langfuse_sdk_version)
|
||||
raise_if_unusable_prompt_cache_ttl()
|
||||
from litellm.integrations.langfuse.langfuse_sdk import configured_release, enable_langfuse_debug_logging
|
||||
from litellm.integrations.langfuse.langfuse_sdk import configured_release
|
||||
|
||||
self.public_key, self.secret_key, self.langfuse_host = resolve_langfuse_credentials(
|
||||
langfuse_public_key=langfuse_public_key,
|
||||
|
|
@ -318,8 +318,6 @@ class LangFuseLogger:
|
|||
self.langfuse_environment = self.resolve_deployment_environment()
|
||||
self.langfuse_release = configured_release()
|
||||
self.langfuse_debug = parse_langfuse_debug(os.getenv("LANGFUSE_DEBUG"))
|
||||
if self.langfuse_debug:
|
||||
enable_langfuse_debug_logging()
|
||||
self.langfuse_flush_interval = LangFuseLogger._get_langfuse_flush_interval(flush_interval)
|
||||
|
||||
if should_use_langfuse_mock():
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import httpx
|
|||
import opentelemetry.trace as otel_trace
|
||||
from langfuse import LangfuseOtelSpanAttributes
|
||||
from langfuse.api import LangfuseAPI, Prompt, Prompt_Chat
|
||||
from langfuse.api.core.api_error import ApiError
|
||||
from langfuse.model import BasePromptClient, ChatPromptClient, PromptClient, TextPromptClient
|
||||
from opentelemetry.context import Context
|
||||
from opentelemetry.exporter.otlp.proto.common.trace_encoder import encode_spans
|
||||
|
|
@ -30,10 +31,11 @@ from opentelemetry.sdk.trace.id_generator import RandomIdGenerator
|
|||
from opentelemetry.sdk.trace.sampling import ALWAYS_ON, Decision, Sampler, SamplingResult
|
||||
from opentelemetry.trace import Link, NonRecordingSpan, Span, SpanContext, SpanKind, TraceFlags, Tracer, TraceState
|
||||
from opentelemetry.util.types import Attributes, AttributeValue
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
import litellm
|
||||
from litellm._logging import verbose_logger
|
||||
from litellm.integrations.langfuse.langfuse import PROMPT_CACHE_TTL_ENV, whole_number
|
||||
from litellm.integrations.langfuse.langfuse import PROMPT_CACHE_TTL_ENV, parse_langfuse_debug, whole_number
|
||||
from litellm.litellm_core_utils.safe_json_dumps import safe_dumps
|
||||
from litellm.llms.custom_httpx.http_handler import HTTPHandler, _get_httpx_client
|
||||
|
||||
|
|
@ -80,6 +82,7 @@ _DEFAULT_FLUSH_AT: Final = 512
|
|||
_CHANNEL_RETIRE_GRACE_SECONDS: Final = 60.0
|
||||
_DEFAULT_TIMEOUT_SECONDS: Final = 20.0
|
||||
_DEFAULT_MAX_RETRIES: Final = 3
|
||||
_MAX_BACKOFF_EXPONENT: Final = 6
|
||||
_DEFAULT_PROMPT_CACHE_TTL_SECONDS: Final = 60.0
|
||||
_JSON_SAFE_INT: Final = 2**53 - 1
|
||||
_COMMON_RELEASE_ENVS: Final = (
|
||||
|
|
@ -686,7 +689,7 @@ def _build_span_exporter(*, public_key: str, secret_key: str, base_url: str) ->
|
|||
}
|
||||
),
|
||||
timeout=configured_timeout(),
|
||||
delays=tuple(2.0**attempt for attempt in range(configured_max_retries())),
|
||||
delays=tuple(2.0 ** min(attempt, _MAX_BACKOFF_EXPONENT) for attempt in range(configured_max_retries())),
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -782,6 +785,8 @@ def acquire_langfuse_tracing(
|
|||
A provider owns a batch export thread, so a channel lives while any logger holds it and is
|
||||
retired through ``release_langfuse_tracing`` once the last holder lets go.
|
||||
"""
|
||||
if parse_langfuse_debug(os.getenv("LANGFUSE_DEBUG")):
|
||||
enable_langfuse_debug_logging()
|
||||
key: Final = _TracingKey(
|
||||
public_key=public_key,
|
||||
secret_key=secret_key,
|
||||
|
|
@ -942,6 +947,19 @@ def _auth_check_failure(reason: str) -> AuthCheckFailure:
|
|||
return AuthCheckFailure(reason)
|
||||
|
||||
|
||||
class _ApiErrorDetail(BaseModel):
|
||||
"""The status and body of an ``ApiError``, whose own ``str`` also dumps every response header."""
|
||||
|
||||
model_config = ConfigDict(frozen=True, from_attributes=True)
|
||||
status_code: int | None
|
||||
body: object
|
||||
|
||||
|
||||
def _api_error_reason(error: ApiError) -> str:
|
||||
detail: Final = _ApiErrorDetail.model_validate(error)
|
||||
return f"status_code: {detail.status_code}, body: {detail.body}"
|
||||
|
||||
|
||||
class LangfuseApiClient:
|
||||
"""litellm's handle on one Langfuse project over its REST API: prompts, ``auth_check`` and the project id.
|
||||
|
||||
|
|
@ -973,7 +991,9 @@ class LangfuseApiClient:
|
|||
"""
|
||||
try:
|
||||
projects: Final = self.api.projects.get().data
|
||||
except Exception as error: # noqa: BLE001 # ApiError, httpx transport errors or a body the response model rejects
|
||||
except ApiError as error:
|
||||
return _auth_check_failure(_api_error_reason(error))
|
||||
except Exception as error: # noqa: BLE001 # httpx transport errors or a body the response model rejects
|
||||
return _auth_check_failure(str(error) or type(error).__name__)
|
||||
if not projects:
|
||||
return _auth_check_failure("no project found for the keys provided")
|
||||
|
|
|
|||
|
|
@ -190,6 +190,31 @@ def test_langfuse_client_init_mock_mode_makes_no_network_calls(monkeypatch):
|
|||
assert received == [], f"LANGFUSE_MOCK still sent spans to the configured host: {received}"
|
||||
|
||||
|
||||
def test_langfuse_debug_reaches_the_export_channel_through_the_registered_callback(monkeypatch):
|
||||
"""The registry maps ``langfuse`` to this class, whose constructor never runs ``LangFuseLogger.__init__``,
|
||||
so wiring ``LANGFUSE_DEBUG`` only there left the flag a no-op on the YAML callback path."""
|
||||
import logging
|
||||
|
||||
from litellm.integrations.langfuse.langfuse_sdk import release_langfuse_tracing
|
||||
|
||||
monkeypatch.setenv("LANGFUSE_MOCK", "true")
|
||||
monkeypatch.setenv("LANGFUSE_HOST", "http://127.0.0.1:1")
|
||||
monkeypatch.setenv("LANGFUSE_PUBLIC_KEY", "pk-pm-debug-wire")
|
||||
monkeypatch.setenv("LANGFUSE_SECRET_KEY", "sk-pm-debug-wire")
|
||||
monkeypatch.setenv("LANGFUSE_DEBUG", "true")
|
||||
langfuse_client_init.cache_clear()
|
||||
langfuse_logger: Final = logging.getLogger("langfuse")
|
||||
level_before: Final = langfuse_logger.level
|
||||
langfuse_logger.setLevel(logging.WARNING)
|
||||
try:
|
||||
logger = LangfusePromptManagement()
|
||||
assert langfuse_logger.level == logging.DEBUG
|
||||
release_langfuse_tracing(logger.tracing, grace_seconds=0.0)
|
||||
finally:
|
||||
langfuse_logger.setLevel(level_before)
|
||||
langfuse_client_init.cache_clear()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_log_failure_event_records_trace_id_for_alerting(monkeypatch):
|
||||
from litellm.integrations.langfuse.langfuse_sdk import resolve_trace_id
|
||||
|
|
|
|||
|
|
@ -1004,7 +1004,7 @@ def test_auth_check_names_the_servers_rejection(caplog):
|
|||
with caplog.at_level(logging.WARNING, logger="LiteLLM"):
|
||||
failure = client.auth_check()
|
||||
assert failure is not None
|
||||
assert "status_code: 401" in failure.reason and "unauthorized" in failure.reason
|
||||
assert failure.reason == "status_code: 401, body: {'message': 'unauthorized'}"
|
||||
assert failure.reason in caplog.text
|
||||
|
||||
|
||||
|
|
@ -1306,6 +1306,16 @@ def test_built_exporter_uses_the_shared_litellm_handler_and_langfuse_headers(mon
|
|||
assert exporter.headers["x-langfuse-ingestion-version"] == "4"
|
||||
|
||||
|
||||
def test_large_retry_count_builds_an_exporter_with_capped_backoff(monkeypatch):
|
||||
"""``LANGFUSE_MAX_RETRIES=1025`` constructed a v2 client; here ``2.0**1024`` would raise ``OverflowError``
|
||||
and take the whole callback down at init."""
|
||||
monkeypatch.setenv("LANGFUSE_MAX_RETRIES", "1025")
|
||||
exporter = _build_span_exporter(public_key="pk", secret_key="sk", base_url="https://lf.internal.example")
|
||||
assert len(exporter.delays) == 1025
|
||||
assert exporter.delays[:4] == (1.0, 2.0, 4.0, 8.0)
|
||||
assert max(exporter.delays) == exporter.delays[-1] <= 64.0
|
||||
|
||||
|
||||
def test_enable_langfuse_debug_logging_makes_deliveries_visible_on_the_langfuse_logger(caplog):
|
||||
"""``LANGFUSE_DEBUG`` turned on the v2 SDK's own logger; it has to do the same for litellm's export channel."""
|
||||
exporter, _ = _exporter_over([200])
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue