api/providers/trace/README.md
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.
| 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.
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.
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, PromptGenerationTraceInfoYou 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.
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 a match case for that enum member returning:
config_class: your Pydantic config typesecret_keys / other_keys: lists of field names as abovetrace_instance: your BaseTraceInstance subclassImportError.If the match case is missing, the provider string will not resolve and tracing will be disabled for that app.
Each provider is a normal uv workspace member, for example:
api/providers/trace/trace-<name>/pyproject.toml — project name dify-trace-<name>, dependencies on vendor SDKsapi/providers/trace/trace-<name>/src/dify_trace_<name>/ — config.py, <name>_trace.py, optional entities/, and an empty py.typed file (PEP 561) so the API type checker treats the package as typed; list py.typed under [tool.setuptools.package-data] for that import name in pyproject.toml.Reference implementations: trace-langfuse/, trace-langsmith/, trace-opik/.
api workspaceIn api/pyproject.toml:
[tool.uv.sources] — dify-trace-<name> = { workspace = true }[dependency-groups] — add trace-<name> = ["dify-trace-<name>"] and include dify-trace-<name> in trace-all if it should ship with the default bundleAfter changing metadata, run uv sync from api/.