fix(langfuse): hand the SDK client a validated sample rate so an unusable LANGFUSE_SAMPLE_RATE no longer breaks the callback
Some checks failed
LiteLLM Rust / rust-lint (push) Waiting to run
LiteLLM Rust / rust-test (push) Waiting to run
LiteLLM Rust / rust-wheel (push) Waiting to run
Terraform Provider / gofmt, vet, build, test (push) Has been cancelled
Terraform Provider / Provider endpoints vs proxy OpenAPI schema (push) Has been cancelled

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
yucheng 2026-09-17 22:06:05 +00:00
parent 2f33397626
commit 58e97f55c0
2 changed files with 37 additions and 0 deletions

View file

@ -395,6 +395,12 @@ def _parse_sample_rate(raw: str) -> float | None:
return rate if 0.0 <= rate <= 1.0 else None
def _usable_sample_rate() -> float:
raw: Final = os.environ.get("LANGFUSE_SAMPLE_RATE")
parsed: Final = _parse_sample_rate(raw) if raw is not None else 1.0
return 1.0 if parsed is None else parsed
def configured_sample_rate() -> float:
"""``LANGFUSE_SAMPLE_RATE`` as a fraction, exporting everything when it is unset or unusable."""
raw: Final = os.environ.get("LANGFUSE_SAMPLE_RATE")
@ -633,6 +639,9 @@ def build_langfuse_client(
That same cache keeps the first secret and host it saw for a public key, so the REST client
behind ``get_prompt`` and ``auth_check`` is rebuilt from the credentials actually supplied.
Without both keys the SDK disables the client, which has no REST client to rebuild.
The SDK reads ``LANGFUSE_SAMPLE_RATE`` itself and raises on anything it cannot parse, so it
gets the rate litellm already validated; the sampler that matters is on litellm's provider.
"""
public_key: Final = parameters.get("public_key")
secret_key: Final = parameters.get("secret_key")
@ -641,6 +650,7 @@ def build_langfuse_client(
credentialed: Final = isinstance(public_key, str) and isinstance(secret_key, str)
client: Final = Langfuse(
**parameters, # pyright: ignore[reportArgumentType] # kwargs-ok: dict mirrors the typed ctor, values resolved by the callers
sample_rate=_usable_sample_rate(),
tracer_provider=TracerProvider(
resource=_resource(environment=environment, release=release), shutdown_on_exit=False
),

View file

@ -555,6 +555,33 @@ def test_sdk_client_without_keys_is_built_disabled_and_fails_auth_check(monkeypa
assert client.auth_check() is False
@pytest.mark.parametrize("raw", ["1.5", "-0.5", "abc"])
def test_sdk_client_is_built_despite_an_unusable_sample_rate(monkeypatch: pytest.MonkeyPatch, raw: str):
"""The SDK parses ``LANGFUSE_SAMPLE_RATE`` itself and would raise, which took the whole callback down."""
monkeypatch.setenv("LANGFUSE_SAMPLE_RATE", raw)
requests = []
def record(request: httpx.Request) -> httpx.Response:
requests.append(request)
return httpx.Response(401, json={"message": "unauthorized"})
client = build_langfuse_client(
parameters={
"public_key": "pk-sr-test-" + raw,
"secret_key": "sk",
"base_url": "http://127.0.0.1:1",
"httpx_client": httpx.Client(transport=httpx.MockTransport(record)),
},
environment=None,
release=None,
mock_mode=True,
)
with pytest.raises(UnauthorizedError):
client.auth_check()
assert requests[-1].headers["authorization"] == "Basic " + b64encode(f"pk-sr-test-{raw}:sk".encode()).decode()
def test_sdk_client_does_not_take_over_the_process_tracer_provider():
provider_before = otel_trace.get_tracer_provider()
build_langfuse_client(