mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-27 01:22:18 +00:00
The callback's TracerProvider now sets its sampler, span limits and id generator explicitly so unrelated OTEL_* variables no longer change what Langfuse receives, and trace metadata is written once on the trace instead of folded into the generation, which kept input and output under the attribute cap. Spans are emitted under the langfuse-sdk scope so Langfuse renders them natively, the batch processor queues 100k spans and honors LANGFUSE_FLUSH_AT, and the proxy shutdown flush runs off the event loop with a 10s deadline and logs a miss. Prompts, auth_check and the project id now go through LangfuseAPI directly with a litellm-owned TTL cache, so no Langfuse() client is built and a host application's client on the same public key is left alone. Dead attributes, the unreachable exporter branch and the export list are cleaned up, and the client-budget eviction behavior is documented. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
122 lines
4.8 KiB
Python
122 lines
4.8 KiB
Python
import litellm
|
|
from litellm.integrations.langfuse.langfuse import resolve_langfuse_credentials
|
|
from litellm.integrations.langfuse.langfuse_handler import LangFuseHandler
|
|
|
|
|
|
def test_resolve_langfuse_credentials_does_not_use_env_for_dynamic_host(monkeypatch):
|
|
monkeypatch.setenv("LANGFUSE_PUBLIC_KEY", "global-public")
|
|
monkeypatch.setenv("LANGFUSE_SECRET_KEY", "global-secret")
|
|
|
|
public_key, secret_key, host = resolve_langfuse_credentials(
|
|
langfuse_host="https://attacker.example",
|
|
allow_env_credentials=False,
|
|
)
|
|
|
|
assert public_key is None
|
|
assert secret_key is None
|
|
assert host == "https://attacker.example"
|
|
|
|
|
|
def test_resolve_langfuse_credentials_accepts_secret_key_alias_for_dynamic_host(
|
|
monkeypatch,
|
|
):
|
|
monkeypatch.setenv("LANGFUSE_SECRET_KEY", "global-secret")
|
|
|
|
public_key, secret_key, host = resolve_langfuse_credentials(
|
|
langfuse_public_key="dynamic-public",
|
|
langfuse_secret_key="dynamic-secret",
|
|
langfuse_host="https://team-langfuse.example",
|
|
allow_env_credentials=False,
|
|
)
|
|
|
|
assert public_key == "dynamic-public"
|
|
assert secret_key == "dynamic-secret"
|
|
assert host == "https://team-langfuse.example"
|
|
|
|
|
|
def test_resolve_langfuse_credentials_keeps_env_for_global_config(monkeypatch):
|
|
monkeypatch.setenv("LANGFUSE_PUBLIC_KEY", "global-public")
|
|
monkeypatch.setenv("LANGFUSE_SECRET_KEY", "global-secret")
|
|
|
|
public_key, secret_key, host = resolve_langfuse_credentials(
|
|
langfuse_host="https://admin-configured.example",
|
|
allow_env_credentials=True,
|
|
)
|
|
|
|
assert public_key == "global-public"
|
|
assert secret_key == "global-secret"
|
|
assert host == "https://admin-configured.example"
|
|
|
|
|
|
def test_upstream_langfuse_env_only_warns_and_opens_no_second_channel(monkeypatch, caplog):
|
|
"""UPSTREAM_LANGFUSE_* configured a second v2 ingestion client. v4 has one export channel per
|
|
credential set, so the values are ignored with a startup warning and never build anything."""
|
|
from litellm.integrations.langfuse import langfuse_sdk
|
|
from litellm.integrations.langfuse.langfuse import LangFuseLogger
|
|
|
|
monkeypatch.setattr(litellm, "initialized_langfuse_clients", 0)
|
|
monkeypatch.setattr(langfuse_sdk, "_TRACING", {})
|
|
monkeypatch.setenv("LANGFUSE_MOCK", "true")
|
|
monkeypatch.setenv("UPSTREAM_LANGFUSE_SECRET_KEY", "upstream-secret")
|
|
monkeypatch.setenv("UPSTREAM_LANGFUSE_PUBLIC_KEY", "upstream-public")
|
|
monkeypatch.setenv("UPSTREAM_LANGFUSE_HOST", "https://upstream.example")
|
|
|
|
with caplog.at_level("WARNING", logger="LiteLLM"):
|
|
logger = LangFuseLogger(
|
|
langfuse_public_key="public",
|
|
langfuse_secret="secret",
|
|
langfuse_host="https://langfuse.example",
|
|
)
|
|
|
|
assert any("UPSTREAM_LANGFUSE_* is no longer supported" in record.getMessage() for record in caplog.records)
|
|
assert list(langfuse_sdk._TRACING.values()) == [logger.tracing]
|
|
assert all(key.public_key == "public" for key in langfuse_sdk._TRACING)
|
|
|
|
|
|
def test_langfuse_handler_accepts_secret_key_alias(monkeypatch):
|
|
captured = {}
|
|
|
|
class FakeLangFuseLogger:
|
|
def __init__(
|
|
self,
|
|
*,
|
|
langfuse_public_key=None,
|
|
langfuse_secret=None,
|
|
langfuse_host=None,
|
|
langfuse_environment=None,
|
|
allow_env_credentials=True,
|
|
):
|
|
captured["langfuse_public_key"] = langfuse_public_key
|
|
captured["langfuse_secret"] = langfuse_secret
|
|
captured["langfuse_host"] = langfuse_host
|
|
captured["langfuse_environment"] = langfuse_environment
|
|
captured["allow_env_credentials"] = allow_env_credentials
|
|
|
|
class FakeDynamicLoggingCache:
|
|
def set_cache(self, *, credentials, service_name, logging_obj):
|
|
captured["cached_credentials"] = credentials
|
|
captured["cached_service_name"] = service_name
|
|
captured["cached_logging_obj"] = logging_obj
|
|
|
|
monkeypatch.setattr(
|
|
"litellm.integrations.langfuse.langfuse_handler.LangFuseLogger",
|
|
FakeLangFuseLogger,
|
|
)
|
|
|
|
logger = LangFuseHandler._create_langfuse_logger_from_credentials(
|
|
credentials={
|
|
"langfuse_public_key": "dynamic-public",
|
|
"langfuse_secret_key": "dynamic-secret",
|
|
"langfuse_host": "https://langfuse.example",
|
|
"langfuse_environment": "dynamic-environment",
|
|
},
|
|
in_memory_dynamic_logger_cache=FakeDynamicLoggingCache(),
|
|
)
|
|
|
|
assert captured["langfuse_public_key"] == "dynamic-public"
|
|
assert captured["langfuse_secret"] == "dynamic-secret"
|
|
assert captured["langfuse_host"] == "https://langfuse.example"
|
|
assert captured["langfuse_environment"] == "dynamic-environment"
|
|
assert captured["allow_env_credentials"] is False
|
|
assert captured["cached_service_name"] == "langfuse"
|
|
assert captured["cached_logging_obj"] is logger
|