Skip to content

RAG 面试专题 ​

RAG 面试主线:为什么用 RAG → 文档处理 → Embedding → 向量检索 → 检索优化 → 质量评估 → 工程实践。

Q1: 什么是 RAG?详细描述一个完整 RAG 系统的详细工作流程? 「🟢 校招/初级」 ​

考察点:RAG 基本概念和完整工作流。

参考答案:

RAG(Retrieval-Augmented Generation)= 检索增强生成

核心思想:在 LLM 生成回答之前,先从外部知识库中检索相关信息,将检索到的内容作为上下文注入 Prompt,让 LLM 基于检索到的信息生成回答。

完整 RAG 工作流程:

┌─────────────────────────────────────────────────────────────┐
│                    RAG 系统完整流程                          │
│                                                             │
│  【离线阶段:知识库构建】                                     │
│  原始文档 → 加载 → 切分 → Embedding → 存入向量数据库        │
│                                                             │
│  【在线阶段:检索生成】                                      │
│  用户问题 → Query 改写 → 向量检索 → 重排序 → 组装 Prompt     │
│           → LLM 生成 → 后处理 → 返回答案                     │
└─────────────────────────────────────────────────────────────┘

离线阶段详解:

1. 文档加载(Document Loading)
   PDF/Word/HTML/Markdown → 统一为文本

2. 文档切分(Chunking)
   长文档 → 切分为语义完整的片段(Chunk)
   常用策略:固定长度 + 重叠、按段落、按语义

3. 向量化(Embedding)
   每个 Chunk → Embedding 模型 → 向量表示(768/1536 维)

4. 存储(Indexing)
   向量 + 原文 + 元数据 → 存入向量数据库

在线阶段详解:

1. Query 处理
   用户问题 → [可选] Query 改写/扩展 → 向量化

2. 检索(Retrieval)
   Query 向量 → 向量数据库 → Top-K 相似 Chunk

3. 重排序(Reranking)
   Top-K Chunk → Cross-Encoder 重排序 → Top-N 最相关

4. 上下文组装
   System Prompt + 检索到的 Chunk + 用户问题 → 完整 Prompt

5. LLM 生成
   完整 Prompt → LLM → 生成回答

6. 后处理
   引用标注、格式化、事实校验 → 最终输出
python
# RAG 完整流程实现
class RAGSystem:
    def __init__(self, llm, embed_model, vector_store, reranker=None):
        self.llm = llm
        self.embed_model = embed_model
        self.vector_store = vector_store
        self.reranker = reranker

    # ===== 离线:知识库构建 =====
    def ingest(self, documents):
        """文档入库"""
        chunks = []
        for doc in documents:
            # 1. 切分
            chunks.extend(self._split(doc))

        # 2. 向量化
        embeddings = [self.embed_model.embed(c.text) for c in chunks]

        # 3. 存储
        self.vector_store.add(
            embeddings=embeddings,
            documents=[c.text for c in chunks],
            metadatas=[c.metadata for c in chunks]
        )

    # ===== 在线:检索生成 =====
    def query(self, question):
        """用户查询"""
        # 1. Query 向量化
        query_emb = self.embed_model.embed(question)

        # 2. 向量检索
        results = self.vector_store.search(query_emb, top_k=10)

        # 3. 重排序
        if self.reranker:
            results = self.reranker.rerank(question, results, top_n=5)

        # 4. 组装 Prompt
        context = "\n\n".join(r.text for r in results[:5])
        prompt = f"""基于以下参考信息回答问题。

参考信息:
{context}

问题:{question}

要求:基于参考信息回答,不要编造。如果参考信息不足,请说明。
"""

        # 5. LLM 生成
        answer = self.llm.generate(prompt)

        return {"answer": answer, "sources": results[:5]}

追问延伸:

  • RAG 和传统搜索引擎有什么区别?(RAG 用 LLM 生成,搜索引擎只返回文档)
  • 为什么不直接把所有文档塞给 LLM?(上下文窗口有限、成本高、噪声大)

Q2: 大模型的 RAG 主要用来解决什么问题? 「🟢 校招/初级」 ​

考察点:RAG 解决的核心痛点。

参考答案:

RAG 主要解决 LLM 的三大问题:

问题说明RAG 解决方式
知识截止LLM 训练数据有截止日期,不知道新信息实时检索最新知识
幻觉问题LLM 会编造看起来合理但错误的信息基于检索到的真实信息回答
私有知识LLM 不知道企业内部数据/私有文档检索私有知识库

1. 知识截止问题:

LLM 知识截止到训练时间(如 GPT-4 截止 2023 年)

用户:"2025 年最新的 Agent 框架有哪些?"
LLM 直接回答 → 可能编造或信息过时

RAG:
  → 检索最新文档 → 获得真实最新信息
  → 基于检索结果回答 → 信息准确

2. 幻觉问题:

LLM 直接回答 → 可能"一本正经地胡说八道"

RAG:
  → 检索真实文档 → 给 LLM 提供事实依据
  → LLM 基于上下文回答 → 大幅减少幻觉
  → 还可以要求 LLM 标注引用来源 → 可验证

3. 私有知识问题:

LLM 不知道企业的 API 文档、业务规则、内部知识

RAG:
  → 向量数据库存储企业私有文档
  → 检索相关文档 → 注入上下文
  → LLM 基于企业知识回答

4. 额外解决的问题:

问题RAG 价值
成本不需要微调模型,用 RAG 即可注入新知识
时效性知识库可实时更新,无需重新训练
可控性知识来源可控,可追溯引用
多语言检索原文,LLM 翻译/总结
权限控制按用户权限检索不同文档

追问延伸:

  • RAG 能完全消除幻觉吗?(不能,但可以大幅减少,还需要事实校验)
  • RAG 和给 LLM 提示"不知道就说不知道"有什么区别?(RAG 提供了真实信息源)

Q3: 相比直接微调 LLM,RAG 解决了什么问题?微调和 RAG 各自的优劣势是什么? 「🟡 中级」 ​

考察点:RAG vs 微调的对比选型。

参考答案:

维度RAG微调(Fine-tuning)
知识更新实时更新(改文档即可)需要重新训练
成本低(不需要训练)高(GPU + 数据标注)
幻觉可减少(基于检索信息)改善有限
知识量理论无限(向量库可扩展)受模型容量限制
可解释性高(可追溯引用来源)低(知识内化在参数中)
响应速度多一步检索,略慢直接推理,快
风格/格式不擅长改变输出风格擅长(可以训练特定风格)
领域适应提供领域知识改变模型行为模式
数据安全知识库可控权限模型可能泄露训练数据

各自擅长什么:

RAG 擅长:
  ✓ 知识密集型任务(问答、文档分析)
  ✓ 需要实时更新的知识
  ✓ 需要引用来源的场景
  ✓ 多源知识融合

微调擅长:
  ✓ 风格/格式定制(代码风格、写作风格)
  ✓ 特定任务优化(分类、抽取、翻译)
  ✓ 减少示例(few-shot → zero-shot)
  ✓ 改变模型行为模式

选型决策:

需求是什么?
├── 需要新知识/实时信息 → RAG
├── 需要改变输出风格 → 微调
├── 需要特定任务能力 → 微调
├── 两者都需要 → RAG + 微调
└── 成本敏感 → RAG(先做 RAG,效果不够再微调)

组合方案(RAG + 微调):

微调:让模型更擅长检索增强问答的格式和风格
RAG:提供实时准确的知识

效果:1+1 > 2
  - 微调后的模型更会利用检索到的信息
  - RAG 提供知识,微调提供能力

追问延伸:

  • 什么时候必须用微调?(模型需要新能力而非新知识时,如学新语言)
  • RAG + LoRA 微调的组合怎么做先用?(先建 RAG,再在 RAG 数据上做 LoRA 微调)

Q4: RAG 中的文档是怎么存的?粒度是多大?详细说说文档切割(Chunking)策略? 「🟡 中级」 ​

考察点:文档处理和 Chunking 策略。

参考答案:

文档存储方式:

原文 + Embedding 向量 + 元数据 → 存入向量数据库

一条记录的结构:
{
  "id": "doc_001_chunk_003",
  "text": "这是切分后的文本片段...",
  "embedding": [0.12, -0.34, 0.56, ...],  // 768/1536维
  "metadata": {
    "source": "handbook.pdf",
    "page": 15,
    "section": "第三章 安全规范",
    "chunk_index": 3,
    "timestamp": "2025-08-25"
  }
}

Chunk 粒度的选择:

粒度大小优点缺点适用场景
粗粒度1000-2000 字上下文完整检索精度低、Token 多长文档摘要
中粒度200-500 字平衡精度和上下文需要调参通用 RAG
细粒度50-100 字检索精度高上下文不完整精确问答
自适应不固定按语义切分实现复杂高质量 RAG

推荐粒度:200-500 字 + 50-100 字重叠

Chunking 策略:

1. 固定长度切分(Fixed-size):

python
def fixed_size_chunking(text, chunk_size=500, overlap=50):
    """固定长度 + 重叠切分"""
    chunks = []
    start = 0
    while start < len(text):
        end = start + chunk_size
        chunk = text[start:end]
        chunks.append(chunk)
        start = end - overlap  # 重叠部分
    return chunks
优点:简单、快速
缺点:可能切断句子/段落
适合:均匀文本(日志、数据)

2. 按分隔符切分(Separator-based):

python
def separator_chunking(text, separators=None):
    """按分隔符切分"""
    if separators is None:
        separators = ["\n\n", "\n", "。", "!", "?", ".", "!", "?"]

    chunks = []
    # 递归尝试不同分隔符
    for sep in separators:
        parts = text.split(sep)
        if len(parts) > 1:
            for part in parts:
                if len(part) > 500:
                    # 太长则继续切
                    chunks.extend(separator_chunking(part, separators[1:]))
                else:
                    chunks.append(part.strip())
            break
    return chunks
优点:保持语义完整性
缺点:Chunk 大小不均
适合:结构化文档(Markdown、HTML)

3. 按语义切分(Semantic Chunking):

python
def semantic_chunking(text, embed_model, threshold=0.5):
    """基于语义相似度切分"""
    sentences = split_sentences(text)

    # 计算相邻句子的语义相似度
    embeddings = [embed_model.embed(s) for s in sentences]

    chunks = [sentences[0]]
    for i in range(1, len(sentences)):
        similarity = cosine_sim(embeddings[i-1], embeddings[i])
        if similarity > threshold:
            # 语义相似,合并到同一 Chunk
            chunks[-1] += sentences[i]
        else:
            # 语义不连续,开始新 Chunk
            chunks.append(sentences[i])

    return chunks
优点:语义完整性最好
缺点:计算成本高(每句都要 Embedding)
适合:高质量 RAG 系统

4. 父子文档切分(Parent-Child):

检索时:用细粒度的子 Chunk(精确匹配)
生成时:返回粗粒度的父 Chunk(上下文完整)

文档结构:
  Parent Chunk(1000 字)
    ├── Child Chunk 1(200 字)
    ├── Child Chunk 2(200 字)
    └── Child Chunk 3(200 字)

检索 → 命中 Child 2
返回 → 整个 Parent Chunk(上下文完整)
python
def parent_child_chunking(text, parent_size=1000, child_size=200):
    """父子文档切分"""
    parents = fixed_size_chunking(text, parent_size, overlap=0)
    result = []
    for i, parent in enumerate(parents):
        children = fixed_size_chunking(parent, child_size, overlap=50)
        result.append({
            "parent_id": f"parent_{i}",
            "parent_text": parent,
            "children": [
                {"id": f"parent_{i}_child_{j}", "text": c}
                for j, c in enumerate(children)
            ]
        })
    return result

5. 表格/代码特殊处理:

表格 → 保持完整不切分,或转为 Markdown 表格
代码 → 按函数/类切分,不切断代码块
图片 → 用多模态模型生成描述文本

Chunking 参数调优建议:

参数推荐值说明
chunk_size300-500通用场景
overlap50-10010-20% 的 chunk_size
separator按优先级\n\n > \n > 。 > .
min_chunk_size50太短的 Chunk 没有检索价值

追问延伸:

  • Chunk 大小对检索效果的影响?(太大→噪声多,太小→上下文不足)
  • 怎么处理超长文档的切分?(层级切分 + 摘要索引)

Q5: 怎么规避语义被切割掉的问题? 「🟡 中级** ​

考察点:Chunking 中的语义完整性保障。

参考答案:

语义被切割的常见场景:

问题切分:
  "Agent 的记忆模块包括短期记忆和长期记忆,长期记忆存储在"  ← 被切断了
  "向量数据库中,短期记忆存储在上下文窗口中。"  ← 上下文断裂

检索时:
  检索到后半句 → 缺少前半句的上下文 → LLM 不知道"长期记忆"指的是什么

规避策略:

1. 重叠切分(Overlap):

Chunk 1: "...Agent 的记忆模块包括短期记忆和长期记忆,长期记忆存储在向量数据库中"
Chunk 2: "长期记忆存储在向量数据库中,短期记忆存储在上下文窗口中..."

重叠部分确保边界信息不丢失

2. 递归分隔符切分:

python
# 按优先级递归切分,尽量在自然边界处切
separators = [
    "\n## ",      # Markdown 标题
    "\n### ",
    "\n\n",       # 段落
    "\n",         # 行
    "。",         # 句号
    ";",         # 分号
    ",",         # 逗号(最后才在逗号处切)
]

3. 上下文增强(Contextual Enrichment):

python
def enrich_chunk(chunk, document_title, section_title, prev_chunk_summary):
    """为每个 Chunk 添加上下文信息"""
    return f"""
文档标题:{document_title}
章节标题:{section_title}
前文摘要:{prev_chunk_summary}

正文:{chunk.text}
"""
原始 Chunk: "它支持事务和 ACID 特性"
增强后: 
  "文档标题:数据库选型指南
   章节标题:PostgreSQL 介绍
   前文摘要:PostgreSQL 是一个开源关系型数据库
   正文:它支持事务和 ACID 特性"

→ LLM 知道"它"指的是 PostgreSQL

4. 父子文档切分(推荐方案):

检索:子 Chunk(精确匹配)
生成:父 Chunk(上下文完整)

→ 根本不会出现语义被切割的问题

5. 后检索拼接(Post-retrieval Concatenation):

python
def post_retrieval_expand(query, retrieved_chunks, vector_store):
    """检索后扩展上下文"""
    expanded = []
    for chunk in retrieved_chunks:
        # 获取前后相邻的 Chunk
        prev = vector_store.get_by_index(chunk.metadata["chunk_index"] - 1)
        next_c = vector_store.get_by_index(chunk.metadata["chunk_index"] + 1)
        # 拼接完整上下文
        full_context = f"{prev.text}\n{chunk.text}\n{next_c.text}"
        expanded.append(full_context)
    return expanded

6. 语义切分(Semantic Chunking):

python
# 只在语义断点处切分,不在语义中间切
# 相邻句子语义相似度低于阈值时切分

7. 文档结构感知切分:

按文档结构切分,不在结构中间断开:
  - Markdown:按标题层级切分
  - HTML:按 DOM 节点切分
  - PDF:按页/章节切分
  - 代码:按函数/类切分

追问延伸:

  • 重叠切分的重叠比例怎么定?(10-20% 为宜,太小没效果,太大浪费)
  • 父子切分的存储成本?(多存一份父 Chunk,但检索用子 Chunk,空间换质量)

Q6: 在 RAG 中 Embedding 是什么?如何选择和评估一个 Embedding 模型? 「🟡 中级」 ​

考察点:Embedding 的原理和模型选型。

参考答案:

Embedding 是什么:

Embedding = 将文本转化为密集向量表示

"今天天气很好" → [0.12, -0.34, 0.56, ..., 0.78]  (768/1536维)

语义相近的文本 → 向量距离近
"今天天气很好" → [0.12, -0.34, ...]
"今日天气不错" → [0.11, -0.32, ...]  ← 语义相近,向量也近
"股票大涨"     → [-0.45, 0.67, ...]  ← 语义无关,向量距离远

Embedding 在 RAG 中的作用:

文档端:Chunk → Embedding → 存入向量库
查询端:Query → Embedding → 与库中向量做相似度检索 → 返回最相关的 Chunk

如何选择 Embedding 模型:

维度考量示例
语言支持中文/英文/多语言bge-m3 支持多语言
维度大小768/1024/1536维度高表达能力强但存储大
模型大小100M-7B影响推理速度和部署成本
领域适配通用/特定领域法律、医疗等领域模型
最大输入512/2048/8192 tokens影响可处理的 Chunk 大小
开源/商用免费/付费OpenAI text-embedding-3 付费

主流 Embedding 模型:

模型维度语言特点适用场景
text-embedding-3-large3072多语言OpenAI 最新,效果好商用、高质量
text-embedding-3-small1536多语言性价比高通用商用
bge-m31024多语言开源最强之一私有部署
bge-large-zh1024中文中文效果好中文场景
gte-large1024多语言阿里开源通用
jina-embeddings-v31024多语言长文本支持好长文档 RAG
e5-mistral-7b4096多语言基于 LLM高质量要求

如何评估 Embedding 模型:

1. 检索指标评估:

python
def evaluate_embedding(embed_model, test_dataset):
    """
    test_dataset: [(query, relevant_docs, irrelevant_docs), ...]
    """
    results = []
    for query, relevant, irrelevant in test_dataset:
        query_emb = embed_model.embed(query)
        doc_embs = [embed_model.embed(d) for d in relevant + irrelevant]

        # 计算相似度排序
        scores = [cosine_sim(query_emb, de) for de in doc_embs]
        ranked = sorted(zip(relevant + irrelevant, scores), key=lambda x: -x[1])

        # 计算 MRR / NDCG / Recall
        results.append({
            "recall@5": recall_at_k(ranked, relevant, k=5),
            "mrr": mrr(ranked, relevant),
            "ndcg": ndcg(ranked, relevant)
        })
    return aggregate(results)

2. 评估指标:

指标说明期望值
Recall@K前 K 个结果中相关文档占比> 0.8
MRR第一个相关文档的排名倒数> 0.7
NDCG考虑排名位置的归一化增益> 0.8
Hit Rate至少命中一个相关文档的比例> 0.9

3. MTEB 排行榜:

MTEB(Massive Text Embedding Benchmark)
  → 评估 Embedding 模型在多个任务上的综合表现
  → 网址:https://huggingface.co/spaces/mteb/leaderboard
  → 按语言/任务筛选,选适合的模型

选型建议:

中文场景 → bge-large-zh 或 bge-m3
多语言 → bge-m3 或 text-embedding-3-small
私有部署 → BGE 系列(开源)
快速原型 → OpenAI API(无需部署)
高质量 → text-embedding-3-large 或 e5-mistral-7b

追问延伸:

  • Embedding 维度越高越好吗?(不一定,维度高存储和检索成本也高)
  • 可以自己微调 Embedding 模型吗?(可以,用对比学习在领域数据上微调)

Q7: Embedding 有哪几种算法你了解过吗? 「🔴 高级」 ​

考察点:Embedding 技术的底层算法理解。

参考答案:

Embedding 算法演进:

Word2Vec → BERT → SimCSE → BGE → LLM-based
 2013      2018    2021     2023    2024+

1. Word2Vec(2013):

方法:CBOW / Skip-gram
原理:通过上下文预测(或被预测)学习词向量

特点:
  - 词级别,不考虑上下文
  - 静态向量:同一个词在不同语境下向量相同
  - "苹果"(水果)和"苹果"(公司)向量一样 → 语义混淆

2. BERT(2018):

方法:Transformer Encoder + 双向注意力
原理:预训练 + 微调

特点:
  - 句子级别,考虑上下文
  - 动态向量:同一个词在不同语境下向量不同
  - 通常取 [CLS] token 的输出作为句向量
  - 问题:句向量质量不高(各向异性,向量分布不均匀)

3. SimCSE(Simple Contrastive Learning, 2021):

方法:对比学习
原理:正例对(相似句子)拉近,负例对(不相似句子)推远

Dropout SimCSE:
  同一个句子过两次模型(不同 Dropout)→ 正例对
  不同句子 → 负例对

特点:
  - 显式优化句向量质量
  - 无监督版本不需要标注数据
  - 句向量各向异性问题大幅改善

4. BGE(BAAI General Embedding, 2023):

方法: RetroMAE 预训练 + 对比学习微调
原理:
  - 预训练:重建被 Mask 的 Token
  - 微调:指令增强的对比学习(Instruction + Query + Document)

特点:
  - 支持指令:不同任务用不同前缀
    "为这个句子生成表示以用于检索相关文档:"
  - 中英文效果好
  - 开源可用

5. LLM-based Embedding(2024+):

方法:用 LLM 本身做 Embedding
原理:取 LLM 的隐层输出作为向量

代表:
  - E5-Mistral(基于 Mistral-7B)
  - LLM2Vec
  - NV-Embed

特点:
  - 维度高(4096+)
  - 效果好但成本高
  - 适合离线 Embedding,不适合实时

6. 其他算法:

算法类型特点
GloVe词级别基于共现矩阵分解
FastText词级别考虑子词(subword)
Sentence-BERT句子级别双塔模型,效率高
ColBERT检索延迟交互(late interaction),效果介于双塔和交叉编码之间

双塔 vs 交叉编码:

双塔模型(Bi-Encoder):
  Query → Encoder → 向量
  Doc → Encoder → 向量
  → 计算相似度

  优点:Doc 可离线 Embedding,检索快
  缺点:Query 和 Doc 交互不充分

交叉编码(Cross-Encoder):
  [Query, Doc] → Encoder → 相似度分数

  优点:交互充分,效果好
  缺点:不能离线处理,速度慢
  用途:Reranking 阶段

追问延伸:

  • 为什么不用 BERT 直接做 Embedding?(向量各向异性,检索效果差)
  • ColBERT 的延迟交互是什么?(保留 Token 级别向量,检索时做细粒度匹配)

Q8: 什么是向量数据库?有没有做过向量数据库的对比选型? 「🟡 中级」 ​

考察点:向量数据库的原理和选型。

参考答案:

向量数据库是什么:

向量数据库 = 专门存储和检索向量的数据库

核心功能:
  1. 存储:高维向量(768-4096维)+ 元数据
  2. 检索:近似最近邻(ANN)搜索
  3. 过滤:元数据过滤 + 向量检索组合
  4. 增删改:动态更新向量数据

主流向量数据库对比:

数据库类型语言特点适用场景
Milvus分布式C++/Go支持百亿级向量,云原生大规模生产
Pinecone云服务-全托管,免运维快速上线
Chroma轻量级Python嵌入式,简单易用原型/小规模
Qdrant独立服务Rust性能好,过滤强中大规模
Weaviate全功能Go内置向量化和检索多模态
pgvectorPostgreSQL 扩展SQL复用 PG 生态已有 PG 环境
Elasticsearch搜索引擎Java向量 + 全文混合检索已有 ES 环境
FAISS库C++高性能 ANN 库离线/嵌入

选型维度:

维度评估点
数据规模10 万 / 100 万 / 1 亿 / 10 亿
检索延迟< 10ms / < 50ms / < 200ms
可用性单机 / 集群 / 云服务
运维成本自部署 / 全托管
元数据过滤支持 / 不支持 / 灵活程度
混合检索向量 + 关键词
动态更新是否支持实时增删改
成本开源 / 付费

选型建议:

快速原型 → Chroma(嵌入式,零配置)
中小规模 → Qdrant(性能好,易部署)
大规模生产 → Milvus(百亿级,云原生)
已有 PostgreSQL → pgvector(无需新引入)
已有 ES → Elasticsearch(复用现有设施)
不想运维 → Pinecone(全托管)
混合检索需求 → Weaviate / Elasticsearch

追问延伸:

  • 向量数据库为什么比传统数据库快?(ANN 算法,牺牲精度换速度)
  • 向量检索的精度和速度怎么平衡?(调整 nprobe/ef 参数,精度越高越慢)

Q9: 讲讲你用的向量数据库?数据量级是多大?性能如何?遇到过性能瓶颈吗? 「🔴 高级」 ​

考察点:向量数据库的实际使用经验。

参考答案:

以 Milvus 为例(面试时替换为自己实际使用的):

基本配置:

向量数据库:Milvus 2.4
部署方式:Docker / K8s 集群
数据量:约 500 万条向量
维度:1024(bge-m3)
索引类型:HNSW
检索 Top-K:10

性能指标:

指标数值
检索延迟(P99)< 30ms
QPS500+
写入速度5000 条/秒
存储大小约 20GB
索引构建时间约 30 分钟

遇到的性能瓶颈和解决方案:

1. 检索延迟突增:

现象:偶尔检索延迟从 30ms 飙到 500ms
原因:HNSW 索引在数据增长后 ef 参数太小,召回率不够
解决:
  - 调大 ef_search 参数:64 → 128
  - 延迟回到 50ms,召回率提升到 98%

2. 元数据过滤慢:

现象:带过滤条件的检索比纯向量检索慢 10 倍
原因:先向量检索再过滤,大量结果被过滤掉
解决:
  - 使用 Milvus 的标量索引加速过滤
  - 调整检索数量:top_k=100 → 过滤 → 取 top 10
  - 或使用分区策略:按部门/时间分区

3. 批量写入 OOM:

现象:大批量写入时内存溢出
原因:一次性插入太多向量,Embedding 计算占用大量内存
解决:
  - 分批写入:每批 1000 条
  - 使用流式 Embedding 计算
  - 增加服务器内存

4. 索引构建时间长:

现象:500 万条向量建 HNSW 索引要 30 分钟
解决:
  - 使用 IVF 索引(构建快但精度略低)
  - 或 HNSW + 增量更新(不重建索引)

监控指标:

python
# 关键监控项
metrics = {
    "search_latency_p99": "< 50ms",
    "search_qps": "> 500",
    "index_size": "约 20GB",
    "memory_usage": "< 80%",
    "disk_usage": "< 70%",
    "recall_rate": "> 95%",
    "error_rate": "< 0.1%"
}

追问延伸:

  • HNSW 和 IVF 索引怎么选?(HNSW 精度高但内存大,IVF 内存小但需训练)
  • 向量数据库怎么做高可用?(Milvus 集群 + 多副本)

Q10: 你使用 RAG 给大模型一个输入,系统是怎样的工作流程? 「🟡 中级」 ​

考察点:RAG 系统的端到端流程。

参考答案:

用户视角的完整流程:

用户输入:"公司请假流程是什么?"

RAG 系统内部流程:

1. Query 接收
   → 接收用户问题:"公司请假流程是什么?"

2. Query 预处理
   → Query 改写:扩展为多个相关查询
   → "公司请假流程" "请假申请流程" "假期审批流程"
   → 向量化每个查询

3. 多路检索
   → 向量检索:检索 Top 10 相关 Chunk
   → 关键词检索:BM25 检索 Top 10
   → 合并去重:15 条结果

4. 重排序
   → Cross-Encoder 对 15 条结果重排序
   → 取 Top 5 最相关

5. 上下文组装
   → 将 5 条 Chunk 拼接为上下文
   → 添加引用元数据

6. Prompt 构建
   System: 你是公司知识库助手,基于以下信息回答。
   Context:
   [1] 员工请假流程:首先在 OA 系统提交申请...
   [2] 请假审批流程:直属领导审批 → HR 审批...
   [3] 年假规定:年假需提前 3 天申请...
   User: 公司请假流程是什么?

7. LLM 生成
   → LLM 基于上下文生成回答
   → "公司请假流程如下:
        1. 在 OA 系统提交请假申请 [1]
        2. 直属领导审批 [2]
        3. HR 审批 [2]
        4. 年假需提前 3 天申请 [3]"

8. 后处理
   → 添加引用标注
   → 格式化输出
   → 返回给用户
python
def rag_pipeline(question, components):
    # 1. Query 改写
    queries = components["query_rewriter"].rewrite(question)

    # 2. 多路检索
    vec_results = components["vector_store"].batch_search(
        [components["embed_model"].embed(q) for q in queries],
        top_k=10
    )
    keyword_results = components["bm25"].search(question, top_k=10)
    all_results = deduplicate(vec_results + keyword_results)

    # 3. 重排序
    reranked = components["reranker"].rerank(question, all_results, top_n=5)

    # 4. 组装上下文
    context = format_context(reranked)

    # 5. 构建 Prompt
    prompt = f"""
    你是知识库助手。基于以下参考信息回答问题。
    如果参考信息不足以回答,请说明。

    参考信息:
    {context}

    问题:{question}
    """

    # 6. LLM 生成
    answer = components["llm"].generate(prompt)

    # 7. 后处理
    answer_with_citations = add_citations(answer, reranked)

    return {
        "answer": answer_with_citations,
        "sources": reranked,
        "latency": {
            "retrieval": "...",
            "rerank": "...",
            "generation": "...",
        }
    }

追问延伸:

  • 怎么减少端到端延迟?(异步检索 + 缓存 + 流式生成)
  • 用户问的问题和知识库不相关怎么办?(意图检测 → 不走 RAG → 直接回答或拒答)

Q11: 请你介绍一下向量检索和关键词检索的区别? 「🟡 中级」 ​

考察点:两种检索方式的对比和互补。

参考答案:

维度向量检索(Dense)关键词检索(Sparse/BM25)
原理语义相似度(向量距离)词频匹配(TF-IDF)
检索方式Embedding → ANN倒排索引
优势理解语义、同义词、近义词精确匹配、专有名词、ID
劣势精确匹配差、计算成本高不理解语义、同义词
适合自然语言问答、概念查询代码搜索、精确名称查询

具体例子:

用户查询:"怎么防止缓存穿透"

向量检索(语义匹配):
  ✓ "缓存击穿的解决方案" → 语义相似,命中
  ✓ "布隆过滤器在缓存中的应用" → 语义相关,命中
  ✗ "防止缓存穿透" → 完全匹配但也命中(向量也能匹配)

关键词检索(词匹配):
  ✗ "缓存击穿的解决方案" → 没有完全匹配的词
  ✓ "防止缓存穿透的最佳实践" → 关键词匹配
  ✓ "缓存穿透 缓存击穿 布隆过滤器" → 包含关键词
用户查询:"ERROR_10001"

向量检索:
  ✗ 可能检索到语义相关但不包含 ERROR_10001 的文档
  → 精确错误码检索效果差

关键词检索:
  ✓ 精确匹配包含 ERROR_10001 的文档
  → 精确查询效果好

混合检索(Hybrid Search):

python
def hybrid_search(query, vector_store, bm25_index, alpha=0.5):
    """向量检索 + 关键词检索 + 融合"""
    # 向量检索
    query_emb = embed_model.embed(query)
    vec_results = vector_store.search(query_emb, top_k=10)

    # 关键词检索
    keyword_results = bm25_index.search(query, top_k=10)

    # 融合排序(Reciprocal Rank Fusion)
    fused = rrf_fusion(vec_results, keyword_results, k=60)

    return fused[:10]

def rrf_fusion(vec_results, keyword_results, k=60):
    """RRF 融合算法"""
    scores = {}
    for rank, doc in enumerate(vec_results):
        scores[doc.id] = scores.get(doc.id, 0) + 1 / (k + rank + 1)
    for rank, doc in enumerate(keyword_results):
        scores[doc.id] = scores.get(doc.id, 0) + 1 / (k + rank + 1)
    return sorted(scores.items(), key=lambda x: -x[1])

结论:

生产 RAG 系统推荐:混合检索
  向量检索 → 语义理解
  关键词检索 → 精确匹配
  RRF 融合 → 两者优势互补

追问延伸:

  • 什么时候纯向量检索就够了?(文档都是自然语言、没有专有名词/代码)
  • RRF 的 k 参数是什么意思?(平滑参数,k 越大排名差异越平滑)

Q12: 如何润色用户的 Query(Query Rewrite)?目的是什么? 「🟡 中级」 ​

考察点:Query 改写的策略和目的。

参考答案:

为什么要 Query Rewrite:

用户输入往往:
  1. 太短 → 信息不足,检索效果差
  2. 口语化 → 与文档语言风格不匹配
  3. 有歧义 → 多种理解,需要消歧
  4. 单一查询 → 漏掉相关信息

Query Rewrite 策略:

1. Query 扩展(Expansion):

原始 Query: "缓存穿透"
扩展后: "缓存穿透 原因 解决方案 布隆过滤器 缓存击穿"

2. Query 改写(Paraphrasing):

python
def query_paraphrase(query, llm):
    """用 LLM 改写为更规范的查询"""
    prompt = f"""将以下口语化问题改写为更清晰、规范的检索查询:
    原始:{query}
    改写:"""
    return llm.generate(prompt)

# 示例
# 原始: "那个缓存老出问题咋办"
# 改写: "缓存故障排查和解决方案"

3. 多查询生成(Multi-Query):

python
def multi_query(query, llm, n=3):
    """生成多个不同角度的查询"""
    prompt = f"""为以下问题生成 {n} 个不同角度的检索查询:
    问题:{query}
    要求:每个查询从不同角度表述,用于多路检索。
    """
    response = llm.generate(prompt)
    return parse_queries(response)

# 示例
# 问题: "怎么提高 RAG 准确率"
# 查询1: "RAG 系统准确率优化方法"
# 查询2: "检索增强生成质量提升策略"
# 查询3: "RAG 检索质量和生成质量评估"

4. HyDE(Hypothetical Document Embeddings):

python
def hyde(query, llm, embed_model):
    """生成假设性文档,用文档做检索"""
    # 1. 让 LLM 假设答案
    hypothetical_answer = llm.generate(
        f"请简短回答以下问题(用于检索):{query}"
    )
    # 2. 用假设答案的向量做检索(答案比问题更接近文档)
    hyp_emb = embed_model.embed(hypothetical_answer)
    results = vector_store.search(hyp_emb, top_k=10)
    return results

# 原理:问题向量 → 答案向量 → 答案与文档更相似
# "怎么防止缓存穿透" → 假设答案 "使用布隆过滤器..." → 检索到相关文档

5. Query 分解(Decomposition):

复杂问题: "RAG 和微调的区别,什么时候用哪个?"
分解为:
  子查询1: "RAG 的优缺点"
  子查询2: "微调的优缺点"
  子查询3: "RAG 和微调选型标准"
  → 分别检索 → 合并上下文

6. 对话上下文改写:

对话历史:
  User: 什么是 RAG?
  Assistant: RAG 是检索增强生成...
  User: 它和微调比有什么优势?  ← "它"指代 RAG

改写后: "RAG 相比微调有什么优势?"  ← 消除指代
python
def contextualize_query(query, chat_history, llm):
    """结合对话历史改写当前查询"""
    prompt = f"""根据对话历史,改写当前问题为独立、完整的问题:

对话历史:
{chat_history}

当前问题:{query}

改写后的问题:"""
    return llm.generate(prompt)

Query Rewrite 的效果:

策略检索提升额外成本适用场景
多查询+15%N 次 LLM复杂查询
HyDE+10-20%1 次 LLM短查询
上下文改写必需1 次 LLM多轮对话
Query 扩展+5-10%1 次 LLM短查询

追问延伸:

  • HyDE 为什么有效?(答案向量比问题向量更接近文档向量)
  • Query Rewrite 增加了延迟,怎么平衡?(异步改写 + 缓存 + 按需启用)

Q13: 什么是多路召回?具体怎么做? 「🟡 中级」 ​

考察点:多路召回策略的工程实现。

参考答案:

多路召回 = 从多个渠道检索文档,融合结果

         ┌→ 向量检索(语义匹配)→ Top 10
Query ──┼→ 关键词检索(BM25) → Top 10
         ├→ 知识图谱检索 → Top 5
         └→ SQL 检索(结构化) → Top 5
                      ↓
              融合去重排序 → Top 5

为什么要多路召回:

单路召回的问题:
  纯向量检索 → 精确匹配差(专有名词、代码、ID)
  纯关键词检索 → 语义理解差(同义词、近义词)

多路召回:
  向量 → 语义覆盖
  关键词 → 精确匹配
  知识图谱 → 关系推理
  结构化 → 精确数据
  → 综合覆盖,互补不足

具体实现:

python
class MultiRouteRetriever:
    def __init__(self, vector_store, bm25_index, kg_store, sql_db, embed_model):
        self.vector_store = vector_store
        self.bm25 = bm25_index
        self.kg = kg_store
        self.sql = sql_db
        self.embed = embed_model

    def retrieve(self, query, top_k=10):
        # 并行检索
        results = []

        # 1. 向量检索
        query_emb = self.embed.embed(query)
        vec_results = self.vector_store.search(query_emb, top_k=top_k)
        results.extend([(r, "vector", rank) for rank, r in enumerate(vec_results)])

        # 2. 关键词检索
        kw_results = self.bm25.search(query, top_k=top_k)
        results.extend([(r, "keyword", rank) for rank, r in enumerate(kw_results)])

        # 3. 知识图谱检索(可选)
        if self.kg:
            kg_results = self.kg.query(query, top_k=5)
            results.extend([(r, "kg", rank) for rank, r in enumerate(kg_results)])

        # 4. 融合排序
        fused = self._rrf_fusion(results)
        return fused[:top_k]

    def _rrf_fusion(self, results, k=60):
        """Reciprocal Rank Fusion"""
        scores = {}
        docs = {}
        for doc, source, rank in results:
            doc_id = doc.id
            scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank + 1)
            docs[doc_id] = doc
        sorted_ids = sorted(scores.items(), key=lambda x: -x[1])
        return [docs[doc_id] for doc_id, _ in sorted_ids]

融合算法对比:

算法原理优点缺点
RRF排名倒数加权不需要分数归一化忽略分数大小
加权平均分数加权考虑分数需要分数归一化
Cascade逐级过滤可控串行慢
交集/并集集合操作简单信息丢失

多路召回的效果:

单路向量检索 Recall@10: 78%
单路关键词检索 Recall@10: 65%
混合检索 Recall@10: 91%  → +13% 提升

追问延伸:

  • 各路检索结果数量不一致怎么办?(统一 top_k,或按比例分配)
  • 多路召回增加了延迟怎么优化?(并行检索 + 超时降级)

Q14: RAG 检索优化策略有哪些? 「🔴 高级」 ​

考察点:RAG 检索的全面优化方法。

参考答案:

检索优化全景图:

┌─────────────────────────────────────┐
│         RAG 检索优化策略             │
│                                     │
│  【检索前】                          │
│  1. Query 改写/扩展                  │
│  2. 多查询生成                       │
│  3. HyDE                            │
│                                     │
│  【检索中】                          │
│  4. 多路混合检索                     │
│  5. 元数据过滤                       │
│  6. 分级检索                         │
│                                     │
│  【检索后】                          │
│  7. 重排序(Reranking)              │
│  8. 上下文压缩                       │
│  9. 去重与多样性                     │
│  10. 引用标注                        │
│                                     │
│  【系统级】                          │
│  11. 缓存                           │
│  12. 预计算                         │
│  13. 索引优化                       │
└─────────────────────────────────────┘

1. Query 改写(见 Q12)

2. 多路混合检索(见 Q13)

3. 重排序(Reranking):

python
# 用 Cross-Encoder 重排序
from sentence_transformers import CrossEncoder

reranker = CrossEncoder("BAAI/bge-reranker-large")

def rerank(query, documents, top_n=5):
    pairs = [(query, doc.text) for doc in documents]
    scores = reranker.predict(pairs)
    ranked = sorted(zip(documents, scores), key=lambda x: -x[1])
    return [doc for doc, _ in ranked[:top_n]]

为什么需要重排序:

向量检索(Bi-Encoder):
  快但粗 → Top 10 中可能有不相关的

重排序(Cross-Encoder):
  慢但精 → 从 Top 10 中选 Top 5 最相关的

流程:向量检索 Top 100 → 重排序选 Top 5

4. 上下文压缩:

python
def compress_context(query, documents, llm):
    """压缩检索到的文档,只保留与问题相关的部分"""
    compressed = []
    for doc in documents:
        # 用 LLM 提取与问题相关的内容
        relevant = llm.generate(
            f"从以下文本中提取与问题相关的信息,去掉无关内容:\n"
            f"文本:{doc.text}\n问题:{query}\n相关内容:"
        )
        if relevant.strip():
            compressed.append(relevant)
    return compressed

5. 去重与多样性:

python
def diversify_results(results, max_per_source=2):
    """限制同一来源的文档数量"""
    source_count = {}
    diversified = []
    for doc in results:
        source = doc.metadata.get("source", "unknown")
        count = source_count.get(source, 0)
        if count < max_per_source:
            diversified.append(doc)
            source_count[source] = count + 1
    return diversified

6. 缓存:

python
# 语义缓存:相似查询复用结果
class SemanticCache:
    def __init__(self, embed_model, vector_store, threshold=0.95):
        self.embed = embed_model
        self.cache_store = vector_store  # 专用向量库
        self.threshold = threshold

    def get(self, query):
        query_emb = self.embed.embed(query)
        results = self.cache_store.search(query_emb, top_k=1)
        if results and results[0].score > self.threshold:
            return results[0].answer  # 缓存命中
        return None

    def set(self, query, answer):
        emb = self.embed.embed(query)
        self.cache_store.add(emb, {"query": query, "answer": answer})

7. 分级检索:

Level 1:摘要检索(快)→ 粗筛
Level 2:全文检索(慢)→ 精选

流程:
  Query → 检索文档摘要 → 选 Top 10 相关文档
       → 对 Top 10 做全文检索 → 选 Top 3

优化效果汇总:

优化策略Recall 提升延迟增加实现难度
Query 改写+10-15%+200ms中
多路检索+10-15%+100ms(并行)中
重排序+5-10%+300ms低
上下文压缩Token -30%+200ms中
缓存命中时 -90%无增加中

追问延伸:

  • 重排序模型怎么选?(bge-reranker / cohere-rerank / 自训练)
  • 怎么平衡优化效果和延迟?(分级优化 + 异步 + 缓存)

Q15: 了解哪些更复杂的 RAG 范式? 「🔴 高级」 ​

考察点:RAG 前沿范式和进阶架构。

参考答案:

1. Self-RAG(自反思 RAG):

LLM 自主决定:是否需要检索 → 检索 → 评估检索质量 → 生成 → 自我批判

流程:
  1. 判断:这个问题需要检索吗?
     → 简单事实("1+1=?")→ 不检索,直接回答
     → 知识型问题 → 检索
  2. 检索:并行检索多个 Chunk
  3. 评估:每个 Chunk 相关吗?
     → 相关 → 使用
     → 不相关 → 丢弃
  4. 生成:基于相关 Chunk 生成
  5. 批判:回答有依据吗?需要再检索吗?

2. Corrective RAG(CRAG):

检索 → 评估检索质量 → 纠正

流程:
  1. 检索文档
  2. 评估文档相关性(Correct/Incorrect/Ambiguous)
  3. 如果 Correct → 直接用
  4. 如果 Incorrect → 触发网络搜索补充
  5. 如果 Ambiguous → 细化查询后重新检索
  6. 知识精炼 → 去噪、提取关键信息
  7. 生成回答

3. Adaptive RAG(自适应 RAG):

python
class AdaptiveRAG:
    """根据问题复杂度自适应选择策略"""
    def query(self, question):
        complexity = self._assess_complexity(question)

        if complexity == "simple":
            # 简单问题:单次检索
            return self._simple_rag(question)
        elif complexity == "moderate":
            # 中等:多路检索 + 重排序
            return self._multi_route_rag(question)
        else:
            # 复杂:迭代检索 + 反思
            return self._iterative_rag(question)

    def _iterative_rag(self, question, max_rounds=3):
        """迭代检索:多轮检索逐步补充信息"""
        context = ""
        for round_i in range(max_rounds):
            results = self._retrieve(question, context)
            context += "\n" + results
            # LLM 判断信息是否充分
            if self._is_sufficient(question, context):
                break
            # 不充分 → 生成补充查询
            question = self._generate_followup(question, context)
        return self._generate(question, context)

4. Graph RAG(知识图谱 RAG):

传统 RAG:文档 → 向量 → 检索
Graph RAG:文档 → 知识图谱 → 图检索

流程:
  1. 从文档中抽取实体和关系 → 构建知识图谱
  2. 用户问题 → 实体识别 → 图遍历
  3. 获取相关子图 + 实体描述
  4. LLM 基于图谱信息生成

优势:
  - 多跳推理(A→B→C 的关系链)
  - 全局摘要(不只是局部 Chunk)
  - 实体关系明确

5. Agentic RAG(Agent 驱动的 RAG):

Agent + RAG 结合:
  Agent 自主决定:
    - 是否需要检索
    - 检索什么
    - 检索结果够不够
    - 需不需要补充检索
    - 如何使用检索结果

特点:
  - 自主决策而非固定流程
  - 可以多轮检索
  - 可以调用多种工具(搜索+RAG+计算)

6. Modular RAG(模块化 RAG):

将 RAG 拆分为可组合的模块:
  - 检索模块(向量/关键词/图)
  - 排序模块
  - 压缩模块
  - 生成模块
  - 评估模块

按需组合,灵活配置

各范式对比:

范式复杂度效果成本适用场景
基础 RAG低基准低简单问答
Self-RAG中+10%中质量要求高
CRAG中+15%中需要纠错
Adaptive中+20%中问题类型多样
Graph RAG高+25%(多跳)高关系推理
Agentic高+30%最高复杂任务

追问延伸:

  • Graph RAG 怎么构建知识图谱?(LLM 抽取实体关系 + 图数据库存储)
  • Self-RAG 的反思 token 怎么训练?(标注反思数据 + 指令微调)

Q16: 在什么场景下,你会选择使用图数据库来增强传统的向量检索? 「🔴 高级」 ​

考察点:Graph RAG 的适用场景判断。

参考答案:

适合图数据库增强的场景:

1. 多跳推理(Multi-hop Reasoning):

问题:"张三的导师合作过哪些公司?"

传统 RAG:
  检索 "张三" → 可能只找到张三的个人信息
  → 无法找到 "导师→合作公司" 的关系链

Graph RAG:
  张三 → [导师是] → 李四 → [合作过] → 公司 A, B, C
  图遍历直接找到答案

2. 关系密集型领域:

领域实体关系适合 Graph RAG
医疗疾病、药物、症状治疗、引起、禁忌✓
法律法条、案例、当事人引用、适用、判决✓
金融公司、人、产品投资、控股、合作✓
研发模块、接口、团队依赖、负责、调用✓

3. 全局摘要需求:

传统 RAG:检索局部 Chunk → 缺乏全局视角
问题:"这家公司的主要业务方向有哪些?"

Graph RAG:
  构建公司实体图谱 → 所有业务实体 → 全局视图
  → 生成完整的业务方向摘要

4. 实体消歧:

问题:"苹果的市值是多少?"

传统 RAG:可能检索到水果"苹果"的文档
Graph RAG:实体 "苹果" → 上下文关联 "科技公司" → 消歧

Graph RAG + 向量检索结合:

python
class GraphRAG:
    def query(self, question):
        # 1. 实体识别
        entities = self.llm.extract_entities(question)

        # 2. 图检索:获取实体子图
        subgraph = self.graph_store.get_subgraph(entities, hops=2)

        # 3. 向量检索:补充语义相关信息
        vec_results = self.vector_store.search(
            self.embed.embed(question), top_k=5
        )

        # 4. 融合上下文
        context = format_graph(subgraph) + "\n" + format_docs(vec_results)

        # 5. LLM 生成
        return self.llm.generate(f"基于图谱和文档回答:\n{context}\n问题:{question}")

不适合 Graph RAG 的场景:

✗ 简单事实问答("今天天气如何")
✗ 文档内容为主(小说、新闻)
✗ 无明确实体关系(通用闲聊)
✗ 数据量小(不值得构建图谱)

追问延伸:

  • 知识图谱怎么自动构建?(LLM 抽取实体关系 → 存入 Neo4j)
  • Graph RAG 的构建成本高吗?(高,需要实体抽取 + 关系建模 + 图存储)

Q17: 如何规避 RAG 系统中大模型的幻觉? 「🟡 中级」 ​

考察点:RAG 中幻觉问题的缓解策略。

参考答案:

RAG 中幻觉的来源:

1. 检索不到相关信息 → LLM 编造答案
2. 检索到不相关信息 → LLM 被误导
3. 检索结果有矛盾 → LLM 选择性采信
4. LLM 自身知识干扰 → 不用检索结果而用自己的知识
5. Prompt 设计不当 → 没有明确约束 LLM 基于上下文回答

规避策略:

1. Prompt 约束:

System Prompt:
"你是一个严格基于参考信息回答的助手。
规则:
1. 只能基于提供的参考信息回答
2. 如果参考信息不足以回答,必须说'根据现有信息无法回答'
3. 不要使用你自己的知识补充
4. 每个陈述必须标注引用来源
5. 不要编造任何信息"

2. 检索质量保障:

python
def retrieve_with_confidence(query, vector_store, threshold=0.7):
    """只返回高置信度的检索结果"""
    results = vector_store.search(embed(query), top_k=10)
    # 过滤低相似度结果
    high_confidence = [r for r in results if r.score > threshold]
    if not high_confidence:
        return None  # 没有高置信度结果 → 不生成
    return high_confidence

3. 答案校验:

python
def verify_answer(answer, context, llm):
    """校验答案是否基于上下文"""
    verification = llm.generate(f"""
    判断以下回答是否完全基于参考信息,有没有编造内容:

    参考信息:{context}
    回答:{answer}

    判断:
    1. 回答中的每个事实是否都能在参考信息中找到?
    2. 有没有编造的信息?
    3. 结果:[PASS/FAIL] 原因:...
    """)
    return verification

4. 引用标注:

要求 LLM 标注每个事实的来源:

回答:"公司请假流程需要 OA 申请 [1],领导审批 [2]"

[1] 来源:员工手册第 15 页
[2] 来源:审批流程文档第 3 节

→ 用户可以验证每个事实

5. 置信度评估:

python
def assess_confidence(answer, sources):
    """评估答案的置信度"""
    # 因素1:检索结果相似度
    avg_similarity = mean(s.score for s in sources)

    # 因素2:答案覆盖的来源数
    source_coverage = len(set(s.id for s in sources if s.cited))

    # 因素3:校验通过率
    verification_pass = verify_answer(answer, sources)

    confidence = (avg_similarity * 0.4 +
                  source_coverage * 0.1 * 0.3 +
                  verification_pass * 0.3)

    if confidence < 0.5:
        return answer + "\n\n⚠️ 此回答置信度较低,建议人工确认。"
    return answer

6. 拒答机制:

当检索结果不足时,不生成答案而是拒答:

if len(high_confidence_results) == 0:
    return "抱歉,知识库中没有找到与您问题相关的信息。"

→ 宁可不回答,也不编造

追问延伸:

  • 怎么平衡"严格基于上下文"和"灵活回答"?(分场景:精确问答严格,闲聊灵活)
  • 幻觉检测有没有自动化工具?(SelfCheckGPT / 事实核查 API)

Q18: 怎么量化你的 RAG 效果? 「🔴 高级** ​

考察点:RAG 评估体系和方法。

参考答案:

RAG 评估的两个维度:

RAG = 检索(Retrieval)+ 生成(Generation)

评估也分两部分:
  1. 检索质量评估:检索到的文档准不准?
  2. 生成质量评估:生成的回答好不好?

1. 检索质量评估:

指标说明计算
Recall@K前 K 个结果中相关文档比例相关命中数 / 总相关数
Precision@K前 K 个结果中相关文档比例相关命中数 / K
MRR第一个相关文档的排名倒数1/第一个相关文档排名
NDCG考虑排名位置的归一化增益DCG / IDCG
Hit Rate至少命中一个的比例有命中的查询 / 总查询
python
def evaluate_retrieval(retriever, test_dataset, k=5):
    """
    test_dataset: [(query, relevant_doc_ids), ...]
    """
    metrics = {"recall@k": [], "mrr": [], "ndcg": []}

    for query, relevant_ids in test_dataset:
        retrieved = retriever.search(query, top_k=k)
        retrieved_ids = [r.id for r in retrieved]

        # Recall@K
        hit = len(set(retrieved_ids) & set(relevant_ids))
        metrics["recall@k"].append(hit / len(relevant_ids))

        # MRR
        for rank, rid in enumerate(retrieved_ids, 1):
            if rid in relevant_ids:
                metrics["mrr"].append(1 / rank)
                break
        else:
            metrics["mrr"].append(0)

    return {k: mean(v) for k, v in metrics.items()}

2. 生成质量评估:

指标说明
Faithfulness回答是否忠于检索到的上下文(不编造)
Answer Relevance回答是否与问题相关
Context Relevance检索到的上下文是否与问题相关
Context Recall检索是否覆盖了回答所需的全部信息

RAGAS 框架:

python
from ragas import evaluate
from ragas.metrics import (
    faithfulness,
    answer_relevancy,
    context_precision,
    context_recall
)

# 评估数据集格式
eval_data = {
    "question": ["公司请假流程是什么?"],
    "answer": ["请假需要OA申请,领导审批..."],
    "contexts": [["员工手册第15页...", "审批流程文档..."]],
    "ground_truth": ["请假流程:1.OA申请 2.领导审批 3.HR审批"]
}

results = evaluate(
    eval_data,
    metrics=[faithfulness, answer_relevancy, context_precision, context_recall]
)
# faithfulness: 0.85 (忠于上下文)
# answer_relevancy: 0.92 (回答相关)
# context_precision: 0.80 (检索精确)
# context_recall: 0.75 (检索召回)

3. 端到端评估:

指标说明
准确率回答正确的比例
完整性回答覆盖了所有要点
引用准确率引用标注是否正确
拒答率无法回答的比例(合理拒答是好的)
幻觉率编造信息的比例
延迟端到端响应时间
成本每次查询的 Token 消耗

4. 人工评估 vs 自动评估:

人工评估:
  - 黄金标准,但成本高
  - 适合小样本验证

自动评估(LLM-as-Judge):
  - 用 GPT-4 评估 RAG 输出质量
  - 成本低,可大规模
  - 适合持续监控

混合:
  - 定期人工评估校准自动评估
  - 日常用自动评估监控

评估流程:

1. 构建评估集(100-500 个标注样本)
2. 离线评估:检索指标 + 生成指标
3. 在线监控:用户反馈(点赞/点踩)
4. A/B 测试:对比不同策略
5. 持续优化:根据评估结果迭代

追问延伸:

  • 评估集怎么构建?(人工标注 + LLM 辅助生成)
  • 怎么做 A/B 测试对比两个 RAG 方案?(同一批评估集,对比指标差异)

Q19: RAG 知识库如何实现动态与持续更新? 「🟡 中级** ​

考察点:RAG 知识库的运维和更新策略。

参考答案:

知识库更新的场景:

1. 新增文档:新产品文档、新政策
2. 修改文档:更新已有文档内容
3. 删除文档:过期文档废弃
4. 增量索引:大量新文档批量入库

更新策略:

1. 全量重建 vs 增量更新:

策略适用场景优点缺点
全量重建文档大量变更简单、一致性好耗时长、服务中断
增量更新少量文档变更快、不中断需要管理版本

2. 增量更新实现:

python
class KnowledgeBaseUpdater:
    def __init__(self, vector_store, embed_model):
        self.vector_store = vector_store
        self.embed = embed_model

    def add_document(self, doc):
        """新增文档"""
        chunks = self._chunk(doc)
        embeddings = [self.embed.embed(c.text) for c in chunks]
        self.vector_store.add(
            embeddings=embeddings,
            documents=[c.text for c in chunks],
            metadatas=[c.metadata for c in chunks]
        )

    def update_document(self, doc_id, new_content):
        """更新文档(先删后增)"""
        # 删除旧文档的所有 Chunk
        self.vector_store.delete(filter={"doc_id": doc_id})
        # 增加新内容
        self.add_document(new_content)

    def delete_document(self, doc_id):
        """删除文档"""
        self.vector_store.delete(filter={"doc_id": doc_id})

3. 文档变更检测:

python
class DocumentWatcher:
    """监控文档变更"""
    def __init__(self, source_dir):
        self.source_dir = source_dir
        self.file_hashes = {}  # 记录文件哈希

    def check_changes(self):
        """检测新增、修改、删除的文件"""
        changes = {"added": [], "modified": [], "deleted": []}
        current_files = {}

        for file in scan_files(self.source_dir):
            hash = md5(file.content)
            current_files[file.path] = hash

            if file.path not in self.file_hashes:
                changes["added"].append(file)
            elif self.file_hashes[file.path] != hash:
                changes["modified"].append(file)

        for path in self.file_hashes:
            if path not in current_files:
                changes["deleted"].append(path)

        self.file_hashes = current_files
        return changes

4. 自动化更新流水线:

文档变更 → 检测变更 → 增量更新向量库 → 质量验证 → 发布

Pipeline:
  1. 定时扫描文档目录(每小时/每天)
  2. 检测文件变更(新增/修改/删除)
  3. 对变更文档做 Chunking + Embedding
  4. 增量更新向量数据库
  5. 运行评估集验证质量
  6. 验证通过 → 上线
     验证失败 → 回滚 + 告警

5. 版本管理:

知识库版本:
  v1.0 → 初始版本
  v1.1 → 新增 10 篇文档
  v1.2 → 修改 3 篇文档
  v2.0 → 大规模重构

支持回滚:
  v1.2 质量下降 → 回滚到 v1.1

6. 多版本共存:

场景:新旧版本文档同时存在(如不同版本 API 文档)

策略:
  - 元数据标注版本号
  - 检索时按版本过滤
  - 或者:只保留最新版本,旧版本归档

追问延伸:

  • 更新后 Embedding 需要重新计算吗?(修改的文档需要,未修改的不需要)
  • 大规模更新怎么不中断服务?(双写 + 灰度切换)

Q20: 在实际落地中,你觉得 RAG 最难的地方是哪里? 「🔴 高级** ​

考察点:RAG 工程实践的经验和深度。

参考答案:

RAG 落地的难点排序:

1. 数据质量(最难的):

"Garbage In, Garbage Out"

问题:
  - 原始文档格式混乱(PDF 表格、图片、扫描件)
  - 文档质量参差不齐(过时信息、错误信息、重复信息)
  - 文档切分后语义不完整
  - 知识冲突(多个文档说法矛盾)

解决投入:占整个 RAG 项目 60-70% 的工作量
  - 数据清洗 → 格式统一 → 结构化标注 → 切分优化 → 质量审核

2. 检索效果调优:

问题:
  - 召回率不够 → 用户问的问题检索不到相关文档
  - 精确度不够 → 检索到大量不相关文档
  - 专有名词/代码/ID 检索差
  - 多语言/跨领域检索效果差

解决:
  - Chunking 策略调优
  - Embedding 模型选型和微调
  - 混合检索(向量+关键词)
  - 重排序模型优化
  - 需要大量评估集和迭代

3. 评估体系缺失:

问题:
  - 没有标注数据 → 不知道效果好不好
  - 评估指标不明确 → 优化没有方向
  - 评估成本高 → 需要人工标注

解决:
  - 构建 100-500 个标注样本的评估集
  - 用 RAGAS / LLM-as-Judge 自动评估
  - 持续收集用户反馈

4. 复杂文档处理:

问题:
  - 表格 → 切分后丢失结构
  - 图片 → 无法直接向量化
  - 公式 → 切分后不可读
  - 代码 → 切分后不完整
  - 超长文档 → 信息密度不均

解决:
  - 多模态 Embedding(表格、图片用专用模型)
  - 文档结构感知切分
  - 父子文档切分

5. 成本与延迟:

问题:
  - 检索 + 生成 = 两步,延迟高
  - 重排序增加延迟
  - 多路检索增加成本
  - 大上下文 Token 消耗大

解决:
  - 缓存(语义缓存)
  - 并行检索
  - 上下文压缩
  - 模型选型(小模型+大模型组合)

6. 权限和安全:

问题:
  - 不同用户能看到不同文档
  - 检索结果可能泄露敏感信息
  - LLM 可能从上下文推断出不该知道的信息

解决:
  - 按用户权限过滤检索结果
  - 敏感信息脱敏
  - 输出审核

经验总结:

RAG 项目投入分布:
  数据处理 → 60%
  检索优化 → 20%
  生成优化 → 10%
  工程架构 → 10%

RAG 成功的关键:
  1. 数据质量 > 模型选择 > 算法优化
  2. 评估驱动迭代,不要凭感觉调参
  3. 先做基础 RAG,再逐步优化
  4. 关注端到端用户体验,不只看检索指标

追问延伸:

  • 怎么说服业务方投入数据清洗的成本?(用评估数据展示数据质量对效果的影响)
  • RAG 项目的 ROI 怎么衡量?(减少人工回答时间 + 提升回答准确率 + 用户满意度)