Langfuse Integration#
Dify ships a first-party Langfuse tracing provider that forwards observability data β traces, spans, and generation observations β to a Langfuse project. It is packaged as an isolated Python sub-package under api/providers/trace/trace-langfuse/ and depends on langfuse>=4.2.0,<5.0.0.
The entry point is LangFuseDataTrace, which implements BaseTraceInstance and creates a dedicated LangfuseAPI client per integration instance. All data is sent via the low-level batch ingestion API (client.ingestion.batch) using explicit IngestionEvent_*Create objects rather than the high-level convenience methods deprecated in SDK v3 .
Data Model#
Three entity types map Dify's runtime events to Langfuse concepts :
| Dify entity | Langfuse concept | Key ID field |
|---|---|---|
LangfuseTrace | Trace | id (defaults None) |
LangfuseSpan | Observation β span | id (defaults None) |
LangfuseGeneration | Observation β generation | id (defaults None) |
All three models use filter_none_values before serialization, which strips any field left as None β including id. This behavior is the root cause of the generation dropout issue described below .
The GenerationUsage model maps token counts and costs. The totalCost field is populated from message_data.total_price in the message_trace path .
Trace Paths#
The trace() dispatcher routes each BaseTraceInfo subtype to a dedicated handler:
workflow_trace()β emits a top-level Langfuse trace, a workflow span, then iterates allWorkflowNodeExecutionrows and creates generation observations for LLM nodes and span observations for others. Every generation is created withid=node_execution_id, so workflow apps are unaffected by the ID bug.message_trace()β used by Chatbot, Agent, and Completion apps. Emits one trace and onellmgeneration observation .moderation_trace(),suggested_question_trace(),dataset_retrieval_trace(),tool_trace()β additional child observations on themessagepath .
The trace_id for all events is resolved as trace_info.trace_id or message_id (or workflow_run_id for workflows) .
SDK v2βv3 Migration (Dify v1.14.0) and Regressions#
PR #34265 (merged 2026-03-30) upgraded the Langfuse SDK from ~=2.51.3 to >=3.0.0 and replaced the old high-level SDK calls with the low-level ingestion API. The v2 methods (client.trace(), client.generation(), etc.) automatically assigned a client-side UUID when no id was supplied; the v3 ingestion API does not.
This introduced two regressions:
1. Generation Observation Dropout (Issue #37824)#
Affected: Chatbot, Agent, and Completion apps (all message_trace paths) from Dify v1.14.0 onward. Workflow / Advanced-Chat apps are not affected.
Root cause: message_trace() constructs LangfuseGeneration without passing an id. filter_none_values strips the None, and CreateGenerationBody is sent with id=None. Langfuse drops the observation, so traces show only the top-level trace node β no cost, 0.00 s latency .
The same id-less pattern affects suggested_question_trace(), moderation_trace(), dataset_retrieval_trace(), and tool_trace() .
Fix (PR #37833, open at time of writing): Pass id=str(uuid.uuid4()) explicitly when creating LangfuseGeneration in all affected handler methods .
2. Cross-Tenant Isolation#
PR #36107 (merged 2026-05-13) and PR #41403 addressed cross-tenant security: earlier versions allowed OpenTelemetry spans from the global process to leak into tenant Langfuse projects, and shared a single Langfuse client instance across tenants with identical public keys but different endpoints or secret keys.
Fix: Each LangFuseDataTrace instance now constructs an independent LangfuseAPI client with its own dedicated httpx.Client. The HTTP client is configured with a timeout from the LANGFUSE_TIMEOUT environment variable (default: 5 seconds). The LangfuseAPI client is initialized with base_url, username (from public_key), password (from secret_key), SDK metadata headers, and the httpx_client . The close() and __del__() lifecycle methods close the HTTP client connection on cache eviction or garbage collection . The close() method is idempotent.
Configuration#
LangfuseConfig (extends BaseTracingConfig) exposes three fields:
| Field | Default |
|---|---|
public_key | β |
secret_key | β |
host | https://api.langfuse.com |
A Pydantic field_validator normalizes custom hosts, including paths. Credentials are stored per-app and loaded when LangFuseDataTrace is instantiated.
Key Files#
| File | Purpose |
|---|---|
langfuse_trace.py | LangFuseDataTrace β all trace handlers and ingestion logic |
entities/langfuse_trace_entity.py | LangfuseTrace, LangfuseSpan, LangfuseGeneration, GenerationUsage |
api/core/ops/ops_trace_manager.py | TraceQueueManager, message_trace(), workflow_trace() β upstream trace builders |
api/core/ops/entities/trace_entity.py | MessageTraceInfo, WorkflowTraceInfo, and other payload models |