CodeVetter · Repo Unpack · Extension points
langgenius/dify
Dify is built for extension at every layer: app modes via generator classes + a match-dispatch, workflow nodes via `DifyNodeFactory`/`resolve_workflow_node_class` (with most builtins now in the external `graphon` package), vector/trace backends via Python entry-point packages, storage via a `StorageType` match, remote config via named sources, HTTP routes via flask-restx Namespaces + module import lists, Celery work via the imports+beat_schedule assembly, moderation/tool/providers via factories and the external plugin daemon, CLI verbs via a generated command tree, and frontend calls via regenerated oRPC contracts.
- New app types plug in at three coordinated points: extend `AppMode` in `models/model.py`, add a generator under `core/app/apps/<mode>/` subclassing `BaseAppGenerator`, and add a `case` in `AppGenerateService._dispatch_generate`.
- Workflow node types resolve through `core/workflow/node_factory.py` (`DifyNodeFactory`, `resolve_workflow_node_class`, `is_start_node_type`); Dify-owned node dirs (`agent`, `agent_v2`, `datasource`, `human_input`, `knowledge_index`, `knowledge_retrieval`, `trigger_*`) coexist with `graphon.nodes.BuiltinNodeTypes` from the external engine package.
- Vector backends are true plugins: each `api/providers/vdb/vdb-*` package declares `[project.entry-points."dify.vector_backends"]` (e.g. `pgvector = "dify_vdb_pgvector.pgvector:PGVectorFactory"`) and joins the uv workspace via `tool.uv.workspace.members`.
- Tracing providers mirror the vdb pattern: `api/providers/trace/*` workspace packages (aliyun, arize-phoenix, langfuse, langsmith, mlflow, opik, tencent, weave) selected via `trace-*` dependency groups.
- Storage backends extend `StorageType` + a `case` in `Storage.get_storage_factory` returning a `BaseStorage` runner; implementations live in `api/extensions/storage/` (s3, opendal, azure_blob, gcs, oss, cos, oci, obs, baidu, supabase, tos, clickzetta_volume).
- Config additions go into the right `configs/` mixin group (deploy/enterprise/extra/feature/middleware/observability/packaging/remote_settings_sources) and are picked up by pydantic-settings; remote sources extend `RemoteSettingsSourceName` + a `case` in `RemoteSettingsSourceFactory`.
- New HTTP routes: add a flask-restx `Namespace` resource module under the right `controllers/` blueprint package; console modules must be imported in `controllers/console/__init__.py` (`RESOURCE_MODULES` tuple or the `from . import …` blocks) so route decorators evaluate.
- New Celery work: add module path to `ext_celery.init_app`'s `imports` list, gate it with an `ENABLE_*` flag in `CeleryScheduleTasksConfig`, and add a `beat_schedule` entry for periodic jobs — the file explicitly asks contributors to add the switch.
- Model/tool/agent-strategy providers live in the external plugin daemon, not this repo: new capabilities surface to the API through `core/plugin/impl/*` clients and are installed via marketplace; local extension is via `ext_code_based_extension` + `api_based_extension` external-data-tool hooks.
- Moderation backends plug into `core/moderation/factory.py` with existing `keywords`, `openai_moderation`, and `api` implementations as templates.
- CLI verbs: create `cli/src/commands/<topic>/<verb>/index.ts` extending `DifyCommand` (sibling `run.ts`/`handlers.ts`/`guide.ts` optional; `_shared/` excluded from discovery), then `pnpm tree:gen` regenerates `tree.generated.ts` — CI gates drift with `tree:check`.
- Frontend↔backend contract extension is generated, not handwritten: new console endpoints flow through `api/controllers` schemas → `api/dev/generate_swagger_specs.py`/`generate_fastopenapi_specs.py` → `packages/contracts` `gen-api-contract` (openapi-ts) → consumed via `consoleClient`/`consoleQuery`; AGENTS.md forbids handwritten REST helpers or direct edits to generated contracts.
- GraphEngine behavior is layered: callers pass `graph_engine_layers: Sequence[GraphEngineLayer]` (e.g. `DebugLoggingLayer`, `ExecutionLimitsLayer`, `ObservabilityLayer`, `PauseStatePersistenceLayer`) into generators/`WorkflowEntry`, so cross-cutting run behavior extends via engine layers rather than engine edits.
- Response-stream processing is a filter chain: `filter_graph_events` accepts additional `ResponseStreamFilter`-style filters (e.g. `HumanInputFormEventFilter`), letting new event semantics be inserted without touching engine internals.
- New Flask extensions follow the `extensions/ext_*` convention: implement `init_app(app)` (optionally `is_enabled()`), add to the ordered list in `initialize_extensions` at the correct dependency position.