AI面试八股文-03_3-RAG 系统面试题

pdf版下载链:https://pan.quark.cn/s/9cc742e9c2a8

03 RAG 系统:架构设计、检索优化与生产实践

更新: 2026-10-12

难度: ⭐⭐⭐⭐

考点: 检索增强生成、向量数据库、Embedding、检索优化

📋 目录

  1. 基础概念题
  2. 架构设计题
  3. 优化策略题
  4. 高分实战案例

一、基础概念题

Q1: 什么是 RAG?为什么需要 RAG?

💡 答案要点

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

核心思想: 先检索相关知识,再让 LLM 基于检索内容回答问题。

为什么需要 RAG?

问题 纯 LLM RAG
知识时效 受训练数据和服务内置搜索限制 可连接更新的数据源,但要处理索引延迟与缓存失效
私有数据 默认不可见 可按权限检索内部知识,但必须做租户/文档级 ACL
幻觉 可能无依据生成 提供 grounding;检索错误、冲突证据和错误归因仍会导致幻觉
可追溯性 通常缺少可验证来源 可返回引用,但必须校验引用确实支持断言

面试话术:

"RAG 的核心是把外部、可更新、可授权的证据放进生成链路。它缓解知识时效和无依据生成,但不自动保证正确。我会把摄取、召回、排序、生成、引用验证和权限评估分层监控。"

Q2: RAG 的完整流程是什么?

图解:RAG 离线索引与在线问答

💡 答案要点

两条流水线:

text 复制代码
索引流水线(离线):
文档 → 加载 → 切分 → 向量化 → 存储到向量库

查询流水线(在线):
用户问题 → 向量化 → 检索 → 生成答案

详细流程:

text 复制代码
┌─────────────────────────────────────────────────────────────────┐
│                        RAG 系统全貌                              │
└─────────────────────────────────────────────────────────────────┘

┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│  文档加载器   │ ──→ │  文档切分器   │ ──→ │  Embedding   │
│  (Loader)    │     │  (Splitter)  │     │   (向量化)    │
└──────────────┘     └──────────────┘     └──────────────┘
                                                 │
                                                 ↓
┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│  LLM 生成答案  │ ←── │  检索器      │ ←── │  向量数据库   │
│  (Generator) │     │  (Retriever) │     │  (Vector DB) │
└──────────────┘     └──────────────┘     └──────────────┘

6 个核心步骤:

  1. 加载:从 PDF/Markdown/网页读取文档
  2. 切分:按结构/语义形成 chunks;大小由语料与评测决定
  3. 向量化:用 Embedding 模型转成向量
  4. 存储:存入向量数据库
  5. 检索:计算相似度,返回 top-k chunks
  6. 生成:LLM 基于检索内容生成答案

Q3: 为什么 RAG 要用向量数据库?其他数据库不行吗?

💡 高分回答

核心区别:检索方式不同

数据库类型 检索方式 适用场景
传统数据库 精确匹配(=、LIKE) 关键词搜索
向量数据库 语义匹配(相似度) 语义搜索

是否需要专用向量库取决于数据规模、过滤、更新频率、SLA 与运维能力:

方案 优势 何时优先
PostgreSQL + pgvector 事务、关系过滤和权限数据同库 中小规模、团队已有 PostgreSQL
Elasticsearch/OpenSearch BM25、过滤、聚合和向量混合检索成熟 搜索业务已有 ES 体系
Qdrant/Milvus/Weaviate 等 ANN、分片和向量检索能力专门化 大规模向量、独立扩缩容或高级向量能力

HNSW/IVF 是近似最近邻索引,延迟与召回受 ef_search、nprobe、过滤选择性、维度、量化和硬件共同影响;不能笼统宣称 O(log N) 或"快 500 倍"。

面试话术:

"向量数据库不是 RAG 的强制组件。先定义 Recall@K、过滤正确率、P95 延迟、更新时延和成本,再用真实数据压测 pgvector、搜索引擎或专用向量库。最终选择往往由权限过滤与运维边界决定,而不是固定数据量阈值。"

Q4: Embedding 是什么?1536 维什么意思?

💡 答案要点

Embedding = 把文本转成向量(一串数字)

1536 维 = 每段文本被编码成包含 1536 个数的稠密向量。单个维度通常不可解释,不对应"性别、年龄、地域"等人工命名特征。

类比:

text 复制代码
"员工请假审批流程" → [0.018, -0.227, ..., 0.041]

向量整体的几何关系编码语义;不要给单一维度强行赋予人类可读含义。

余弦相似度:

  • 算两个向量"方向"有多接近
  • 1 = 完全同向(语义几乎一样)
  • 0 = 垂直(语义无关)
  • -1 = 完全反向(语义相反)

面试话术:

"Embedding 是语义的数学表示。我用余弦相似度衡量相关性,用 HNSW 索引加速检索。"

二、架构设计题

Q5: 设计一个企业级知识库问答系统,你会怎么架构?

图解:企业级知识库问答架构

💡 高分回答

  1. 架构选型:RAG 是首选,Fine-tuning 是补充

"我会优先采用 Advanced RAG 架构而非微调。原因有三:

  1. 知识库需要频繁更新,RAG 只需更新向量库,而微调成本太高

  2. RAG 可以提供引用溯源,消除幻觉

  3. 微调无法处理海量非结构化文档的检索"

  4. 数据清洗与切片

"切片不能简单按字符数。我会先做版面解析,保留标题路径、页码、表格结构和跨页关系。复杂表格可用专用文档解析器或当前视觉模型转成 HTML/Markdown,并抽样核对数字、合并单元格和表头;视觉模型输出也不能直接当真值。"

进阶提分:

"我会给每个 Chunk 增加 Metadata(元数据),比如文件名、页码、所属部门。这样在检索时可以进行 Self-Querying(根据用户权限或范围过滤标签)。"

  1. 检索优化

"单次向量检索往往不够。我会引入 Multi-Query Retrieval(将用户问题扩展成多个同义句)和 Hybrid Search(向量检索 + 关键词 BM25 检索)来提升召回率。"

进阶提分:

"我会加入 Rerank(重排序)环节。先从向量库召回 50 个候选片断,再用专门的 Reranker 模型(如 BGE-Reranker)精选出最相关的 Top-5。"

  1. 评估与工程化

"我会建立分层评估:检索层用 Recall@K、MRR/nDCG 和 ACL 正确率;生成层用任务成功率、Faithfulness、引用正确率和拒答质量;线上监控延迟、成本、用户纠错和失败类型。Ragas 可以辅助,但 LLM-as-a-judge 要用人工样本校准。"

进阶提分:

"为了降低 Token 成本和延迟,我会部署 Semantic Cache(语义缓存)。如果两个用户问了语义相似的问题,直接从 Redis 缓存中读取答案,无需再次调用大模型。"

Q6: 如何选择 Chunk 大小?有什么影响?

图解:Chunk 大小选择逻辑

💡 答案要点

Chunk 大小的权衡:

Chunk 大小 优点 缺点 适用场景
小(256-512) 检索精确 语义不完整 技术文档、代码
中(512-1000) 平衡 平衡 通用文档
大(1000-2000) 语义完整 检索不精确 对话数据、文章

以下只是起始搜索范围,不是行业标准:

  • 通用文档:500-1000 tokens
  • 技术文档:256-512 tokens
  • 对话数据:1024-2048 tokens
  • 重叠:chunk_size 的 10-20%

进阶策略:

  • 父子文档(Parent-Child):检索小 chunk,返回大文档
  • 重叠切分:相邻 chunk 重叠 10-20%
  • 语义切分:用 embedding 聚类后切分

三、优化策略题

Q7: 如何解决检索结果不相关(Recall 质量差)?

图解:RAG 召回质量优化闭环

💡 答案要点

解决方案:

  1. 混合检索
  • 向量检索 + 关键词检索(BM25)
  • 优先用 RRF 等对分数尺度不敏感的融合;若线性加权,要先校准两路分数
  1. Multi-Query
  • 用 LLM 生成多个查询变体
  • 合并检索结果,去重
  1. Rerank
  • 用 Cross-Encoder 重新排序
  • 先从向量库召回 50 个,再精选 Top-5
  1. 优化 Embedding
  • 在领域评测集上比较候选模型;BGE-M3、Qwen3-Embedding 等只是候选
  1. 优化 Chunking
  • 调整 chunk 大小和重叠

Q8: 如何降低 RAG 系统的成本?

图解:RAG 成本优化优先级

💡 答案要点

成本优化策略:

  1. 语义缓存
  • 相同/相似问题直接返回缓存
  • 命中率高度依赖流量重复度,必须实测
  1. Prompt 压缩
  • 用 LLMLingua 压缩检索结果
  • 记录压缩率与任务质量,不能只看 token 下降
  1. 模型路由
  • 简单问题用小模型(便宜)
  • 复杂问题用大模型(贵但效果好)
  1. 优化检索
  • 减少 k 值(只返回最相关的 2-3 个)
  • 用 Rerank 提升精度,减少无效 token
  1. 批量处理
  • 多个请求合并成一个 LLM 调用
  • 适合离线任务

四、高分实战案例

案例:处理复杂 PDF 表格

背景: 扫描版 PDF,包含大量跨页表格,直接 OCR 导致文字逻辑错乱。

解决方案:

  1. 布局分析(Layout Analysis)
  • 用 Layout-Parser 或 PaddleOCR 的区域检测模型
  • 先识别出文档中的"表格区"、"正文区"
  1. 多模态增强
  • 针对极难处理的表格,用当前视觉模型读取图像,并用规则/人工复核关键数字
  1. 管道化清洗(Pipeline)
  • 先用 PyMuPDF 提取可复制文字
  • 对无法识别的图片层启动 OCR
  • 最后通过 Cleaner LLM 修复噪点

结果表达:只有真实做过并保存评测口径时才报告"从 X 到 Y";否则说明应按单元格准确率、表头关联、跨页完整率和人工复核率评估。

Q9: 什么是Query改写?如何提升检索效果?

💡 答案要点

Query改写 = 优化用户原始问题,使其更适合检索

核心问题:

text 复制代码
用户问题往往不够精确:
- "昨天说的那个事" → 缺少上下文
- "怎么办" → 太模糊
- "价格多少" → 缺少主语

改写策略:

1. 补充上下文(多轮对话)

python 复制代码
# 对话历史
history = [
    ("什么是RAG?", "RAG是检索增强生成..."),
    ("它有什么优势?", "...")
]

# 用户新问题
user_query = "如何实现它?"

# 改写后
rewritten_query = "如何实现RAG检索增强生成系统?"

2. 查询扩展

python 复制代码
# 原始查询
query = "Python多线程"

# 扩展后
expanded_query = """
Python多线程
Python threading模块
Python GIL全局解释器锁
Python并发编程
"""
# 用扩展后的多个查询检索,合并结果

3. HyDE(假设性文档嵌入)

python 复制代码
# 原始问题
query = "如何优化RAG检索准确率?"

# 让LLM生成假设性答案
hypothetical_doc = llm.generate(f"假设回答: {query}")
# "可以通过混合检索、Rerank、query改写等方法..."

# 用假设性答案的向量去检索(而非原问题)
embedding = embed(hypothetical_doc)
results = vector_db.search(embedding)

HyDE优势: 答案和文档库中的文档更相似,检索更准

4. 多查询生成

python 复制代码
# 原始问题
query = "RAG系统慢怎么办?"

# 生成多个视角的查询
sub_queries = llm.generate(f"将问题拆分成3个子问题: {query}")
# 1. "如何提升RAG检索速度?"
# 2. "RAG系统性能瓶颈在哪?"
# 3. "向量数据库优化方法?"

# 分别检索后合并结果

实现示例:

python 复制代码
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import LLMChainExtractor

# Query改写器
def rewrite_query(query, chat_history):
    prompt = f"""
    对话历史: {chat_history}
    当前问题: {query}

    请补充上下文,改写成独立完整的问题。
    """
    return llm.generate(prompt)

# HyDE改写
def hyde_rewrite(query):
    prompt = f"假设你要回答这个问题,你会怎么说: {query}"
    hypothetical_answer = llm.generate(prompt)
    return hypothetical_answer

# 使用
user_query = "如何优化?"
rewritten = rewrite_query(user_query, history)
results = retriever.get_relevant_documents(rewritten)

性能对比必须在同一带标注文档 ID 的测试集上完成。至少同时报告 Recall@K、nDCG/MRR、查询改写额外延迟与 token 成本,并按短查询、指代、多跳、精确实体等类型切片分析。HyDE 和多查询会提高召回机会,也可能引入错误概念或查询漂移。

面试话术:

"Query 改写要保留原始意图和关键实体。我会并行保留原查询,用 RRF 合并改写结果,并对 HyDE 的查询漂移做评测。多轮指代先生成独立问题;实时或权限敏感问题还要保留时间和 ACL 条件。"


Q10: 什么是上下文压缩?如何减少无效Token?

💡 答案要点

上下文压缩 = 从检索结果中提取最相关片段,减少LLM输入

问题背景:

text 复制代码
检索返回5个文档,每个1000 tokens
→ 总共5000 tokens送给LLM
→ 但只有500 tokens真正有用
→ 浪费4500 tokens,增加成本和延迟

压缩策略:

1. 基于LLM的提取

python 复制代码
from langchain.retrievers.document_compressors import LLMChainExtractor

compressor = LLMChainExtractor.from_llm(llm)

prompt = """
文档: {document}
问题: {query}

请只提取与问题相关的句子,其他都删除。
"""

# 压缩前: 1000 tokens
# 压缩后: 200 tokens (只保留相关部分)

2. 基于Embedding的过滤

python 复制代码
from langchain.retrievers.document_compressors import EmbeddingsFilter

# 计算每个句子与query的相似度
# 只保留相似度>阈值的句子
embeddings_filter = EmbeddingsFilter(
    embeddings=OpenAIEmbeddings(),
    similarity_threshold=0.76
)

3. Rerank + Top-K选择

python 复制代码
# 先检索50个候选
candidates = vector_db.search(query, top_k=50)

# Rerank重排
reranked = reranker.rerank(query, candidates)

# 只取top-3,并提取关键段落
compressed = []
for doc in reranked[:3]:
    # 从每个文档中提取最相关的3个句子
    relevant_sentences = extract_relevant(doc, query, max_sentences=3)
    compressed.append(relevant_sentences)

4. Contextual Compression完整流程

python 复制代码
from langchain.retrievers import ContextualCompressionRetriever

# 基础检索器
base_retriever = vector_store.as_retriever(search_kwargs={"k": 20})

# 压缩器(LLM提取)
compressor = LLMChainExtractor.from_llm(llm)

# 组合
compression_retriever = ContextualCompressionRetriever(
    base_compressor=compressor,
    base_retriever=base_retriever
)

# 使用
compressed_docs = compression_retriever.get_relevant_documents(query)

示例评测维度(数值应由项目压测产生):

方法 输入 token 输出质量 成本 延迟
无压缩 基线 任务成功率/引用正确率 基线 基线
Embedding过滤 通常降低 可能漏掉否定或限定条件 增加本地/服务计算 通常较低
LLM提取 可显著降低 存在摘要漂移 增加一次模型调用 较高
Rerank+提取 可控 依赖 Reranker 与提取器 增加排序/提取成本 中到高

Late Chunking(晚期分块):

python 复制代码
# 传统chunking: 先切分再embedding
doc = "AI技术正在改变世界。大模型是核心。RAG系统很重要。"
chunks = ["AI技术正在改变世界。", "大模型是核心。", "RAG系统很重要。"]
embeddings = [embed(c) for c in chunks]  # 丢失跨chunk上下文

# Late chunking: 先让长上下文 embedding 模型编码全文的 token 表示,
# 再按 chunk 边界对对应 token 向量做 pooling。
token_embeddings = encoder.encode_tokens(doc)
chunk_embeddings = [
    mean_pool(token_embeddings[start:end])
    for start, end in chunk_token_boundaries
]

优势:chunk 向量能利用周围上下文。限制:全文必须落在 embedding 模型窗口内,编码成本更高,且效果要在目标语料评测。不能把"一个全文向量"直接切开当作 chunk 向量。

面试话术:

"上下文压缩是在 token、延迟和证据完整性之间做权衡。我会先 Rerank,再按 token 预算选择或抽取段落,并把否定词、时间、单位和引用位置列为不可丢字段。上线前比较压缩率、任务成功率和引用完整率。"


Q11: Embedding模型如何选择?

💡 答案要点

选择维度

维度 考虑因素 推荐
1. 数据分布 中文/英文/跨语言、代码、表格、短问长文 用项目语料构造 query-document 标注集
2. 检索任务 dense、sparse、multi-vector、图文 确认 query/document 是否需要不同指令
3. 质量 Recall@K、MRR、nDCG、困难负例 排行榜只用于初筛
4. 系统成本 维度、索引内存、吞吐、P95、重建时间 低维/量化需测质量损失
5. 上下文 chunk 长度与模型截断策略 "支持 32K"不表示应该把 chunk 做到 32K
6. 治理 数据地域、留存、许可证、离线部署 API 与自部署分别核算总拥有成本

当前候选(截至 2026-10,非"最强榜")

类型 官方当前候选 已核对的公开特性 适合验证的场景
托管多模态 Embedding Cohere embed-v5.0-pro/fast 文本/图像,128K,可选 256-2048 维 图文/PDF、多语言
托管文本 Embedding Voyage voyage-4-large/4/4-lite 32K,默认 1024 维,可选 256-2048 维 通用、多语言、质量/延迟分层
上下文化 chunk Voyage voyage-context-4 120K 总上下文,按 chunk 输出上下文化向量 长文档、跨 chunk 指代
开放权重 Qwen3-Embedding 0.6B/4B/8B 32K,instruction-aware,支持 MRL 中文/多语言、私有化
开放权重 BGE-M3 1024 维、8192 token、dense+sparse+multi-vector 混合检索、跨语言
Reranker Cohere rerank-v4.0-pro/fast、Qwen3-Reranker Cross-Encoder 精排;当前官方型号见来源 Top-N 精排、半结构化数据

模型和价格变化频繁,因此不在本文硬编码价格。生产选型要锁定模型 ID/快照并记录向量版本;更换 embedding 模型通常需要重建索引,不能把不同模型的向量直接混用,除非官方明确保证兼容。

实战代码

方案1: 闭源API(OpenAI)
python 复制代码
from openai import OpenAI

client = OpenAI(api_key="sk-xxx")

def embed_text(text):
    response = client.embeddings.create(
        model="text-embedding-3-large",
        input=text,
        dimensions=1024  # 可选256/1024/3072
    )
    return response.data[0].embedding

# 使用
vector = embed_text("什么是RAG系统?")
print(len(vector))  # 1024

优点: 零部署,调用即用

缺点: 需核对当前价格、数据留存/地域与供应商可用性

方案2: 开源本地部署
python 复制代码
from sentence_transformers import SentenceTransformer

# 加载模型(首次会下载,约1.3GB)
model = SentenceTransformer('BAAI/bge-large-zh-v1.5')

def embed_text(text):
    # 编码
    embedding = model.encode(
        text,
        normalize_embeddings=True  # 归一化,方便余弦相似度
    )
    return embedding

# 批量处理(更快)
texts = ["什么是RAG?", "如何优化检索?", "向量数据库选择"]
embeddings = model.encode(texts, batch_size=32)
print(embeddings.shape)  # (3, 1024)

优点: 免费,数据不外传,可微调

缺点: 需要GPU(CPU慢10倍),首次下载模型

方案3: 混合策略
python 复制代码
class HybridEmbedding:
    def __init__(self):
        # 开源模型处理中文
        self.zh_model = SentenceTransformer('BAAI/bge-large-zh-v1.5')
        # API处理英文
        self.openai_client = OpenAI(api_key="sk-xxx")

    def embed(self, text, language='auto'):
        # 自动检测语言
        if language == 'auto':
            language = 'zh' if contains_chinese(text) else 'en'

        if language == 'zh':
            # 用本地模型(免费)
            return self.zh_model.encode(text)
        else:
            # 用OpenAI(付费但质量好)
            response = self.openai_client.embeddings.create(
                model="text-embedding-3-large",
                input=text
            )
            return response.data[0].embedding

def contains_chinese(text):
    return any('\u4e00' <= char <= '\u9fff' for char in text)

优势: 中文省钱,英文质量保证

性能测试

python 复制代码
import time
from sentence_transformers import SentenceTransformer

# 测试embedding速度
model = SentenceTransformer('BAAI/bge-large-zh-v1.5')

texts = ["测试文本"] * 1000

# CPU
start = time.time()
embeddings = model.encode(texts, device='cpu')
cpu_time = time.time() - start
print(f"CPU: {cpu_time:.2f}s, {len(texts)/cpu_time:.1f} texts/s")

# GPU
start = time.time()
embeddings = model.encode(texts, device='cuda')
gpu_time = time.time() - start
print(f"GPU: {gpu_time:.2f}s, {len(texts)/gpu_time:.1f} texts/s")

# 输出取决于硬件、batch、序列长度、精度与运行时;不要把示例吞吐当结论

微调Embedding模型

场景: 通用模型在你的领域(如医疗/法律)效果差

python 复制代码
from sentence_transformers import SentenceTransformer, InputExample, losses
from torch.utils.data import DataLoader

# 1. 准备训练数据
train_examples = [
    InputExample(
        texts=["患者出现发热症状", "病人体温升高"],
        label=1.0  # 相似
    ),
    InputExample(
        texts=["患者出现发热症状", "今天天气很好"],
        label=0.0  # 不相似
    ),
    # ... 至少1000对
]

# 2. 加载基础模型
model = SentenceTransformer('BAAI/bge-large-zh-v1.5')

# 3. 定义损失函数
train_dataloader = DataLoader(train_examples, shuffle=True, batch_size=16)
train_loss = losses.CosineSimilarityLoss(model)

# 4. 微调
model.fit(
    train_objectives=[(train_dataloader, train_loss)],
    epochs=3,
    warmup_steps=100,
    output_path='./my-domain-embedding'
)

# 5. 使用微调模型
custom_model = SentenceTransformer('./my-domain-embedding')
embedding = custom_model.encode("医学专业术语")

效果必须用保留测试集与困难负例验证,并检查通用检索回归;不要预设固定提升。

决策树

text 复制代码
先定义真实 query-document 测试集
├─ 数据不能外发?→ 比较 Qwen3-Embedding / BGE-M3 等开放权重候选
├─ 需要图文统一检索?→ 比较支持图像的托管/开放模型
├─ 需要跨 chunk 上下文?→ 比较 contextual embedding / late chunking / contextual retrieval
├─ 召回够但排序差?→ 增加 Cross-Encoder Reranker
└─ 最终按 Recall/nDCG + P95 + 内存 + 总成本选择,并锁版本

面试话术:

"Embedding 选型不能按品牌拍脑袋。我会先用真实查询、相关文档和困难负例构建测试集,再比较 Recall@K/nDCG、吞吐、维度内存、截断和许可证。中文/多语言可把 Qwen3-Embedding、BGE-M3 与托管 API 一起测;召回后再单独评估 Reranker。"


Q12: GraphRAG是什么?与普通向量RAG的核心区别?何时选择?

图解:GraphRAG 构建与回答流程

💡 答案要点

GraphRAG 是结构化、分层的 RAG 路线。以 Microsoft GraphRAG 为例,它从文本抽取知识图谱、构建社区层次与社区摘要,再支持 Global、Local、DRIFT 和 Basic Search;它不是简单"用图替代向量"。

核心区别

维度 向量RAG GraphRAG
数据组织 文本切块→向量 实体+关系→图节点/边
检索方式 语义相似度(近似邻居) 图遍历/Cypher查询
多跳推理 需要分解/多轮检索 图路径提供结构,但关系抽取错误会传播
可解释性 低(黑盒相似度) 高(关系路径可追踪)
构建成本 低 高(需要NER+关系抽取)
适用场景 语义搜索、局部文档问答 全局主题、实体关系、多跳与语料整体洞察

GraphRAG实现流程

Step 1:知识图谱构建

python 复制代码
from neo4j import GraphDatabase
import spacy

class KnowledgeGraphBuilder:
    def __init__(self, neo4j_uri, user, password):
        self.driver = GraphDatabase.driver(neo4j_uri, auth=(user, password))
        self.nlp = spacy.load("zh_core_web_sm")

    def extract_entities_relations(self, text: str):
        """用LLM抽取实体和关系"""
        prompt = f"""
        从以下文本中提取实体和关系,以JSON格式输出:

        文本:{text}

        输出格式:
        {{
            "entities": [
                {{"name": "实体名", "type": "Person/Organization/Product/Location"}}
            ],
            "relations": [
                {{"source": "实体A", "relation": "关系类型", "target": "实体B"}}
            ]
        }}
        """
        result = llm.generate(prompt, temperature=0)
        return json.loads(result)

    def build_graph(self, documents: list):
        """将文档转化为知识图谱"""
        with self.driver.session() as session:
            for doc in documents:
                data = self.extract_entities_relations(doc)

                # 创建实体节点
                for entity in data["entities"]:
                    session.run(
                        "MERGE (e:Entity {name: $name}) SET e.type = $type",
                        name=entity["name"], type=entity["type"]
                    )

                # 创建关系边
                for rel in data["relations"]:
                    session.run("""
                        MATCH (a:Entity {name: $source})
                        MATCH (b:Entity {name: $target})
                        MERGE (a)-[r:RELATION {type: $rel_type}]->(b)
                    """, source=rel["source"],
                         target=rel["target"],
                         rel_type=rel["relation"])

# 示例:构建公司知识图谱
builder = KnowledgeGraphBuilder("bolt://localhost:7687", "neo4j", "password")

docs = [
    "张三是阿里巴巴的CTO,负责技术战略",
    "阿里巴巴旗下有淘宝、天猫、支付宝等产品",
    "支付宝由彭蕾创立,现由韩歆毅担任CEO",
]
builder.build_graph(docs)

Step 2:图检索查询

python 复制代码
class GraphRAGRetriever:
    def __init__(self, driver, llm):
        self.driver = driver
        self.llm = llm

    def query_to_cypher(self, user_query: str) -> str:
        """将自然语言查询转为Cypher图查询"""
        prompt = f"""
        将以下问题转为Neo4j Cypher查询语句:

        问题:{user_query}

        图结构:节点(Entity)有name和type属性,关系(RELATION)有type属性

        只输出Cypher语句,不加解释:
        """
        return self.llm.generate(prompt, temperature=0).strip()

    def retrieve(self, user_query: str) -> list:
        """图检索 + 向量检索混合"""
        results = []

        # 1. 图检索:精确关系查询
        try:
            cypher = self.query_to_cypher(user_query)
            with self.driver.session() as session:
                graph_results = session.run(cypher).data()
                results.extend(graph_results)
        except Exception as e:
            print(f"图查询失败,降级到向量检索:{e}")

        # 2. 向量检索:语义相似度补充
        vector_results = vectordb.search(user_query, k=3)
        results.extend(vector_results)

        return results

    def multi_hop_reasoning(self, query: str, max_hops=3) -> str:
        """多跳推理:沿图关系路径推理"""
        # 提取问题中的实体
        entities = self.extract_entities(query)

        reasoning_chain = []
        for entity in entities:
            # 从实体出发,遍历K跳邻居
            cypher = """
            MATCH path = (start:Entity {name: $entity})-[*1..3]-(end:Entity)
            RETURN path, length(path) as hops
            ORDER BY hops
            LIMIT 20
            """
            with self.driver.session() as session:
                paths = session.run(cypher, entity=entity).data()
                reasoning_chain.extend(paths)

        return reasoning_chain

Step 3:结合LLM生成

python 复制代码
def graphrag_answer(user_query: str):
    retriever = GraphRAGRetriever(driver, llm)

    # 1. 多跳图检索
    graph_context = retriever.multi_hop_reasoning(user_query, max_hops=2)

    # 2. 格式化图上下文
    graph_text = format_graph_paths(graph_context)

    # 3. LLM生成
    prompt = f"""
    基于以下知识图谱信息回答问题:

    图谱信息(实体关系路径):
    {graph_text}

    问题:{user_query}

    请基于上述关系链条进行推理并回答:
    """

    return llm.generate(prompt)

# 示例
answer = graphrag_answer("支付宝的创始人和阿里巴巴CTO是什么关系?")
# 推理路径:彭蕾 → 创立 → 支付宝 → 归属 → 阿里巴巴 ← 任职 → 张三(CTO)
# 回答:"彭蕾创立了支付宝,支付宝隶属于阿里巴巴,张三是阿里巴巴的CTO,所以彭蕾和张三都服务于阿里巴巴体系..."

选型建议

text 复制代码
问自己3个问题:

1. 问题需要多跳推理吗?
   例:"A的老板的老板是谁?" → 需要 → 选GraphRAG

2. 实体间关系是核心信息吗?
   例:医药、法律、供应链 → 是 → 选GraphRAG

3. 数据是自由文本为主吗?
   例:文章、客服记录 → 是 → 选向量RAG

推荐:生产环境用混合方案(向量召回 + 图精排)

面试话术:

"GraphRAG 不等于把向量库换成 Neo4j。以 Microsoft GraphRAG 为例,重点是图抽取、社区层次、社区摘要和多种查询模式。全局主题用 Global Search,实体邻域用 Local/DRIFT,简单问题仍可用 Basic Search。上线前要量化索引 token 成本、增量更新、实体消歧和关系错误传播。"


Q13: Agentic RAG是什么?与普通RAG的区别?如何实现多跳推理?

图解:Agentic RAG 多跳推理循环

💡 答案要点

Agentic RAG = Agent主动控制检索策略,而非被动执行单次检索

普通RAG vs Agentic RAG

text 复制代码
普通RAG(被动):
用户问 → 检索1次 → 固定Top-K → LLM生成
(检索策略固定,不管结果好不好)

Agentic RAG(主动):
用户问 → Agent分析 → 决定是否检索/检索什么/怎么检索
         → 评估结果 → 决定是否再检索
         → 循环直到信息充分 → 生成

核心能力

python 复制代码
        context = []
        retrieval_count = 0

        while retrieval_count < self.max_retrieval_rounds:
            # Step 1: Agent分析当前上下文,决定下一步
            decision = self.agent_decide(user_query, context)

            if decision["action"] == "answer":
                # 信息充分,直接回答
                break

            elif decision["action"] == "retrieve_vector":
                # 语义检索
                query = decision["query"]
                docs = self.vectordb.search(query, k=5)
                context.extend(docs)

            elif decision["action"] == "retrieve_web":
                # 实时网络检索
                query = decision["query"]
                docs = self.search_engine.search(query)
                context.extend(docs)

            elif decision["action"] == "decompose":
                # 分解子问题,分别检索
                sub_questions = decision["sub_questions"]
                for sq in sub_questions:
                    docs = self.vectordb.search(sq, k=3)
                    context.extend(docs)

            retrieval_count += 1

        # 最终生成
        return self.generate(user_query, context)

    def agent_decide(self, query: str, context: list) -> dict:
        """Agent决策:下一步做什么"""

        context_text = "\n".join(context[-5:]) if context else "无"

        prompt = f"""
        你是RAG系统的检索策略Agent。

        用户问题:{query}
        当前已检索到的信息:{context_text}

        请判断下一步操作(输出JSON):

        选项:
        1. {{"action": "answer"}} - 信息已充分,可以回答
        2. {{"action": "retrieve_vector", "query": "检索词"}} - 需要语义检索
        3. {{"action": "retrieve_web", "query": "搜索词"}} - 需要最新网络信息
        4. {{"action": "decompose", "sub_questions": ["子问题1", "子问题2"]}} - 需要分解问题

        判断依据:
        - 当前信息能否回答问题?
        - 是否需要最新数据?
        - 问题是否包含多个子问题?

        输出:
        """

        result = self.llm.generate(prompt, temperature=0)
        return json.loads(result)

    def generate(self, query: str, context: list) -> str:
        """基于收集到的上下文生成最终答案"""
        ctx_text = "\n---\n".join(context)

        prompt = f"""
        基于以下信息回答问题:

        {ctx_text}

        问题:{query}

        请综合所有信息给出完整回答:
        """
        return self.llm.generate(prompt)

# 使用场景示例:复杂多步问题
rag = AgenticRAG(llm, vectordb, search_engine)

result = rag.run("比较当前候选模型的官方价格与长文档能力,哪个更适合本任务?")

# Agent执行过程:
# 轮次1:检索供应商 A 官方模型/价格页
# 轮次2:检索供应商 B 官方模型/价格页
# 轮次3:retrieve_vector("长文档摘要模型对比") → 获取性能对比
# → 信息充分,生成综合对比回答

多跳推理实现

python 复制代码
class MultiHopRAG:
    """多跳推理:每次检索结果作为下一次检索的输入"""

    def multi_hop_retrieve(self, query: str, max_hops=3) -> str:
        reasoning_trace = []
        current_query = query
        accumulated_context = []

        for hop in range(max_hops):
            # 检索当前子问题
            docs = self.vectordb.search(current_query, k=3)
            accumulated_context.extend(docs)

            # 评估是否已有足够信息
            eval_prompt = f"""
            原始问题:{query}
            已收集信息:{" ".join(accumulated_context)}

            判断(JSON):
            1. 能直接回答原始问题吗?{{"can_answer": true}}
            2. 还需要追问什么?{{"can_answer": false, "next_question": "下一个子问题"}}
            """

            eval_result = json.loads(self.llm.generate(eval_prompt, temperature=0))

            if eval_result["can_answer"]:
                break  # 信息足够,停止检索

            # 继续追问
            current_query = eval_result["next_question"]
            reasoning_trace.append(f"Hop {hop+1}: {current_query}")

        print("推理链:", " → ".join(reasoning_trace))
        return self.generate(query, accumulated_context)

# 示例:
# 问题:"参与过阿里投资的公司中,谁的CEO毕业于清华?"
# Hop 1: 检索"阿里巴巴投资的公司" → 找到饿了么、优酷等
# Hop 2: 检索"饿了么CEO教育背景" → 张旭豪复旦大学
# Hop 3: 检索"优酷CEO教育背景" → ...
# 逐步推理找到答案

面试话术:

"Agentic RAG 让模型在有预算的状态机中选择检索源、拆分问题、评估证据并决定停止。生产要限制轮次、token、工具和墙钟时间,保留原查询,去重证据,并用可回答性/引用覆盖率而不是模型一句'信息足够'来停止。"


📝 速记卡片

概念 一句话解释
RAG 先检索知识,再生成答案
Embedding 文本转语义向量
Embedding选型 用真实语料比较候选模型的质量、成本、治理与版本兼容
余弦相似度 算向量接近程度(-1 到 1)
向量数据库 专为"找相似"设计的数据库
Chunking 把长文档切成小块
混合检索 向量 + 关键词一起搜
GraphRAG 图、社区层次与摘要增强的结构化 RAG,适合全局/关系问题
Agentic RAG Agent主动控制检索策略,动态多轮检索
Rerank 对检索结果重新排序
ANN 近似最近邻搜索,加速检索
Query改写 优化问题,补充上下文,HyDE生成假设答案
上下文压缩 提取相关片段,减少无效Token,降成本
Late Chunking 全文 token 编码后按边界 pooling,生成带上下文的 chunk 向量
幻觉优化 证据 grounding + 引用验证 + 拒答 + 人工复核
RAG效果提升 混合检索+Rerank+Query重写+上下文压缩四步走

高频追问:RAG 效果如何提升?出现幻觉怎么办?

Q14: RAG 效果不好如何排查和提升?

💡 答案要点

效果问题诊断流程:

text 复制代码
RAG回答不好
    ├── 检索没找到相关内容(召回问题)
    │       ├── Embedding 模型不够好 → 换 bge-large-zh
    │       ├── Chunk 切分太大/太小 → 调整 chunk_size
    │       ├── 查询和文档表达不一致 → 加 Query 改写
    │       └── 只用向量检索 → 改混合检索(BM25 + 向量)
    │
    ├── 找到了但排序靠后(排序问题)
    │       └── 加 Rerank(BGE-Reranker/Cohere)
    │
    └── 找到了但模型没用上(生成问题)
            ├── Context 太长模型忽略 → 上下文压缩
            ├── Prompt 没引导好 → 优化 Prompt
            └── 模型能力不足 → 换更强模型

四步优化组合拳:

python 复制代码
# Step 1: Query 改写(解决表达不一致)
rewritten_query = await query_rewriter.rewrite(
    query=user_query,
    history=conversation_history
)

# Step 2: 混合检索(提升召回率)
vector_results = vector_db.search(rewritten_query, top_k=20)
bm25_results   = bm25_index.search(rewritten_query, top_k=20)
merged_results = rrf_merge(vector_results, bm25_results)  # RRF 融合

# Step 3: Rerank 精排(提升相关性)
reranked = reranker.rerank(
    query=rewritten_query,
    docs=merged_results,
    top_k=5  # 精选 top5
)

# Step 4: 上下文压缩(避免 Lost in Middle)
compressed = context_compressor.compress(
    query=rewritten_query,
    docs=reranked,
    max_tokens=2000
)

# 生成
answer = await llm.generate(query=user_query, context=compressed)

各优化手段效果量化:

优化手段 主要评估指标 成本 / 风险
混合检索(BM25+向量) Recall@K / nDCG 多一路索引与查询
Query 改写 指代/短查询召回、查询漂移率 增加模型调用
Rerank nDCG / MRR / Precision@K 增加精排延迟
上下文压缩 任务成功率、引用完整率、压缩率 可能增加提取调用
组合使用 端到端成功率、P95、单次成本 做消融实验归因

面试话术:

"RAG 优化要分层诊断:摄取与 ACL、召回、排序、上下文组装、生成与引用。每次只改一个变量并做消融;报告真实测试集大小、标注方式和置信区间,不能把模板中的 65%→88% 当项目经验。"

Q15: RAG 出现幻觉怎么办?

💡 答案要点

幻觉来源分类:

幻觉类型 原因 示例
检索幻觉 检索内容不相关,模型自行编造 问A知识,找到B知识,模型混用
融合幻觉 多个 chunk 信息矛盾,模型错误融合 文档A说10%,文档B说20%,模型说15%
过度推断幻觉 模型超出检索内容范围推断 文档说"可能",模型断言"一定"
记忆幻觉 模型用训练记忆覆盖检索内容 检索到最新数据,模型仍用旧知识

对应解决方案:

python 复制代码
class HallucinationGuard:
    """幻觉防护三层体系"""

    # Layer 1: 预防 - 让模型只能基于检索内容回答
    SYSTEM_PROMPT = """
    你只能根据提供的<context>内容回答问题。
    如果context中没有相关信息,回答"根据现有资料无法回答该问题"。
    不要推断或补充context中没有的信息。
    每个关键论断必须引用对应的context片段。
    """

    # Layer 2: 检测 - 答案与来源的一致性验证
    async def check_faithfulness(
        self, answer: str, sources: list[str]
    ) -> float:
        """RAGAS Faithfulness 评估:答案有多少来自检索内容"""
        prompt = f"""
        判断以下答案中的每个陈述是否能从来源文本中找到依据。
        答案:{answer}
        来源:{sources}
        输出 JSON: {{"faithful_score": 0.0-1.0, "unsupported_claims": []}}
        """
        result = await llm.complete(prompt)
        return result["faithful_score"]

    # Layer 3: 兜底 - 低置信度转人工
    async def safe_answer(self, query: str, context: list) -> str:
        answer = await llm.generate(query, context)
        score = await self.check_faithfulness(answer, context)

        if score < 0.7:
            # 置信度低 → 明确告知不确定
            return f"基于现有资料,{answer}(注:该回答置信度较低,建议人工核实)"
        return answer

实际指标目标:

text 复制代码
阈值必须按业务风险、评估器校准结果和人工复核成本确定。
Faithfulness 只衡量"答案是否受给定上下文支持",不代表上下文本身真实,也不等于答案完整。
拒答率需要和错误回答率、覆盖率一起看,不能套固定"合理范围"。

面试话术:

"我把 RAG 幻觉分为证据没找对、证据冲突、模型错误归因和超范围推断。分别用检索评测、版本/时间元数据、逐断言引用校验与拒答处理。低温只能减少波动;Ragas Faithfulness 是辅助评估,阈值要用人工标注校准。"

Q16: RAG 系统的语义缓存(Semantic Cache)如何实现?有哪些关键问题?

💡 答案要点

语义缓存 = 用语义相似度判断是否命中缓存

问题背景:

  • 传统缓存:精确匹配(相同问题 → 相同答案)
  • 语义缓存:语义相似 → 相同答案
text 复制代码
用户问题 A:"如何优化 Python 代码性能?"
用户问题 B:"Python 性能优化方法有哪些?"
→ 语义相似 → 命中缓存

用户问题 C:"如何优化 Python 代码性能?"
用户问题 C 再次问:"如何优化 Python 代码性能?"
→ 完全相同 → 精确命中

核心挑战:

挑战 说明 解决方案
相似度阈值 设太高 → 命中率低;设太低 → 答案不相关 线上 A/B 测试调参
向量检索开销 每次请求都要做向量检索 粗排 + 精排
缓存失效 知识库更新后,缓存可能过期 TTL + 版本号
多租户 不同用户看到不同答案 用户级缓存隔离

实现架构:

text 复制代码
用户问题
    ↓
┌─────────────┐
│ Query 向量化 │ → embedding
└─────────────┘
    ↓
┌─────────────┐
│ 缓存命中判断 │ → 向量数据库检索 top-k
└─────────────┘
    ↓
命中? → 是 → 返回缓存答案
    ↓ 否
┌─────────────┐
│ 正常 RAG 流程 │ → 执行检索 + 生成
└─────────────┘
    ↓
┌─────────────┐
│ 写入缓存    │ → 新问题 + 答案 → 向量存入
└─────────────┘

实现示例:

python 复制代码
import numpy as np

class SemanticCache:
    def __init__(self, threshold=0.85, ttl=3600):
        self.cache = []  # [(embedding, question, answer, timestamp)]
        self.threshold = threshold
        self.ttl = ttl

    def get(self, question, embedding):
        # 1. 向量检索
        scores = [cosine(e1, embedding) for e1, _, _, _ in self.cache]

        # 2. 找最佳匹配
        if not scores:
            return None

        best_idx = np.argmax(scores)
        best_score = scores[best_idx]

        # 3. 检查阈值和 TTL
        if best_score >= self.threshold:
            _, _, answer, timestamp = self.cache[best_idx]
            if time.time() - timestamp < self.ttl:
                return answer

        return None

    def set(self, question, embedding, answer):
        # 检查缓存大小,超限时淘汰最旧的
        if len(self.cache) > 10000:
            self.cache.pop(0)
        self.cache.append((embedding, question, answer, time.time()))

性能对比:

方案 命中率 延迟 成本
无缓存 0% 500ms ¥0.01/次
精确缓存 10-20% 10ms ¥0.002/次
语义缓存 30-50% 50ms ¥0.005/次

面试话术:

"语义缓存的关键不是固定 0.85 阈值,而是缓存键必须包含租户、权限、知识库版本、模型/Prompt 版本和时间敏感性。阈值按 false-hit 成本用标注对校准;高风险或个性化回答默认不跨用户复用。"

官方与论文来源(截至 2026-10)

相关推荐
lazy熊1 小时前
Codex 能聊天但读不到项目文件:通常是这 4 个地方没有对上
人工智能
tianyatest1 小时前
魔兽争霸 III 接入大语言模型:基于文件通道的混合式游戏 AI 智能体系统设计与实现
人工智能·游戏·语言模型·war3
buddyMe智能助手1 小时前
不改模型权重,让智能体自己进化:脚手架自我改良的路线图
人工智能
Fang_YuanAI1 小时前
AI反推提示词指南:12种场景反推模板,角色、插画、摄影、风景全覆盖
人工智能
Sayai1 小时前
ComfyUI+Flux ControlNet 实战:一张图拆线稿和深度图,双模型重构画面(12G 显存实测,附模型与工作流)
人工智能·重构·aigc·flux·controlnet·comfyui
墨天梦1 小时前
D21_第三周系统功能回归
人工智能·数据挖掘·回归
牛战士coding1 小时前
LLM 选错工具怎么办?提示词之外还需要确定性规则兜底
java·人工智能
虎鲸不是鱼1 小时前
【虎鲸进化论 07】给 AI 装上手和脚:MCP 协议与“给 Agent 用的工具“设计
人工智能·自动化·大模型·llm·自进化agent