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

知识库开启 Rerank 重排序
Xinference bge-reranker-v2-m3

CPU / GPU 两条 launch、两处配置。步骤未改,代码块已换成完整程序。

专题十三:知识库开启 Rerank 重排序(Xinference bge-reranker-v2-m3)

本文记录在 192.168.110.207 上,为播城行政问答智能体接入 Rerank 重排序:用已有 Xinference 部署 bge-reranker-v2-m3,在知识库检索设置中开启 Rerank、Top-K=5,并同步改新 Agent 运行时配置。

验收用题是此前答得含糊的「法定假日加班的制度是什么」。开启后命中测试把 300% 加班费条款 排到第 1,Agent 能直接给出计算公式。

全文分三部分:原理说明、可照做的操作步骤(含配置和代码)、本次实战踩坑与解法。

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


目录

  1. 第一部分 原理与说明
  2. 第二部分 详细操作步骤
  3. 第三部分 本次实战问题与解决方案

步骤 3 分两条:无 GPU 看 3.1;有 GPU 看 3.2。


第一部分 原理与说明

一、本文解决什么问题

目标只有一件事:

向量检索先召回一批可能相关的制度片段,再用 Rerank 模型按「和当前问句到底有多相关」重新排序,把真正能回答问题的条款送到大模型面前。

验收必须同时看到:

  1. Xinference 里 bge-reranker-v2-m3 状态为 running,/v1/rerank 能打分。
  2. Dify 知识库检索设置:Rerank 开启,模型为该 reranker,Top-K = 5。
  3. 新 Agent 对话用的也是同一套 Rerank(不能只改知识库页、Agent 仍按旧检索跑)。
  4. 用以前答得含糊的复杂问题对比前后:命中测试第 1 条应变成更贴题的条款;Agent 回答要能引用关键数字(本次是法定节假日加班费 300%)。
  5. Langfuse 里能看到这轮问答 Trace。召回名次变化以知识库命中测试为准(原因见第三部分)。

二、Rerank 在检索链路里做什么

知识库问答通常是两段:

text
用户问句
    │
    ▼
① Embedding 召回(bge-m3 + Weaviate)
    按向量相似度取出一批 chunk
    分数高只代表「向量靠近」,不一定是能直接答题的条款
    │
    ▼
② Rerank 精排(bge-reranker-v2-m3)
    把「问句 + 每个 chunk 原文」成对送进交叉编码器
    输出 relevance_score,按分数重排
    只把 Top-K 条交给大模型
    │
    ▼
③ LLM 根据精排后的片段生成答案

没有 Rerank 时,Dify 把 Embedding 的前几条直接塞进 Prompt。问「法定假日加班费怎么算」时,向量检索更容易先命中标题像「法定节假日管理规定」的放假天数段落,真正写 300% 加班费公式 的 11.1.6 可能排在后面,模型就答得空、答得偏。

有 Rerank 时,模型看到的第 1 条更可能是「加班工资计算方法」。本次命中测试就是这样翻过来的。

三、为什么选 Xinference + bge-reranker-v2-m3

可选两条路:单独起一套 TEI(Text Embeddings Inference),或复用已经在跑的 Xinference。

本次选 Xinference,原因:

  1. 207 上已经有 Xinference 在跑 bge-m3(专题二),Dify 的 Xinference 插件本身支持 rerank 类型。
  2. 再起 TEI 要新端口、新镜像、新供应商配置,和现有 Embedding 拆成两套,运维面更大。
  3. Xinference 内置列表里就有 bge-reranker-v2-m3 和 jina-reranker-v2。前者和现有 bge-m3 同一家族、中英都支持,适合制度类中文文档。

为什么本次跑在 207 CPU,不放到 GPU 机 116.62.10.183:

有 GPU、且 nvidia-smi 确认有余量时,应走步骤 3.2 的完整 GPU 操作(容器加 --gpus all,launch 用 --n-gpu auto 或 --n-gpu 1)。生产检索并发高时,Rerank 上 GPU 比长期趴在 Dify 同机 CPU 上更合适。

四、必须改两处配置,不能只改知识库页

Dify 1.16 新 Agent(mode=agent)的检索参数 不是只读知识库上的 retrieval_model。

位置谁在用本次改前本次改后
知识库 datasets.retrieval_model知识库页、命中测试reranking_enable=false,top_k=3开启,Top-K=5,模型 bge-reranker-v2-m3
Agent config_snapshot.knowledge.sets[].retrieval对话真正召回reranking_enable=false,top_k=4,reranking_mode=weighted_score开启,Top-K=5,reranking_mode=reranking_model

只改知识库:命中测试变好,Agent 问答仍按旧的 Top-K=4、无 Rerank 跑。 只改 Agent:对话好了,知识库页上显示仍是关着的,后人会以为没配。

两处的 JSON 字段名还不一样,不能复制粘贴错:

知识库:

json
"reranking_model": {
  "reranking_provider_name": "langgenius/xinference/xinference",
  "reranking_model_name": "bge-reranker-v2-m3"
}

新 Agent(AgentKnowledgeRerankingModelConfig,extra=forbid):

json
"reranking_model": {
  "provider": "langgenius/xinference/xinference",
  "model": "bge-reranker-v2-m3"
}

运行时 Dify 会把 Agent 的 provider/model 转成知识库那套 reranking_provider_name/reranking_model_name 再调用插件。

五、和已有专题的关系

专题关系
专题二Xinference + bge-m3。本文在同一容器再 launch 一个 model_type=rerank 实例。容器起来 ≠ 模型在跑,对 rerank 同样成立。
专题三知识库必须 available、embedding 可达,Rerank 才有意义。
专题十一 / 十二改 Agent 库表后 不要点发布,否则 max_tokens 可能被写回 null 或再踩 2048。本文改 knowledge.sets 时沿用同一条禁忌。
Langfuse 连接 Dify用来看这轮问答 Trace。Generation 的 Input 目前往往只有用户原话,召回名次请看命中测试。

六、本次环境

项值
服务器192.168.110.207
Dify1.16.1,http://192.168.110.207
应用播城行政问答智能体
应用 ID40e95fd3-e7f5-44f2-a03b-dc3e60413540
Agent ID01a012e7-bed3-7574-b0d1-a5cc79a0d3de
活动快照 ID01a0135c-320e-79dc-97a0-94da9b904c13
编辑草稿 ID01a012e7-bf60-7673-a894-64551d984a23
知识库播城内部行政管理知识库
知识库 IDee2495bf-c370-49d1-8268-10c51f650fa6
Embeddingbge-m3(Xinference)
Rerankbge-reranker-v2-m3(Xinference,CPU,engine=sentence_transformers)
Xinferencehttp://192.168.110.207:9997,容器名 xinference
Xinference 管理员admin / admin1234
Xinference API Keyxf-K8H3LiS27H4188XdOyWJj74MWMOlIR9tZbiybDbxbSas7eAI(只能推理,不能 launch)
Dify 控制台38205036@qq.com / Difyai123456
插件langgenius/xinference:0.0.15
Top-K5
验收 Tracehttp://192.168.110.207:3000/project/default/traces/45b0e430-e592-45c2-a582-b94b7ff63a70

换环境时 UUID、密钥、IP 都要重查,不要直接抄。


第二部分 详细操作步骤

Python 约定(全库统一)

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

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

- 换环境只改 /opt/workbuddy/scripts_cursor/Dify_专题十三_知识库Rerank重排序/_config.py。在 207 上运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/脚本名.py

- 各步骤正文已嵌入对应完整程序;插件源码级补丁也做成完整 apply 脚本放在 /opt/workbuddy/scripts_cursor/。

下面按本次真实顺序写。人在 207 上可以直接 docker exec;从 Windows 工作区过去,普通 ssh 容易卡密码,用现有 dify_api.py(Paramiko)即可。

步骤速查(1~11:目标 · 通过 · 卡住)

步目标通过标准卡住 →
1~2基线命中测试有结果、未开 Rerank专题二 embedding
3~4launch rerank/v1/rerank 有分数本文第三部分 Q1~Q4
5~6Dify 凭据 + 库设置库 retrieval 开 Rerank字段名勿混
7Agent 快照+草稿两处 JSON 都改只改库未改 Agent
8~9命中+对话Top-1 变化、能引用新开对话
10~11Trace + 检查单Langfuse 有 trace第三部分 Q10

Python 约定(全库统一)

- 正文不再只贴片段。每个步骤对应 /opt/workbuddy/scripts_cursor/ 下一个完整可运行的 .py 文件(含 main()、常量、登录)。

- 有配置改动时给 修改前 / 修改后 两个完整程序,并附 改动对比 表。

- 换环境只改 /opt/workbuddy/scripts_cursor/Dify_专题十三_知识库Rerank重排序/_config.py。运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/文件名.py(在仓库根目录)。

步骤 1 · 确认现状

确认 Xinference 在跑、只有 embedding、知识库还没开 Rerank。

207 上可先跑 shell(容器/内存):

完整脚本:scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_01.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_01.sh

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

docker ps --filter name=xinference --format '{{.Names}} {{.Status}} {{.Ports}}'
docker stats xinference --no-stream --format '{{.MemUsage}} CPU={{.CPUPerc}}'
free -h

本次实录:容器 Up,映射 9997;大约占用 2.2Gi / 31Gi;宿主机还有约 21Gi available。

Dify / 库表侧用下面完整程序一次查完(含 Xinference 模型列表、知识库 retrieval、Agent retrieval、rerank 模型列表):

完整程序:/opt/workbuddy/scripts_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_01_确认现状.py

在 207 上运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_01_确认现状.py

python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题十三 · 步骤 1:确认现状(Rerank 开启前的基线)。"""
from __future__ import annotations

import json
import sys

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


def main() -> int:
    print("=== 1/4 Xinference 容器与内存(在 207 上 ssh 执行 docker 命令)===")
    d = Dify(C.DIFY_EMAIL, C.DIFY_PASSWORD)
    try:
        for cmd in [
            "docker ps --filter name=%s --format '{{.Names}} {{.Status}} {{.Ports}}'" % C.XF_CONTAINER,
            "docker stats %s --no-stream --format '{{.MemUsage}} CPU={{.CPUPerc}}'" % C.XF_CONTAINER,
            "free -h",
        ]:
            out, err = d.sh(cmd)
            print(out or err)

        print("\n=== 2/4 Xinference 已加载模型(应只有 bge-m3 embedding)===")
        out, err = d.sh(
            "curl -s -H 'Authorization: Bearer %s' %s/v1/models" % (C.XF_KEY, C.XF_URL)
        )
        print(out or err)
        try:
            models = json.loads(out)
            for m in models.get("data", models if isinstance(models, list) else []):
                if isinstance(m, dict):
                    print(" -", m.get("id"), m.get("model_type"), m.get("accelerators"))
        except Exception:
            pass

        print("\n=== 3/4 登录 Dify,读知识库 retrieval_model ===")
        ok, _, csrf = d.login()
        print("login ok:", ok, "csrf:", bool(csrf))
        r = d.get("/datasets/%s" % C.DS_ID)
        pp("dataset retrieval_model_dict", r)
        retr = (r.get("json") or {}).get("retrieval_model_dict") or {}
        if retr.get("reranking_enable"):
            print("[WARN] 知识库已开 Rerank,不是「修改前」基线")

        print("\n=== 4/4 Agent 快照 knowledge.sets[0].retrieval + rerank 模型列表 ===")
        sql = (
            "docker exec docker-db_postgres-1 psql -U postgres -d dify -t -A -c "
            "\"SELECT config_snapshot::jsonb -> 'knowledge' -> 'sets' -> 0 -> 'retrieval' "
            "FROM agent_config_snapshots WHERE id='%s';\"" % C.SNAP_ID
        )
        out, err = d.sh(sql)
        print("agent retrieval:", out.strip() or err)
        r2 = d.get("/workspaces/current/models/model-types/rerank")
        pp("model-types/rerank(期望 data 为空)", r2)
        return 0
    finally:
        d.close()


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

改前知识库 retrieval_model_dict 期望:

json
{
  "search_method": "semantic_search",
  "reranking_enable": false,
  "reranking_mode": null,
  "reranking_model": {
    "reranking_provider_name": null,
    "reranking_model_name": null
  },
  "top_k": 3,
  "score_threshold_enabled": false,
  "score_threshold": null
}

改前 Agent knowledge.sets[0].retrieval 关键字段:

json
{
  "mode": "multiple",
  "top_k": 4,
  "reranking_mode": "weighted_score",
  "reranking_enable": false,
  "reranking_model": null
}

Dify 侧当时 GET /workspaces/current/models/model-types/rerank 返回 "data": [],说明还没有 Rerank 模型凭据。

步骤 2 · 先做一轮「开启前」命中测试(对照基线)

不要一上来就改。先把旧排序留下。

完整程序:/opt/workbuddy/scripts_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_02_命中测试_修改前.py

在 207 上运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_02_命中测试_修改前.py

python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题十三 · 步骤 2:开启 Rerank 前的命中测试(对照基线)。"""
from __future__ import annotations

import sys

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


QUERY = "法定假日加班的制度是什么"


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": QUERY,
                "retrieval_model": {
                    "search_method": "semantic_search",
                    "reranking_enable": False,
                    "top_k": 5,
                    "score_threshold_enabled": False,
                },
            },
        )
        pp("hit-testing before rerank", r)
        records = r.get("json")
        if not isinstance(records, list):
            records = (records or {}).get("records") or []
        print("\n--- 前 5 条 ---")
        for i, rec in enumerate(records[:5], 1):
            seg = (rec.get("segment") or {}).get("content") or ""
            print(i, rec.get("score"), seg[:80].replace("\n", " "))
        return 0
    finally:
        d.close()


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

本次开启前前三条(分数为 Embedding 相似度):

名次约分数片段大意
10.69510、法定节假日管理规定(放假天数、国务院办法)
20.656节假日加班认定 + 加班定义
3约 0.6511.1.6 加班工资,含 300% 公式

问加班费时,公式条款不在第 1,这就是「回答模糊」的检索侧原因。

步骤 3 · 用管理员登录 Xinference,再 launch Rerank

推理用的 xf-... Key 不能 launch。必须先在容器里用管理员登录,token 写进容器用户目录,再 launch。这条对 CPU / GPU 都成立。

按机器有没有 GPU 选下面 3.1 或 3.2,不要混用参数。

机器容器launch 的 GPU 参数结果
无 GPU(本次 207)不要加 --gpus不要传 --n-gpu,尤其不要 --n-gpu 0accelerators: [],CPU
有 GPU必须 --gpus all(或指定卡号)--n-gpu auto 或 --n-gpu 1accelerators 非空,CUDA

3.1 本次实录:207 无 GPU,走 CPU

不要加 --n-gpu 0。207 没有 GPU,传 0 会被拒绝。不传该参数,行为与已经跑着的 bge-m3 一样,走 CPU。

完整脚本:scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_02.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_02.sh

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

docker exec xinference bash -lc '
set -e
xinference login -e http://127.0.0.1:9997 --username admin --password admin1234
xinference registrations -e http://127.0.0.1:9997 -t rerank
xinference launch -e http://127.0.0.1:9997 \
  -n bge-reranker-v2-m3 \
  -t rerank \
  -u bge-reranker-v2-m3
'

registrations 里应能看到内置:

text
rerank  bge-reranker-v2-m3   ['en', 'zh', 'multilingual']  True
rerank  jina-reranker-v2     ['en', 'zh', 'multilingual']  True

本次 launch 会从 HuggingFace 镜像拉权重。实录大约 392 秒,进度条从长时间 0% 跳到 80% 再 100%。成功标志:

text
Model uid: bge-reranker-v2-m3

日志应有:

text
ModelActor(bge-reranker-v2-m3-rep0) loaded
Launch finished: model_name=bge-reranker-v2-m3

再次列模型,应同时有 embedding 和 rerank:

完整脚本:scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_03.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_03.sh

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

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

本次成功条目要点:

json
{
  "id": "bge-reranker-v2-m3",
  "model_type": "rerank",
  "model_name": "bge-reranker-v2-m3",
  "model_engine": "sentence_transformers",
  "accelerators": [],
  "language": ["en", "zh", "multilingual"],
  "replica": 1
}

accelerators 为空表示 CPU。engine 是 sentence_transformers,容器里即使没装 FlagEmbedding 也能起。

Xinference 重启后实例会丢,要按本步重新 login + launch,和 bge-m3 一样。无 GPU 继续用 3.1;有 GPU 用 3.2 的 launch 命令(带 --n-gpu)。

3.2 有 GPU 时的完整操作(生产推荐)

CPU 能把 Rerank 跑通,但交叉编码器比 Embedding 更吃算力。10 条长文档在 CPU 上十几秒是硬件问题,不是 Dify 配错。有 GPU、且确认不会挤掉 Qwen / 业务进程时,按下面做完。

先判断走哪条路径:

路径适用做什么
A当前这台 Xinference 机器已经有 NVIDIA GPU,容器也已经加了 --gpus只 login + launch,指定 --n-gpu
B本次这种:Xinference 在 207(无 GPU),要把检索迁到 GPU 机 116.62.10.183先查显存 → 在 116 起 GPU 容器 → login + launch Embedding 和 Rerank → 207 验网 → 改 Dify 两处凭据 URL

两条路径的 login / uid / 不要用推理 Key 去 launch,规则相同。

路径 A · 本机已有 GPU

① 确认 GPU 和容器真能用到卡

完整脚本:scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_04.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_04.sh

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

nvidia-smi
docker inspect xinference --format '{{json .HostConfig.DeviceRequests}} {{.HostConfig.Runtime}}'

nvidia-smi 里要有空闲显存。reranker 权重大约 1GB 级,连运行时按 预留 2~4GB 估。若这张卡已经给 vLLM 留了 gpu-memory-utilization 0.75、再加上业务 mxh_env / mxh_live,余量不够就不要硬上,继续 3.1。

DeviceRequests 不能是 null。若容器创建时没加 --gpus all,容器内看不到 CUDA,launch 即使用 --n-gpu 1 也会失败。需要重建容器时用路径 B 的 docker run(本机把 host 换成这台 GPU 机即可)。

容器内再确认:

完整脚本:scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_05.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_05.sh

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

docker exec xinference bash -lc 'nvidia-smi -L'

应列出 GPU 0: ...。这里报错就先修 NVIDIA Container Toolkit / --gpus,不要继续 launch。

② 管理员登录后 launch(必须带 --n-gpu,且必须 > 0)

完整脚本:scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_06.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_06.sh

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

docker exec xinference bash -lc '
set -e
xinference login -e http://127.0.0.1:9997 --username admin --password admin1234
xinference registrations -e http://127.0.0.1:9997 -t rerank
xinference launch -e http://127.0.0.1:9997 \
  -n bge-reranker-v2-m3 \
  -t rerank \
  -u bge-reranker-v2-m3 \
  --n-gpu auto
'

只给一张卡、要写死时,把 --n-gpu auto 换成 --n-gpu 1。

等价 REST(仍须管理员身份,推理 xf- Key 会 403)。login 之后也可用 Web UI:Models → Launch Model → 搜 bge-reranker-v2-m3 → 类型 rerank → Model UID 设成 bge-reranker-v2-m3 → GPU 数量选 auto 或 1,不要填 0。

完整脚本:scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_07.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_07.sh

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

# 仅在已具备「能 launch 的管理员 token」时使用;Dify 用的 xf- Key 不能发这条
curl -s -X POST 'http://127.0.0.1:9997/v1/models' \
  -H 'Authorization: Bearer <管理员或可 launch 的 token>' \
  -H 'Content-Type: application/json' \
  -d '{
    "model_uid": "bge-reranker-v2-m3",
    "model_name": "bge-reranker-v2-m3",
    "model_type": "rerank",
    "n_gpu": "auto"
  }'

指定卡也可以写成 "device": "cuda:0"(与手册里 bge-m3 的 GPU 写法一致)。n_gpu 为 "auto" 或整数 1 均可;不要 "n_gpu": 0。

③ 确认真的在 GPU 上

完整脚本:scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_08.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_08.sh

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

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

GPU 成功时,rerank 条目应类似:

json
{
  "id": "bge-reranker-v2-m3",
  "model_type": "rerank",
  "model_name": "bge-reranker-v2-m3",
  "model_engine": "sentence_transformers",
  "accelerators": ["0"],
  "replica": 1
}

accelerators 仍是 [] 就还在 CPU,回到 ① 查容器是否挂上 GPU。同时看:

完整脚本:scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_09.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_09.sh

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

docker logs xinference 2>&1 | grep -E 'bge-reranker-v2-m3|cuda|CUDA|Launch finished'
nvidia-smi

nvidia-smi 里应出现对应 Python/Xinference 进程,显存有增加。然后再做步骤 4 的 /v1/rerank 打分。

路径 B · 把 Xinference 迁到 GPU 机 116.62.10.183

和安装手册步骤 9.1(bge-m3 上 GPU)同一思路:检索服务换机器,Dify 只改供应商 URL。Rerank 必须和 Embedding 在同一套 Xinference 上,否则 Dify 两个凭据各指一台,运维会裂开。

① 先确认 GPU 余量(关键前置,避免挤压业务)

完整脚本:scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_10.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_10.sh

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

# 在 GPU 主机 116.62.10.183 上
nvidia-smi

确认:Qwen(约 0.75 利用率)+ 业务(约 11GB)之外,还要给检索留显存。

迁什么建议空闲显存
只迁 Rerank(Embedding 仍在 207 CPU)不推荐。Dify 两套 URL,重启后容易漏 launch
Embedding + Rerank 一起迁(推荐)合计预留 约 4~8GB(bge-m3 约 2~4GB + reranker 约 2~4GB)

余量不够就不要迁,继续 207 CPU。共享 GPU 保护见专题九·三。

② 在 GPU 主机起 Xinference(必须 --gpus all)

完整脚本:scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_11.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_11.sh

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

# GPU 版(在 116 上)
docker run -d --name xinference-gpu --restart always --gpus all -p 9997:9997 \
  -e HF_ENDPOINT=https://hf-mirror.com \
  xprobe/xinference:latest

docker exec xinference-gpu bash -lc 'nvidia-smi -L'
docker exec xinference-gpu pip install sentence-transformers
docker logs -f xinference-gpu
# 看到 Uvicorn running on http://0.0.0.0:9997 即就绪

若 116 上已有同名容器,先 docker ps 看清楚,不要误删正在给别的系统服务的实例。端口 9997 被占用就换宿主机映射,Dify URL 跟着改。

③ 建管理员、生成推理 Key,再 login + launch 两个模型

新起的容器没有 207 上那套账号。按专题二先建 admin,再生成推理用 xf-... Key(这条 Key 仍然只能推理,不能 launch)。

完整脚本:scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_12.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_12.sh

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

docker exec xinference-gpu bash -lc '
set -e
xinference login -e http://127.0.0.1:9997 --username admin --password admin1234
xinference launch -e http://127.0.0.1:9997 \
  -n bge-m3 -t embedding -u bge-m3 --n-gpu auto
xinference launch -e http://127.0.0.1:9997 \
  -n bge-reranker-v2-m3 -t rerank -u bge-reranker-v2-m3 --n-gpu auto
'

uid 必须仍是 bge-m3 和 bge-reranker-v2-m3,否则已有知识库/Agent 对不上。

若是把 207 的数据盘/账号迁过来,原 xf- Key 可以继续用;新起一套就必须换 Key。

④ 在 Dify 机 207 上确认能访问新端点

完整脚本:scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_13.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_13.sh

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

curl -s -m 10 http://116.62.10.183:9997/v1/cluster/auth
# 若不通:116 防火墙放行 9997,两机要可路由

带新 Key 列模型,应同时看到 embedding、rerank,且 accelerators 非空:

完整脚本:scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_14.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_14.sh

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

curl -s -H 'Authorization: Bearer <116上的xf-Key>' \
  http://116.62.10.183:9997/v1/models

再打一次 /v1/rerank(把步骤 4 的 URL 换成 http://116.62.10.183:9997/v1/rerank)。

⑤ 改 Dify 两处 Xinference 凭据(唯一必改项)

控制台 → 设置 → 模型供应商 → Xorbits Inference:

凭据改什么不要改
Embedding bge-m3服务器 URL → http://116.62.10.183:9997(仍不带 /v1);若新容器则 API Key 换成 116 上新 Keymodel_uid 仍为 bge-m3
Rerank bge-reranker-v2-m3同上,URL(和必要时 API Key)model_uid 仍为 bge-reranker-v2-m3

知识库向量 不必重建(仍是 1024 维)。知识库检索设置、Agent 快照里的 provider / model 名也不必改,它们认的是 Dify 供应商里的模型名,不是 IP。

等价 API(Rerank 凭据;Embedding 同理改 server_url):

完整程序:/opt/workbuddy/scripts_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_03B_改Dify凭据_GPU路径.py

在 207 上运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_03B_改Dify凭据_GPU路径.py

python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题十三 · 步骤 3.2 路径 B:把 Dify 的 Xinference URL 改到 GPU 机(修改后)。"""
from __future__ import annotations

import sys

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


def patch_credentials(d: Dify, model: str, model_type: str, server_url: str, api_key: str) -> None:
    body = {
        "model": model,
        "model_type": model_type,
        "credentials": {
            "server_url": server_url,
            "model_uid": model,
            "api_key": api_key,
            "invoke_timeout": "60",
            "max_retries": "3",
        },
        "name": model,
    }
    path = "/workspaces/current/model-providers/%s/models/credentials" % C.XINFERENCE_PROVIDER
    r = d.post(path, body)
    pp("POST %s %s" % (model_type, model), r)
    if r.get("code") not in ("200", "201"):
        r = d.req("PUT", path, body=body)
        pp("PUT %s %s" % (model_type, model), r)


def main() -> int:
    gpu_key = C.XF_KEY  # 新起 GPU 容器时换成 116 上生成的 xf- Key
    d = Dify(C.DIFY_EMAIL, C.DIFY_PASSWORD)
    try:
        d.login()
        print("=== 修改前 URL(207 CPU)===")
        print("embedding/rerank server_url:", C.XF_URL)
        print("\n=== 修改后 URL(116 GPU)===")
        print("embedding/rerank server_url:", C.XF_GPU_URL)
        patch_credentials(d, C.EMBED_MODEL, "text-embedding", C.XF_GPU_URL, gpu_key)
        patch_credentials(d, C.RERANK_MODEL, "rerank", C.XF_GPU_URL, gpu_key)
        return 0
    finally:
        d.close()


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

⑥ 切换前后对照

位置本次 207 CPUGPU 生产(路径 B)
Xinference docker run在 207,无 --gpus在 116,--gpus all,容器名示例 xinference-gpu
launch --n-gpu省略(禁止 0)auto 或 1
模型列表 accelerators[]非空,如 ["0"]
Dify 供应商 URLhttp://192.168.110.207:9997http://116.62.10.183:9997
model_uidbge-m3 / bge-reranker-v2-m3不变
知识库索引已建无需重建
GPU 显存0Embedding+Rerank 合计约 +4~8GB
vLLM / vllm_qwen_run.sh不动不动

迁完后:停掉 207 上旧的 embedding/rerank 实例(或停旧容器),避免 Dify 改 URL 不彻底时两套模型同时活着、难以判断打到哪边。116 上 Xinference 重启后,按 ③ 重新 login + 两条 launch。

之后步骤 4~11 在 GPU 路径上同样要做;只是 /v1/rerank 和 Dify server_url 指向 GPU 机。

步骤 4 · 直接打 /v1/rerank 确认模型真能打分

完整脚本:scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_15.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_15.sh

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

curl -s -m 60 \
  -H 'Authorization: Bearer xf-K8H3LiS27H4188XdOyWJj74MWMOlIR9tZbiybDbxbSas7eAI' \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "bge-reranker-v2-m3",
    "query": "法定假日加班怎么算工资",
    "documents": [
      "法定节假日加班的,加班费 = 固定工资÷应出勤天数×300% ×加班天数。",
      "公司班车发车时间以通知为准。",
      "员工请事假须提前申请。"
    ]
  }' \
  http://127.0.0.1:9997/v1/rerank

本次返回(节选):

json
{
  "results": [
    {"index": 0, "relevance_score": 0.9980502128601074, "document": null},
    {"index": 1, "relevance_score": 0.00312977796420455, "document": null},
    {"index": 2, "relevance_score": 0.00271617597900331, "document": null}
  ]
}

第 0 条(300% 公式)接近 1,另外两条接近 0。模型可用。document 为 null 不影响 Dify:插件主要用 index 和 relevance_score。

Dify 插件调用等价于:

完整程序:/opt/workbuddy/scripts_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_01.py

运行/引用:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_01.py

python
handle = RESTfulRerankModelHandle(model_uid, server_url, auth_headers)
response = handle.rerank(
    documents=docs,
    query=query,
    top_n=top_n,
    return_documents=True,
)

保存凭据时,插件会用这段样例做校验(必须 launch 成功后才能添加):

完整程序:/opt/workbuddy/scripts_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_02.py

运行/引用:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_02.py

python
self.invoke(
    model=model,
    credentials=credentials,
    query="Whose kasumi",
    docs=[
        "Kasumi is a girl's name of Japanese origin meaning \"mist\".",
        "Her music is a kawaii bass, a mix of future bass, pop, and kawaii music ",
        "and she leads a team named PopiParty.",
    ],
    score_threshold=0.8,
)

步骤 5 · 把 Rerank 模型加进 Dify(自定义模型凭据)

控制台路径:设置 → 模型供应商 → Xorbits Inference → 添加模型 → 类型选 Rerank。

这一页在 工作区「设置 / 模型供应商」,不是新 Agent 工作室里。工作室里找不到 Rerank 开关是正常的。本次添加凭据走的是下面的 API(HTTP 201),UI 只作对照。

字段:

项值
模型名称bge-reranker-v2-m3
Server urlhttp://192.168.110.207:9997(不要带 /v1)
Model UIDbge-reranker-v2-m3
API Keyxf-K8H3LiS27H4188XdOyWJj74MWMOlIR9tZbiybDbxbSas7eAI
invoke timeout60
max retries3

等价 API(本次 HTTP 201):

完整程序:/opt/workbuddy/scripts_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_05_添加Rerank凭据.py

在 207 上运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_05_添加Rerank凭据.py

python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题十三 · 步骤 5:把 Rerank 模型加进 Dify 供应商凭据。"""
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 = {
            "model": C.RERANK_MODEL,
            "model_type": "rerank",
            "credentials": {
                "server_url": C.XF_URL,
                "model_uid": C.RERANK_MODEL,
                "api_key": C.XF_KEY,
                "invoke_timeout": "60",
                "max_retries": "3",
            },
            "name": C.RERANK_MODEL,
        }
        r = d.post(
            "/workspaces/current/model-providers/%s/models/credentials" % C.XINFERENCE_PROVIDER,
            body,
        )
        pp("add rerank cred", r)
        r2 = d.get("/workspaces/current/models/model-types/rerank")
        pp("rerank models", r2)
        return 0 if r.get("code") in ("200", "201") else 1
    finally:
        d.close()


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

成功后 model-types/rerank 里应有:

json
{
  "provider": "langgenius/xinference/xinference",
  "models": [
    {
      "model": "bge-reranker-v2-m3",
      "model_type": "rerank",
      "fetch_from": "customizable-model",
      "status": "active"
    }
  ]
}

若 POST 不是 200/201,可再试同路径 PUT。本次 POST 已成功,不必 PUT。

步骤 6 · 改知识库检索设置:开启 Rerank,Top-K=5

控制台:知识库 → 播城内部行政管理知识库 → 检索设置 / 召回设置:

等价 API:

改动对比(知识库 retrieval_model)

字段修改前修改后
reranking_enablefalsetrue
reranking_modenull"reranking_model"
reranking_model两个 nullprovider + bge-reranker-v2-m3
top_k35

修改前

完整程序:/opt/workbuddy/scripts_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_06_改知识库检索_修改前.py

在 207 上运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_06_改知识库检索_修改前.py

python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题十三 · 步骤 6 · 修改前:只读知识库 retrieval_model(不要 PATCH)。"""
from __future__ import annotations

import json
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()
        r = d.get("/datasets/%s" % C.DS_ID)
        pp("dataset before patch", r)
        retr = (r.get("json") or {}).get("retrieval_model_dict") or {}
        print("\n期望修改前关键字段:")
        print(json.dumps({
            "reranking_enable": retr.get("reranking_enable"),
            "top_k": retr.get("top_k"),
            "reranking_mode": retr.get("reranking_mode"),
            "reranking_model": retr.get("reranking_model"),
        }, ensure_ascii=False, indent=2))
        return 0
    finally:
        d.close()


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

修改后

完整程序:/opt/workbuddy/scripts_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_06_改知识库检索_修改后.py

在 207 上运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_06_改知识库检索_修改后.py

python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题十三 · 步骤 6 · 修改后:PATCH 知识库,开启 Rerank,Top-K=5。"""
from __future__ import annotations

import sys

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


def build_retrieval_after() -> 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()
        ds_retrieval = build_retrieval_after()
        r = d.req("PATCH", "/datasets/%s" % C.DS_ID, body={"retrieval_model": ds_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())

本次 PATCH HTTP 200,回读:

json
{
  "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": null
}

对应后端:PATCH /console/api/datasets/<dataset_id>,body 字段 retrieval_model(DatasetUpdatePayload)。

步骤 7 · 同步改新 Agent 的快照和草稿(对话真正生效的地方)

路径:config_snapshot -> knowledge -> sets -> [0] -> retrieval。

两张表都要改:

表ID作用
agent_config_snapshots01a0135c-320e-79dc-97a0-94da9b904c13运行时
agent_config_drafts01a012e7-bf60-7673-a894-64551d984a23配置页草稿

目标 retrieval:

json
{
  "mode": "multiple",
  "model": null,
  "top_k": 5,
  "weights": {
    "weight_type": "customized",
    "vector_setting": {
      "vector_weight": 1,
      "embedding_model_name": "bge-m3",
      "embedding_provider_name": "langgenius/xinference/xinference"
    },
    "keyword_setting": {
      "keyword_weight": 0
    }
  },
  "reranking_mode": "reranking_model",
  "reranking_model": {
    "provider": "langgenius/xinference/xinference",
    "model": "bge-reranker-v2-m3"
  },
  "score_threshold": null,
  "reranking_enable": true
}

注意:AgentKnowledgeRetrievalConfig 是 extra=forbid,不要塞知识库那种 reranking_provider_name 字段,保存/运行可能直接拒。

推荐做法:先备份整段 JSON,用 Python 改对象再写回(比多层 jsonb_set 不容易漏字段)。

改动对比(Agent knowledge.sets[0].retrieval)

字段修改前修改后
top_k45
reranking_enablefalsetrue
reranking_mode"weighted_score""reranking_model"
reranking_modelnull{provider, model}(不是知识库那套字段名)

修改前

完整程序:/opt/workbuddy/scripts_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_07_改Agent快照_修改前.py

在 207 上运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_07_改Agent快照_修改前.py

python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题十三 · 步骤 7 · 修改前:备份 Agent 快照/草稿 retrieval(只读)。"""
from __future__ import annotations

import json
import sys
from pathlib import Path

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


def load_cfg(d: Dify, table: str, cid: str) -> dict:
    out, err = 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)
    )
    raw = out.strip()
    if not raw:
        raise RuntimeError(err or "empty config_snapshot")
    return json.loads(raw)


def main() -> int:
    d = Dify(C.DIFY_EMAIL, C.DIFY_PASSWORD)
    try:
        d.login()
        snap = load_cfg(d, "agent_config_snapshots", C.SNAP_ID)
        draft = load_cfg(d, "agent_config_drafts", C.DRAFT_ID)
        bak_dir = Path(__file__).resolve().parent / "backup"
        bak_dir.mkdir(exist_ok=True)
        for name, cfg in [("snap_before", snap), ("draft_before", draft)]:
            p = bak_dir / ("专题十三_07_%s.json" % name)
            p.write_text(json.dumps(cfg, ensure_ascii=False, indent=2), encoding="utf-8")
            retr = cfg.get("knowledge", {}).get("sets", [{}])[0].get("retrieval", {})
            print(name, "retrieval:", json.dumps(retr, ensure_ascii=False))
            print("  -> 已备份", p)
        return 0
    finally:
        d.close()


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

修改后

完整程序:/opt/workbuddy/scripts_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_07_改Agent快照_修改后.py

在 207 上运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_07_改Agent快照_修改后.py

python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题十三 · 步骤 7 · 修改后:同步改 Agent 快照与草稿的 knowledge.sets[0].retrieval。"""
from __future__ import annotations

import json
import sys

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


def load_cfg(d: Dify, table: str, cid: str) -> dict:
    out, err = 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)
    )
    raw = out.strip()
    if not raw:
        raise RuntimeError(err or "empty config_snapshot")
    return json.loads(raw)


def save_cfg(d: Dify, table: str, cid: str, cfg: dict, tag: str) -> None:
    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 enable_agent_rerank(cfg: dict) -> dict:
    retr = cfg["knowledge"]["sets"][0].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 = load_cfg(d, "agent_config_snapshots", C.SNAP_ID)
        draft = load_cfg(d, "agent_config_drafts", C.DRAFT_ID)
        save_cfg(d, "agent_config_snapshots", C.SNAP_ID, enable_agent_rerank(snap), "snap_rerank")
        save_cfg(d, "agent_config_drafts", C.DRAFT_ID, enable_agent_rerank(draft), "draft_rerank")
        out, _ = d.sh(
            "docker exec docker-db_postgres-1 psql -U postgres -d dify -t -A -c "
            "\"SELECT config_snapshot::json -> 'knowledge' -> 'sets' -> 0 -> 'retrieval' "
            "FROM agent_config_snapshots WHERE id='%s';\"" % C.SNAP_ID
        )
        print("验证 retrieval:", out.strip())
        print("\n[WARN] 改完不要点「构建/发布」")
        return 0
    finally:
        d.close()


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

成功应看到两次 UPDATE 1。

验证:

完整脚本:scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_16.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_16.sh

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

docker exec docker-db_postgres-1 psql -U postgres -d dify -c "
SELECT config_snapshot::json -> 'knowledge' -> 'sets' -> 0 -> 'retrieval'
FROM agent_config_snapshots
WHERE id = '01a0135c-320e-79dc-97a0-94da9b904c13';
"

改完不要点「构建 / 发布」。 新 Agent 发布可能把 max_tokens 写回 null(专题十一、十二)。Rerank 改库后直接强刷页面、新开对话测试即可。

步骤 8 · 开启后命中测试(看召回名次变了没有)

完整程序:/opt/workbuddy/scripts_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_08_命中测试_修改后.py

在 207 上运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_08_命中测试_修改后.py

python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题十三 · 步骤 8:开启 Rerank 后的命中测试(修改后)。"""
from __future__ import annotations

import sys

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


QUERY = "法定假日加班的制度是什么,加班费怎么算?"


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": QUERY})
        pp("hit-testing after rerank", r)
        records = r.get("json")
        if not isinstance(records, list):
            records = (records or {}).get("records") or []
        print("\n--- 前 5 条(分数为 Rerank relevance_score)---")
        for i, rec in enumerate(records[:5], 1):
            seg = (rec.get("segment") or {}).get("content") or ""
            print(i, rec.get("score"), seg[:120].replace("\n", " "))
        return 0
    finally:
        d.close()


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

本次开启后(分数已是 Rerank 的 relevance_score,和开启前的向量分不是同一量纲):

名次分数片段大意
10.94711.1.6 加班工资计算方法(150% / 200% / 300%)
20.84410、法定节假日管理规定
30.611节假日加班认定、续假/旷工
40.50711.1.4 加班管理规定(审批、钉钉)
50.221打卡认定、加班纪律

对比步骤 2:300% 计费条款从靠后升到第 1,放假天数规定降到第 2。这就是 Rerank 的效果。

步骤 9 · 新开对话做 Agent 验收

不要用专题十二里已经 error 的旧会话。新开对话,问:

text
法定假日加班的制度是什么,加班费怎么算?

控制台 API(本次 HTTP 200,messages.status=normal):

完整程序:/opt/workbuddy/scripts_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_09_Agent验收对话.py

在 207 上运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_09_Agent验收对话.py

python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题十三 · 步骤 9:新开 Agent 对话验收(修改后)。"""
from __future__ import annotations

import sys

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


QUERY = "法定假日加班的制度是什么,加班费怎么算?"


def main() -> int:
    d = Dify(C.DIFY_EMAIL, C.DIFY_PASSWORD)
    try:
        d.login()
        payload = {
            "inputs": {},
            "query": QUERY,
            "response_mode": "streaming",
            "files": [],
            "conversation_id": "",
        }
        r = d.post(
            "/agent/%s/chat-messages" % C.AGENT_ID,
            body=payload,
            extra="-m 420 --max-time 420",
            t=450,
        )
        print("HTTP", r.get("code"))
        text = r.get("text") or ""
        print(text[-1500:] if len(text) > 1500 else text)
        if r.get("stderr"):
            print("[stderr]", r["stderr"][:400])
        return 0 if r.get("code") == "200" else 1
    finally:
        d.close()


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

本次答案要点(已实际生成):

流式结束事件里 metadata.retriever_resources 本次是空数组 [],不代表没检索。新 Agent 这条字段经常不填,以命中测试和答案内容为准。

Token 实录:prompt_tokens=7325,completion_tokens=1065,耗时约 23s。Prompt 已包含检索进的制度原文。

步骤 10 · 在 Langfuse 里核对这轮 Trace

Worker 日志应有:

text
Task tasks.ops_trace_task.process_trace_tasks ... Processing trace tasks success

查 Trace:

完整脚本:scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_17.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_17.sh

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

curl -s -u 'pk-lf-84a76d89-b6b4-4943-b4ea-2dee48d4cda7:sk-lf-24aa6d73-ed36-4447-9eb6-3ebd446fb298' \
  'http://127.0.0.1:3000/api/public/traces?limit=8'

本次对应该问句的 Trace ID:45b0e430-e592-45c2-a582-b94b7ff63a70

打开:

http://192.168.110.207:3000/project/default/traces/45b0e430-e592-45c2-a582-b94b7ff63a70

能看到用户原话、模型完整回答(含 300% 公式)、Token。Generation 的 Input 目前只有用户那一句,没有逐条 chunk 列表。要看「召回片段名次变了」,回到步骤 2 和步骤 8 的命中测试对比。

步骤 11 · 收尾检查单



本专题 Python 程序一览

| 文件 | 用途 | |---|---|| 专题十三_01_确认现状.py | 专题十三 · 步骤 1:确认现状(Rerank 开启前的基线)。 | | 专题十三_02_命中测试_修改前.py | 专题十三 · 步骤 2:开启 Rerank 前的命中测试(对照基线)。 | | 专题十三_03B_改Dify凭据_GPU路径.py | 专题十三 · 步骤 3.2 路径 B:把 Dify 的 Xinference URL 改到 GPU 机(修改后)。 | | 专题十三_05_添加Rerank凭据.py | 专题十三 · 步骤 5:把 Rerank 模型加进 Dify 供应商凭据。 | | 专题十三_06_改知识库检索_修改前.py | 专题十三 · 步骤 6 · 修改前:只读知识库 retrieval_model(不要 PATCH)。 | | 专题十三_06_改知识库检索_修改后.py | 专题十三 · 步骤 6 · 修改后:PATCH 知识库,开启 Rerank,Top-K=5。 | | 专题十三_07_改Agent快照_修改前.py | 专题十三 · 步骤 7 · 修改前:备份 Agent 快照/草稿 retrieval(只读)。 | | 专题十三_07_改Agent快照_修改后.py | 专题十三 · 步骤 7 · 修改后:同步改 Agent 快照与草稿的 knowledge.sets[0].retrieval。 | | 专题十三_08_命中测试_修改后.py | 专题十三 · 步骤 8:开启 Rerank 后的命中测试(修改后)。 | | 专题十三_09_Agent验收对话.py | 专题十三 · 步骤 9:新开 Agent 对话验收(修改后)。 |

专题十三_01_确认现状.py

运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_01_确认现状.py

完整补丁:patches_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_01.py

运行/引用:见 patches_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_01.py

python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题十三 · 步骤 1:确认现状(Rerank 开启前的基线)。"""
from __future__ import annotations

import json
import sys

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


def main() -> int:
    print("=== 1/4 Xinference 容器与内存(在 207 上 ssh 执行 docker 命令)===")
    d = Dify(C.DIFY_EMAIL, C.DIFY_PASSWORD)
    try:
        for cmd in [
            "docker ps --filter name=%s --format '{{.Names}} {{.Status}} {{.Ports}}'" % C.XF_CONTAINER,
            "docker stats %s --no-stream --format '{{.MemUsage}} CPU={{.CPUPerc}}'" % C.XF_CONTAINER,
            "free -h",
        ]:
            out, err = d.sh(cmd)
            print(out or err)

        print("\n=== 2/4 Xinference 已加载模型(应只有 bge-m3 embedding)===")
        out, err = d.sh(
            "curl -s -H 'Authorization: Bearer %s' %s/v1/models" % (C.XF_KEY, C.XF_URL)
        )
        print(out or err)
        try:
            models = json.loads(out)
            for m in models.get("data", models if isinstance(models, list) else []):
                if isinstance(m, dict):
                    print(" -", m.get("id"), m.get("model_type"), m.get("accelerators"))
        except Exception:
            pass

        print("\n=== 3/4 登录 Dify,读知识库 retrieval_model ===")
        ok, _, csrf = d.login()
        print("login ok:", ok, "csrf:", bool(csrf))
        r = d.get("/datasets/%s" % C.DS_ID)
        pp("dataset retrieval_model_dict", r)
        retr = (r.get("json") or {}).get("retrieval_model_dict") or {}
        if retr.get("reranking_enable"):
            print("[WARN] 知识库已开 Rerank,不是「修改前」基线")

        print("\n=== 4/4 Agent 快照 knowledge.sets[0].retrieval + rerank 模型列表 ===")
        sql = (
            "docker exec docker-db_postgres-1 psql -U postgres -d dify -t -A -c "
            "\"SELECT config_snapshot::jsonb -> 'knowledge' -> 'sets' -> 0 -> 'retrieval' "
            "FROM agent_config_snapshots WHERE id='%s';\"" % C.SNAP_ID
        )
        out, err = d.sh(sql)
        print("agent retrieval:", out.strip() or err)
        r2 = d.get("/workspaces/current/models/model-types/rerank")
        pp("model-types/rerank(期望 data 为空)", r2)
        return 0
    finally:
        d.close()


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

专题十三_02_命中测试_修改前.py

运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_02_命中测试_修改前.py

完整补丁:patches_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_02.py

运行/引用:见 patches_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_02.py

python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题十三 · 步骤 2:开启 Rerank 前的命中测试(对照基线)。"""
from __future__ import annotations

import sys

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


QUERY = "法定假日加班的制度是什么"


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": QUERY,
                "retrieval_model": {
                    "search_method": "semantic_search",
                    "reranking_enable": False,
                    "top_k": 5,
                    "score_threshold_enabled": False,
                },
            },
        )
        pp("hit-testing before rerank", r)
        records = r.get("json")
        if not isinstance(records, list):
            records = (records or {}).get("records") or []
        print("\n--- 前 5 条 ---")
        for i, rec in enumerate(records[:5], 1):
            seg = (rec.get("segment") or {}).get("content") or ""
            print(i, rec.get("score"), seg[:80].replace("\n", " "))
        return 0
    finally:
        d.close()


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

专题十三_03B_改Dify凭据_GPU路径.py

运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_03B_改Dify凭据_GPU路径.py

完整补丁:patches_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_03.py

运行/引用:见 patches_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_03.py

python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题十三 · 步骤 3.2 路径 B:把 Dify 的 Xinference URL 改到 GPU 机(修改后)。"""
from __future__ import annotations

import sys

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


def patch_credentials(d: Dify, model: str, model_type: str, server_url: str, api_key: str) -> None:
    body = {
        "model": model,
        "model_type": model_type,
        "credentials": {
            "server_url": server_url,
            "model_uid": model,
            "api_key": api_key,
            "invoke_timeout": "60",
            "max_retries": "3",
        },
        "name": model,
    }
    path = "/workspaces/current/model-providers/%s/models/credentials" % C.XINFERENCE_PROVIDER
    r = d.post(path, body)
    pp("POST %s %s" % (model_type, model), r)
    if r.get("code") not in ("200", "201"):
        r = d.req("PUT", path, body=body)
        pp("PUT %s %s" % (model_type, model), r)


def main() -> int:
    gpu_key = C.XF_KEY  # 新起 GPU 容器时换成 116 上生成的 xf- Key
    d = Dify(C.DIFY_EMAIL, C.DIFY_PASSWORD)
    try:
        d.login()
        print("=== 修改前 URL(207 CPU)===")
        print("embedding/rerank server_url:", C.XF_URL)
        print("\n=== 修改后 URL(116 GPU)===")
        print("embedding/rerank server_url:", C.XF_GPU_URL)
        patch_credentials(d, C.EMBED_MODEL, "text-embedding", C.XF_GPU_URL, gpu_key)
        patch_credentials(d, C.RERANK_MODEL, "rerank", C.XF_GPU_URL, gpu_key)
        return 0
    finally:
        d.close()


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

专题十三_05_添加Rerank凭据.py

运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_05_添加Rerank凭据.py

完整补丁:patches_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_04.py

运行/引用:见 patches_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_04.py

python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题十三 · 步骤 5:把 Rerank 模型加进 Dify 供应商凭据。"""
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 = {
            "model": C.RERANK_MODEL,
            "model_type": "rerank",
            "credentials": {
                "server_url": C.XF_URL,
                "model_uid": C.RERANK_MODEL,
                "api_key": C.XF_KEY,
                "invoke_timeout": "60",
                "max_retries": "3",
            },
            "name": C.RERANK_MODEL,
        }
        r = d.post(
            "/workspaces/current/model-providers/%s/models/credentials" % C.XINFERENCE_PROVIDER,
            body,
        )
        pp("add rerank cred", r)
        r2 = d.get("/workspaces/current/models/model-types/rerank")
        pp("rerank models", r2)
        return 0 if r.get("code") in ("200", "201") else 1
    finally:
        d.close()


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

专题十三_06_改知识库检索_修改前.py

运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_06_改知识库检索_修改前.py

完整补丁:patches_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_05.py

运行/引用:见 patches_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_05.py

python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题十三 · 步骤 6 · 修改前:只读知识库 retrieval_model(不要 PATCH)。"""
from __future__ import annotations

import json
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()
        r = d.get("/datasets/%s" % C.DS_ID)
        pp("dataset before patch", r)
        retr = (r.get("json") or {}).get("retrieval_model_dict") or {}
        print("\n期望修改前关键字段:")
        print(json.dumps({
            "reranking_enable": retr.get("reranking_enable"),
            "top_k": retr.get("top_k"),
            "reranking_mode": retr.get("reranking_mode"),
            "reranking_model": retr.get("reranking_model"),
        }, ensure_ascii=False, indent=2))
        return 0
    finally:
        d.close()


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

专题十三_06_改知识库检索_修改后.py

运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_06_改知识库检索_修改后.py

完整补丁:patches_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_06.py

运行/引用:见 patches_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_06.py

python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题十三 · 步骤 6 · 修改后:PATCH 知识库,开启 Rerank,Top-K=5。"""
from __future__ import annotations

import sys

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


def build_retrieval_after() -> 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()
        ds_retrieval = build_retrieval_after()
        r = d.req("PATCH", "/datasets/%s" % C.DS_ID, body={"retrieval_model": ds_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())

专题十三_07_改Agent快照_修改前.py

运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_07_改Agent快照_修改前.py

完整补丁:patches_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_07.py

运行/引用:见 patches_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_07.py

python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题十三 · 步骤 7 · 修改前:备份 Agent 快照/草稿 retrieval(只读)。"""
from __future__ import annotations

import json
import sys
from pathlib import Path

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


def load_cfg(d: Dify, table: str, cid: str) -> dict:
    out, err = 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)
    )
    raw = out.strip()
    if not raw:
        raise RuntimeError(err or "empty config_snapshot")
    return json.loads(raw)


def main() -> int:
    d = Dify(C.DIFY_EMAIL, C.DIFY_PASSWORD)
    try:
        d.login()
        snap = load_cfg(d, "agent_config_snapshots", C.SNAP_ID)
        draft = load_cfg(d, "agent_config_drafts", C.DRAFT_ID)
        bak_dir = Path(__file__).resolve().parent / "backup"
        bak_dir.mkdir(exist_ok=True)
        for name, cfg in [("snap_before", snap), ("draft_before", draft)]:
            p = bak_dir / ("专题十三_07_%s.json" % name)
            p.write_text(json.dumps(cfg, ensure_ascii=False, indent=2), encoding="utf-8")
            retr = cfg.get("knowledge", {}).get("sets", [{}])[0].get("retrieval", {})
            print(name, "retrieval:", json.dumps(retr, ensure_ascii=False))
            print("  -> 已备份", p)
        return 0
    finally:
        d.close()


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

专题十三_07_改Agent快照_修改后.py

运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_07_改Agent快照_修改后.py

完整补丁:patches_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_08.py

运行/引用:见 patches_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_08.py

python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题十三 · 步骤 7 · 修改后:同步改 Agent 快照与草稿的 knowledge.sets[0].retrieval。"""
from __future__ import annotations

import json
import sys

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


def load_cfg(d: Dify, table: str, cid: str) -> dict:
    out, err = 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)
    )
    raw = out.strip()
    if not raw:
        raise RuntimeError(err or "empty config_snapshot")
    return json.loads(raw)


def save_cfg(d: Dify, table: str, cid: str, cfg: dict, tag: str) -> None:
    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 enable_agent_rerank(cfg: dict) -> dict:
    retr = cfg["knowledge"]["sets"][0].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 = load_cfg(d, "agent_config_snapshots", C.SNAP_ID)
        draft = load_cfg(d, "agent_config_drafts", C.DRAFT_ID)
        save_cfg(d, "agent_config_snapshots", C.SNAP_ID, enable_agent_rerank(snap), "snap_rerank")
        save_cfg(d, "agent_config_drafts", C.DRAFT_ID, enable_agent_rerank(draft), "draft_rerank")
        out, _ = d.sh(
            "docker exec docker-db_postgres-1 psql -U postgres -d dify -t -A -c "
            "\"SELECT config_snapshot::json -> 'knowledge' -> 'sets' -> 0 -> 'retrieval' "
            "FROM agent_config_snapshots WHERE id='%s';\"" % C.SNAP_ID
        )
        print("验证 retrieval:", out.strip())
        print("\n[WARN] 改完不要点「构建/发布」")
        return 0
    finally:
        d.close()


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

专题十三_08_命中测试_修改后.py

运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_08_命中测试_修改后.py

完整补丁:patches_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_09.py

运行/引用:见 patches_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_09.py

python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题十三 · 步骤 8:开启 Rerank 后的命中测试(修改后)。"""
from __future__ import annotations

import sys

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


QUERY = "法定假日加班的制度是什么,加班费怎么算?"


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": QUERY})
        pp("hit-testing after rerank", r)
        records = r.get("json")
        if not isinstance(records, list):
            records = (records or {}).get("records") or []
        print("\n--- 前 5 条(分数为 Rerank relevance_score)---")
        for i, rec in enumerate(records[:5], 1):
            seg = (rec.get("segment") or {}).get("content") or ""
            print(i, rec.get("score"), seg[:120].replace("\n", " "))
        return 0
    finally:
        d.close()


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

专题十三_09_Agent验收对话.py

运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_09_Agent验收对话.py

完整补丁:patches_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_10.py

运行/引用:见 patches_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_10.py

python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题十三 · 步骤 9:新开 Agent 对话验收(修改后)。"""
from __future__ import annotations

import sys

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


QUERY = "法定假日加班的制度是什么,加班费怎么算?"


def main() -> int:
    d = Dify(C.DIFY_EMAIL, C.DIFY_PASSWORD)
    try:
        d.login()
        payload = {
            "inputs": {},
            "query": QUERY,
            "response_mode": "streaming",
            "files": [],
            "conversation_id": "",
        }
        r = d.post(
            "/agent/%s/chat-messages" % C.AGENT_ID,
            body=payload,
            extra="-m 420 --max-time 420",
            t=450,
        )
        print("HTTP", r.get("code"))
        text = r.get("text") or ""
        print(text[-1500:] if len(text) > 1500 else text)
        if r.get("stderr"):
            print("[stderr]", r["stderr"][:400])
        return 0 if r.get("code") == "200" else 1
    finally:
        d.close()


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

第三部分 本次实战问题与解决方案

这一节只写 本次 207 上真实碰到的问题,按发生顺序。不是清单式猜测。

问题 1 · 不带 Key 列模型:Could not validate credentials

现象:curl http://127.0.0.1:9997/v1/models 返回 401/校验失败。

原因:Xinference 开了鉴权(GET /v1/cluster/auth → {"auth":true})。

解决:所有推理请求加:

完整脚本:scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_18.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_18.sh

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

-H 'Authorization: Bearer xf-K8H3LiS27H4188XdOyWJj74MWMOlIR9tZbiybDbxbSas7eAI'

问题 2 · 用推理 Key 做 launch:403

现象:

text
RuntimeError: Failed to launch model, detail: API keys can only access model query and inference endpoints

日志:POST /v1/models?wait_ready=False HTTP/1.1" 403

原因:Dify 里填的 xf-... 是推理 Key,权限仅查询和推理,不能创建模型实例。

解决:在容器内用管理员登录后再 launch:

完整脚本:scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_19.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_19.sh

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

xinference login -e http://127.0.0.1:9997 --username admin --password admin1234
xinference launch ...

不要把管理员密码配进 Dify。Dify 继续用推理 Key 调用 /v1/rerank 即可。

问题 3 · 未登录就 launch:Cannot find access token

现象:容器里直接 xinference launch,不带 -ak:

text
RuntimeError: Cannot find access token, please login first!

原因:CLI 会先读本地登录 token;没 login 就没有 token。

解决:同一 docker exec bash -lc 里先 xinference login 再 launch,保证 token 写在同一次会话的 home 里。

问题 4 · --n-gpu 0 被拒绝

现象:

text
ValueError: The parameter `n_gpu` must be greater than 0 and not greater than the number of GPUs: 0 on the machine.

CLI 进度条会假成功打出 Model uid: bge-reranker-v2-m3,但 GET /v1/models 里没有 rerank。

原因:207 没有 GPU。--n-gpu 0 被解析成非法:必须 > 0,同时机器 GPU 数是 0,怎么填都不成立。

解决(按有没有 GPU 分开):

无 GPU(本次 207):不要传 --n-gpu。与已经跑着的 bge-m3 一样走 auto,结果 accelerators: [],即 CPU。

完整脚本:scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_20.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_20.sh

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

# 错误:无 GPU 机器上传 0 会被拒绝
xinference launch ... --n-gpu 0

# 正确:207 CPU,省略该参数
xinference launch -e http://127.0.0.1:9997 -n bge-reranker-v2-m3 -t rerank -u bge-reranker-v2-m3

有 GPU:容器必须已加 --gpus all,launch 要传且必须 > 0:

完整脚本:scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_21.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_21.sh

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

xinference launch -e http://127.0.0.1:9997 \
  -n bge-reranker-v2-m3 -t rerank -u bge-reranker-v2-m3 \
  --n-gpu auto
# 或 --n-gpu 1

完整步骤见 3.2。不要在 GPU 机器上为了「和 207 一样」去省略 --gpus,也不要把 207 上那条「不要传 --n-gpu」抄到有卡的机器上当唯一正确写法。

问题 5 · 查询内置 rerank 列表的 URL 抄错

现象:

原因:Xinference 3.1 这条 REST 路径不是这样;CLI 会把 registrations 当成 model uid。

解决:用 CLI:

完整脚本:scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_22.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_22.sh

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

xinference login ...
xinference registrations -e http://127.0.0.1:9997 -t rerank

问题 6 · 容器里没有 FlagEmbedding,会不会起不来

现象:启动前检查 FlagEmbedding 为 False,只有 sentence_transformers 5.7.0。

原因:有的教程按 FlagEmbedding 装依赖。Xinference 3.1 对 bge-reranker-v2-m3 实际用的 engine 是 sentence_transformers。

解决:先按步骤 3 直接 launch。本次拉权重并加载成功,不必先 pip install FlagEmbedding。若将来换 jina-reranker 或别的 engine 再按报错补依赖。

问题 7 · 只改知识库,Agent 对话仍不走 Rerank

现象:知识库页已显示开启 Rerank、Top-K=5,命中测试也变了,但对话检索仍像旧的。

原因:新 Agent 的召回读 agent_config_snapshots.config_snapshot.knowledge.sets[].retrieval,改前是 top_k=4、reranking_enable=false、reranking_mode=weighted_score。

解决:快照和草稿一起改,字段用 {provider, model},reranking_mode 改成 reranking_model。见步骤 7。

问题 8 · 两套 JSON 字段不能混用

现象:若把知识库的 reranking_provider_name 写进 Agent 配置,可能被 extra=forbid 拒绝。

原因:

运行时由 knowledge_retrieval_inner_service.py 做映射:

完整程序:/opt/workbuddy/scripts_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_03.py

运行/引用:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十三_知识库Rerank重排序/专题十三_内联_03.py

python
"reranking_provider_name": request.retrieval.reranking_model.provider,
"reranking_model_name": request.retrieval.reranking_model.model,

解决:改哪一层就用哪一层的字段名。不要两个结构对半混。

问题 9 · 改完点「发布」可能把 max_tokens 搞丢

现象:为了「让 Rerank 生效」去点发布,之后 Max Tokens 又回到默认或对话再超限。

原因:专题十一已确认,新 Agent UI 发布不一定把 model.model_settings.max_tokens 正确落库。专题十二刚把值改回 8192。

解决:Rerank 改库后只强刷、新开对话。不要发布。

问题 10 · Langfuse 里看不到逐条召回 chunk

现象:Trace 只有一条 Generation,Input 是用户原话;retriever_resources 为空。

原因:当前 Dify → Langfuse 上报对这条链路没有把每条检索片段做成独立 Observation;Generation Input 也被收成用户句。这和 Rerank 是否生效不是一回事。

解决:

不要因为 Langfuse 没列出 chunk 就再 launch 一次或重装插件。

问题 11 · CLI 显示 100% 但模型列表没有 rerank

现象:问题 4 时 CLI 已打印 Model uid: bge-reranker-v2-m3,列表仍只有 bge-m3。

原因:supervisor 接受了请求(wait_ready=False 先 200),worker 随后 launch 失败。CLI 的 100% 不是「已经可服务」。

解决:以 GET /v1/models 是否出现 model_type=rerank,以及日志 ModelActor(...) loaded 为准。失败时看 docker logs xinference 里 Leave launch_builtin_model, error:。

问题 12 · Xinference 重启后 Rerank 消失

现象:重启 xinference 容器后,Dify 命中测试/对话报 Rerank 不可用或校验失败。

原因:launch 的实例不随容器持久化(和 bge-m3 相同)。Dify 凭据还在,对面 uid 没了。

解决:按步骤 3 重新 login + launch,保持 uid 仍为 bge-reranker-v2-m3。不必重加 Dify 凭据,也不必重建知识库。Embedding 的 bge-m3 若一并没了,也要先 launch 回来,否则知识库会从 Agent 列表消失(专题三)。


附录 · 开启前后对照(本次实录)

项开启前开启后
Xinference 模型仅 bge-m3bge-m3 + bge-reranker-v2-m3
知识库 Top-K35
知识库 Rerank关开,bge-reranker-v2-m3
Agent Top-K45
Agent Rerank关,weighted_score开,reranking_model
命中第 1 条法定节假日放假规定加班工资 300% 公式
对话问加班制度时条款容易偏、空给出认定条件 + 300% 公式 + 调休规则
验收 Trace—45b0e430-e592-45c2-a582-b94b7ff63a70

附录 · 结论

  1. Rerank 是第二段精排,不替代 bge-m3,也不替代大模型。
  2. 本次 207 复用 Xinference、CPU 跑 bge-reranker-v2-m3 即可,不必上 TEI。有 GPU 且显存有余量时,按步骤 3.2 上 GPU(容器 --gpus all,launch --n-gpu auto 或 1)。
  3. 必须同时改知识库 retrieval_model 和新 Agent 的 knowledge.sets[].retrieval,字段名两套。
  4. launch 用管理员登录,推理用 xf- Key。无 GPU 不要 --n-gpu 0(直接省略);有 GPU 必须传 auto 或 1。
  5. 改完不要发布。召回名次看命中测试,对话看新开会话,Trace 看 Langfuse。