The occurrence selector tells a per-call list from a single call's list value by length alone. One test pinned the side where that is unambiguous -- a 3-element list against 2 calls, replayed whole. The side that decides whether the rule is safe is the other one: a tool called n times whose single stored value is a list of length n. That case was decided by the rule and described by no test, so it read as an oversight rather than a decision. Pin it at both readers. The replay reader in base_agent_runner and the display reader on MessageAgentThought each get a two-call record whose one stored value is a two-element list, asserting that call 1 reads element 0 and call 2 reads element 1 -- what the rule does today. The name says what the case concedes rather than what it asserts. The docstrings say why the asymmetry is tolerable. observation values are always str: ToolEngine.agent_invoke is typed -> tuple[str, list[str], ToolInvokeMeta] and both runners store element 0, so a list under a tool name is not a shape any writer produces and the length check is defensive there. tool_input values are json.loads of the model's arguments with no shape check, so a legacy list-valued input is possible in principle, and that is the side the collision can reach. The selector is defined twice, identically, because models/ importing from core/agent/ is the worse layering trade and the reverse is odd. Neither copy is in the wrong place, so each now names the other and says the duplication is deliberate -- enough for a future editor to find both. No behaviour change: the condition, the ordering and the fallback are untouched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| .idea | ||
| .vscode | ||
| clients | ||
| commands | ||
| configs | ||
| constants | ||
| context | ||
| contexts | ||
| controllers | ||
| core | ||
| dev | ||
| docker | ||
| enterprise | ||
| enums | ||
| events | ||
| extensions | ||
| factories | ||
| fields | ||
| libs | ||
| migrations | ||
| models | ||
| openapi/markdown | ||
| providers | ||
| repositories | ||
| schedule | ||
| services | ||
| tasks | ||
| templates | ||
| tests | ||
| .dockerignore | ||
| .env.example | ||
| .importlinter | ||
| .ruff.toml | ||
| AGENTS.md | ||
| app_factory.py | ||
| app.py | ||
| celery_entrypoint.py | ||
| celery_healthcheck.py | ||
| cnt_base.sh | ||
| conftest.py | ||
| dify_app.py | ||
| Dockerfile | ||
| Dockerfile.dockerignore | ||
| gunicorn.conf.py | ||
| knowledge-fs-contract.lock.json | ||
| pyproject.toml | ||
| pyrefly-local-excludes.txt | ||
| pytest.ini | ||
| README.md | ||
| uv.lock | ||
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 Claude.mdcd api 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