> ## Documentation Index
> Fetch the complete documentation index at: https://opc-43d279b8.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# RAG (检索增强生成)

> 覆盖 RAG 的索引、检索、生成全链路，以及切片、重排、评估等高频面试问题。

> 我最早用 RAG 只盯着生成效果，查过几次坏例子才明白，瓶颈多半在检索那一段。切片、Embedding、召回、重排、生成，哪一环出问题得能分开看。

***

## Q1: RAG 的历史？为什么要有它？

**1. 2020年: Facebook AI 发表 RAG 论文**

2020 年，Facebook AI Research（现 Meta AI）的 Patrick Lewis 等人发表了论文《Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks》。核心思想：在 LLM 生成答案之前，先从外部知识库中检索相关文档，把检索到的文档作为上下文注入 Prompt，让 LLM 基于真实文档回答，而不是依赖训练时"记住"的知识。

**2. 致命痛点: LLM 的知识幻觉问题**

LLM 的训练数据有截止日期，且无法访问私有数据。当你问它最新的 API 文档或公司内部制度时，它要么编造不存在的信息（幻觉），要么说"我不知道"。更严重的是，LLM 编造的内容往往非常"自信"——格式规范、逻辑自洽，让人难以分辨真假。

**3. RAG 的核心价值: 让 LLM '查资料'而不是'背书'**

没有 RAG 的 LLM 就像一个闭卷考试的学生——全靠记忆，记不住就瞎编。引入 RAG 后，LLM 变成了开卷考试——先翻书找到相关章节，再根据章节内容回答问题。核心价值不是让 LLM 变得更"聪明"，而是让它变得更"可靠"。

**4. 2023-2024年: RAG 成为 AI 落地的标准范式**

随着 LangChain、LlamaIndex 等框架的成熟，RAG 从学术论文变成了工业标准。几乎所有企业级 AI 应用（知识库问答、客服系统、代码助手）都以 RAG 为核心架构。向量数据库（Milvus、Pinecone、Weaviate）也因此成为独立赛道。

**5. 2025年: 从 Naive RAG 到 Advanced RAG**

RAG 技术持续演进。从最初的"检索 + 生成"（Naive RAG），发展到加入查询改写、混合检索、重排序、多跳推理等优化策略的 Advanced RAG。更前沿的方向包括 Agentic RAG（Agent 自主决定何时检索、检索什么）和 Graph RAG（结合知识图谱的结构化检索）。

**RAG 解决的核心问题：**

```text theme={null}
问题：LLM 知识截止 + 无法访问私有数据 → 幻觉或拒绝回答

没有 RAG：LLM 根据训练数据猜测 → 编造不存在的信息（幻觉）

解决：先检索真实文档，再让 LLM 基于文档回答

有 RAG：检索相关文档 → 文档作为上下文 → LLM 基于文档生成答案（有据可查）
```

> [!NOTE]
>
> RAG 的本质是把 LLM 从"闭卷考试"变成"开卷考试"——先检索真实文档，再让 LLM 基于文档生成答案，从根本上解决知识幻觉问题。

> [card] 没有 RAG
>
> 你问 ChatGPT"我们公司的年假制度是什么？"，它要么说"我不知道"，要么编造一套听起来很合理的年假政策。你无法区分它是"真知道"还是"在编"。

> [card] 引入 RAG
>
> 你把公司的制度文档导入知识库。用户提问时，系统先检索到相关制度条目，再让 LLM 基于条目回答。答案有出处、可验证、不会编造。

> [!TIP]
>
> RAG 就像把闭卷考试变成开卷考试。闭卷考试（没有 RAG 的 LLM）全靠背书，背不下来就瞎编（幻觉）。开卷考试（有 RAG 的 LLM）允许翻书——先根据题目找到相关章节（检索），再根据章节内容组织答案（生成）。开卷考试的关键不是考生有多聪明（LLM 能力），而是翻书的速度和准确性（检索质量）。翻到错误的章节，再聪明的考生也答不对。

***

## Q2: RAG 的核心原理

RAG 的本质是**检索增强生成**，完整流程分为三个阶段：**索引（Indexing）→ 检索（Retrieval）→ 生成（Generation）**。Harrison Chase 在 LangChain 的 "RAG from Scratch" 系列中强调，RAG 的质量瓶颈不在 LLM 的生成能力，而在检索的准确性——"检索不到正确的文档，LLM 再强也答不对"。<sup>\[1]</sup>

```text theme={null}
┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│   索引阶段    │     │   检索阶段    │     │   生成阶段    │
│              │     │              │     │              │
│ 文档加载     │     │ 用户提问     │     │ 拼接 Prompt  │
│    ↓        │     │    ↓        │     │    ↓        │
│ 文档切片     │     │ 问题向量化   │     │ 调用 LLM     │
│    ↓        │     │    ↓        │     │    ↓        │
│ 向量化       │     │ 相似度检索   │     │ 生成答案     │
│    ↓        │     │    ↓        │     │              │
│ 存入向量库   │     │ 返回 Top-K   │     │              │
└──────────────┘     └──────────────┘     └──────────────┘
```

```python theme={null}
# ============================================================
# RAG 完整流程（带详细注释）
# 比喻：RAG 就像开卷考试——先翻书找相关章节（检索），
#       再根据章节内容组织答案（生成）
#
# ASCII 流程图：
# ┌──────────────┐     ┌──────────────┐     ┌──────────────┐
# │   索引阶段    │     │   检索阶段    │     │   生成阶段    │
# │ 文档 → 切片  │     │ 问题 → 向量  │     │ 拼接 Prompt  │
# │ → 向量 → 入库│     │ → 相似度检索 │     │ → 调用 LLM   │
# └──────────────┘     └──────────────┘     └──────────────┘
# ============================================================
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate

# ========== 索引阶段（离线，只需执行一次）==========

# 1. 文档加载：读取 PDF / Word / 网页等格式的文档
# 比喻：把图书馆里的书搬到桌上来
documents = load_documents("./company-docs/")  # 返回 Document 对象列表

# 2. 文档切片：把长文档拆成小块（每块 1000 字符，重叠 200 字符）
# 比喻：把一本书拆成一个个段落卡片
# 为什么重叠？——如果一个概念跨两个段落，重叠确保两个段落都能检索到
splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,      # 每个切片最大 1000 字符
    chunk_overlap=200,    # 相邻切片重叠 200 字符（避免语义在边界被截断）
    separators=["\n\n", "\n", "。", "！", "？", " "],  # 按优先级切分
)
chunks = splitter.split_documents(documents)  # 返回切片后的 Document 列表

# 3. 向量化 + 存入向量数据库
# 比喻：把每张段落卡片编码成一组数字（向量），存入"数字图书馆"
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vector_store = Milvus.from_documents(chunks, embeddings)  # 向量化并存入 Milvus

# ========== 检索阶段（在线，每次用户提问执行）==========

# 4. 用户提问 → 向量化 → 相似度检索 → 返回最相似的 Top-K 个切片
# 比喻：用户问了一个问题，馆员根据问题找最相关的几本书的章节
retriever = vector_store.as_retriever(search_kwargs={"k": 3})  # 返回最相似的 3 个切片
relevant_docs = retriever.invoke("什么是 Fiber 架构？")

# ========== 生成阶段（在线，每次用户提问执行）==========

# 5. 拼接 Prompt（问题 + 检索到的文档）→ 调用 LLM → 生成答案
# 比喻：把找到的章节复印好，交给专家（LLM），专家根据复印件回答问题
prompt = ChatPromptTemplate.from_template(
    """根据以下文档回答问题。如果文档中没有相关信息，说"我不确定"。

文档：
{context}

问题：{question}

回答："""
)

# 格式化检索到的文档
context = "\n---\n".join([doc.page_content for doc in relevant_docs])

# 调用 LLM 生成答案
llm = ChatOpenAI(model="gpt-4o")
answer = llm.invoke(prompt.format(context=context, question="什么是 Fiber 架构？"))

# --- 重要细节 ---
# 1. 质量瓶颈在检索，不在 LLM —— 检索不到正确文档，LLM 再强也答不对
# 2. 切片大小推荐 500-1500 字符，太大包含无关信息，太小语义不完整
# 3. 重叠比例推荐 10-20%（即 chunk_size=1000 时 overlap=100-200）
```

```javascript theme={null}
// RAG 的完整流程伪代码

// === 索引阶段 ===
// 1. 文档加载：读取 PDF / Word / 网页
const documents = await loadDocuments("./company-docs/");

// 2. 文档切片：把长文档拆成小块
const splitter = new RecursiveCharacterTextSplitter({
  chunkSize: 1000, // 每个切片 1000 字符
  chunkOverlap: 200, // 相邻切片重叠 200 字符
});
const chunks = await splitter.splitDocuments(documents);

// 3. 向量化 + 存入向量数据库
const embeddings = new OpenAIEmbeddings();
const vectorStore = await Milvus.fromDocuments(chunks, embeddings);

// === 检索阶段 ===
// 4. 用户提问 → 向量检索 → 返回最相似的 Top-K 个切片
const retriever = vectorStore.asRetriever({ k: 3 });
const relevantDocs = await retriever.invoke("什么是 Fiber 架构？");

// === 生成阶段 ===
// 5. 拼接 Prompt → 调用 LLM → 生成答案
const prompt = `根据以下文档回答问题。
如果文档中没有相关信息，说"我不确定"。

文档：
${relevantDocs.map((d) => d.pageContent).join("\n---\n")}

问题：什么是 Fiber 架构？`;

const answer = await llm.invoke(prompt);
```

> [!WARNING]
>
> RAG 的质量不在 LLM，而在**检索**。检索不到正确的文档，LLM 再强也答不对。
>
> * 切片策略决定文档能不能被检索到
> * Embedding 模型决定语义理解能力
> * 检索策略决定召回率和精确率
>
> 优化顺序：切片策略 > 检索策略 > Embedding 模型 > LLM 能力。

> [!TIP]
>
> RAG 就像开卷考试。闭卷考试（没有 RAG）要求你记住所有知识，记不住就只能瞎猜（幻觉）。开卷考试（有 RAG）允许你翻书——先根据题目找到相关的章节（检索），再根据章节内容回答问题（生成）。开卷考试的关键不是你有多聪明（LLM 的能力），而是你翻书的速度和准确性（检索质量）。

### 向量化原理（Embedding 模型选择）

向量化是把文本转为高维向量的过程。核心思想：**语义相似的文本，向量距离相近**。

```text theme={null}
文本 → Embedding 模型 → 向量（如 1536 维）

"什么是 Fiber？"  →  [0.023, -0.156, 0.892, ..., 0.041]
"Fiber 架构介绍"  →  [0.025, -0.153, 0.889, ..., 0.039]   ← 向量距离近（语义相似）
"今天天气不错"    →  [0.782, 0.341, -0.125, ..., 0.567]   ← 向量距离远（语义无关）
```

**主流 Embedding 模型对比：**

| 模型 | 维度 | 说明 | 成本 | 适用场景 |
| - | - | - | - | - |
| **text-embedding-3-large**（OpenAI） | 3072 | 支持 MRL 截断维度的旗舰模型 | \$0.13/1M tokens | 高质量英文检索 |
| **text-embedding-3-small**（OpenAI） | 1536 | 支持 MRL 截断，性价比高 | \$0.02/1M tokens | 性价比之选 |
| **BGE-large-zh**（BAAI） | 1024 | 中文开源模型，可自部署 | 免费（自部署） | 中文场景首选 |
| **Jina-embeddings-v3** | 1024 | 多语言、支持长文本 | \$0.02/1M tokens | 多语言场景 |
| **Cohere embed-v3** | 1024 | 支持 int8/binary 压缩，可配混合检索 | \$0.1/1M tokens | 多语言 + 混合检索 |

> MTEB 榜单更新频繁，排名会随新模型发布变化。选型时应以自己的业务语料构造评测集，而不是只看榜单名次。

### 检索策略（向量检索、BM25、混合检索）

| 检索策略 | 原理 | 优势 | 劣势 |
| - | - | - | - |
| **向量检索** | 计算问题向量与文档向量的余弦相似度 | 理解语义（"汽车"能匹配"轿车"） | 对精确关键词不敏感 |
| **BM25** | 基于词频（TF）和逆文档频率（IDF）的关键词匹配 | 精确匹配能力强 | 无法理解同义词 |
| **混合检索** | 向量检索 + BM25 加权融合 | 兼顾语义和关键词 | 实现复杂度更高 |

```javascript theme={null}
// 混合检索实现（LangChain）
import { EnsembleRetriever } from "langchain/retrievers/ensemble";
import { BM25Retriever } from "langchain/retrievers/bm25";

const vectorRetriever = vectorStore.asRetriever({ k: 10 });
const bm25Retriever = BM25Retriever.fromDocuments(docs, { k: 10 });

// 混合检索：向量 0.6 + BM25 0.4
const ensembleRetriever = new EnsembleRetriever({
  retrievers: [vectorRetriever, bm25Retriever],
  weights: [0.6, 0.4],
});
```

### 切片策略（语义切片、递归切片、按段落切片）

切片质量直接决定检索质量——切片太大，包含太多无关信息；切片太小，语义不完整。

| 切片策略 | 原理 | 适用场景 | 推荐程度 |
| - | - | - | - |
| **语义切片** | 用 Embedding 计算相邻句子相似度，在相似度低处切分 | 高质量检索 | 效果最好但最慢 |
| **递归切片** | 先按段落切，再按句子切，最后按字符切 | 通用场景 | 最常用的方案 |
| **按段落切** | 按 Markdown 标题或 HTML 标签切分 | 结构化文档 | 保留文档结构 |

```javascript theme={null}
// 递归切片（最通用）
import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";

const splitter = new RecursiveCharacterTextSplitter({
  chunkSize: 1000, // 推荐 500-1500
  chunkOverlap: 200, // 推荐 10-20%
  separators: ["\n\n", "\n", "。", "！", "？", "，", " "],
});

// 语义切片（效果最好但最慢）
import { SemanticChunker } from "langchain/text_splitter";

const semanticSplitter = new SemanticChunker(new OpenAIEmbeddings(), {
  breakpointThresholdType: "percentile",
  breakpointThresholdAmount: 75,
});
```

### 重排序（Reranking）原理

重排序是 RAG 的关键优化环节。流程：先粗检索 Top-K 个候选（如 20 个），再用 Reranker 模型精排，取 Top-N（如 3 个）给 LLM。

```text theme={null}
用户提问
   ↓
向量检索 Top-20（Bi-Encoder 粗筛：速度快，精度一般）
   ↓
Reranker 精排（Cross-Encoder 逐对计算相关性分数）
   ↓
取 Top-3（精筛：速度慢，精度高）
   ↓
拼接 Prompt → LLM 生成
```

向量检索用的是 **Bi-Encoder**（双编码器）——问题和文档分别编码为向量，再计算相似度。速度快，但"分别编码"会丢失问题和文档之间的细粒度交互信息。

Reranker 用的是 **Cross-Encoder**（交叉编码器）——把问题和文档拼接在一起，输入同一个模型计算相关性分数。能捕获更细粒度的语义交互，但速度慢（每对都要单独计算）。

```javascript theme={null}
// Reranker 实现
import { CohereRerank } from "@langchain/cohere";

const reranker = new CohereRerank({
  model: "rerank-v3.5",
  topN: 3,
});

// 先粗检索 20 个候选，再精排取 Top 3
const retriever = vectorStore.asRetriever({ k: 20 });
const retrievalChain = retriever.pipe(reranker);
```

***

## Q3: 如何自主搭建企业级 RAG

### 架构设计（文档加载 → 切片 → 向量化 → 存储 → 检索 → 生成）

企业级 RAG 的完整架构分为 7 层：

```text theme={null}
┌─────────────────────────────────────────────────────────────────┐
│                     企业级 RAG 架构                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  文档层：PDF / Word / 网页 / API 文档 / 数据库                   │
│    ↓                                                            │
│  加载层：DocumentLoader（Unstructured / LlamaParse）             │
│    ↓                                                            │
│  切片层：TextSplitter（递归 / 语义 / 按段落）                    │
│    ↓                                                            │
│  向量化层：Embedding Model（OpenAI / BGE / Jina）                │
│    ↓                                                            │
│  存储层：Vector Database（Milvus / Pinecone / Weaviate）        │
│    ↓                                                            │
│  检索层：Retriever（向量 / BM25 / 混合检索 + Reranker）          │
│    ↓                                                            │
│  生成层：LLM（GPT-4o / Claude / 开源模型）                      │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
```

### 向量数据库选型（Milvus、Pinecone、Weaviate、Chroma、Qdrant、Neo4j、ElasticSearch）

| 向量数据库 | 类型 | 部署方式 | 混合检索 | 特点 | 适用场景 |
| - | - | - | - | - | - |
| **Milvus** | 纯向量数据库 | 自部署 / Zilliz Cloud | 支持 | 高性能，支持亿级向量 | 大规模生产环境 |
| **Pinecone** | 纯向量数据库 | 全托管 | 支持 | 开箱即用，无需运维 | 快速原型、小团队 |
| **Weaviate** | 纯向量数据库 | 自部署 / Cloud | 原生支持 | 内置 BM25 + 向量混合检索 | 需要精确检索的场景 |
| **Chroma** | 纯向量数据库 | 嵌入式 | 不支持 | 轻量级，零配置 | 本地开发、小型项目 |
| **Qdrant** | 纯向量数据库 | 自部署 / Cloud | 支持 | Rust 实现，高性能 | 高性能生产环境 |
| **Neo4j** | 图数据库 | 自部署 / Aura Cloud | 支持 | 原生向量索引 + 图关系遍历 | 实体关系复杂的知识库 |
| **ElasticSearch** | 搜索引擎 | 自部署 / Elastic Cloud | 原生支持 | BM25 + 向量混合检索 | 全文搜索 + 语义检索 |
| **Easysearch** | 搜索引擎 | 自部署 / Cloud | 原生支持 | ES API 兼容，内置中文分词，Apache 2.0 | 国产替代、企业级搜索 + RAG |

**对比：纯向量数据库 vs 图数据库 vs 搜索引擎（ElasticSearch / Easysearch）**

| 维度 | 纯向量数据库（Milvus/Pinecone） | 图数据库（Neo4j） | 搜索引擎（ElasticSearch / Easysearch） |
| - | - | - | - |
| **核心能力** | 高维向量相似度检索 | 实体关系遍历 + 向量检索 | 全文搜索 + 向量检索 |
| **检索方式** | 余弦相似度 / 内积 | Cypher 图查询 + 向量索引 | BM25 + kNN 向量检索 |
| **优势** | 向量检索性能最强，延迟最低 | 捕获实体间的多跳关系 | 关键词精确匹配 + 语义检索 |
| **劣势** | 不擅长关系推理 | 向量检索性能不如专用向量库 | 向量检索精度不如专用向量库 |
| **典型场景** | 纯语义检索（文档 QA） | 知识图谱 + RAG（"谁的老板是谁"） | 混合检索（关键词 + 语义） |
| **学习成本** | 低 | 高（需要学 Cypher 查询语言） | 中（Query DSL 较复杂） |

### Embedding 模型选型（OpenAI、BGE、Jina、Cohere）

| 场景 | 推荐模型 | 理由 |
| - | - | - |
| **中文场景** | BGE-large-zh（BAAI） | MTEB 中文排名第一，开源免费 |
| **英文场景** | text-embedding-3-small（OpenAI） | 性价比最高，MTEB 英文 Top 20 |
| **多语言场景** | Jina-embeddings-v3 | 支持 89 种语言，成本低 |
| **高质量检索** | text-embedding-3-large（OpenAI） | MTEB 英文 Top 5，效果最好 |
| **本地部署** | BGE-large / E5-large | 开源免费，无需调用外部 API |

> [!TIP]
>
> 传统 RAG 的向量检索就像在一堆拼图碎片中找最像的那一块——靠"长得像"（语义相似度）来匹配，但拼图碎片之间的连接关系（谁和谁相邻）它不知道。Graph RAG（Neo4j）就像社交网络——不仅能找到"张三"这个人（向量检索），还能顺着关系链找到"张三的同事用的技术栈的相关文档"（图遍历）。当问题涉及"谁"、"什么关系"、"哪些相关"时，图数据库远优于纯向量检索。

### Neo4j 在 RAG 中的应用

**什么是图数据库？**

传统数据库（关系型 / 向量）存储的是"扁平"的数据——每条记录独立存在。图数据库存储的是**实体（Node）和关系（Edge）**——数据之间的连接本身就是一等公民。

```text theme={null}
传统数据库：
┌──────────┐  ┌──────────┐  ┌──────────┐
│ 张三      │  │ 字节跳动  │  │ React    │
│ 工程师    │  │ 公司     │  │ 技术     │
└──────────┘  └──────────┘  └──────────┘
（三张独立的表，关系靠外键关联）

图数据库：
  张三 ──[就职于]──→ 字节跳动
  张三 ──[精通]──→ React
  字节跳动 ──[使用]──→ React
（实体和关系天然存储在一起，查询"谁在用 React 的公司工作"只需一步遍历）
```

**Neo4j 的向量检索能力：**

Neo4j 从 5.x 版本开始原生支持向量索引。可以在节点上存储 Embedding 向量，并通过向量相似度检索。这意味着你可以在一个数据库中同时做**图关系遍历**和**语义向量检索**。

```javascript theme={null}
// Neo4j 向量索引创建（Cypher 语句）
// 1. 创建向量索引
CREATE VECTOR INDEX document_embeddings IF NOT EXISTS
FOR (d:Document)
ON (d.embedding)
OPTIONS {
  indexConfig: {
    `vector.dimensions`: 1536,
    `vector.similarity_function`: 'cosine'
  }
}

// 2. 存储文档时同时存储 Embedding
CREATE (d:Document {
  content: 'Fiber 是 React 16 引入的协调算法',
  embedding: [0.023, -0.156, 0.892, ...]  // 1536 维向量
})

// 3. 向量检索：找到语义最相似的文档
CALL db.index.vector.queryNodes(
  'document_embeddings',
  5,  // Top-K
  [0.025, -0.153, 0.889, ...]  // 问题的 Embedding 向量
) YIELD node, score
RETURN node.content, score
ORDER BY score DESC
```

**知识图谱 + RAG 的结合（Graph RAG）：**

Graph RAG 是 2024-2025 年的前沿方向。核心思想：先用 LLM 从文档中抽取实体和关系，构建知识图谱，再结合图遍历 + 向量检索做 RAG。

```text theme={null}
传统 RAG：
用户问题 → 向量检索文档切片 → LLM 生成答案
（只能找到"语义相似"的切片，无法推理实体关系）

Graph RAG：
用户问题
   ↓
┌─────────────────────────────────────────────┐
│ ① 向量检索：找到语义相关的实体节点            │
│ ② 图遍历：沿关系边找到关联实体（多跳推理）    │
│ ③ 合并：图遍历结果 + 向量检索结果             │
└─────────────────────────────────────────────┘
   ↓
LLM 基于结构化的实体关系 + 文档上下文生成答案
```

```javascript theme={null}
// Graph RAG 查询示例：找到"张三精通的技术栈的所有文档"
// 传统 RAG 无法回答这种需要多跳推理的问题

// Step 1: 向量检索找到"张三"相关的实体节点
// Step 2: 图遍历找到张三精通的所有技术
// Step 3: 再遍历找到这些技术的文档
// Step 4: 合并结果作为 LLM 上下文

// Cypher 查询：两跳推理
MATCH (p:Person {name: '张三'})-[:精通]->(t:Tech)<-[:关于]-(d:Document)
RETURN d.content, t.name
```

**适用场景：** 实体关系复杂的知识库——如企业内部的人员-项目-技术栈关系、法律条文的引用链、医学知识的症状-疾病-药物关系。当用户的问题涉及"谁"、"什么关系"、"哪些相关"等需要关系推理的场景时，Graph RAG 远优于传统向量检索。

### ElasticSearch 在 RAG 中的应用

**ElasticSearch 的向量检索能力：**

ElasticSearch 从 8.0 版本开始原生支持 kNN 向量检索（dense\_vector 字段类型）。这意味着你可以在 ElasticSearch 中同时做 **BM25 关键词检索**和**向量语义检索**，无需引入额外的向量数据库。

```javascript theme={null}
// ElasticSearch 向量索引创建
// PUT /rag-documents
{
  "mappings": {
    "properties": {
      "content": { "type": "text" },           // BM25 检索字段
      "embedding": {                            // 向量检索字段
        "type": "dense_vector",
        "dims": 1536,
        "index": true,
        "similarity": "cosine"
      }
    }
  }
}

// 存储文档
// PUT /rag-documents/_doc/1
{
  "content": "Fiber 是 React 16 引入的协调算法，用于实现增量渲染",
  "embedding": [0.023, -0.156, 0.892, ..., 0.041]
}
```

**BM25 + 向量混合检索：**

ElasticSearch 的最大优势是**原生支持 BM25 + 向量混合检索**，不需要额外的 EnsembleRetriever 或权重调优——它在同一个请求中同时执行两种检索并融合结果。

```javascript theme={null}
// ElasticSearch 混合检索查询
// POST /rag-documents/_search
{
  "retriever": {
    "rrf": {   // Reciprocal Rank Fusion：融合 BM25 和向量检索的结果
      "retrievers": [
        {
          "standard": {    // BM25 关键词检索
            "query": {
              "match": {
                "content": "React Fiber 架构"
              }
            }
          }
        },
        {
          "knn": {         // 向量语义检索
            "field": "embedding",
            "query_vector": [0.025, -0.153, 0.889, ...],
            "k": 10,
            "num_candidates": 100
          }
        }
      ],
      "rank_window_size": 50,
      "rank_constant": 20
    }
  }
}
```

```javascript theme={null}
// LangChain 集成 ElasticSearch 混合检索
import { ElasticVectorSearch } from "@langchain/community/vectorstores/elasticsearch";

const vectorStore = new ElasticVectorSearch(new OpenAIEmbeddings(), {
  client: elasticClient,
  indexName: "rag-documents",
});

// ElasticSearch 自动处理 BM25 + 向量混合检索
const retriever = vectorStore.asRetriever({
  k: 5,
  searchType: "similarity", // 或 'mmr'（最大边际相关性）
});
```

**适用场景：** 全文搜索 + 语义检索——当你的知识库同时需要精确关键词匹配（如搜索特定的 API 名称、错误代码、配置参数）和语义理解（如"怎么让页面快一点"匹配"前端性能优化策略"）时，ElasticSearch 是最佳选择。典型场景：技术文档搜索、代码搜索引擎、客服知识库。

### Easysearch：Elasticsearch 的国产替代

2024 年，Elastic 将 Elasticsearch 的开源协议从 Apache 2.0 改为 SSPL/Elastic License，许多国内企业面临合规风险。极限科技（INFINI Labs）推出的 Easysearch 应运而生——它是一个完全兼容 Elasticsearch API 的国产分布式搜索与数据分析引擎，同时集成了向量搜索、混合搜索等 AI-native 能力。<sup>\[5]</sup>

**Easysearch 的核心优势：**

```text theme={null}
┌─────────────────────────────────────────────────────┐
│              Easysearch vs ElasticSearch              │
├──────────────┬──────────────────┬────────────────────┤
│   维度        │   ElasticSearch  │   Easysearch       │
├──────────────┼──────────────────┼────────────────────┤
│ 开源协议      │ SSPL/弹性许可    │ Apache 2.0         │
│ API 兼容性    │ 原生             │ 完全兼容 ES API     │
│ 中文分词      │ 需装 IK 插件     │ 内置优化中文分词     │
│ 运维复杂度    │ 高（JVM 调优）   │ 低（开箱即用）      │
│ 向量检索      │ 8.0+ 支持 kNN   │ 原生支持 dense/sparse│
│ 混合检索      │ RRF 融合         │ RRF 融合（兼容）    │
│ 国内合规      │ 有协议风险       │ 完全合规            │
└──────────────┴──────────────────┴────────────────────┘
```

Easysearch 的三大差异化能力：

1. **开箱即用**：内置中文分词优化（无需额外安装 IK 分词插件），降低部署和运维门槛。对于国内企业来说，这意味着从安装到生产可用的时间大幅缩短。

2. **AI-native 搜索**：原生支持 dense vector（稠密向量）和 sparse vector（稀疏向量），内置 HNSW 索引算法，支持 kNN 向量检索和 BM25 + 向量混合检索（RRF 融合排序）。这些能力与 ElasticSearch 8.x 对齐，但部署更简单。

3. **国产合规**：Apache 2.0 开源协议，无 SSPL/Elastic License 的合规风险，适合政府、金融、国企等对软件供应链安全有严格要求的行业。

**适用场景：** 企业级搜索（知识库问答、文档检索、代码搜索）、RAG 向量存储、需要国产化替代的场景。对于已有 ElasticSearch 经验的团队，迁移到 Easysearch 几乎零成本——API 完全兼容，只需修改连接配置。

### LangChain + Easysearch 集成

Easysearch 兼容 OpenSearch/ElasticSearch API，因此可以直接使用 LangChain 的 `OpenSearchVectorSearch` 类进行集成，无需额外适配。<sup>\[5]</sup>

**基础连接与向量检索：**

```python theme={null}
# LangChain + Easysearch 向量检索
from langchain_community.vectorstores import OpenSearchVectorSearch
from langchain_openai import OpenAIEmbeddings

# 连接 Easysearch（API 兼容 OpenSearch）
vector_store = OpenSearchVectorSearch(
    opensearch_url="https://localhost:9200",
    index_name="rag-documents",
    embedding_function=OpenAIEmbeddings(model="text-embedding-3-small"),
    http_auth=("admin", "admin"),
    use_ssl=True,
    verify_certs=False,
    engine="nmslib",  # HNSW 向量索引引擎
)

# 存入文档
from langchain.text_splitter import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
chunks = splitter.split_documents(documents)
vector_store.add_documents(chunks)

# 向量检索
results = vector_store.similarity_search("什么是 Fiber 架构？", k=5)
```

**混合检索（向量 + BM25）配置：**

Easysearch 原生支持 BM25 + 向量混合检索。通过 RRF（Reciprocal Rank Fusion）融合两种检索结果，兼顾语义理解和关键词精确匹配。

```python theme={null}
# LangChain + Easysearch 混合检索（向量 + BM25）
from langchain_community.vectorstores import OpenSearchVectorSearch

hybrid_store = OpenSearchVectorSearch(
    opensearch_url="https://localhost:9200",
    index_name="rag-documents",
    embedding_function=OpenAIEmbeddings(model="text-embedding-3-small"),
    http_auth=("admin", "admin"),
    use_ssl=True,
    verify_certs=False,
    # 混合检索配置
    engine="nmslib",
    space_type="cosinesimil",
    hybrid_search=True,       # 开启混合检索
    vector_field="embedding",  # 向量字段名
    text_field="content",      # BM25 文本字段名
)

# 混合检索：向量语义 + BM25 关键词
results = hybrid_store.similarity_search(
    query="React Fiber 架构的增量渲染原理",
    k=5,
    search_type="hybrid",  # 混合检索模式
)

# 也可以用 as_retriever 接入 LangChain RAG Chain
retriever = hybrid_store.as_retriever(
    search_type="similarity_score_threshold",
    search_kwargs={"score_threshold": 0.7, "k": 5},
)
```

**完整的 RAG Chain（LangChain + Easysearch）：**

```python theme={null}
# 完整的 RAG Chain：Easysearch + LangChain
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser

# 1. Retriever（混合检索）
retriever = hybrid_store.as_retriever(search_kwargs={"k": 5})

# 2. Prompt 模板
prompt = ChatPromptTemplate.from_template(
    """根据以下上下文回答问题。如果上下文中没有相关信息，说"我不确定"。

上下文：
{context}

问题：{question}

回答："""
)

# 3. 格式化函数
def format_docs(docs):
    return "\n---\n".join(doc.page_content for doc in docs)

# 4. 组装 RAG Chain
rag_chain = (
    {"context": retriever | format_docs, "question": RunnablePassthrough()}
    | prompt
    | ChatOpenAI(model="gpt-4o")
    | StrOutputParser()
)

# 5. 调用
answer = rag_chain.invoke("什么是 Fiber 架构？")
print(answer)
```

### Easysearch 与 LangChain 的兼容性踩坑

Easysearch 兼容 Elasticsearch API，但 LangChain 的集成方式有一个容易踩的坑。ES 8.x 官方 Python 客户端（`elasticsearch` 包）会检测服务端是否为真正的 Elasticsearch——它通过检查服务端返回的 `X-elastic-product` 响应头来判断。Easysearch 虽然完全兼容 ES API，但不是 Elasticsearch，不会返回这个头，因此 ES 8.x 客户端会直接拒绝连接并抛出 `UnsupportedProductError`。

LangChain 的 `ElasticsearchStore`（`langchain-elasticsearch` 包）底层依赖该客户端，所以用 `ElasticsearchStore` 连接 Easysearch 会失败。正确的方式是使用 `OpenSearchVectorSearch`——因为 Easysearch 同时兼容 OpenSearch API，而 LangChain 的 `OpenSearchVectorSearch` 类底层使用 `opensearch-py` 客户端，不会做产品检测。

```python theme={null}
# ❌ 错误：用 ElasticsearchStore 连接 Easysearch
# ES 8.x 客户端会检测 X-elastic-product 头，Easysearch 不返回该头，直接拒绝连接
from langchain_elasticsearch import ElasticsearchStore
store = ElasticsearchStore(
    es_url="https://localhost:9200",
    index_name="my_index",
    embedding=embeddings,
)
# 抛出 elasticsearch.UnsupportedProductError

# ✅ 正确：用 OpenSearchVectorSearch 连接 Easysearch
# opensearch-py 客户端不做产品检测，兼容 Easysearch
from langchain_community.vectorstores import OpenSearchVectorSearch
store = OpenSearchVectorSearch(
    opensearch_url="https://localhost:9200",
    index_name="my_index",
    embedding_function=embeddings,
    http_auth=("admin", "admin"),
    use_ssl=True,
    verify_certs=False,
)
```

> [!WARNING]
>
> ES 8.x 客户端在每个请求的响应中检查 `X-elastic-product: Elasticsearch` 头。如果服务端不返回这个头（如 Easysearch、OpenSearch），客户端会抛出 `UnsupportedProductError` 并拒绝所有后续请求。这个检测逻辑硬编码在客户端中，无法通过配置关闭。解决方式只有两条路：1) 使用不检测产品的客户端（如 `opensearch-py`）；2) 在服务端配置返回该头（部分兼容方案支持）。

> [!NOTE]
>
> Easysearch 是 ElasticSearch 的国产替代——兼容 ES API、内置中文分词、Apache 2.0 开源协议，适合对国产合规有要求的企业。LangChain 集成 Easysearch 时必须用 `OpenSearchVectorSearch` 而非 `ElasticsearchStore`——因为 ES 8.x 客户端会检测服务端产品标识，Easysearch 不通过检测会被拒绝连接。

### 混合检索策略（向量检索 vs BM25 vs 混合检索）

混合检索是 RAG 质量优化中投入产出比最高的策略。核心思想：**向量检索捕获语义，BM25 捕获关键词，两者互补**。

```text theme={null}
向量检索的盲区：
用户搜索 "React 18 concurrent 模式"
→ 向量检索可能返回 "Vue 3 响应式系统"（语义相近，但关键词完全不同）

BM25 的盲区：
用户搜索 "怎么优化首屏加载"
→ BM25 无法匹配 "如何提升 FCP 指标"（关键词不同，但语义一致）

混合检索 = 向量检索（语义） + BM25（关键词） → 两者互补
```

**混合检索的三种实现方式：**

| 实现方式 | 原理 | 代表产品 | 适用场景 |
| - | - | - | - |
| **应用层融合** | 分别调用向量库和 BM25，应用层合并结果 | LangChain EnsembleRetriever | 任意向量数据库 + 任意 BM25 引擎 |
| **引擎原生融合** | 数据库内部同时执行两种检索并融合 | ElasticSearch RRF / Easysearch RRF / Weaviate | 一体化部署，性能最优 |
| **向量数据库内建** | 向量数据库内置 BM25 或全文检索 | Milvus 2.4+ Hybrid Search | 不想引入额外引擎 |

**Reciprocal Rank Fusion (RRF) 算法：**

RRF 是混合检索中最常用的融合算法。核心思想：不关心每个检索器返回的绝对分数，只关心**排名**。两个检索器都排在前面的文档，最终排名更高。

```text theme={null}
RRF 公式：
RRF_score(d) = Σ 1 / (k + rank_i(d))

其中：
- k = 60（常数，避免排名靠前的文档权重过大）
- rank_i(d) = 文档 d 在第 i 个检索器中的排名

示例：
文档 A：向量检索排名 #1，BM25 排名 #3
  RRF = 1/(60+1) + 1/(60+3) = 0.0164 + 0.0159 = 0.0323

文档 B：向量检索排名 #5，BM25 排名 #1
  RRF = 1/(60+5) + 1/(60+1) = 0.0154 + 0.0164 = 0.0318

文档 C：向量检索排名 #2，BM25 排名 #10
  RRF = 1/(60+2) + 1/(60+10) = 0.0161 + 0.0143 = 0.0304

→ 文档 A 排名最高（两种检索器都认为它相关）
```

```javascript theme={null}
// RRF 算法实现
function reciprocalRankFusion(
  rankedLists, // 多个检索器的排序结果
  k = 60, // 常数，通常取 60
) {
  const scores = new Map();

  for (const rankedList of rankedLists) {
    rankedList.forEach((doc, index) => {
      const rank = index + 1; // 排名从 1 开始
      const prevScore = scores.get(doc.id) || 0;
      scores.set(doc.id, prevScore + 1 / (k + rank));
    });
  }

  // 按 RRF 分数降序排列
  return [...scores.entries()]
    .sort((a, b) => b[1] - a[1])
    .map(([id, score]) => ({ id, score }));
}

// 使用示例
const vectorResults = ["docA", "docC", "docB"]; // 向量检索排名
const bm25Results = ["docB", "docA", "docD"]; // BM25 排名

const fusedResults = reciprocalRankFusion([vectorResults, bm25Results]);
// → [{ id: 'docA', score: 0.0323 }, { id: 'docB', score: 0.0318 }, ...]
```

```javascript theme={null}
// LangChain 混合检索 + RRF 完整实现
import { EnsembleRetriever } from "langchain/retrievers/ensemble";
import { BM25Retriever } from "langchain/retrievers/bm25";

// 1. 创建向量检索器
const vectorRetriever = vectorStore.asRetriever({ k: 10 });

// 2. 创建 BM25 检索器
const bm25Retriever = BM25Retriever.fromDocuments(docs, { k: 10 });

// 3. 混合检索：向量 0.6 + BM25 0.4
const ensembleRetriever = new EnsembleRetriever({
  retrievers: [vectorRetriever, bm25Retriever],
  weights: [0.6, 0.4], // 向量权重更高，因为语义理解更通用
});

// 4. 检索 → Reranker 精排 → 生成
const results = await ensembleRetriever.invoke("什么是 Fiber 架构？");
```

> [!NOTE]
>
> 混合检索的核心是"语义 + 关键词"互补——向量检索理解用户意图（"怎么让页面快一点"匹配"性能优化"），BM25 精确匹配关键词（"React 18"不会匹配到"Vue 3"）。实际项目中，ElasticSearch 的 RRF 原生融合是最省心的方案，LangChain 的 EnsembleRetriever 是最灵活的方案。

### 质量优化（查询改写、HyDE、多路召回、重排序）

RAG 质量优化的投入产出比排序：

```text theme={null}
1. 切片策略（投入低，收益高）
   └── 调整 chunkSize 和 overlap 就能显著提升效果

2. 混合检索 + Reranker（投入中，收益高）
   └── 通常能提升 20-30% 的准确率

3. 查询改写 / HyDE（投入中，收益中）
   └── 对模糊问题效果明显，对精确问题提升有限

4. Embedding 模型升级（投入高，收益中）
   └── 从 small 升级到 large，效果提升有限但成本翻倍

5. Fine-tuning Embedding（投入最高，收益看场景）
   └── 只有当通用模型在特定领域表现差时才考虑
```

> [!NOTE]
>
> 企业级 RAG 的搭建核心是"分层设计"——文档层、切片层、向量化层、存储层、检索层、生成层各自独立，可单独优化和替换。向量数据库选型看规模和场景：纯语义检索选专用向量库（Milvus/Pinecone），需要关系推理选 Neo4j（Graph RAG），需要关键词 + 语义混合检索选 ElasticSearch 或 Easysearch（国产替代，兼容 ES API，内置中文分词）。Embedding 模型选型看语言，质量优化优先调切片策略和混合检索策略。

***

## Q4: RAG 的高级优化策略

> [!TIP]
>
> RAG 的高级优化就像做搜索引擎 SEO。查询改写是把用户的口语化问题（"怎么让页面快一点"）改写成更适合检索的专业表述（"前端页面加载性能优化策略，减少 FCP、LCP"）。HyDE 是先写一篇假的参考文章，再用这篇文章去搜索——假文章虽然不准确，但用词和真文章更接近，搜索效果更好。多路召回是同一个问题问不同的人（多个子问题），汇总所有人的答案，覆盖面更广。重排序是先粗筛出 20 个候选，再精挑细选出最相关的 3 个。

### 查询改写（Query Rewriting）

用 LLM 改写用户问题，提高检索命中率。用户的问题通常是口语化的短句，和文档的书面表述差异大。查询改写把口语化问题转为更适合检索的形式。

```javascript theme={null}
import { ChatOpenAI } from "@langchain/openai";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { StringOutputParser } from "@langchain/core/output_parsers";

const rewritePrompt = ChatPromptTemplate.fromTemplate(
  `你是一个搜索查询改写专家。请把以下用户问题改写为更适合向量检索的形式，
  保持原意但增加关键词。

  用户问题：{question}
  改写后的查询：`,
);

const rewriter =
  rewritePrompt |
  new ChatOpenAI({ model: "gpt-4o-mini" }) |
  new StringOutputParser();

// "怎么让页面快一点" → "前端页面加载性能优化策略，减少 FCP、LCP，降低 CLS"
const rewrittenQuery = await rewriter.invoke({ question: "怎么让页面快一点" });
```

### HyDE（Hypothetical Document Embeddings）

让 LLM 先生成一个"假设性答案"，再用这个答案去检索。假设性答案虽然可能不准确，但它的语义和真实答案更接近，检索效果更好。

```text theme={null}
传统 RAG：用户问题 → 向量检索 → 生成答案

HyDE：    用户问题 → LLM 生成假设性答案 → 用假设性答案向量检索 → 生成最终答案
```

```javascript theme={null}
// HyDE 实现
const hydePrompt = ChatPromptTemplate.fromTemplate(
  `请根据以下问题，写一段可能的答案（不需要准确，只需要符合问题的语义）。

  问题：{question}
  假设性答案：`,
);

const hydeChain =
  hydePrompt |
  new ChatOpenAI({ model: "gpt-4o-mini" }) |
  new StringOutputParser();

async function hydeRetrieve(question) {
  // 1. 生成假设性答案
  const hypotheticalAnswer = await hydeChain.invoke({ question });
  // 2. 用假设性答案的向量检索
  const docs = await retriever.invoke(hypotheticalAnswer);
  return docs;
}
```

### 多路召回（Multi-Route Retrieval）

用 LLM 把一个问题拆成多个子问题，分别检索，合并去重。覆盖更多角度，提高召回率。

```javascript theme={null}
import { MultiQueryRetriever } from "langchain/retrievers/multi_query";

const multiQueryRetriever = MultiQueryRetriever.fromLLM({
  llm: new ChatOpenAI({ model: "gpt-4o-mini" }),
  retriever: vectorStore.asRetriever({ k: 5 }),
  // LLM 会自动生成 3-5 个不同角度的查询
});

// "React 性能优化" → 拆成：
//   "React 渲染性能优化策略"
//   "React useMemo useCallback 使用场景"
//   "React 虚拟列表实现方案"
// 分别检索，合并去重
```

### 重排序（Reranking）

先粗检索 Top-K 个候选，再用 Reranker（Cross-Encoder）精排取 Top-N。详见 Q2 的重排序原理部分。

### 上下文压缩（Context Compression）

检索到的文档切片可能包含大量无关信息。上下文压缩用 LLM 提取切片中与问题相关的部分，减少噪声，节省 Token。

```javascript theme={null}
import { ContextualCompressionRetriever } from "langchain/retrievers/contextual_compression";
import { LLMChainExtractor } from "langchain/retrievers/document_compressors/chain_extract";

const compressor = LLMChainExtractor.fromLLM(
  new ChatOpenAI({ model: "gpt-4o-mini" }),
);

const compressionRetriever = new ContextualCompressionRetriever({
  baseCompressor: compressor,
  baseRetriever: vectorStore.asRetriever({ k: 10 }),
});

// 检索到的 10 个切片会被压缩为只包含相关内容的精简版本
```

### 自适应检索（Adaptive Retrieval）

让 LLM 自主决定是否需要检索、检索什么、检索几次。这是 Agentic RAG 的核心思想。

```text theme={null}
传统 RAG：所有问题都检索 → 有些问题不需要检索（浪费资源）

自适应 RAG：
├── LLM 判断：这个问题需要检索吗？
│   ├── 不需要（常识问题）→ 直接回答
│   └── 需要（知识类问题）→ 检索
├── LLM 判断：检索到的信息够吗？
│   ├── 够了 → 生成答案
│   └── 不够 → 改写查询，再检索一次（多跳）
└── LLM 判断：答案可靠吗？
    ├── 可靠 → 返回答案
    └── 不可靠 → 标注"不确定"
```

> [!NOTE]
>
> RAG 高级优化的核心思路是"多阶段精炼"——查询改写提升检索命中率，多路召回扩大覆盖面，重排序提升精确度，上下文压缩减少噪声，自适应检索让 Agent 自主决策。实际项目中，混合检索 + Reranker 的投入产出比最高。

***

## Q5: LangChain + RAG 集成

> [!TIP]
>
> LangChain 的 RAG Chain 就像图书馆的参考咨询服务。你带着问题去咨询台（用户提问），馆员先去书架上找相关书籍（Retriever 检索），把找到的章节复印好（formatDocs 格式化），然后拿着复印件去找专家（LLM），专家根据复印件内容回答你的问题（生成答案）。整个流程用一条流水线（RunnableSequence）串联起来——你只需要提问，后面的找书、复印、问专家全自动完成。

### LangChain RAG Chain 设计

LangChain 的 RAG Chain 本质是一个 `RunnableSequence`：检索器（Retriever）+ 提示词模板（Prompt）+ LLM + 输出解析器（Parser）。

```javascript theme={null}
import {
  RunnableSequence,
  RunnablePassthrough,
} from "@langchain/core/runnables";
import { ChatOpenAI } from "@langchain/openai";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { StringOutputParser } from "@langchain/core/output_parsers";

// 1. 定义 Prompt 模板
const prompt = ChatPromptTemplate.fromTemplate(
  `根据以下上下文回答问题。如果上下文中没有相关信息，说"我不确定"。

上下文：
{context}

问题：{question}

回答：`,
);

// 2. 定义格式化函数
const formatDocs = (docs) => docs.map((d) => d.pageContent).join("\n---\n");

// 3. 组装 RAG Chain
const ragChain = RunnableSequence.from([
  {
    context: retriever.pipe(formatDocs), // 检索 + 格式化
    question: new RunnablePassthrough(), // 原样传递问题
  },
  prompt, // 拼接 Prompt
  new ChatOpenAI({ model: "gpt-4o" }), // 调用 LLM
  new StringOutputParser(), // 解析输出
]);

const answer = await ragChain.invoke("什么是 Fiber 架构？");
```

### Retriever 原理

Retriever 是 LangChain 的检索抽象。核心接口只有一个方法：`invoke(query) → Document[]`。所有检索器都实现这个接口，可以自由组合。

```text theme={null}
Retriever 接口：
┌─────────────────────────────────────────┐
│  invoke(query: string) → Document[]     │
└─────────────────────────────────────────┘

实现方式：
├── VectorStoreRetriever（向量检索）
├── BM25Retriever（关键词检索）
├── EnsembleRetriever（混合检索）
├── MultiQueryRetriever（多路召回）
└── ContextualCompressionRetriever（带 Reranker 的检索）
```

### Chain 类型（Stuff、MapReduce、Refine、Map-Rerank）

当检索到的文档太多，超出 LLM 上下文窗口时，需要用不同的 Chain 策略处理：

| Chain 类型 | 原理 | 优势 | 劣势 | 适用场景 |
| - | - | - | - | - |
| **Stuff** | 把所有文档拼接成一个 Prompt | 最简单，一次调用 | 文档太多会超出上下文窗口 | 文档少（\< 5 个） |
| **MapReduce** | 每个文档单独摘要，再合并摘要 | 支持大量文档 | 多次调用 LLM，成本高 | 文档多且需要全局视角 |
| **Refine** | 先用第一个文档生成答案，再用后续文档逐个修正 | 答案逐步精炼 | 后续文档的影响递减 | 需要逐步深入的场景 |
| **Map-Rerank** | 每个文档单独评分，选最高分的答案 | 忽略无关文档 | 可能丢失跨文档信息 | 有明确答案的场景 |

```javascript theme={null}
import { loadQAStuffChain, loadQAMapReduceChain } from "langchain/chains";

// Stuff Chain：所有文档拼接成一个 Prompt（最常用）
const stuffChain = loadQAStuffChain(llm);
const result = await stuffChain.call({
  input_documents: docs.slice(0, 3),
  question: "什么是 Fiber？",
});

// MapReduce Chain：每个文档单独摘要，再合并
const mapReduceChain = loadQAMapReduceChain(llm);
const summary = await mapReduceChain.call({
  input_documents: docs,
  question: "总结所有文档的核心观点",
});
```

### 自定义 RAG Pipeline

实际项目中，基础的 RAG Chain 往往不够用。你需要自定义 Pipeline，加入查询改写、混合检索、Reranker、答案验证等环节。

```javascript theme={null}
import { RunnableSequence } from "@langchain/core/runnables";

// 自定义 RAG Pipeline：查询改写 → 混合检索 → Reranker → 生成
const customRAGPipeline = RunnableSequence.from([
  // Step 1: 查询改写
  async (question) => {
    const rewritten = await rewriter.invoke({ question });
    return { original: question, rewritten };
  },

  // Step 2: 混合检索（向量 + BM25）
  async ({ original, rewritten }) => {
    const docs = await ensembleRetriever.invoke(rewritten);
    return { question: original, docs };
  },

  // Step 3: Reranker 精排
  async ({ question, docs }) => {
    const rerankedDocs = await reranker.invoke({
      query: question,
      documents: docs,
    });
    return { question, docs: rerankedDocs.slice(0, 3) };
  },

  // Step 4: 生成答案
  {
    answer: ragChain,
    context: (input) => input.docs,
  },
]);
```

> [!WARNING]
>
> 90% 的场景用 Stuff Chain 就够了——限制检索数量为 3-5 个切片，每个切片 1000 字符，总共 3000-5000 字符，远在 LLM 上下文窗口之内。只有当文档量特别大（如 100+ 篇文档的全局摘要）时才用 MapReduce。

***

## Q6: n8n 和 Dify 是什么？

n8n 和 Dify 是当前主流的可视化 AI 工作流与 Agent 编排构建平台。n8n 偏向节点自动化，Dify 偏向大模型应用全栈开发。两者可以组合使用——Dify 提供 RAG 和 Agent 能力，n8n 负责把 Dify 的 AI 能力接入到更大的自动化工作流中。

> [!TIP]
>
> n8n 就像工厂里的通用自动化流水线——把各种设备（API）用传送带（节点连接）串起来，收到订单（Webhook 触发）就自动执行：检查库存→调用物流→发送通知。AI 只是流水线上的一个工位。Dify 就像专门的 AI 车间——内置了文档上架系统（RAG 知识库）、智能机械臂（Agent）、操作手册管理（Prompt 模板），专门生产 AI 产品。两者可以配合：Dify 车间生产出 AI 产品，n8n 流水线负责把产品配送到各个渠道。

### n8n：开源工作流自动化工具

n8n（读作 "n-eight-n"）是一个开源的工作流自动化工具，类似 Zapier，但可以自托管。它的核心是通过可视化的节点编辑器，把不同的服务和 API 连接起来，构建自动化工作流。

```text theme={null}
n8n 的核心概念：

┌─────────┐     ┌─────────┐     ┌─────────┐     ┌─────────┐
│ 触发器   │ → │ 节点 A  │ → │ 节点 B  │ → │ 节点 C  │
│ Trigger  │     │ HTTP    │     │ IF 条件  │     │ Email   │
└─────────┘     └─────────┘     └─────────┘     └─────────┘

- 触发器（Trigger）：工作流的启动条件（定时、Webhook、手动）
- 节点（Node）：每个节点执行一个操作（调用 API、处理数据、发送邮件）
- 连接（Connection）：节点之间的数据流转
```

**n8n 与 LLM 集成：**

n8n 通过 HTTP 请求节点调用 OpenAI API，实现 AI 工作流自动化。

```text theme={null}
典型场景：
├── 定时抓取网页 → 调用 LLM 摘要 → 发送到 Slack
├── 收到邮件 → LLM 分类 → 路由到不同处理流程
├── 数据库变更 → LLM 生成报告 → 发送到飞书
└── GitHub Issue → LLM 自动回复 → 标签分类
```

**适用场景：** 自动化工作流、数据同步、定时任务、API 编排、跨系统集成。

### Dify：开源 LLM 应用开发平台

Dify 是一个开源的 LLM 应用开发平台，提供可视化的界面来构建 RAG 应用和 Agent。它的核心价值是**让不懂代码的人也能搭建 RAG 知识库和 AI 应用**。

```text theme={null}
Dify 的核心概念：

┌─────────────────────────────────────────────┐
│                  Dify 平台                    │
├───────────┬───────────┬─────────────────────┤
│  知识库    │   应用     │      工作流          │
│           │           │                     │
│ 文档上传   │ 聊天助手   │ 可视化编排           │
│ 自动切片   │ 文本生成   │ 条件分支             │
│ 向量化     │ Agent     │ 循环迭代             │
│ 检索配置   │ 工具调用   │ 多步骤 LLM 调用      │
└───────────┴───────────┴─────────────────────┘
```

**Dify 的 RAG 能力：**

```text theme={null}
文档上传 → 自动切片（支持自定义切片策略）
    → 向量化（支持多种 Embedding 模型）
    → 存入内置向量数据库
    → 检索配置（Top-K、相似度阈值、Reranker）
    → 应用集成（聊天助手、Agent、工作流）
```

**Dify 的 Agent 能力：** 工具调用（Function Calling）、多轮对话、上下文管理、Agent 推理循环。

**适用场景：** 快速搭建 RAG 应用、企业知识库、AI 客服、内部文档问答。

### n8n vs Dify 对比

| 维度 | n8n | Dify |
| - | - | - |
| **核心定位** | 工作流自动化（通用） | LLM 应用开发平台（AI 专项） |
| **RAG 能力** | 需要自行集成（HTTP 调用向量数据库） | 内置完整 RAG 流程 |
| **Agent 能力** | 需要自行编排 | 内置 Agent 推理循环 |
| **可视化** | 工作流节点编辑器 | 应用配置 + 工作流编排 |
| **目标用户** | 运维/开发人员 | 产品经理/业务人员/开发者 |
| **适用场景** | 跨系统自动化、定时任务 | AI 应用快速搭建、企业知识库 |

> [!NOTE]
>
> n8n 是"通用的自动化胶水"——把各种 API 和服务连接起来，AI 只是其中一个节点。Dify 是"AI 专项的开发平台"——内置 RAG、Agent、工作流，专注于 LLM 应用的快速搭建。两者可以组合使用：Dify 提供 RAG 和 Agent 能力，n8n 负责把 Dify 的 AI 能力接入到更大的自动化工作流中。

***

## Q7: RAG vs Fine-tuning 怎么选？

> [!TIP]
>
> RAG 和 Fine-tuning 就像学外语的两种方式。RAG 是给你一本字典——遇到不会的词就查（检索文档），字典越全你回答得越好，但你还是用原来的语法说话（LLM 风格不变）。Fine-tuning 是去上语法班——学完之后你的说话方式变了（输出风格改变），能用更地道的表达（领域术语），但遇到没学过的知识还是不会。大多数场景只需要字典（RAG），只有当你需要改变说话方式（特定风格输出）或学习专业术语时，才需要上语法班（Fine-tuning）。

### 核心区别：知识注入 vs 能力注入

RAG 和 Fine-tuning 解决的是完全不同类型的问题：

```text theme={null}
RAG（知识注入）：让 LLM "查资料"
├── 解决的问题：LLM 不知道某些信息
├── 典型场景：公司制度问答、产品文档查询、技术知识库
├── 数据流：知识库 → 检索 → LLM 上下文 → 生成
└── 更新方式：换文档就行，实时生效

Fine-tuning（能力注入）：让 LLM "学会"某种风格/格式
├── 解决的问题：LLM 的输出风格或格式不对
├── 典型场景：特定语气写作、特定格式输出（SQL/JSON）、领域术语理解
├── 数据流：训练数据 → 微调模型 → 新的模型行为
└── 更新方式：需要重新训练，成本高
```

### 适用场景对比

| 维度 | RAG | Fine-tuning |
| - | - | - |
| **解决的问题** | LLM 不知道（知识缺失） | LLM 不会（能力不足） |
| **知识更新** | 实时（换文档就行） | 需要重新训练 |
| **成本** | 低（只需向量数据库） | 高（需要 GPU 和训练数据） |
| **幻觉控制** | 好（基于真实文档） | 一般（仍可能幻觉） |
| **私有数据** | 天然支持 | 需要脱敏处理 |
| **输出风格** | 不改变 LLM 的风格 | 可以精确控制输出风格 |
| **适用场景** | 知识问答、文档检索 | 风格迁移、特定格式输出 |

```javascript theme={null}
// RAG 场景：知识问答
// 问题："公司的年假制度是什么？"
// 方案：检索公司制度文档 → LLM 基于文档回答
// 特点：知识经常变化，RAG 可以实时更新

// Fine-tuning 场景：风格迁移
// 需求：让 LLM 用公司的品牌语气写营销文案
// 方案：用 1000 条品牌文案训练 → 微调模型
// 特点：风格是固定的，Fine-tuning 一次训练永久使用

// 组合使用：Fine-tuning + RAG
// Fine-tuning 让模型理解行业术语和写作风格
// RAG 提供最新的产品信息和客户数据
// 两者互补，覆盖"知道"和"会做"两个维度
```

> [!NOTE]
>
> RAG 解决的是"不知道"的问题（知识注入），Fine-tuning 解决的是"不会做"的问题（能力注入）。大多数企业场景用 RAG 就够了。Fine-tuning 只在两种场景下考虑：1) 需要特定的输出风格；2) 需要理解领域专用术语。两者也可以组合使用。

***

## Q8: 多模态 RAG（图文混排、表格、图片语义）

传统 RAG 只处理纯文本，但企业文档中大量信息以图片、表格、PDF 图文混排的形式存在。多模态 RAG 的目标是**让 RAG 系统能"看懂"图片和表格，而不仅仅是"读懂"文字**。<sup>\[1]</sup>

```text theme={null}
传统 RAG（纯文本）：
PDF 文档 → 提取文字 → 切片 → 向量化 → 检索 → 生成
              ↑
         图片/表格被丢弃，关键信息丢失

多模态 RAG：
PDF 文档 → 文字提取 + 图片解析 + 表格结构化
              ↓              ↓            ↓
           文本切片      图片语义描述    表格转文本
              ↓              ↓            ↓
           文本向量化    图片向量化     结构化存储
              ↓              ↓            ↓
           └───── 多模态融合检索 ─────┘
                      ↓
              LLM（支持图文输入）→ 生成答案
```

> [!TIP]
>
> 传统 RAG 就像一个只能读文字的秘书——你给他一份带图表的报告，他会把文字部分整理好，但图表、流程图、数据表格全部忽略。多模态 RAG 就像一个能"看图说话"的秘书——他不仅整理文字，还能看懂流程图的含义、识别表格中的数据、理解图片中的关键信息。当你问"这个季度的销售趋势如何"，他能看懂折线图并告诉你"Q2 环比增长 15%"。

### PDF 图文混排解析（MinerU、LlamaParse、Docling）

企业级文档（合同、财报、技术手册）通常是图文混排的 PDF。传统的 `pdf-parse` 或 `pdfjs-dist` 只能提取纯文本，图片、表格、公式全部丢失。专业的文档解析工具通过 OCR + 版面分析来保留完整的文档结构。

| 工具 | 类型 | 特点 | 适用场景 |
| - | - | - | - |
| **MinerU**（OpenDataLab） | 开源，自部署 | 版面分析 + OCR + 公式识别，保留文档结构 | 企业私有化部署，中文文档 |
| **LlamaParse**（LlamaIndex） | 托管 API | 深度集成 LlamaIndex 生态，支持复杂 PDF/PPT | 快速原型，不想自建解析流水线 |
| **Docling**（IBM Research） | 开源，自部署 | 支持 90+ 文件格式，文档级理解，多格式导出 | 多格式文档统一解析 |

```javascript theme={null}
// MinerU 解析 PDF（保留图片、表格、公式）
import { MinerU } from "magic-pdf";

const parser = new MinerU();
const result = await parser.parse("./quarterly-report.pdf");

// result 包含：
// - text_blocks：纯文本段落（带位置信息）
// - tables：结构化表格数据
// - images：图片及其位置
// - formulas：数学公式（LaTeX 格式）
// - layout：文档版面结构（标题、正文、页眉页脚）
```

### 表格解析（Table Transformer、Camelot、VLM）

表格是 RAG 系统中最难处理的结构化数据。传统的文本切片会打碎表格的行列关系，导致检索到的切片丢失结构化语义。

| 方案 | 原理 | 优势 | 劣势 |
| - | - | - | - |
| **Table Transformer**（Microsoft） | 基于 Transformer 的表格检测 + 结构识别 | 端到端，从图片中提取表格结构 | 需要 GPU，部署成本高 |
| **Camelot** | 基于 PDF 线条检测的表格提取 | 轻量级，适合有明确边框的表格 | 无法处理无线框表格 |
| **VLM 直接理解**（GPT-4V/Qwen-VL） | 多模态大模型直接"看"表格图片并结构化 | 理解力最强，能处理任意复杂表格 | 成本高，速度慢 |

```javascript theme={null}
// VLM 表格解析：用多模态大模型直接理解表格图片
const tableImage = fs.readFileSync("./table.png", { base64: true });

const response = await openai.chat.completions.create({
  model: "gpt-4o",
  messages: [
    {
      role: "user",
      content: [
        {
          type: "text",
          text: "请将这个表格解析为 JSON 格式，保留所有行列关系。",
        },
        {
          type: "image_url",
          image_url: { url: `data:image/png;base64,${tableImage}` },
        },
      ],
    },
  ],
});
// 输出结构化 JSON，可直接存入向量数据库或关系型数据库
```

### 图片语义提取（GPT-4V、Qwen-VL）

图片中的关键信息（流程图、架构图、数据可视化）需要被转化为文本描述，才能参与 RAG 检索。核心思路是用多模态大模型生成图片的语义描述。

```text theme={null}
图片语义提取流程：
原始图片 → 多模态大模型（GPT-4V / Qwen-VL）
              ↓
         生成结构化描述（标题、关键元素、关系）
              ↓
         文本描述 → Embedding → 存入向量数据库
         原始图片 → 图片 Embedding（CLIP/SigLIP）→ 存入向量数据库
```

### 多模态嵌入（CLIP、SigLIP）

多模态嵌入的核心是**把文本和图片映射到同一个向量空间**，使得"一张猫的图片"和"一只猫"在向量空间中距离很近。<sup>\[2]</sup>

| 模型 | 来源 | 特点 | 适用场景 |
| - | - | - | - |
| **CLIP** | OpenAI | 图文对比学习的开创者，零样本图文匹配 | 通用图文检索基线 |
| **SigLIP** | Google | 用 Sigmoid 替代 Softmax，扩展性更好 | 大规模图文检索 |

> BGE-M3 属于文本 Embedding（多语言，同时输出 Dense、Sparse、Multi-vector），不能处理图片，不要和多模态模型混用。

```javascript theme={null}
// 多模态 RAG 索引：文本和图片分别向量化
import { OpenAIEmbeddings } from "@langchain/openai";
import { Milvus } from "@langchain/community/vectorstores/milvus";

// 文本切片 → 文本 Embedding
const textChunks = await splitter.splitDocuments(documents);
const textEmbeddings = new OpenAIEmbeddings({
  model: "text-embedding-3-small",
});

// 图片 → CLIP/SigLIP Embedding
const imageEmbeddings = new CLIPEmbeddings(); // 或 SigLIP Embedding

// 分别存入向量数据库的文本字段和图片字段（同一 Collection，不同字段）
await milvus.addDocuments(textChunks, { embeddingField: "text_embedding" });
// 图片先经 CLIP/SigLIP 编码，再以 Document 形式写入 image_embedding 字段
await milvus.addDocuments(imageDocs, { embeddingField: "image_embedding" });

// 检索时：文本查询同时检索文本和图片两个字段，再在应用层融合排序
const results = await milvus.similaritySearch(query, { k: 5 });
```

> [!NOTE]
>
> 多模态 RAG 的核心是让 RAG 系统能"看懂"文档中的图片和表格——用 MinerU/LlamaParse/Docling 解析 PDF 图文混排，用 Table Transformer/VLM 结构化表格数据，用 GPT-4V/Qwen-VL 提取图片语义描述，用 CLIP/SigLIP 实现跨模态向量检索。2025 年的趋势是"视觉原生解析"——直接用多模态大模型理解文档版面，而不是传统的 OCR + 规则解析。

***

## Q9: 行业 RAG 应用（食品、工业、金融、医疗）

RAG 的价值在不同行业中体现为不同的落地形态。核心差异在于**知识库的结构、合规要求和容错标准**。<sup>\[3]</sup>

```text theme={null}
行业 RAG 差异化要素：

┌──────────┬──────────────┬──────────────┬──────────────┐
│   行业    │   知识库类型  │   合规要求    │   容错标准    │
├──────────┼──────────────┼──────────────┼──────────────┤
│ 食品/快消 │ SOP/配方/供应链│ FDA/GB标准  │ 中（配方错误→召回）│
│ 工业      │ 设备手册/工艺  │ ISO/GMP     │ 高（误操作→停机）│
│ 金融      │ 合规/风控/报告 │ 证监会/银保监 │ 极高（错误→监管处罚）│
│ 医疗      │ 病历/指南/药物 │ HIPAA/药典   │ 极高（错误→生命安全）│
└──────────┴──────────────┴──────────────┴──────────────┘
```

> [!TIP]
>
> 行业 RAG 就像不同行业的图书馆管理员。食品行业的管理员专门管理配方和质检报告——你需要查找某款饮料的配方，他能立刻找到并告诉你每种原料的配比（但你必须确认配方版本是最新的）。工业行业的管理员管理设备维修手册——设备报警时，他能根据错误代码找到对应的维修步骤（但如果手册有误，可能导致停机损失）。金融行业的管理员管理合规文件——你问某个业务是否合规，他能精确引用相关法规条文（但回答必须 100% 准确，错误回答可能导致监管处罚）。医疗行业的管理员管理临床指南——他能根据症状推荐诊断方案（但错误推荐可能危及生命，所以必须有医生复核环节）。

### 食品/快消行业 RAG

食品行业的 RAG 核心场景覆盖**配方管理、质量控制、供应链知识管理和营销文案生成**四大领域。知识库包含产品配方文档、质检标准（GB/T 系列、FDA 21 CFR）、供应商资质文件、生产 SOP、营销素材库。<sup>\[3]</sup>

**配方管理知识库**是食品行业 RAG 最核心的应用。同一产品可能有多个版本的配方（如"减糖版"、"无麸质版"），RAG 系统必须支持**配方版本控制**——在向量数据库的元数据中存储`产品ID`、`版本号`、`生效日期`、`适用市场`，检索时强制过滤为最新版本。配方查询需要精确匹配成分名称和配比数值，因此必须采用**混合检索**（向量语义 + BM25 关键词），纯向量检索可能将"蔗糖"和"果葡糖浆"混为一谈。

```javascript theme={null}
// 食品配方 RAG：带版本控制和成分精确查询
async function queryRecipe(productName, ingredient) {
  // 1. 混合检索：向量语义 + BM25 关键词精确匹配
  const results = await hybridSearch({
    query: `${productName} 配方中 ${ingredient} 的含量和供应商`,
    vectorWeight: 0.4, // 降低语义权重
    bm25Weight: 0.6, // 提高关键词权重（成分名必须精确匹配）
    metadataFilter: {
      documentType: "recipe",
      status: "active", // 只检索当前生效的配方
      market: "CN", // 按市场过滤
    },
    topK: 5,
  });

  // 2. 版本校验：确保返回的是最新版本
  const latestVersion = results.sort(
    (a, b) => b.metadata.version - a.metadata.version,
  )[0];

  return {
    content: latestVersion.pageContent,
    version: latestVersion.metadata.version,
    effectiveDate: latestVersion.metadata.effectiveDate,
    source: latestVersion.metadata.source,
  };
}
```

**质量控制文档**包括质检标准（GB 4789 系列微生物检测、GB 5009 系列理化检测）、HACCP 关键控制点文档、不合格品处理记录。RAG 系统需要支持**合规检查自动化**——输入检测数据，自动比对标准限值，输出合规/不合规判定及依据条文。

**供应链知识管理**整合供应商资质文件、物流追踪记录、原料溯源信息。当发生食品安全事件时，RAG 系统需要在秒级内完成**召回溯源**——从成品批次反查原料供应商、生产日期、物流路径。

**营销文案生成**是食品行业 RAG 的增值应用。知识库包含品牌调性指南、历史营销文案、产品卖点文档。RAG 系统根据产品信息自动生成符合品牌调性的产品描述和广告语，同时确保营养声称符合法规要求（如"低糖"需满足 GB 28050 标准）。

**技术方案：知识图谱 + RAG + 多模态**

食品行业的知识具有强关联性（原料-配方-产品-供应商-法规），适合用**知识图谱 + RAG**（Graph RAG）构建。例如查询"含有花生的产品有哪些"需要遍历原料-配方-产品的图关系，纯向量检索无法完成。同时，产品图片（包装设计、质检报告扫描件）需要**多模态 RAG** 支持图文混排检索。

### 工业行业 RAG

工业行业的 RAG 核心场景是**设备维护手册检索**和**制造流程知识库**。知识库包含设备操作手册、维修 SOP、故障代码对照表、工艺参数文档、PLC 程序注释。<sup>\[3]</sup>

工业 RAG 与其他行业最大的差异是**毫秒级响应需求**。设备报警时，生产线每停一分钟可能损失数万元。RAG 系统必须在设备报警后几秒内返回维修方案，而不是像传统 RAG 那样等待 2-3 秒的检索 + 生成时间。

**设备维护手册**是工业 RAG 最高频的知识源。一个大型工厂可能有数千台设备，每台设备的维护手册数百页。传统做法是工程师翻阅纸质手册或搜索 PDF，效率极低。RAG 系统将所有设备手册索引化，工程师输入故障代码或描述故障现象，系统返回对应的维修步骤、所需备件、预计维修时间。

```javascript theme={null}
// 工业设备维护 RAG：毫秒级响应架构
async function diagnoseFault(faultCode, equipmentId) {
  // 1. 一级缓存：Redis 缓存高频故障的维修方案（< 5ms）
  const cacheKey = `fault:${equipmentId}:${faultCode}`;
  const cached = await redis.get(cacheKey);
  if (cached) return JSON.parse(cached);

  // 2. 二级检索：向量数据库检索维修手册（< 200ms）
  const docs = await vectorStore.similaritySearch(
    `设备 ${equipmentId} 故障代码 ${faultCode} 维修方案`,
    {
      k: 3,
      filter: {
        documentType: "maintenance_manual",
        equipmentModel: equipmentId.split("-")[0], // 按设备型号过滤
      },
    },
  );

  // 3. LLM 生成维修方案（< 1s）
  const plan = await llm.invoke({
    prompt: `你是设备维修专家。根据以下维修手册内容，为故障代码 ${faultCode} 生成分步维修方案。
    要求：每步包含操作说明、所需工具、安全注意事项。

    维修手册：
    ${docs.map((d) => d.pageContent).join("\n---\n")}`,
  });

  // 4. 写入缓存（高频故障永久缓存）
  await redis.setex(cacheKey, 86400 * 30, JSON.stringify(plan));

  return plan;
}
```

**制造流程知识库**整合工艺参数（温度、压力、速度等）、操作规范、质量检验标准。RAG 系统需要支持**结构化参数查询**——输入产品型号和工序，返回精确的工艺参数范围。这要求切片策略保留参数表格的结构化语义，不能把表格打碎成无序文本。

**技术方案：向量数据库 + 缓存 + 边缘部署**

工业 RAG 的架构核心是**三级缓存 + 边缘部署**：

```text theme={null}
┌─────────────────────────────────────────────────────┐
│              工业 RAG 三级响应架构                     │
├─────────────────────────────────────────────────────┤
│                                                     │
│  L1 缓存层（Redis）：高频故障方案，< 5ms              │
│    ↓ 缓存未命中                                      │
│  L2 边缘检索层（本地向量库）：设备手册索引，< 200ms    │
│    ↓ 边缘未命中                                      │
│  L3 云端生成层（LLM）：复杂故障分析，< 2s             │
│                                                     │
└─────────────────────────────────────────────────────┘
```

边缘部署是工业场景的关键——工厂网络可能不稳定，不能依赖云端 API。解决方案是在工厂内网部署轻量级向量数据库（如 Qdrant 单机版）和本地 LLM（如 Qwen-7B），实现离线可用的 RAG 系统。

### 金融行业 RAG

金融行业的 RAG 核心场景是**合规知识库、投研分析、风险管理**三大领域。知识库包含监管法规（证监会、银保监会、人民银行文件）、内部合规制度、券商研报、风险案例库、审计文档。<sup>\[3]</sup>

金融 RAG 与其他行业最大的差异是**法规出处可追溯**。金融合规问答不允许"大概"、"可能"的回答，每句话都必须能追溯到具体的法规条文。错误的合规建议可能导致监管处罚，甚至刑事责任。

**合规知识库**是金融 RAG 最基础的应用。金融机构需要回答"某项业务是否合规"、"某类产品的监管要求是什么"等问题。知识库包含数千份法规文件，且法规之间存在引用、修订、废止等复杂关系。纯向量检索无法处理"A 法规引用 B 法规第 X 条"这种关系型查询，必须引入**知识图谱**。

```javascript theme={null}
// 金融合规 RAG：GraphRAG + 法规出处追溯
async function complianceCheck(businessType, scenario) {
  // 1. 知识图谱查询：找到与业务类型相关的法规链
  const regulationChain = await graphDB.run(
    `
    MATCH (b:Business {type: $bizType})<-[:REGULATES]-(r:Regulation)
    WHERE r.status = 'active'
    MATCH (r)-[:REFERENCES]->(referenced:Regulation)
    RETURN r.id, r.title, r.clause, referenced.id, referenced.clause
    ORDER BY r.effectiveDate DESC
  `,
    { bizType: businessType },
  );

  // 2. 向量检索：找到法规原文和相关案例
  const docs = await vectorStore.similaritySearch(
    `${businessType} ${scenario} 合规要求 监管规定`,
    {
      k: 5,
      filter: { documentType: { $in: ["regulation", "case_study"] } },
    },
  );

  // 3. LLM 生成合规分析报告（强制引用法规编号）
  const report = await llm.invoke({
    prompt: `你是金融合规专家。分析以下业务场景的合规性。

    业务场景：${scenario}

    相关法规：
    ${docs.map((d, i) => `[${i + 1}] ${d.pageContent}（来源：${d.metadata.regulationId}）`).join("\n\n")}

    要求：
    1. 明确给出合规/不合规/需进一步确认的结论
    2. 每个结论必须引用法规编号和具体条款
    3. 如果法规之间存在冲突，指出冲突并建议咨询法务
    4. 在回答末尾附上法规原文链接`,
  });

  return report;
}
```

**投研分析**是金融 RAG 的增值应用。券商研报通常数百页，分析师需要快速提取关键信息（如某公司的营收增长率、行业竞争格局、风险因素）。RAG 系统支持**多文档摘要**和**跨报告对比**——输入"对比 A 公司和 B 公司的毛利率趋势"，系统从多份研报中提取相关数据并生成对比分析。

**风险管理**整合风险案例库、预警指标、历史审计文档。RAG 系统支持**风险预警**——输入当前业务数据，检索历史上类似场景的风险案例，输出风险等级和应对建议。

**技术方案：GraphRAG + 权限控制 + 审计日志**

金融 RAG 的架构核心是**法规关系图谱 + 细粒度权限 + 全链路审计**。法规之间存在引用、修订、废止等关系，必须用知识图谱建模。不同角色（合规专员、业务人员、审计人员）对法规的访问权限不同，需要文档级权限控制。所有 RAG 查询和 LLM 生成的答案都必须记录审计日志，支持事后追溯。

### 医疗行业 RAG

医疗行业的 RAG 核心场景是**医疗知识库、药品信息查询、病历分析**三大领域。知识库包含医学文献（PubMed）、临床诊疗指南、药物说明书、病历数据（需脱敏）、影像报告。<sup>\[3]</sup>

医疗 RAG 与其他行业最大的差异是**容错标准极高**——错误的诊断建议可能危及生命。因此，所有 LLM 生成的医疗建议必须标注"仅供参考，需医生复核"，且系统必须支持**证据等级标注**（如"基于 RCT 研究的 A 级推荐" vs "基于专家意见的 D 级推荐"）。

**医疗知识库**整合临床诊疗指南、医学教科书、Meta 分析。RAG 系统需要支持**多模态检索**——医生输入症状描述或影像图片，系统检索相关的诊疗指南和文献。影像报告（CT、MRI、X 光）需要多模态大模型（如 Med-PaLM）辅助解读。

**药品信息查询**是医疗 RAG 最高频的应用。知识库包含药物说明书、药物相互作用数据库、剂量指南。RAG 系统需要精确回答"A 药和 B 药能否同时服用"、"肾功能不全患者的剂量调整"等问题。这要求切片策略保留药物信息的结构化字段（通用名、商品名、适应症、禁忌症、相互作用），不能把说明书打碎成无序文本。

```javascript theme={null}
// 医疗药品 RAG：结构化切片 + 药物相互作用查询
async function checkDrugInteraction(drugA, drugB) {
  // 1. 精确匹配药物名称（支持通用名和商品名）
  const docs = await vectorStore.similaritySearch(
    `${drugA} ${drugB} 药物相互作用 配伍禁忌`,
    {
      k: 5,
      filter: {
        documentType: "drug_label",
        $or: [
          { genericName: { $in: [drugA, drugB] } },
          { brandName: { $in: [drugA, drugB] } },
        ],
      },
    },
  );

  // 2. LLM 生成药物相互作用分析（强制标注证据等级）
  const analysis = await llm.invoke({
    prompt: `你是临床药师。分析 ${drugA} 和 ${drugB} 的相互作用。

    药物说明书：
    ${docs.map((d) => d.pageContent).join("\n---\n")}

    要求：
    1. 明确指出是否存在相互作用
    2. 如果存在，说明严重程度（轻度/中度/重度/禁忌）
    3. 标注证据等级（A: RCT / B: 队列研究 / C: 病例报告 / D: 专家意见）
    4. 给出临床建议（是否需要调整剂量、监测指标等）
    5. 末尾标注"以上信息仅供参考，具体用药方案请咨询主治医师"`,
  });

  return analysis;
}
```

**病历分析**是医疗 RAG 最敏感的应用。病历数据包含患者的个人信息、诊断记录、用药历史，必须严格遵守 **HIPAA**（美国）或《个人信息保护法》（中国）的脱敏要求。

**技术方案：多模态 RAG + 安全隔离 + HIPAA 脱敏**

医疗 RAG 的架构核心是**数据安全隔离 + 脱敏处理 + 多模态支持**：

```text theme={null}
┌─────────────────────────────────────────────────────────┐
│              医疗 RAG 安全架构                             │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  数据脱敏层：NER 模型自动识别并脱敏 PII 信息              │
│    ├── 患者姓名 → [姓名已脱敏]                            │
│    ├── 身份证号 → [证件号已脱敏]                          │
│    ├── 联系电话 → [电话已脱敏]                            │
│    └── 地址信息 → [地址已脱敏]                            │
│    ↓                                                    │
│  安全隔离层：病历数据与通用知识库物理隔离                   │
│    ├── 病历向量库：部署在医院内网，不接入外网               │
│    ├── 医学知识库：可部署在云端（无 PII）                  │
│    └── 查询路由：根据问题类型路由到不同知识库               │
│    ↓                                                    │
│  审计追溯层：所有查询和生成结果记录审计日志                 │
│    ├── 查询人、查询时间、查询内容                          │
│    ├── 检索到的文档列表                                   │
│    └── LLM 生成的答案 + 引用来源                          │
│                                                         │
└─────────────────────────────────────────────────────────┘
```

```javascript theme={null}
// 医疗 RAG：HIPAA 脱敏处理
import { pipeline } from "@huggingface/transformers";

// 加载 NER 模型（识别 PII 实体）
const nerPipeline = await pipeline(
  "token-classification",
  "dslim/bert-base-NER",
);

async function deidentifyMedicalRecord(record) {
  // 1. 识别 PII 实体
  const entities = await nerPipeline(record);

  // 2. 脱敏替换
  let deidentified = record;
  const piiMap = {
    PER: "[姓名已脱敏]",
    LOC: "[地址已脱敏]",
    ORG: "[机构已脱敏]",
  };

  // 按位置倒序替换（避免偏移）
  entities
    .sort((a, b) => b.start - a.start)
    .forEach((entity) => {
      const replacement = piiMap[entity.entity] || "[已脱敏]";
      deidentified =
        deidentified.slice(0, entity.start) +
        replacement +
        deidentified.slice(entity.end);
    });

  return deidentified;
}

// 脱敏后的病历才能进入 RAG 知识库
async function indexMedicalRecord(record) {
  const deidentified = await deidentifyMedicalRecord(record);
  await vectorStore.addDocuments([
    {
      pageContent: deidentified,
      metadata: {
        documentType: "medical_record",
        deidentified: true, // 标记已脱敏
        originalHash: hash(record), // 原始记录哈希（用于审计追溯）
      },
    },
  ]);
}
```

> [!NOTE]
>
> 行业 RAG 的核心差异不在技术架构，而在**合规要求和容错标准**。食品行业重版本管理（配方不能用错）和知识图谱（原料-配方-产品的关联关系）；工业行业重响应速度（三级缓存 + 边缘部署实现毫秒级返回）；金融行业重出处可追溯（GraphRAG 建模法规引用链 + 审计日志）；医疗行业重隐私保护（HIPAA 脱敏 + 安全隔离 + 多模态影像支持）。

***

## Q10: AI Agent 客服系统

AI Agent 客服系统是 RAG + Agent 最典型的落地场景——结合知识库检索、意图识别、多轮对话和转人工策略，构建端到端的智能客服解决方案。2024-2025 年，随着大模型能力的提升，智能客服从"关键词匹配 + 规则路由"进化为"Agent 自主决策 + RAG 知识检索 + Human-in-the-Loop 人机协同"。<sup>\[1]</sup>

```text theme={null}
┌───────────────────────────────────────────────────────────────┐
│              AI Agent 客服系统端到端架构                         │
│                                                                │
│  ┌──────────┐   ┌──────────┐   ┌──────────┐   ┌──────────┐  │
│  │ 用户输入  │ → │ 意图识别  │ → │ 知识库检索│ → │ 回答生成  │  │
│  │ 文本/语音 │   │ LLM 分类  │   │ RAG 检索  │   │ LLM 生成  │  │
│  └──────────┘   └──────────┘   └──────────┘   └──────────┘  │
│       ↑                                            │          │
│       │         ┌──────────┐   ┌──────────┐       │          │
│       └──────── │ 人工接管  │ ← │ 转人工判断│ ←─────┘          │
│                 │ 工单系统  │   │ 多维度策略│                    │
│                 └──────────┘   └──────────┘                    │
│                                                                │
│  ┌──────────────────────────────────────────────────────────┐ │
│  │                    工单闭环                                │ │
│  │  创建 → 分配 → 处理 → 反馈 → 关闭                         │ │
│  └──────────────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────┘
```

> [!TIP]
>
> AI Agent 客服系统就像医院的分诊台。用户进来先分诊（意图识别）——护士（LLM）判断你是感冒还是骨折。如果是感冒（简单问题），直接从药房（知识库 RAG）取药（检索文档）给你。如果是骨折（复杂问题），分诊台帮你挂骨科专家号（转人工）。如果护士连续判断错误 3 次（连续无法回答），或者你情绪激动大喊大叫（情绪检测），也会帮你转专家。整个过程中，你的病历（对话历史）被完整记录，专家看完后会把诊断结果录入系统（工单闭环），下次类似症状可以直接参考。

### 意图识别（Intent Classification）

意图识别是客服系统的第一步——判断用户想做什么，然后路由到对应的处理流程。

```text theme={null}
意图识别的两种方案：

方案 A：传统 NLU（小模型分类）
  用户输入 → 意图分类模型（BERT/TextCNN）→ 预定义意图标签
  优点：速度快（&lt; 50ms），成本低
  缺点：只能识别预定义意图，无法处理新意图

方案 B：LLM 判断（大模型理解）
  用户输入 → LLM 分析 → 输出意图 + 置信度 + 提取的实体
  优点：能理解任意意图，泛化能力强
  缺点：速度慢（500ms-2s），成本高

推荐方案：混合架构（LLM + 小模型）
  高频意图 → 小模型快速分类（80% 的请求）
  低频/新意图 → LLM 理解（20% 的请求）
```

```python theme={null}
# Python 示例：意图识别（混合架构）
from typing import Literal

class IntentClassifier:
    def __init__(self, small_model, llm):
        self.small_model = small_model  # BERT/TextCNN 分类器
        self.llm = llm                  # 大语言模型
        self.intent_cache = {}          # 意图缓存

    async def classify(self, user_message: str, history: list) -> dict:
        # 1. 缓存命中：相同问题直接返回
        cache_key = self._normalize(user_message)
        if cache_key in self.intent_cache:
            return self.intent_cache[cache_key]

        # 2. 小模型快速分类（高频意图）
        small_result = await self.small_model.predict(user_message)
        if small_result["confidence"] > 0.9:
            return small_result

        # 3. LLM 深度理解（低置信度或新意图）
        llm_result = await self.llm.invoke(
            f"""分析用户意图，返回 JSON 格式：
            用户消息：{user_message}
            对话历史：{history[-3:]}

            返回格式：
            {{"intent": "意图标签", "confidence": 0.95, "entities": {{"key": "value"}}}}
            """
        )
        return llm_result
```

```javascript theme={null}
// JavaScript 示例：意图识别（混合架构）
class IntentClassifier {
  constructor(smallModel, llm) {
    this.smallModel = smallModel; // BERT/TextCNN 分类器
    this.llm = llm; // 大语言模型
    this.intentCache = new Map();
  }

  async classify(userMessage, history) {
    // 1. 缓存命中
    const cacheKey = this.normalize(userMessage);
    if (this.intentCache.has(cacheKey)) {
      return this.intentCache.get(cacheKey);
    }

    // 2. 小模型快速分类
    const smallResult = await this.smallModel.predict(userMessage);
    if (smallResult.confidence > 0.9) {
      return smallResult;
    }

    // 3. LLM 深度理解
    const llmResult = await this.llm.invoke(
      `分析用户意图，返回 JSON 格式：
      用户消息：${userMessage}
      对话历史：${JSON.stringify(history.slice(-3))}

      返回格式：
      {"intent": "意图标签", "confidence": 0.95, "entities": {"key": "value"}}`,
    );

    return JSON.parse(llmResult);
  }
}
```

### 多轮对话（Multi-turn Dialogue）

客服系统的核心挑战是多轮对话——用户可能在多轮交互中逐步补充信息、切换话题、回溯之前的问题。系统需要维护完整的对话状态。

```text theme={null}
多轮对话状态追踪：

┌─────────────────────────────────────────────────┐
│              对话状态管理                          │
│                                                   │
│  对话历史：[                                      │
│    { role: "user", content: "我想退货" },         │
│    { role: "assistant", content: "请问订单号？" }, │
│    { role: "user", content: "123456" },           │
│  ]                                                │
│                                                   │
│  槽位状态：{                                      │
│    intent: "退货申请",                             │
│    order_id: "123456",          ← 已填充          │
│    reason: null,                ← 待填充          │
│    refund_method: null,         ← 待填充          │
│  }                                                │
│                                                   │
│  对话阶段：信息收集（还需要退货原因和退款方式）      │
└─────────────────────────────────────────────────┘
```

```python theme={null}
# Python 示例：多轮对话状态管理
class ConversationManager:
    def __init__(self, llm, knowledge_base):
        self.llm = llm
        self.kb = knowledge_base
        self.sessions = {}  # session_id -> conversation state

    async def handle_message(self, session_id: str, user_message: str) -> dict:
        # 获取或创建会话状态
        state = self.sessions.get(session_id, self._new_session())

        # 1. 更新对话历史
        state["history"].append({"role": "user", "content": user_message})

        # 2. 意图识别 + 槽位填充
        intent_result = await self._classify_intent(user_message, state["history"])
        state["intent"] = intent_result["intent"]
        state["slots"].update(intent_result.get("entities", {}))

        # 3. 知识库检索（RAG）
        context = await self.kb.search(user_message, filter={"intent": state["intent"]})

        # 4. 生成回答
        response = await self._generate_response(state, context)

        # 5. 更新对话历史
        state["history"].append({"role": "assistant", "content": response["content"]})

        # 6. 保存会话状态
        self.sessions[session_id] = state

        return response

    async def _generate_response(self, state, context):
        """基于对话状态和检索结果生成回答"""
        # 检查槽位是否完整
        missing_slots = self._get_missing_slots(state["intent"], state["slots"])

        if missing_slots:
            # 槽位不完整，追问缺失信息
            return {
                "content": await self.llm.invoke(
                    f"用户意图：{state['intent']}，"
                    f"已知信息：{state['slots']}，"
                    f"还需要：{missing_slots}。"
                    f"请用自然语言追问缺失信息。"
                ),
                "action": "ask_slot",
                "missing": missing_slots,
            }

        # 槽位完整，基于知识库生成回答
        return {
            "content": await self.llm.invoke(
                f"基于以下知识回答用户问题：\n{context}\n\n"
                f"用户意图：{state['intent']}\n"
                f"用户信息：{state['slots']}\n"
                f"对话历史：{state['history'][-5:]}"
            ),
            "action": "respond",
        }
```

```javascript theme={null}
// JavaScript 示例：多轮对话状态管理
class ConversationManager {
  constructor(llm, knowledgeBase) {
    this.llm = llm;
    this.kb = knowledgeBase;
    this.sessions = new Map();
  }

  async handleMessage(sessionId, userMessage) {
    const state = this.sessions.get(sessionId) || this.newSession();

    // 1. 更新对话历史
    state.history.push({ role: "user", content: userMessage });

    // 2. 意图识别 + 槽位填充
    const intentResult = await this.classifyIntent(userMessage, state.history);
    state.intent = intentResult.intent;
    Object.assign(state.slots, intentResult.entities || {});

    // 3. 知识库检索（RAG）
    const context = await this.kb.search(userMessage, {
      filter: { intent: state.intent },
    });

    // 4. 生成回答
    const response = await this.generateResponse(state, context);

    // 5. 更新历史 + 保存状态
    state.history.push({ role: "assistant", content: response.content });
    this.sessions.set(sessionId, state);

    return response;
  }

  async generateResponse(state, context) {
    const missingSlots = this.getMissingSlots(state.intent, state.slots);

    if (missingSlots.length > 0) {
      // 槽位不完整，追问
      return {
        content: await this.llm.invoke(
          `用户意图：${state.intent}，已知信息：${JSON.stringify(state.slots)}，还需要：${missingSlots}。请用自然语言追问缺失信息。`,
        ),
        action: "ask_slot",
        missing: missingSlots,
      };
    }

    // 槽位完整，基于知识库回答
    return {
      content: await this.llm.invoke(
        `基于以下知识回答用户问题：\n${context}\n\n用户意图：${state.intent}\n用户信息：${JSON.stringify(state.slots)}`,
      ),
      action: "respond",
    };
  }
}
```

### 转人工策略（Human Handoff）

转人工是客服系统的安全阀——当 Agent 无法处理时，必须无缝转接人工客服。触发条件需要多维度判断，不能只看"能不能回答"。

```text theme={null}
转人工触发条件：

┌─────────────────────────────────────────────────────────┐
│              转人工判断引擎                                │
│                                                          │
│  ┌──────────────┐  连续 3 次无法回答 → 转人工             │
│  │ 能力边界检测  │  置信度 &lt; 0.6         → 转人工          │
│  └──────────────┘  知识库无匹配         → 转人工          │
│                                                          │
│  ┌──────────────┐  检测到愤怒/焦虑      → 转人工          │
│  │ 情绪检测     │  连续负面情绪          → 转人工          │
│  └──────────────┘  用户主动要求          → 转人工          │
│                                                          │
│  ┌──────────────┐  涉及退款/赔偿        → 转人工          │
│  │ 敏感问题检测  │  涉及法律/合规        → 转人工          │
│  └──────────────┘  涉及账户安全          → 转人工          │
│                                                          │
│  ┌──────────────┐  对话超过 10 轮       → 转人工          │
│  │ 效率检测     │  同一问题反复出现      → 转人工          │
│  └──────────────┘  用户等待时间过长      → 转人工          │
└─────────────────────────────────────────────────────────┘
```

```python theme={null}
# Python 示例：转人工策略
class HumanHandoffStrategy:
    def __init__(self):
        self.consecutive_failures = 0
        self.sentiment_history = []

    def should_transfer(self, message: str, response: dict, session: dict) -> dict:
        """判断是否需要转人工，返回决策和原因"""
        reasons = []

        # 1. 连续无法回答
        if response.get("confidence", 1.0) < 0.6:
            self.consecutive_failures += 1
        else:
            self.consecutive_failures = 0

        if self.consecutive_failures >= 3:
            reasons.append("连续 3 次无法高置信度回答")

        # 2. 情绪检测
        sentiment = self.detect_sentiment(message)
        self.sentiment_history.append(sentiment)
        if sentiment == "angry" or sentiment == "frustrated":
            reasons.append(f"检测到用户情绪：{sentiment}")

        # 3. 敏感问题
        if self.is_sensitive_topic(message):
            reasons.append("涉及敏感话题（退款/法律/安全）")

        # 4. 用户主动要求
        if self.user_requests_human(message):
            reasons.append("用户主动要求转人工")

        # 5. 对话轮次过多
        if len(session.get("history", [])) > 20:
            reasons.append("对话超过 20 轮，可能陷入循环")

        return {
            "transfer": len(reasons) > 0,
            "reasons": reasons,
            "priority": "high" if len(reasons) >= 2 else "medium",
        }

    def detect_sentiment(self, message: str) -> str:
        """情绪检测（简化版，实际用 NLP 模型）"""
        angry_keywords = ["投诉", "垃圾", "骗子", "退款", "差评", "愤怒"]
        if any(kw in message for kw in angry_keywords):
            return "angry"
        return "neutral"

    def is_sensitive_topic(self, message: str) -> bool:
        """敏感话题检测"""
        sensitive_keywords = ["退款", "赔偿", "律师", "法律", "起诉", "密码", "账户"]
        return any(kw in message for kw in sensitive_keywords)
```

```javascript theme={null}
// JavaScript 示例：转人工策略
class HumanHandoffStrategy {
  constructor() {
    this.consecutiveFailures = 0;
    this.sentimentHistory = [];
  }

  shouldTransfer(message, response, session) {
    const reasons = [];

    // 1. 连续无法回答
    if ((response.confidence ?? 1) < 0.6) {
      this.consecutiveFailures++;
    } else {
      this.consecutiveFailures = 0;
    }
    if (this.consecutiveFailures >= 3) {
      reasons.push("连续 3 次无法高置信度回答");
    }

    // 2. 情绪检测
    const sentiment = this.detectSentiment(message);
    this.sentimentHistory.push(sentiment);
    if (sentiment === "angry" || sentiment === "frustrated") {
      reasons.push(`检测到用户情绪：${sentiment}`);
    }

    // 3. 敏感问题
    if (this.isSensitiveTopic(message)) {
      reasons.push("涉及敏感话题（退款/法律/安全）");
    }

    // 4. 用户主动要求
    if (this.userRequestsHuman(message)) {
      reasons.push("用户主动要求转人工");
    }

    // 5. 对话轮次过多
    if ((session.history?.length ?? 0) > 20) {
      reasons.push("对话超过 20 轮，可能陷入循环");
    }

    return {
      transfer: reasons.length > 0,
      reasons,
      priority: reasons.length >= 2 ? "high" : "medium",
    };
  }

  detectSentiment(message) {
    const angryKeywords = ["投诉", "垃圾", "骗子", "退款", "差评", "愤怒"];
    if (angryKeywords.some((kw) => message.includes(kw))) return "angry";
    return "neutral";
  }

  isSensitiveTopic(message) {
    const sensitiveKeywords = [
      "退款",
      "赔偿",
      "律师",
      "法律",
      "起诉",
      "密码",
      "账户",
    ];
    return sensitiveKeywords.some((kw) => message.includes(kw));
  }
}
```

### 工单闭环（Ticket Lifecycle）

转人工不是终点——人工客服解决问题后，需要把解决方案录入知识库，形成闭环。这样下次遇到类似问题，Agent 就能直接回答。

```text theme={null}
工单闭环流程：

┌─────────┐   ┌─────────┐   ┌─────────┐   ┌─────────┐   ┌─────────┐
│  创建    │ → │  分配    │ → │  处理    │ → │  反馈    │ → │  关闭    │
│ Agent自动 │   │ 智能路由 │   │ 人工解决 │   │ 满意度   │   │ 知识沉淀 │
│ 或用户创建│   │ 技能匹配 │   │ 记录方案 │   │ 评价收集 │   │ 更新知识库│
└─────────┘   └─────────┘   └─────────┘   └─────────┘   └─────────┘
```

```python theme={null}
# Python 示例：工单系统
class TicketSystem:
    async def create_ticket(self, session: dict, handoff_reason: str) -> dict:
        """创建工单：自动提取对话上下文"""
        ticket = {
            "id": generate_ticket_id(),
            "session_id": session["id"],
            "user_id": session["user_id"],
            "intent": session["intent"],
            "conversation_history": session["history"],  # 完整对话记录
            "handoff_reason": handoff_reason,
            "status": "open",
            "priority": self._calculate_priority(session),
            "created_at": datetime.now(),
            "assigned_to": None,
            "resolution": None,
        }

        # 智能分配：根据意图匹配技能最合适的客服
        agent = await self._find_best_agent(ticket["intent"], ticket["priority"])
        ticket["assigned_to"] = agent["id"]

        # 通知客服
        await self._notify_agent(agent, ticket)

        return ticket

    async def resolve_ticket(self, ticket_id: str, resolution: str, agent_id: str):
        """关闭工单并沉淀知识"""
        ticket = await self._get_ticket(ticket_id)
        ticket["status"] = "resolved"
        ticket["resolution"] = resolution
        ticket["resolved_by"] = agent_id
        ticket["resolved_at"] = datetime.now()

        # 知识沉淀：把解决方案写入知识库
        await self._ingest_to_knowledge_base({
            "question": self._extract_question(ticket["conversation_history"]),
            "answer": resolution,
            "source": f"ticket_{ticket_id}",
            "agent": agent_id,
            "verified": True,  # 人工验证过的答案
        })

        # 发送满意度调查
        await self._send_satisfaction_survey(ticket)
```

```javascript theme={null}
// JavaScript 示例：工单系统
class TicketSystem {
  async createTicket(session, handoffReason) {
    const ticket = {
      id: generateTicketId(),
      sessionId: session.id,
      userId: session.userId,
      intent: session.intent,
      conversationHistory: session.history,
      handoffReason,
      status: "open",
      priority: this.calculatePriority(session),
      createdAt: new Date(),
      assignedTo: null,
      resolution: null,
    };

    // 智能分配
    const agent = await this.findBestAgent(ticket.intent, ticket.priority);
    ticket.assignedTo = agent.id;

    // 通知客服
    await this.notifyAgent(agent, ticket);

    return ticket;
  }

  async resolveTicket(ticketId, resolution, agentId) {
    const ticket = await this.getTicket(ticketId);
    ticket.status = "resolved";
    ticket.resolution = resolution;
    ticket.resolvedBy = agentId;
    ticket.resolvedAt = new Date();

    // 知识沉淀：解决方案写入知识库
    await this.ingestToKnowledgeBase({
      question: this.extractQuestion(ticket.conversationHistory),
      answer: resolution,
      source: `ticket_${ticketId}`,
      agent: agentId,
      verified: true,
    });

    // 发送满意度调查
    await this.sendSatisfactionSurvey(ticket);
  }
}
```

### 端到端实现：CustomerServiceAgent

把意图识别、多轮对话、转人工策略、工单系统组合成一个完整的客服 Agent。

```python theme={null}
# Python 示例：完整的客服 Agent
class CustomerServiceAgent:
    def __init__(self, llm, knowledge_base, ticket_system):
        self.llm = llm
        self.kb = knowledge_base
        self.tickets = ticket_system
        self.conversations = {}  # session_id -> ConversationManager
        self.handoff = HumanHandoffStrategy()

    async def handle_message(self, session_id: str, user_message: str) -> dict:
        # 获取会话管理器
        conv = self.conversations.get(session_id) or ConversationManager(self.llm, self.kb)

        # 1. 意图识别
        intent = await conv.classify_intent(user_message, conv.history)

        # 2. 知识库检索（RAG）
        context = await self.kb.search(user_message, filter={"intent": intent["intent"]})

        # 3. 生成回答
        response = await conv.generate_response(intent, context)

        # 4. 转人工判断
        handoff_decision = self.handoff.should_transfer(
            user_message, response, {"history": conv.history}
        )

        if handoff_decision["transfer"]:
            # 创建工单并转人工
            ticket = await self.tickets.create_ticket(
                {"id": session_id, "user_id": conv.user_id,
                 "intent": intent["intent"], "history": conv.history},
                handoff_reason="; ".join(handoff_decision["reasons"]),
            )
            return {
                "action": "transfer_to_human",
                "message": "正在为您转接人工客服，请稍候...",
                "ticket_id": ticket["id"],
            }

        # 5. 记录对话
        conv.history.append({"role": "user", "content": user_message})
        conv.history.append({"role": "assistant", "content": response["content"]})
        self.conversations[session_id] = conv

        return {"action": "respond", "content": response["content"]}
```

```javascript theme={null}
// JavaScript 示例：完整的客服 Agent
class CustomerServiceAgent {
  constructor(llm, knowledgeBase, ticketSystem) {
    this.llm = llm;
    this.kb = knowledgeBase;
    this.tickets = ticketSystem;
    this.conversations = new Map();
    this.handoff = new HumanHandoffStrategy();
  }

  async handleMessage(sessionId, userMessage) {
    const conv =
      this.conversations.get(sessionId) ||
      new ConversationManager(this.llm, this.kb);

    // 1. 意图识别
    const intent = await conv.classifyIntent(userMessage, conv.history);

    // 2. 知识库检索（RAG）
    const context = await this.kb.search(userMessage, {
      filter: { intent: intent.intent },
    });

    // 3. 生成回答
    const response = await conv.generateResponse(intent, context);

    // 4. 转人工判断
    const handoffDecision = this.handoff.shouldTransfer(userMessage, response, {
      history: conv.history,
    });

    if (handoffDecision.transfer) {
      const ticket = await this.tickets.createTicket(
        {
          id: sessionId,
          userId: conv.userId,
          intent: intent.intent,
          history: conv.history,
        },
        handoffDecision.reasons.join("; "),
      );
      return {
        action: "transfer_to_human",
        message: "正在为您转接人工客服，请稍候...",
        ticketId: ticket.id,
      };
    }

    // 5. 记录对话
    conv.history.push({ role: "user", content: userMessage });
    conv.history.push({ role: "assistant", content: response.content });
    this.conversations.set(sessionId, conv);

    return { action: "respond", content: response.content };
  }
}
```

> [!NOTE]
>
> AI Agent 客服系统的核心是"RAG 检索 + 意图识别 + 多轮对话 + 转人工策略 + 工单闭环"五位一体。意图识别用 LLM + 小模型混合架构（高频意图小模型快速分类，低频意图 LLM 深度理解）；多轮对话维护槽位状态，逐步收集信息；转人工策略多维度判断（连续失败、情绪检测、敏感问题、用户主动要求）；工单闭环把人工解决方案沉淀回知识库，让 Agent 越用越聪明。

***

## Q11: RAG 评估体系（RAGAS、自动化评估）

RAG 的优化不能靠"感觉"，必须靠数据。RAGAS（Retrieval Augmented Generation Assessment）是 RAG 系统的权威评估框架，核心思想是**用 LLM 作为评判者，自动化评估 RAG 的检索质量和生成质量**。<sup>\[4]</sup>

```text theme={null}
RAGAS 评估流程：

准备测试集（Question + Ground Truth + Contexts）
              ↓
┌─────────────────────────────────────────┐
│            RAGAS 评估框架                 │
├─────────────┬───────────────────────────┤
│  检索质量    │        生成质量            │
│             │                           │
│ Context     │ Faithfulness（忠实度）     │
│ Precision   │ Answer Relevancy          │
│ （上下文精确度）│ （答案相关性）            │
│             │                           │
│ Context     │ Answer Correctness        │
│ Recall      │ （答案正确性）              │
│ （上下文召回率）│                           │
├─────────────┴───────────────────────────┤
│         综合评分（0-1）                    │
└─────────────────────────────────────────┘
              ↓
输出：每个指标的分数 + 详细评估报告
```

> [!TIP]
>
> RAGAS 就像一个自动化的考试阅卷系统。Faithfulness（忠实度）检查的是"你是不是抄了参考资料"——答案必须基于检索到的文档，不能自己编造。Answer Relevancy（答案相关性）检查的是"你有没有答非所问"——答案必须和问题相关。Context Precision（上下文精确度）检查的是"参考资料是不是对的"——检索到的文档必须和问题相关。Context Recall（上下文召回率）检查的是"参考资料够不够全"——是否检索到了所有相关文档。没有评估的 RAG 优化就像不看考试成绩就改教学方法——你根本不知道改对了没有。

### RAGAS 核心指标详解

| 指标 | 评估维度 | 计算方式 | 含义 |
| - | - | - | - |
| **Faithfulness（忠实度）** | 生成质量 | 答案中的每句话是否能从 Context 中找到依据 | 答案是否基于文档（不编造） |
| **Answer Relevancy（答案相关性）** | 生成质量 | 用 LLM 生成反向问题，计算与原问题的相似度 | 答案是否回答了问题 |
| **Context Precision（上下文精确度）** | 检索质量 | 相关文档在检索结果中的排名 | 检索结果排序是否合理 |
| **Context Recall（上下文召回率）** | 检索质量 | Ground Truth 中的关键信息是否被检索到 | 是否找到了所有相关文档 |
| **Answer Correctness（答案正确性）** | 端到端 | 答案与 Ground Truth 的语义相似度 | 最终答案是否正确 |

```javascript theme={null}
// RAGAS 评估实现
import { evaluate } from "ragas";
import { RagasLLM, RagasEmbeddings } from "ragas";

// 1. 准备测试集
const testDataset = [
  {
    question: "什么是 React Fiber 架构？",
    ground_truth: "Fiber 是 React 16 引入的协调算法...",
    contexts: ["Fiber 是 React 16 引入的增量渲染架构..."],
    answer: "Fiber 是 React 的协调算法，用于实现增量渲染...",
  },
  // ... 更多测试用例
];

// 2. 运行评估
const result = await evaluate(testDataset, {
  metrics: [
    "faithfulness",
    "answer_relevancy",
    "context_precision",
    "context_recall",
  ],
  llm: new RagasLLM(new ChatOpenAI({ model: "gpt-4o" })),
  embeddings: new RagasEmbeddings(new OpenAIEmbeddings()),
});

// 3. 输出评估报告
console.log(result);
// {
//   faithfulness: 0.92,
//   answer_relevancy: 0.88,
//   context_precision: 0.85,
//   context_recall: 0.78,
// }
```

### 自动化评估流水线

生产级 RAG 系统需要建立**自动化评估流水线**——每次优化（调整 chunkSize、换 Embedding 模型、加 Reranker）后，自动跑一遍 RAGAS 评估，对比前后指标变化。

```text theme={null}
自动化评估流水线：

代码变更（PR）
    ↓
CI/CD 触发评估
    ↓
┌─────────────────────────────┐
│ 1. 加载测试集（100+ 条 QA）   │
│ 2. 对每条 QA 运行 RAG Pipeline│
│ 3. RAGAS 计算各项指标         │
│ 4. 与基线对比，生成报告        │
└─────────────────────────────┘
    ↓
指标下降 → 阻断合并
指标提升 → 允许合并 + 更新基线
```

> [!WARNING]
>
> RAG 的优化不能靠"感觉"，必须靠数据。每次优化后（如调整 chunkSize、换 Embedding 模型、加 Reranker），都要用 RAGAS 跑一遍评估，看指标是否有提升。没有评估的优化就是盲人摸象。建议建立 100+ 条 QA 的测试集作为评估基线，覆盖高频问题、边缘场景、多跳问题。

> [!NOTE]
>
> RAGAS 是 RAG 系统的"考试阅卷系统"——Faithfulness 检查是否编造，Answer Relevancy 检查是否答非所问，Context Precision 检查检索排序是否合理，Context Recall 检查是否找到了所有相关文档。生产级 RAG 必须建立自动化评估流水线，每次优化后回归测试，用数据驱动迭代。

***

## Q12: 企业级 RAG 部署（离线预处理、混合索引、VLM Re-ranking、前端集成）

从 Demo 到生产级 RAG，需要解决**性能、成本、可靠性**三大工程挑战。<sup>\[5]</sup>

```text theme={null}
企业级 RAG 部署架构：

┌─────────────────────────────────────────────────┐
│                 离线预处理层                       │
│                                                   │
│  文档上传 → 解析（MinerU/LlamaParse）             │
│    → 切片 → 向量化 → 构建索引                      │
│    → 增量更新（只处理变化的文档）                    │
└─────────────────────────────────────────────────┘
                      ↓
┌─────────────────────────────────────────────────┐
│                 在线检索层                         │
│                                                   │
│  用户提问 → 查询改写 → 混合索引检索               │
│    → VLM Re-ranking → 上下文压缩                  │
│    → LLM 生成 → 答案 + 引用来源                   │
└─────────────────────────────────────────────────┘
                      ↓
┌─────────────────────────────────────────────────┐
│                 前端集成层                         │
│                                                   │
│  流式渲染（SSE）→ 引用来源高亮 → 反馈收集         │
│  → 评估驱动迭代                                   │
└─────────────────────────────────────────────────┘
```

> [!TIP]
>
> 企业级 RAG 就像一个大型图书馆的运营。离线预处理是"后台"——新书到了要编目、贴标签、上架（文档解析、切片、向量化），这个过程不需要用户等待，可以批量处理。在线检索是"前台"——用户来问问题，馆员要快速找到相关书籍（混合索引检索），然后精挑细选出最相关的几本（VLM Re-ranking），最后交给专家（LLM）回答。前端集成是"咨询服务台"——用户在这里提问、看到答案和出处、给出反馈。后台做得好（索引质量高），前台才能答得准（检索质量高）。

### 离线预处理 + 在线检索

企业级 RAG 的核心工程原则是**离线预处理和在线检索分离**。离线阶段处理文档解析、切片、向量化、索引构建（可以批量、异步、批处理）；在线阶段只做检索和生成（必须低延迟、高可用）。

```javascript theme={null}
// 离线预处理：文档上传 → 解析 → 切片 → 向量化 → 索引
// 这个过程可以异步执行，用户不需要等待
async function indexDocument(file) {
  // 1. 文档解析（MinerU/LlamaParse）
  const parsed = await minerU.parse(file);

  // 2. 切片
  const chunks = await splitter.splitDocuments(parsed.textBlocks);

  // 3. 向量化（批处理，降低成本）
  const embeddings = await embeddingModel.embedBatch(chunks, {
    batchSize: 100,
  });

  // 4. 存入向量数据库
  await vectorStore.addDocuments(chunks, embeddings);

  // 5. 更新索引版本
  await cache.invalidate("index_version");
}

// 在线检索：用户提问 → 检索 → 生成
// 这个过程必须低延迟（目标 < 2 秒）
async function answerQuestion(question) {
  // 1. 查询改写
  const rewritten = await rewriteQuery(question);

  // 2. 混合索引检索
  const candidates = await hybridSearch(rewritten, { k: 20 });

  // 3. VLM Re-ranking
  const ranked = await vlmRerank(question, candidates, { topN: 3 });

  // 4. LLM 生成
  const answer = await generateAnswer(question, ranked);

  return { answer, sources: ranked.map((d) => d.metadata.source) };
}
```

### 混合索引

混合索引是企业级 RAG 的标准配置——\*\*向量索引（语义检索）+ 倒排索引（关键词检索）+ 元数据索引（过滤）\*\*三者结合。

```text theme={null}
混合索引架构：

用户查询："React 18 的 concurrent 模式"
    ↓
┌──────────────────────────────────────┐
│          查询路由器                    │
├──────────┬───────────┬───────────────┤
│ 向量索引  │ 倒排索引   │ 元数据索引     │
│          │           │              │
│ 语义检索  │ 关键词检索  │ 过滤（日期/   │
│ "前端框架 │ "React 18" │  来源/权限）   │
│  的并发   │ "concurrent│              │
│  特性"   │  mode"     │              │
├──────────┴───────────┴───────────────┤
│          RRF 融合排序                  │
└──────────────────────────────────────┘
    ↓
Top-K 结果
```

### RAG with VLM Re-ranking

VLM（Vision Language Model）Re-ranking 是 2025 年的前沿优化——用多模态大模型对检索结果进行精排，而不仅仅是用 Cross-Encoder。VLM Re-ranking 的优势是**能同时理解文本和图片的相关性**，特别适合多模态 RAG 场景。

```javascript theme={null}
// VLM Re-ranking：用多模态大模型精排
async function vlmRerank(query, candidates, { topN = 3 }) {
  const scores = await Promise.all(
    candidates.map(async (doc) => {
      // 对于包含图片的文档，VLM 能同时评估文本和图片的相关性
      const response = await openai.chat.completions.create({
        model: "gpt-4o",
        messages: [
          {
            role: "user",
            content: [
              {
                type: "text",
                text: `查询：${query}\n\n文档：${doc.pageContent}\n\n请评估相关性（0-10 分）：`,
              },
              ...(doc.images
                ? doc.images.map((img) => ({
                    type: "image_url",
                    image_url: { url: img },
                  }))
                : []),
            ],
          },
        ],
        max_tokens: 10,
      });
      return { doc, score: parseFloat(response.choices[0].message.content) };
    }),
  );

  return scores
    .sort((a, b) => b.score - a.score)
    .slice(0, topN)
    .map((s) => s.doc);
}
```

### 前端集成最佳实践

前端与 RAG 系统的集成需要关注**流式渲染、引用来源展示、用户反馈收集**三个核心体验。

| 最佳实践 | 实现方式 | 用户体验价值 |
| - | - | - |
| **流式渲染（SSE）** | 后端用 Server-Sent Events 逐字返回答案 | 用户不用等全部生成完，实时看到答案 |
| **引用来源高亮** | 答案中标注 `[1]`、`[2]`，点击跳转到原文 | 用户可以验证答案的可信度 |
| **反馈收集（Thumbs Up/Down）** | 每个答案下方加反馈按钮 | 收集用户满意度数据，驱动评估迭代 |
| **答案置信度展示** | 低置信度答案标注"仅供参考" | 降低用户对错误答案的信任 |

```javascript theme={null}
// 前端流式渲染 RAG 答案
const response = await fetch("/api/rag/query", {
  method: "POST",
  body: JSON.stringify({ question: "什么是 Fiber 架构？" }),
});

const reader = response.body.getReader();
const decoder = new TextDecoder();

while (true) {
  const { done, value } = await reader.read();
  if (done) break;

  const chunk = decoder.decode(value);
  // 解析 SSE 事件，逐字渲染到页面
  const events = parseSSE(chunk);
  for (const event of events) {
    if (event.type === "token") {
      appendToken(event.data); // 逐字追加到 DOM
    } else if (event.type === "source") {
      showCitation(event.data); // 显示引用来源 [1]
    }
  }
}
```

> [!NOTE]
>
> 企业级 RAG 部署的核心是"离线/在线分离"——离线阶段做文档解析、切片、向量化（批量异步），在线阶段只做检索和生成（低延迟高可用）。混合索引（向量 + 倒排 + 元数据）是标准配置，VLM Re-ranking 是前沿优化（多模态精排），前端集成关注流式渲染和引用来源展示。

***

## Q13: 高频面试题

**每个 Q 下面必须有 `###` 子标题（延伸知识），用于深入解释该 Q 的主题。这些子标题不是面试问题，而是对主题的深度扩展。**

> [!TIP]
>
> RAG 面试的核心就像侦探破案。切片策略是"怎么整理案卷"——案卷太厚翻不过来（切片太大），太碎又丢失上下文（切片太小）。检索策略是"怎么找线索"——语义检索靠直觉（"这个人可疑"），关键词检索靠证据（"指纹匹配"），混合检索两者结合效果最好。Reranker 是"线索精筛"——先粗筛出 20 条线索，再精挑出最相关的 3 条给侦探（LLM）分析。RAGAS 评估是"结案报告审核"——每次破案后都要审核质量，不能靠感觉。

### RAG 的核心流程是什么？

RAG 的核心流程是**索引 → 检索 → 生成**三个阶段：

1. **索引阶段**：文档加载 → 文档切片（RecursiveCharacterTextSplitter，chunkSize 500-1500，overlap 10-20%） → 向量化（Embedding 模型） → 存入向量数据库
2. **检索阶段**：用户提问 → 问题向量化 → 向量相似度检索（返回 Top-K 个切片） → 可选：Reranker 精排
3. **生成阶段**：检索到的切片作为上下文 → 拼接 Prompt → 调用 LLM → 生成答案

质量瓶颈在**检索**——检索不到正确的文档，LLM 再强也答不对。优化优先级：切片策略 > 检索策略 > Embedding 模型 > LLM 能力。

### 向量数据库怎么选？

向量数据库选型取决于三个维度：**规模、运维能力、是否需要混合检索**。

* **小项目/本地开发**：Chroma（零配置，嵌入式）
* **快速原型/小团队**：Pinecone（全托管，无需运维）
* **中等规模**：Weaviate（原生支持混合检索）或 Qdrant（Rust 实现，高性能）
* **大规模生产**：Milvus（支持亿级向量，性能最强）
* **已有 PostgreSQL**：pgvector（零成本引入，不需要额外维护向量数据库）

### RAG 的质量怎么优化？

RAG 质量优化从四个维度入手：

1. **切片策略**：按语义切 > 递归切 > 按段落切。chunkSize 推荐 500-1500，overlap 推荐 10-20%。
2. **检索策略**：混合检索（向量 + BM25，权重 0.6:0.4）效果最好。加 Reranker（Cross-Encoder）精排，通常能提升 20-30% 准确率。
3. **查询优化**：查询改写（把口语化问题转为检索友好的表述）、HyDE（用假设性答案检索）、多路召回（拆成多个子问题分别检索）。
4. **上下文优化**：上下文压缩（提取切片中与问题相关的部分，减少噪声）、自适应检索（Agent 自主决定是否需要检索）。

### RAG 和 Fine-tuning 的区别？

核心区别是**解决的问题不同**：

* **RAG** 解决的是"LLM 不知道"的问题——知识注入。让 LLM 查资料而不是背书。适用于知识问答、文档检索。成本低，实时更新。
* **Fine-tuning** 解决的是"LLM 不会做"的问题——能力注入。让 LLM 学会某种风格或格式。适用于风格迁移、特定格式输出。成本高，需要重新训练。

大多数企业场景用 RAG 就够了。Fine-tuning 只在需要特定输出风格或领域术语理解时考虑。两者也可以组合：Fine-tuning 让模型理解领域术语，RAG 提供最新知识。

### n8n 和 Dify 的区别？

两者解决的问题不同：

* **n8n** 是通用的工作流自动化工具——把各种 API 和服务连接起来，AI 只是其中一个节点。适用于跨系统自动化、定时任务、数据同步。目标用户是运维和开发人员。
* **Dify** 是 AI 专项的 LLM 应用开发平台——内置 RAG、Agent、工作流，专注于 LLM 应用的快速搭建。适用于企业知识库、AI 客服、内部文档问答。目标用户包括产品经理和业务人员。

两者可以组合：Dify 提供 RAG 和 Agent 能力，n8n 负责把 Dify 接入到更大的自动化工作流中。

### RAGAS 评估指标有哪些？

RAGAS（Retrieval Augmented Generation Assessment）是 RAG 系统的权威评估框架，核心指标包括：

| 指标 | 评估维度 | 含义 |
| - | - | - |
| **Faithfulness（忠实度）** | 生成质量 | 答案是否基于检索到的文档（不编造） |
| **Answer Relevancy（答案相关性）** | 生成质量 | 答案是否与问题相关 |
| **Context Precision（上下文精确度）** | 检索质量 | 检索到的文档是否与问题相关 |
| **Context Recall（上下文召回率）** | 检索质量 | 是否检索到了所有相关文档 |

> [!WARNING]
>
> RAG 的优化不能靠"感觉"，必须靠数据。每次优化后（如调整 chunkSize、换 Embedding 模型、加 Reranker），都要用 RAGAS 跑一遍评估，看指标是否有提升。没有评估的优化就是盲人摸象。

### 如何处理 RAG 中的多跳问题？

多跳问题是指需要多次检索才能回答的问题。例如："React 的并发模式用了什么调度算法？这个算法是谁提出的？"

```text theme={null}
多跳 RAG 流程：

第 1 跳：问题 "React 的并发模式用了什么调度算法？"
   → 检索 → 答案："时间片调度（Time Slicing）"

第 2 跳：新问题 "时间片调度是谁提出的？"
   → 检索 → 答案："由 Andrew Clark 设计"

最终答案：合并两次检索的结果
```

实现方式：用 LangGraph 的条件路由——第一次检索后判断是否需要继续检索，如果需要就用第一次的答案构造新查询，再次检索。

### 生产级 RAG 需要考虑哪些问题？

生产级 RAG 需要考虑 5 个层面：

1. **文档更新**：增量索引（只索引变化的文档），而不是每次全量重建。支持多种格式（PDF、Word、网页、数据库）。
2. **性能优化**：缓存热门查询的结果，Embedding 模型批量化处理，向量数据库的索引优化（HNSW / IVF）。
3. **安全与权限**：文档级别的访问控制——不同用户只能检索到自己有权限的文档。向量数据库需要支持元数据过滤。
4. **监控与可观测性**：追踪每个 RAG 请求的检索质量（命中率、相关性分数）、生成质量（忠实度、答案相关性）、延迟和成本。
5. **容错与降级**：向量数据库不可用时降级为 BM25 关键词检索；LLM 不可用时返回检索到的原始文档片段。

> [!NOTE]
>
> RAG 面试的核心是理解"检索质量决定最终质量"——切片策略、Embedding 模型、检索策略、Reranker 四个环节环环相扣。高级话题包括 HyDE、多路召回、自适应检索、多跳推理。实际项目中，先用 RAGAS 建立评估基线，再逐步优化。

***

## 🔍 底层源码：向量检索是如何实现的？

向量数据库是怎么在百万级向量中快速找到最相似的 Top-K 个？

主流向量数据库使用 **HNSW（Hierarchical Navigable Small World）** 算法实现近似最近邻搜索。核心思想是构建一个多层的图结构，从顶层开始贪心搜索，逐层下降，最终在底层找到最近邻（详见 [HNSW 论文](https://arxiv.org/abs/1603.09320)）：

```text theme={null}
HNSW 的多层图结构：

Layer 2（最稀疏）:  A ──────── D
                   / \         |
                  /   \        |
Layer 1:        B ── C ── E ── F
                |    |    |    |
Layer 0（最密集）: G ─ H ─ I ─ J ─ K ─ L

搜索过程：
1. 从 Layer 2 的入口点 A 开始
2. 在当前层贪心搜索，找到离目标最近的节点
3. 下降到下一层，继续贪心搜索
4. 重复直到 Layer 0，返回 Top-K 个最近邻
```

HNSW 的时间复杂度是 O(log N)，比暴力搜索的 O(N) 快得多。100 万条 1536 维的向量，暴力搜索需要几秒，HNSW 只需要几毫秒。

BM25 的底层实现原理是什么？

BM25 基于\*\*词频（TF）和逆文档频率（IDF）\*\*的统计模型（详见 [Lucene BM25 实现](https://github.com/apache/lucene)）：

```text theme={null}
BM25 公式：
score(D, Q) = Σ IDF(qi) * (f(qi, D) * (k1 + 1)) / (f(qi, D) + k1 * (1 - b + b * |D|/avgdl))

其中：
- IDF(qi)：词 qi 的逆文档频率（越稀有的词权重越高）
- f(qi, D)：词 qi 在文档 D 中的词频
- |D|/avgdl：文档长度与平均文档长度的比值（归一化）
- k1, b：超参数（通常 k1=1.2, b=0.75）
```

BM25 的优势是精确关键词匹配，劣势是无法理解语义（"汽车"和"轿车"是完全不同的词）。所以实际项目中用混合检索：向量检索捕获语义，BM25 捕获关键词。

***

## Q14: 满分回答 vs 及格回答

### 问题："说说 RAG 的核心原理和质量优化策略"

**及格回答（60-70分）：**

> "RAG 就是先检索文档，再让 LLM 根据文档回答。流程是文档切片、向量化、检索、生成。质量优化可以用更好的 Embedding 模型。"

问题出在：**知道流程但不理解瓶颈**。

**满分回答（90-100分）：**

> "RAG 的核心是**检索增强生成**——先从知识库中检索相关文档，再把文档作为上下文注入 Prompt，让 LLM 基于真实文档回答，而不是依赖训练时记住的知识。完整流程分三个阶段：索引（文档加载 → 切片 → 向量化 → 存入向量数据库）、检索（问题向量化 → 相似度检索 → 返回 Top-K）、生成（拼接 Prompt → 调用 LLM → 生成答案）。<sup>\[1]</sup>"

> "RAG 的质量瓶颈不在 LLM，而在**检索**。检索不到正确的文档，LLM 再强也答不对。优化有四个维度：1) 切片策略——推荐递归切片，chunkSize 500-1500，overlap 10-20%；2) 检索策略——混合检索（向量 + BM25，权重 0.6:0.4）效果最好，加 Reranker（Cross-Encoder）精排通常能明显提升检索精度；3) 查询优化——查询改写、HyDE、多路召回；4) 上下文优化——上下文压缩减少噪声。<sup>\[2]</sup>"

> "实际项目中，我会先用 RAGAS 建立评估基线（Faithfulness、Answer Relevancy、Context Precision、Context Recall），然后逐步优化。优先级：切片策略 > 混合检索 + Reranker > 查询改写 > Embedding 模型升级。没有评估的优化就是盲人摸象。<sup>\[3]</sup>"

> "企业级部署还需要考虑多模态 RAG（用 MinerU/LlamaParse 解析 PDF 图文混排，用 GPT-4V 提取图片语义，用 CLIP/SigLIP 实现跨模态向量检索）、行业合规（金融要求每句话有法规出处，医疗要求病历脱敏）、以及离线/在线分离架构（离线批量预处理文档，在线低延迟检索生成）。前端集成关注流式渲染（SSE）和引用来源展示。<sup>\[4]</sup>"

### 常见追问

> 题目来源于真实面经（字节/腾讯/阿里前端面试、牛客网、haizlin/fe-interview），按难度递进。每个模块 **8 个 Accordion**，覆盖 4 个难度层级。

> [accordion] RAG 的核心流程是什么?
>
> **答题要点：**
> 说出索引、检索、生成三个阶段，以及每个阶段的关键步骤。
>
> **高分回答：**
> "RAG 的核心流程分三个阶段：
>
> 1. **索引阶段**：文档加载 → 文档切片（RecursiveCharacterTextSplitter，chunkSize 500-1500，overlap 10-20%） → 向量化（Embedding 模型把文本转为高维向量） → 存入向量数据库。
>
> 2. **检索阶段**：用户提问 → 问题向量化 → 向量相似度检索（返回 Top-K 个最相似的切片） → 可选：Reranker 精排。
>
> 3. **生成阶段**：检索到的切片作为上下文 → 拼接 Prompt → 调用 LLM → 生成答案。
>
> 关键点：质量瓶颈在检索——检索不到正确的文档，LLM 再强也答不对。"

> [accordion] 向量数据库怎么选?
>
> **答题要点：**
> 从规模、运维能力、检索场景三个维度分析，重点区分纯向量库、图数据库、搜索引擎的适用场景。
>
> **高分回答：**
> "向量数据库选型取决于三个维度：
>
> 1. **规模**：小项目用 Chroma（零配置，嵌入式），中等规模用 Qdrant 或 Weaviate，大规模生产用 Milvus（支持亿级向量）。
>
> 2. **运维能力**：没有运维能力的团队用 Pinecone（全托管，开箱即用），有运维能力的团队自部署 Milvus 或 Qdrant。
>
> 3. **检索场景**：纯语义检索选专用向量库（Milvus/Pinecone）；需要关键词 + 语义混合检索选 ElasticSearch（原生 BM25 + kNN RRF 融合）；需要实体关系推理选 Neo4j（Graph RAG，结合图遍历和向量检索）。已有 PostgreSQL 的团队可以用 pgvector（零成本引入）。
>
> 实际建议：先用 Chroma 本地开发验证效果，上线时根据场景选型——技术文档搜索用 ElasticSearch 或 Easysearch（国产替代，API 兼容，内置中文分词），知识图谱用 Neo4j，纯语义检索用 Milvus。"

> [accordion] RAG 的切片策略怎么选?
>
> **答题要点：**
> 从语义完整性、检索精确性、性能三个维度分析。
>
> **高分回答：**
> "切片策略的选择取决于文档类型和检索需求：
>
> 1. **递归切片**（RecursiveCharacterTextSplitter）：最通用，先按段落切，再按句子切，最后按字符切。chunkSize 推荐 500-1500 字符，overlap 10-20%。
>
> 2. **语义切片**（SemanticChunker）：用 Embedding 计算相邻句子的相似度，在相似度低的地方切分。效果最好，但最慢（需要调用 Embedding API）。
>
> 3. **按段落切**（MarkdownHeaderTextSplitter）：适合 Markdown 文档，保留文档结构。
>
> 关键原则：每个切片应该是一个完整的语义单元，能独立回答一个问题。chunkSize 太小语义不完整，太大包含太多无关信息。"

> [accordion] 混合检索为什么比纯向量检索好?
>
> **答题要点：**
> 说出向量检索和 BM25 各自的优缺点，以及混合检索如何互补。
>
> **高分回答：**
> "纯向量检索的缺陷：当用户搜索'React 18 的 concurrent 模式'时，向量检索可能返回'Vue 3 的响应式'——因为它们在语义空间中距离很近（都是'前端框架的核心特性'），但关键词完全不同。
>
> BM25 的缺陷：当用户搜索'怎么优化首屏加载'时，BM25 无法匹配'如何提升 FCP 指标'——因为关键词完全不同，但语义完全一致。
>
> 混合检索 = 向量检索（捕获语义）+ BM25（捕获关键词），两者的权重可以通过实验调优。LangChain 的 EnsembleRetriever 默认权重是 0.5:0.5，实际项目中推荐 0.6:0.4（偏向语义检索）。"

> [accordion] RAG 和 Fine-tuning 的区别?
>
> **答题要点：**
> 从知识注入和能力注入两个维度区分，结合实际场景说明。
>
> **高分回答：**
> "RAG 和 Fine-tuning 解决的是完全不同类型的问题：
>
> RAG 解决的是'LLM 不知道'的问题——知识注入。典型场景：公司制度问答、产品文档查询。数据流：知识库 → 检索 → LLM 上下文 → 生成。成本低，实时更新。
>
> Fine-tuning 解决的是'LLM 不会做'的问题——能力注入。典型场景：特定语气写作、特定格式输出（SQL/JSON）。数据流：训练数据 → 微调模型 → 新的模型行为。成本高，需要重新训练。
>
> 选择标准：大多数场景用 RAG 就够了。Fine-tuning 只在两种场景下考虑：1) 需要特定的输出风格；2) 需要理解领域专用术语。两者也可以组合：Fine-tuning 让模型理解领域术语，RAG 提供最新知识。"

> [accordion] n8n 和 Dify 的区别?
>
> **答题要点：**
> 从核心定位、RAG 能力、目标用户三个维度对比。
>
> **高分回答：**
> "n8n 和 Dify 解决的问题不同：
>
> n8n 是通用的工作流自动化工具——把各种 API 和服务连接起来，AI 只是其中一个节点。适用于跨系统自动化、定时任务、数据同步。目标用户是运维和开发人员。与 LLM 集成需要通过 HTTP 请求节点调用 API。
>
> Dify 是 AI 专项的 LLM 应用开发平台——内置 RAG、Agent、工作流，专注于 LLM 应用的快速搭建。适用于企业知识库、AI 客服。目标用户包括产品经理和业务人员。
>
> 两者可以组合：Dify 提供 RAG 和 Agent 能力，n8n 负责把 Dify 接入到更大的自动化工作流中。"

> [accordion] 🔥 Reranking 的原理是什么?为什么需要两阶段检索?
>
> **答题要点：**
> 说出 Bi-Encoder 和 Cross-Encoder 的区别，以及两阶段检索架构的设计动机。
>
> **高分回答：**
> "Reranking 的核心是两阶段检索架构：第一阶段用 Bi-Encoder 粗筛（快但不精确），第二阶段用 Cross-Encoder 精排（慢但精确）。
>
> Bi-Encoder 的工作方式：问题和文档分别编码为向量，再计算余弦相似度。速度快（向量可以预计算），但'分别编码'会丢失问题和文档之间的细粒度交互信息。
>
> Cross-Encoder 的工作方式：把问题和文档拼接在一起，输入同一个模型计算相关性分数。能捕获更细粒度的语义交互（比如否定词和肯定词的矛盾），但速度慢（每对都要单独计算，无法预计算）。
>
> 实际架构：向量检索 Top-20（Bi-Encoder，毫秒级）→ Reranker 精排取 Top-3（Cross-Encoder，百毫秒级）。这是一个经典的'召回-精排'两阶段架构。2025 年的前沿方向是 VLM Re-ranking——用多模态大模型（GPT-4o）精排，能同时理解文本和图片的相关性，特别适合多模态 RAG 场景。"

> [accordion] 🔥 如何搭建一个生产级的 RAG 系统?
>
> **答题要点：**
> 从架构设计、向量数据库选型、质量优化、监控评估四个维度回答。
>
> **高分回答：**
> "生产级 RAG 的架构设计需要考虑 5 个层面：
>
> 1. **文档层**：支持多种格式（PDF、Word、网页、数据库），用 MinerU 或 LlamaParse 解析。关键是处理好表格、图片、代码块等复杂格式——多模态 RAG 需要用 GPT-4V/Qwen-VL 提取图片语义，用 CLIP/SigLIP 做跨模态向量检索。
>
> 2. **切片层**：推荐递归切片（chunkSize 500-1500，overlap 10-20%）。Markdown 文档用 MarkdownHeaderTextSplitter 保留结构。
>
> 3. **存储层**：小项目用 Chroma，中等项目用 Pinecone，大规模生产用 Milvus 或 Qdrant。需要关键词 + 语义混合检索用 ElasticSearch 或 Easysearch（国产替代，兼容 ES API，内置中文分词，Apache 2.0 协议），需要知识图谱用 Neo4j。
>
> 4. **检索层**：混合检索（向量 + BM25，权重 0.6:0.4）+ Reranker 精排。加入查询改写和多路召回。
>
> 5. **评估层**：用 RAGAS 框架评估忠实度、相关性、准确性。建立自动化评估流水线，每次优化后回归测试。
>
> 此外还需要考虑：增量索引（不是每次全量重建）、文档级权限控制、缓存热门查询、向量数据库不可用时降级为 BM25、行业合规（金融要求法规出处，医疗要求病历脱敏）。"

***

## 📚 延伸阅读与引用参考

* 📖 [Harrison Chase - RAG from Scratch (LangChain)](https://blog.langchain.dev/)：*LangChain 创始人 Harrison Chase 的 RAG 系列教程，从索引、检索、生成三阶段系统性讲解 RAG 架构设计，强调"检索质量决定最终质量"。* <sup>\[1]</sup>
* 🔗 [RAG 论文原文](https://arxiv.org/abs/2005.11401)：*Meta AI 在 2020 年发表的 RAG 原始论文，理解检索增强生成的理论基础。* <sup>\[2]</sup>
* 📖 [Lilian Weng - LLM Powered Autonomous Agents](https://lilianweng.github.io/posts/2023-06-23-agent/)：*对 LLM Agent 架构的系统梳理，其中讨论了检索、规划、记忆等组件在 RAG 类应用中的作用。* <sup>\[3]</sup>
* 🔗 [LangChain RAG 教程](https://js.langchain.com/docs/tutorials/rag/)：*LangChain 官方的 RAG 实战教程，包含从文档加载到检索生成的完整流程。* <sup>\[4]</sup>
* 🟢 [MTEB Leaderboard](https://huggingface.co/spaces/mteb/leaderboard)：*Embedding 模型的权威评测排行榜，选型时的参考标准。* <sup>\[5]</sup>
* 📖 [Chip Huyen - AI Engineering (2025)](https://huyenchip.com/ai-engineering/)：*系统性梳理 RAG 架构的工程化落地方法论，涵盖 Naive RAG、Advanced RAG、Agentic RAG 的演进路线。* <sup>\[6]</sup>
* 🔗 [RAGAS 评估框架](https://docs.ragas.io/)：*RAG 系统的权威评估框架，包含 Faithfulness、Relevancy 等核心指标的定义和使用方法。* <sup>\[7]</sup>
* 🔗 [Milvus 官方文档](https://milvus.io/docs)：*大规模向量数据库 Milvus 的官方文档，理解 HNSW 索引、混合检索等底层原理。* <sup>\[8]</sup>
* 🔗 [n8n 官方文档](https://docs.n8n.io/)：*开源工作流自动化工具 n8n 的官方文档，了解节点、连接、触发器的核心概念。* <sup>\[9]</sup>
* 🔗 [Dify 官方文档](https://docs.dify.ai/)：*开源 LLM 应用开发平台 Dify 的官方文档，了解知识库、应用、工作流的核心能力。* <sup>\[10]</sup>
* 🔗 [Neo4j Vector Index 文档](https://neo4j.com/docs/cypher-manual/current/indexes-for-vectors/)：*Neo4j 原生向量索引的官方文档，理解图数据库如何支持向量检索和 Graph RAG。* <sup>\[11]</sup>
* 🔗 [ElasticSearch kNN Search 文档](https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html)：*ElasticSearch 向量检索和混合检索（RRF）的官方文档，理解 BM25 + 向量融合的实现原理。* <sup>\[12]</sup>
* 🔗 [Easysearch 官方文档（极限科技 INFINI Labs）](https://docs.easysearch.io.cn/docs/overview/)：*ElasticSearch 的国产替代方案，兼容 ES API，内置中文分词和向量检索，Apache 2.0 开源协议，适合国内企业合规场景。* <sup>\[21]</sup>
* 📖 [Microsoft GraphRAG 论文](https://arxiv.org/abs/2404.16130)：*微软 2024 年发表的 GraphRAG 论文，提出基于知识图谱的 RAG 架构，解决传统 RAG 无法进行多跳推理的问题。* <sup>\[13]</sup>
* 🔗 [MinerU (OpenDataLab)](https://github.com/opendatalab/MinerU)：*开源 PDF 解析工具，支持版面分析、OCR、公式识别、表格提取，保留文档结构，是多模态 RAG 的核心组件。* <sup>\[14]</sup>
* 🔗 [LlamaParse (LlamaIndex)](https://docs.cloud.llamaindex.ai/llamaparse)：*LlamaIndex 的托管文档解析服务，深度集成 LlamaIndex 生态，支持复杂 PDF/PPT 解析。* <sup>\[15]</sup>
* 🔗 [Docling (IBM Research)](https://github.com/DS4SD/docling)：*IBM 开源的文档转换库，支持 90+ 文件格式，提供文档级理解和多格式导出。* <sup>\[16]</sup>
* 📖 [OpenAI CLIP 论文](https://arxiv.org/abs/2103.00020)：*OpenAI 发表的对比语言-图像预训练模型，开创了图文对比学习范式，是多模态 RAG 嵌入层的基础。* <sup>\[17]</sup>
* 📖 [Anthropic Engineering Blog](https://www.anthropic.com/engineering)：*Anthropic 的工程博客，讨论 MCP 等底层标准与 RAG 相关实践。* <sup>\[20]</sup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.