Skip to content

Commit 10d9c85

Browse files
committed
chore: distribute metadata
1 parent 702d277 commit 10d9c85

3 files changed

Lines changed: 32 additions & 42 deletions

File tree

langfuse/_client/client.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
"""
55

66
import asyncio
7-
import json
87
import logging
98
import os
109
import re
@@ -3614,11 +3613,9 @@ def metadata(
36143613
yield
36153614
return
36163615

3617-
# Convert metadata dict to JSON string for context storage
3618-
metadata_json = json.dumps(kwargs)
3619-
3620-
# Set context variable
3621-
new_context = otel_context_api.set_value(LANGFUSE_CTX_METADATA, metadata_json)
3616+
# Store metadata as a dict in context (not JSON string)
3617+
# This allows span_processor to distribute keys as individual attributes
3618+
new_context = otel_context_api.set_value(LANGFUSE_CTX_METADATA, kwargs)
36223619
token = otel_context_api.attach(new_context)
36233620

36243621
# Set baggage if requested

langfuse/_client/span.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515

1616
from datetime import datetime
1717
from time import time_ns
18-
import json
1918
import warnings
2019
from typing import (
2120
TYPE_CHECKING,
@@ -1274,11 +1273,9 @@ def metadata(
12741273
yield
12751274
return
12761275

1277-
# Convert metadata dict to JSON string for context storage
1278-
metadata_json = json.dumps(kwargs)
1279-
1280-
# Set context variable
1281-
new_context = otel_context_api.set_value(LANGFUSE_CTX_METADATA, metadata_json)
1276+
# Store metadata as a dict in context (not JSON string)
1277+
# This allows span_processor to distribute keys as individual attributes
1278+
new_context = otel_context_api.set_value(LANGFUSE_CTX_METADATA, kwargs)
12821279
token = otel_context_api.attach(new_context)
12831280

12841281
# Set baggage if requested

langfuse/_client/span_processor.py

Lines changed: 26 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ def on_start(self, span: Span, parent_context: Optional[Context] = None) -> None
128128
This method is called when a span starts and applies context propagation:
129129
1. Propagates all baggage keys as span attributes
130130
2. Propagates langfuse.ctx.* context variables as span attributes
131-
3. Merges langfuse.ctx.metadata.* keys into a single metadata JSON attribute
131+
3. Distributes langfuse.ctx.metadata keys as individual langfuse.metadata.* attributes
132132
133133
Args:
134134
span: The span that is starting
@@ -180,39 +180,35 @@ def on_start(self, span: Span, parent_context: Optional[Context] = None) -> None
180180
except Exception as e:
181181
langfuse_logger.debug(f"Could not read context key '{ctx_key}': {e}")
182182

183-
# 3. Handle langfuse.ctx.metadata.* keys - merge into single metadata JSON
183+
# 3. Handle langfuse.ctx.metadata - distribute keys as individual attributes
184184
try:
185-
# Get metadata as a single JSON object from context
186-
metadata_value = context_api.get_value(
185+
# Get metadata dict from context
186+
metadata_dict = context_api.get_value(
187187
LANGFUSE_CTX_METADATA, context=current_context
188188
)
189-
if metadata_value is not None:
190-
if isinstance(metadata_value, str):
191-
# If it's already a JSON string, validate it
192-
try:
193-
json.loads(metadata_value) # Validate JSON
194-
metadata_json = metadata_value
195-
except json.JSONDecodeError:
196-
# If invalid JSON, wrap in quotes
197-
metadata_json = json.dumps({"value": metadata_value})
198-
elif isinstance(metadata_value, dict):
199-
# Convert dict to JSON string
200-
metadata_json = json.dumps(metadata_value)
201-
else:
202-
# Convert other types to a wrapped JSON object
203-
metadata_json = json.dumps({"value": str(metadata_value)})
204-
205-
# Only propagate if not already set or different
206-
existing_metadata = (
207-
span.attributes.get("metadata")
208-
if hasattr(span, "attributes") and span.attributes is not None
209-
else None
210-
)
211-
if existing_metadata != metadata_json:
212-
propagated_attributes["metadata"] = metadata_json
213-
langfuse_logger.debug(
214-
f"Propagated metadata to span '{span.name}': {metadata_json}"
189+
if metadata_dict is not None and isinstance(metadata_dict, dict):
190+
# Set each metadata key as a separate span attribute with langfuse.metadata. prefix
191+
for key, value in metadata_dict.items():
192+
attr_key = f"langfuse.metadata.{key}"
193+
194+
# Convert value to appropriate type for span attribute
195+
if isinstance(value, (str, int, float, bool)):
196+
attr_value = value
197+
else:
198+
# For complex types, convert to JSON string
199+
attr_value = json.dumps(value)
200+
201+
# Only propagate if not already set or different
202+
existing_value = (
203+
span.attributes.get(attr_key)
204+
if hasattr(span, "attributes") and span.attributes is not None
205+
else None
215206
)
207+
if existing_value != attr_value:
208+
propagated_attributes[attr_key] = attr_value
209+
langfuse_logger.debug(
210+
f"Propagated metadata key '{key}' = '{attr_value}' to span '{span.name}'"
211+
)
216212
except Exception as e:
217213
langfuse_logger.debug(f"Could not read metadata from context: {e}")
218214

0 commit comments

Comments
 (0)