infra-dify-ops
配套 CLI
difyctl(difyresctl旧包与 shim 已下线)。
用途
用于在 组织内部集群 场景中做 Dify 平台的使用层资源运维与 workflow DSL 创作,而不是处理部署层。它作为 AI 平台基础设施治理的一部分纳入 infra-*,但不接管 Dify Compose、数据库或容器生命周期。
这个 Skill 主要覆盖:
- Dify app / workflow / chatflow / agent 等资源的本地归档
- DSL 创作:从业务需求生成 import-ready DSL(
workflow-create+ spec v2)、本地校验(dsl validate) - 双轨 DSL 导入:优先 Console API(
dsl import --via auto),5xx/网络错误自动降级 Playwright;不再默认依赖 RPA - DSL 导出:经 Console API 拉取现网 app 的 DSL(
dsl export) - 导出的 DSL 文件校验、摘要、快照与 current 版本维护
- 基于 app API key 的使用层检查
- 模型 provider 配置(
openai_api_compatible加模型,联动 newapictl 消费) - Dify 1.13.x workflow DSL / graph 兼容性排障与生产验收
- Studio 导入后 graph 损坏、未发布 app、输出结构漂移等使用层救援
- 资源清单
resources.yml的登记与批量筛选计划 - 把后续新 workflow 及时沉淀到统一资源目录
DSL 创作与导入(0.2.0 新增)
三步创作流程
difyctl workflow-create intake ... --spec-out ./specs/x.yml:从业务需求生成结构化 spec(默认 v2)difyctl workflow-create scaffold --spec ./specs/x.yml --output ./drafts/x.dify.yml:产出 import-ready DSL(含app/kind: app/version: "0.6.0"/workflow.features/完整 graph node wrapper + position + edges)difyctl dsl validate ./drafts/x.dify.yml:本地校验(version/mode、图端点可达性、环检测、edge 引用、LLM 必填字段、变量引用语法、if-else handle)
双轨导入决策
difyctl dsl import --dsl ./drafts/x.dify.yml --via auto(推荐):- Console API 成功 → 返回
app_id+app_url - Console API 返回 5xx 或网络错误 → 自动降级 Playwright 浏览器导入
- Console API 返回 4xx(配置/鉴权错误)→ 直接报错,不降级
- Console API 成功 → 返回
--via api:只走 Console API,失败即报错(CI / 无 GUI 环境)--via browser:只走 Playwright(现网 API 不可用时的兜底)
导入前默认自动跑一次 dsl validate;用 --skip-validate 跳过。
详细 DSL 结构、节点 schema、布局与踩坑见:
references/REFERENCE-DSL-AUTHORING.mdreferences/REFERENCE-DSL-GOTCHAS.md
输入
- 当前资源目标,例如
app、workflow、chatflow - 本地 DSL 导出文件路径
- Dify app base URL 与 app API key(如需远端检查)
- 资源 ID、标题、标签、app_id、app_name
- 当前项目的资源工作区目录
输出
- 规范化的资源目录
resource.json元数据dsl/current.*与dsl/snapshots/*快照- 远端 app 信息或参数检查结果
- 对后续沉淀方式的明确建议
工作流
- 先判断这是“远端 app 检查”还是“本地资源沉淀”。
- 若需要远端检查:
- 优先使用
difyctl app info - 必要时再用
difyctl app parameters - 如需补充环境描述,可再用
difyctl app meta - 若任务涉及命名、描述、发布对象或 Studio 卡片展示,先确认 live app 事实,再讨论本地 DSL 或 registry
- 优先使用
- 若需要沉淀资源:
- 先用
difyctl resource init <resource_id> --mode ... - 再把导出的 DSL 用
difyctl resource capture ...归档 - 默认维护
dsl/current.*与时间戳快照
- 先用
- 若要维护项目级资源清单:
- 先用
difyctl registry init - 再用
difyctl registry upsert ... - 批量筛选时用
difyctl batch plan ...
- 先用
- 若需要从零生成一个业务需求对应的 workflow:
- 先用
difyctl workflow-create intake ...或draft --from-demand ... - 再用
difyctl workflow-create validate-spec ... - 然后用
difyctl workflow-create scaffold ...产出 DSL 草稿
- 先用
- 若需要把本地资源同步回 Dify Studio:
- 先用
difyctl studio create-plan ... - 或对已有资源用
difyctl studio export-plan ... - 或用
difyctl studio duplicate-plan ...
- 先用
- 若准备切到真实浏览器自动化执行:
- 先用
difyctl studio browser-doctor - 再用
difyctl studio import-dsl-run --dsl ... - 当前阶段要预期登录页和按钮选择器仍需现场微调
- 先用
- 若只想快速理解一个 DSL:
- 先用
difyctl dsl summarize <path>
- 先用
- 每次新增或明显改造 workflow 后,都要判断是否应该:
- 继续补当前资源目录
- 新增一个独立资源目录
- 回填到项目级知识卡片或 SOP
- 若导入后的 workflow 在 Dify 1.13.x 中无法运行或 Studio 无法正常打开:
- 先把问题分层为
graph schema、publish state、runtime invoke三类 - 优先用黑盒 app API 调用和只读数据库检查定位,不要一上来就改库
- 确认是旧 DSL 兼容性问题后,再决定是“最小字段补丁”还是“整图原生重建”
- 若需要做生产救援:
- 先确认 draft graph、published workflow、apps.workflow_id、api_tokens 四个对象是否齐全
- 再确认
start、llm、end三类节点是否符合当前 Dify 原生 schema - 最后用真实样例做健康路径与异常路径双验证,结果通过后再宣布可用
- 若任务同时涉及 Git 台账、本地 DSL、Studio 页面和 live app:
- 先把四层对象分开看,不要假设它们天然一致
- 优先核对 live app,再核对
resources.yml/resource.json/dsl/current.* - 若只是 Studio 资产漂移,先修正 Studio,再把本地台账回写到一致状态
- 若需要查询/新增 Dify 模型提供商下的模型(模型台账):
- 先
difyctl provider login --username-env ... --password-env ... --headed取 Console 会话,用返回的full_cookie_header(含csrf_token,纯session_cookie会 401)作为--console-key - 用
difyctl provider models --provider <full-path>查某提供商已配置模型(台账视图),随时看清现状 - 用
difyctl provider add-model --provider <full-path> --model <name> --endpoint-url <base>/v1 --api-key <key>给 openai_api_compatible 类可自定义模型的提供商加单个模型;先--dry-run预览 payload(无需 Console 会话) - newapi 消费 key 无法经 Admin API 读取(服务端全程脱敏、忽略客户端传入 key),必须从 newapi Web UI 复制或由用户提供;获取后
newapi/v1/chat/completions冒烟测试确认模型可用再注册
- 若需要改某 workflow 的 LLM 节点模型:
GET /console/api/apps定位 app_id →GET .../workflows/draft取 graph- 改
data.type=="llm"节点的model.provider与model.name→POST .../workflows/draft保存 →POST .../workflows/publish发布 - 用
POST .../workflows/draft/run(SSE 流)跑一次,确认node_finished/workflow_finished均status=succeeded且error=None
一致性校验与命名漂移
四层事实源
处理 Dify 使用层问题时,至少区分以下四层:
- live app:
/v1/info、/v1/parameters、/v1/meta实际返回的对象 - Studio asset:
/apps卡片、应用标题、描述、发布态和可见名称 - local registry:
resources.yml、resource.json中登记的resource_id、title、app_id、app_name - local DSL:
dsl/current.*、dsl/snapshots/*、drafts/*中沉淀的 graph 与元数据
不要因为其中一层已经更新,就推断其他三层也已经同步。
推荐校验顺序
- 先确认当前问题属于 live app、Studio asset、local registry、local DSL 中的哪一层,或是否多层叠加。
- 只要任务涉及 app 名称、描述、默认 workflow 绑定或“页面显示不对”,优先执行
difyctl app info。 - 若 组织内部集群 本地网络、代理或浏览器自动化环境不稳定,优先以内网可达机器上的 live app 检查结果为准。
- 确认 live app 事实后,再决定是:
- 更新本地 registry / DSL
- 更新 Studio app 信息
- 同时做两边对齐
- 完成修改后,至少做一次双验证:
- live app 验证
- 本地资源验证
命名治理规则
命名格式的权威真源在
fleet-platform/resources/dify/README.md「资源命名规范」章节(resource_id 正则^[a-z][a-z0-9]*(-[a-z0-9]+)*$、文件布局、扩展名、ledger v2 schema)。本节只讲漂移治理,不重复定义命名格式;difyctl亦在代码/--help内置同一正则并硬校验非法 id。
resource_id是稳定标识,优先保持稳定,不要因为展示名称变化就频繁改动。title和app_name可以随业务语义演进,但改名后要主动检查 Studio 与 live app 是否同步。drafts/*与dsl/snapshots/*允许保留历史名称,但不能拿历史快照代表当前现网事实。- 浏览器自动化如果按名称查找 app,默认要警惕 registry 名称与现网名称已经漂移。
处理顺序建议
遇到“代码里已经叫新名字,但 Dify 页面还是旧名字”这类问题时,默认按下面顺序处理:
- 用 app API 先确认现网
name/description/mode - 再核对
resources.yml、resource.json、dsl/current.* - 若只是 Studio / live app 漂移,先修正 Studio
- 再把本地 registry 和 README 等元数据回写到最终一致状态
- 最后保留一次可追溯验证结果,避免下次会话重新猜
Dify 1.13.x 修复策略
故障分层
graph schema:Studio 打不开、运行前即报validation errors,通常是旧 DSL 与当前start/llm/end节点 schema 不兼容。publish state:app 存在但不可调用,常见特征是apps.workflow_id为空、未指向 published workflow,或只有 draft 没有 published 版本。runtime invoke:API 可进业务层但 blocking 运行异常、超时、输出漂移,通常需要看 workflow runs 和 API logs。
推荐排查顺序
- 先做黑盒调用,确认错误是在鉴权、参数校验、graph 校验还是节点执行阶段。
- 再检查 app 发布态是否完整:
apps.workflow_id应指向 published workflow,而不是 draft- 同一个 app 通常至少有一条
draftworkflow 和一条带时间戳版本号的 published workflow - 若需要使用 service API,还要确认存在
api_tokens.type = app
- 再检查 graph 结构是否是当前 Dify 原生格式:
start.variables应是variable/label/type: text-input这类VariableEntityllm节点至少要有model、context、visionend.outputs应是variable+value_selector+value_type
- 只有在 Studio 已不可用、且黑盒与只读检查都证明是元数据损坏时,才进入 break-glass 数据层修复。
Break-Glass 原则
- 这类修复属于使用层资源救援,不是常规路径,必须有明确授权。
- 优先做“可回滚的最小改动”,例如先补齐缺失字段,再验证是否足够。
- 若旧 graph 与当前 Dify 版本差异过大,不要持续缝补,直接重建成当前原生 graph。
- 不在总结里扩散完整 API token、数据库密码等凭证;验证时只汇报是否成功与关键输出形状。
生产可用判定
published workflow已存在,且apps.workflow_id已切到该 published 版本。- app API 黑盒调用成功,不再停留在 schema 校验或参数缺失报错。
- 至少完成两类验证:
- 健康样例:应返回稳定的
title、markdown_body、plain_text_body - 异常样例:正文必须贴合输入事实,不能复用历史样例或编造新数字
- 健康样例:应返回稳定的
- 最新
workflow_runs记录应能落库并收敛到succeeded,必要时辅以 API logs 核对。
Dify 1.13.x Graph 约定
优先选择
- 对监控日报、通知封装、结构化摘要这类场景,优先用
start -> 单个 llm(structured_output) -> end。 - 不要默认串多个自由文本 LLM 节点再靠 end 兜底拆字段,这样更容易产生幻觉和字段漂移。
原生字段约定
start.variables使用当前VariableEntity结构,例如variable、label、type: text-input。llm节点在 Dify 1.13.x 下应显式带上:modelcontextvisionretry_config(difyctlscaffold 默认注入 3 次重试)error_strategy(difyctlscaffold 默认注入default_valuefallback)- 需要结构化结果时加
structured_output_enabled与structured_output.schema
- 新建 workflow 的默认 LLM 模型使用
langgenius/deepseek/deepseek/deepseek-v4-flash,mode: chat,temperature: 0.1。 end.outputs直接映射到上游structured_output字段,例如:["llm_node", "structured_output", "title"]["llm_node", "structured_output", "markdown_body"]["llm_node", "structured_output", "plain_text_body"]
提示词约束
- 明确写出“只能依据 snapshot_json 中已有事实,不允许猜测、补造、外推”。
- 明确要求空区块输出“未观测到异常”或“无数据”。
- 对正文结构写死章节名,而不是依赖模型自行发挥。
- 若需要多语言,只让
language影响表述语言,不影响事实本身。
LLM 节点容错默认行为(0.4.0 新增)
difyctl workflow-create scaffold 默认为每个 LLM 节点注入两层容错:
retry_config:{retry_enabled: true, max_retries: 3, retry_interval: 1000}- 触发条件:模型返回 429 / 5xx / 超时
- 与 newapi 侧的 proxy 重试 + 渠道 fallback 互补:newapi 处理上游不可用,Dify 侧处理模型响应本身的异常
- 传
retry_config=None可禁用(仅限调试)
error_strategy:{type: default_value, default_value: ""}- 触发条件:重试全部耗尽后
- 效果:输出空值让下游节点继续执行,而非整个 workflow 崩溃
- 对于
structured_output节点,default_value应是一个含全部必填字段的空对象,例如{"title":"","markdown_body":"","plain_text_body":""} - 传
error_strategy=None可禁用(不推荐用于生产)
difyctl dsl validate 会对缺失这两项的 LLM 节点发出 warning,提示手动补齐。
手写或从 Dify 导出的 DSL 如果缺少 retry_config / error_strategy,建议用 difyctl dsl lint 检查后再导入。
App 服务密钥 / 调用 / 运维(0.7.0 新增)
创建工作流后「取 key → 调用」现已在 CLI 内闭环,覆盖各类应用:
# 1) 取 app 的 service API key(Console API):完整 token 仅打印一次 + 存 config.yaml assets.difyctl.app_keys
difyctl app keys create --app-id <app_id>
difyctl app keys list --app-id <app_id> # 只显示掩码前缀
difyctl app keys delete --app-id <app_id> --key-id <kid>
# 2) 调用应用(mode 自动从 ledger/--mode 解析,覆盖全类型)
difyctl app run --app-id <app_id> --inputs '{"text":"hi"}' # workflow -> /v1/workflows/run (blocking)
difyctl app run --app-id <app_id> --query "你好" # chat/advanced-chat/chatflow -> /v1/chat-messages (blocking)
difyctl app run --app-id <app_id> --mode agent-chat --query "你好" # agent-chat -> /v1/chat-messages (streaming,Dify 拒绝 blocking,自动改流式聚合)
difyctl app run --app-id <app_id> --mode completion --query "写首诗" # completion -> /v1/completion-messages
# 3) 导入即可用(opt-in 闭环)
difyctl dsl import --dsl draft.dify.yml --via api --resource-id my-wf --create-key --smoke --smoke-inputs '{"text":"hi"}'
# 导入默认带重名去重守卫:现网已存在同名 app 时 block(列出 existing app_id);确需重复用 --allow-duplicate。
# 排查现网重复:difyctl registry audit 的 duplicate_names 段列出同名 app 分组。
# 3b) 渐进式:原地更新已有 app(不产生副本)
difyctl dsl export --app-id <app_id> --output cur.dify.yml # 拉当前线上 DSL
difyctl dsl diff --app-id <app_id> --dsl new.dify.yml # 推送前预览差异
difyctl dsl import --dsl new.dify.yml --via api --app-id <app_id> # 原地更新(同 app_id,无副本)
difyctl dsl import --dsl new.dify.yml --via api --update-if-exists # 或按同名自动定位单个 app 更新
difyctl app publish --app-id <app_id> # workflow/advanced-chat 更新后需 publish 才能 /v1 run
difyctl dsl lint --dsl new.dify.yml # 推送前静态检查:硬编码密钥/悬空变量/空模型
difyctl dsl retarget --dsl new.dify.yml --provider <p> --model <m> # 批量改绑模型
difyctl dsl apply --dir ./dify-apps --dry-run # 声明式:目录 DSL 幂等同步(新建/原地更新/歧义跳过)
difyctl dsl apply --dir ./dify-apps --publish # 执行同步并发布
# 3c) 长期治理:台账回填 + 重复清理
difyctl registry sync --dry-run # 预览把未登记的线上 app 回填进台账(分页全量)
difyctl registry sync # 执行回填
difyctl registry prune-duplicates # dry-run:列出同名冗余 app 的删除计划(保留最新)
difyctl registry prune-duplicates --apply # 执行删除(破坏性,保留 --keep newest/oldest 之一)
# 3d) 知识库(RAG 数据集)+ 全量备份
difyctl dataset list # 列出所有知识库(分页全量)
difyctl dataset list --name 营销 # 按名称子串过滤
difyctl dataset documents --dataset-id <id> # 某知识库的文档列表
difyctl dataset create --name "KB" --indexing-technique high_quality # 建库(high_quality 需 embedding 模型)
difyctl dataset add-doc --dataset-id <id> --name doc --text "..." # 加文本文档(/v1 + 自动取 dataset key)
difyctl dataset add-doc --dataset-id <id> --name doc --file ./a.md # 或从文件读入
difyctl dataset delete --dataset-id <id> --yes # 删库(破坏性,需 --yes)
difyctl app annotations --app-id <id> # 列出 app 标注(问答缓存)
difyctl dsl export --all --output-dir ./dify-backup # 全量备份每个 app 的 DSL(灾备)
# 输出约定:stdout 恒为纯 JSON(可直接管道消费);人类可读的错误/告警走 stderr。全局 --quiet 静默 stderr、--verbose 追踪、-q 简写。
# 4) 运维车队
difyctl app rename --app-id <app_id> --name "New Name" # Console PUT + 同步 ledger
difyctl app delete --app-id <app_id> # Console 删除 + ledger status→deprecated
difyctl registry audit # live apps vs ledger:僵尸/未登记/漂移
# 5) /v1 运行时全覆盖(0.8.0,均用 app-* service token)
difyctl app run --app-id <id> --mode agent-chat --query "你好" --stream # 强制流式聚合
difyctl app run-status --app-id <id> --run-id <workflow_run_id> # 运行明细/状态
difyctl app stop --app-id <id> --task-id <task_id> --user <u> # 中止流式任务
difyctl app upload --app-id <id> --file ./a.png --user <u> # 文件/图片输入
difyctl app logs --app-id <id> --limit 20 # 运行日志/审计
difyctl app conversations --app-id <id> --user <u> # 会话列表
difyctl app messages --app-id <id> --conversation-id <cid> --user <u> # 会话消息
difyctl app conversation-rename --app-id <id> --conversation-id <cid> --name "X" --user <u>
difyctl app conversation-delete --app-id <id> --conversation-id <cid> --user <u>
difyctl app feedback --app-id <id> --message-id <mid> --rating like --user <u> # 点赞/踩
difyctl app suggested --app-id <id> --message-id <mid> --user <u> # 追问建议
difyctl app audio-to-text --app-id <id> --file ./a.wav --user <u> # 语音转文字
difyctl app text-to-audio --app-id <id> --text "你好" --output ./o.mp3 --user <u> # 文字转语音
安全红线:完整 app-* token 只存本地 ~/.harness-ai-kit/config.yaml(app_keys 映射,禁入 Git);app keys create 完整 token 仅一次性打印,其余输出与 ledger 一律掩码(prefix+****)。key 解析优先级:--app-key > config.yaml app_keys[app_id] > app_api_key。
配置上下文
本技能依赖以下配置,AI 在运行时按如下优先级解析:
- 用户对话中明确提供的值(最高优先级)
~/.harness-ai-kit/config.yaml中assets.difyctl(配套 CLI 配置段)或global段config.defaults.yaml中的默认值
如用户未提供且无默认值的 required 字段,必须主动询问用户。 禁止从 AGENTS.md 或脚本中读取硬编码配置值。
推荐输出格式
执行完毕后按以下结构输出:
状态:✅ / ⚠️ / ❌
| 资源ID | 类型(app/workflow/chatflow) | 动作(init/capture/reconcile/validate) | DSL 快照 / live 校验结果 |
|---|
(漂移排查场景补充四层事实源对照:live app / Studio asset / local registry / local DSL 的一致性结论;生产救援场景注明 published workflow、apps.workflow_id、健康/异常双验证是否通过。)
约束
- 这个 Skill 不负责 Docker、Compose、Nginx、容器启停与 Dify 升级。
- 优先把 Dify 资源沉淀为项目内可跟踪文件,而不是只留在 Dify UI 中。
- 公开稳定的使用层入口先以 app API key 与导出 DSL 为主,不默认依赖不稳定的控制台私有接口。
- 数据库直修只作为 break-glass 救援手段,不作为常规导入、发布或编辑路径。
- 当前已对齐中文 Dify Studio 登录页与
/apps->创建空白应用/导入 DSL 文件/更多 -> 导出 DSL / 复制 / 编辑信息流程。 - 新资源命名应稳定、可检索、可长期复用,不要使用一次性临时名称。
- 不要把本地
drafts/*、历史 snapshot 名称或.env中的展示名,直接当成现网 app 事实;涉及展示层问题时必须再做 live app 校验。
命令约定
初始化本地配置:
difyctl config init --base-url https://dify.example.com --workspace-dir ./fleet-platform/resources/dify
推荐最小环境变量:
DIFY_BASE_URL=https://dify.example.com
DIFY_STUDIO_USERNAME=you@example.com
DIFY_STUDIO_PASSWORD=your-password
远端检查解析顺序:
- 显式命令参数
--base-url/--app-api-key difyctl已保存配置- 环境变量
DIFY_BASE_URL workspace_dir附近.env中的DIFY_BASE_URL、DIFY_API_KEY或DIFY_APP_API_KEY
若 DIFY_BASE_URL 以 /v1 结尾,CLI 会自动收口到站点根地址。
检查 readiness:
difyctl doctor
远端 app 检查:
difyctl app info
difyctl app parameters
difyctl app meta
命名或描述漂移校验:
difyctl app info
difyctl reconcile diff-only sales-intake
difyctl reconcile show sales-intake
difyctl registry show sales-intake
difyctl resource show sales-intake
本地资源沉淀:
difyctl resource init my-workflow --mode workflow --title "My Workflow" --dsl ./exports/my-workflow.yml
difyctl resource capture my-workflow --dsl ./exports/my-workflow-v2.yml --label exported-2026-05-11
difyctl resource list
difyctl resource show my-workflow
DSL 快速摘要:
difyctl dsl summarize ./exports/my-workflow.yml
资源清单与批量计划:
difyctl registry init
difyctl registry upsert sales-intake --mode workflow --title "Sales Intake" --tag sales --dsl-path resources/sales-intake/dsl/current.yml
difyctl batch plan --mode workflow --tag sales
从零生成 workflow 草稿:
difyctl workflow-create intake --name "Sales Intake" --mode workflow --goal "Triage inbound leads" --input lead_text --output final_reply --step "Classify intent" --step "Generate reply" --spec-out ./specs/sales-intake.yml
difyctl workflow-create validate-spec --spec ./specs/sales-intake.yml
difyctl workflow-create scaffold --spec ./specs/sales-intake.yml --output ./drafts/sales-intake.dify.yml
Studio 操作计划:
difyctl studio create-plan --name "Sales Intake" --mode workflow --dsl ./drafts/sales-intake.dify.yml
difyctl studio export-plan sales-intake
difyctl studio duplicate-plan sales-intake
浏览器自动化入口:
difyctl studio browser-doctor
difyctl studio import-dsl-run --dsl ./drafts/sales-intake.dify.yml --headed
生产验收建议:
curl -X POST "$DIFY_BASE_URL/v1/workflows/run" \
-H "Authorization: Bearer $DIFY_APP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"inputs": {
"snapshot_json": "{\"summary\":{\"overall_status\":\"healthy\"},\"hosts\":[],\"containers\":[],\"entrypoints\":[],\"alerts\":[],\"links\":[]}",
"period_label": "2026-05-12",
"audience": "ops-oncall",
"language": "zh-CN"
},
"response_mode": "blocking",
"user": "smoke-test"
}'
示例
示例 1:刚从 Dify 导出一个 workflow DSL,想立即沉淀
difyctl resource init sales-intake --mode workflow --title "Sales Intake" --dsl ./exports/sales-intake.yml- 确认
resource.json、dsl/current.yml与首个 snapshot 已生成 - 后续版本继续用
capture累积快照
示例 2:想先看当前 app 的运行参数
- 配置
base_url与app_api_key - 运行
difyctl app parameters - 把结果作为后续资源整理和比对依据
示例 3:从业务需求直接起一个 workflow 草稿
- 用
workflow-create intake生成结构化 spec - 用
validate-spec校验 spec - 用
scaffold生成 starter DSL - 再进入
resource init / capture与后续 Studio 导入流程
示例 4:对已登记资源生成 Dify Studio 操作计划
- 先确认该资源已存在于
resources.yml - 运行
difyctl studio export-plan sales-intake - 按计划把导出的 DSL 回填到本地资源目录
示例 5:导入后的 workflow 在 Dify 1.13.x 中无法运行
- 先用 app API 做黑盒调用,记录它卡在鉴权、参数校验、graph 校验还是节点执行
- 对照一个正常 app,检查 draft / published workflow、
apps.workflow_id和 app token 是否齐全 - 若报
VariableEntity、EndNodeData或LLMNodeData校验错误,优先按当前原生 schema 重建 graph - 修复后至少跑一条健康样例和一条异常样例,再确认
workflow_runs已succeeded
示例 6:本地 registry 已改名,但 Studio 页面仍显示旧名称
- 先运行
difyctl app info,确认 live app 当前name和description - 再核对
resources.yml、resource.json和dsl/current.*是否已经切到新语义 - 若确认只是 Studio asset 漂移,再执行
studio edit-info-run - 修改完成后重新做一次 live app 校验,不要只看 Studio 页面标题
示例 7:新开会话时想先快速确认是否存在漂移
- 先运行
difyctl reconcile diff-only sales-intake - 若只有 live app 网络错误,优先切到内网可达环境复核
- 若存在
observations,再运行difyctl reconcile show sales-intake看完整上下文
给 LLM 应用加"联网搜索/工具"能力(Dify 1.x 插件版)
关键事实:Dify 的 LLM 节点本身没有内置"联网搜索"开关。联网搜索通过**工具(1.x 里是插件)**提供,两种接法:
- Agent 应用 / Agent 节点:把搜索工具挂到 Agent,由模型自主决定何时搜(依赖模型 function-calling 能力,不够确定)。
- Chatflow/Workflow 的 Tool 节点(推荐,确定性):显式放一个搜索 Tool 节点在 LLM 节点前,把检索结果通过
context拼进 LLM 上下文。不依赖模型 tool-calling,最稳。
可用 web-search 工具(本实例,GET /console/api/workspaces/current/tools/builtin 探测):
| 工具 | provider(1.x 全名) | tool_name | 授权/密钥 |
|---|---|---|---|
| Wikipedia | langgenius/wikipedia/wikipedia |
wikipedia_search(query+language 必填) |
已授权·免密钥·稳定 |
| DuckDuckGo | langgenius/duckduckgo/duckduckgo |
ddgo_search(query 必填,max_results 必填) |
已授权·免密钥·免费端点常被限流(202 Ratelimit) |
| Tavily | langgenius/tavily/tavily |
tavily search | 已授权·需 key·生产推荐 |
| Google/Bing/SearchApi/Brave/Jina | 对应插件 | — | 需各自 API key |
工具节点 DSL 关键 schema(1.x,workflow.graph.nodes[].data):
type: tool
provider_id: langgenius/wikipedia/wikipedia # 与 provider_name 一致,用插件全名
provider_name: langgenius/wikipedia/wikipedia
provider_type: builtin
tool_name: wikipedia_search
tool_configurations: {} # provider 级配置(proxy 等)
tool_parameters:
query: {type: mixed, value: "{{#<start_node_id>.query#}}"} # 变量引用
language: {type: constant, value: zh} # 常量
retry_config: {max_retries: 2, retry_enabled: true, retry_interval: 1000}
LLM 节点消费工具输出:context.enabled: true + context.variable_selector: [<tool_node_id>, "text"],prompt 里引用 {{#<tool_node_id>.text#}}。节点外壳统一 type: custom,真实类型在 data.type。
CLI 一键运维闭环(已真机验证 GREEN):
# 1) 作者一个 start -> tool(wikipedia_search) -> llm(ollama) -> end 的 workflow DSL(见上 schema)
# 2) 导入(带重名去重) + 建 key
difyctl dsl import --dsl websearch.dify.yml --via api --resource-id web-search-pilot-wiki --create-key
# 3) 发布(workflow 必须 publish 才能 /v1 run)
difyctl app publish --app-id <app_id>
# 4) 试点跑通:联网检索 -> LLM 汇总
difyctl app run --app-id <app_id> --mode workflow --inputs @q.json # q.json: {"query":"..."}
# 排错:run_status=failed 时看 error(如 DuckDuckGo 202 Ratelimit 即免费端点限流,非配置错误)
踩坑与选型:DuckDuckGo 免费端点在服务器 IP 上常返回 202 Ratelimit(节点结构正确、确实发起了真实搜索,但被限流);生产联网搜索优先 Tavily / SearchApi(配 key) 或给 DDG 配 proxy_server。零成本试点/演示用 Wikipedia 最稳(wikipedia_search,免密钥、无限流)。
Tool 节点参数放置(关键坑,已真机踩过 3 次)
- 运行时输入(
form: llm,如query)→ 放tool_parameters,必须包裹{type: mixed|variable|constant, value: ...}。 - 静态配置项(
form: form,如max_results/search_depth/include_answer)→ 放tool_configurations,用原始值、不要包裹(写成{max_results: 5, include_answer: "advanced"};写成{type:constant,value:...}会报value {...} not in options)。 select型但默认值是布尔的配置项(Tavily 的include_answer/include_raw_content)必须显式赋一个合法字符串选项(如include_answer: "advanced"、include_raw_content: "false"),否则默认False触发value False not in options。
本实例 web-search 现状(探测结论,随时可复查)
- Tavily 已授权且已配 key(
tavily_api_key: <set>),5 个工具tavily_search/tavily_extract/tavily_crawl/tavily_map/tavily_research→ search+read 开箱即用,已真机 GREEN(tavily_search→LLM 返回带来源 URL 的实时答案)。生产联网搜索首选它。 - SearXNG 插件未安装(无 provider):自托管要先装 marketplace 插件 + 部署 SearXNG 容器 + 配 base_url + 上游引擎国际出口代理。
- Firecrawl 插件在册但未配(
base_url/api_key空):需云端 key 或自托管base_url;旧 app 里provider_id: firecrawl短名是遗留写法,新建用插件全名langgenius/firecrawl/firecrawl。
自托管 SearXNG + Firecrawl 接入(已真机 GREEN)
CLI/API 授权 tool provider(difyctl 暂无此命令,走 Console API):
POST /console/api/workspaces/current/tool-provider/builtin/<provider>/add
body: {"credentials": {...}, "type": "api-key", "name": "self-hosted"}
- 坑:该接口常返回 400
Cannot release a lock that's no longer owned,但凭据其实已保存——以GET tools/builtin的is_team_authorization=True为准,别盲目重试(会建重复凭据)。
SearXNG(langgenius/searxng/searxng,cred 字段 searxng_base_url,免密钥):
- 搜索工具
searxng_search:query(llm) +search_type(form,必填,options: general/images/videos/news/map/music/it/science/files/social_media) +time_range(form,可选)。 - 关键坑:SearXNG 节点结果只在
json(array[object],含 title/url/content)里,text为空!prompt 必须引用{{#节点.json#}}(非text),context 关掉或指 json。真机 GREEN:MCP 查询→9501 tokens、带来源 URL。 - 直连自检:
GET http://<searxng>/search?q=...&format=json返回results[]+unresponsive_engines(本实例 google cse+duckduckgo 正常、brave 挂)。
Firecrawl(langgenius/firecrawl/firecrawl,creds base_url+firecrawl_api_key,自托管 key 任意非空):
scrape:url(llm) +formats/onlyMainContent/timeout(form)。输出走text(与 SearXNG 不同)。真机 GREEN:抓 runoob 页→摘要。
联网搜索 workflow spec 模板库(一键脚手架)
技能自带 3 个可直接脚手架的 spec(references/specs/),difyctl workflow-create scaffold --spec <spec> --output x.dify.yml 一键生成 DSL:
| spec | 拓扑 | 特点 | 适用 |
|---|---|---|---|
web-search-searxng.spec.yml |
start→tool(SearXNG)→llm→end |
自托管、快、无需 sandbox(json 含 content 摘要) | 多数联网问答 |
web-search-tavily.spec.yml |
start→tool(Tavily)→llm→end |
托管兜底、结果已 LLM 清洗、带来源 | 快速接入/无自托管时 |
deep-research-websearch.spec.yml |
start→tool(SearXNG)→template(取URL)→tool(Firecrawl)→llm→end |
两段式、读全文、最扎实(50-73s) | 深研/需全文落地引用 |
均真机验证 scaffold→import→run 全绿。导入+运行通用流程:
difyctl workflow-create scaffold --spec <skill>/references/specs/<spec>.spec.yml --output x.dify.yml
difyctl dsl import --dsl x.dify.yml --via api --resource-id <rid> --create-key
difyctl app publish --app-id <id>
difyctl app run --app-id <id> --mode workflow --inputs '{"query":"..."}'
注意各自的输出变量与参数坑(SearXNG 在 json 非 text;Tavily include_answer 等 select-with-bool-default 必须显式赋合法字符串;工具节点静态配置进 tool_configurations 原始值、运行时输入进 tool_parameters 包裹)。
两段式深研(SearXNG搜索→取URL→Firecrawl读→LLM)—— 已真机 GREEN
- 一键脚手架(推荐):技能自带 spec
references/specs/deep-research-websearch.spec.yml,直接生成两段式 DSL:difyctl workflow-create scaffold --spec <skill>/references/specs/deep-research-websearch.spec.yml --output dr.dify.yml difyctl dsl validate dr.dify.yml difyctl dsl import --dsl dr.dify.yml --via api --resource-id deep-research-websearch --create-key difyctl app publish --app-id <id> difyctl app run --app-id <id> --mode workflow --inputs '{"query":"..."}'workflow-create scaffold(difyctl 0.13.0+)支持toolstep 与跨节点引用重映射:spec 里用稳定 step-id(start/searxng/pick_url/…)互引{{#id.field#}}与value_selector,scaffold 自动改写为生成的 node-id。 - 拓扑:
start → searxng_search → 取首条url(template-transform) → firecrawl scrape → llm → end。取 URL 也可用 Code 节点(def main(arg1)->{"url":...})。 - 依赖 dify-sandbox:Code 与 Template-Transform 节点都在
dify-sandbox里执行;曾遇-500 fork/exec /usr/local/bin/python3(sandbox 缺 python3,拖垮所有含 Code/Template 的 workflow),修复 sandbox 后恢复。 - 超时:全链 50-65s;difyctl 默认超时已调到 120s(0.12.2+),或
app run --timeout单次覆盖。真机 GREEN:56-73s、带来源 URL。 - 规避:单段
SearXNG→LLM(json 已含 content 摘要)无需 sandbox、更快,适合多数问答;两段式用于需要读全文的深研。
参考文档
- REFERENCE-DSL-AUTHORING.md
- REFERENCE-DSL-GOTCHAS.md