Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .claude/skills/dify-docs-api-reference/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,21 @@ Pattern: `{verb}{AppType}{Resource}`
- **Descriptions must add value**: `"Session identifier."` is a label, not a description. Instead: `"The \`user\` identifier provided in API requests."`.
- **Nullable/conditional fields**: Explain when present or `null`.

#### Endpoint vs parameter descriptions

| Field | Scope |
|---|---|
| Endpoint `description` | What the endpoint does, plus any whole-API surprise (cascading delete, long-poll duration). One sentence when possible. |
| Parameter `description` | Field meaning, valid values, deprecation, normalization, and rules about when to use it vs an alternative. |

If the endpoint description explains a parameter, move it down.

❌ "Remove one or more tag bindings from a knowledge base. Provide tag IDs in `tag_ids`. The legacy `tag_id` field is still accepted for single-tag requests and is normalized into `tag_ids` server-side; supply at least one of the two."

✅ Endpoint: "Remove one or more tags from a knowledge base."
`tag_ids`: "Tag IDs to unbind. Required unless the legacy `tag_id` is provided."
`tag_id`: "Legacy single-tag form. Normalized into `tag_ids` server-side. Use `tag_ids` for new integrations."

### Cross-API Links

When a description mentions another endpoint, add a markdown link. Pattern: `/api-reference/{category}/{endpoint-name}` (kebab-case from endpoint summary).
Expand Down
16 changes: 11 additions & 5 deletions .claude/skills/dify-docs-env-vars/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,21 @@ Read these shared guides:
2. `writing-guides/formatting-guide.md`
3. `writing-guides/glossary.md`

## Source of Truth: `docker/.env.example`
## Source of Truth: `docker/.env.example` + `docker/envs/**/*.env.example`

The doc mirrors `docker/.env.example`, the supported self-host knob surface.
After Dify PR #31586, the supported self-host knob surface is split across:

- `docker/.env.example` — essential startup values
- `docker/envs/**/*.env.example` — categorized optional vars (core-services, databases, infrastructure, security, vectorstores, middleware)

The verifier reads both. Pass `--env-example docker/.env.example --env-example docker/envs` (the second arg is a directory; the verifier globs `**/*.env.example` recursively).

| Var location | Action |
|---|---|
| In `.env.example`, uncommented | Document. |
| In `.env.example`, commented (`#FOO=bar`) | Document; add to **Verifier false positives** in `ignored-vars.md` (the verifier can't parse defaults from comments). |
| Only in `api/configs/` Pydantic, not in `.env.example` | **Don't document.** Upstream-deferred; file a PR adding it to `.env.example` first. |
| In any `.env.example` file, uncommented | Document. |
| In any `.env.example` file, commented (`#FOO=bar`) | Document; add to **Verifier false positives** in `ignored-vars.md` (the verifier can't parse defaults from comments). |
| Only in `api/configs/` Pydantic, not in any `.env.example` | **Don't document.** Upstream-deferred; file a PR adding it to the appropriate `.env.example` file first. |
| Removed from `.env.example` because the code no longer reads it | **Remove from docs.** Documenting unreferenced vars implies they still take effect. Discoverability for upgraders belongs in upstream Dify release notes, not this docs site. |

The verifier's "extra in docs" signal is not an escape hatch. Never suppress it for Pydantic-only vars via `ignored-vars.md`.

Expand Down
85 changes: 70 additions & 15 deletions .claude/skills/dify-docs-env-vars/verify-env-docs.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
#!/usr/bin/env python3
"""
Verify Dify environment variable documentation against .env.example.
Verify Dify environment variable documentation against .env.example sources.

Parses both files, extracts variable names and defaults, and reports
discrepancies between what the documentation says and what .env.example defines.
Parses one or more env-example files plus the docs MDX, extracts variable names
and defaults, and reports discrepancies.

After Dify PR #31586, env vars were split across `docker/.env.example` and
`docker/envs/**/*.env.example`. The verifier accepts either a single file or
a directory; passing a directory globs `**/*.env.example` recursively.

Usage:
python3 verify-env-docs.py [--env-example PATH] [--docs PATH]
python3 verify-env-docs.py --env-example PATH [--env-example PATH ...] --docs PATH

Both arguments are required (no defaults).
`--env-example` may be repeated and may point to either a file or a directory.
"""

import argparse
Expand All @@ -17,8 +21,8 @@
from pathlib import Path


def parse_env_example(path: str) -> dict[str, str]:
"""Parse .env.example and return {VARIABLE_NAME: default_value}."""
def parse_env_file(path: Path) -> dict[str, str]:
"""Parse a single env-example file and return {VARIABLE_NAME: default_value}."""
variables = {}
with open(path, encoding="utf-8") as f:
for line in f:
Expand All @@ -35,6 +39,52 @@ def parse_env_example(path: str) -> dict[str, str]:
return variables


def collect_env_files(sources: list[str]) -> list[Path]:
"""Resolve each source (file or directory) into a list of env-example files.

Files are returned as-is. Directories are scanned recursively for any
file whose name ends with `.env.example` (matches both `.env.example`
and `<name>.env.example`).
"""
files: list[Path] = []
seen: set[Path] = set()
for source in sources:
p = Path(source)
if not p.exists():
print(f"ERROR: env source not found: {source}", file=sys.stderr)
sys.exit(1)
if p.is_dir():
for f in sorted(p.rglob("*.env.example")):
if f not in seen:
files.append(f)
seen.add(f)
# Also catch the bare `.env.example` filename, which rglob's
# `*.env.example` pattern does include via the leading wildcard,
# but be explicit for clarity.
for f in sorted(p.rglob(".env.example")):
if f not in seen:
files.append(f)
seen.add(f)
else:
if p not in seen:
files.append(p)
seen.add(p)
return files


def parse_env_example(sources: list[str]) -> tuple[dict[str, str], list[Path]]:
"""Parse all env-example files reachable from the given sources.

Later files override earlier ones for duplicate keys. Returns the merged
variable map plus the list of files actually parsed (for diagnostics).
"""
files = collect_env_files(sources)
merged: dict[str, str] = {}
for f in files:
merged.update(parse_env_file(f))
return merged, files


def parse_ignored_vars(path: str) -> set[str]:
"""Parse the ignored-vars markdown file and return the set of ignored names.

Expand Down Expand Up @@ -183,7 +233,13 @@ def main():
parser.add_argument(
"--env-example",
required=True,
help="Path to .env.example file (e.g., /path/to/dify/docker/.env.example)",
action="append",
help=(
"Path to a .env.example file or to a directory containing them. "
"May be repeated. When given a directory, the verifier globs "
"**/*.env.example recursively. Pass both `docker/.env.example` and "
"`docker/envs/` to capture the post-PR-#31586 layout."
),
)
parser.add_argument(
"--docs",
Expand All @@ -197,18 +253,17 @@ def main():
)
args = parser.parse_args()

if not Path(args.env_example).exists():
print(f"ERROR: .env.example not found at {args.env_example}")
sys.exit(1)
if not Path(args.docs).exists():
print(f"ERROR: Documentation not found at {args.docs}")
sys.exit(1)

env_vars = parse_env_example(args.env_example)
env_vars, env_files = parse_env_example(args.env_example)
doc_vars = parse_mdx_docs(args.docs)
ignored = parse_ignored_vars(args.ignored)

print(f"Parsed {len(env_vars)} variables from .env.example")
print(f"Parsed {len(env_vars)} variables from {len(env_files)} env-example file(s):")
for f in env_files:
print(f" - {f}")
print(f"Parsed {len(doc_vars)} variables from documentation")
print(f"Loaded {len(ignored)} ignored variables from {args.ignored}")
print()
Expand All @@ -223,12 +278,12 @@ def main():
print(f" {name}={env_vars[name]}")
print()

# --- Check 2: Variables in docs but not in .env.example ---
# --- Check 2: Variables in docs but not in any env-example source ---
extra_in_docs = sorted(
(set(doc_vars.keys()) - set(env_vars.keys())) - ignored
)
if extra_in_docs:
print(f"=== IN DOCS BUT NOT IN .env.example ({len(extra_in_docs)}) ===")
print(f"=== IN DOCS BUT NOT IN ANY .env.example ({len(extra_in_docs)}) ===")
for name in extra_in_docs:
print(f" {name} (doc default: {doc_vars[name]!r})")
print()
Expand Down
24 changes: 22 additions & 2 deletions .claude/skills/dify-docs-release-sync/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,23 @@ git log <from>..<to> --oneline --grep="(#"

This captures every change between the two versions, regardless of whether PRs were tagged to a milestone.

**Do not pre-filter the diff to a hand-picked path list.** Run `git diff --stat` over the full change set, then categorize in 1.2. Pre-filtering hides files like new `docker/dify-compose*` scripts, `docker/.env.default`, `docker/README.md`, or root `README.md` that drive deployment-doc updates.

### 1.1a Cross-check existing docs PRs

Before generating the report, check whether dify-docs already has open or recently-merged PRs covering the same source PRs. This prevents duplicate work and reveals doc paths you might miss:

```bash
# Search by source-PR number(s) you plan to flag
gh pr list --repo langgenius/dify-docs --state all --search "#<dify-PR-number>" \
--json number,title,state,files
# Or scan recent docs PRs for the release window
gh pr list --repo langgenius/dify-docs --state all --limit 30 \
--json number,title,state,mergedAt,files
```

If a docs PR already covers an item, mark it **Already addressed (PR #N)** in the report and exclude it from execution.

For context on specific changes, fetch the relevant PR details:
```bash
# Extract PR numbers from commit messages
Expand Down Expand Up @@ -110,8 +127,8 @@ Read the PR description for context. Map changed source paths to likely doc area
| dify | `api/core/agent/` | `en/use-dify/build-apps/agent.mdx` |
| dify | `api/core/app/` | `en/use-dify/build-apps/` |
| dify | `web/app/components/` | UI-related docs (check PR description for specifics) |
| dify | `docker/`, deployment configs | `en/getting-started/install/` |
| dify | `api/configs/` | Configuration/environment variable docs |
| dify | `docker/.env.example`, `docker/docker-compose.yaml`, `docker/docker-compose-template.yaml`, `api/configs/` | `en/self-host/configuration/environments.mdx` (env var docs) |
| dify | `docker/README.md`, `docker/dify-compose*`, `docker/.env.default`, root `README.md`, any new file under `docker/` | `en/self-host/quick-start/docker-compose.mdx` and `en/self-host/quick-start/faqs.mdx` (deployment workflow docs) |
| graphon | `src/graphon/nodes/` (built-in nodes: llm, code, http_request, if_else, loop, iteration, parameter_extractor, document_extractor, list_operator, variable_aggregator/assigner, question_classifier, template_transform, tool, start/end/answer, human_input) | `en/use-dify/workflow/nodes/` |
| graphon | `src/graphon/model_runtime/` | `en/use-dify/model-providers/` |
| graphon | `src/graphon/graph_engine/`, `src/graphon/runtime/` | workflow engine behavior, execution semantics |
Expand Down Expand Up @@ -296,3 +313,6 @@ For each affected variable group, use `dify-docs-env-vars` skill:
| Skipping Pydantic model changes | A model change may affect multiple endpoints — trace which controllers use it |
| Forgetting to checkout target version | Audit against the target release code, not whatever is currently checked out |
| Manually translating after EN fixes | Translation is automatic on PR push — never run manual translation scripts. **Exception**: `environments.mdx` is in the ignore list and must be translated manually. |
| Pre-filtering the diff to a hand-picked path list | Run the full `git diff --stat` first; categorize after. Pre-filtering hides deployment scripts, new docker files, and root README changes. |
| Trusting the `chore:` / `fix:` prefix as a no-doc-impact signal | Read the PR title and body. "chore: easier and simpler deploy" is a deployment workflow change. Prefix is not a category. |
| Skipping the dify-docs PR cross-check | Always run `gh pr list --repo langgenius/dify-docs` against the source PR numbers before reporting. Avoids duplicate work and surfaces doc paths the heuristic mapping missed. |
91 changes: 88 additions & 3 deletions en/api-reference/openapi_knowledge.json
Original file line number Diff line number Diff line change
Expand Up @@ -4544,7 +4544,7 @@
"Tags"
],
"summary": "Delete Tag Binding",
"description": "Remove a tag binding from a knowledge base.",
"description": "Remove one or more tags from a knowledge base.",
"operationId": "unbindTagFromDataset",
"requestBody": {
"required": true,
Expand All @@ -4553,13 +4553,21 @@
"schema": {
"type": "object",
"required": [
"tag_id",
"target_id"
],
Comment thread
RiskeyL marked this conversation as resolved.
"properties": {
"tag_ids": {
"type": "array",
"items": {
"type": "string"
},
"minItems": 1,
"description": "Tag IDs to unbind. Required unless the legacy `tag_id` is provided."
},
"tag_id": {
"type": "string",
"description": "Tag ID to unbind."
"deprecated": true,
"description": "Legacy single-tag form. Normalized into `tag_ids` server-side. Use `tag_ids` for new integrations."
},
"target_id": {
"type": "string",
Expand Down Expand Up @@ -6871,6 +6879,83 @@
}
}
}
},
"metadata_filtering_conditions": {
"type": "object",
"nullable": true,
"description": "Restrict retrieval to chunks whose document metadata matches the given conditions. Conditions are evaluated server-side against document metadata fields.",
"properties": {
"logical_operator": {
"type": "string",
"enum": [
"and",
"or"
],
"default": "and",
"nullable": true,
"description": "How to combine multiple conditions."
},
"conditions": {
"type": "array",
"nullable": true,
"description": "List of metadata conditions to evaluate.",
"items": {
"type": "object",
"required": [
"name",
"comparison_operator"
],
"properties": {
"name": {
"type": "string",
"description": "Metadata field name to compare against."
},
"comparison_operator": {
"type": "string",
"description": "Comparison to apply. String operators (`contains`, `not contains`, `start with`, `end with`, `is`, `is not`, `empty`, `not empty`, `in`, `not in`) act on string or array metadata. Numeric operators (`=`, `≠`, `>`, `<`, `≥`, `≤`) act on numeric metadata. Time operators (`before`, `after`) act on time metadata.",
"enum": [
"contains",
"not contains",
"start with",
"end with",
"is",
"is not",
"empty",
"not empty",
"in",
"not in",
"=",
"≠",
">",
"<",
"≥",
"≤",
"before",
"after"
]
},
"value": {
"nullable": true,
"description": "Value to compare against. Type depends on `comparison_operator`: string for most string operators, array of strings for `in` and `not in`, number for numeric operators, and omitted for `empty` and `not empty`.",
"oneOf": [
{
"type": "string"
},
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "number"
}
]
}
}
}
}
}
}
}
}
Expand Down
7 changes: 3 additions & 4 deletions en/self-host/advanced-deployments/local-source-code.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,10 @@ A series of middlewares for storage (e.g. PostgreSQL / Redis / Weaviate (if not
```bash
cd docker

cp middleware.env.example middleware.env
cp envs/middleware.env.example middleware.env

# change the profile to mysql if you are not using postgresql
# change the profile to other vector database if you are not using weaviate
docker compose -f docker-compose.middleware.yaml --profile postgresql --profile weaviate -p dify up -d
# Change DB_TYPE or COMPOSE_PROFILES in middleware.env if you are not using PostgreSQL and Weaviate.
docker compose --env-file middleware.env -f docker-compose.middleware.yaml -p dify up -d
```

---
Expand Down
Loading
Loading