← 返回文章列表

Dify 企业级实验(10):知识库持续更新闭环——数据飞轮怎么转起来?

1. 业务场景

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

知识库上线那天不是结束,是开始。每周都有新文档:产品更新、FAQ 积累、售后案例——直接丢进知识库?

我们第一次接这类需求时,第一反应也是「入库不就是调个 API」。真正动手才发现——脏文档一旦入库,检索质量整体下滑:用户问什么都答非所问,知识库从「资产」变成「负担」。入库之前必须有一道门禁,验证之后才允许生效。

知识库不是「建一次就完」,而是持续进化的资产。

这不是个例。任何 RAG 项目交付后都是这个模式:知识库不更新会死,乱更新会烂。

2. 场景痛点

这个流程的痛点,在知识库维护者和用户身上体现得最直接:

本质上,知识库的价值不在「建」,在「持续进化的能力」——入库有门禁、验证后才生效、反馈能回写

3. 方案:为什么是质量门禁 + 反馈回写

Dify 的 HTTP 节点直接调知识库 API(create_by_text),配合 code 节点做质量门禁,正好组成数据飞轮。选它的理由:

这篇文章我们就用它搭一套「知识库数据飞轮」:入库应用 + 问答应用。

4. 整体架构

两个应用:入库 workflow + 问答 workflow。

graph TD subgraph sub_ingest["入库应用"] i_start["开始:title/content/base_url"] cd_clean["cd_clean:清洗(换行归一/去控制字符/合并空行)"] cd_gate["cd_gate:质量门禁(完整性 40 + 格式 30 + 自包含 30,<80 拒绝)"] if_gate{"if_gate:门禁判断"} cd_body["cd_body:组装 create_by_text 载荷"] http_kb["http_kb:POST /v1/datasets/{id}/document/create_by_text"] cd_parse["cd_parse:解析 batch/document_id"] cd_result["cd_result"] end_ok["结束"] cd_reject["cd_reject:拒绝原因"] i_start --> cd_clean --> cd_gate --> if_gate if_gate -- "pass" --> cd_body --> http_kb --> cd_parse --> cd_result --> end_ok if_gate -- "false" --> cd_reject --> cd_result end subgraph sub_qa["问答应用"] q_start["开始(query)"] kb["kb:知识库检索"] cd_ctx["cd_ctx:拼接引用上下文"] lm["lm:生成回答,引用编号"] q_end["结束"] q_start --> kb --> cd_ctx --> lm --> q_end end

链路很清晰:清洗 → 质量门禁 → 入库(create_by_text)→ 检索验证;问答侧未命中反馈回写,形成闭环。门禁前置是这条链的关键设计。

5. 模块设计

5.1 入库开始变量

- variable: title      # 文本,必填,文档标题

- variable: content    # 段落,必填,文档内容(最长 10000)

- variable: base_url   # 文本,必填,Dify 服务地址

5.2 质量门禁 cd_gate

def main(title: str, content: str) -> dict:

    import re

    text = content or ""

    # 完整性 40:长度

    score_complete = 40 if len(text) >= 150 else (20 if len(text) >= 80 else 0)

    # 格式 30:含 markdown 标题

    score_format = 30 if re.search(r"^#{1,3} ", text, re.M) else (15 if text.count("\n") >= 3 else 0)

    # 自包含 30:含具体数字/规格/明确陈述

    has_specific = bool(re.search(r"\d+", text)) or any(k in text for k in ["支持", "可以", "是", "提供", "需"])

    score_self = 30 if has_specific else 10

    score = score_complete + score_format + score_self

    valid = "true" if score >= 80 else "false"

    reasons = []

    if score_complete < 40: reasons.append("内容过短(<200字)")

    if score_format < 30: reasons.append("缺少标题结构")

    if score_self < 30: reasons.append("缺少具体规格/明确陈述")

    return {"score": str(score), "valid": valid,

            "reasons": ";".join(reasons) if reasons else "质量合格",

            "summary": f"标题:{title or '无'} | 评分 {score}/100"}

5.3 入库载荷组装 cd_body 与提交 http_kb

JSON body 用 code 节点组装(http 节点里直接写 JSON 字符串易错):

def main(title: str, content: str) -> dict:

    import json

    payload = {

        "name": title or "untitled.md",

        "text": content or "",

        "indexing_technique": "high_quality",

        "process_rule": {

            "mode": "custom",

            "rules": {

                "pre_processing_rules": [{"id": "remove_extra_spaces", "enabled": True}],

                "segmentation": {"separator": "\n\n", "max_tokens": 500}

            }

        }

    }

    return {"payload": json.dumps(payload, ensure_ascii=False)}
url: "{{#start.base_url#}}/v1/datasets/dfac575f-2490-4e1f-b730-76ccc8463c23/document/create_by_text"

method: POST

body: "{{#cd_body.payload#}}"   # JSON 字符串,Content-Type: application/json

5.4 问答应用

kb 节点选知识库(召回 top_k=3);cd_ctx 拼 [1] 内容 引用块;lm 系统提示词强制「基于知识库内容回答,引用来源编号 [1][2],知识库没有的内容直接说明未找到,不要编造」。

6. 运行验证

输入 预期 实测
合格新文档(产品更新说明,含标题与规格) 清洗 → 门禁 ≥80 → 入库 → 返回 batch/document_id 与预期一致,评分 85 分入库成功
脏文档(过短/无标题/无具体陈述) 门禁 <80,拒绝并给出原因报告 与预期一致,返回「未通过质量门禁(xx/100):内容过短;缺少标题结构」
问答应用问已入库内容 检索命中,回答带引用编号 与预期一致,索引完成后可检索
未命中提问(模拟) 记录问题 → 生成新条目 → 下一轮入库生效 与预期一致,飞轮闭环(2026-08-02 实测)

7. 实战坑

现象 修复
http 节点访问本机服务被 SSRF 拦截 调 172.19.0.50 请求失败,疑似网络不可达 Dify 环境变量放行私有网段(SSRF_PROXY_ALLOW_PRIVATE_IPS=172.16.0.0/12),经 squid 代理实测可达(实测)
服务名 URL 不可达 httpbin.org 等外部演示服务本机连不通 演示端点改本机 KV /echo(172.19.0.50:8123),生产换真实域名(实测)
JSON body 直接写在 http 节点 转义/换行易错,请求体不合法 code 节点 json.dumps 组装 payload,http 节点引用 {{#cd_body.payload#}}(实测)
脏文档直接入库 检索质量下降,答非所问 质量门禁前置,<80 拒绝并回原因(103-04 实测)
FAQ 拆成 Q/A 两段 检索单段命中缺答案 自包含分段硬要求:每段自含答案(103-04 实测)
索引未完成就生效 检索不到新文档 先 create_by_text 等索引完成,再 hit-testing 验证后才算上线(103 实测)

8. 实验文档及源码获取

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

联系我

15088711270

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

微信二维码

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