Skip to content

Latest commit

 

History

History
121 lines (87 loc) · 7.97 KB

File metadata and controls

121 lines (87 loc) · 7.97 KB

FAQ - 项目常见问题与核心逻辑解答

本文档汇总了关于 PaiSmart RAG 系统实现细节的常见疑问及其技术解答。

1. 为什么检索时使用的是原始 query 而不是 normalized

  • 问题背景: 在 SearchService 中,向量化使用的是原始 query,而 ES 的文本检索使用的是去噪后的 normalized
  • 解答:
    • 向量化 (Embedding): 原始 query 保留了完整的语气、句法结构和上下文(如疑问句式、逻辑连接词)。高质量的向量模型(如 DeepSeek、豆包)能够理解这些细微的语义,若是去噪反而会丢失信息。
    • 文本检索 (BM25): 传统的倒排索引对“的”、“是”、“请问”等高频停用词非常敏感,不去噪会导致大量无关文档被召回。
    • 结论: 向量用全句(保语义),BM25 用关键词(保精准),这是混合检索的最佳实践。

2. ES 查询中的 must match 会不会太严格?

  • 问题背景: 当前代码将文本匹配 (match) 放在了 must 子句中。
  • 解答:
    • 最新策略: 我们已将关键词匹配策略恢复为严格模式 (minimum_should_match: 100%)
    • 原因: 相比单纯降低阈值引入噪声,我们引入了更高级的 Query 重写与扩写。通过 LLM 将模糊、口语化的输入转换为多条精准的书面查询,即使关键词匹配严格,也能通过多路并发检索覆盖所有可能的表达方式。

3. should: buildPhraseShould(phrase) 是什么意思?

  • 问题背景: 代码中构建了一个针对 phraseshould 查询。
  • 解答:
    • 这是为了给连续短语匹配加分
    • 如果文档中不仅包含查询词,而且这些词是连续出现的(即完全匹配短语),ES 会通过 match_phrase 给该文档一个很高的额外分数(Boost 3.0)。
    • 这是一种轻量级的精排机制:保证了“零散匹配”能搜到,“精确匹配”排前面。

4. 向量检索 (kNN) 是谁来做的?

  • 问题背景: 代码中构造了 knn json 对象,但计算在哪里发生?
  • 解答:
    • 完全由 Elasticsearch 完成。
    • Go 代码仅负责构造查询 DSL(JSON),ES 收到请求后利用底层的 HNSW 索引在向量空间中进行最近邻搜索。ES 在这里充当了向量数据库的角色。

5. StreamResponse (Generation) 具体在做什么?

  • 问题背景: chat_service.go 中的流式响应逻辑。
  • 解答: 它是 RAG 的“最后一公里”,负责:
    1. 组装上下文 (Prompt Engineering): 将搜到的 10 个文档片段格式化为 [1]... [2]... 形式,拼接到 Prompt 中。
    2. 调用 LLM: 将完整的上下文发给 DeepSeek 模型。
    3. 流式转发 (Streaming): 利用 wsWriterInterceptor 拦截 AI 生成的每一个字符,并通过 WebSocket 实时推送给前端,实现这“打字机”效果。

6. pkg/embedding 是做什么的?

  • 解答:
    • 它是一个向量化客户端
    • 负责调用外部 API(如阿里云 DashScope/豆包),将文本转化为数学向量 (float32 array)
    • 它充当了 RAG 系统的“翻译官”,连接了非结构化文本与数学向量空间。

7. BM25 在哪里?为什么代码里没看到?

  • 问题背景: 提到混合检索是 Vector + BM25,但代码里只有 match
  • 解答:
    • Elasticsearch 的 match 查询默认就是使用 BM25 算法进行打分的。
    • 代码中的 must match (初筛) 和 rescore match (重排) 底层都在调用 ES 的 BM25 评分器。
    • 由 ES 服务端内部实现,不需要 Go 客户端显式调用。

8. 为什么有了 BM25 + Vector 还需要 Cross-Encoder Rerank?

  • 问题背景: 既然已经做了混合检索,为什么还要引入重排序?
  • 解答:
    • BM25 (字面匹配): 只能看字面,不懂“苹果手机充电”和“吃苹果补充体力”的区别。
    • Vector (语义匹配): 压缩了信息,可能丢失“不推荐”、“避免”等否定语义。
    • Cross-Encoder (重排序): 它是一个交互式模型,像专家一样逐字阅读“问题”和“文档”,能精准判断相关性。
    • 结论:
      • BM25+Vector 负责海选 (Top 50,快但粗)。
      • Rerank 负责决赛 (Top 10,慢但准)。这是提升 LLM 回答质量的关键一步。

9. 引入 Rerank 后还需要 BM25 吗?

  • 解答:
    • 绝对需要
    • Rerank 速度太慢,无法处理百万级文档,必须依赖 BM25+Vector 进行快速初筛 (First-stage Retrieval)
    • BM25 擅长精确关键词匹配(如错误码、专有名词),是向量检索的重要互补(向量容易漂移)。
    • 二者是**前鋒(召回)守门员(精排)**的关系,缺一不可。

10. Rerank 和 RRF 的作用和区别?

  • 渠道融合 vs 终极评审
    • RRF (裁判员):它的职责是“合并名单”。它把来自“向量检索”和“关键词检索”这两份完全不同维度的名单,根据各自的排名公平地捏合到一起,确保两路优秀的结果都能出线。它解决的是异构数据如何汇总的问题。
    • Rerank (面试官):它的职责是“择优录用”。它是对 RRF 汇总后的名单(如前 50 名)进行逐一面试,用更昂贵的语义模型(Cross-Encoder)重新打分,把真正准确的文档推到最顶端。
  • 协作模式:RRF 保证了名单的多样性与广度,Rerank 保证了结果的极端精准度

11. 高级 Query 优化(查询转换)是如何实现的?

  • 问题背景: 为什么系统能听懂“那怎么用”或者把口语转换得非常专业?
  • 解答:
    • 核心机制:在 SearchService 执行检索前,增加了一个 LLM 预处理层
    • 实现步骤
      1. 历史注入:将最近 3 轮对话历史连同当前问题一起发给 LLM,实现指代消解(如把“那”替换为前文提到的具体产品名)。
      2. 语义重写:通过精心设计的 Prompt 引导 LLM 将口语(如“咋整”)重构为标准检索词(如“处理规程”)。
      3. 多路扩写:LLM 一次生成 3 个语义互补的查询变体,以此覆盖文档中可能存在的不同表达方式。
      • 严格兜底:因为 Query 质量变高,系统配合使用了 minimum_should_match: 100% 进行严格过滤,实现了“高召回”与“高精准”的统一。

12. 为什么重新上传同 MD5 文档后需要“物理清理”?

  • 问题背景: 如果不清理,重新上传(覆盖)文档后,搜索结果里偶尔会出现“未知文件”或过时的内容片段。
  • 解答:
    • 残留数据 (Ghost Data): RAG 系统将文档切分为多个分块(Chunks)。Elasticsearch 的默认逻辑是根据 vector_id 执行 upsert(更新或插入)。
    • 问题成因: 如果新版文档生成的切片数量减少了(例如删减了文字),或者分词器的分片逻辑发生了变化,原先多出来的旧切片 ID 将无法被覆盖,从而残留在索引库中。这些残留切片往往缺少新的元数据(如 file_name 字段)。
    • 解决方案: 在 Processor 处理流水线中,我们在入库前引入了物理 Purge 机制。根据文件的 MD5,系统会首先调用 DeleteByQuery 强制清空该文档在 Elasticsearch 和 MySQL 中的所有既有记录。
    • 结论: 这种“先破后立”的幂等性设计,确保了每次入库的数据都是绝对纯净的,彻底杜绝了 RAG 系统中常见的“僵尸数据”干扰问题。