kangda-robot-backend/ruoyi-fastapi-backend/ES_BM25_KB_PLAN.md

10 KiB
Raw Blame History

目标

在保留现有 RAGFlow 方案的前提下,引入一套 ES/OpenSearchBM25为主 的“内部固定资料问答”链路,用配置开关在 ragflowes_bm25 间切换;并将外部实时问题(天气/新闻等)继续沿用项目现有的“搜索增强”流程。

核心诉求:

  1. 首 token 更快(内部资料命中时尽量做到“检索毫秒级 + LLM首字”
  2. 可上线稳定:有置信度、兜底、同义词、索引治理、可观测。
  3. 可回滚:配置切回 RAGFlow 即可。

现状简述(与你们当前代码对齐)

当前对话入口:POST /system/ragflow/converse_with_chat_assistantmodule_admin/controller/ragflow_controller.py

现有大致流程:

  1. 移除风格提示词(轻量)
  2. StaticQA 静态FAQ本地相似度
  3. SearchService.classify_intent:判断是否走外部搜索(可能有外部模型调用)
  4. SemanticCacheRedis 问答历史缓存(目前实现会扫 keys
  5. 调用 RAGFlow 返回流式答案

对于“首 token 慢”,主要风险点在于:前置链路过长、外部意图识别网络 RTT、缓存扫描、以及 RAGFlow 调用与流式处理的阻塞/日志等。


方案 B可上线稳定的 ES/BM25 内部知识链路

1) 总体架构(保留 RAGFlow通过配置切换

抽象一个内部知识提供方KB Provider提供“检索 +(可选)生成”的能力:

  • KB_PROVIDER=ragflow:保持现状(走 RAGFlow
  • KB_PROVIDER=es_bm25:内部知识走 ES/OpenSearchBM25

外部实时问题(天气/新闻等)继续由现有 SearchService 分流处理,不受 KB_PROVIDER 影响。

建议新增配置(示例,实际以你们 env.py 读取方式为准):

  • KB_PROVIDER=ragflow|es_bm25(默认 ragflow
  • KB_ES_URL=http(s)://host:9200KB_OS_URL=http(s)://host:9200
  • KB_ES_INDEX=kb_chunks_v1
  • KB_ES_USERNAME=...(如开启安全)
  • KB_ES_PASSWORD=...
  • KB_TOP_K=8(检索召回)
  • KB_MIN_SCORE=...(置信度阈值的一个维度)
  • KB_CACHE_TTL=...(检索结果缓存 TTL

2) 请求路由(兼容你们已有流程)

建议稳定且首 token 友好的顺序:

  1. 静态FAQ命中直接回
  2. 内部会话缓存(命中直接回)
  3. 意图识别(外部搜索 vs 内部KB
  4. 内部KB根据 KB_PROVIDERes_bm25ragflow
  5. 兜底内部KB低置信度 →可配置fallback 到 ragflow 或通用对话

说明:

  • 把“缓存”放到“外部意图识别”前,避免命中时仍需等待外部网络调用。

ES/OpenSearch 索引治理

1) 数据结构Chunk 化)

内部资料固定,推荐离线预处理:文档 → 分块chunk→ 入索引。

每条 chunk 建议字段:

  • doc_id文档ID
  • chunk_id分块ID
  • title:标题/章节名(可空)
  • content:正文内容(核心检索字段)
  • source:来源(文件名/系统名)
  • tags:标签(产品/制度/流程/人名/地名等)
  • category:大类(可选)
  • updated_at:更新时间
  • version:索引版本号/数据版本

2) 中文分词与同义词

选择建议:

  • OpenSearch优先使用 analysis-icu +(可选)analysis-smartcn(若可用),并结合自定义同义词。
  • Elasticsearch常见做法是安装 IK 分词(analysis-ik)或 analysis-smartcn

同义词治理建议:

  • 维护一份 synonyms.txt(公司/产品/缩写/别名),例如:
    • 康达新材, 康达
    • 胶粘剂, 胶水
    • 厂区, 园区, 工厂
  • 分两层:
    • query-time synonyms(更安全,不必全量重建索引)
    • index-time synonyms(效果可能更强,但改词需 reindex

3) Mapping & QueryBM25

建议用多字段策略:

  • titleboost 高
  • content:主字段
  • tags/category:过滤与加权

查询 DSL 推荐组合:

  • multi_matchbest_fieldsmost_fields
  • match_phrase(对关键短语加分)
  • minimum_should_match(抑制泛匹配)
  • function_score(用字段权重/新鲜度加权)

置信度与兜底策略(上线稳定的关键)

内部资料“固定且可控”,建议对检索结果计算一个可解释的置信度,用于:

  1. 决定是否直接回答
  2. 决定是否 fallback 到 ragflow
  3. 决定是否提示“我不确定/需要更多信息”

可落地的置信度指标(建议先从简单开始):

  • top1_scoreES 返回的最高分
  • score_gap = top1_score - top2_score:区分度
  • topk_hit_count:有效 chunk 数
  • keyword_coverage:问题关键词在 top chunks 中的覆盖率(你们已有 MatchService.calculate_keyword_coverage

决策建议:

  • 高置信:top1_score >= S1keyword_coverage >= C1 → 直接走“基于上下文生成”或“抽取式回答”
  • 中置信:走“生成 + 引用”
  • 低置信:
    • KB_PROVIDER=es_bm25,则 fallback 到 ragflow(可配置开关 KB_FALLBACK=ragflow|none
    • 或提示用户改写/补充关键词

缓存与性能策略

建议两级缓存Redis

  1. 检索缓存:kb:es:retrieval:{hash(question_norm)} → 存 topK chunksTTL 10~30min
  2. 答案缓存:你们已有 SemanticCache(建议把 KEYS 扫描改为 SCAN/MGET 并限制扫描数量)

首 token 优化要点:

  • 检索缓存命中时:可立即开始构造 prompt 并流式输出。
  • 生成端:
    • 若你们用 DeepSeek/OpenAI 兼容接口,尽量使用 stream=True 并减少前置串行步骤。

观测与回归指标(上线必须)

建议打点并输出到日志/监控:

  • ttft_msTime To First Token
  • retrieve_msES 检索耗时
  • llm_first_token_msLLM 首 token若可获得
  • cache_hit静态FAQ/语义缓存/检索缓存命中率
  • fallback_ratees_bm25 → ragflow 的兜底比例
  • no_hit_rate:检索无结果占比

迁移/上线步骤(建议)

  1. 部署 OpenSearch/ES见下文
  2. 设计索引与分析器(中文分词、同义词)
  3. 编写离线入库脚本:固定资料 → chunk → bulk ingest
  4. 新增 es_bm25 Provider只影响内部 KB 路径)
  5. 灰度:
    • KB_PROVIDER=es_bm25 仅在一台实例开启
    • 对比 ttft_ms、命中率、fallback_rate
  6. 全量切换;保留随时回滚到 KB_PROVIDER=ragflow

生产环境安装 OpenSearch推荐/Elasticsearch 方法

下面给出两种最常见可控方案Docker与 systemd。生产建议至少做到安全认证/TLS、资源限制、数据盘、备份与监控。

前置Linux 内核与系统参数(必须)

  1. 设置虚拟内存映射Lucene 需要):
sudo sysctl -w vm.max_map_count=262144
echo 'vm.max_map_count=262144' | sudo tee -a /etc/sysctl.conf
  1. 建议关闭 swap或至少调低 swappiness

  2. 文件句柄与进程限制systemd/ulimit

  • nofile 建议 >= 65535
  • memlock 尽量 unlimited视环境

方案 1Docker 部署 OpenSearch最快上手

适合:你们已有 Docker想快速上线验证。

  1. 创建数据目录(挂载到数据盘):
sudo mkdir -p /data/opensearch
sudo chown -R 1000:1000 /data/opensearch
  1. 启动单节点(示例,生产请开启安全与持久化配置):
docker run -d --name opensearch \
  -p 9200:9200 -p 9600:9600 \
  -e "discovery.type=single-node" \
  -e "OPENSEARCH_JAVA_OPTS=-Xms4g -Xmx4g" \
  -e "plugins.security.disabled=false" \
  -v /data/opensearch:/usr/share/opensearch/data \
  opensearchproject/opensearch:2
  1. 验证:
curl -k -u admin:admin https://127.0.0.1:9200

生产建议:

  • 用 docker-compose 管理,显式配置 admin 密码、证书、网络白名单。
  • 至少做反向代理/防火墙,仅允许后端访问 9200。

方案 2systemd 部署 OpenSearch更适合生产

适合:长期稳定运行、要配合 systemd 管理与日志。

  1. 安装 JDKOpenSearch 自带 JDK 的版本较多,但建议按官方要求确认):
java -version
  1. 下载并解压 OpenSearch示例命令按你们版本选择
mkdir -p /opt/opensearch
tar -xf opensearch-2.x.x-linux-x64.tar.gz -C /opt/opensearch --strip-components=1
  1. 创建专用用户与数据目录:
sudo useradd --system --home /opt/opensearch --shell /usr/sbin/nologin opensearch
sudo mkdir -p /data/opensearch
sudo chown -R opensearch:opensearch /opt/opensearch /data/opensearch
  1. 配置 opensearch.yml(至少设置数据路径、网络、集群名、安全):
  • path.data: /data/opensearch
  • network.host: 0.0.0.0或内网IP
  • http.port: 9200
  1. systemd service示例骨架

/etc/systemd/system/opensearch.service

[Unit]
Description=OpenSearch
After=network.target

[Service]
Type=simple
User=opensearch
Group=opensearch
WorkingDirectory=/opt/opensearch
ExecStart=/opt/opensearch/bin/opensearch
Restart=always
LimitNOFILE=65535
LimitNPROC=4096
LimitMEMLOCK=infinity

[Install]
WantedBy=multi-user.target

启停:

sudo systemctl daemon-reload
sudo systemctl enable --now opensearch
sudo systemctl status opensearch

Elasticsearch 安装说明(可选)

若你们更倾向 Elasticsearch

  • 官方更推荐包管理rpm/deb+ systemd
  • 中文分词可用 analysis-ikanalysis-smartcn 插件(需要安装插件并重启)

生产同样要保证vm.max_map_count、nofile、数据盘、认证/TLSElastic 8+ 默认安全开启)。


与本项目集成点(开发落点)

建议改动范围(保持接口不变):

  1. 新增 module_admin/service/kb_es_service.py(或类似命名):封装检索、置信度计算、缓存。
  2. 新增“LLM 生成器”(可复用你们已有 DeepSeek/OpenAI 客户端,如果已有);支持 SSE 流式输出。
  3. ragflow_controller 内部 KB 分支增加:
    • KB_PROVIDER=es_bm25:走 ES → 生成 →(低置信度 fallback
    • KB_PROVIDER=ragflow:保持现状

备注:RAGFLOW_* 配置与现有服务保持不变,作为兜底/回滚路径。