📎 配套脚本附件正文未内嵌 · 共 22 个 · 点开可在线查看
📚 另需公共库(正文未重复内嵌,点名字直接看):_agent_cfg.py、_bootstrap.py、_config.py、_plugin_util.py
📂 查看全部 761 个脚本附件 →
⚠️ 脚本内含明文凭据(与本页正文一致),请勿在公网环境直接运行。
Dify · 专题十五

新 Agent 从零创建完整教程
步骤、注意实现、常见报错

工作室里没有的开关去模型供应商或 dify_api.py;改快照后不要点发布。步骤未改,代码块已换成完整程序。

专题十五:Dify 1.16 新 Agent 从零创建完整教程

本文按 192.168.110.207、Dify 1.16.1 实战写成。目标是:从零创建一个能答知识库的新 Agent,并且知道哪些事在工作室里点不到、必须走工作区设置 / 控制台 API / 改库。

口诀就一句:工作室里没有的开关,去工作区「设置 → 模型供应商」,或直接用 dify_api.py。改 Agent 快照后仍然不要点发布。

全文三部分:新 Agent 和旧应用差在哪、可照做的创建步骤(UI + API + SQL)、常见报错与处理。细节专题(模型接入、Rerank、Langfuse、空泡)文末有索引,本文不重复整篇。

运行环境:业务脚本均在 207 固定目录 /opt/workbuddy 执行,Python 为 /usr/bin/python3。 Windows 开发机仅保留 _probe_* 调试脚本;同步命令:python scripts_cursor/完整代码约定/00_部署_workbuddy到207.py。


目录

  1. 第一部分 原理与注意实现
  2. 第二部分 详细操作步骤
  3. 第三部分 常见报错与处理方式

第一部分 原理与注意实现

一、本文解决什么

做出一个新 Agent,验收必须同时满足:

  1. 应用类型是新 Agent(库表 apps.mode=agent,另有 agents 行)。
  2. 模型是 qwen3.6-27b,对话能出字。
  3. 绑了知识库,问制度能引用原文。
  4. 检索参数(Top-K / Rerank)在 知识库页和 Agent 快照两处一致。
  5. max_tokens 已显式写进快照(不要停在 null)。
  6. 需要观测时,Langfuse Tracing 已用 API 打开(工作室里没有这个菜单)。

二、新 Agent 和旧「Agent / 聊天助手」不是同一套配置

旧聊天助手 / 旧 Agent新 Agent(本文)
apps.modechat / agent-chatagent
运行配置app_model_configsagent_config_snapshots + agent_config_drafts
绑知识库model_config.dataset_configsconfig_snapshot.knowledge.sets[]
工作室编排 / 上下文 / 监测配置 / 知识 / 工具,没有监测
点「发布」通常能把参数写入 model_config经常把 max_tokens 写回 null(专题十一)
对话 API/v1/chat-messages 或 /apps/<id>/chat-messages/console/api/agent/<agent_id>/chat-messages

专题三里用 POST /apps/<id>/model-config 绑知识库,那是 旧应用。新 Agent 绑库要改快照 JSON,不要抄错表。

三、配置到底写在哪(三层,不要找错)

text
工作区「设置 → 模型供应商」
  模型凭据:Qwen / bge-m3 / reranker
  默认 LLM、默认 Embedding
  工作室里没有这些开关
        │
        ▼
知识库页(datasets.retrieval_model)
  命中测试用这套
  开 Rerank、Top-K 先改这里
        │
        ▼
新 Agent 快照 + 草稿(必须两张表一起改)
  对话真正用的模型参数、知识库、检索、提示词
  只改知识库页 → 命中测试变好,对话仍按旧检索跑
你想改的正确位置错误位置
添加 Qwen / bge-m3 / Rerank 凭据设置 → 模型供应商,或 API .../models/credentials新 Agent 工作室
默认模型模型供应商「设为默认」,或 tenant_default_models / default-model API只在某个应用里选一次就以为全局改了
命中测试排序PATCH /datasets/<id> 的 retrieval_model只改 Agent
对话检索 / Top-K / Rerank快照 knowledge.sets[].retrieval只改知识库页
Max Tokens快照 model.model_settings.max_tokens抽屉里改完再点发布
LangfusePOST .../trace-config + POST .../trace在工作室找「监测」

四、创建时必须遵守的实现注意

  1. 先通模型,再创建应用。 LLM 列表空、标「不兼容」、或知识库 embedding 不可用,创建向导会失败或随后选不到库。
  2. 创建可以用 UI 向导,落地参数用 API/SQL。 向导适合起一个空壳并拿到 app_id / agent_id。Max Tokens、Rerank、Tracing 不要指望点发布一次搞定。
  3. 改快照必须同时改草稿。 只改 agent_config_snapshots,配置页一刷新会用草稿盖回去。
  4. 改完不要点「构建 / 发布」。 发布可能把 max_tokens 写成 null,运行时又回到插件默认 8192,长知识问答会空泡或 thinking 耗尽。改完只强刷、新开对话。
  5. 知识库 JSON 两套字段名。 知识库用 reranking_provider_name / reranking_model_name;Agent 用 provider / model。混用会被 extra=forbid 拒绝。
  6. Prompt 体积。 制度类 Top-K=5 时 Prompt 经常 7k–14k。Qwen3.6 的 thinking 也算进 max_tokens。本环境制度问答建议 16384–32768,且 Prompt + max_tokens < 65536。
  7. 失败会话不要继续点。 空泡、3/3、停在 knowledge_base_search 时先查 messages.status。已是 error 就新开对话。
  8. Xinference 实例重启会丢。 容器 Up ≠ bge-m3 / reranker 在跑。知识库从列表消失时先 GET /v1/models。
  9. 插件补丁会丢。 chat.py 合并 system、llm.py schema 克隆、Langfuse Observation UUID,都在容器内。重建 plugin_daemon / worker 后要再打。
  10. 登录控制台 API:密码 base64 + CSRF。 用现成 dify_api.py,不要手写裸 POST。

五、本次环境对照(换机器请重查)

项值
Dify1.16.1,http://192.168.110.207
登录38205036@qq.com / Difyai123456
参考应用播城行政问答智能体
参考 app_id40e95fd3-e7f5-44f2-a03b-dc3e60413540
参考 agent_id01a012e7-bed3-7574-b0d1-a5cc79a0d3de
快照 / 草稿01a0135c-... / 01a012e7-bf60-...
知识库ee2495bf-c370-49d1-8268-10c51f650fa6
LLMqwen3.6-27b,context_size=64000
Embedding / RerankXinference bge-m3 / bge-reranker-v2-m3
建议 max_tokens32768(长制度 + thinking)
建议 Top-K3–5
对话 APIPOST /console/api/agent/<agent_id>/chat-messages

新建应用会得到 另一套 UUID。下面步骤里的 ID 只作范例。


第二部分 详细操作步骤

Python 约定(全库统一)

- 每个操作步骤对应 /opt/workbuddy/scripts_cursor/ 下一个完整可运行程序(含 main()、登录、常量);不要从正文里抄片段拼程序。

- 有配置改动时提供 修改前 / 修改后 两个文件(文件名带 _修改前 / _修改后),正文附 改动对比 表。

- 换环境只改 /opt/workbuddy/scripts_cursor/Dify_专题十五_新Agent从零创建完整教程/_config.py。在 207 上运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/脚本名.py

- 程序索引见下文「本专题 Python 程序一览」;插件源码级补丁见各步骤正文(补丁也做成完整 apply 脚本)。

顺序不要跳:前置检查 → 创建空壳 → 记下 ID → 提示词与知识库 → 检索/Rerank → max_tokens →(可选)Langfuse → 新开对话验收。

从 Windows 工作区操作 207,用 dify_api.py(Paramiko)。普通 ssh 容易卡密码。

步骤速查:做完什么、卡住看哪(不替代下文逐步操作)

步骤本步目标通过标准常见卡点 → 下文/专题
0创建前检查LLM/emb/rerank/datasets 列表正常不通过不要创建;回安装手册、专题一/二
1创建 Agent 空壳POST /agent 201400 用错 /apps;见步骤 1 正文
2记下四个 IDapp/agent/snap/draft 已写入 _config.pypsql 查不到 → 步骤 2 SQL
3~4提示词 + 知识库检索快照里已有 system_prompt专题三绑库;字段名见步骤 5
5绑库 + Rerank(可选)快照+草稿 retrieval 一致专题十三 两处 JSON
6max_tokens快照 model_settings.max_tokens 非 null专题十一/十二;勿点发布
7~8Langfuse(可选)Trace 可见Langfuse 连接专题
9新开对话验收streaming 无 error、能引用库专题四/十四;空泡→专题十二

步骤 0 · 创建前检查单(不通过就不要点「创建」)

python
from dify_api import Dify, pp
d = Dify("38205036@qq.com", "Difyai123456")
d.login()

pp("llm", d.get("/workspaces/current/models/model-types/llm"))
pp("emb", d.get("/workspaces/current/models/model-types/text-embedding"))
pp("rerank", d.get("/workspaces/current/models/model-types/rerank"))
pp("datasets", d.get("/datasets?page=1&limit=20"))
d.close()

同时在 207 上:

完整脚本:scripts_bash_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_01.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_01.sh

bash
#!/usr/bin/env bash
set -euo pipefail

curl -s -m 10 -H 'Authorization: Bearer 123456' \
  http://116.62.10.183:8000/v1/models

curl -s -m 15 -H 'Authorization: Bearer xf-K8H3LiS27H4188XdOyWJj74MWMOlIR9tZbiybDbxbSas7eAI' \
  http://127.0.0.1:9997/v1/models

必须看到:

缺哪项先做对应专题,不要带着红字去创建。

缺什么先看
LLM 空 / 不兼容专题一、十、五
无 bge-m3 / 知识库灰专题二、三
无 Rerank专题十三
System message must be at the beginning专题四(chat.py 补丁)
插件 SyntaxError专题八

步骤 1 · 创建应用空壳

1.1 控制台(适合第一次)

  1. 打开 http://192.168.110.207,登录。
  2. 工作室 / 应用列表 → 创建应用。
  3. 类型选 Agent(新 Agent,不要选「聊天助手」除非你明确要旧链路)。
  4. 名称例如:播城行政问答智能体。
  5. 模型选 qwen3.6-27b。选不到就停,回到步骤 0。
  6. 创建成功后进入工作室。左侧通常是 配置、知识、工具。没有「监测」是正常的。

1.2 控制台 API(自动化或 UI 类型不好选时)

完整程序:/opt/workbuddy/scripts_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_01.py

运行/引用:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_01.py

python
from dify_api import Dify, pp

d = Dify("38205036@qq.com", "Difyai123456")
d.login()

body = {
    "name": "播城行政问答智能体",
    "description": "企业内部制度问答",
    "mode": "agent",
    "icon_type": "emoji",
    "icon": "🤖",
    "icon_background": "#FFEAD5",
}
r = d.post("/apps", body)
pp("create app", r)
print("app_id", (r.get("json") or {}).get("id"))
d.close()

若 mode=agent 返回 400,把响应全文留下,改试控制台向导;创建后用步骤 2 的 SQL 确认 apps.mode 必须是 agent。不要用 chat / agent-chat 硬当新 Agent。

步骤 2 · 立刻把三个 ID 记下来

sql
SELECT
  ap.id AS app_id,
  ap.mode,
  ap.name,
  a.id AS agent_id,
  a.active_config_snapshot_id AS snapshot_id,
  d.id AS draft_id
FROM apps ap
LEFT JOIN agents a ON a.app_id = ap.id
LEFT JOIN agent_config_drafts d ON d.agent_id = a.id
WHERE ap.name ILIKE '%行政问答%'
ORDER BY ap.created_at DESC;

没有 agents 行、或 mode 不是 agent:你建的是旧应用,后面步骤会对不上。

也可用 API:

完整程序:/opt/workbuddy/scripts_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_02.py

运行/引用:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_02.py

python
r = d.get("/apps?page=1&limit=50")
for item in (r.get("json") or {}).get("data") or []:
    print(item.get("id"), item.get("mode"), item.get("name"))

后面所有 URL 里的 <app_id> / <agent_id> 换成你刚查到的值。

步骤 3 · 写系统提示词(指令)

工作室 配置 页的指令框建议:

text
你是企业内部知识助手。请根据提供的知识库内容回答问题。
要求:
1. 如果知识库中有明确答案,请基于知识库回答。
2. 如果知识库中没有答案,请明确说“知识库中未找到相关内容”。
3. 不要编造公司制度、流程或数据。
4. 回答要简洁,适合企业内部员工阅读。

保存方式:

核对:

sql
SELECT config_snapshot::json -> 'prompt' ->> 'system_prompt'
FROM agent_config_snapshots
WHERE id = '<snapshot_id>';

步骤 4 · 绑定知识库

4.1 先确认库可选

完整程序:/opt/workbuddy/scripts_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_03.py

运行/引用:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_03.py

python
r = d.get("/datasets?page=1&limit=20")
for ds in (r.get("json") or {}).get("data") or []:
    print(ds.get("id"), ds.get("name"), ds.get("indexing_status"),
          ds.get("embedding_model"), ds.get("embedding_model_provider"))

indexing_status 必须是 available,embedding 必须是正在跑的 bge-m3。列表空或灰掉:专题三,先 GET /v1/models。

4.2 工作室

配置 / 知识 → 添加知识库 → 勾选目标库。

新 Agent 能不能选中 看 embedding 是否可达;对话用哪套 Top-K/Rerank 看快照,不单看这个勾选。

4.3 用快照确认(新 Agent 真正生效的绑定)

sql
SELECT jsonb_pretty(
  (config_snapshot::jsonb) -> 'knowledge'
)
FROM agent_config_snapshots
WHERE id = '<snapshot_id>';

sets 里应有目标 dataset 的 id。若工作室勾了库但这里是空的:按专题十三的 Python 改对象再写回快照和草稿,不要点发布。

绑库时建议顺手把 retrieval 写成步骤 5 的目标结构,避免只勾了库、检索仍是默认 top_k=4、Rerank 关。

旧应用那套 POST /apps/<id>/model-config + dataset_configs 不要用在新 Agent 上。

步骤 5 · 检索与 Rerank(必须改两处)

知识库问答要准,建议:语义检索 + Rerank + Top-K=3 或 5。只改一处会「命中测试好、对话差」或反过来。

5.1 知识库页 / API(命中测试用)

字段名:reranking_provider_name / reranking_model_name。

完整程序:/opt/workbuddy/scripts_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_04.py

运行/引用:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_04.py

python
DS_ID = "<dataset_id>"
ds_retrieval = {
    "search_method": "semantic_search",
    "reranking_enable": True,
    "reranking_mode": "reranking_model",
    "reranking_model": {
        "reranking_provider_name": "langgenius/xinference/xinference",
        "reranking_model_name": "bge-reranker-v2-m3",
    },
    "top_k": 5,
    "score_threshold_enabled": False,
    "score_threshold": None,
}
r = d.req("PATCH", "/datasets/%s" % DS_ID, body={"retrieval_model": ds_retrieval})
print(r["code"], r["json"]["retrieval_model_dict"])

没有 rerank 凭据时,先按专题十三把模型加进「设置 → 模型供应商」(工作室里没有)。API:

完整程序:/opt/workbuddy/scripts_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_05.py

运行/引用:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_05.py

python
r = d.post(
    "/workspaces/current/model-providers/langgenius/xinference/xinference/models/credentials",
    {
        "model": "bge-reranker-v2-m3",
        "model_type": "rerank",
        "credentials": {
            "server_url": "http://192.168.110.207:9997",
            "model_uid": "bge-reranker-v2-m3",
            "api_key": "xf-K8H3LiS27H4188XdOyWJj74MWMOlIR9tZbiybDbxbSas7eAI",
            "invoke_timeout": "60",
            "max_retries": "3",
        },
        "name": "bge-reranker-v2-m3",
    },
)

Xinference 里还没有该实例时,先管理员 login 再 launch,不要 --n-gpu 0。

5.2 Agent 快照(对话用)

字段名:provider / model。reranking_mode 用 reranking_model。

完整程序:/opt/workbuddy/scripts_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_06.py

运行/引用:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_06.py

python
import json
from dify_api import Dify

SNAP_ID = "<snapshot_id>"
DRAFT_ID = "<draft_id>"
DS_ID = "<dataset_id>"
PROVIDER = "langgenius/xinference/xinference"
RERANK = "bge-reranker-v2-m3"

d = Dify("38205036@qq.com", "Difyai123456")

def load_cfg(table, cid):
    out, _ = d.sh(
        "docker exec docker-db_postgres-1 psql -U postgres -d dify -t -A -c "
        "\"SELECT config_snapshot FROM %s WHERE id='%s'\"" % (table, cid)
    )
    return json.loads(out.strip())

def save_cfg(table, cid, cfg, tag):
    sql = "UPDATE %s SET config_snapshot = $cfg$%s$cfg$ WHERE id='%s';" % (
        table, json.dumps(cfg, ensure_ascii=False), cid
    )
    remote = "/tmp/upd_%s.sql" % tag
    with d.sftp.open(remote, "w") as f:
        f.write(sql)
    out, err = d.sh(
        "docker exec -i docker-db_postgres-1 psql -U postgres -d dify < %s" % remote
    )
    print(table, (out or err).strip())

def bind_and_rerank(cfg):
    know = cfg.setdefault("knowledge", {})
    sets = know.setdefault("sets", [{}])
    sets[0]["dataset_id"] = DS_ID   # 若现有结构用 id / dataset 字段,以 load 出来的为准
    retr = sets[0].setdefault("retrieval", {})
    retr["top_k"] = 5
    retr["reranking_enable"] = True
    retr["reranking_mode"] = "reranking_model"
    retr["reranking_model"] = {"provider": PROVIDER, "model": RERANK}
    return cfg

snap = load_cfg("agent_config_snapshots", SNAP_ID)
draft = load_cfg("agent_config_drafts", DRAFT_ID)
# 先把 snap/draft 备份成本地 json
save_cfg("agent_config_snapshots", SNAP_ID, bind_and_rerank(snap), "snap")
save_cfg("agent_config_drafts", DRAFT_ID, bind_and_rerank(draft), "draft")

sets[0] 里知识库主键字段以你 load_cfg 的实际 JSON 为准(可能是 dataset_id 或嵌套对象),不要盲目覆盖丢字段。

改完不要发布。 强刷工作室即可。

步骤 6 · 把 max_tokens 写成显式值

新 Agent 刚创建时,model.model_settings.max_tokens 经常是 null,运行时回退插件默认 8192。制度问答 Prompt 可达 1.4 万,8192 会被 thinking 吃光(专题十二、十四)。

本环境建议写入 32768(context_size 64000,合计低于 65536)。

sql
BEGIN;
UPDATE agent_config_snapshots
SET config_snapshot = jsonb_set(
  config_snapshot::jsonb,
  '{model,model_settings,max_tokens}',
  '32768'::jsonb
)::text
WHERE id = '<snapshot_id>';

UPDATE agent_config_drafts
SET config_snapshot = jsonb_set(
  config_snapshot::jsonb,
  '{model,model_settings,max_tokens}',
  '32768'::jsonb
)::text
WHERE id = '<draft_id>';
COMMIT;

核对:

sql
SELECT config_snapshot::json -> 'model' -> 'model_settings' -> 'max_tokens'
FROM agent_config_snapshots WHERE id = '<snapshot_id>';

必须是数字 32768,不能是 null。控制台抽屉里改完再点发布,值可能又没了。

步骤 7 ·(可选)打开 Langfuse Tracing

新 Agent 工作室 没有「监测」菜单。用 API(Langfuse 连接 Dify 专题步骤 3):

完整程序:/opt/workbuddy/scripts_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_07.py

运行/引用:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_07.py

python
APP_ID = "<app_id>"
body = {
    "tracing_provider": "langfuse",
    "tracing_config": {
        "public_key": "pk-lf-84a76d89-b6b4-4943-b4ea-2dee48d4cda7",
        "secret_key": "sk-lf-24aa6d73-ed36-4447-9eb6-3ebd446fb298",
        "host": "http://192.168.110.207:3000",
    },
}
d.post("/apps/%s/trace-config" % APP_ID, body=body)
d.post("/apps/%s/trace" % APP_ID,
       body={"enabled": True, "tracing_provider": "langfuse"})

Host 不要写 localhost。只写 Dify .env 的 LANGFUSE_* 不会上报。

步骤 8 · 工具(可选)

制度问答可以不挂 Shell。若要挂 DifyShellLayer:

  1. 确认 sandbox / ssrf_proxy / plugin_daemon Up,SANDBOX_URL 用 compose 服务名(专题七)。
  2. 工作室「工具」页若对不上旧截图,用 API 列 provider,再看快照 tools。
  3. 挂完不要为了「生效」去点发布。

步骤 9 · 验收(必须新开对话)

9.1 命中测试

完整程序:/opt/workbuddy/scripts_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_08.py

运行/引用:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_08.py

python
r = d.post("/datasets/%s/hit-testing" % DS_ID,
           body={"query": "法定假日加班的制度是什么,加班费怎么算?"})
for i, rec in enumerate(r["json"] if isinstance(r["json"], list) else r["json"].get("records", []), 1):
    seg = rec.get("segment") or rec
    print(i, rec.get("score"), (seg.get("content") or "")[:80])

开了 Rerank 后,直接答题的条款应排在前面(本环境加班费 300% 会到第 1)。

9.2 对话

完整程序:/opt/workbuddy/scripts_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_09.py

运行/引用:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_09.py

python
r = d.post(
    "/agent/%s/chat-messages" % AGENT_ID,
    body={
        "inputs": {},
        "query": "法定假日加班的制度是什么,加班费怎么算?",
        "response_mode": "streaming",
        "files": [],
        "conversation_id": "",
    },
    extra="-m 420 --max-time 420",
    t=450,
)
print(r["code"], (r["text"] or "")[-800:])

conversation_id 留空 = 新开对话。控制台也要新开会话,不要在失败气泡上接着问。

9.3 库表

sql
SELECT id, status, message_tokens, answer_tokens,
       left(query, 40), left(coalesce(error,''), 120)
FROM messages
ORDER BY created_at DESC
LIMIT 5;

期望 status=normal,有 answer_tokens。error 里出现 token limit 见第三部分。

步骤 10 · 创建收尾检查单



本专题 Python 程序一览

| 文件 | 用途 | |---|---|| 专题十五_00_创建前检查.py | 专题十五 · 步骤 0:创建前检查 LLM / Embedding / Rerank / 知识库 / 外部探针。 | | 专题十五_01_创建应用空壳.py | 专题十五 · 步骤 1.2:POST /apps 创建 mode=agent 空壳。 | | 专题十五_02_查三个ID.py | 专题十五 · 步骤 2:查 app_id / agent_id / snapshot_id / draft_id。 | | 专题十五_03_确认知识库可选.py | 专题十五 · 步骤 4.1:确认知识库 available 且 embedding 为 bge-m3。 | | 专题十五_04_改知识库检索.py | 专题十五 · 步骤 5.1:PATCH 知识库 retrieval_model(命中测试用)。 | | 专题十五_05_改Agent快照绑库与Rerank.py | 专题十五 · 步骤 5.2:改 Agent 快照+草稿 knowledge.sets 绑库与 Rerank。 | | 专题十五_06_改max_tokens.py | 专题十五 · 步骤 6:快照+草稿写入 max_tokens=32768。 | | 专题十五_07_配置Langfuse_Tracing.py | 专题十五 · 步骤 7:POST trace-config + /trace 打开 Langfuse Tracing。 | | 专题十五_08_命中测试验收.py | 专题十五 · 步骤 9.1:命中测试验收。 | | 专题十五_09_Agent对话验收.py | 专题十五 · 步骤 9.2:新开 Agent 对话 + messages 表验收。 |

专题十五_00_创建前检查.py

运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_00_创建前检查.py

完整补丁:patches_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_01.py

运行/引用:见 patches_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_01.py

python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题十五 · 步骤 0:创建前检查 LLM / Embedding / Rerank / 知识库 / 外部探针。"""
from __future__ import annotations

import sys

import _bootstrap  # noqa: F401
import _config as C
from dify_api import Dify, pp


def main() -> int:
    d = Dify(C.DIFY_EMAIL, C.DIFY_PASSWORD)
    try:
        d.login()
        pp("llm", d.get("/workspaces/current/models/model-types/llm"))
        pp("emb", d.get("/workspaces/current/models/model-types/text-embedding"))
        pp("rerank", d.get("/workspaces/current/models/model-types/rerank"))
        pp("datasets", d.get("/datasets?page=1&limit=20"))

        print("\n=== vLLM ===")
        out, err = d.sh(
            "curl -s -m 10 -H 'Authorization: Bearer %s' %s/models"
            % (C.VLLM_KEY, C.VLLM_BASE)
        )
        print(out or err)

        print("\n=== Xinference ===")
        out2, err2 = d.sh(
            "curl -s -m 15 -H 'Authorization: Bearer %s' %s/v1/models"
            % (C.XF_KEY, C.XF_URL)
        )
        print(out2 or err2)
        return 0
    finally:
        d.close()


if __name__ == "__main__":
    sys.exit(main())

专题十五_01_创建应用空壳.py

运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_01_创建应用空壳.py

完整补丁:patches_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_02.py

运行/引用:见 patches_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_02.py

python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题十五 · 步骤 1.2:POST /apps 创建 mode=agent 空壳。"""
from __future__ import annotations

import sys

import _bootstrap  # noqa: F401
import _config as C
from dify_api import Dify, pp


def main() -> int:
    d = Dify(C.DIFY_EMAIL, C.DIFY_PASSWORD)
    try:
        d.login()
        body = {
            "name": C.NEW_APP_NAME,
            "description": "企业内部制度问答",
            "mode": "agent",
            "icon_type": "emoji",
            "icon": "🤖",
            "icon_background": "#FFEAD5",
        }
        r = d.post("/apps", body)
        pp("create app", r)
        app_id = (r.get("json") or {}).get("id")
        print("app_id", app_id)
        if not app_id:
            return 1
        print("下一步:运行 专题十五_02_查三个ID.py 记下 agent_id / snapshot_id / draft_id")
        return 0 if r.get("code") in ("200", "201") else 1
    finally:
        d.close()


if __name__ == "__main__":
    sys.exit(main())

专题十五_02_查三个ID.py

运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_02_查三个ID.py

完整补丁:patches_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_03.py

运行/引用:见 patches_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_03.py

python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题十五 · 步骤 2:查 app_id / agent_id / snapshot_id / draft_id。"""
from __future__ import annotations

import sys

import _bootstrap  # noqa: F401
import _config as C
from dify_api import Dify


def main() -> int:
    d = Dify(C.DIFY_EMAIL, C.DIFY_PASSWORD)
    try:
        d.login()
        sql = """
SELECT
  ap.id AS app_id,
  ap.mode,
  ap.name,
  a.id AS agent_id,
  a.active_config_snapshot_id AS snapshot_id,
  d.id AS draft_id
FROM apps ap
LEFT JOIN agents a ON a.app_id = ap.id
LEFT JOIN agent_config_drafts d ON d.agent_id = a.id
WHERE ap.name ILIKE '%%%s%%'
ORDER BY ap.created_at DESC
LIMIT 5;
""" % C.NEW_APP_NAME.replace("'", "''")
        out, err = d.sh(
            'docker exec %s psql -U postgres -d dify -c "%s"'
            % (C.PG_CONTAINER, sql.replace("\n", " "))
        )
        print(out or err)

        print("\n=== API 列表(最近 10 个应用)===")
        r = d.get("/apps?page=1&limit=10")
        for item in (r.get("json") or {}).get("data") or []:
            print(item.get("id"), item.get("mode"), item.get("name"))
        return 0
    finally:
        d.close()


if __name__ == "__main__":
    sys.exit(main())

专题十五_03_确认知识库可选.py

运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_03_确认知识库可选.py

完整补丁:patches_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_04.py

运行/引用:见 patches_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_04.py

python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题十五 · 步骤 4.1:确认知识库 available 且 embedding 为 bge-m3。"""
from __future__ import annotations

import sys

import _bootstrap  # noqa: F401
import _config as C
from dify_api import Dify


def main() -> int:
    d = Dify(C.DIFY_EMAIL, C.DIFY_PASSWORD)
    try:
        d.login()
        r = d.get("/datasets?page=1&limit=20")
        ok = False
        for ds in (r.get("json") or {}).get("data") or []:
            print(
                ds.get("id"), ds.get("name"), ds.get("indexing_status"),
                ds.get("embedding_model"), ds.get("embedding_model_provider"),
            )
            if ds.get("id") == C.DS_ID and ds.get("indexing_status") == "available":
                ok = True
        if not ok:
            print("[WARN] 目标库 %s 不可用或不在列表" % C.DS_ID)
            return 1
        return 0
    finally:
        d.close()


if __name__ == "__main__":
    sys.exit(main())

专题十五_04_改知识库检索.py

运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_04_改知识库检索.py

完整补丁:patches_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_05.py

运行/引用:见 patches_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_05.py

python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题十五 · 步骤 5.1:PATCH 知识库 retrieval_model(命中测试用)。"""
from __future__ import annotations

import sys

import _bootstrap  # noqa: F401
import _config as C
from dify_api import Dify, pp


def build_retrieval() -> dict:
    return {
        "search_method": "semantic_search",
        "reranking_enable": True,
        "reranking_mode": "reranking_model",
        "reranking_model": {
            "reranking_provider_name": C.XINFERENCE_PROVIDER,
            "reranking_model_name": C.RERANK_MODEL,
        },
        "top_k": 5,
        "score_threshold_enabled": False,
        "score_threshold": None,
    }


def main() -> int:
    d = Dify(C.DIFY_EMAIL, C.DIFY_PASSWORD)
    try:
        d.login()
        r = d.req("PATCH", "/datasets/%s" % C.DS_ID, body={"retrieval_model": build_retrieval()})
        print("HTTP", r.get("code"))
        j = r.get("json") or {}
        print("retrieval_model_dict:", j.get("retrieval_model_dict"))
        pp("patch dataset", r)
        return 0 if r.get("code") == "200" else 1
    finally:
        d.close()


if __name__ == "__main__":
    sys.exit(main())

专题十五_05_改Agent快照绑库与Rerank.py

运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_05_改Agent快照绑库与Rerank.py

完整补丁:patches_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_06.py

运行/引用:见 patches_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_06.py

python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题十五 · 步骤 5.2:改 Agent 快照+草稿 knowledge.sets 绑库与 Rerank。"""
from __future__ import annotations

import json
import sys

import _bootstrap  # noqa: F401
import _agent_cfg as AC
import _config as C
from dify_api import Dify


def bind_and_rerank(cfg: dict) -> dict:
    know = cfg.setdefault("knowledge", {})
    sets = know.setdefault("sets", [{}])
    s0 = sets[0]
    if "dataset_id" not in s0 and "id" not in s0:
        s0["dataset_id"] = C.DS_ID
    elif "dataset_id" in s0:
        s0["dataset_id"] = C.DS_ID
    retr = s0.setdefault("retrieval", {})
    retr["top_k"] = 5
    retr["reranking_enable"] = True
    retr["reranking_mode"] = "reranking_model"
    retr["reranking_model"] = {
        "provider": C.XINFERENCE_PROVIDER,
        "model": C.RERANK_MODEL,
    }
    return cfg


def main() -> int:
    d = Dify(C.DIFY_EMAIL, C.DIFY_PASSWORD)
    try:
        d.login()
        snap = AC.load_cfg(d, "agent_config_snapshots", C.SNAP_ID)
        draft = AC.load_cfg(d, "agent_config_drafts", C.DRAFT_ID)
        AC.save_cfg(d, "agent_config_snapshots", C.SNAP_ID, bind_and_rerank(snap), "snap15")
        AC.save_cfg(d, "agent_config_drafts", C.DRAFT_ID, bind_and_rerank(draft), "draft15")
        out, _ = d.sh(
            "docker exec %s psql -U postgres -d dify -t -A -c "
            "\"SELECT config_snapshot::json -> 'knowledge' FROM agent_config_snapshots WHERE id='%s';\""
            % (C.PG_CONTAINER, C.SNAP_ID)
        )
        print("knowledge:", out.strip())
        print("\n[WARN] 改完不要点「构建/发布」")
        return 0
    finally:
        d.close()


if __name__ == "__main__":
    sys.exit(main())

专题十五_06_改max_tokens.py

运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_06_改max_tokens.py

完整补丁:patches_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_07.py

运行/引用:见 patches_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_07.py

python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题十五 · 步骤 6:快照+草稿写入 max_tokens=32768。"""
from __future__ import annotations

import subprocess
import sys
from pathlib import Path


def main() -> int:
    script = Path(__file__).resolve().parent / "专题十四_04_改max_tokens.py"
    return subprocess.call([sys.executable, str(script)])


if __name__ == "__main__":
    sys.exit(main())

专题十五_07_配置Langfuse_Tracing.py

运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_07_配置Langfuse_Tracing.py

完整补丁:patches_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_08.py

运行/引用:见 patches_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_08.py

python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题十五 · 步骤 7:POST trace-config + /trace 打开 Langfuse Tracing。"""
from __future__ import annotations

import subprocess
import sys
from pathlib import Path


def main() -> int:
    script = Path(__file__).resolve().parent / "Langfuse_02_配置Dify_Tracing.py"
    return subprocess.call([sys.executable, str(script)])


if __name__ == "__main__":
    sys.exit(main())

专题十五_08_命中测试验收.py

运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_08_命中测试验收.py

完整补丁:patches_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_09.py

运行/引用:见 patches_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_09.py

python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题十五 · 步骤 9.1:命中测试验收。"""
from __future__ import annotations

import sys

import _bootstrap  # noqa: F401
import _config as C
from dify_api import Dify


def main() -> int:
    d = Dify(C.DIFY_EMAIL, C.DIFY_PASSWORD)
    try:
        d.login()
        r = d.post(
            "/datasets/%s/hit-testing" % C.DS_ID,
            body={"query": C.PROBE_QUERY_OVERTIME},
            extra="-m 180 --max-time 180",
            t=200,
        )
        j = r.get("json")
        recs = j if isinstance(j, list) else (j or {}).get("records") or []
        for i, rec in enumerate(recs[:5], 1):
            seg = rec.get("segment") or rec
            print(i, rec.get("score"), (seg.get("content") or "")[:80])
        return 0 if r.get("code") == "200" else 1
    finally:
        d.close()


if __name__ == "__main__":
    sys.exit(main())

专题十五_09_Agent对话验收.py

运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_09_Agent对话验收.py

完整补丁:patches_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_10.py

运行/引用:见 patches_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_10.py

python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题十五 · 步骤 9.2:新开 Agent 对话 + messages 表验收。"""
from __future__ import annotations

import subprocess
import sys
from pathlib import Path

import _bootstrap  # noqa: F401
import _config as C
from dify_api import Dify


def main() -> int:
    chat_script = Path(__file__).resolve().parent / "专题十三_09_Agent验收对话.py"
    rc = subprocess.call([sys.executable, str(chat_script)])

    d = Dify(C.DIFY_EMAIL, C.DIFY_PASSWORD)
    try:
        out, err = d.sh(
            """docker exec %s psql -U postgres -d dify -c "
SELECT id, status, message_tokens, answer_tokens,
       left(query, 40), left(coalesce(error,''), 120)
FROM messages ORDER BY created_at DESC LIMIT 5;
"
"""
            % C.PG_CONTAINER
        )
        print("\n=== messages ===")
        print(out or err)
    finally:
        d.close()
    return rc


if __name__ == "__main__":
    sys.exit(main())

第三部分 常见报错与处理方式

按创建和第一次问答的时间顺序。详细日志排法见专题十四。

1 · 创建时模型列表为空 / 保存报一大段 JSON

现象:创建 Agent 选不到 qwen3.6-27b,或弹 schema 校验错误。

原因:GET /models/model-types/llm 返回空。常见是 llm.py 把 max_tokens 写进了 model_properties,API 端 ModelPropertyKey 不含该键(专题十)。

处理:按专题一/十修 get_customizable_model_schema,只保留 context_size=64000。重启 plugin_daemon 后再 GET model-types/llm。

2 · 模型标「不兼容」

现象:列表有 qwen,但灰色不兼容。

原因:OpenAI 插件按基座名找 schema,qwen3.6-27b 没有预定义基座。

处理:专题一,clone 通用 chat schema。不要把 max_tokens 塞进 model_properties。

3 · 创建成了聊天助手,后面步骤全对不上

现象:没有 agents 表记录;改 model-config 好像有用,但本文的快照 SQL 查不到。

原因:向导选了旧类型,apps.mode 不是 agent。

处理:删了重建,类型选新 Agent。或用步骤 2 SQL 确认后再继续。不要把旧应用的 dataset_configs 教程套到新 Agent。

4 · 知识库选不到 / 置灰

现象:工作室知识列表空,或能看见但不能勾。

原因:库不是 available,或 embedding 实例没了(Xinference 重启后 bge-m3 消失)。专题三。

处理:

完整脚本:scripts_bash_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_02.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_02.sh

bash
#!/usr/bin/env bash
set -euo pipefail

curl -s -H 'Authorization: Bearer xf-...' http://127.0.0.1:9997/v1/models

没有 bge-m3 就重新 login + launch,uid 保持 bge-m3。不必重建知识库。命中测试通了再回工作室勾选。

5 · 勾了知识库,对话仍像没检索

现象:命中测试有结果,Agent 回答空、偏。

原因:只改了知识库页,快照 knowledge.sets[].retrieval 仍是旧的 reranking_enable=false。

处理:步骤 5.2 两张表一起改。用 SQL 打印 knowledge 确认。

6 · 点了发布,Max Tokens 又回去了

现象:抽屉里改过,发布后变回 8192,或库里变成 null。

原因:新 Agent UI 发布不持久化 max_tokens(专题十一)。

处理:步骤 6 改库。以后改检索、提示词也尽量改 JSON,不要发布。

7 · 空泡 / 3/3 / token limit (2048) 或 (8192)

现象:空气泡,像卡死;或思考停在 knowledge_base_search 一两分钟后失败。

原因:

处理:查 messages.status 和 error。已是 error 就弃用该会话。把 max_tokens 提到 16384–32768。新开对话。不要先关 Rerank。

8 · 400 System message must be at the beginning

现象:一问知识库就 400。说明请求已经到 vLLM。

原因:Qwen3 只接受一条 system。新 Agent 会堆多条。专题四。

处理:chat.py 里合并 system;from __future__ 必须在最前(专题八)。重启 plugin_daemon。

9 · Connection error / 超时

现象:保存模型失败,或问答连不上。

原因:vLLM 没起、API Base 漏 /v1、207 到 116.62.10.183:8000 不通。专题五。

处理:从 Dify 机 curl /v1/models。凭据 API Base 必须是 http://116.62.10.183:8000/v1。

10 · Invalid Credentials(配 Langfuse 时)

现象:POST trace-config 失败。

原因:pk/sk 错、Host 写成 localhost、Langfuse 未启动。

处理:Host 用 http://192.168.110.207:3000。先 curl /api/public/health。密钥见 Langfuse 专题。

11 · Rerank 添加失败 / 命中测试很慢

现象:凭据保存失败,或检索十几秒。

原因:rerank 实例没 launch;用推理 Key 去 launch(403);CPU 上长文档 rerank 本来就慢。专题十三。

处理:管理员 login 再 launch。短文档 /v1/rerank 应 1 秒内。10 条长文 CPU 到 16s 是硬件问题,不是 Agent 配错。

12 · no available node(工具 / 插件)

现象:Shell 或保存模型报 no available node。

原因:plugin_daemon 没加载插件;sandbox 没起;SANDBOX_URL=localhost。专题七、八。

处理:看 docker logs docker-plugin_daemon-1。from __future__ 顺序错会导致整插件挂掉。

13 · 对话 API 404 Conversation Not Exists

现象:用错了路径,例如把 app_id 当 agent_id,或用旧的 /apps/<id>/chat-messages 去打新 Agent。

处理:新 Agent 用:

text
POST /console/api/agent/<agent_id>/chat-messages

agent_id 来自 agents.id,不是 apps.id。

14 · 全体变慢,单条命中测试却很快

现象:多人同时问就排队。

原因:vLLM --max-num-seqs(本环境 8)不够。专题十四档位建议。

处理:不要先把 Top-K 加到 10。查 GPU 排队。新建 Agent 不会提高并发。


附录 · 一张图记住「该点哪」

text
要加模型凭据     → 工作区设置 → 模型供应商  或  POST .../models/credentials
要建应用空壳     → 创建应用(类型 Agent)     或  POST /apps  mode=agent
要改对话检索     → 快照 + 草稿 knowledge.sets   不要发布
要改命中测试     → PATCH /datasets/<id>
要改 Max Tokens  → 快照 model_settings         不要发布
要开 Langfuse    → POST trace-config + /trace  工作室没有监测
要验收           → 新开对话 + messages.status

附录 · 和其它专题的关系

专题创建时何时去看
一 / 五 / 十模型选不到、不兼容、列表空
二 / 三embedding、知识库灰
四 / 八问答 400 system、插件 SyntaxError
七要挂 Shell
十一 / 十二max_tokens 不落库、空泡 2048
十三Rerank 两处配置
十四慢、卡住、按人数配参
Langfuse 连接步骤 7 Tracing API

附录 · 结论

  1. 新 Agent 的运行真相在 agent_config_snapshots,不在工作室抽屉是否好看。
  2. 创建可以用向导;凭据、检索、Token、Tracing 用 API 或改库。
  3. 知识库要绑两层:库本身 available,快照 sets 有 id 且 retrieval 正确。
  4. 改快照后不要发布。失败会话不要续问。
  5. 工作室没有的开关,去「设置 → 模型供应商」,或 dify_api.py。