diff --git a/.claude/skills/dify-docs-api-reference/SKILL.md b/.claude/skills/dify-docs-api-reference/SKILL.md index 0ca2b5315..6d833334d 100644 --- a/.claude/skills/dify-docs-api-reference/SKILL.md +++ b/.claude/skills/dify-docs-api-reference/SKILL.md @@ -145,8 +145,7 @@ Pattern: `{verb}{AppType}{Resource}` ### Cross-API Links -When a description mentions another endpoint, add a markdown link. -Pattern: `/api-reference/{category}/{endpoint-name}` (kebab-case from endpoint summary). +When a description mentions another endpoint, add a markdown link. Pattern: `/api-reference/{category}/{endpoint-name}` (kebab-case from endpoint summary). ## Parameters @@ -222,7 +221,4 @@ Exception: Tags without a create operation (e.g., Conversations). GET list comes ## Post-Writing Verification -After completing the document: - -1. Invoke `dify-docs-terminology-check` to verify terminology consistency against the glossary and codebase. -2. Invoke `dify-docs-reader-test` to verify it from the reader's perspective. +After completing the document, run the post-writing checks listed in `writing-guides/index.md#post-writing-verification`. diff --git a/.claude/skills/dify-docs-env-vars/SKILL.md b/.claude/skills/dify-docs-env-vars/SKILL.md index a1ce22cd0..daf088f9f 100644 --- a/.claude/skills/dify-docs-env-vars/SKILL.md +++ b/.claude/skills/dify-docs-env-vars/SKILL.md @@ -143,7 +143,4 @@ The automated translation pipeline does not cover `en/self-host/configuration/en ## Post-Writing Verification -After completing the document: - -1. Invoke `dify-docs-terminology-check` to verify terminology consistency against the glossary and codebase. -2. Invoke `dify-docs-reader-test` to verify it from the reader's perspective. +After completing the document, run the post-writing checks listed in `writing-guides/index.md#post-writing-verification`. diff --git a/.claude/skills/dify-docs-format-check-cjk/SKILL.md b/.claude/skills/dify-docs-format-check-cjk/SKILL.md new file mode 100644 index 000000000..637b49096 --- /dev/null +++ b/.claude/skills/dify-docs-format-check-cjk/SKILL.md @@ -0,0 +1,158 @@ +--- +name: dify-docs-format-check-cjk +description: > + Check formatting compliance in changed Chinese and Japanese documentation + against writing-guides/formatting-guide.md, tools/translate/formatting-zh.md, + and tools/translate/formatting-ja.md. Use after finalizing a translation + batch, or when the user says "check formatting (zh/ja)", "check CJK + formatting", or "format audit" on translated content. +--- + +# CJK Formatting Check (Chinese + Japanese) + +## Purpose + +Verify changed Chinese (`zh/`) and Japanese (`ja/`) documentation against every rule in: + +- `writing-guides/formatting-guide.md` — general rules +- `tools/translate/formatting-zh.md` — Chinese-specific rules +- `tools/translate/formatting-ja.md` — Japanese-specific rules + +Mechanical rules are enforced by the linter script (`check-format-cjk.py`); judgment-call rules are checked by reading the file. The linter automatically selects the right rule set based on whether the path starts with `zh/` or `ja/`. + +## Before Starting + +Detect changed documentation files by combining: + +- `git diff --name-only` +- `git diff --cached --name-only` +- Untracked files from `git status --porcelain` (lines starting with `??`) + +Filter for `.mdx` and `.md` files under `zh/` or `ja/`. If no changed files are detected, ask the user which files to check. + +Because translations are typically produced as a zh/ja pair from the same English source, it is natural to audit both languages in a single session. + +## Checks + +### Part 1 — Deterministic (run the linter) + +Run the linter: + +```bash +python3 .claude/skills/dify-docs-format-check-cjk/check-format-cjk.py [ ...] +``` + +The script selects zh or ja rules based on the file path. Rules fall into three groups: shared (both zh and ja), zh-only, and ja-only. + +**Shared structural rules (zh + ja)** + +- `F-title-missing` — frontmatter missing `title`. +- `F-desc-trailing-period` — `description` ends with a period. +- `F-quote-needed` / `F-quote-unnecessary` / `F-single-quote` — frontmatter quoting rules. +- `F-blank-after-fm` — no blank line between frontmatter close and body. +- `H-trailing-hash`, `H-blank-before`, `H-blank-after`, `H-skip-level` — heading structure rules from the general guide. +- `B-trailing-colon-inside` — colon inside `**...**`. +- `L-asterisk-bullet`, `L-nested-indent`, `L-blank-before`, `L-blank-after` — list structure. +- `C-no-language`, `C-blank-before`, `C-blank-after` — code block rules. +- `Li-click-here`, `Li-http-external` — link rules. +- `I-raw-img-tag`, `I-alt-too-long`, `I-caption-alt-mismatch`, `I-filename-*` — image rules (same set as the EN skill). +- `M-tab-no-title`, `M-component-blank-before`, `M-component-blank-after` — Mintlify component rules. +- `S-double-blank`, `S-trailing-whitespace` — spacing. +- `P-em-dash-spaces`, `P-en-dash-spaces` — general punctuation. + +**Shared CJK rules (zh + ja)** + +- `CJK-latin-spacing` — CJK character directly adjacent to Latin letter, digit, or backtick without a space. Exceptions: punctuation boundary, start/end of line, inside code/URLs. +- `CJK-halfwidth-punct` — half-width punctuation `, . : ; ? ! ( )` directly adjacent to a CJK character. (Slash and other exceptions are handled by language-specific rules below.) +- `CJK-bold-no-space` — bold span `**...**` adjacent to CJK character without a space on each side. +- `CJK-link-no-space` — markdown link text adjacent to CJK character without a space on each side. +- `CJK-italic` — `*text*` italic used on a CJK span. Chinese and Japanese should use bold, never italic. +- `CJK-em-dash` — em dash `—` or double em dash `——` appears in CJK text. Restructure the sentence. +- `CJK-disclaimer-missing` — translation disclaimer (` ⚠️ ...`) missing directly below the frontmatter. +- `CJK-cross-lang-link` — internal link begins with the wrong language prefix (e.g., `/en/...` inside a `zh/` file). + +**Heading rules** + +- `H-heading-end-punct` — CJK heading ends with sentence-ending punctuation (`。,、;:`). + +**Chinese-only rules** + +- `ZH-ascii-ellipsis` — `...` used where Chinese ellipsis `……` is expected. +- `ZH-fullwidth-slash` — full-width slash `/` used; must be `/`. +- `ZH-quotes` — mainland-style double or single quotation marks `""`, `''` used; must be corner brackets `「」` (single) or `『』` (nested). +- `ZH-range-hyphen` — numeric range uses `-` or `–`; must use `~`. +- `ZH-percent-space` — space between a digit and `%` or `°`. + +**Japanese-only rules** + +- `JA-fullwidth-digit` — full-width digit used (`1`, `2`, ...). +- `JA-fullwidth-latin` — full-width Latin letter used (`A`, `B`, ...). +- `JA-fullwidth-space` — full-width space (` `) used. +- `JA-sentence-too-long` — sentence longer than 80 Japanese characters. +- `JA-go-prefix` — `ご` prefix used on a verb in the "avoid" list (`ご確認ください`, `ご参照ください`, `ご入力ください`). `ご利用` is allowed. +- `JA-heading-sentence-ending` — heading ends with `します` / `します。` / `します?`, indicating a full-sentence rather than noun-phrase form. +- `JA-style-mix` — the file contains both です/ます and だ/である forms in body text. Only one register should be used. + +### Part 2 — Judgment-call review (LLM reads each file) + +For each changed file, read it and look for: + +**Translatable elements** + +- Tab titles (``) translated with the glossary value. +- Frame captions and image alt text translated. +- Bold UI labels translated, matching the codebase i18n file (`web/i18n/zh-Hans/` for zh, `web/i18n/ja-JP/` for ja). Cross-check with the terminology-check skill. +- Natural-language prompt examples inside code blocks translated. Variable placeholders (`{{variable_name}}`) stay unchanged. + +**Anchor translation (zh/ja)** + +- Cross-references `[text](#anchor)` or `[text](/path#anchor)` use the translated heading slug, not the English original. Example: the heading `## 响应` produces `#响应`, not `#response`. + +**Chinese style** + +- Enumeration comma `、` used for parallel items within a sentence (where Chinese separates items that would use commas in English). +- Chinese ellipsis `……` used correctly; not combined with `等` in the same phrase. +- Arabic numerals used for technical content (`3 种`, not `三种`) — judgment call since some idiomatic expressions use Chinese numerals. +- Translationese patterns from `tools/translate/formatting-zh.md`: redundant `你的`, unnecessary `会`, `当...时` wrappers, `能够` instead of `能`, `可以` where `可` would suffice. Flag sentences that read as machine-translated. + +**Japanese style** + +- `です/ます` maintained in body text. +- Headings are noun phrases, not full sentences. +- Katakana long-vowel mark rules: short loanwords (3 morae or fewer) keep the trailing `ー`; longer ones drop it. Established compound katakana terms (ワークフロー, ナレッジベース) match the glossary. +- Middle dot `・` only where needed for readability; established compound terms have no middle dot. +- Translationese patterns from `tools/translate/formatting-ja.md`: redundant `あなたの`, `〜することができます` instead of `〜できます`, `〜とき/〜場合` wrappers, stacked `の`. Flag sentences that read as machine-translated. + +**General** + +- Terminology matches the glossary (`writing-guides/glossary.md`). Prefer invoking the terminology-check skill for rigorous checking rather than duplicating its logic here. + +## Output Format + +``` +## CJK Formatting Check Results + +### File: {path} ({lang}) + +**Deterministic violations** ({n}) +- Line {n} [{rule-id}]: {message} +- ... + +**Judgment-call findings** ({n}) +- Line {n}: {description of issue} — {rule area} +- ... + +**Clean checks** +- ✅ Disclaimer present +- ✅ No bold/CJK spacing issues +- ... +``` + +Group by file. If a file has no issues at all, report a single ✅ line. + +## Important + +- Do NOT modify any files. This is a read-only audit. +- When a deterministic rule flags something that is clearly intentional on inspection (e.g., a half-width comma inside a Latin acronym, a mainland quotation mark quoted from an external source), surface it but note the ambiguity so the user can decide. +- The terminology-check skill is the authoritative source for glossary and UI-label verification. If a finding overlaps, defer to that skill and just note the overlap. +- Japanese sentence-length and style-mix checks are heuristic. They flag candidates for human review; they are not definitive rules violations. diff --git a/.claude/skills/dify-docs-format-check-cjk/check-format-cjk.py b/.claude/skills/dify-docs-format-check-cjk/check-format-cjk.py new file mode 100644 index 000000000..54d4b86e8 --- /dev/null +++ b/.claude/skills/dify-docs-format-check-cjk/check-format-cjk.py @@ -0,0 +1,883 @@ +#!/usr/bin/env python3 +"""CJK (Chinese + Japanese) documentation format linter for Dify docs. + +Checks a list of .mdx or .md files under zh/ or ja/ against the deterministic +rules in writing-guides/formatting-guide.md, +tools/translate/formatting-zh.md, and tools/translate/formatting-ja.md. +Prints a grouped report to stdout. + +Usage: + python3 check-format-cjk.py [ ...] +""" +from __future__ import annotations + +import re +import sys +from dataclasses import dataclass +from pathlib import Path + + +# ---------- data types ---------- + +@dataclass +class Violation: + line: int + rule: str + message: str + + +# ---------- constants ---------- + +FENCE_RE = re.compile(r'^(\s*)(`{3,}|~{3,})(.*)$') +HEADING_RE = re.compile(r'^(#{1,6})(\s+)(.+?)\s*$') +FM_RE = re.compile(r'\A---\n(.*?)\n---\n', re.DOTALL) +FM_FIELD_RE = re.compile(r'^([A-Za-z][\w-]*)\s*:\s*(.*?)\s*$') +INLINE_CODE_RE = re.compile(r'`[^`\n]*`') +URL_RE = re.compile(r'https?://[^\s)()<>]+') +LINK_RE = re.compile(r'\[([^\]\n]+)\]\(([^)\n]+)\)') + +# CJK: CJK Unified Ideographs + hiragana/katakana + extensions +CJK = r'[\u3040-\u30FF\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF]' +CJK_RE = re.compile(CJK) +LATIN_ALNUM = r'[A-Za-z0-9]' + +CJK_PUNCT = ',。、;:!?()「」『』【】〈〉《》・…' + +# Japanese heuristic char ranges +HIRAGANA = r'[\u3040-\u309F]' +KATAKANA = r'[\u30A0-\u30FF]' +JA_ONLY = r'[\u3040-\u30FF]' + + +# ---------- shared helpers ---------- + +def _strip_inline_code(line: str) -> str: + return INLINE_CODE_RE.sub('', line) + + +def _strip_code_and_urls(line: str) -> str: + s = INLINE_CODE_RE.sub('', line) + # collapse markdown link syntax [text](url) -> text so the bracket and + # paren characters don't count as prose punctuation + s = re.sub(r'\[([^\]\n]+)\]\([^)\n]+\)', r'\1', s) + s = URL_RE.sub('', s) + return s + + +def _yaml_needs_quotes(value: str) -> bool: + """Match the documented rule: quote only when the value contains a colon + followed by a space. Other YAML edge cases are intentionally not flagged + because the formatting guide restricts the rule to the `: ` case.""" + return ': ' in value + + +def _unquote(v: str) -> tuple[str, str]: + v = v.rstrip() + if len(v) >= 2 and v[0] == '"' and v[-1] == '"': + return v[1:-1], '"' + if len(v) >= 2 and v[0] == "'" and v[-1] == "'": + return v[1:-1], "'" + return v, '' + + +def detect_lang(path: Path) -> str: + s = path.as_posix() + if s.startswith('zh/') or '/zh/' in s: + return 'zh' + if s.startswith('ja/') or '/ja/' in s: + return 'ja' + return 'unknown' + + +# ---------- shared structural checks ---------- + +def check_frontmatter(text: str, lines: list[str]) -> list[Violation]: + vs: list[Violation] = [] + m = FM_RE.match(text) + if not m: + return vs + has_title = False + for offset, line in enumerate(m.group(1).split('\n')): + line_num = 2 + offset + fm = FM_FIELD_RE.match(line) + if not fm: + continue + field, raw = fm.group(1), fm.group(2) + if field == 'title': + has_title = True + if field == 'description': + stripped, _ = _unquote(raw) + if stripped.rstrip().endswith('.') or stripped.rstrip().endswith('。'): + vs.append(Violation(line_num, 'F-desc-trailing-period', + f'description ends with a period: {raw!r}')) + if field in ('title', 'description', 'sidebarTitle'): + stripped, q = _unquote(raw) + needs = _yaml_needs_quotes(stripped) + if needs and not q: + vs.append(Violation(line_num, 'F-quote-needed', + f'{field!r} needs double quotes.')) + elif q == "'": + vs.append(Violation(line_num, 'F-single-quote', + f'{field!r} uses single quotes; use double.')) + elif q == '"' and not needs: + vs.append(Violation(line_num, 'F-quote-unnecessary', + f'{field!r} is quoted unnecessarily.')) + if not has_title: + vs.append(Violation(1, 'F-title-missing', + 'Frontmatter missing required `title`.')) + fm_end_line = text[:m.end()].count('\n') + if fm_end_line < len(lines): + after = lines[fm_end_line] + if after.strip() != '': + vs.append(Violation(fm_end_line + 1, 'F-blank-after-fm', + 'No blank line after frontmatter.')) + return vs + + +def check_headings(lines: list[str]) -> list[Violation]: + vs: list[Violation] = [] + in_fence = False + prev_depth = 0 + for i, line in enumerate(lines, 1): + if FENCE_RE.match(line): + in_fence = not in_fence + continue + if in_fence: + continue + hm = HEADING_RE.match(line) + if not hm: + continue + depth = len(hm.group(1)) + title = hm.group(3) + if title.rstrip().endswith('#'): + vs.append(Violation(i, 'H-trailing-hash', + 'Heading has a trailing `#`.')) + if prev_depth and depth > prev_depth + 1: + vs.append(Violation(i, 'H-skip-level', + f'Heading jumped H{prev_depth} -> H{depth}.')) + prev_depth = depth + if i > 2 and lines[i - 2].strip() != '': + vs.append(Violation(i, 'H-blank-before', + 'Missing blank line before heading.')) + if i < len(lines) and lines[i].strip() != '': + vs.append(Violation(i, 'H-blank-after', + 'Missing blank line after heading.')) + # heading ending punctuation in CJK context + last = title.rstrip().rstrip('#').rstrip() + if last and last[-1] in '。,、;:': + vs.append(Violation( + i, 'H-heading-end-punct', + f'Heading ends with punctuation {last[-1]!r}; drop it.')) + return vs + + +def check_bold_italic(lines: list[str], lang: str) -> list[Violation]: + vs: list[Violation] = [] + in_fence = False + bold_colon_re = re.compile(r'\*\*[^*\n]+?[:\uFF1A]\*\*') + for i, line in enumerate(lines, 1): + if FENCE_RE.match(line): + in_fence = not in_fence + continue + if in_fence: + continue + s = _strip_inline_code(line) + if bold_colon_re.search(s): + parts = s.split('**') + if len(parts) >= 3 and len(parts) % 2 == 1: + for idx in range(1, len(parts) - 1, 2): + seg = parts[idx] + if seg and seg[-1] in (':', '\uFF1A'): + vs.append(Violation( + i, 'B-trailing-colon-inside', + f'Colon inside bold: "**{seg}**"')) + # CJK italic detection: *text* where text contains CJK, not in code + # Skip lines where the line is predominantly code markers. + ital_re = re.compile( + r'(? list[Violation]: + vs: list[Violation] = [] + in_fence = False + for i, line in enumerate(lines, 1): + if FENCE_RE.match(line): + in_fence = not in_fence + continue + if in_fence: + continue + if re.match(r'^(\s*)\*\s', line): + vs.append(Violation(i, 'L-asterisk-bullet', + 'List uses `* ` bullet; use `- `.')) + m = re.match(r'^( +)[-*]\s', line) + if m: + indent = len(m.group(1)) + if indent % 2 != 0: + vs.append(Violation( + i, 'L-nested-indent', + f'Nested list indented with {indent} spaces.')) + list_re = re.compile(r'^\s*[-*]\s|^\s*\d+\.\s') + in_fence = False + prev_list = False + for i, line in enumerate(lines, 1): + if FENCE_RE.match(line): + in_fence = not in_fence + prev_list = False + continue + if in_fence: + continue + is_list = list_re.match(line) is not None + if is_list and not prev_list and i > 1: + prev_line = lines[i - 2] + if not HEADING_RE.match(prev_line) and prev_line.strip() != '': + vs.append(Violation(i, 'L-blank-before', + 'Missing blank line before list.')) + if prev_list and not is_list and line.strip() != '': + vs.append(Violation(i, 'L-blank-after', + 'Missing blank line after list.')) + prev_list = is_list + return vs + + +def check_code_blocks(lines: list[str]) -> list[Violation]: + vs: list[Violation] = [] + in_fence = False + for i, line in enumerate(lines, 1): + m = FENCE_RE.match(line) + if not m: + continue + if not in_fence: + in_fence = True + lang_tag = m.group(3).strip() + if not lang_tag: + vs.append(Violation(i, 'C-no-language', + 'Code block has no language tag.')) + if i > 1 and lines[i - 2].strip() != '': + vs.append(Violation(i, 'C-blank-before', + 'Missing blank line before code block.')) + else: + in_fence = False + if i < len(lines) and lines[i].strip() != '': + vs.append(Violation(i, 'C-blank-after', + 'Missing blank line after code block.')) + return vs + + +DISCLAIMER_MARKERS = ('本文档由 AI 自动翻译', + 'このドキュメントは AI によって自動翻訳') + + +def check_links(lines: list[str], lang: str) -> list[Violation]: + vs: list[Violation] = [] + in_fence = False + click_here = ('click here', 'here', '点击这里', '点击此处', 'こちら', + 'ここ', 'クリック') + wrong_prefix = '/en/' + target_prefix = f'/{lang}/' + for i, line in enumerate(lines, 1): + if FENCE_RE.match(line): + in_fence = not in_fence + continue + if in_fence: + continue + # Skip cross-lang link checks inside the translation disclaimer — + # the disclaimer intentionally links to the English source. + is_disclaimer_line = any(m in line for m in DISCLAIMER_MARKERS) + s = _strip_inline_code(line) + for m in LINK_RE.finditer(s): + text, url = m.group(1), m.group(2) + t = text.strip().lower().rstrip('.。') + if t in click_here: + vs.append(Violation(i, 'Li-click-here', + f'Non-descriptive link text: {text!r}.')) + if url.startswith('http://'): + vs.append(Violation(i, 'Li-http-external', + f'External http:// link: {url!r}.')) + if url.startswith(wrong_prefix) and not is_disclaimer_line: + vs.append(Violation( + i, 'CJK-cross-lang-link', + f'Internal link starts with {wrong_prefix!r} in {lang}/ ' + f'file; use {target_prefix!r}.')) + return vs + + +def check_images(lines: list[str]) -> list[Violation]: + vs: list[Violation] = [] + in_fence = False + raw_img_re = re.compile(r']*)>') + caption_re = re.compile(r'caption=\"([^\"]*)\"') + for i, line in enumerate(lines, 1): + if FENCE_RE.match(line): + in_fence = not in_fence + continue + if in_fence: + continue + if raw_img_re.search(line): + vs.append(Violation( + i, 'I-raw-img-tag', + 'Raw tag; use with markdown image.')) + # frame/caption pairing + i = 0 + while i < len(lines): + line = lines[i] + fm = frame_re.search(line) + if fm: + cap_m = caption_re.search(fm.group(1) or '') + caption = cap_m.group(1) if cap_m else None + alts: list[tuple[int, str, str]] = [] + j = i + 1 + while j < len(lines) and '' not in lines[j]: + for mm in md_img_re.finditer(lines[j]): + alts.append((j + 1, mm.group(1), mm.group(2))) + j += 1 + if caption is not None and alts: + alt_line, alt, _ = alts[0] + if alt != caption: + vs.append(Violation( + alt_line, 'I-caption-alt-mismatch', + f'Frame caption "{caption}" != alt "{alt}".')) + i = j + 1 + else: + i += 1 + in_fence = False + for i, line in enumerate(lines, 1): + if FENCE_RE.match(line): + in_fence = not in_fence + continue + if in_fence: + continue + for mm in md_img_re.finditer(line): + alt, url = mm.group(1), mm.group(2) + if len(alt) > 125: + vs.append(Violation(i, 'I-alt-too-long', + f'Alt text {len(alt)} chars; <125.')) + if url.startswith('/images/'): + fname = url.rsplit('/', 1)[-1] + stem, _, ext = fname.rpartition('.') + if ext and ext != ext.lower(): + vs.append(Violation(i, 'I-filename-uppercase-ext', + f'Uppercase ext: {fname!r}.')) + if re.search(r'@\d+x', fname): + vs.append(Violation(i, 'I-filename-retina-suffix', + f'Retina suffix: {fname!r}.')) + if re.match(r'^(CleanShot|Screenshot|IMG_|image\d)', fname, + re.IGNORECASE): + vs.append(Violation(i, 'I-filename-default-tool', + f'Default tool name: {fname!r}.')) + if re.search(r'[_\s]', stem) or re.search(r'[A-Z]', stem): + vs.append(Violation(i, 'I-filename-non-kebab', + f'Not kebab-case: {fname!r}.')) + return vs + + +def check_mintlify(lines: list[str]) -> list[Violation]: + vs: list[Violation] = [] + in_fence = False + callout_re = re.compile(r'^\s*<(Info|Tip|Note|Warning)\b[^>]*>', + re.IGNORECASE) + closer_re = re.compile(r'^\s*\s*$', + re.IGNORECASE) + tab_re = re.compile(r']*)?>') + tab_title_re = re.compile(r'\btitle\s*=\s*"[^"]*"') + for i, line in enumerate(lines, 1): + if FENCE_RE.match(line): + in_fence = not in_fence + continue + if in_fence: + continue + if callout_re.match(line) and i > 1 and lines[i - 2].strip() != '': + vs.append(Violation(i, 'M-component-blank-before', + 'Missing blank line before callout.')) + if closer_re.match(line) and i < len(lines) \ + and lines[i].strip() != '': + vs.append(Violation(i, 'M-component-blank-after', + 'Missing blank line after callout.')) + for m in tab_re.finditer(line): + if not tab_title_re.search(m.group(0)): + vs.append(Violation(i, 'M-tab-no-title', + ' without title attribute.')) + return vs + + +def check_spacing(lines: list[str]) -> list[Violation]: + vs: list[Violation] = [] + in_fence = False + blank_run = 0 + for i, line in enumerate(lines, 1): + if FENCE_RE.match(line): + in_fence = not in_fence + blank_run = 0 + continue + if in_fence: + continue + if line.strip() == '': + blank_run += 1 + if blank_run == 2: + vs.append(Violation(i, 'S-double-blank', + 'Consecutive blank lines.')) + else: + blank_run = 0 + if line != line.rstrip(): + vs.append(Violation(i, 'S-trailing-whitespace', + 'Line has trailing whitespace.')) + return vs + + +def check_dashes(lines: list[str]) -> list[Violation]: + vs: list[Violation] = [] + in_fence = False + em_space = re.compile(r'\s—\s|\s—|—\s') + en_space = re.compile(r'\d\s+–\s+\d|\d\s+–\d|\d–\s+\d') + for i, line in enumerate(lines, 1): + if FENCE_RE.match(line): + in_fence = not in_fence + continue + if in_fence: + continue + s = _strip_inline_code(line) + if em_space.search(s): + vs.append(Violation(i, 'P-em-dash-spaces', + 'Em dash has surrounding spaces.')) + if en_space.search(s): + vs.append(Violation(i, 'P-en-dash-spaces', + 'En dash in range has surrounding spaces.')) + return vs + + +# ---------- CJK-shared rules ---------- + +def check_cjk_latin_spacing(lines: list[str]) -> list[Violation]: + vs: list[Violation] = [] + in_fence = False + # CJK followed immediately by Latin alphanumeric or leading ` + # (excluding punctuation-adjacent cases) + pat_cjk_latin = re.compile(rf'{CJK}[A-Za-z0-9`]') + pat_latin_cjk = re.compile(rf'[A-Za-z0-9`]{CJK}') + for i, line in enumerate(lines, 1): + if FENCE_RE.match(line): + in_fence = not in_fence + continue + if in_fence: + continue + s = _strip_code_and_urls(line) + if pat_cjk_latin.search(s) or pat_latin_cjk.search(s): + vs.append(Violation( + i, 'CJK-latin-spacing', + 'CJK character adjacent to Latin/digit/backtick without ' + 'space.')) + return vs + + +def check_cjk_halfwidth_punct(lines: list[str]) -> list[Violation]: + vs: list[Violation] = [] + in_fence = False + # half-width , . : ; ? ! ( ) adjacent to CJK + pat = re.compile(rf'{CJK}[,\.:;?!()]|[,\.:;?!()]{CJK}') + for i, line in enumerate(lines, 1): + if FENCE_RE.match(line): + in_fence = not in_fence + continue + if in_fence: + continue + s = _strip_code_and_urls(line) + if pat.search(s): + vs.append(Violation( + i, 'CJK-halfwidth-punct', + 'Half-width punctuation adjacent to CJK character.')) + return vs + + +def check_cjk_bold_spacing(lines: list[str]) -> list[Violation]: + vs: list[Violation] = [] + in_fence = False + # Bold adjacent to CJK without space: X**bold** or **bold**X where X is + # CJK and no space between. + pat_after = re.compile(rf'{CJK}\*\*[^*\n]+?\*\*') + pat_before = re.compile(rf'\*\*[^*\n]+?\*\*{CJK}') + for i, line in enumerate(lines, 1): + if FENCE_RE.match(line): + in_fence = not in_fence + continue + if in_fence: + continue + s = _strip_inline_code(line) + if pat_after.search(s) or pat_before.search(s): + vs.append(Violation( + i, 'CJK-bold-no-space', + 'Bold adjacent to CJK without space on that side.')) + return vs + + +def check_cjk_link_spacing(lines: list[str]) -> list[Violation]: + vs: list[Violation] = [] + in_fence = False + # [text](url) adjacent to CJK without space + pat_after = re.compile(rf'{CJK}\[[^\]\n]+?\]\([^)\n]+?\)') + pat_before = re.compile(rf'\[[^\]\n]+?\]\([^)\n]+?\){CJK}') + for i, line in enumerate(lines, 1): + if FENCE_RE.match(line): + in_fence = not in_fence + continue + if in_fence: + continue + s = _strip_inline_code(line) + if pat_after.search(s) or pat_before.search(s): + vs.append(Violation( + i, 'CJK-link-no-space', + 'Link text adjacent to CJK without space on that side.')) + return vs + + +def check_cjk_em_dash(lines: list[str]) -> list[Violation]: + vs: list[Violation] = [] + in_fence = False + # em dash in text that is predominantly CJK (contains at least one CJK + # char on the line outside code) + for i, line in enumerate(lines, 1): + if FENCE_RE.match(line): + in_fence = not in_fence + continue + if in_fence: + continue + s = _strip_inline_code(line) + if ('—' in s or '——' in s) and CJK_RE.search(s): + vs.append(Violation( + i, 'CJK-em-dash', + 'Em dash in CJK text; restructure without the dash.')) + return vs + + +def check_cjk_disclaimer(lines: list[str], text: str, lang: str + ) -> list[Violation]: + # Must appear directly below frontmatter. Permit a single blank line + # between close of frontmatter and the disclaimer. + vs: list[Violation] = [] + m = FM_RE.match(text) + if not m: + return vs + fm_end_line = text[:m.end()].count('\n') + # scan next ~10 lines for the disclaimer + needle = '本文档由 AI 自动翻译' if lang == 'zh' \ + else 'このドキュメントは AI によって自動翻訳' if lang == 'ja' \ + else None + if not needle: + return vs + window = '\n'.join(lines[fm_end_line:fm_end_line + 10]) + if needle not in window: + vs.append(Violation(fm_end_line + 1, 'CJK-disclaimer-missing', + f'Translation disclaimer not found near top ' + f'(expected text containing {needle!r}).')) + return vs + + +# ---------- Chinese-only rules ---------- + +def check_zh_ascii_ellipsis(lines: list[str]) -> list[Violation]: + vs: list[Violation] = [] + in_fence = False + pat = re.compile(rf'(?:{CJK}|\s)\.{{3}}') + for i, line in enumerate(lines, 1): + if FENCE_RE.match(line): + in_fence = not in_fence + continue + if in_fence: + continue + s = _strip_code_and_urls(line) + if '...' in s and CJK_RE.search(s): + vs.append(Violation( + i, 'ZH-ascii-ellipsis', + '`...` used in Chinese text; use `……` instead.')) + return vs + + +def check_zh_fullwidth_slash(lines: list[str]) -> list[Violation]: + vs: list[Violation] = [] + in_fence = False + for i, line in enumerate(lines, 1): + if FENCE_RE.match(line): + in_fence = not in_fence + continue + if in_fence: + continue + s = _strip_code_and_urls(line) + if '/' in s: + vs.append(Violation(i, 'ZH-fullwidth-slash', + 'Full-width slash `/`; use `/`.')) + return vs + + +def check_zh_quotes(lines: list[str]) -> list[Violation]: + vs: list[Violation] = [] + in_fence = False + # mainland curly quotes or straight quotes around CJK content + for i, line in enumerate(lines, 1): + if FENCE_RE.match(line): + in_fence = not in_fence + continue + if in_fence: + continue + s = _strip_code_and_urls(line) + # Curly double quotes + if re.search(rf'“[^”\n]*{CJK}[^”\n]*”', s): + vs.append(Violation( + i, 'ZH-quotes', + 'Mainland-style double quotes around CJK; use 「」.')) + # Curly single quotes + if re.search(rf'‘[^’\n]*{CJK}[^’\n]*’', s): + vs.append(Violation( + i, 'ZH-quotes', + 'Mainland-style single quotes around CJK; use 「」.')) + return vs + + +def check_zh_range_hyphen(lines: list[str]) -> list[Violation]: + vs: list[Violation] = [] + in_fence = False + # digit-digit or digit–digit in Chinese context where ~ should be used + pat = re.compile(r'\d+\s?[-–]\s?\d+') + for i, line in enumerate(lines, 1): + if FENCE_RE.match(line): + in_fence = not in_fence + continue + if in_fence: + continue + s = _strip_code_and_urls(line) + if not CJK_RE.search(s): + continue + for m in pat.finditer(s): + # look at surrounding 8 chars for CJK context + start = max(0, m.start() - 8) + end = min(len(s), m.end() + 8) + ctx = s[start:end] + if CJK_RE.search(ctx): + vs.append(Violation( + i, 'ZH-range-hyphen', + f'Numeric range {m.group(0)!r} uses hyphen/en-dash; ' + 'use `~`.')) + break # one flag per line is enough + return vs + + +def check_zh_percent_space(lines: list[str]) -> list[Violation]: + vs: list[Violation] = [] + in_fence = False + pat = re.compile(r'\d\s+[%°]') + for i, line in enumerate(lines, 1): + if FENCE_RE.match(line): + in_fence = not in_fence + continue + if in_fence: + continue + s = _strip_code_and_urls(line) + if pat.search(s): + vs.append(Violation(i, 'ZH-percent-space', + 'Space before `%` or `°`.')) + return vs + + +# ---------- Japanese-only rules ---------- + +FW_DIGIT_RE = re.compile(r'[\uFF10-\uFF19]') +FW_LATIN_RE = re.compile(r'[\uFF21-\uFF3A\uFF41-\uFF5A]') +FW_SPACE = '\u3000' +SENTENCE_END_RE = re.compile(r'[。!?]') + + +def check_ja_fullwidth_ascii(lines: list[str]) -> list[Violation]: + vs: list[Violation] = [] + in_fence = False + for i, line in enumerate(lines, 1): + if FENCE_RE.match(line): + in_fence = not in_fence + continue + if in_fence: + continue + s = _strip_code_and_urls(line) + if FW_DIGIT_RE.search(s): + vs.append(Violation(i, 'JA-fullwidth-digit', + 'Full-width digit used; use half-width.')) + if FW_LATIN_RE.search(s): + vs.append(Violation(i, 'JA-fullwidth-latin', + 'Full-width Latin letter; use half-width.')) + if FW_SPACE in s: + vs.append(Violation(i, 'JA-fullwidth-space', + 'Full-width space ` `; use half-width.')) + return vs + + +def check_ja_sentence_length(lines: list[str]) -> list[Violation]: + vs: list[Violation] = [] + in_fence = False + for i, line in enumerate(lines, 1): + if FENCE_RE.match(line): + in_fence = not in_fence + continue + if in_fence: + continue + if HEADING_RE.match(line): + continue + s = _strip_code_and_urls(line).strip() + if not CJK_RE.search(s): + continue + # split on Japanese sentence-ending punctuation, then count chars + segments = SENTENCE_END_RE.split(s) + for seg in segments: + seg = seg.strip() + if len(seg) > 80 and CJK_RE.search(seg): + vs.append(Violation( + i, 'JA-sentence-too-long', + f'Sentence ~{len(seg)} characters; split sentences >80.')) + break + return vs + + +def check_ja_go_prefix(lines: list[str]) -> list[Violation]: + vs: list[Violation] = [] + in_fence = False + bad = ('ご確認', 'ご参照', 'ご入力') + for i, line in enumerate(lines, 1): + if FENCE_RE.match(line): + in_fence = not in_fence + continue + if in_fence: + continue + s = _strip_code_and_urls(line) + for w in bad: + if w in s: + vs.append(Violation( + i, 'JA-go-prefix', + f'Overly formal honorific {w!r}; drop the ご prefix.')) + return vs + + +def check_ja_heading_sentence(lines: list[str]) -> list[Violation]: + vs: list[Violation] = [] + in_fence = False + for i, line in enumerate(lines, 1): + if FENCE_RE.match(line): + in_fence = not in_fence + continue + if in_fence: + continue + hm = HEADING_RE.match(line) + if not hm: + continue + t = hm.group(3).rstrip(' #。!?').rstrip() + if re.search(r'します$|します。$', t): + vs.append(Violation( + i, 'JA-heading-sentence-ending', + 'Japanese heading ends with "します"; prefer noun-phrase ' + 'form (e.g., 〜の作成).')) + return vs + + +def check_ja_style_mix(lines: list[str], text: str) -> list[Violation]: + # Detect mix of です/ます and だ/である in body text outside code. + vs: list[Violation] = [] + body_parts = [] + in_fence = False + for line in lines: + if FENCE_RE.match(line): + in_fence = not in_fence + continue + if in_fence: + continue + body_parts.append(_strip_code_and_urls(line)) + body = '\n'.join(body_parts) + polite = bool(re.search(r'です。|ます。|ですか。|ません。', body)) + plain = bool(re.search(r'だ。|である。|ではない。', body)) + if polite and plain: + vs.append(Violation( + 1, 'JA-style-mix', + 'File mixes です/ます and だ/である forms; use one register.')) + return vs + + +# ---------- runner ---------- + +def lint_file(path: Path) -> tuple[str, dict[str, list[Violation]]]: + lang = detect_lang(path) + text = path.read_text() + lines = text.split('\n') + results: dict[str, list[Violation]] = {} + + results['frontmatter'] = check_frontmatter(text, lines) + results['headings'] = check_headings(lines) + results['bold_italic'] = check_bold_italic(lines, lang) + results['lists'] = check_lists(lines) + results['code'] = check_code_blocks(lines) + results['links'] = check_links(lines, lang) + results['images'] = check_images(lines) + results['mintlify'] = check_mintlify(lines) + results['spacing'] = check_spacing(lines) + results['dashes'] = check_dashes(lines) + + results['cjk_latin_spacing'] = check_cjk_latin_spacing(lines) + results['cjk_halfwidth_punct'] = check_cjk_halfwidth_punct(lines) + results['cjk_bold_spacing'] = check_cjk_bold_spacing(lines) + results['cjk_link_spacing'] = check_cjk_link_spacing(lines) + results['cjk_em_dash'] = check_cjk_em_dash(lines) + results['cjk_disclaimer'] = check_cjk_disclaimer(lines, text, lang) + + if lang == 'zh': + results['zh_ellipsis'] = check_zh_ascii_ellipsis(lines) + results['zh_slash'] = check_zh_fullwidth_slash(lines) + results['zh_quotes'] = check_zh_quotes(lines) + results['zh_range'] = check_zh_range_hyphen(lines) + results['zh_percent'] = check_zh_percent_space(lines) + if lang == 'ja': + results['ja_fullwidth_ascii'] = check_ja_fullwidth_ascii(lines) + results['ja_sentence_length'] = check_ja_sentence_length(lines) + results['ja_go_prefix'] = check_ja_go_prefix(lines) + results['ja_heading_sentence'] = check_ja_heading_sentence(lines) + results['ja_style_mix'] = check_ja_style_mix(lines, text) + + return lang, results + + +def print_report(path: Path, lang: str, results: dict[str, list[Violation]]): + total = sum(len(v) for v in results.values()) + print(f'\n### {path} ({lang})') + if total == 0: + print(' ✅ no deterministic issues found') + return + by_line: list[Violation] = [] + for group in results.values(): + by_line.extend(group) + by_line.sort(key=lambda v: (v.line, v.rule)) + for v in by_line: + print(f' {v.line:>5} [{v.rule}] {v.message}') + + +def main(argv: list[str]) -> int: + if not argv: + print('usage: check-format-cjk.py [ ...]', file=sys.stderr) + return 2 + total = 0 + for arg in argv: + p = Path(arg) + if not p.exists(): + print(f'skip (missing): {p}', file=sys.stderr) + continue + lang = detect_lang(p) + if lang == 'unknown': + print(f'skip (non-zh/ja): {p}', file=sys.stderr) + continue + lang, results = lint_file(p) + print_report(p, lang, results) + total += sum(len(v) for v in results.values()) + print(f'\nTotal violations: {total}') + return 0 if total == 0 else 1 + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/.claude/skills/dify-docs-format-check-en/SKILL.md b/.claude/skills/dify-docs-format-check-en/SKILL.md new file mode 100644 index 000000000..60eeb1cb2 --- /dev/null +++ b/.claude/skills/dify-docs-format-check-en/SKILL.md @@ -0,0 +1,175 @@ +--- +name: dify-docs-format-check-en +description: > + Check formatting compliance in changed English documentation against + writing-guides/formatting-guide.md. Use after finalizing a draft, or when + the user says "check formatting", "format audit", "format check" on English + content. +--- + +# English Formatting Check + +## Purpose + +Verify changed English documentation against every rule in `writing-guides/formatting-guide.md`. Mechanical rules are enforced by a linter script (`check-format-en.py`); judgment-call rules are checked by reading the file. + +## Before Starting + +Detect changed documentation files by combining: + +- `git diff --name-only` (unstaged changes in tracked files) +- `git diff --cached --name-only` (staged changes) +- Untracked files from `git status --porcelain` (lines starting with `??`) + +Filter for `.mdx` and `.md` files under `en/`. If no changed files are detected, ask the user which files to check. + +## Checks + +### Part 1 — Deterministic (run the linter) + +Run the linter and capture its output: + +```bash +python3 .claude/skills/dify-docs-format-check-en/check-format-en.py [ ...] +``` + +The script checks these rules. Each violation includes the file path, line number, rule ID, severity, and a short message. + +**Frontmatter** + +- `F-title-missing` — file lacks `title` field. +- `F-desc-trailing-period` — `description` ends with a period. +- `F-quote-needed` — value contains `: ` (colon + space) but is not quoted. +- `F-quote-unnecessary` — value is quoted but contains no character that requires quoting. +- `F-single-quote` — value uses single quotes; should use double. +- `F-blank-after-fm` — no blank line between closing `---` and body. + +**Headings** + +- `H-trailing-hash` — heading ends with a trailing `#`. +- `H-blank-before` / `H-blank-after` — missing blank line adjacent to a heading. +- `H-skip-level` — jump in depth (e.g., H2 to H4 without H3). +- `H-ing-verb` — heading starts with a verb in `-ing` form where the base form should be used. The linter carries a curated verb list and strips leading numeric/parenthetical prefixes (`1.`, `(a)`, etc.) before detection. Section-name gerunds in a skip list are not flagged (`Troubleshooting`, `Logging`, `Getting Started`, `Monitoring Data List`, etc.). The skip list lives in the script constant `SKIP_TEXTS`. + +**Bold and Italic** + +- `B-trailing-colon-inside` — `**X:**` or `**X:**` patterns. Colon must be outside the bold markers. +- `B-underscore-italic` — `_italic_` style; must use `*italic*`. + +**Lists** + +- `L-asterisk-bullet` — `* ` used as a bullet; must use `- `. +- `L-blank-before` / `L-blank-after` — missing blank line around a list block. +- `L-nested-indent` — nested list item not indented by a multiple of 2 spaces. + +**Code** + +- `C-no-language` — fenced code block opens with ` ``` ` but no language tag. +- `C-blank-before` / `C-blank-after` — missing blank line around a fenced code block. + +**Links** + +- `Li-click-here` — link text is `click here`, `here`, or `Click here`. +- `Li-http-external` — external link uses `http://` rather than `https://`. +- `Li-internal-no-prefix` — internal link starts with a relative path or omits the language prefix (`/en/...`). + +**Images** + +- `I-raw-img-tag` — raw `` element used instead of `` + markdown. +- `I-alt-empty` — `![](...)` with no alt text on a non-decorative image. The script flags every empty alt as a prompt to confirm the image is truly decorative. +- `I-alt-too-long` — alt text exceeds 125 characters. +- `I-caption-alt-mismatch` — `` value differs from the alt text of the enclosed markdown image. +- `I-filename-uppercase-ext` — filename ends with an uppercase extension (`.PNG`, `.JPG`). +- `I-filename-retina-suffix` — filename contains `@2x`, `@3x`, or similar. +- `I-filename-default-tool` — filename begins with `CleanShot`, `Screenshot`, `IMG_`, etc. +- `I-filename-non-kebab` — filename has underscores, spaces, uppercase letters, or non-ASCII characters. +- `I-filename-ing-verb` — filename begins with a verb in `-ing` form. + +**Mintlify Components** + +- `M-tab-no-title` — `` without a `title` attribute. +- `M-component-blank-before` / `M-component-blank-after` — missing blank line around ``, ``, ``, or ``. + +**UI Element References** + +- `U-menu-arrow` — menu path uses `→`, `->`, or `=>` instead of `>`. + +**Spacing** + +- `S-double-blank` — two or more consecutive blank lines. +- `S-trailing-whitespace` — line ends with whitespace. + +**Punctuation** + +- `P-em-dash-spaces` — em dash `—` surrounded by spaces. +- `P-en-dash-spaces` — en dash `–` surrounded by spaces in a numeric range. +- `P-fullwidth-in-english` — full-width punctuation (`,。;:!?()、`) adjacent to ASCII letters. + +### Part 2 — Judgment-call review (LLM reads each file) + +For each changed file, read it and look for: + +**Headings** + +- **Title case**: every heading uses proper title case. Major words capitalized; minor words (`a`, `and`, `the`, `of`, `in`, `on`, `to`, etc.) lowercase except at the start. Flag any heading that reads as sentence case, all-lowercase, or inconsistent. + +**Bold and emphasis** + +- **Bold for UI elements and key terms only.** Running-text emphasis should be italic or restructured. Flag bold that isn't a UI label, a menu path, a tab/field name, or a first-use key term. +- **Double quotation marks not used for emphasis.** Flag `"word"` patterns used emphatically (not as direct quotations or literal-phrase references). + +**Lists** + +- **Numbered lists only for sequential steps.** If items are not ordered steps, they should be unordered (dashes). +- **Descriptive-list pattern**: `- **Label**: description.` A list where each item introduces a labeled concept should use this pattern. +- **Period consistency**: items are either all complete sentences (period) or all fragments (no period). Flag mixed styles within a list. +- **Child content indentation**: when a list item has an expanded description, callout, or image, it must be indented two spaces under that item. + +**Links** + +- **Descriptive link text**. Flag link text that is vague (`this page`, `here`) even when it isn't `click here` exactly. + +**Images** + +- **Alt text in title case, describes what the image communicates** (not its appearance). "LLM Node Configuration Panel" is correct; "Screenshot of a form" is not. +- **Comparison images** (two or more images shown together for comparison) each need a caption. The linter cannot detect image adjacency; check by reading. +- **Decorative `alt=""`** should be used only for images that carry no information. Confirm that any empty alt flagged by the linter is truly decorative. +- **Image storage path** under `/images///...` follows the content-domain taxonomy in the guide. +- **Filenames are descriptive and specific** (`workflow-llm-node-parameters.png`, not `workflow-01.png` or `screenshot.png`). + +**Tables** + +- Left-aligned with `:---`; bold used in header row only when it aids clarity. +- Multi-line cells: prefer lists or components; fall back to `



` when a manual break is unavoidable. + +**General** + +- Content inside Mintlify components reads cleanly (bold, links, etc. work as expected inside ``, ``, etc.). + +## Output Format + +``` +## English Formatting Check Results + +### File: {path} + +**Deterministic violations** ({n}) +- Line {n} [{rule-id}]: {message} +- ... + +**Judgment-call findings** ({n}) +- Line {n}: {description of issue} — {rule area} +- ... + +**Clean checks** +- ✅ Frontmatter valid +- ✅ No deterministic violations in {area} +``` + +Group by file. If a file has no issues in any area, report a single ✅ line for it. + +## Important + +- Do NOT modify any files. This is a read-only audit. +- Report findings to the user for review. +- When the script flags a rule that looks like a false positive on inspection (e.g., `-ing` verb that should have been in `SKIP_TEXTS`, or `_` that is a legitimate filename/identifier), surface it as a finding but note the ambiguity so the user can decide. diff --git a/.claude/skills/dify-docs-format-check-en/check-format-en.py b/.claude/skills/dify-docs-format-check-en/check-format-en.py new file mode 100644 index 000000000..0818edb25 --- /dev/null +++ b/.claude/skills/dify-docs-format-check-en/check-format-en.py @@ -0,0 +1,698 @@ +#!/usr/bin/env python3 +"""English documentation format linter for Dify docs. + +Checks a list of .mdx or .md files against the deterministic rules in +writing-guides/formatting-guide.md. Prints a grouped report to stdout. + +Usage: + python3 check-format-en.py [ ...] +""" +from __future__ import annotations + +import re +import sys +from dataclasses import dataclass +from pathlib import Path + + +# ---------- data types ---------- + +@dataclass +class Violation: + line: int + rule: str + message: str + + +# ---------- shared regexes ---------- + +FENCE_RE = re.compile(r'^(\s*)(`{3,}|~{3,})(.*)$') +HEADING_RE = re.compile(r'^(#{1,6})(\s+)(.+?)\s*$') +FM_RE = re.compile(r'\A---\n(.*?)\n---\n', re.DOTALL) +FM_FIELD_RE = re.compile(r'^([A-Za-z][\w-]*)\s*:\s*(.*?)\s*$') + + +# ---------- frontmatter ---------- + +def _yaml_needs_quotes(value: str) -> bool: + """Match the documented rule: quote only when the value contains a colon + followed by a space. Other YAML edge cases (leading special chars, + reserved words, numeric-looking values) are not flagged because the + formatting guide intentionally restricts the rule to the `: ` case.""" + return ': ' in value + + +def _unquote(v: str) -> tuple[str, str]: + """Return (stripped_value, quote_char_or_empty).""" + v = v.rstrip() + if len(v) >= 2 and v[0] == '"' and v[-1] == '"': + return v[1:-1], '"' + if len(v) >= 2 and v[0] == "'" and v[-1] == "'": + return v[1:-1], "'" + return v, '' + + +def check_frontmatter(text: str, lines: list[str]) -> list[Violation]: + vs: list[Violation] = [] + m = FM_RE.match(text) + if not m: + if text.lstrip().startswith('---'): + vs.append(Violation(1, 'F-title-missing', + 'Malformed frontmatter block.')) + return vs + + has_title = False + fm_body = m.group(1) + for offset, line in enumerate(fm_body.split('\n')): + line_num = 2 + offset # frontmatter begins at line 2 content + fm = FM_FIELD_RE.match(line) + if not fm: + continue + field, raw = fm.group(1), fm.group(2) + if field == 'title': + has_title = True + if field == 'description': + stripped, _ = _unquote(raw) + if stripped.rstrip().endswith('.'): + vs.append(Violation(line_num, 'F-desc-trailing-period', + f'description ends with a period: {raw!r}')) + if field in ('title', 'description', 'sidebarTitle'): + stripped, q = _unquote(raw) + needs = _yaml_needs_quotes(stripped) + if needs and not q: + vs.append(Violation(line_num, 'F-quote-needed', + f'{field!r} value contains a colon+space; ' + f'wrap in double quotes.')) + elif q == "'": + vs.append(Violation(line_num, 'F-single-quote', + f'{field!r} uses single quotes; use ' + 'double quotes.')) + elif q == '"' and not needs: + vs.append(Violation(line_num, 'F-quote-unnecessary', + f'{field!r} is quoted unnecessarily; ' + 'quote only when value contains `: `.')) + + if not has_title: + vs.append(Violation(1, 'F-title-missing', + 'Frontmatter missing required `title` field.')) + + fm_end_line = text[:m.end()].count('\n') + # line after closing `---` should be blank + if fm_end_line < len(lines): + after = lines[fm_end_line] + if after.strip() != '': + vs.append(Violation(fm_end_line + 1, 'F-blank-after-fm', + 'No blank line after frontmatter.')) + + return vs + + +# ---------- headings ---------- + +# Verbs where the -ing form is almost always a true verb and should be base form. +ING_VERBS = { + 'Accessing': 'Access', 'Adding': 'Add', 'Adjusting': 'Adjust', + 'Building': 'Build', 'Calling': 'Call', 'Choosing': 'Choose', + 'Completing': 'Complete', 'Configuring': 'Configure', + 'Connecting': 'Connect', 'Creating': 'Create', 'Customizing': 'Customize', + 'Debugging': 'Debug', 'Defining': 'Define', 'Deleting': 'Delete', + 'Developing': 'Develop', 'Editing': 'Edit', 'Embedding': 'Embed', + 'Enabling': 'Enable', 'Exporting': 'Export', 'Filling': 'Fill', + 'Finding': 'Find', 'Generating': 'Generate', 'Getting': 'Get', + 'Handling': 'Handle', 'Importing': 'Import', 'Improving': 'Improve', + 'Initializing': 'Initialize', 'Installing': 'Install', + 'Integrating': 'Integrate', 'Introducing': 'Introduce', + 'Invoking': 'Invoke', 'Managing': 'Manage', 'Modifying': 'Modify', + 'Obtaining': 'Obtain', 'Packaging': 'Package', 'Passing': 'Pass', + 'Placing': 'Place', 'Preparing': 'Prepare', 'Publishing': 'Publish', + 'Resetting': 'Reset', 'Retrieving': 'Retrieve', 'Running': 'Run', + 'Saving': 'Save', 'Setting': 'Set', 'Sharing': 'Share', 'Signing': 'Sign', + 'Storing': 'Store', 'Syncing': 'Sync', 'Synchronizing': 'Synchronize', + 'Using': 'Use', 'Viewing': 'View', +} + +# Heading texts that are legitimate section concepts (gerund-as-noun) and must +# not be flagged. Compared after stripping surrounding ** and numeric prefixes. +SKIP_ING_HEADINGS = { + 'Troubleshooting', 'Getting Started', + 'Logging', 'Indexing', 'Testing', 'Tracing', 'Billing', + 'Filtering', 'Sorting', + 'Chunking and Cleaning', + 'Mounting & Volumes', 'Breaking Changes', + 'Streaming Output', 'Streaming Behavior', + 'Processing Mode', 'Processing Considerations', + 'Branching Logic', + 'Publishing Methods', 'Publishing Options', + 'Publishing Best Practices', 'Publishing Recommendations', + 'Monitoring Data List', + 'Branding and Appearance', + 'Embedding Model', + 'TextEmbedding Implementation', + 'During Pull Request (PR) Review', + 'Packaging and Deployment', +} + +HEADING_PREFIX_RE = re.compile(r'^\s*(?:\d+[\.\)]\s*|\([a-z\d]+\)\s*)') + + +def _strip_bold(t: str) -> str: + t = t.strip() + while t.startswith('**') and t.endswith('**') and len(t) > 4: + t = t[2:-2].strip() + return t + + +def check_headings(lines: list[str]) -> list[Violation]: + vs: list[Violation] = [] + in_fence = False + prev_heading_depth = 0 + heading_lines: list[int] = [] + + for i, line in enumerate(lines, 1): + if FENCE_RE.match(line): + in_fence = not in_fence + continue + if in_fence: + continue + hm = HEADING_RE.match(line) + if not hm: + continue + hashes, _, title = hm.group(1), hm.group(2), hm.group(3) + depth = len(hashes) + + # trailing # + if title.rstrip().endswith('#'): + vs.append(Violation(i, 'H-trailing-hash', + 'Heading has a trailing `#`.')) + + # level skip (only flag forward jumps by >1, not regressions) + if prev_heading_depth and depth > prev_heading_depth + 1: + vs.append(Violation(i, 'H-skip-level', + f'Heading jumped from H{prev_heading_depth} ' + f'to H{depth}; no intermediate level.')) + prev_heading_depth = depth + heading_lines.append(i) + + # blank line before / after (unless adjacent to file boundary or + # frontmatter) + if i > 2 and lines[i - 2].strip() != '': + # allow heading immediately after frontmatter close + vs.append(Violation(i, 'H-blank-before', + 'Missing blank line before heading.')) + if i < len(lines) and lines[i].strip() != '': + vs.append(Violation(i, 'H-blank-after', + 'Missing blank line after heading.')) + + # -ing verb detection + core = _strip_bold(title) + core2 = HEADING_PREFIX_RE.sub('', core) + if core2 not in SKIP_ING_HEADINGS and core not in SKIP_ING_HEADINGS: + m2 = re.match(r'([A-Za-z][\w]*)', core2) + if m2: + first = m2.group(1) + if first in ING_VERBS: + vs.append(Violation( + i, 'H-ing-verb', + f'Heading starts with "{first}"; use base form ' + f'"{ING_VERBS[first]}".')) + return vs + + +# ---------- bold / italic ---------- + +INLINE_CODE_RE = re.compile(r'`[^`\n]*`') + + +def _strip_inline_code(line: str) -> str: + return INLINE_CODE_RE.sub('', line) + + +def check_bold_italic(lines: list[str]) -> list[Violation]: + vs: list[Violation] = [] + in_fence = False + bold_colon_re = re.compile(r'\*\*[^*\n]+?[:\uFF1A]\*\*') + # underscore italic: _X_ where underscores are not inside words + ital_re = re.compile(r'(?:(?<=^)|(?<=\s)|(?<=[\(\[\{\"\']))_[^_\n]+?_' + r'(?=$|[\s\.,;:!?\)\]\}\"\'])') + for i, line in enumerate(lines, 1): + if FENCE_RE.match(line): + in_fence = not in_fence + continue + if in_fence: + continue + s = _strip_inline_code(line) + if bold_colon_re.search(s): + # need bold-pair awareness to avoid false positives (like + # "**A** text:**B**"): a real match means colon sits inside a + # properly paired bold span. + parts = s.split('**') + if len(parts) >= 3 and len(parts) % 2 == 1: + for idx in range(1, len(parts) - 1, 2): + seg = parts[idx] + if seg and seg[-1] in (':', '\uFF1A'): + vs.append(Violation( + i, 'B-trailing-colon-inside', + f'Colon inside bold: "**{seg}**" — move colon ' + 'outside the asterisks.')) + for m in ital_re.finditer(s): + vs.append(Violation( + i, 'B-underscore-italic', + f'Italic with underscores: {m.group(0)!r}. Use `*text*`.')) + return vs + + +# ---------- lists ---------- + +def check_lists(lines: list[str]) -> list[Violation]: + vs: list[Violation] = [] + in_fence = False + for i, line in enumerate(lines, 1): + if FENCE_RE.match(line): + in_fence = not in_fence + continue + if in_fence: + continue + if re.match(r'^(\s*)\*\s', line): + vs.append(Violation( + i, 'L-asterisk-bullet', + 'List uses `* ` bullet; use `- ` instead.')) + # nested list indentation: any leading whitespace must be multiple of 2 + m = re.match(r'^( +)[-*]\s', line) + if m: + indent = len(m.group(1)) + if indent % 2 != 0: + vs.append(Violation( + i, 'L-nested-indent', + f'Nested list item indented with {indent} spaces; ' + 'use multiples of 2.')) + # blank lines around list blocks + list_re = re.compile(r'^\s*[-*]\s|^\s*\d+\.\s') + in_fence = False + for i, line in enumerate(lines, 1): + if FENCE_RE.match(line): + in_fence = not in_fence + continue + if in_fence: + continue + is_list = list_re.match(line) is not None + prev_is_list = i > 1 and list_re.match(lines[i - 2]) is not None + prev_is_blank = i > 1 and lines[i - 2].strip() == '' + if is_list and not prev_is_list and not prev_is_blank and i > 1: + # first item of a list + # only flag if previous line is non-heading content + prev = lines[i - 2] + if not HEADING_RE.match(prev) and prev.strip() != '': + vs.append(Violation( + i, 'L-blank-before', + 'Missing blank line before list.')) + if (is_list and not prev_is_list + and i < len(lines) + and i + 1 <= len(lines)): + # check that the item after the final list item has a blank + pass # handled implicitly by pair-wise scan below + # after-list blank check + in_fence = False + prev_list = False + for i, line in enumerate(lines, 1): + if FENCE_RE.match(line): + in_fence = not in_fence + prev_list = False + continue + if in_fence: + continue + is_list = list_re.match(line) is not None + if prev_list and not is_list and line.strip() != '': + # transitioning out of a list block without a blank line + vs.append(Violation( + i, 'L-blank-after', + 'Missing blank line after list block.')) + prev_list = is_list + return vs + + +# ---------- code blocks ---------- + +def check_code_blocks(lines: list[str]) -> list[Violation]: + vs: list[Violation] = [] + in_fence = False + fence_start_line = 0 + for i, line in enumerate(lines, 1): + m = FENCE_RE.match(line) + if not m: + continue + if not in_fence: + in_fence = True + fence_start_line = i + lang = m.group(3).strip() + if not lang: + vs.append(Violation( + i, 'C-no-language', + 'Code block has no language tag.')) + # blank line before fenced block + if i > 1 and lines[i - 2].strip() != '': + # allow start of file or after frontmatter + vs.append(Violation( + i, 'C-blank-before', + 'Missing blank line before code block.')) + else: + in_fence = False + # blank line after + if i < len(lines) and lines[i].strip() != '': + vs.append(Violation( + i, 'C-blank-after', + 'Missing blank line after code block.')) + return vs + + +# ---------- links ---------- + +def check_links(lines: list[str]) -> list[Violation]: + vs: list[Violation] = [] + in_fence = False + link_re = re.compile(r'\[([^\]\n]+)\]\(([^)\n]+)\)') + for i, line in enumerate(lines, 1): + if FENCE_RE.match(line): + in_fence = not in_fence + continue + if in_fence: + continue + for m in link_re.finditer(_strip_inline_code(line)): + text, url = m.group(1), m.group(2) + t = text.strip().lower().rstrip('.') + if t in ('click here', 'here'): + vs.append(Violation( + i, 'Li-click-here', + f'Non-descriptive link text: {text!r}.')) + if url.startswith('http://'): + vs.append(Violation( + i, 'Li-http-external', + f'External link uses http://: {url!r}.')) + if url.startswith('/') and not url.startswith('/en/') \ + and not url.startswith('/images/') \ + and not url.startswith('/api-reference/') \ + and not url.startswith('/#'): + # only flag clearly internal-looking paths + if re.match(r'^/[a-z]+/', url): + vs.append(Violation( + i, 'Li-internal-no-prefix', + f'Internal link does not start with /en/: {url!r}.')) + return vs + + +# ---------- images ---------- + +def check_images(lines: list[str]) -> list[Violation]: + vs: list[Violation] = [] + in_fence = False + + raw_img_re = re.compile(r']*)>') + caption_re = re.compile(r'caption=\"([^\"]*)\"') + + # collect frame captions to match with alt text on following lines + for i, line in enumerate(lines, 1): + if FENCE_RE.match(line): + in_fence = not in_fence + continue + if in_fence: + continue + if raw_img_re.search(line): + vs.append(Violation( + i, 'I-raw-img-tag', + 'Raw tag; use with markdown image instead.')) + + # Frame/caption pairing: for each block, find the + # enclosed markdown image's alt on a subsequent line before . + i = 0 + while i < len(lines): + line = lines[i] + fm = frame_re.search(line) + if fm: + attrs = fm.group(1) or '' + cap_m = caption_re.search(attrs) + caption = cap_m.group(1) if cap_m else None + # scan forward for , collect first markdown image + alts: list[tuple[int, str, str]] = [] # (line, alt, url) + j = i + 1 + while j < len(lines) and '' not in lines[j]: + for mm in md_img_re.finditer(lines[j]): + alts.append((j + 1, mm.group(1), mm.group(2))) + j += 1 + if caption is not None and alts: + alt_line, alt, _ = alts[0] + if alt != caption: + vs.append(Violation( + alt_line, 'I-caption-alt-mismatch', + f'Frame caption "{caption}" does not match alt ' + f'text "{alt}".')) + i = j + 1 + else: + i += 1 + + # Alt text size / default-name / filename checks on any markdown image + in_fence = False + for i, line in enumerate(lines, 1): + if FENCE_RE.match(line): + in_fence = not in_fence + continue + if in_fence: + continue + for mm in md_img_re.finditer(line): + alt, url = mm.group(1), mm.group(2) + if len(alt) > 125: + vs.append(Violation( + i, 'I-alt-too-long', + f'Alt text is {len(alt)} chars; keep under 125.')) + if not alt.strip(): + vs.append(Violation( + i, 'I-alt-empty', + 'Image has empty alt text; confirm the image is ' + 'purely decorative.')) + # filename checks (only on local paths under /images/) + if url.startswith('/images/'): + fname = url.rsplit('/', 1)[-1] + stem, _, ext = fname.rpartition('.') + if ext and ext != ext.lower(): + vs.append(Violation( + i, 'I-filename-uppercase-ext', + f'Image extension is uppercase: {fname!r}.')) + if re.search(r'@\d+x', fname): + vs.append(Violation( + i, 'I-filename-retina-suffix', + f'Image filename has retina suffix: {fname!r}.')) + if re.match(r'^(CleanShot|Screenshot|IMG_|image\d)', fname, + re.IGNORECASE): + vs.append(Violation( + i, 'I-filename-default-tool', + f'Image filename looks like a default tool output: ' + f'{fname!r}.')) + if re.search(r'[_\s]', stem) or re.search(r'[A-Z]', stem): + vs.append(Violation( + i, 'I-filename-non-kebab', + f'Image filename is not kebab-case/lowercase: ' + f'{fname!r}.')) + if not re.match(r'^[\x00-\x7f]+$', fname): + vs.append(Violation( + i, 'I-filename-non-kebab', + f'Image filename contains non-ASCII characters: ' + f'{fname!r}.')) + m3 = re.match(r'([A-Za-z]+)', stem) + if m3: + first_word = m3.group(1).lower().capitalize() + if first_word in ING_VERBS: + vs.append(Violation( + i, 'I-filename-ing-verb', + f'Image filename starts with -ing verb ' + f'{first_word!r}; use base form.')) + return vs + + +# ---------- Mintlify components ---------- + +def check_mintlify_components(lines: list[str]) -> list[Violation]: + vs: list[Violation] = [] + in_fence = False + callout_re = re.compile(r'^\s*<(Info|Tip|Note|Warning)\b[^>]*>', re.IGNORECASE) + tab_no_title_re = re.compile(r']*)?>') + tab_title_attr_re = re.compile(r'\btitle\s*=\s*"[^"]*"') + for i, line in enumerate(lines, 1): + if FENCE_RE.match(line): + in_fence = not in_fence + continue + if in_fence: + continue + if callout_re.match(line): + if i > 1 and lines[i - 2].strip() != '': + vs.append(Violation( + i, 'M-component-blank-before', + 'Missing blank line before Mintlify callout.')) + for m in tab_no_title_re.finditer(line): + if not tab_title_attr_re.search(m.group(0)): + vs.append(Violation( + i, 'M-tab-no-title', + ' element without a title attribute.')) + # end-of-component blank + closer_re = re.compile(r'^\s*\s*$', + re.IGNORECASE) + in_fence = False + for i, line in enumerate(lines, 1): + if FENCE_RE.match(line): + in_fence = not in_fence + continue + if in_fence: + continue + if closer_re.match(line): + if i < len(lines) and lines[i].strip() != '': + vs.append(Violation( + i, 'M-component-blank-after', + 'Missing blank line after Mintlify callout.')) + return vs + + +# ---------- UI elements ---------- + +def check_ui_elements(lines: list[str]) -> list[Violation]: + vs: list[Violation] = [] + in_fence = False + arrow_re = re.compile(r'\*\*[^*\n]+?\*\*\s*(?:→|->|=>)\s*\*\*[^*\n]+?\*\*') + for i, line in enumerate(lines, 1): + if FENCE_RE.match(line): + in_fence = not in_fence + continue + if in_fence: + continue + if arrow_re.search(line): + vs.append(Violation( + i, 'U-menu-arrow', + 'Menu path uses `→`, `->`, or `=>`; use `>` instead.')) + return vs + + +# ---------- spacing ---------- + +def check_spacing(lines: list[str]) -> list[Violation]: + vs: list[Violation] = [] + in_fence = False + blank_run = 0 + for i, line in enumerate(lines, 1): + if FENCE_RE.match(line): + in_fence = not in_fence + blank_run = 0 + continue + if in_fence: + continue + if line.strip() == '': + blank_run += 1 + if blank_run == 2: + vs.append(Violation( + i, 'S-double-blank', + 'Two or more consecutive blank lines.')) + else: + blank_run = 0 + if line != line.rstrip(): + vs.append(Violation( + i, 'S-trailing-whitespace', + 'Line has trailing whitespace.')) + return vs + + +# ---------- punctuation ---------- + +def check_punctuation(lines: list[str]) -> list[Violation]: + vs: list[Violation] = [] + in_fence = False + em_dash_spaces = re.compile(r'\s—\s|\s—|—\s') + en_dash_spaces = re.compile(r'\d\s+–\s+\d|\d\s+–\d|\d–\s+\d') + fw_punct = re.compile(r'[A-Za-z][,。;:!?()、]|[,。;:!?()、][A-Za-z]') + for i, line in enumerate(lines, 1): + if FENCE_RE.match(line): + in_fence = not in_fence + continue + if in_fence: + continue + s = _strip_inline_code(line) + if em_dash_spaces.search(s): + vs.append(Violation( + i, 'P-em-dash-spaces', + 'Em dash has surrounding whitespace; write `word—word`.')) + if en_dash_spaces.search(s): + vs.append(Violation( + i, 'P-en-dash-spaces', + 'En dash in numeric range has surrounding whitespace; ' + 'write `2–4`.')) + if fw_punct.search(s): + vs.append(Violation( + i, 'P-fullwidth-in-english', + 'Full-width punctuation adjacent to Latin letters in English ' + 'content.')) + return vs + + +# ---------- runner ---------- + +ALL_CHECKS = [ + ('frontmatter', check_frontmatter), + ('headings', check_headings), + ('bold_italic', check_bold_italic), + ('lists', check_lists), + ('code', check_code_blocks), + ('links', check_links), + ('images', check_images), + ('mintlify', check_mintlify_components), + ('ui', check_ui_elements), + ('spacing', check_spacing), + ('punctuation', check_punctuation), +] + + +def lint_file(path: Path) -> dict[str, list[Violation]]: + text = path.read_text() + lines = text.split('\n') + results: dict[str, list[Violation]] = {} + for name, fn in ALL_CHECKS: + # frontmatter needs text for regex; others only need lines + if name == 'frontmatter': + results[name] = fn(text, lines) + else: + results[name] = fn(lines) + return results + + +def print_report(path: Path, results: dict[str, list[Violation]]): + total = sum(len(v) for v in results.values()) + print(f'\n### {path}') + if total == 0: + print(' ✅ no deterministic issues found') + return + by_line: list[Violation] = [] + for group in results.values(): + by_line.extend(group) + by_line.sort(key=lambda v: (v.line, v.rule)) + for v in by_line: + print(f' {v.line:>5} [{v.rule}] {v.message}') + + +def main(argv: list[str]) -> int: + if not argv: + print('usage: check-format-en.py [ ...]', file=sys.stderr) + return 2 + total = 0 + for arg in argv: + p = Path(arg) + if not p.exists(): + print(f'skip (missing): {p}', file=sys.stderr) + continue + # only process .mdx/.md under en/ + if not (str(p).startswith('en/') or '/en/' in str(p.as_posix())): + print(f'skip (non-en): {p}', file=sys.stderr) + continue + results = lint_file(p) + print_report(p, results) + total += sum(len(v) for v in results.values()) + print(f'\nTotal violations: {total}') + return 0 if total == 0 else 1 + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/.claude/skills/dify-docs-guides/SKILL.md b/.claude/skills/dify-docs-guides/SKILL.md index 9b45e0331..af2ca94d0 100644 --- a/.claude/skills/dify-docs-guides/SKILL.md +++ b/.claude/skills/dify-docs-guides/SKILL.md @@ -61,7 +61,4 @@ No overrides. Follow `writing-guides/style-guide.md` as written. ## Post-Writing Verification -After completing the document: - -1. Invoke `dify-docs-terminology-check` to verify terminology consistency against the glossary and codebase. -2. Invoke `dify-docs-reader-test` to verify it from the reader's perspective. +After completing the document, run the post-writing checks listed in `writing-guides/index.md#post-writing-verification`. diff --git a/en/develop-plugin/dev-guides-and-walkthroughs/agent-strategy-plugin.mdx b/en/develop-plugin/dev-guides-and-walkthroughs/agent-strategy-plugin.mdx index 825f74b48..e6e0e5ac4 100644 --- a/en/develop-plugin/dev-guides-and-walkthroughs/agent-strategy-plugin.mdx +++ b/en/develop-plugin/dev-guides-and-walkthroughs/agent-strategy-plugin.mdx @@ -19,7 +19,7 @@ For details on preparing the plugin development tool, see [Initializing the Deve --- -### 1. Initializing the Plugin Template +### 1. Initialize the Plugin Template Run the following command to create a development template for your Agent plugin: @@ -88,7 +88,7 @@ All key functionality for this plugin is in the `strategies/` directory. --- -### 2. Developing the Plugin +### 2. Develop the Plugin Agent Strategy Plugin development revolves around two files: @@ -186,7 +186,7 @@ class BasicAgentAgentStrategy(AgentStrategy): params = BasicParams(**parameters) ``` -### 3. Invoking the Model +### 3. Invoke the Model In an Agent Strategy Plugin, **invoking the model** is central to the workflow. You can invoke an LLM efficiently using `session.model.llm.invoke()` from the SDK, handling text generation, dialogue, and so forth. @@ -265,7 +265,7 @@ With this in place, your Agent Strategy Plugin can automatically perform **Funct ![Tool Invocation](https://assets-docs.dify.ai/2025/01/80e5de8acc2b0ed00524e490fd611ff5.png) -### 5. Creating Logs +### 5. Create Logs Often, multiple steps are necessary to complete a complex task in an **Agent Strategy Plugin**. It’s crucial for developers to track each step’s results, analyze the decision process, and optimize strategy. Using `create_log_message` and `finish_log_message` from the SDK, you can log real-time states before and after calls, aiding in quick problem diagnosis. @@ -1042,7 +1042,7 @@ class BasicAgentAgentStrategy(AgentStrategy): -### 6. Debugging the Plugin +### 6. Debug the Plugin After finalizing the plugin’s declaration file and implementation code, run `python -m main` in the plugin directory to restart it. Next, confirm the plugin runs correctly. Dify offers remote debugging—go to **Plugin Management** to obtain your debug key and remote server address. @@ -1070,7 +1070,7 @@ You’ll see the plugin installed in your Workspace, and team members can also a ![Browser Plugins](https://assets-docs.dify.ai/2025/01/c82ec0202e5bf914b36e06c796398dd6.png) -### Packaging the Plugin (Optional) +### Package the Plugin (Optional) Once everything works, you can package your plugin by running: @@ -1084,7 +1084,7 @@ A file named `google.difypkg` (for example) appears in your current folder—thi **Congratulations!** You’ve fully developed, tested, and packaged your Agent Strategy Plugin. -### Publishing the Plugin (Optional) +### Publish the Plugin (Optional) You can now upload it to the [Dify Plugins repository](https://github.com/langgenius/dify-plugins). Before doing so, ensure it meets the [Plugin Publishing Guidelines](/en/develop-plugin/publishing/marketplace-listing/release-to-dify-marketplace). Once approved, your code merges into the main branch, and the plugin automatically goes live on the [Dify Marketplace](https://marketplace.dify.ai/). diff --git a/en/develop-plugin/dev-guides-and-walkthroughs/cheatsheet.mdx b/en/develop-plugin/dev-guides-and-walkthroughs/cheatsheet.mdx index 26495352f..ed7f2c280 100644 --- a/en/develop-plugin/dev-guides-and-walkthroughs/cheatsheet.mdx +++ b/en/develop-plugin/dev-guides-and-walkthroughs/cheatsheet.mdx @@ -20,13 +20,13 @@ description: A comprehensive reference guide for Dify plugin development, includ > Learn more: [Initializing Development Tools](/en/develop-plugin/getting-started/cli) -### Obtaining the Dify Plugin Development Package +### Obtain the Dify Plugin Development Package [Dify Plugin CLI](https://github.com/langgenius/dify-plugin-daemon/releases) #### Installation Methods for Different Platforms -**macOS [Brew](https://github.com/langgenius/homebrew-dify) (Global Installation):** +**macOS [Brew](https://github.com/langgenius/homebrew-dify) (Global Installation)**: ```bash brew tap langgenius/dify @@ -35,7 +35,7 @@ brew install dify After installation, open a new terminal window and enter the `dify version` command. If it outputs the version information, the installation was successful. -**macOS ARM (M Series Chips):** +**macOS ARM (M Series Chips)**: ```bash # Download dify-plugin-darwin-arm64 @@ -43,7 +43,7 @@ chmod +x dify-plugin-darwin-arm64 ./dify-plugin-darwin-arm64 version ``` -**macOS Intel:** +**macOS Intel**: ```bash # Download dify-plugin-darwin-amd64 @@ -51,7 +51,7 @@ chmod +x dify-plugin-darwin-amd64 ./dify-plugin-darwin-amd64 version ``` -**Linux:** +**Linux**: ```bash # Download dify-plugin-linux-amd64 @@ -59,7 +59,7 @@ chmod +x dify-plugin-linux-amd64 ./dify-plugin-linux-amd64 version ``` -**Global Installation (Recommended):** +**Global Installation (Recommended)**: ```bash # Rename and move to system path @@ -69,7 +69,7 @@ sudo mv dify /usr/local/bin/ dify version ``` -### Running the Development Package +### Run the Development Package Here we use `dify` as an example. If you are using a local installation method, please replace the command accordingly, for example `./dify-plugin-darwin-arm64 plugin init`. diff --git a/en/develop-plugin/dev-guides-and-walkthroughs/creating-new-model-provider.mdx b/en/develop-plugin/dev-guides-and-walkthroughs/creating-new-model-provider.mdx index 5b5354a59..07a3f985f 100644 --- a/en/develop-plugin/dev-guides-and-walkthroughs/creating-new-model-provider.mdx +++ b/en/develop-plugin/dev-guides-and-walkthroughs/creating-new-model-provider.mdx @@ -74,13 +74,13 @@ Dify supports two model configuration methods that determine how users will inte These are models that only require unified provider credentials to use. Once a user configures their API key or other authentication details for the provider, they can immediately access all predefined models. -**Example:** The `OpenAI` provider offers predefined models like `gpt-3.5-turbo-0125` and `gpt-4o-2024-05-13`. A user only needs to configure their OpenAI API key once to access all these models. +**Example**: The `OpenAI` provider offers predefined models like `gpt-3.5-turbo-0125` and `gpt-4o-2024-05-13`. A user only needs to configure their OpenAI API key once to access all these models. ### Custom Models (`customizable-model`) These require additional configuration for each specific model instance. This approach is useful when models need individual parameters beyond the provider-level credentials. -**Example:** `Xinference` supports both LLM and Text Embedding, but each model has a unique **model_uid**. Users must configure this model_uid separately for each model they want to use. +**Example**: `Xinference` supports both LLM and Text Embedding, but each model has a unique **model_uid**. Users must configure this model_uid separately for each model they want to use. These configuration methods **can coexist** within a single provider. For instance, a provider might offer some predefined models while also allowing users to add custom models with specific configurations. @@ -91,8 +91,6 @@ Creating a new model provider involves two main components: 1. **Provider Configuration YAML File** - Defines the provider's basic information, supported model types, and credential requirements 2. **Provider Class Implementation** - Implements authentication validation and other provider-level functionality -*** - ### 3.1 Create Model Provider Configuration File The provider configuration is defined in a YAML file that declares the provider's basic information, supported model types, configuration methods, and credential rules. This file will be placed in the root directory of your plugin project. @@ -280,7 +278,7 @@ For detailed instructions on these steps, please refer to: For each specific model, create a YAML file in the appropriate model type directory (e.g., `models/llm/`) to define its properties, parameters, and features. -**Example (`claude-3-5-sonnet-20240620.yaml`):** +**Example (`claude-3-5-sonnet-20240620.yaml`)**: ```yaml model: claude-3-5-sonnet-20240620 # API identifier for the model diff --git a/en/develop-plugin/dev-guides-and-walkthroughs/datasource-plugin.mdx b/en/develop-plugin/dev-guides-and-walkthroughs/datasource-plugin.mdx index abf0ca700..1eb4400d7 100644 --- a/en/develop-plugin/dev-guides-and-walkthroughs/datasource-plugin.mdx +++ b/en/develop-plugin/dev-guides-and-walkthroughs/datasource-plugin.mdx @@ -1,5 +1,5 @@ --- -title: "Data Source Plugin" +title: Data Source Plugin --- Data source plugins are a new type of plugin introduced in Dify 1.9.0. In a knowledge pipeline, they serve as the document data source and the starting point for the entire pipeline. @@ -18,7 +18,7 @@ Before reading on, ensure you have a basic understanding of the knowledge pipeli Dify supports three types of data source plugins: web crawler, online document, and online drive. When implementing the plugin code, the class that provides the plugin's functionality must inherit from a specific data source class. Each of the three plugin types corresponds to a different parent class. - To learn how to inherit from a parent class to implement plugin functionality, see [Tool Plugin: Preparing Tool Code](/en/develop-plugin/dev-guides-and-walkthroughs/tool-plugin#4-preparing-tool-code). + To learn how to inherit from a parent class to implement plugin functionality, see [Tool Plugin: Prepare Tool Code](/en/develop-plugin/dev-guides-and-walkthroughs/tool-plugin#4-prepare-tool-code). Each data source plugin type supports multiple data sources. For example: @@ -110,7 +110,7 @@ datasources: ``` - For more about creating a provider YAML file, see [Tool Plugin: Completing Third-Party Service Credentials](/en/develop-plugin/dev-guides-and-walkthroughs/tool-plugin#2-completing-third-party-service-credentials). + For more about creating a provider YAML file, see [Tool Plugin: Complete Third-Party Service Credentials](/en/develop-plugin/dev-guides-and-walkthroughs/tool-plugin#2-complete-third-party-service-credentials). diff --git a/en/develop-plugin/dev-guides-and-walkthroughs/develop-a-slack-bot-plugin.mdx b/en/develop-plugin/dev-guides-and-walkthroughs/develop-a-slack-bot-plugin.mdx index 70301f5eb..01cd0dd50 100644 --- a/en/develop-plugin/dev-guides-and-walkthroughs/develop-a-slack-bot-plugin.mdx +++ b/en/develop-plugin/dev-guides-and-walkthroughs/develop-a-slack-bot-plugin.mdx @@ -13,7 +13,7 @@ description: This guide provides a complete walkthrough for developing a Slack B scaffolding tool and a pre-created Slack App to build an AI-powered chatbot on Slack. --- -**What You’ll Learn:** +**What You’ll Learn**: Gain a solid understanding of how to build a Slack Bot that’s powered by AI—one that can respond to user questions right inside Slack. If you haven't developed a plugin before, we recommend reading the [Plugin Development Quick Start Guide](/en/develop-plugin/dev-guides-and-walkthroughs/tool-plugin) first. @@ -32,7 +32,7 @@ Slack is an open, real-time communication platform with a robust API. Among its > * **Slack Bot** A chatbot on the Slack platform, acting as a virtual user you can interact with in real-time. > * **Slack Bot Plugin** A plugin in the Dify Marketplace that connects a Dify application with Slack. This guide focuses on how to develop that plugin. -**How It Works (A Simple Overview):** +**How It Works (A Simple Overview)**: 1. **Send a Message to the Slack Bot** @@ -58,13 +58,13 @@ Go to the [Slack API platform](https://api.slack.com/apps), create a Slack app f ![](https://assets-docs.dify.ai/2025/01/c1fd0ac1467faf5a3ebf3818bb234aa8.png) -1. **Enable Webhooks:** +1. **Enable Webhooks**: ![Enable Webhooks](https://assets-docs.dify.ai/2025/01/7112e0710300f1db16827e17f3deac00.png) -2. **Install the App in Your Slack Workspace:** +2. **Install the App in Your Slack Workspace**: ![Install the App in Your Slack Workspace](https://assets-docs.dify.ai/2025/01/88c360ff4f7b04fea52174ce330522fa.png) @@ -76,7 +76,7 @@ Go to the [Slack API platform](https://api.slack.com/apps), create a Slack app f ![Obtain an OAuth Token for Future Plugin Development](https://assets-docs.dify.ai/2025/01/dcd8ec947253f2ef9ae121ed77ec9f26.png) -### 1. Developing the Plugin +### 1. Develop the Plugin Now we’ll dive into the actual coding. Before starting, make sure you’ve read [Quick Start: Developing an Extension Plugin](/en/develop-plugin/dev-guides-and-walkthroughs/endpoint) or have already built a Dify plugin before. @@ -102,7 +102,7 @@ This plugin needs to know which Dify app should handle the replies, as well as t Modify the YAML file in the group directory—for example, `group/slack.yaml`. The form’s filename is determined by the info you provided when creating the plugin, so adjust it accordingly. -**Sample Code:** +**Sample Code**: `slack.yaml` @@ -161,7 +161,7 @@ Explanation of the Configuration Fields: Finally, in the `endpoints/slack.yaml` file, change the request method to POST to handle incoming Slack messages properly. -**Sample Code:** +**Sample Code**: `endpoints/slack.yaml` @@ -357,12 +357,12 @@ For a complete Dify plugin project example, visit the [GitHub repository](https: If you want to explore more about plugin development, check the following: -**Quick Starts:** +**Quick Starts**: - [Develop an Extension Plugin](/en/develop-plugin/dev-guides-and-walkthroughs/endpoint) - [Develop a Model Plugin](/en/develop-plugin/dev-guides-and-walkthroughs/creating-new-model-provider) - [Bundle Plugins: Packaging Multiple Plugins](/en/develop-plugin/features-and-specs/advanced-development/bundle) -**Plugin Interface Docs:** +**Plugin Interface Docs**: - [Defining Plugin Information via Manifest File](/en/develop-plugin/features-and-specs/plugin-types/plugin-info-by-manifest) - Manifest structure - [Endpoint](/en/develop-plugin/dev-guides-and-walkthroughs/endpoint) - Endpoint detailed definition - [Reverse Invocation](/en/develop-plugin/features-and-specs/advanced-development/reverse-invocation) - Reverse-calling Dify capabilities diff --git a/en/develop-plugin/dev-guides-and-walkthroughs/develop-flomo-plugin.mdx b/en/develop-plugin/dev-guides-and-walkthroughs/develop-flomo-plugin.mdx index 848c6f24b..76abd9270 100644 --- a/en/develop-plugin/dev-guides-and-walkthroughs/develop-flomo-plugin.mdx +++ b/en/develop-plugin/dev-guides-and-walkthroughs/develop-flomo-plugin.mdx @@ -1,6 +1,6 @@ --- -title: '10-Minute Guide to Building Dify Plugins' -description: 'Learn how to build a functional Dify plugin that connects with Flomo note-taking service in just 10 minutes' +title: 10-Minute Guide to Building Dify Plugins +description: Learn how to build a functional Dify plugin that connects with Flomo note-taking service in just 10 minutes language: en standard_title: 10-Minute Guide to Building Dify Plugins --- diff --git a/en/develop-plugin/dev-guides-and-walkthroughs/develop-md-exporter.mdx b/en/develop-plugin/dev-guides-and-walkthroughs/develop-md-exporter.mdx index 8c378d426..7916e6bd5 100644 --- a/en/develop-plugin/dev-guides-and-walkthroughs/develop-md-exporter.mdx +++ b/en/develop-plugin/dev-guides-and-walkthroughs/develop-md-exporter.mdx @@ -1,6 +1,6 @@ --- -title: 'Building a Markdown Exporter Plugin' -description: 'Learn how to create a plugin that exports conversations to different document formats' +title: Build a Markdown Exporter Plugin +description: Learn how to create a plugin that exports conversations to different document formats language: en standard_title: Building a Markdown Exporter Plugin --- diff --git a/en/develop-plugin/dev-guides-and-walkthroughs/tool-oauth.mdx b/en/develop-plugin/dev-guides-and-walkthroughs/tool-oauth.mdx index 2b12e1d40..98b82b85d 100644 --- a/en/develop-plugin/dev-guides-and-walkthroughs/tool-oauth.mdx +++ b/en/develop-plugin/dev-guides-and-walkthroughs/tool-oauth.mdx @@ -1,5 +1,5 @@ --- -title: "Add OAuth Support to Your Tool Plugin" +title: Add OAuth Support to Your Tool Plugin --- diff --git a/en/develop-plugin/dev-guides-and-walkthroughs/tool-plugin.mdx b/en/develop-plugin/dev-guides-and-walkthroughs/tool-plugin.mdx index 739719e50..198b939ff 100644 --- a/en/develop-plugin/dev-guides-and-walkthroughs/tool-plugin.mdx +++ b/en/develop-plugin/dev-guides-and-walkthroughs/tool-plugin.mdx @@ -41,7 +41,7 @@ This article will use `Google Search` as an example to demonstrate how to quickl For detailed instructions on how to prepare the plugin development scaffolding tool, please refer to [Initializing Development Tools](/en/develop-plugin/getting-started/cli). If you are developing a plugin for the first time, it is recommended to read [Dify Plugin Development: Hello World Guide](/en/develop-plugin/dev-guides-and-walkthroughs/tool-plugin) first. -### Creating a New Project +### Create a New Project Run the scaffolding command line tool to create a new Dify plugin project. @@ -57,7 +57,7 @@ dify plugin init > In the following text, `dify` will be used as a command line example. If you encounter any issues, please replace the `dify` command with the path to the command line tool. -### Choosing Plugin Type and Template +### Choose Plugin Type and Template All templates in the scaffolding tool provide complete code projects. In this example, select the `Tool` plugin. @@ -67,7 +67,7 @@ All templates in the scaffolding tool provide complete code projects. In this ex ![Plugin Type: Tool](https://assets-docs.dify.ai/2024/12/dd3c0f9a66454e15868eabced7b74fd6.png) -#### Configuring Plugin Permissions +#### Configure Plugin Permissions The plugin also needs permissions to read from the Dify platform. Grant the following permissions to this example plugin: @@ -84,9 +84,9 @@ After checking all permission items, press Enter to complete the plugin creation ![Plugin Permissions](https://assets-docs.dify.ai/2024/12/9cf92c2e74dce55e6e9e331d031e5a9f.png) -### Developing the Tool Plugin +### Develop the Tool Plugin -#### 1. Creating the Tool Provider File +#### 1. Create the Tool Provider File The tool provider file is a yaml format file, which can be understood as the basic configuration entry for the tool plugin, used to provide necessary authorization information to the tool. @@ -142,7 +142,7 @@ class ToolLabelEnum(Enum): OTHER = 'other' ``` -#### **2. Completing Third-Party Service Credentials** +#### **2. Complete Third-Party Service Credentials** For development convenience, we choose to use the Google Search API provided by the third-party service `SerpApi`. `SerpApi` requires an API Key for use, so we need to add the `credentials_for_provider` field in the `yaml` file. @@ -188,7 +188,7 @@ extra: - You need to specify which tools the provider includes. This example only includes one `tools/google_search.yaml` file. - As a provider, in addition to defining its basic information, you also need to implement some of its code logic, so you need to specify its implementation logic. In this example, we put the code file for the functionality in `google.py`, but we won't implement it yet, but instead write the code for `google_search` first. -#### 3. Filling in the Tool YAML File +#### 3. Fill in the Tool YAML File A tool plugin can have multiple tool functions, and each tool function needs a `yaml` file for description, including basic information about the tool function, parameters, output, etc. @@ -248,7 +248,7 @@ extra: - `placeholder` prompt text for the input field, can be set when the form type is `form` and the parameter type is `string`, `number`, `secret-input`, supports multiple languages. - `llm_description` introduction passed to the LLM. To make the LLM better understand this parameter, please write as detailed information as possible about this parameter here, so that the LLM can understand the parameter. -#### 4. Preparing Tool Code +#### 4. Prepare Tool Code After filling in the configuration information for the tool, you can start writing the code for the tool's functionality, implementing the logical purpose of the tool. Create `google_search.py` in the `/tools` directory with the following content: @@ -299,7 +299,7 @@ class GoogleSearchTool(Tool): This example means requesting `serpapi` and using `self.create_json_message` to return a formatted `json` data string. If you want to learn more about return data types, you can refer to the [Remote Debugging Plugins](/en/develop-plugin/features-and-specs/plugin-types/remote-debug-a-plugin) and [Persistent Storage KV](/en/develop-plugin/features-and-specs/plugin-types/persistent-storage-kv) documents. -#### 5. Completing the Tool Provider Code +#### 5. Complete the Tool Provider Code Finally, you need to create an implementation code for the provider to implement the credential validation logic. If credential validation fails, a `ToolProviderCredentialValidationError` exception will be thrown. After validation succeeds, the `google_search` tool service will be correctly requested. @@ -323,7 +323,7 @@ class GoogleProvider(ToolProvider): raise ToolProviderCredentialValidationError(str(e)) ``` -### Debugging the Plugin +### Debug the Plugin After completing plugin development, you need to test whether the plugin can function properly. Dify provides a convenient remote debugging method to help you quickly verify the plugin's functionality in a test environment. @@ -349,7 +349,7 @@ Run the `python -m main` command to start the plugin. On the plugins page, you c ![Run the Python -M Main Command to Start the Plugin](https://assets-docs.dify.ai/2024/11/0fe19a8386b1234755395018bc2e0e35.png) -### Packaging the Plugin (Optional) +### Package the Plugin (Optional) After confirming that the plugin can run normally, you can package and name the plugin using the following command line tool. After running, you will discover a `google.difypkg` file in the current folder, which is the final plugin package. @@ -361,7 +361,7 @@ dify plugin package ./google Congratulations, you have completed the entire process of developing, debugging, and packaging a tool-type plugin! -### Publishing the Plugin (Optional) +### Publish the Plugin (Optional) If you want to publish the plugin to the Dify Marketplace, please ensure that your plugin follows the specifications in [Publish to Dify Marketplace](/en/develop-plugin/publishing/marketplace-listing/release-to-dify-marketplace). After passing the review, the code will be merged into the main branch and automatically launched to the [Dify Marketplace](https://marketplace.dify.ai/). @@ -369,13 +369,13 @@ If you want to publish the plugin to the Dify Marketplace, please ensure that yo ### Explore More -#### **Quick Start:** +#### **Quick Start**: - [Developing Extension Plugins](/en/develop-plugin/dev-guides-and-walkthroughs/endpoint) - [Developing Model Plugins](/en/develop-plugin/dev-guides-and-walkthroughs/creating-new-model-provider) - [Bundle Plugins: Packaging Multiple Plugins](/en/develop-plugin/features-and-specs/advanced-development/bundle) -#### **Plugin Interface Documentation:** +#### **Plugin Interface Documentation**: - [General Specifications](/en/develop-plugin/features-and-specs/plugin-types/general-specifications) - Manifest Structure and Tool Specifications - [Endpoint](/en/develop-plugin/dev-guides-and-walkthroughs/endpoint) - Detailed Endpoint Definition diff --git a/en/develop-plugin/dev-guides-and-walkthroughs/trigger-plugin.mdx b/en/develop-plugin/dev-guides-and-walkthroughs/trigger-plugin.mdx index 80377cd82..ccfb1aac3 100644 --- a/en/develop-plugin/dev-guides-and-walkthroughs/trigger-plugin.mdx +++ b/en/develop-plugin/dev-guides-and-walkthroughs/trigger-plugin.mdx @@ -1,5 +1,5 @@ --- -title: "Trigger Plugin" +title: Trigger Plugin --- ## What Is a Trigger Plugin? diff --git a/en/develop-plugin/features-and-specs/advanced-development/customizable-model.mdx b/en/develop-plugin/features-and-specs/advanced-development/customizable-model.mdx index 9ec1dc854..d135a1aba 100644 --- a/en/develop-plugin/features-and-specs/advanced-development/customizable-model.mdx +++ b/en/develop-plugin/features-and-specs/advanced-development/customizable-model.mdx @@ -6,7 +6,7 @@ dimensions: level: advanced standard_title: Customizable Model language: en -title: Integrating Custom Models +title: Integrate Custom Models description: This document details how to integrate custom models into Dify, using the Xinference model as an example. It covers the complete process, including creating model provider files, writing code based on model type, implementing model invocation @@ -21,7 +21,7 @@ By default, a custom model automatically includes two parameters—its **model t You do not need to implement `validate_provider_credential` in your provider configuration file. During runtime, based on the user’s choice of model type or model name, Dify automatically calls the corresponding model layer’s `validate_credentials` method to verify credentials. -## Integrating a Custom Model Plugin +## Integrate a Custom Model Plugin Below are the steps to integrate a custom model: @@ -34,15 +34,13 @@ Below are the steps to integrate a custom model: 4. **Debug the Plugin**\ Write unit and integration tests for the new provider functionality, ensuring that all components work as intended. -*** - ### 1. **Create a Model Provider File** In your plugin’s `/provider` directory, create a `xinference.yaml` file. The `Xinference` family of models supports **LLM**, **Text Embedding**, and **Rerank** model types, so your `xinference.yaml` must include all three. -**Example:** +**Example**: ```yaml provider: xinference # Identifies the provider @@ -338,13 +336,13 @@ Publish to Dify Marketplace ## Explore More -**Quick Start:** +**Quick Start**: * [Develop Extension Plugin](/en/develop-plugin/features-and-specs/plugin-types/general-specifications) * [Develop Tool Plugin](/en/develop-plugin/dev-guides-and-walkthroughs/tool-plugin) * [Bundle Plugins: Package Multiple Plugins](/en/develop-plugin/features-and-specs/advanced-development/bundle) -**Plugins Endpoint Docs:** +**Plugins Endpoint Docs**: * [Manifest](/en/develop-plugin/features-and-specs/plugin-types/plugin-info-by-manifest) Structure * [Endpoint](/en/develop-plugin/dev-guides-and-walkthroughs/endpoint) Definitions diff --git a/en/develop-plugin/features-and-specs/advanced-development/reverse-invocation-app.mdx b/en/develop-plugin/features-and-specs/advanced-development/reverse-invocation-app.mdx index 079d6ed6c..849947858 100644 --- a/en/develop-plugin/features-and-specs/advanced-development/reverse-invocation-app.mdx +++ b/en/develop-plugin/features-and-specs/advanced-development/reverse-invocation-app.mdx @@ -15,7 +15,7 @@ description: This document details how plugins can reverse invoke App services w Reverse invoking an App means that a plugin can access data from an App within Dify. This module supports both streaming and non-streaming App calls. If you are unfamiliar with the basic concepts of reverse invocation, please first read [Reverse Invocation of Dify Services](/en/develop-plugin/features-and-specs/advanced-development/reverse-invocation). -**Interface Types:** +**Interface Types**: * For `Chatbot/Agent/Chatflow` type applications, they are all chat-based applications and thus share the same input and output parameter types. Therefore, they can be uniformly treated as the **Chat Interface.** * For Workflow applications, they occupy a separate **Workflow Interface.** @@ -23,7 +23,7 @@ Reverse invoking an App means that a plugin can access data from an App within D Please note that plugins are only allowed to access Apps within the Workspace where the plugin resides. -### Calling the Chat Interface +### Call the Chat Interface #### **Entry Point** @@ -82,7 +82,7 @@ class Duck(Endpoint): return Response(generator(), status=200, content_type="text/html") ``` -### Calling the Workflow Interface +### Call the Workflow Interface #### **Entry Point** @@ -103,7 +103,7 @@ class Duck(Endpoint): pass ``` -### Calling the Completion Interface +### Call the Completion Interface #### **Entry Point** diff --git a/en/develop-plugin/features-and-specs/advanced-development/reverse-invocation-node.mdx b/en/develop-plugin/features-and-specs/advanced-development/reverse-invocation-node.mdx index 8cadd8ba7..64c0a9dcb 100644 --- a/en/develop-plugin/features-and-specs/advanced-development/reverse-invocation-node.mdx +++ b/en/develop-plugin/features-and-specs/advanced-development/reverse-invocation-node.mdx @@ -18,7 +18,7 @@ Reverse invoking a Node means that a plugin can access the capabilities of certa The `ParameterExtractor` and `QuestionClassifier` nodes in `Workflow` encapsulate complex Prompt and code logic, enabling tasks that are difficult to solve with hardcoding through LLMs. Plugins can call these two nodes. -### Calling the Parameter Extractor Node +### Call the Parameter Extractor Node #### **Entry Point** @@ -78,7 +78,7 @@ class ParameterExtractorTool(Tool): yield self.create_text_message(extracted_name) ``` -### Calling the Question Classifier Node +### Call the Question Classifier Node #### **Entry Point** diff --git a/en/develop-plugin/features-and-specs/advanced-development/reverse-invocation-tool.mdx b/en/develop-plugin/features-and-specs/advanced-development/reverse-invocation-tool.mdx index 9e961b2d1..0e3dc45b6 100644 --- a/en/develop-plugin/features-and-specs/advanced-development/reverse-invocation-tool.mdx +++ b/en/develop-plugin/features-and-specs/advanced-development/reverse-invocation-tool.mdx @@ -25,7 +25,7 @@ In these cases, you need to call other existing tools within your plugin. These These requirements can be met by calling the `self.session.tool` field of the plugin. -### Calling Installed Tools +### Call Installed Tools Allows the plugin to call various tools installed in the current Workspace, including other tool-type plugins. @@ -46,7 +46,7 @@ Allows the plugin to call various tools installed in the current Workspace, incl Here, `provider` is the plugin ID plus the tool provider name, formatted like `langgenius/google/google`. `tool_name` is the specific tool name, and `parameters` are the arguments passed to the tool. -### Calling Workflow as Tool +### Call Workflow as Tool For more information on Workflow as Tool, please refer to the [Tool Plugin documentation](/en/develop-plugin/dev-guides-and-walkthroughs/tool-plugin). @@ -67,7 +67,7 @@ For more information on Workflow as Tool, please refer to the [Tool Plugin docum In this case, `provider` is the ID of this tool, and `tool_name` is specified during the creation of the tool. -### Calling Custom Tool +### Call Custom Tool **Entry Point** diff --git a/en/develop-plugin/features-and-specs/plugin-types/model-schema.mdx b/en/develop-plugin/features-and-specs/plugin-types/model-schema.mdx index 33b29dcfe..fb54f7b0c 100644 --- a/en/develop-plugin/features-and-specs/plugin-types/model-schema.mdx +++ b/en/develop-plugin/features-and-specs/plugin-types/model-schema.mdx @@ -1121,8 +1121,6 @@ class PromptMessageTool(BaseModel): ``` -*** - #### LLMResult ```python @@ -1188,8 +1186,6 @@ class LLMUsage(ModelUsage): latency: float # Request time (s) ``` -*** - #### TextEmbeddingResult ```python @@ -1218,8 +1214,6 @@ class EmbeddingUsage(ModelUsage): latency: float # Request time (s) ``` -*** - #### RerankResult ```python diff --git a/en/develop-plugin/features-and-specs/plugin-types/persistent-storage-kv.mdx b/en/develop-plugin/features-and-specs/plugin-types/persistent-storage-kv.mdx index cf825adbe..a22ee9dfa 100644 --- a/en/develop-plugin/features-and-specs/plugin-types/persistent-storage-kv.mdx +++ b/en/develop-plugin/features-and-specs/plugin-types/persistent-storage-kv.mdx @@ -26,7 +26,7 @@ The persistent storage mechanism allows plugins to store data persistently withi Dify currently provides a key-value (KV) storage system for plugins, with plans to introduce more flexible and powerful storage interfaces in the future based on developer needs. -## Accessing Storage +## Access Storage All storage operations are performed through the `storage` object available in your plugin's session: @@ -37,7 +37,7 @@ storage = self.session.storage ## Storage Operations -### Storing Data +### Store Data Store data with the `set` method: @@ -74,7 +74,7 @@ with open("image.jpg", "rb") as f: storage.set("profile_image", image_data) ``` -### Retrieving Data +### Retrieve Data Retrieve stored data with the `get` method: @@ -109,7 +109,7 @@ if user_data_bytes: print(f"User preferences: {user_data['preferences']}") ``` -### Deleting Data +### Delete Data Delete stored data with the `delete` method: diff --git a/en/develop-plugin/features-and-specs/plugin-types/tool.mdx b/en/develop-plugin/features-and-specs/plugin-types/tool.mdx index 30fd98382..dc75e9c09 100644 --- a/en/develop-plugin/features-and-specs/plugin-types/tool.mdx +++ b/en/develop-plugin/features-and-specs/plugin-types/tool.mdx @@ -228,7 +228,7 @@ The streaming variable method (`create_stream_variable_message`) currently only To reference a tool's output variables in a workflow application, you need to define which variables might be output. This is done using the [JSON Schema](https://json-schema.org/) format in your tool's manifest. -### Defining Output Schema +### Define Output Schema ```yaml Tool Manifest with Output Schema diff --git a/en/develop-plugin/publishing/marketplace-listing/release-by-file.mdx b/en/develop-plugin/publishing/marketplace-listing/release-by-file.mdx index d207e37ac..62936bf27 100644 --- a/en/develop-plugin/publishing/marketplace-listing/release-by-file.mdx +++ b/en/develop-plugin/publishing/marketplace-listing/release-by-file.mdx @@ -31,7 +31,7 @@ This article will introduce how to package a plugin project as a local file and After configuration, enter the `dify version` command in the terminal to check if it outputs version information to confirm that the necessary development tools have been installed. -### Packaging the Plugin +### Package the Plugin > Before packaging the plugin, please ensure that the `author` field in the plugin's `manifest.yaml` file and the `.yaml` file under the `/provider` path is consistent with your GitHub ID. For detailed information about the manifest file, please refer to [General Specifications](/en/develop-plugin/features-and-specs/plugin-types/general-specifications). @@ -47,7 +47,7 @@ After running the command, a file with the `.difypkg` extension will be generate ![Generate Plugin File](https://assets-docs.dify.ai/2024/12/98e09c04273eace8fe6e5ac976443cca.png) -### Installing the Plugin +### Install the Plugin Visit the Dify plugin management page, click **Install Plugin** in the upper right corner → **Via Local File** to install, or drag and drop the plugin file to a blank area of the page to install the plugin. @@ -55,7 +55,7 @@ Visit the Dify plugin management page, click **Install Plugin** in the upper rig ![Install Plugin File](https://assets-docs.dify.ai/2024/12/8c31c4025a070f23455799f942b91a57.png) -### Publishing the Plugin +### Publish the Plugin You can share the plugin file with others or upload it to the internet for others to download. If you want to share your plugin more widely, you can consider: diff --git a/en/develop-plugin/publishing/marketplace-listing/release-overview.mdx b/en/develop-plugin/publishing/marketplace-listing/release-overview.mdx index b335c7193..127793ce2 100644 --- a/en/develop-plugin/publishing/marketplace-listing/release-overview.mdx +++ b/en/develop-plugin/publishing/marketplace-listing/release-overview.mdx @@ -6,7 +6,7 @@ dimensions: level: beginner standard_title: Release Overview language: en -title: Publishing Plugins +title: Publish Plugins description: This document introduces three ways to publish Dify plugins - official Marketplace, open-source GitHub repository, and local plugin file package. It details the characteristics, publishing process, and applicable scenarios for each method, diff --git a/en/develop-plugin/publishing/marketplace-listing/release-to-dify-marketplace.mdx b/en/develop-plugin/publishing/marketplace-listing/release-to-dify-marketplace.mdx index 7cc638b6a..cccd6dac8 100644 --- a/en/develop-plugin/publishing/marketplace-listing/release-to-dify-marketplace.mdx +++ b/en/develop-plugin/publishing/marketplace-listing/release-to-dify-marketplace.mdx @@ -36,8 +36,6 @@ Plugin submission, review, and publication flow chart: > **Note**: The Contributor Agreement in the above diagram refers to the [Plugin Developer Guidelines](/en/develop-plugin/publishing/standards/contributor-covenant-code-of-conduct). -*** - ### During Pull Request (PR) Review Actively respond to reviewer questions and feedback: @@ -45,8 +43,6 @@ Actively respond to reviewer questions and feedback: * PR comments unresolved within **14 days** will be marked as stale (can be reopened). * PR comments unresolved within **30 days** will be closed (cannot be reopened, a new PR needs to be created). -*** - ### **After Pull Request (PR) Approval** **1. Ongoing Maintenance** @@ -60,8 +56,6 @@ Actively respond to reviewer questions and feedback: * Avoid introducing breaking changes to existing plugins. -*** - ### Review Process **1. Review Order** @@ -80,8 +74,6 @@ Actively respond to reviewer questions and feedback: [Dify.AI](https://dify.ai/) reserves the right to accept or reject plugin submissions. -*** - ### Frequently Asked Questions 1. **How to determine if a plugin is unique?** diff --git a/en/develop-plugin/publishing/standards/privacy-protection-guidelines.mdx b/en/develop-plugin/publishing/standards/privacy-protection-guidelines.mdx index 896e6fad2..47a102eac 100644 --- a/en/develop-plugin/publishing/standards/privacy-protection-guidelines.mdx +++ b/en/develop-plugin/publishing/standards/privacy-protection-guidelines.mdx @@ -25,7 +25,7 @@ Center your privacy policy around the following points: #### 1. List the types of data collected -**Type A:** **Direct Identifiers** +**Type A**: **Direct Identifiers** * Name (e.g., full name, first name, last name) * Email address @@ -47,7 +47,7 @@ Center your privacy policy around the following points: * Health information * Financial information -**Type C: Data that can be combined with other data to identify an individual:** +**Type C: Data that can be combined with other data to identify an individual**: * Age * Gender @@ -67,7 +67,7 @@ For example, if the plugin you are developing involves Slack services, make sure * Whether any data is shared with third parties, and if so, identify those third parties and provide links to their privacy policies. * If you have no clue of how to write a privacy policy, you can also check the privacy policy of Plugins issued by Dify Team. -#### 3. Introducing a privacy policy statement within the plugin Manifest file +#### 3. Introduce a privacy policy statement within the plugin Manifest file For detailed instructions on filling out specific fields, please refer to the [General Specifications](/en/develop-plugin/features-and-specs/plugin-types/general-specifications) documentation. diff --git a/en/develop-plugin/publishing/standards/third-party-signature-verification.mdx b/en/develop-plugin/publishing/standards/third-party-signature-verification.mdx index 9aebc73ae..41fd14195 100644 --- a/en/develop-plugin/publishing/standards/third-party-signature-verification.mdx +++ b/en/develop-plugin/publishing/standards/third-party-signature-verification.mdx @@ -6,14 +6,10 @@ dimensions: level: intermediate standard_title: Third-Party Signature Verification language: en -title: Signing Plugins for Third-Party Signature Verification +title: Sign Plugins for Third-Party Signature Verification description: This document describes how to enable and use the third-party signature verification feature in the Dify Community Edition, including key pair generation, plugin signing and verification, and environment configuration steps, enabling administrators to securely install plugins not available on the Dify Marketplace. --- ---- -title: ---- - This feature is available only in the Dify Community Edition. Third-party signature verification is not currently supported on Dify Cloud Edition. Third-party signature verification allows Dify administrators to safely approve the installation of plugins not listed on the Dify Marketplace without completely disabling signature verification. This supports the following scenarios for example: @@ -23,7 +19,7 @@ Third-party signature verification allows Dify administrators to safely approve Both Dify administrators and plugin developers can add a signature to a plugin using a pre-generated key pair. Additionally, administrators can configure Dify to enforce signature verification using specific public keys during plugin installation. -## Generating a Key Pair for Signing and Verification +## Generate a Key Pair for Signing and Verification Generate a new key pair for adding and verifying the plugin's signature with the following command: @@ -40,7 +36,7 @@ The private key is used to sign the plugin, and the public key is used to verify Keep the private key secure. If it is compromised, an attacker could add a valid signature to any plugin, which would compromise Dify's security. -## Adding a Signature to the Plugin and Veriyfing It +## Add a Signature to the Plugin and Verify It Add a signature to your plugin by running the following command. Note that you must specify the **plugin file to sign** and the **private key**: @@ -58,11 +54,11 @@ dify signature verify your_plugin_project.signed.difypkg -p your_key_pair.public If you omit the public key argument, verification will use the Dify Marketplace public key. In that case, signature verification will fail for any plugin file not downloaded from the Dify Marketplace. -## Enabling Third-Party Signature Verification +## Enable Third-Party Signature Verification Dify administrators can enforce signature verification using pre-approved public keys before installing a plugin. -### Placing the Public Key +### Place the Public Key Place the **public key** corresponding to the private key used for signing in a location that the plugin daemon can access. diff --git a/en/self-host/advanced-deployments/local-source-code.mdx b/en/self-host/advanced-deployments/local-source-code.mdx index e0076d558..421359ea4 100644 --- a/en/self-host/advanced-deployments/local-source-code.mdx +++ b/en/self-host/advanced-deployments/local-source-code.mdx @@ -275,7 +275,7 @@ To start the web frontend service, [Node.js v22 (LTS)](https://nodejs.org/en) an pnpm start ``` - Expected output: + Expected output: ``` ▲ Next.js 15 - Local: http://localhost:3000 diff --git a/en/self-host/quick-start/faqs.mdx b/en/self-host/quick-start/faqs.mdx index 157897451..350c95e8c 100644 --- a/en/self-host/quick-start/faqs.mdx +++ b/en/self-host/quick-start/faqs.mdx @@ -26,7 +26,7 @@ unzip dify.zip && rm dify.zip Alternatively, download the ZIP on another device and transfer it manually. -**To upgrade:** +**To upgrade**: ```bash wget -O dify-latest.zip "$(curl -s https://api.github.com/repos/langgenius/dify/releases/latest | jq -r '.zipball_url')" unzip dify-latest.zip && rm dify-latest.zip diff --git a/en/self-host/troubleshooting/integrations.mdx b/en/self-host/troubleshooting/integrations.mdx index af49b8936..e12711ac8 100644 --- a/en/self-host/troubleshooting/integrations.mdx +++ b/en/self-host/troubleshooting/integrations.mdx @@ -30,18 +30,18 @@ Get credentials from [Notion Integrations](https://www.notion.so/my-integrations OpenAI TTS requires FFmpeg for audio stream segmentation. -**macOS:** +**macOS**: ```bash brew install ffmpeg ``` -**Ubuntu:** +**Ubuntu**: ```bash sudo apt-get update sudo apt-get install ffmpeg ``` -**CentOS:** +**CentOS**: ```bash sudo yum install epel-release sudo rpm -Uvh http://li.nux.ro/download/nux/dextop/el7/x86_64/nux-dextop-release-0-5.el7.nux.noarch.rpm @@ -49,7 +49,7 @@ sudo yum update sudo yum install ffmpeg ffmpeg-devel ``` -**Windows:** +**Windows**: 1. Download from [FFmpeg website](https://ffmpeg.org/download.html) 2. Extract and move to `C:\Program Files\` 3. Add FFmpeg bin directory to system PATH diff --git a/en/use-dify/build/additional-features.mdx b/en/use-dify/build/additional-features.mdx index e3500ab68..d5d8188f1 100644 --- a/en/use-dify/build/additional-features.mdx +++ b/en/use-dify/build/additional-features.mdx @@ -1,6 +1,6 @@ --- -title: "App Toolkit" -description: "Optional features that make your Dify apps more useful" +title: App Toolkit +description: Optional features that make your Dify apps more useful --- Dify apps come with optional features you can enable to improve the end-user experience. Open the **Features** panel of the builder to see what's available for your app type. diff --git a/en/use-dify/build/mcp.mdx b/en/use-dify/build/mcp.mdx index 30558c9b6..cf2544ccc 100644 --- a/en/use-dify/build/mcp.mdx +++ b/en/use-dify/build/mcp.mdx @@ -1,5 +1,5 @@ --- -title: "Using MCP Tools" +title: Use MCP Tools --- Connect external tools from [MCP servers](https://modelcontextprotocol.io/docs/getting-started/intro) to your Dify apps. Instead of just built-in tools, you can use tools from the growing [MCP ecosystem](https://mcpservers.org/). @@ -12,7 +12,7 @@ Connect external tools from [MCP servers](https://modelcontextprotocol.io/docs/g Only supports MCP servers with [HTTP transport](https://modelcontextprotocol.io/docs/learn/architecture#transport-layer) right now. -## Adding MCP servers +## Add MCP servers Go to **Tools** → **MCP** in your workspace. @@ -50,7 +50,7 @@ You'll see a server card once it finds tools: ![MCP Server Card](/images/use-dify/workspace/mcp-server-card.png) -## Managing servers +## Manage servers Click any server card to: @@ -66,7 +66,7 @@ Click any server card to: **Remove**: Disconnect the server (this breaks apps using its tools) -## Using MCP tools +## Use MCP tools Once connected, MCP tools show up everywhere you'd expect: @@ -76,7 +76,7 @@ Once connected, MCP tools show up everywhere you'd expect: **In agent nodes**: Same as regular agents -## Customizing tools +## Customize tools When you add an MCP tool, you can customize it: @@ -92,7 +92,7 @@ When you add an MCP tool, you can customize it: **Example**: For a search tool, set `numResults` to 5 (fixed) but keep `query` on auto. -## Sharing apps +## Share apps When you export apps that use MCP tools: - The export includes server IDs diff --git a/en/use-dify/build/orchestrate-node.mdx b/en/use-dify/build/orchestrate-node.mdx index 51be422ae..c8f5d27b9 100644 --- a/en/use-dify/build/orchestrate-node.mdx +++ b/en/use-dify/build/orchestrate-node.mdx @@ -1,5 +1,5 @@ --- -title: "Flow Logic" +title: Flow Logic --- diff --git a/en/use-dify/build/predefined-error-handling-logic.mdx b/en/use-dify/build/predefined-error-handling-logic.mdx index 28b6e84ee..c8cce4f75 100644 --- a/en/use-dify/build/predefined-error-handling-logic.mdx +++ b/en/use-dify/build/predefined-error-handling-logic.mdx @@ -1,5 +1,5 @@ --- -title: "Handling Errors" +title: Handle Errors --- diff --git a/en/use-dify/build/shortcut-key.mdx b/en/use-dify/build/shortcut-key.mdx index c306c5030..3bd2fa9c5 100644 --- a/en/use-dify/build/shortcut-key.mdx +++ b/en/use-dify/build/shortcut-key.mdx @@ -1,5 +1,5 @@ --- -title: "Hotkeys" +title: Hotkeys --- Speed up your workflow building with keyboard shortcuts. diff --git a/en/use-dify/build/version-control.mdx b/en/use-dify/build/version-control.mdx index f0c1ddf19..ab461b103 100644 --- a/en/use-dify/build/version-control.mdx +++ b/en/use-dify/build/version-control.mdx @@ -1,5 +1,5 @@ --- -title: "Version Control" +title: Version Control --- Track changes and manage versions in Chatflow and Workflow apps. @@ -28,7 +28,7 @@ Track changes and manage versions in Chatflow and Workflow apps. ![Previous Versions](https://assets-docs.dify.ai/2025/03/4cd05033b93d84b53496f3d02e88601f.png) -## Publishing versions +## Publish versions Click **Publish** → **Publish Update** to make your draft live. @@ -42,7 +42,7 @@ Your draft becomes the new Latest Version, and you get a fresh draft to work in. ![](https://assets-docs.dify.ai/2025/03/67e95de17577bc272addad6c33f8ea59.png) -## Viewing versions +## View versions Click the history icon to see all versions: @@ -58,7 +58,7 @@ Filter by: ![Only Named Versions (Skip Auto-Generated Names)](https://assets-docs.dify.ai/2025/03/0bf8fef8858671a8fef160f49dd83dad.jpg) -## Managing versions +## Manage versions **Name a version**: Give it a proper name instead of the auto-generated one diff --git a/en/use-dify/debug/history-and-logs.mdx b/en/use-dify/debug/history-and-logs.mdx index c5c7788e3..0183e67e7 100644 --- a/en/use-dify/debug/history-and-logs.mdx +++ b/en/use-dify/debug/history-and-logs.mdx @@ -1,5 +1,5 @@ --- -title: "Run History" +title: Run History --- Dify records detailed Run History every time your workflow runs. You can see what happened at both the application level and for individual nodes. diff --git a/en/use-dify/debug/step-run.mdx b/en/use-dify/debug/step-run.mdx index 65f47e212..b9a79a76f 100644 --- a/en/use-dify/debug/step-run.mdx +++ b/en/use-dify/debug/step-run.mdx @@ -1,5 +1,5 @@ --- -title: "Single Node" +title: Single Node --- Test individual nodes or run through your workflow step-by-step to catch issues before publishing. @@ -28,7 +28,7 @@ When you run nodes one at a time, their outputs are cached in the Variable Inspe This is useful when you want to test how a node responds to different data without having to modify and re-run all the nodes before it. Just change the variable values in the inspector and run the node again. -## Viewing execution history +## View execution history Every node execution creates a record. Click "Last run" on any node to see its most recent execution details including what data went in, what came out, and how long it took. diff --git a/en/use-dify/debug/variable-inspect.mdx b/en/use-dify/debug/variable-inspect.mdx index dce52d9d1..3f95cfdf2 100644 --- a/en/use-dify/debug/variable-inspect.mdx +++ b/en/use-dify/debug/variable-inspect.mdx @@ -1,5 +1,5 @@ --- -title: "Variable Inspector" +title: Variable Inspector --- The Variable Inspector shows you all the data flowing through your workflow. It captures inputs and outputs from each node after they run, so you can see what's happening and test different scenarios. @@ -8,7 +8,7 @@ The Variable Inspector shows you all the data flowing through your workflow. It ![](https://assets-docs.dify.ai/2025/06/38f26d7339f64abfdfb6955b1c34f4ae.png) -## Viewing variables +## View variables After any node runs, its output variables appear in the inspector panel at the bottom of the screen. Click any variable to see its full content. @@ -16,7 +16,7 @@ After any node runs, its output variables appear in the inspector panel at the b ![Any Node Runs, Its Output Variables Appear in the Inspector Panel at the Bottom](https://assets-docs.dify.ai/2025/06/94a4741c25204db5fd1281ec475093d9.png) -## Editing variables +## Edit variables You can edit most variable values by clicking on them. When you run downstream nodes, they'll use your edited values instead of the original ones. This lets you test different scenarios without re-running the entire workflow. @@ -30,7 +30,7 @@ For example, if an LLM node generates SQL like `SELECT * FROM users`, you can ed ![](https://assets-docs.dify.ai/2025/06/fb8c49fc0c8c63866f1a9379e8752d9e.png) -## Resetting variables +## Reset variables Click the revert icon next to any variable to restore its original value, or click "Reset all" to clear all cached variables at once. diff --git a/en/use-dify/getting-started/introduction.mdx b/en/use-dify/getting-started/introduction.mdx index 027ae6801..424f27dc3 100644 --- a/en/use-dify/getting-started/introduction.mdx +++ b/en/use-dify/getting-started/introduction.mdx @@ -1,5 +1,5 @@ --- -title: "Introduction" +title: Introduction mode: "wide" --- diff --git a/en/use-dify/getting-started/key-concepts.mdx b/en/use-dify/getting-started/key-concepts.mdx index 41b3a6728..03804386c 100644 --- a/en/use-dify/getting-started/key-concepts.mdx +++ b/en/use-dify/getting-started/key-concepts.mdx @@ -1,6 +1,6 @@ --- -title: "Key Concepts" -description: "Quick overview of essential Dify concepts" +title: Key Concepts +description: Quick overview of essential Dify concepts --- ### Dify App diff --git a/en/use-dify/getting-started/quick-start.mdx b/en/use-dify/getting-started/quick-start.mdx index c28e41828..b75cec323 100644 --- a/en/use-dify/getting-started/quick-start.mdx +++ b/en/use-dify/getting-started/quick-start.mdx @@ -1,6 +1,6 @@ --- -title: "30-Minute Quick Start" -description: "Dive into Dify through an example app" +title: 30-Minute Quick Start +description: Dive into Dify through an example app --- This step-by-step tutorial will walk you through creating a multi-platform content generator from scratch. diff --git a/en/use-dify/knowledge/create-knowledge/import-text-data/sync-from-notion.mdx b/en/use-dify/knowledge/create-knowledge/import-text-data/sync-from-notion.mdx index 3550448a8..f1e913d1a 100644 --- a/en/use-dify/knowledge/create-knowledge/import-text-data/sync-from-notion.mdx +++ b/en/use-dify/knowledge/create-knowledge/import-text-data/sync-from-notion.mdx @@ -13,7 +13,7 @@ Dify datasets support importing from Notion and setting up **synchronization** s ![Alternatively, You Can Go to Settings -- Data Sources -- Add Data Source, Click](https://assets-docs.dify.ai/2024/12/f1d5bcdcfbd57407e0bce1597df4daad.png) -### Importing Notion Data +### Import Notion Data After completing the authorization verification, go to the create dataset page, click **Sync from Notion Content**, and select the authorized pages you need to import. @@ -31,7 +31,7 @@ _**Note: images and files cannot be imported, and data from tables will be conve ![_Note](https://assets-docs.dify.ai/2025/04/723f7782853698598726d09997383747.png) -### Synchronizing Notion Data +### Synchronize Notion Data If your Notion content has been updated, you can sync the changes by clicking the **Sync** button for the corresponding page in the document list of your knowledge base. Syncing involves an embedding process, which will consume tokens from your embedding model. @@ -43,7 +43,7 @@ If your Notion content has been updated, you can sync the changes by clicking th Notion offers two integration options: **internal integration** and **public integration**. For more details on the differences between these two methods, please refer to the [official Notion documentation](https://developers.notion.com/guides/get-started/authorization). -#### 1. Using Internal Integration +#### 1. Use Internal Integration First, create an integration in the integration settings page [Create Integration](https://www.notion.so/my-integrations). By default, all integrations start as internal integrations; internal integrations will be associated with the workspace you choose, so you need to be the workspace owner to create an integration. @@ -68,7 +68,7 @@ NOTION_INTEGRATION_TYPE = internal or NOTION_INTEGRATION_TYPE = public NOTION_INTERNAL_SECRET=you-internal-secret ``` -#### **Using Public Integration** +#### **Use Public Integration** **You need to upgrade the internal integration to a public integration.** Navigate to the Distribution page of the integration, and toggle the switch to make the integration public. When switching to the public setting, you need to fill in additional information in the Organization Information form below, including your company name, website, and redirect URL, then click the **Submit** button. diff --git a/en/use-dify/knowledge/create-knowledge/import-text-data/sync-from-website.mdx b/en/use-dify/knowledge/create-knowledge/import-text-data/sync-from-website.mdx index d2f0f487b..6aad53236 100644 --- a/en/use-dify/knowledge/create-knowledge/import-text-data/sync-from-website.mdx +++ b/en/use-dify/knowledge/create-knowledge/import-text-data/sync-from-website.mdx @@ -40,11 +40,9 @@ The configuration options include: Whether to crawl sub-pages, Page crawling lim After importing the parsed text from the webpage, it is stored in the knowledge base documents. View the import results and click **Add URL** to continue importing new web pages. -*** - ## Jina Reader -### 1. Configuring Jina Reader Credentials +### 1. Configure Jina Reader Credentials Click on the avatar in the upper right corner, then go to the **DataSource** page, and click the **Configure** button next to Jina Reader. @@ -54,7 +52,7 @@ Click on the avatar in the upper right corner, then go to the **DataSource** pag Log in to the [Jina Reader website](https://jina.ai/reader/), complete registration, obtain the API Key, then fill it in and save. -### 2. Using Jina Reader to Crawl Web Content +### 2. Use Jina Reader to Crawl Web Content On the knowledge base creation page, select **Sync from website**, choose Jina Reader as the provider, and enter the target URL to be crawled. diff --git a/en/use-dify/knowledge/create-knowledge/setting-indexing-methods.mdx b/en/use-dify/knowledge/create-knowledge/setting-indexing-methods.mdx index 4058a2582..a4437a3ee 100644 --- a/en/use-dify/knowledge/create-knowledge/setting-indexing-methods.mdx +++ b/en/use-dify/knowledge/create-knowledge/setting-indexing-methods.mdx @@ -103,7 +103,7 @@ Both retrieval methods are supported in Dify’s knowledge base. The specific re ![Vector Search Settings](https://assets-docs.dify.ai/2024/12/620044faa47a5037f85b32a27a56fce5.png) -**Vector Search Settings:** +**Vector Search Settings**: **Rerank Model**: Disabled by default. When enabled, a third-party Rerank model will sort the text chunks returned by Vector Search to optimize results. This helps the LLM access more precise information and improve output quality. Before enabling this option, go to **Settings** → **Model Providers** and configure the Rerank model’s API key. @@ -119,11 +119,9 @@ Both retrieval methods are supported in Dify’s knowledge base. The specific re > The TopK and Score configurations are only effective during the Rerank phase. Therefore, to apply either of these settings, it is necessary to add and enable a Rerank model. - *** - **Full-Text Search** -**Definition:** Indexing all terms in the document, allowing users to query any terms and return text fragments containing those terms. +**Definition**: Indexing all terms in the document, allowing users to query any terms and return text fragments containing those terms. ![Full-Text Search Settings](https://assets-docs.dify.ai/2024/12/513bff1ca38ec746b3246502b0311b39.png) @@ -143,8 +141,6 @@ Both retrieval methods are supported in Dify’s knowledge base. The specific re > The TopK and Score configurations are only effective during the Rerank phase. Therefore, to apply either of these settings, it is necessary to add and enable a Rerank model. - *** - **Hybrid Search** **Definition**: This process combines full-text search and vector search, performing both simultaneously. It includes a reordering step to select the best-matching results from both search outcomes based on the user’s query. @@ -168,8 +164,6 @@ In this mode, you can specify **"Weight settings"** without needing to configure * **Custom Keyword and Semantic Weights** In addition to enabling only semantic search or keyword search, we provide flexible custom weight settings. You can continuously adjust the weights of the two methods to identify the optimal weight ratio that suits your business scenario. - *** - **Rerank Model** Disabled by default. When enabled, a third-party Rerank model will sort the text chunks returned by Hybrid Search to optimize results. This helps the LLM access more precise information and improve output quality. Before enabling this option, go to **Settings** → **Model Providers** and configure the Rerank model’s API key. @@ -191,7 +185,7 @@ The **"Weight Settings"** and **"Rerank Model"** settings support the following In **Economical Indexing** mode, only the inverted index approach is available. An inverted index is a data structure designed for fast keyword retrieval within documents, commonly used in online search engines. Inverted indexing supports only the **TopK** setting. -**TopK:** Determines how many text chunks, deemed most similar to the user’s query, are retrieved. It also automatically adjusts the number of chunks based on the chosen model’s context window. The default value is **3**, and higher numbers will recall more text chunks. +**TopK**: Determines how many text chunks, deemed most similar to the user’s query, are retrieved. It also automatically adjusts the number of chunks based on the chosen model’s context window. The default value is **3**, and higher numbers will recall more text chunks.

diff --git a/en/use-dify/knowledge/integrate-knowledge-within-application.mdx b/en/use-dify/knowledge/integrate-knowledge-within-application.mdx index c63335459..f9a166c04 100644 --- a/en/use-dify/knowledge/integrate-knowledge-within-application.mdx +++ b/en/use-dify/knowledge/integrate-knowledge-within-application.mdx @@ -3,7 +3,7 @@ title: Integrate Knowledge within Apps sidebarTitle: Integrate in Apps --- -### Creating an Application Integrated with Knowledge Base +### Create an Application Integrated with Knowledge Base A **"Knowledge Base"** can be used as an external information source to provide precise answers to user questions via LLM. You can associate an existing knowledge base with any [application type](/en/use-dify/getting-started/key-concepts#dify-app) in Dify. @@ -18,9 +18,7 @@ Taking a chat assistant as an example, the process is as follows: 7. In **Debug and Preview**, input user questions related to the knowledge base for debugging 8. After debugging, click **Publish** button to make an AI application based on your own knowledge! -*** - -### Connecting Knowledge and Setting Retrieval Mode +### Connect Knowledge and Set Retrieval Mode In applications that utilize multiple knowledge bases, it is essential to configure the retrieval mode to enhance the precision of retrieved content. To set the retrieval mode for the knowledge bases, navigate to **Context -- Retrieval Settings -- Rerank Setting**. @@ -84,9 +82,9 @@ The **Knowledge Retrieval** node allows you to filter documents using metadata f #### Steps 1. Select Filter Mode: - - **Disabled (Default):** No metadata filtering. + - **Disabled (Default)**: No metadata filtering. - - **Automatic:** Filters auto-configure from query variables in the **Knowledge Retrieval** node. + - **Automatic**: Filters auto-configure from query variables in the **Knowledge Retrieval** node. > Note: Automatic Mode requires model selection for document retrieval. @@ -94,7 +92,7 @@ The **Knowledge Retrieval** node allows you to filter documents using metadata f ![Model_Selection](https://assets-docs.dify.ai/2025/03/fe387793ad9923660f9f9470aacff01b.png) - - **Manual:** Configure filters manually. + - **Manual**: Configure filters manually. ![Manual](https://assets-docs.dify.ai/2025/03/ec6329e265e035e3a0d6941c9313a19d.png) @@ -151,13 +149,13 @@ The **Knowledge Retrieval** node allows you to filter documents using metadata f 5. Add filter values: - - **Variable:** Select from existing **Chatflow/Workflow** variables. + - **Variable**: Select from existing **Chatflow/Workflow** variables. ![Variable](https://assets-docs.dify.ai/2025/03/4c2c55ffcf0f72553fabdf23f86597d0.png) - - **Constant:** Enter specific values. + - **Constant**: Enter specific values. > Time-type fields can only be filtered by constants The date picker is for time-type fields. diff --git a/en/use-dify/knowledge/knowledge-pipeline/authorize-data-source.mdx b/en/use-dify/knowledge/knowledge-pipeline/authorize-data-source.mdx index 42497c8e9..b376cf6aa 100644 --- a/en/use-dify/knowledge/knowledge-pipeline/authorize-data-source.mdx +++ b/en/use-dify/knowledge/knowledge-pipeline/authorize-data-source.mdx @@ -4,7 +4,7 @@ title: Authorize Data Source Dify supports connections to various external data sources. To ensure data security and access control, different data sources require appropriate authorization configurations. Dify provides two main authorization methods: **API Key** and **OAuth**. -## Accessing Data Source Authorization +## Access Data Source Authorization In Dify, you can access data source authorization through the following two methods: diff --git a/en/use-dify/knowledge/metadata.mdx b/en/use-dify/knowledge/metadata.mdx index 68710022b..a5fecd827 100644 --- a/en/use-dify/knowledge/metadata.mdx +++ b/en/use-dify/knowledge/metadata.mdx @@ -13,21 +13,21 @@ This guide aims to help you understand metadata and effectively manage your know ### Core Concepts -- **Field:** The label of a metadata field (e.g., "author", "language"). +- **Field**: The label of a metadata field (e.g., "author", "language"). -- **Value:** The information stored in a metadata field (e.g., "Jack", "English"). +- **Value**: The information stored in a metadata field (e.g., "Jack", "English"). ![field_name_and_value](https://assets-docs.dify.ai/2025/03/b6a197aa21ab92db93869fcbfa156b62.png) -- **Value Count:** The number of values contained in a metadata field,including duplicates. (e.g., "3"). +- **Value Count**: The number of values contained in a metadata field, including duplicates. (e.g., "3"). ![metadata_field](https://assets-docs.dify.ai/2025/03/330f26e90438cf50167c4cb6ce30e458.png) -- **Value Type:** The type of value a field can contain. +- **Value Type**: The type of value a field can contain. - Dify supports three value types: - String: For text-based information - Number: For numerical data @@ -263,21 +263,21 @@ To update metadata in bulk: 1. In the editor: -- **Add Values:** Type directly in the field boxes. +- **Add Values**: Type directly in the field boxes. -- **Reset Values:** Click the blue dot that appears on hover. +- **Reset Values**: Click the blue dot that appears on hover. ![reset_values](https://assets-docs.dify.ai/2025/03/01c0cde5a6eafa48e1c6e5438fc2fa6b.png) -- **Delete Values:** Clear the field or delete the **Multiple Value** card. +- **Delete Values**: Clear the field or delete the **Multiple Value** card. ![multiple_values](https://assets-docs.dify.ai/2025/03/5c4323095644d2658881b783246914f1.png) -- **Delete fields:** Click the delete icon (fields appear struck through and grayed out). +- **Delete fields**: Click the delete icon (fields appear struck through and grayed out). > Note: This only deletes the field from this document, not from your knowledge base. @@ -366,11 +366,11 @@ To update a single document's metadata fields and values: 2. Edit metadata: - - **Update Values:** Type directly in value fields or delete it. + - **Update Values**: Type directly in value fields or delete it. > Note: You can only modify the value, not the value name. - - **Delete Fields:** Click the delete icon. + - **Delete Fields**: Click the delete icon. > Note: This only deletes the field from this document, not from your knowledge base. diff --git a/en/use-dify/monitor/analysis.mdx b/en/use-dify/monitor/analysis.mdx index 8bcd095ae..21925325f 100644 --- a/en/use-dify/monitor/analysis.mdx +++ b/en/use-dify/monitor/analysis.mdx @@ -1,6 +1,6 @@ --- -title: "Dashboard" -description: "Monitor performance, costs, and user engagement through Dify's built-in analytics dashboard" +title: Dashboard +description: Monitor performance, costs, and user engagement through Dify's built-in analytics dashboard --- The dashboard tracks four metrics over time to show how your application performs: @@ -9,9 +9,9 @@ The dashboard tracks four metrics over time to show how your application perform ![Monitoring Dashboard](/images/use-dify/monitor/monitoring-dashboard.png) -**Total Messages:** Conversation volume -**Active Users:** Users with meaningful interactions (more than one exchange) -**Average User Interactions:** Engagement depth per session -**Token Usage:** Resource consumption and costs +**Total Messages**: Conversation volume +**Active Users**: Users with meaningful interactions (more than one exchange) +**Average User Interactions**: Engagement depth per session +**Token Usage**: Resource consumption and costs Use the time selector to view trends over different periods. Click **"Tracing app performance"** to connect external observability platforms like Langfuse or LangSmith for deeper analytics. \ No newline at end of file diff --git a/en/use-dify/monitor/annotation-reply.mdx b/en/use-dify/monitor/annotation-reply.mdx index 59e0a6916..f24e8f323 100644 --- a/en/use-dify/monitor/annotation-reply.mdx +++ b/en/use-dify/monitor/annotation-reply.mdx @@ -1,6 +1,6 @@ --- -title: "Annotation System" -description: "Build a curated library of high-quality responses to improve consistency and bypass AI generation" +title: Annotation System +description: Build a curated library of high-quality responses to improve consistency and bypass AI generation --- Annotations let you create a curated library of perfect responses for specific questions. When users ask similar questions, Dify returns your pre-written answers instead of generating new responses, ensuring consistency and eliminating AI hallucinations for critical topics. @@ -27,16 +27,16 @@ When annotation reply is enabled: This creates a "fast path" for known good answers while maintaining AI flexibility for new questions. -## Setting Up Annotations +## Set Up Annotations **Enable in App Configuration** Navigate to **Orchestrate → Add Features** and enable annotation reply. Configure the similarity threshold and embedding model for matching. -**Similarity Threshold:** Higher values require closer matches. Start with moderate settings and adjust based on hit rates. +**Similarity Threshold**: Higher values require closer matches. Start with moderate settings and adjust based on hit rates. -**Embedding Model:** Used to vectorize questions for semantic matching. Changing the model regenerates all embeddings. +**Embedding Model**: Used to vectorize questions for semantic matching. Changing the model regenerates all embeddings. -## Creating Annotations +## Create Annotations **From Conversations** In debug mode or logs, click on AI responses and edit them into the perfect answer. Save as an annotation for future use. @@ -47,7 +47,7 @@ Download the template, create Q&A pairs in the specified format, and upload for **Manual Entry** Add annotations directly in the Logs & Annotations interface with custom questions and responses. -## Managing Annotation Quality +## Manage Annotation Quality **Hit Tracking** Monitor which annotations are matched, how often they're used, and the similarity scores of matches. This shows which annotations provide value. diff --git a/en/use-dify/monitor/integrations/integrate-aliyun.mdx b/en/use-dify/monitor/integrations/integrate-aliyun.mdx index 9bc0e0604..52a417201 100644 --- a/en/use-dify/monitor/integrations/integrate-aliyun.mdx +++ b/en/use-dify/monitor/integrations/integrate-aliyun.mdx @@ -13,8 +13,6 @@ Alibaba Cloud provides a fully managed, maintenance-free observability platform For more details, please refer to the [Cloud Monitor documentation](https://www.alibabacloud.com/help/en/cms/cloudmonitor-1-0/product-overview/what-is-cloudmonitor?spm=a3c0i.63551.2277339270.1.76c7112eeKEvSr). -*** - ## How to Configure Alibaba Cloud Monitor ### 1. Get Alibaba Cloud Endpoint and License Key @@ -49,8 +47,6 @@ Alibaba Cloud provides a fully managed, maintenance-free observability platform 4. In the dialog that appears, enter the **License Key** and **Endpoint** obtained in step 1, and customize the **App Name** (the application name displayed in the ARMS console), then click **Save & Enable**. -*** - ## View Monitoring Data in Alibaba Cloud Monitor After configuration, debug or production data from applications in Dify can be monitored in Cloud Monitor. @@ -63,8 +59,6 @@ In the Dify console, select an application with tracing enabled, go to **Tracing Go to the corresponding Dify application in the **LLM Application Monitoring > Application List** page of the ARMS console. -*** - ## Access More Data Cloud Monitor provides multi-language non-invasive agents that support accessing various components of the Dify cluster to achieve end-to-end tracing. @@ -77,8 +71,6 @@ Cloud Monitor provides multi-language non-invasive agents that support accessing | Worker | OpenTelemetry Agent | [Submit Python Application Data via OpenTelemetry](https://www.alibabacloud.com/help/en/opentelemetry/user-guide/use-managed-service-for-opentelemetry-to-submit-the-trace-data-of-python-applications?spm=a2c63.p38356.help-menu-90275.d_2_0_5_0.18ee53a4EGoGuS) | | Plugin-Daemon | LoongSuite-Go Agent | [loongsuite-go-agent](https://github.com/alibaba/loongsuite-go-agent/blob/main/README.md) | -*** - ## Monitoring Data List Cloud Monitor supports collecting data from Dify's Workflow/Chatflow/Chat/Agent applications, including execution details of workflows and workflow nodes, covering model calls, tool calls, knowledge retrieval, execution details of various process nodes, as well as metadata such as conversations and user information. diff --git a/en/use-dify/monitor/integrations/integrate-arize.mdx b/en/use-dify/monitor/integrations/integrate-arize.mdx index 773c168ad..e9175aba8 100644 --- a/en/use-dify/monitor/integrations/integrate-arize.mdx +++ b/en/use-dify/monitor/integrations/integrate-arize.mdx @@ -12,8 +12,6 @@ Enterprise-grade LLM observability, online & offline evaluation, monitoring, and For more details, please refer to [Arize](https://arize.com). -*** - ### How to Configure Arize #### 1. Register/Login to [Arize](https://app.arize.com/auth/join) @@ -26,7 +24,7 @@ Retrieve your Arize API Key from the user menu at the top-right. Click on **API ![Arize API Key](https://i.ibb.co/JwBmQxnf/dify-docs-arize-api-key.png) -#### 3. Integrating Arize with Dify +#### 3. Integrate Arize with Dify Configure Arize in the Dify application. Open the application you need to monitor, open **Monitoring** in the side menu, and select **Tracing app performance** on the page. diff --git a/en/use-dify/monitor/integrations/integrate-langfuse.mdx b/en/use-dify/monitor/integrations/integrate-langfuse.mdx index bdb166271..bbd35b3cf 100644 --- a/en/use-dify/monitor/integrations/integrate-langfuse.mdx +++ b/en/use-dify/monitor/integrations/integrate-langfuse.mdx @@ -11,8 +11,6 @@ Langfuse is an open-source LLM engineering platform that helps teams collaborate Introduction to Langfuse: [https://langfuse.com/](https://langfuse.com/) -*** - ### How to Configure Langfuse 1. Register and log in to Langfuse on the [official website](https://langfuse.com/) @@ -64,9 +62,7 @@ Once successfully saved, you can view the status on the current page. If it show ![View Configuration Status](https://assets-docs.dify.ai/dify-enterprise-mintlify/en/guides/monitoring/integrate-external-ops-tools/1aa086a1aead0e29948e7d6d5815d5d1.png) -*** - -### Viewing Monitoring Data in Langfuse +### View Monitoring Data in Langfuse After configuration, debugging or production data of the application in Dify can be viewed in Langfuse. @@ -78,8 +74,6 @@ After configuration, debugging or production data of the application in Dify can ![](https://assets-docs.dify.ai/2025/04/88f3adfb03f325d2ea800ba5685b9ec9.png) -*** - ### List of monitoring data #### Trace the information of Workflow and Chatflow @@ -558,7 +552,7 @@ After configuration, debugging or production data of the application in Dify can * tool\_inputs - Tool inputs * tool\_outputs - Tool outputs * message\_data - Message data -* error - Error Message,if exist +* error - Error Message, if exist * inputs - Input of Message * outputs - Output of Message * tool\_config - Tool config @@ -641,8 +635,8 @@ After configuration, debugging or production data of the application in Dify can The [Langfuse Prompt Management Plugin](https://github.com/gao-ai-com/dify-plugin-langfuse) (community maintained) lets you use prompts that are [managed and versioned in Langfuse](https://langfuse.com/docs/prompt-management/get-started) in your Dify applications, enhancing your LLM application development workflow. Key features include: -- **Get Prompt:** Fetch specific prompts managed in Langfuse. -- **Search Prompts:** Search for prompts in Langfuse using various filters. -- **Update Prompt:** Create new versions of prompts in Langfuse and set tags/labels. +- **Get Prompt**: Fetch specific prompts managed in Langfuse. +- **Search Prompts**: Search for prompts in Langfuse using various filters. +- **Update Prompt**: Create new versions of prompts in Langfuse and set tags/labels. This integration streamlines the process of managing and versioning your prompts, contributing to more efficient development and iteration cycles. You can find the plugin and installation instructions [here](https://github.com/gao-ai-com/dify-plugin-langfuse). diff --git a/en/use-dify/monitor/integrations/integrate-langsmith.mdx b/en/use-dify/monitor/integrations/integrate-langsmith.mdx index 99aadb321..b3f5d4705 100644 --- a/en/use-dify/monitor/integrations/integrate-langsmith.mdx +++ b/en/use-dify/monitor/integrations/integrate-langsmith.mdx @@ -12,8 +12,6 @@ LangSmith is a platform for building production-grade LLM applications. It is us For more details, please refer to [LangSmith](https://www.langchain.com/langsmith). -*** - ### How to Configure LangSmith #### 1. Register/Login to [LangSmith](https://www.langchain.com/langsmith) @@ -58,7 +56,7 @@ Copy and save the created API key. ![Copy API Key](https://assets-docs.dify.ai/dify-enterprise-mintlify/en/guides/monitoring/integrate-external-ops-tools/723e96a13e8f722d6df714b11ffd0bb1.png) -#### 4. Integrating LangSmith with Dify +#### 4. Integrate LangSmith with Dify Configure LangSmith in the Dify application. Open the application you need to monitor, open **Monitoring** in the side menu, and select **Tracing app performance** on the page. @@ -82,7 +80,7 @@ Once successfully saved, you can view the monitoring status on the current page. ![View Configuration Status](https://assets-docs.dify.ai/dify-enterprise-mintlify/en/guides/monitoring/integrate-external-ops-tools/43369dc4de8f606c166fae2efab97d73.png) -### Viewing Monitoring Data in LangSmith +### View Monitoring Data in LangSmith Once configured, the debug or production data from applications within Dify can be monitored in LangSmith. diff --git a/en/use-dify/monitor/integrations/integrate-opik.mdx b/en/use-dify/monitor/integrations/integrate-opik.mdx index fe35fda48..2dbc0d539 100644 --- a/en/use-dify/monitor/integrations/integrate-opik.mdx +++ b/en/use-dify/monitor/integrations/integrate-opik.mdx @@ -26,7 +26,7 @@ Retrieve your Opik API Key from the user menu at the top-right. Click on **API K ![Opik API Key](https://assets-docs.dify.ai/2025/01/a66603f01e4ffaa593a8b78fcf3f8204.png) -#### 3. Integrating Opik with Dify +#### 3. Integrate Opik with Dify Configure Opik in the Dify application. Open the application you need to monitor, open **Monitoring** in the side menu, and select **Tracing app performance** on the page. @@ -42,7 +42,7 @@ After clicking configure, paste the **API Key** and **project name** created in Once successfully saved, you can view the monitoring status on the current page. -### Viewing Monitoring Data in Opik +### View Monitoring Data in Opik Once configured, you can debug or use the Dify application as usual. All usage history can be monitored in Opik. diff --git a/en/use-dify/monitor/integrations/integrate-phoenix.mdx b/en/use-dify/monitor/integrations/integrate-phoenix.mdx index 7ffa82457..98a1c3b77 100644 --- a/en/use-dify/monitor/integrations/integrate-phoenix.mdx +++ b/en/use-dify/monitor/integrations/integrate-phoenix.mdx @@ -12,8 +12,6 @@ Open-source & OpenTelemetry-based observability, evaluation, prompt engineering For more details, please refer to [Phoenix](https://phoenix.arize.com). -*** - ### How to Configure Phoenix #### 1. Register/Login to [Phoenix](https://app.arize.com/auth/phoenix/signup) @@ -26,7 +24,7 @@ Retrieve your Phoenix API Key from the user menu at the top-right. Click on **AP ![Phoenix API Key](https://i.ibb.co/pB1W0pk8/dify-docs-phoenix-api-key.png) -#### 3. Integrating Phoenix with Dify +#### 3. Integrate Phoenix with Dify Configure Phoenix in the Dify application. Open the application you need to monitor, open **Monitoring** in the side menu, and select **Tracing app performance** on the page. @@ -72,7 +70,7 @@ After launching your space, you can create your Phoenix API Key from the **Setti ![Phoenix Cloud API Key](https://i.ibb.co/SXMyX9K3/dify-docs-phoenix-cloud-api-key.png) -#### 4. Integrating Phoenix Cloud with Dify +#### 4. Integrate Phoenix Cloud with Dify Configure Phoenix in the Dify application. Open the application you need to monitor, open **Monitoring** in the side menu, and select **Tracing app performance** on the page. diff --git a/en/use-dify/monitor/integrations/integrate-weave.mdx b/en/use-dify/monitor/integrations/integrate-weave.mdx index 1f0b3b995..68ede6d4c 100644 --- a/en/use-dify/monitor/integrations/integrate-weave.mdx +++ b/en/use-dify/monitor/integrations/integrate-weave.mdx @@ -12,15 +12,13 @@ Weights & Biases (W&B) Weave is a framework for tracking, experimenting with, ev For more details, please refer to [Weave](https://docs.wandb.ai/weave). -*** - ### How to Configure Weave #### 1. Register/Login Register/Login to [W&B Weave](https://wandb.ai/signup) and get your API key. Then, copy your API key from [here](https://wandb.ai/authorize). -#### 2. Integrating W&B Weave with Dify +#### 2. Integrate W&B Weave with Dify Configure Weave in the Dify application. Open the application you need to monitor, open **Monitoring** in the side menu, and select **Tracing app performance** on the page. @@ -40,7 +38,7 @@ Once successfully saved, you can view the monitoring status on the current page. ![Once Successfully Saved, You Can View the Monitoring Status on the Current Page](https://assets-docs.dify.ai/2025/04/9486cee7bbb61f069842c9ea860e679c.png) -### Viewing Monitoring Data in Weave +### View Monitoring Data in Weave Once configured, the debug or production data from applications within Dify can be monitored in Weave. diff --git a/en/use-dify/monitor/logs.mdx b/en/use-dify/monitor/logs.mdx index 590c1136e..c5c0a4227 100644 --- a/en/use-dify/monitor/logs.mdx +++ b/en/use-dify/monitor/logs.mdx @@ -1,6 +1,6 @@ --- -title: "Logs" -description: "Monitor real-time conversations, debug issues, and collect user feedback" +title: Logs +description: Monitor real-time conversations, debug issues, and collect user feedback --- Conversation logs provide detailed visibility into every interaction with your AI application. Use them to debug specific issues, understand user behavior patterns, and collect feedback for continuous improvement. @@ -16,18 +16,18 @@ Thumbs up/down ratings and user comments are captured alongside the conversation **System Context** Model used, token consumption, response times, and any errors or warnings during processing. -**Exclusions:** Debugging sessions and prompt testing are not included in logs. +**Exclusions**: Debugging sessions and prompt testing are not included in logs. -## Using the Logs Console +## Use the Logs Console Access logs from your application's navigation menu. The interface shows: -- **Conversation Timeline:** Chronological list of user interactions -- **Message Details:** Full conversation context with AI responses -- **Performance Data:** Response times and token usage per interaction -- **User Feedback:** Ratings and comments from users and team members +- **Conversation Timeline**: Chronological list of user interactions +- **Message Details**: Full conversation context with AI responses +- **Performance Data**: Response times and token usage per interaction +- **User Feedback**: Ratings and comments from users and team members -## Debugging with Logs +## Debug with Logs **Failed Interactions** Quickly identify conversations where the AI provided poor responses, failed to understand user intent, or encountered errors. @@ -61,7 +61,7 @@ Ensure your application complies with local data privacy regulations. Publish a - **Self-hosted**: Unlimited by default; configurable via environment variables `WORKFLOW_LOG_CLEANUP_ENABLED`, `WORKFLOW_LOG_RETENTION_DAYS`, and `WORKFLOW_LOG_CLEANUP_BATCH_SIZE`. -## Improving Applications with Logs +## Improve Applications with Logs **Pattern Recognition** Look for recurring user questions that your application handles poorly. These indicate opportunities for prompt improvements or knowledge base updates. diff --git a/en/use-dify/nodes/agent.mdx b/en/use-dify/nodes/agent.mdx index 44e9ce0b7..4de469659 100644 --- a/en/use-dify/nodes/agent.mdx +++ b/en/use-dify/nodes/agent.mdx @@ -1,6 +1,6 @@ --- -title: "Agent" -description: "Give LLMs autonomous control over tools for complex task execution" +title: Agent +description: Give LLMs autonomous control over tools for complex task execution --- The Agent node gives your LLM autonomous control over tools, enabling it to iteratively decide which tools to use and when to use them. Instead of pre-planning every step, the Agent reasons through problems dynamically, calling tools as needed to complete complex tasks. diff --git a/en/use-dify/nodes/answer.mdx b/en/use-dify/nodes/answer.mdx index b2057e81e..d5184e757 100644 --- a/en/use-dify/nodes/answer.mdx +++ b/en/use-dify/nodes/answer.mdx @@ -1,6 +1,6 @@ --- -title: "Answer" -description: "Define response content in chatflow applications" +title: Answer +description: Define response content in chatflow applications --- The Answer node defines what content gets delivered to users in chatflow applications. Use it to format responses, combine text with variables, and stream multimodal content including text, images, and files. diff --git a/en/use-dify/nodes/code.mdx b/en/use-dify/nodes/code.mdx index cb6543928..2685cf8b8 100644 --- a/en/use-dify/nodes/code.mdx +++ b/en/use-dify/nodes/code.mdx @@ -1,6 +1,6 @@ --- -title: "Code" -description: "Execute custom Python or JavaScript for data processing" +title: Code +description: Execute custom Python or JavaScript for data processing --- The Code node executes custom Python or JavaScript to handle complex data transformations, calculations, and logic within your workflow. Use it when preset nodes aren't sufficient for your specific processing needs. diff --git a/en/use-dify/nodes/doc-extractor.mdx b/en/use-dify/nodes/doc-extractor.mdx index 320f3d5fc..f329ff362 100644 --- a/en/use-dify/nodes/doc-extractor.mdx +++ b/en/use-dify/nodes/doc-extractor.mdx @@ -1,6 +1,6 @@ --- -title: "Document Extractor" -description: "Extract text content from uploaded documents for AI processing" +title: Document Extractor +description: Extract text content from uploaded documents for AI processing --- The Document Extractor node converts uploaded files into text that LLMs can process. Since language models can't directly read document formats like PDF or DOCX, this node serves as the essential bridge between file uploads and AI analysis. diff --git a/en/use-dify/nodes/http-request.mdx b/en/use-dify/nodes/http-request.mdx index e725b6b25..09c18748c 100644 --- a/en/use-dify/nodes/http-request.mdx +++ b/en/use-dify/nodes/http-request.mdx @@ -1,6 +1,6 @@ --- -title: "HTTP Request" -description: "Connect to external APIs and web services" +title: HTTP Request +description: Connect to external APIs and web services --- The HTTP Request node connects your workflow to external APIs and web services. Use it to fetch data, send webhooks, upload files, or integrate with any service that accepts HTTP requests. diff --git a/en/use-dify/nodes/ifelse.mdx b/en/use-dify/nodes/ifelse.mdx index 8bc46ea30..e5c068624 100644 --- a/en/use-dify/nodes/ifelse.mdx +++ b/en/use-dify/nodes/ifelse.mdx @@ -1,6 +1,6 @@ --- -title: "If-Else" -description: "Add conditional logic and branching to workflows" +title: If-Else +description: Add conditional logic and branching to workflows --- The If-Else node adds decision-making logic to your workflows by routing execution down different paths based on conditions you define. It evaluates variables and determines which branch your workflow should follow. diff --git a/en/use-dify/nodes/iteration.mdx b/en/use-dify/nodes/iteration.mdx index 3f34cdb48..6fe25402d 100644 --- a/en/use-dify/nodes/iteration.mdx +++ b/en/use-dify/nodes/iteration.mdx @@ -1,6 +1,6 @@ --- -title: "Iteration" -description: "Process arrays by applying workflows to each element" +title: Iteration +description: Process arrays by applying workflows to each element --- The Iteration node processes arrays by running the same workflow steps on each element sequentially or in parallel. Use it for batch processing tasks that would otherwise hit limits or be inefficient as single operations. @@ -13,7 +13,7 @@ The Iteration node processes arrays by running the same workflow steps on each e The node takes an array input and creates a sub-workflow that runs once for each array element. During each iteration, the current item and its index are available as variables that internal nodes can reference. -**Core Components:** +**Core Components**: - **Input Variables** - Array data from upstream nodes - **Internal Workflow** - The processing steps to perform on each element - **Output Variables** - Collected results from all iterations (also an array) @@ -85,7 +85,7 @@ Generate lengthy content by processing chapter outlines individually: ![Long Article Generation Workflow](https://assets-docs.dify.ai/dify-enterprise-mintlify/en/guides/workflow/node/3a403551d48b178d0a41ce2a5748dd2d.png) -**Workflow Steps:** +**Workflow Steps**: 1. **Start Node** - User provides story title and outline 2. **LLM Node** - Generate detailed chapter breakdown diff --git a/en/use-dify/nodes/knowledge-retrieval.mdx b/en/use-dify/nodes/knowledge-retrieval.mdx index 2ed99f711..543790e63 100644 --- a/en/use-dify/nodes/knowledge-retrieval.mdx +++ b/en/use-dify/nodes/knowledge-retrieval.mdx @@ -1,6 +1,6 @@ --- title: Knowledge Retrieval -description: "Retrieve relevant content from knowledge bases and use it as context for downstream nodes" +description: Retrieve relevant content from knowledge bases and use it as context for downstream nodes --- Use the Knowledge Retrieval node to integrate existing knowledge bases into your workflows. The node searches specific knowledge for information relevant to queries and outputs results as contextual content for use in downstream nodes (e.g., LLMs). diff --git a/en/use-dify/nodes/list-operator.mdx b/en/use-dify/nodes/list-operator.mdx index 8ecce66e7..7277067b7 100644 --- a/en/use-dify/nodes/list-operator.mdx +++ b/en/use-dify/nodes/list-operator.mdx @@ -1,6 +1,6 @@ --- -title: "List Operator" -description: "Filter, sort, and select elements from arrays" +title: List Operator +description: Filter, sort, and select elements from arrays --- The List Operator node processes arrays by filtering, sorting, and selecting specific elements. Use it when you need to work with mixed file uploads, large datasets, or any array data that requires separation or organization before downstream processing. @@ -79,7 +79,7 @@ Handle workflows where users upload both documents and images: ![Mixed File Processing Workflow](https://assets-docs.dify.ai/dify-enterprise-mintlify/en/guides/workflow/node/610358293217e54b55b7e1d4d16bf83c.png) -**Implementation Steps:** +**Implementation Steps**: 1. **Configure Mixed Uploads** - Enable file upload features to accept multiple file types 2. **Split by Type** - Use separate List Operator nodes with different filters: diff --git a/en/use-dify/nodes/llm.mdx b/en/use-dify/nodes/llm.mdx index 3e496d792..85bd055c9 100644 --- a/en/use-dify/nodes/llm.mdx +++ b/en/use-dify/nodes/llm.mdx @@ -1,6 +1,6 @@ --- -title: "LLM" -description: "Invoke language models for text generation and analysis" +title: LLM +description: Invoke language models for text generation and analysis --- The LLM node invokes language models to process text, images, and documents. It sends prompts to your configured models and captures their responses, supporting structured outputs, context management, and multimodal inputs. diff --git a/en/use-dify/nodes/loop.mdx b/en/use-dify/nodes/loop.mdx index 0d41059c5..4f8b7e972 100644 --- a/en/use-dify/nodes/loop.mdx +++ b/en/use-dify/nodes/loop.mdx @@ -1,6 +1,6 @@ --- -title: "Loop" -description: "Execute repetitive workflows with progressive refinement" +title: Loop +description: Execute repetitive workflows with progressive refinement --- The Loop node executes repetitive workflows where each cycle builds on the results of the previous one. Unlike iteration, which processes array elements independently, loops create progressive workflows that evolve with each repetition. @@ -59,7 +59,7 @@ Generate random numbers until finding one less than 50: ![Basic Loop Workflow for Random Number Generation](https://assets-docs.dify.ai/2025/04/282013c48b46d3cc4ebf99323da10a31.png) -**Workflow Steps:** +**Workflow Steps**: 1. **Code node** generates random integers between 1-100 2. **If-Else node** checks if number is less than 50 3. **Template node** returns "done" for numbers < 50 to trigger loop termination @@ -75,11 +75,11 @@ Create a poem through iterative refinement, with each version building upon the

``` -**响应式设计:** +**响应式设计**: ```html