fix(langfuse_otel): read string mask flags the way the classic callback does
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) Waiting to run
Terraform Provider / Provider endpoints vs proxy OpenAPI schema (push) Waiting to run
Terraform Modules / fmt, validate, test (aws) (push) Has been cancelled
Terraform Modules / fmt, validate, test (gcp) (push) Has been cancelled

The langfuse_mask_input / langfuse_mask_output headers arrive as strings,
so "false" redacted the derived trace io. Parse them with str_to_bool and
fall back to truthiness, as the classic langfuse callback does
This commit is contained in:
yucheng 2026-10-15 08:22:26 -07:00
parent 63f1722089
commit 1106e25f2e
3 changed files with 32 additions and 2 deletions

View file

@ -16,6 +16,7 @@ from litellm.integrations.langfuse.langfuse_otel_attributes import (
from litellm.integrations.opentelemetry import OpenTelemetry, OpenTelemetryConfig
from litellm.litellm_core_utils.safe_json_dumps import safe_dumps
from litellm.litellm_core_utils.safe_json_loads import safe_json_loads
from litellm.secret_managers.main import str_to_bool
from litellm.types.integrations.langfuse_otel import (
LangfuseSpanAttributes,
)
@ -54,6 +55,12 @@ def _preset_cache_key(kwargs: Mapping[str, object]) -> object:
return litellm.cache.get_preset_cache_key_from_kwargs(**kwargs)
def _mask_flag(value: object) -> bool:
"""A mask flag as the classic langfuse callback reads it: "true" / "false" strings parse, anything else by truthiness."""
parsed: Final = str_to_bool(value) if isinstance(value, str) else None
return bool(value) if parsed is None else parsed
def _caller_tag_list(value: object) -> tuple[str, ...]:
"""Caller tags as Langfuse ingestion reads a raw tags value: a list, a JSON array string, a comma list or one tag."""
parsed: Final = (
@ -248,7 +255,7 @@ class LangfuseOtelLogger(OpenTelemetry):
safe_set_attribute(
span,
LangfuseSpanAttributes.TRACE_INPUT.value,
REDACTED_BY_LITELLM if metadata.get("mask_input") else input_json,
REDACTED_BY_LITELLM if _mask_flag(metadata.get("mask_input")) else input_json,
)
output_json: Final = LangfuseOtelLogger._observation_output(response_obj)
@ -258,7 +265,7 @@ class LangfuseOtelLogger(OpenTelemetry):
safe_set_attribute(
span,
LangfuseSpanAttributes.TRACE_OUTPUT.value,
REDACTED_BY_LITELLM if metadata.get("mask_output") else output_json,
REDACTED_BY_LITELLM if _mask_flag(metadata.get("mask_output")) else output_json,
)
@staticmethod

View file

@ -337,6 +337,26 @@ def test_mask_flags_redact_the_derived_trace_io(v1: Rig) -> None:
assert attributes.get("langfuse.trace.name") == "litellm-acompletion", attributes
@pytest.mark.parametrize(
("headers", "masked"),
[
pytest.param({"langfuse_mask_input": "true", "langfuse_mask_output": "true"}, True, id="true"),
pytest.param({"langfuse_mask_input": "false", "langfuse_mask_output": "false"}, False, id="false"),
],
)
def test_mask_flag_headers_parse_as_booleans(v1: Rig, headers: Mapping[str, str], masked: bool) -> None:
marker: Final = "lit8281-mask-header-" + uuid.uuid4().hex
_chat(v1, marker, headers=headers)
attributes: Final = v1.sink.one(marker).attributes
expected: Final = (
(REDACTED, REDACTED)
if masked
else (attributes.get("langfuse.observation.input"), attributes.get("langfuse.observation.output"))
)
assert (attributes.get("langfuse.trace.input"), attributes.get("langfuse.trace.output")) == expected, attributes
assert marker in str(attributes.get("langfuse.observation.input")), attributes
@pytest.mark.parametrize(
"tags",
[

View file

@ -1127,6 +1127,9 @@ class TestDerivedTraceFields:
({"mask_input": True}, {"langfuse.trace.input"}),
({"mask_output": True}, {"langfuse.trace.output"}),
({"mask_input": True, "mask_output": True}, {"langfuse.trace.input", "langfuse.trace.output"}),
({"mask_input": "true", "mask_output": "TRUE"}, {"langfuse.trace.input", "langfuse.trace.output"}),
({"mask_input": "false", "mask_output": "False"}, set()),
({"mask_input": False, "mask_output": None}, set()),
],
)
def test_mask_flags_redact_the_derived_trace_io(self, mask_flags, masked_trace_keys):