Trace providers
This directory holds **optional workspace packages** that send Dify **ops tracing** data (workflows, messages, tools, moderation, etc.) to an external observability backend (Langfuse, LangSmith, OpenTelemetry-style exporters, and others).
Overview
Trace providers
This directory holds optional workspace packages that send Dify ops tracing data (workflows, messages, tools, moderation, etc.) to an external observability backend (Langfuse, LangSmith, OpenTelemetry-style exporters, and others).
Unlike VDB providers, trace plugins are not discovered via entry points. The API core imports your package explicitly from core/ops/ops_trace_manager.py after you register the provider id and mapping.
Architecture
| Layer | Location | Role |
|---|---|---|
| Contracts | api/core/ops/base_trace_instance.py, api/core/ops/entities/trace_entity.py, api/core/ops/entities/config_entity.py | BaseTraceInstance, BaseTracingConfig, and typed *TraceInfo payloads |
| Registry | api/core/ops/ops_trace_manager.py | TracingProviderEnum, OpsTraceProviderConfigMap — maps provider string → config class, encrypted keys, and trace class |
| Your package | api/providers/trace/trace-<name>/ | Pydantic config + subclass of BaseTraceInstance |
At runtime, OpsTraceManager decrypts stored credentials, builds your config model, caches a trace instance, and calls trace(trace_info) with a concrete BaseTraceInfo subtype.
What you implement
1. Config model (BaseTracingConfig)
Subclass BaseTracingConfig from core.ops.entities.config_entity. Use Pydantic validators; reuse helpers from core.ops.utils (for example validate_url, validate_url_with_path, validate_project_name) where appropriate.
Fields fall into two groups used by the manager:
secret_keys— names of fields that are encrypted at rest (API keys, tokens, passwords).other_keys— non-secret connection settings (hosts, project names, endpoints).
List these key names in your OpsTraceProviderConfigMap entry so encrypt/decrypt and merge logic stay correct.
2. Trace instance (BaseTraceInstance)
Subclass BaseTraceInstance and implement:
def trace(self, trace_info: BaseTraceInfo) -> None:
...
Dispatch on the concrete type with isinstance (see trace_langfuse or trace_langsmith for full patterns). Payload types are defined in core/ops/entities/trace_entity.py, including:
WorkflowTraceInfo,WorkflowNodeTraceInfo,DraftNodeExecutionTraceMessageTraceInfo,ToolTraceInfo,ModerationTraceInfo,SuggestedQuestionTraceInfoDatasetRetrievalTraceInfo,GenerateNameTraceInfo,PromptGenerationTraceInfo
You may ignore categories your backend does not support; existing providers often no-op unhandled types.
Optional: use get_service_account_with_tenant(app_id) from the base class when you need tenant-scoped account context.
3. Register in the API core
Upstream changes are required so Dify knows your provider exists:
TracingProviderEnum(api/core/ops/entities/config_entity.py) — add a new member whose value is the stable string stored in app tracing config (e.g."mybackend").OpsTraceProviderConfigMap.__getitem__(api/core/ops/ops_trace_manager.py) — add amatchcase for that enum member returning:config_class: your Pydantic config typesecret_keys/other_keys: lists of field names as abovetrace_instance: yourBaseTraceInstancesubclass
Lazy-import your package inside the case so missing optional installs raise a clearImportError.
If the match case is missing, the provider string will not resolve and tracing will be disabled for that app.
Package layout
Each provider is a normal uv workspace member, for example:
api/providers/trace/trace-<name>/pyproject.toml— project namedify-trace-<name>, dependencies on vendor SDKsapi/providers/trace/trace-<name>/src/dify_trace_<name>/—config.py,<name>_trace.py, optionalentities/, and an emptypy.typedfile (PEP 561) so the API type checker treats the package as typed; listpy.typedunder[tool.setuptools.package-data]for that import name inpyproject.toml.
Reference implementations: trace-langfuse/, trace-langsmith/, trace-opik/.
Wiring into the api workspace
In api/pyproject.toml:
[tool.uv.sources]—dify-trace-<name> = { workspace = true }[dependency-groups]— addtrace-<name> = ["dify-trace-<name>"]and includedify-trace-<name>intrace-allif it should ship with the default bundle
After changing metadata, run uv sync from api/.