
bug: server stores list-type observation input as Python str() repr (single quotes) instead of JSON
快速结论:该 Bug 发生在使用 OTel 方式的 Langfuse Python SDK 配合 LangChain 的 SystemMessage(content_blocks=...) 时。Langfuse 服务器存储的观察值中的 list 类型内容被错误地保存为 Python 的 str() 表现形式(带单引号),而不是有效的 JSON 数组。优先排查:确认 LangChain 回调处理器中对象序列化是否使用了 str() 而非 json.dumps()。
问题场景
用户在 Langfuse 中使用基于 OpenTelemetry (OTel) 的 Python SDK,搭配 LangChain,并使用了带 content_blocks 参数的 SystemMessage。这会导致 message.content 变成 Python 的 list[dict] 类型。在 Langfuse UI 和 API 中,该字段内容被显示为带单引号的 Python repr 字符串,而不是预期的 Markdown 文本或 JSON 数组。该问题在使用 langfuse==4.13.0 (OTel-based)、自托管 Langfuse Server 3.162.0、langchain-core==1.4.8 的环境中被复现。
报错原文
在 Langfuse API / UI 中,input[0].content 字段显示为:
[{'type': 'text', 'text': 'You are a test agent.\n\n# Role\n\n...'}]
该值实际类型为 str(而非 list),且以 Python repr 风格包裹(双引号被替换为单引号)。
原因分析
可能原因:问题可能不出在 Langfuse 服务端。服务端的 TypeScript 代码始终使用 JSON.stringify(),并且只保留字符串原样。当属性值本身已经是字符串时,服务端不会进行二次转换。然而,Python SDK 中的 LangChain 回调处理器 (langfuse.langchain.CallbackHandler) 在处理 SystemMessage.content 中的 list 类型值时,可能误用了 Python 的 str() 函数进行序列化,而非 json.dumps()。由于 OTel SDK 仅接受原始类型作为 span 属性值,任何 list/dict 类型都需要显式序列化;如果使用了 str(),就会产生带单引号的 Python repr 字符串。
环境排查
- Langfuse Python SDK 版本 (尤其是
langfuse): 确认是否为 4.x 系列及具体版本号 - Langfuse 服务端版本 (自托管用户关注)
- LangChain 包版本:
langchain-core,langchain,langchain-openai - 验证
SystemMessage.content在 Python 中的实际类型 (是否为list)
解决步骤
- 优先排查:检查
langfuse-pythonSDK 中 LangChain 回调处理器的消息序列化逻辑,确认是否存在str(content)的地方。 - 如果发现
str()调用,应将其替换为json.dumps(content),确保 list/dict 类型的 content 被序列化为合法的 JSON 字符串。 - 上述改动应在 langfuse/langfuse-python 仓库中进行提交。
- 部署修复后的 SDK 版本,并重新运行你的 LangChain 链路。
验证方法
在修复后,通过 Langfuse API 查询具体的 observation 对象:
GET /api/public/observations/{observation_id}
确认 input[0].content 字段:
- 类型为
list(在 JSON 中为数组结构) - 值中的字符串使用双引号(标准 JSON 格式),而非单引号
- 不再出现 Python 风格的 repr 字符串



