The question-classifier default shipped two `classes` with blank names. That
turns a loud preflight refusal into a node that is written, reported as a
success, and silently classifies into two meaningless categories -- and the
fabricated ids `["1", "2"]` pin `declared_branch_handles`, so a semantic
`connect` is dropped while the create survives. That is the failure shape the
registry exists to eliminate.
Draw the line explicitly and apply it to every entry: a default may supply a
field whose empty value is semantically NEUTRAL -- a container, a mode, a config
or auth block -- but never one that carries the node's PURPOSE, where an empty
value produces a node that RUNS and yields nothing. Dropped, with the engine
behaviour each empty value would have produced:
end.outputs End runs OK, so run_finished_without_output is
False -> "All checks passed" on an empty result
answer.answer streams nothing
template-transform.template renders ""
variable-aggregator.variables SUCCEEDED, outputs={} -- triage cause (d)
question-classifier.classes two blank categories + fabricated handle ids
question-classifier.query_variable_selector classifies `query or ""`
knowledge-retrieval.dataset_ids retrieves from no dataset, succeeds empty
The whole `if-else` entry goes too, though nothing is gained or lost by it: its
one case has an empty `conditions` list that graphon combines with `all([])`, so
it always matches -- but `iter_cases()` synthesizes that identical case when
`cases` is absent, so dropping the entry does not cure it. It only stops us
writing a fabricated always-true condition that makes the node look configured.
An empty value that fails LOUDLY is not in that category: there is no silence to
protect against, and dropping it would pay the full batch-refusal cost for
nothing. Verified loud, so these stay -- `llm.model` (never launches, "model not
configured"), `llm.prompt_template` (empty messages are filtered out, leaving
zero, so graphon raises `NoPromptFoundError`), `code.code` (the sandbox runner
calls `main(**inputs_obj)` with no `main` defined), `code.outputs` (graphon
raises `CodeNodeError("Not all output parameters are validated.")` for any key
the code returns), and `http-request.url` (`Executor._init_url` raises
`InvalidURLError("url is required")` before any request goes out).
`template-transform.variables` stays too -- it is structural, and it is the
exact field the live F4 failure was missing.
The placeholder agent genuinely means "no dataset, no output", so it now says
so itself instead of borrowing a default that would say it for every caller.
The engine sweep is re-pinned in both directions: the fields the engine reports
missing from a node built from a default ALONE must be exactly the purpose
fields the registry refuses to fabricate -- no more (a structural regression)
and no fewer (a silent-success regression).
Also: name the `filter_applicable` test after what it actually asserts, since
that function does not return its working graph; and reword the comment in
`graph_normalizers` that quoted a bare `type` marker, one reflow away from
being read as a type comment and silently killing the type check again.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Dify Backend API
Setup and Run
Important
In the v1.3.0 release,
poetryhas been replaced withuvas the package manager for Dify API backend service.
uv and pnpm are required to run the setup and development commands below.
Using scripts (recommended)
The scripts resolve paths relative to their location, so you can run them from anywhere.
-
Run setup (copies env files and installs dependencies).
./dev/setup -
Review
api/.env,web/.env.local, anddocker/middleware.envvalues (see theSECRET_KEYnote below). -
Start middleware (PostgreSQL/Redis/Weaviate).
./dev/start-docker-compose -
Start backend (runs migrations first).
./dev/start-api -
Start Dify web service.
./dev/start-web./dev/setupand./dev/start-webinstall JavaScript dependencies through the repository root workspace, so you do not need a separatecd web && pnpm installstep. -
Set up your application by visiting
http://localhost:3000. -
Start the worker service (async and scheduler tasks, runs from
api)../dev/start-worker -
Optional: start Celery Beat (scheduled tasks).
./dev/start-beat
Environment notes
Important
When the frontend and backend run on different subdomains, set COOKIE_DOMAIN to the site’s top-level domain (e.g.,
example.com). The frontend and backend must be under the same top-level domain in order to share authentication cookies.
-
Generate a
SECRET_KEYin the.envfile.bash for Linux
sed -i "/^SECRET_KEY=/c\\SECRET_KEY=$(openssl rand -base64 42)" .envbash for Mac
secret_key=$(openssl rand -base64 42) sed -i '' "/^SECRET_KEY=/c\\ SECRET_KEY=${secret_key}" .env
Testing
-
Install dependencies for both the backend and the test environment
cd api uv sync --group dev -
Run the tests locally with mocked system environment variables in
tool.pytest_envsection inpyproject.toml, more can check AGENTS.mdContinue in the
apidirectory from the previous step.uv run pytest # Run all tests uv run pytest tests/unit_tests/ # Unit tests only uv run pytest tests/integration_tests/ # Integration tests # Code quality ../dev/reformat # Run all formatters and linters uv run ruff check --fix ./ # Fix linting issues uv run ruff format ./ # Format code uv run pyrefly check # Type checking
Generate TS stub
uv run dev/generate_swagger_specs.py --output-dir openapi
use https://jsontotable.org/openapi-to-typescript to convert to typescript