fix(langfuse): write the metadata truncation marker under a flattened key so Langfuse keeps it

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
yucheng 2026-09-23 06:53:23 +00:00
parent da3544653d
commit 3f2e013eea
2 changed files with 20 additions and 5 deletions

View file

@ -76,10 +76,11 @@ _LANGFUSE_INGESTION_VERSION_HEADER: Final = "x-langfuse-ingestion-version"
_LANGFUSE_INGESTION_VERSION: Final = "4"
_NO_REST_RETRIES: Final = RequestOptions(max_retries=0)
_TRUNCATION_MARKER: Final = "<truncated due to size exceeding limit>"
_METADATA_PREFIXES: Final = (LangfuseOtelSpanAttributes.OBSERVATION_METADATA, LangfuseOtelSpanAttributes.TRACE_METADATA)
_TRUNCATION_GROUPS: Final = (
(LangfuseOtelSpanAttributes.OBSERVATION_INPUT, LangfuseOtelSpanAttributes.TRACE_INPUT),
(LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT, LangfuseOtelSpanAttributes.TRACE_OUTPUT),
(LangfuseOtelSpanAttributes.OBSERVATION_METADATA, LangfuseOtelSpanAttributes.TRACE_METADATA),
_METADATA_PREFIXES,
)
_SERVER_FLOOR_HINT: Final = (
"; the OTLP traces route needs a self-hosted Langfuse server on 3.63.0 or newer "
@ -605,6 +606,11 @@ def _group_size(attributes: Mapping[str, AttributeValue], group: tuple[str, ...]
)
def _marker_key(prefix: str) -> str:
"""Langfuse reads input and output as one string but metadata only as flattened keys, so the marker gets one."""
return f"{prefix}.truncated" if prefix in _METADATA_PREFIXES else prefix
def _truncated(span: ReadableSpan) -> ReadableSpan | None:
"""The span with its largest remaining input, output or metadata replaced by the marker the v2 consumer wrote
when an event went over ``LANGFUSE_MAX_EVENT_SIZE_BYTES``, or ``None`` once all three are gone."""
@ -614,7 +620,9 @@ def _truncated(span: ReadableSpan) -> ReadableSpan | None:
return None
kept: Final = {key: value for key, value in attributes.items() if not _in_group(key, largest)}
marked: Final = {
prefix: _TRUNCATION_MARKER for prefix in largest if any(_in_group(key, (prefix,)) for key in attributes)
_marker_key(prefix): _TRUNCATION_MARKER
for prefix in largest
if any(_in_group(key, (prefix,)) for key in attributes)
}
return ReadableSpan(
name=span.name,

View file

@ -1313,13 +1313,15 @@ def test_exporter_truncates_a_single_oversized_span_the_way_v2_did_instead_of_dr
def test_exporter_truncates_largest_first_and_drops_only_when_nothing_is_left(monkeypatch, caplog):
"""Langfuse stores a bare ``langfuse.observation.metadata`` string as nothing, so the metadata marker travels
under a flattened key the way every other metadata value does."""
monkeypatch.setattr("litellm.integrations.langfuse.langfuse_sdk.sleep", lambda _: None)
span = _generation_span(
**{
"langfuse.observation.input": "i" * 3000,
"langfuse.observation.output": "o" * 2000,
"langfuse.observation.metadata.a": "m" * 500,
"langfuse.observation.metadata.b": "m" * 500,
"langfuse.trace.metadata.b": "m" * 500,
}
)
posted: list[dict[str, str]] = []
@ -1343,9 +1345,14 @@ def test_exporter_truncates_largest_first_and_drops_only_when_nothing_is_left(mo
[],
["langfuse.observation.input"],
["langfuse.observation.input", "langfuse.observation.output"],
["langfuse.observation.input", "langfuse.observation.metadata", "langfuse.observation.output"],
[
"langfuse.observation.input",
"langfuse.observation.metadata.truncated",
"langfuse.observation.output",
"langfuse.trace.metadata.truncated",
],
]
assert "langfuse.observation.metadata.a" not in posted[-1]
assert "langfuse.observation.metadata.a" not in posted[-1] and "langfuse.trace.metadata.b" not in posted[-1]
assert "dropping it" in caplog.text