← 返回文章列表

Dify 中级实验(10):知识库深度调优——如何科学评估检索质量?

1. 业务场景

先讲一个我们实际遇到的场景。

一家做企业软件的公司,把 500 页产品技术文档做成知识库,上线 RAG 问答。用户问「怎么重置密码」,回答牛头不对马嘴;问「错误码 E1001 是什么」,答非所问。技术支持团队每天收到「AI 答得不对」的投诉,但问题出在哪——分段?检索模式?TopK?——没人说得清,因为没有任何数据。

我们第一次接这类需求时,第一反应也是「把 TopK、分段策略、Rerank 挨个调一遍,看哪个效果好」。真正动手才发现——改了一堆参数,效果原地踏步,因为「好不好」根本没有数字:不知道现在多差,就不知道改没改对。后来才想明白:调优的前提是评估——先有尺子,才有调参。

这不是个例。任何 RAG 系统的调优都是这个模式:改一个参数,好不好,得用数字说话——没有评估体系的调优就是瞎调。产品文档知识库、客服 FAQ、RAG 问答系统,上线前调优与持续监控都需要一把「尺子」。

2. 场景痛点

这个流程的痛点,在知识库建设团队身上体现得最直接:

本质上,调优的前提是评估——先能量化「现在多差」,才能知道「改没改对」。参数可以慢慢调,尺子必须一开始就有。

3. 方案:为什么是评估工作流

本实验不调某个单一参数,而是先搭一把「尺子」——一个检索质量评估工作流,用测试集自动打分:

这篇文章我们就用它搭一个「检索质量评估器」:10 组用例自动检索、自动评分、自动汇总。

4. 整体架构

graph TD start["开始:无变量"] gen["生成测试集:Code 10 组 query/expected_keywords/expected_doc"] subgraph iter_loop["逐条检索评估:迭代"] split["拆解测试项:Code item → query/expected_keywords/expected_doc"] kb["技术文档库检索:KB top_k 3 迭代内"] eval["评估检索质量:Code keyword_rate + top1_match + score"] end summary["汇总综合评分:Code"] end1["结束"] start --> gen --> iter_loop iter_loop --> split --> kb --> eval eval --> summary --> end1

链路很清晰:生成测试集 → 迭代逐条检索并评估 → 汇总综合评分。迭代把 10 组用例逐条过一遍 KB 检索,每条算一个分数,最后汇总成 avg_score/avg_keyword_rate/top1_matched——一把可复用的尺子。

5. 模块设计

5.1 测试集(Code)

测试用例要覆盖典型查询形态:操作类(重置密码)、错误码类(E1001)、概念类(什么是 RAG):

def main() -> dict:

    test_cases = [

        {"query": "如何重置密码", "expected_keywords": ["密码重置", "忘记密码", "重置步骤"], "expected_doc": "用户管理_密码重置"},

        {"query": "API 调用频率限制", "expected_keywords": ["限流", "Rate Limit", "API配额"], "expected_doc": "API文档_限流策略"},

        {"query": "错误码 E1001 是什么", "expected_keywords": ["E1001", "错误码", "排查"], "expected_doc": "排障手册_错误码"},

        # ... 共 10 组

    ]

    return {"test_cases": test_cases}

5.2 迭代内知识库检索

迭代内 KB 节点需要五件套(isInIteration/iteration_id/parentId/sourcePosition/targetPosition):

- data:

    dataset_ids:

    - 459c4981-6b76-43e7-b44f-c430f33ef035   # 你的知识库 ID

    isInIteration: true

    iteration_id: iter_test

    output_retrieval_result: true            # 必须 true,否则 result 为空

    parentId: iter_test

    query_attachment_selector: []

    query_variable_selector: [cd_split, query]

    retrieval_mode: single

    score_threshold: 0.0

    title: 技术文档库检索

    top_k: 3

    type: knowledge-retrieval

  id: kb_retrieve

5.3 评估节点(Code)

KB 结果传给代码节点,参数类型必须声明为 list(检索结果是 list[dict]),评分 = 0.5 × 关键词命中率 + 0.5 × Top1 文档匹配:

def main(query: str, expected_keywords: str, expected_doc: str, retrieved: list) -> dict:

    import json

    retrieved = retrieved if isinstance(retrieved, list) else []

    texts = [str(r.get("content", "")) for r in retrieved if isinstance(r, dict)]

    all_text = " ".join(texts).lower()

    try:

        kws = json.loads(expected_keywords or "[]")

    except Exception:

        kws = []

    kws = [str(k).lower() for k in kws if k]

    hits = sum(1 for k in kws if k in all_text)

    keyword_rate = round(hits / len(kws), 2) if kws else 0.0

    top1_title = str(retrieved[0].get("title", "")) if retrieved and isinstance(retrieved[0], dict) else ""

    top1_match = bool(top1_title) and str(expected_doc or "").lower() in top1_title.lower()

    score = round(keyword_rate * 0.5 + (1.0 if top1_match else 0.0) * 0.5, 2)

    return {"eval_text": json.dumps({...}, ensure_ascii=False)}   # 序列化成 string 供迭代收集

5.4 迭代输出与汇总

迭代 output_selector 只能选可见类型(array[object] 收集不到),所以评估节点输出 eval_text 字符串、output_type: array[string];汇总节点再逐个 json.loads 聚合:

def main(results: list) -> dict:

    import json

    total = 0; score_sum = 0.0; kw_sum = 0.0; top1_ok = 0

    for r in results:

        try:

            d = json.loads(r) if isinstance(r, str) else {}

        except Exception:

            d = {}

        total += 1

        score_sum += float(d.get("score", 0) or 0)

        kw_sum += float(d.get("keyword_hit_rate", 0) or 0)

        if d.get("top1_match"):

            top1_ok += 1

    return {

        "total_cases": total,

        "avg_score": round(score_sum / total, 2) if total else 0.0,

        "avg_keyword_rate": round(kw_sum / total, 2) if total else 0.0,

        "top1_matched": top1_ok,

        "summary_text": "检索质量评估:共 {} 组用例,综合评分 {},关键词命中率均值 {},Top1 文档匹配 {} 组".format(

            total, round(score_sum / total, 2) if total else 0.0,

            round(kw_sum / total, 2) if total else 0.0, top1_ok),

    }

6. 运行验证

检查项 预期 实测
total_cases 10 10
avg_keyword_rate 0~1 之间,反映关键词召回水平 视知识库质量而定
top1_matched 期望文档命中 Top1 的组数 视知识库质量而定
summary_text 「共 10 组用例,综合评分 X,关键词命中率均值 Y,Top1 文档匹配 Z 组」 与预期一致

调优闭环:跑出基线 → 调整知识库分段策略/TopK/是否开 Rerank → 重跑同一测试集 → 对比 avg_score。分数上升说明改对了——这就是「评估比配置重要」的落地方式。

7. 实战坑

现象 修复
KB 结果传给 code 签名写 str 运行报 'list' object has no attribute ... 参数类型声明 list,用 r.get("content", "") 提取
忘了 output_retrieval_result [kb, result] 返回空,评估全 0 设为 true,结果才是结构化 list[dict]
把 Rerank 当工作流节点 找不到 rerank 节点类型 Rerank 是知识库检索设置(dataset 层面 reranking_enable + reranking_model),不在工作流里配置
迭代 output_selector 选 array[object] 迭代输出为空数组 评估节点输出 json.dumps 字符串,output_type: array[string]
测试集超过 30 组 迭代报 then length of var "item" must be less than 30 elements 测试集留余量(本实验 10 组)
种子文档不含测试关键词 关键词命中率恒 0,误判「检索坏了」 测试集与知识库种子文档对齐,先验证数据再调参

💡 调优顺序建议:① 先保证文档质量(预处理清洗、分段语义完整)——垃圾进垃圾出;② 再选检索模式(混合 > 单一,有 rerank 模型就开);③ 最后调 TopK/Score 阈值,每次只改一个变量并用同一测试集回归。

8. 实验文档及源码获取

文章聚焦核心配置与采坑点;实验的完整分步操作(节点搭建/参数表/调试指引)见实验文档原文。

本系列 · Dify 实验 · 中级
  1. Dify 中级实验(01):参数提取器实战——如何从自然语言中提取结构化数据?
  2. Dify 中级实验(02):问题分类器——智能路由引擎如何四路分发?
  3. Dify 中级实验(03):模板转换实战——如何用零 Token 完成文本加工?
  4. Dify 中级实验(04):迭代进阶——如何批量处理数据并守住性能边界?
  5. Dify 中级实验(05):并行执行——如何让多路任务同时跑?
  6. Dify 中级实验(06):变量聚合——如何确定性合并多路分支结果?
  7. Dify 中级实验(07):子工作流——如何把公共逻辑做成可复用积木?
  8. Dify 中级实验(08):代码节点进阶——如何用标准库处理文件与数据?
  9. Dify 中级实验(09):HTTP 节点进阶——如何搞定认证、分页与错误重试?
  10. Dify 中级实验(10):知识库深度调优——如何科学评估检索质量?
  11. Dify 中级实验(11):高级 RAG 流水线——如何搭建多路检索与精排?
  12. Dify 中级实验(12):Agent 深度配置——如何让智能体自主调用工具?
  13. Dify 中级实验(13):多 Agent 协作——如何编排多个智能体分工干活?
  14. Dify 中级实验(14):对话变量与状态管理——如何让工作流记住多轮对话的状态?
  15. Dify 中级实验(15):条件分支高阶策略——多条件路由如何避免分支爆炸?
  16. Dify 中级实验(16):错误处理与降级——工作流如何有尊严地失败?
  17. Dify 中级实验(17):调试监控与性能优化——响应慢和 Token 超支如何定位?
  18. Dify 中级实验(18):插件开发入门——如何把工作流变成 Agent 可调用的工具?
  19. Dify 中级实验(19):综合实战——如何把 19 个实验串成一条生产级流水线?
  20. Dify 中级实验(20):综合实战——自动化报告生成流水线如何从数据到周报一步到位?

联系我

15088711270

手机端点击号码可直接拨打 · 桌面端可复制

微信二维码

扫码加微信 · 备注「门户」更快通过