From 076e5a26d8673ecb5f2e27d1af400cbd84d64954 Mon Sep 17 00:00:00 2001 From: Aryan Srivastava Date: Mon, 1 Jun 2026 03:06:23 +0530 Subject: [PATCH 1/4] fix(propagation): coerce non-string metadata values to string Allows propagate_attributes to accept and automatically convert non-string metadata values (int, float, bool) to strings instead of silently dropping them. This resolves tracing compatibility issues with LangGraph integrations. --- langfuse/_client/propagation.py | 15 +++++++------ tests/unit/test_propagate_attributes.py | 29 +++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/langfuse/_client/propagation.py b/langfuse/_client/propagation.py index 597d8126e..5d485249d 100644 --- a/langfuse/_client/propagation.py +++ b/langfuse/_client/propagation.py @@ -90,12 +90,11 @@ def _detach_context_token_safely(token: Any) -> None: except Exception: pass - def propagate_attributes( *, user_id: Optional[str] = None, session_id: Optional[str] = None, - metadata: Optional[Dict[str, str]] = None, + metadata: Optional[Dict[str, Any]] = None, version: Optional[str] = None, tags: Optional[List[str]] = None, trace_name: Optional[str] = None, @@ -229,7 +228,7 @@ def _propagate_attributes( *, user_id: Optional[str] = None, session_id: Optional[str] = None, - metadata: Optional[Dict[str, str]] = None, + metadata: Optional[Dict[str, Any]] = None, version: Optional[str] = None, tags: Optional[List[str]] = None, trace_name: Optional[str] = None, @@ -247,10 +246,9 @@ def _propagate_attributes( "trace_name": trace_name, } - propagated_metadata_attributes: Dict[str, Optional[Dict[str, str]]] = { + propagated_metadata_attributes: Dict[str, Optional[Dict[str, Any]]] = { "metadata": metadata, } - if experiment: for key, value in experiment.items(): if key in ("experiment_metadata", "experiment_item_metadata"): @@ -286,8 +284,11 @@ def _propagate_attributes( validated_metadata: Dict[str, str] = {} for key, value in metadata_value.items(): - if _validate_string_value(value=value, key=f"{metadata_key}.{key}"): - validated_metadata[key] = value + if value is None: + continue + string_value = value if isinstance(value, str) else str(value) + if _validate_string_value(value=string_value, key=f"{metadata_key}.{key}"): + validated_metadata[key] = string_value if validated_metadata: context = _set_propagated_attribute( diff --git a/tests/unit/test_propagate_attributes.py b/tests/unit/test_propagate_attributes.py index c783e65dd..a77eceffe 100644 --- a/tests/unit/test_propagate_attributes.py +++ b/tests/unit/test_propagate_attributes.py @@ -489,7 +489,36 @@ def test_mixed_valid_invalid_metadata(self, langfuse_client, memory_exporter): self.verify_missing_attribute( child_span, f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.invalid_key" ) + def test_metadata_coercion_of_non_string_values(self, langfuse_client, memory_exporter): + """Verify metadata values that are not strings (int, float, bool) are coerced to strings.""" + with langfuse_client.start_as_current_observation(name="parent-span"): + with propagate_attributes( + metadata={ + "int_val": 42, # type: ignore + "float_val": 3.14, # type: ignore + "bool_val": True, # type: ignore + } + ): + child = langfuse_client.start_observation(name="child-span") + child.end() + # Verify child has all metadata keys coerced to string values + child_span = self.get_span_by_name(memory_exporter, "child-span") + self.verify_span_attribute( + child_span, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.int_val", + "42", + ) + self.verify_span_attribute( + child_span, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.float_val", + "3.14", + ) + self.verify_span_attribute( + child_span, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.bool_val", + "True", + ) class TestPropagateAttributesNesting(TestPropagateAttributesBase): """Tests for nested propagate_attributes contexts.""" From 98b20d45980f81a9e3f5ec49ba427b5752d0f348 Mon Sep 17 00:00:00 2001 From: Aryan Srivastava Date: Mon, 1 Jun 2026 03:18:24 +0530 Subject: [PATCH 2/4] fix(propagation): coerce boolean metadata to lowercase JSON strings --- langfuse/_client/propagation.py | 2 +- tests/unit/test_propagate_attributes.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/langfuse/_client/propagation.py b/langfuse/_client/propagation.py index 5d485249d..7ad7eeb28 100644 --- a/langfuse/_client/propagation.py +++ b/langfuse/_client/propagation.py @@ -286,7 +286,7 @@ def _propagate_attributes( for key, value in metadata_value.items(): if value is None: continue - string_value = value if isinstance(value, str) else str(value) + string_value = value if isinstance(value, str) else ("true" if value is True else "false" if value is False else str(value)) if _validate_string_value(value=string_value, key=f"{metadata_key}.{key}"): validated_metadata[key] = string_value diff --git a/tests/unit/test_propagate_attributes.py b/tests/unit/test_propagate_attributes.py index a77eceffe..39cf26574 100644 --- a/tests/unit/test_propagate_attributes.py +++ b/tests/unit/test_propagate_attributes.py @@ -517,7 +517,7 @@ def test_metadata_coercion_of_non_string_values(self, langfuse_client, memory_ex self.verify_span_attribute( child_span, f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.bool_val", - "True", + "true", ) class TestPropagateAttributesNesting(TestPropagateAttributesBase): From 0e29ec93de317cad67aaca333995090d6f2e57eb Mon Sep 17 00:00:00 2001 From: Aryan Srivastava Date: Mon, 1 Jun 2026 03:33:54 +0530 Subject: [PATCH 3/4] docs/style(propagation): update docstring and fix PEP8 spacing issues --- langfuse/_client/propagation.py | 5 +++-- tests/unit/test_propagate_attributes.py | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/langfuse/_client/propagation.py b/langfuse/_client/propagation.py index 7ad7eeb28..ac372a7e0 100644 --- a/langfuse/_client/propagation.py +++ b/langfuse/_client/propagation.py @@ -204,8 +204,9 @@ def propagate_attributes( Note: - **Validation**: All attribute values (user_id, session_id, metadata values) - must be strings ≤200 characters. Invalid values will be dropped with a - warning logged. Ensure values meet constraints before calling. + must be ≤200 characters. Non-string metadata values (int, float, bool, etc.) + are automatically coerced to their string representation. Values exceeding + 200 characters will be dropped with a warning logged. - **OpenTelemetry**: This uses OpenTelemetry context propagation under the hood, making it compatible with other OTel-instrumented libraries. diff --git a/tests/unit/test_propagate_attributes.py b/tests/unit/test_propagate_attributes.py index 39cf26574..3d1c4fa58 100644 --- a/tests/unit/test_propagate_attributes.py +++ b/tests/unit/test_propagate_attributes.py @@ -489,6 +489,7 @@ def test_mixed_valid_invalid_metadata(self, langfuse_client, memory_exporter): self.verify_missing_attribute( child_span, f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.invalid_key" ) + def test_metadata_coercion_of_non_string_values(self, langfuse_client, memory_exporter): """Verify metadata values that are not strings (int, float, bool) are coerced to strings.""" with langfuse_client.start_as_current_observation(name="parent-span"): @@ -518,7 +519,7 @@ def test_metadata_coercion_of_non_string_values(self, langfuse_client, memory_ex child_span, f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.bool_val", "true", - ) + ) class TestPropagateAttributesNesting(TestPropagateAttributesBase): """Tests for nested propagate_attributes contexts.""" From 1d1910ff76a7ec38440a14464c950c8b6a563da5 Mon Sep 17 00:00:00 2001 From: Aryan Srivastava Date: Mon, 1 Jun 2026 03:52:39 +0530 Subject: [PATCH 4/4] fix(propagation): support json serialization for container metadata values --- langfuse/_client/propagation.py | 11 ++++++++++- tests/unit/test_propagate_attributes.py | 14 +++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/langfuse/_client/propagation.py b/langfuse/_client/propagation.py index ac372a7e0..2cdc6acf4 100644 --- a/langfuse/_client/propagation.py +++ b/langfuse/_client/propagation.py @@ -287,7 +287,16 @@ def _propagate_attributes( for key, value in metadata_value.items(): if value is None: continue - string_value = value if isinstance(value, str) else ("true" if value is True else "false" if value is False else str(value)) + if isinstance(value, str): + string_value = value + elif isinstance(value, bool): + string_value = "true" if value else "false" + elif isinstance(value, (dict, list, tuple)): + import json + string_value = json.dumps(value) + else: + string_value = str(value) + if _validate_string_value(value=string_value, key=f"{metadata_key}.{key}"): validated_metadata[key] = string_value diff --git a/tests/unit/test_propagate_attributes.py b/tests/unit/test_propagate_attributes.py index 3d1c4fa58..8230a23ac 100644 --- a/tests/unit/test_propagate_attributes.py +++ b/tests/unit/test_propagate_attributes.py @@ -491,13 +491,15 @@ def test_mixed_valid_invalid_metadata(self, langfuse_client, memory_exporter): ) def test_metadata_coercion_of_non_string_values(self, langfuse_client, memory_exporter): - """Verify metadata values that are not strings (int, float, bool) are coerced to strings.""" + """Verify metadata values that are not strings (int, float, bool, dict, list) are coerced to strings.""" with langfuse_client.start_as_current_observation(name="parent-span"): with propagate_attributes( metadata={ "int_val": 42, # type: ignore "float_val": 3.14, # type: ignore "bool_val": True, # type: ignore + "dict_val": {"nested": "val"}, # type: ignore + "list_val": [1, 2], # type: ignore } ): child = langfuse_client.start_observation(name="child-span") @@ -520,6 +522,16 @@ def test_metadata_coercion_of_non_string_values(self, langfuse_client, memory_ex f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.bool_val", "true", ) + self.verify_span_attribute( + child_span, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.dict_val", + '{"nested": "val"}', + ) + self.verify_span_attribute( + child_span, + f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.list_val", + "[1, 2]", + ) class TestPropagateAttributesNesting(TestPropagateAttributesBase): """Tests for nested propagate_attributes contexts."""