专题十五: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。
目录
第一部分 原理与注意实现
一、本文解决什么
做出一个新 Agent,验收必须同时满足:
- 应用类型是新 Agent(库表
apps.mode=agent,另有agents行)。 - 模型是
qwen3.6-27b,对话能出字。 - 绑了知识库,问制度能引用原文。
- 检索参数(Top-K / Rerank)在 知识库页和 Agent 快照两处一致。
max_tokens已显式写进快照(不要停在null)。- 需要观测时,Langfuse Tracing 已用 API 打开(工作室里没有这个菜单)。
二、新 Agent 和旧「Agent / 聊天助手」不是同一套配置
| 旧聊天助手 / 旧 Agent | 新 Agent(本文) | |
|---|---|---|
apps.mode | chat / agent-chat | agent |
| 运行配置 | app_model_configs | agent_config_snapshots + agent_config_drafts |
| 绑知识库 | model_config.dataset_configs | config_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,不要抄错表。
三、配置到底写在哪(三层,不要找错)
工作区「设置 → 模型供应商」
模型凭据: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 | 抽屉里改完再点发布 |
| Langfuse | POST .../trace-config + POST .../trace | 在工作室找「监测」 |
四、创建时必须遵守的实现注意
- 先通模型,再创建应用。 LLM 列表空、标「不兼容」、或知识库 embedding 不可用,创建向导会失败或随后选不到库。
- 创建可以用 UI 向导,落地参数用 API/SQL。 向导适合起一个空壳并拿到
app_id/agent_id。Max Tokens、Rerank、Tracing 不要指望点发布一次搞定。 - 改快照必须同时改草稿。 只改
agent_config_snapshots,配置页一刷新会用草稿盖回去。 - 改完不要点「构建 / 发布」。 发布可能把
max_tokens写成null,运行时又回到插件默认 8192,长知识问答会空泡或 thinking 耗尽。改完只强刷、新开对话。 - 知识库 JSON 两套字段名。 知识库用
reranking_provider_name/reranking_model_name;Agent 用provider/model。混用会被extra=forbid拒绝。 - Prompt 体积。 制度类 Top-K=5 时 Prompt 经常 7k–14k。Qwen3.6 的 thinking 也算进
max_tokens。本环境制度问答建议 16384–32768,且Prompt + max_tokens < 65536。 - 失败会话不要继续点。 空泡、
3/3、停在knowledge_base_search时先查messages.status。已是error就新开对话。 - Xinference 实例重启会丢。 容器 Up ≠
bge-m3/ reranker 在跑。知识库从列表消失时先GET /v1/models。 - 插件补丁会丢。
chat.py合并 system、llm.pyschema 克隆、Langfuse Observation UUID,都在容器内。重建plugin_daemon/worker后要再打。 - 登录控制台 API:密码 base64 + CSRF。 用现成
dify_api.py,不要手写裸 POST。
五、本次环境对照(换机器请重查)
| 项 | 值 |
|---|---|
| Dify | 1.16.1,http://192.168.110.207 |
| 登录 | 38205036@qq.com / Difyai123456 |
| 参考应用 | 播城行政问答智能体 |
| 参考 app_id | 40e95fd3-e7f5-44f2-a03b-dc3e60413540 |
| 参考 agent_id | 01a012e7-bed3-7574-b0d1-a5cc79a0d3de |
| 快照 / 草稿 | 01a0135c-... / 01a012e7-bf60-... |
| 知识库 | ee2495bf-c370-49d1-8268-10c51f650fa6 |
| LLM | qwen3.6-27b,context_size=64000 |
| Embedding / Rerank | Xinference bge-m3 / bge-reranker-v2-m3 |
| 建议 max_tokens | 32768(长制度 + thinking) |
| 建议 Top-K | 3–5 |
| 对话 API | POST /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 201 | 400 用错 /apps;见步骤 1 正文 |
| 2 | 记下四个 ID | app/agent/snap/draft 已写入 _config.py | psql 查不到 → 步骤 2 SQL |
| 3~4 | 提示词 + 知识库检索 | 快照里已有 system_prompt | 专题三绑库;字段名见步骤 5 |
| 5 | 绑库 + Rerank(可选) | 快照+草稿 retrieval 一致 | 专题十三 两处 JSON |
| 6 | max_tokens | 快照 model_settings.max_tokens 非 null | 专题十一/十二;勿点发布 |
| 7~8 | Langfuse(可选) | Trace 可见 | Langfuse 连接专题 |
| 9 | 新开对话验收 | streaming 无 error、能引用库 | 专题四/十四;空泡→专题十二 |
步骤 0 · 创建前检查单(不通过就不要点「创建」)
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
#!/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必须看到:
qwen3.6-27b在 LLM 列表且status=active(不要「不兼容」)bge-m3embeddingactive,Xinference/v1/models里也有- 目标知识库
indexing_status=available - 若要用 Rerank:列表里已有
bge-reranker-v2-m3
缺哪项先做对应专题,不要带着红字去创建。
| 缺什么 | 先看 |
|---|---|
| LLM 空 / 不兼容 | 专题一、十、五 |
| 无 bge-m3 / 知识库灰 | 专题二、三 |
| 无 Rerank | 专题十三 |
System message must be at the beginning | 专题四(chat.py 补丁) |
| 插件 SyntaxError | 专题八 |
步骤 1 · 创建应用空壳
1.1 控制台(适合第一次)
- 打开
http://192.168.110.207,登录。 - 工作室 / 应用列表 → 创建应用。
- 类型选 Agent(新 Agent,不要选「聊天助手」除非你明确要旧链路)。
- 名称例如:
播城行政问答智能体。 - 模型选
qwen3.6-27b。选不到就停,回到步骤 0。 - 创建成功后进入工作室。左侧通常是 配置、知识、工具。没有「监测」是正常的。
1.2 控制台 API(自动化或 UI 类型不好选时)
完整程序:/opt/workbuddy/scripts_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_01.py
运行/引用:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_01.py
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 记下来
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
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 · 写系统提示词(指令)
工作室 配置 页的指令框建议:
你是企业内部知识助手。请根据提供的知识库内容回答问题。
要求:
1. 如果知识库中有明确答案,请基于知识库回答。
2. 如果知识库中没有答案,请明确说“知识库中未找到相关内容”。
3. 不要编造公司制度、流程或数据。
4. 回答要简洁,适合企业内部员工阅读。保存方式:
- 可以在工作室改指令后先 不要点发布,看草稿是否变了。
- 更稳:改快照 JSON 的
prompt.system_prompt(和 max_tokens 同一套改库,见步骤 6)。
核对:
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
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 真正生效的绑定)
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
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
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
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)。
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;核对:
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
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:
- 确认
sandbox/ssrf_proxy/plugin_daemonUp,SANDBOX_URL用 compose 服务名(专题七)。 - 工作室「工具」页若对不上旧截图,用 API 列 provider,再看快照
tools。 - 挂完不要为了「生效」去点发布。
步骤 9 · 验收(必须新开对话)
9.1 命中测试
完整程序:/opt/workbuddy/scripts_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_08.py
运行/引用:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十五_新Agent从零创建完整教程/专题十五_内联_08.py
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
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 库表
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 · 创建收尾检查单
- [ ]
apps.mode=agent,有agent_id/snapshot_id/draft_id - [ ] LLM
qwen3.6-27bactive;对话 API 能出字 - [ ] 知识库 available,快照
knowledge.sets已绑定 - [ ] 知识库
retrieval_model与快照retrieval都是目标 Top-K / Rerank - [ ] 两套 JSON 字段名没有混用
- [ ] 快照和草稿
max_tokens是数字,不是 null - [ ] 创建或改库之后没有点发布
- [ ] 新开对话验收通过
- [ ](可选)Langfuse
apps.tracing.enabled=true
本专题 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
#!/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
#!/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
#!/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
#!/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
#!/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
#!/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
#!/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
#!/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
#!/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
#!/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
#!/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 一两分钟后失败。
原因:
- 2048:生成预算太小(专题十二)。
- 8192 且
answer_tokens=0:Prompt 已 1 万+,thinking 把预算用光(专题十四)。界面仍显示检索,其实检索早已结束。
处理:查 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 用:
POST /console/api/agent/<agent_id>/chat-messagesagent_id 来自 agents.id,不是 apps.id。
14 · 全体变慢,单条命中测试却很快
现象:多人同时问就排队。
原因:vLLM --max-num-seqs(本环境 8)不够。专题十四档位建议。
处理:不要先把 Top-K 加到 10。查 GPU 排队。新建 Agent 不会提高并发。
附录 · 一张图记住「该点哪」
要加模型凭据 → 工作区设置 → 模型供应商 或 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 |
附录 · 结论
- 新 Agent 的运行真相在
agent_config_snapshots,不在工作室抽屉是否好看。 - 创建可以用向导;凭据、检索、Token、Tracing 用 API 或改库。
- 知识库要绑两层:库本身 available,快照
sets有 id 且 retrieval 正确。 - 改快照后不要发布。失败会话不要续问。
- 工作室没有的开关,去「设置 → 模型供应商」,或
dify_api.py。