Skip to content

RAG 检索增强生成与本地/企业知识库架构 ​

回到总览:AI 时代的工程师能力
相关模块:Prompt 工程、Tool Calling 与 Agent 记忆机制 (Memory Workflow) · Agent Tool Calling (Function Calling) 架构与安全防护

一句话定义 ​

RAG(Retrieval-Augmented Generation,检索增强生成)是指通过将外部私有知识库文档切片(Chunking)、文本向量化(Embedding)并存入向量数据库,在用户提问时检索最相关的 Context 注入到 Prompt 中,从而消除 LLM 幻觉并赋予大模型精准解答私有/实时知识的能力。

代码索引 ​

主题Lab 说明源码
最小 RAG 检索闭环tool-calling-rag-streaming-demoserver.js · client.js

为什么需要 ​

  • 为什么不能直接将包含几十万字的企业文档全部塞给 LLM 的 Context Window 进行提问?
    • 一句话答:盲目塞入全部文档不仅会瞬间超出上下文窗口上限并产生巨大的 Token 费用,而且会导致 LLM 遭遇“Needle In A Haystack(大海捞针)”难题,忽略中间的关键细节;RAG 只检索最相关的 Top-K 切片,精准且高效。
  • RAG 如何解决 LLM 的“幻觉(Hallucination)”与“知识时效性”问题?
    • 一句话答:RAG 将问题锚定在检索到的真实文档切片上,要求大模型“严格基于提供的 Context 回答”,并在答案中附带出处引用;当私有知识更新时只需更新向量库索引,无需重新训练模型。
  • 移动端 / 端侧应用如何构建轻量级本地 RAG 架构?
    • 一句话答:在移动端通过 SQLite 向量扩展(或 ObjectBox Vector)存储向量,配合端侧轻量 Embedding 模型(如 ONNX/BGE-small)做本地检索,彻底不依赖网络即可实现 100% 隐私安全的本地知识助手。

底层机制 ​

1. RAG 核心三步管线 (Indexing → Retrieval → Generation) ​

2. 向量余弦相似度 (Cosine Similarity) 匹配原理 ​

将文本转化为高维向量 A 与 B,通过计算向量夹角余弦值判断语义相关度:

Similarity = cos(θ) = (A·B) / (‖A‖ × ‖B‖)

余弦值越接近 1.0,说明两段文本的语义相关度越高。

数值手算示例(降维到 3 维便于理解) ​

假设把两句文本编码成 3 维向量:

  • 查询 Q:“如何开启深色模式” → Q = (0.9, 0.2, 0.1)
  • 候选 A:“设置里切换深色主题” → A = (0.8, 0.3, 0.2)
  • 候选 B:“餐厅今日营业时间” → B = (0.1, 0.2, 0.9)

计算 Q 与 A 的相似度:

text
Q·A   = 0.9×0.8 + 0.2×0.3 + 0.1×0.2 = 0.72 + 0.06 + 0.02 = 0.80
|Q|   = sqrt(0.81 + 0.04 + 0.01)      = sqrt(0.86)          ≈ 0.927
|A|   = sqrt(0.64 + 0.09 + 0.04)      = sqrt(0.77)          ≈ 0.877
cos   = 0.80 / (0.927 × 0.877)        ≈ 0.80 / 0.813        ≈ 0.984  ← 语义接近

计算 Q 与 B 的相似度:

text
Q·B   = 0.9×0.1 + 0.2×0.2 + 0.1×0.9 = 0.09 + 0.04 + 0.09  = 0.22
|B|   = sqrt(0.01 + 0.04 + 0.81)      = sqrt(0.86)          ≈ 0.927
cos   = 0.22 / (0.927 × 0.927)        ≈ 0.22 / 0.859        ≈ 0.256  ← 语义很远
  • 观察重点:检索 Top-K 时按余弦值降序取前 K 条——上面例子中 A 的 0.984 远高于 B 的 0.256,A 会被选中作为上下文;真实场景向量有几百到几千维,但计算思想完全相同。归一化向量后,内积即可代替余弦(长度恒为 1),这也是很多向量库的实际做法。

3. 仓库内最小 RAG 样板是怎么收口的 ​

对应 Lab:tool-calling-rag-streaming-demo

当前仓库里没有单独接入真实向量库,也没有为了这个主题额外起一套 embedding 服务。最合理的最小落地,是在 Node demo 中保留 RAG 的工程骨架:

  1. 遍历 docs/ 读取 Markdown。
  2. 以标题块为单位做 chunkMarkdown()。
  3. 把 query 和 chunk 都转成关键词向量。
  4. 用 cosineSimilarity() 排序取 Top-K。
  5. 把检索结果带着来源路径回灌给最终回答。
js
function searchDocs(query, limit) {
  const files = walkMarkdownFiles(DOCS_DIR);
  const queryVector = buildKeywordVector(query);
  const matches = [];

  for (const filePath of files) {
    const content = fs.readFileSync(filePath, 'utf8');
    const chunks = chunkMarkdown(content);
    chunks.forEach((chunk, index) => {
      const score = cosineSimilarity(queryVector, buildKeywordVector(chunk));
      if (score > 0) {
        matches.push({ file: ..., chunkIndex: index, score, excerpt: ... });
      }
    });
  }

  return matches.sort((a, b) => b.score - a.score).slice(0, limit);
}
  • 可能执行顺序
    1. 用户问题进入检索层。
    2. 问题与文档 chunk 生成简化向量。
    3. 余弦相似度排序取 Top-K。
    4. 把命中片段拼进最终回答。
  • 可能输出
    text
    [tool_result] {
      "matches": [
        {
          "file": "docs/08-ai-engineering/04-rag-and-knowledge-retrieval.md",
          "chunkIndex": 6,
          "score": 0.44,
          "excerpt": "...Chunk Overlap..."
        }
      ]
    }
  • 预期现象:问仓库已有知识点时,会命中对应文档片段并带出处;问完全无关的问题时,应诚实返回“基于当前知识库无法回答”。
  • 观察重点:这套样板没有真实 embedding 模型,因此它验证的是RAG 工程分层,不是语义检索质量上限。

代码示例:文本切片与 Context 组装 ​

kotlin
fun buildRagPrompt(userQuery: String, retrievedChunks: List<String>): String {
    val contextText = retrievedChunks.joinToString("
---
")
    return """
        你是一个严谨的技术专家。请严格仅依据以下给出的参考上下文回答问题。
        如果参考上下文中没有包含问题的答案,请坦诚回答“基于现有资料无法回答”。
        
        【参考上下文】:
        $contextText
        
        【用户问题】:
        $userQuery
    """.trimIndent()
}

Android / Flutter / Web / Backend 对照 ​

维移动端本地 RAG (端侧)企业级服务端 RAG
向量数据库SQLite sqlite-vss / ObjectBox VectorMilvus / Qdrant / Pinecone
Embedding 模型ONNX Runtime (MobileBert / BGE-small)OpenAI text-embedding-3-small / Cohere
LLM 引擎On-Device LLM (ExecuTorch / MLC-LLM)Claude / GPT / Gemini 等服务端模型

常见场景 ​

1. 企业私有技术文档与 API 规范智能问答 ​

员工提问“接口鉴权失败返回 40102 怎么处理”,RAG 检索 API 规范文档中的 40102 错误码定义与排错步骤,注入 Prompt 后大模型精准生成排错指导。

常见误配、事故后果与排障 ​

1. 事故:Chunk 切片过大或没有重叠 (Overlap) 导致语义断裂 ​

  • 误配原因:按固定 2000 字简单切片,且未设置重叠区域(Overlap = 0)。
  • 后果:关键的句子或代码块恰好在切片边界被强行砍成两半,检索到的上下文缺失了上文或下文,导致 LLM 生成的回答发生严重的逻辑混乱。
  • 排障与修法:使用按 Markdown 标题语义切片(RecursiveCharacterTextSplitter),并设置 chunkSize = 500, chunkOverlap = 100。

2. 误配:把“接上向量库”当成 RAG 完成,忽略来源引用与未命中收口 ​

  • 后果:系统虽然“看起来在检索”,但回答不附出处,未命中时还继续硬答,用户无法判断可信度。
  • 排障与修法:返回 Top-K 时带 file/chunkIndex 等来源信息;无命中时显式返回“基于现有资料无法回答”,不要让生成层裸奔。

与相近概念对比 ​

技术方案成本实时性适合场景
RAG 检索增强低 (仅向量计算与少量 Token)高 (数据修改后索引立刻生效)企业私有文档、API 手册、频繁更新的知识
Fine-tuning 微调极高 (需要 GPU 训练)极低 (需重新训练模型)改变模型的输出风格、特定领域语法结构

对应实验 ​

Lab说明源码
tool-calling-rag-streaming-demo最小 RAG:chunk、Top-K、来源引用与 SSE 流式输出(与文首「代码索引」一致)server.js · client.js

复习检查题 ​

  1. 在 RAG 系统中,为什么在进行文本切片 (Chunking) 时需要设置“重叠区域 (Chunk Overlap)”?

    答:设置重叠区域(如切片大小 500 字符,重叠 100 字符)是为了防止关键的语义、句子或代名词引用在切片边界处被强行切断。重叠区域能保证前一个切片的末尾上下文保留在后一个切片的开头,从而维持了切片之间语义的连贯性,避免检索时丢失上下文导致 LLM 误读。

  2. 为什么说对于企业私有文档问答,RAG 比直接对大模型进行微调 (Fine-tuning) 更加高效?

    答:原因有两个:第一,成本与实时性,企业文档经常更新,RAG 只需要重新计算变更文档的向量并存入数据库,秒级生效;而微调需要昂贵的 GPU 重新训练模型,耗时极长;第二,防幻觉与可追溯性,RAG 可以在回答中明确给出引用的具体文档切片与页码(支持 Source Attribution),而微调把知识变成了模型的黑盒参数,依然容易产生幻觉且无法验证出处。

速记 ​

  • RAG 三步走:文档切片向量化 (Indexing) → 相似度检索 Top-K (Retrieval) → 注入 Prompt 生成 (Generation)。
  • 切片留重叠:使用语义切片并保留 Overlap,防止边界语义断裂。
  • RAG vs 微调:RAG 适合频繁更新的私有知识,微调适合改变模型语言风格。

站点构建时间:2026/8/24 23:43:17