08-RAG技术详解:让大模型拥有"外挂知识库"

RAG技术详解:让大模型拥有"外挂知识库"

理解检索增强生成的原理与实践,让大模型访问最新、最准确的信息。

前言

大语言模型虽然强大,但存在一个致命缺陷:知识截止。GPT-4的知识只到2023年,无法回答关于最新事件的问题。而且,模型可能对某些专业知识了解不足。

**RAG(Retrieval-Augmented Generation,检索增强生成)**就是为了解决这个问题而生------让大模型能够"查阅资料"后再回答问题。


一、RAG的核心概念

为什么需要RAG?

markdown 复制代码
大模型的局限性:

1. 知识截止
   用户:2024年奥运会金牌榜是怎样的?
   模型:我的知识截止于2023年... ❌

2. 幻觉问题
   用户:介绍一下《时间简史》这本书
   模型:《时间简史》是霍金写的...作者是张三 ❌(错误信息)

3. 私有数据
   用户:根据公司内部文档回答这个问题
   模型:我无法访问您的私有数据 ❌

4. 专业领域
   用户:这个医学影像显示什么问题?
   模型:我不是医学专家... ❌

RAG的解决思路

markdown 复制代码
RAG工作流程:

用户提问
    ↓
┌─────────────────┐
│   检索阶段       │  在知识库中搜索相关内容
└────────┬────────┘
         ↓
┌─────────────────┐
│   增强阶段       │  将检索结果与问题组合
└────────┬────────┘
         ↓
┌─────────────────┐
│   生成阶段       │  大模型基于上下文生成回答
└────────┬────────┘
         ↓
    最终回答

RAG vs 微调

维度 RAG 微调
知识更新 实时更新知识库 需要重新训练
成本 较低 较高
可解释性 可追溯知识来源 黑盒
适用场景 知识密集型任务 特定能力学习
私有数据 天然支持 需要训练数据

二、RAG架构详解

基础架构

复制代码
┌─────────────────────────────────────────────────────────────┐
│                      RAG系统架构                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                    离线阶段                          │   │
│  │  文档 → 分块 → 向量化 → 存入向量数据库                 │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                    在线阶段                          │   │
│  │                                                     │   │
│  │  用户问题 ─→ 向量化 ─→ 向量检索 ─→ 获取相关文档      │   │
│  │                                        ↓            │   │
│  │  问题 + 相关文档 ─→ Prompt构建 ─→ LLM生成回答        │   │
│  │                                                     │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

代码实现

python 复制代码
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Chroma
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.llms import OpenAI
from langchain.chains import RetrievalQA

class RAGSystem:
    def __init__(self, documents, embedding_model="text-embedding-ada-002"):
        self.embeddings = OpenAIEmbeddings(model=embedding_model)
        self.llm = OpenAI(temperature=0)
        self.vectorstore = None
        self.documents = documents

    def build_index(self, chunk_size=1000, chunk_overlap=200):
        """构建向量索引"""
        # 文档分块
        text_splitter = RecursiveCharacterTextSplitter(
            chunk_size=chunk_size,
            chunk_overlap=chunk_overlap
        )
        chunks = text_splitter.split_documents(self.documents)

        # 创建向量存储
        self.vectorstore = Chroma.from_documents(
            documents=chunks,
            embedding=self.embeddings
        )

        print(f"已索引 {len(chunks)} 个文档块")
        return self.vectorstore

    def query(self, question, k=4):
        """查询并生成回答"""
        # 检索相关文档
        docs = self.vectorstore.similarity_search(question, k=k)

        # 构建Prompt
        context = "\n\n".join([doc.page_content for doc in docs])
        prompt = f"""根据以下参考资料回答问题。如果资料中没有相关信息,请说"根据现有资料无法回答"。

参考资料:
{context}

问题:{question}

回答:"""

        # 生成回答
        response = self.llm(prompt)

        return {
            "answer": response,
            "sources": docs
        }

# 使用示例
from langchain.document_loaders import TextLoader

# 加载文档
loader = TextLoader("knowledge_base.txt")
documents = loader.load()

# 创建RAG系统
rag = RAGSystem(documents)
rag.build_index()

# 查询
result = rag.query("什么是机器学习?")
print(f"回答:{result['answer']}")
print(f"来源:{len(result['sources'])} 个文档块")

三、文档处理与分块

分块策略

python 复制代码
from langchain.text_splitter import (
    RecursiveCharacterTextSplitter,
    CharacterTextSplitter,
    TokenTextSplitter,
    MarkdownHeaderTextSplitter
)

# 策略1:递归字符分块(推荐)
recursive_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=200,
    separators=["\n\n", "\n", "。", "!", "?", " ", ""]
)

# 策略2:按Token分块
token_splitter = TokenTextSplitter(
    chunk_size=500,
    chunk_overlap=50
)

# 策略3:Markdown按标题分块
markdown_splitter = MarkdownHeaderTextSplitter(
    headers_to_split_on=[
        ("#", "header1"),
        ("##", "header2"),
        ("###", "header3")
    ]
)

# 策略4:语义分块(按句子边界)
def semantic_chunk(text, min_chunk_size=100, max_chunk_size=500):
    """按语义边界分块"""
    import re
    # 按句子分割
    sentences = re.split(r'[。!?\n]', text)

    chunks = []
    current_chunk = ""

    for sentence in sentences:
        if len(current_chunk) + len(sentence) < max_chunk_size:
            current_chunk += sentence
        else:
            if len(current_chunk) >= min_chunk_size:
                chunks.append(current_chunk)
            current_chunk = sentence

    if current_chunk:
        chunks.append(current_chunk)

    return chunks

分块最佳实践

markdown 复制代码
分块考虑因素:

1. 块大小
   ├── 太小:语义不完整
   └── 太大:检索精度下降

   推荐:500-1500字符

2. 块重叠
   ├── 避免关键信息被截断
   └── 推荐重叠:10-20%

3. 分块边界
   ├── 自然语言:按段落/句子
   ├── 代码:按函数/类
   └── Markdown:按标题层级

4. 元数据
   ├── 来源文件名
   ├── 页码/位置
   └── 创建时间

四、向量数据库

主流向量数据库对比

数据库 特点 适用场景
Pinecone 全托管,易用 生产环境
Milvus 开源,高性能 大规模部署
Chroma 轻量,本地运行 开发测试
Weaviate 支持混合检索 复杂查询
FAISS Meta开源,纯向量检索 研究原型
Qdrant Rust实现,高性能 生产环境

向量检索原理

python 复制代码
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity

def vector_search(query_embedding, document_embeddings, top_k=5):
    """
    向量相似度检索

    query_embedding: 查询向量 (d,)
    document_embeddings: 文档向量矩阵 (n, d)
    """
    # 计算余弦相似度
    similarities = cosine_similarity(
        [query_embedding],
        document_embeddings
    )[0]

    # 获取top-k索引
    top_indices = np.argsort(similarities)[-top_k:][::-1]

    return top_indices, similarities[top_indices]

# 示例
query = np.array([0.1, 0.2, 0.3])
docs = np.array([
    [0.1, 0.2, 0.35],  # 相似度最高
    [0.5, 0.6, 0.7],   # 相似度较低
    [0.15, 0.25, 0.32] # 相似度较高
])

indices, scores = vector_search(query, docs, top_k=2)
print(f"最相似的文档索引: {indices}")
print(f"相似度分数: {scores}")

混合检索

python 复制代码
def hybrid_search(query, vectorstore, keyword_index, alpha=0.5, k=10):
    """
    混合检索:向量检索 + 关键词检索

    alpha: 向量检索权重 (0-1)
    """
    # 向量检索
    vector_results = vectorstore.similarity_search(query, k=k*2)
    vector_scores = {r.id: r.score for r in vector_results}

    # 关键词检索 (BM25)
    keyword_results = keyword_index.search(query, k=k*2)
    keyword_scores = {r.id: r.score for r in keyword_results}

    # 分数归一化
    vector_scores = normalize_scores(vector_scores)
    keyword_scores = normalize_scores(keyword_scores)

    # 加权融合
    all_ids = set(vector_scores.keys()) | set(keyword_scores.keys())
    final_scores = {}

    for doc_id in all_ids:
        v_score = vector_scores.get(doc_id, 0)
        k_score = keyword_scores.get(doc_id, 0)
        final_scores[doc_id] = alpha * v_score + (1 - alpha) * k_score

    # 排序返回
    sorted_ids = sorted(final_scores.keys(),
                        key=lambda x: final_scores[x],
                        reverse=True)

    return sorted_ids[:k]

def normalize_scores(scores):
    """分数归一化到0-1"""
    if not scores:
        return scores
    max_score = max(scores.values())
    min_score = min(scores.values())
    if max_score == min_score:
        return {k: 1.0 for k in scores}
    return {k: (v - min_score) / (max_score - min_score)
            for k, v in scores.items()}

五、高级RAG技术

1. 多查询检索

python 复制代码
def multi_query_retrieval(query, llm, vectorstore, n_queries=3):
    """
    多查询检索:生成多个相关查询,扩展检索范围
    """
    # 生成多个查询变体
    prompt = f"""请生成{ n_queries}个与以下问题语义相似但表述不同的问题:

原始问题:{query}

要求:
1. 保持核心语义不变
2. 使用不同的表述方式
3. 每行一个问题

问题列表:"""

    response = llm(prompt)
    queries = [query] + response.strip().split('\n')

    # 对每个查询进行检索
    all_docs = []
    for q in queries:
        docs = vectorstore.similarity_search(q, k=3)
        all_docs.extend(docs)

    # 去重
    unique_docs = list({doc.id: doc for doc in all_docs}.values())

    return unique_docs

2. 重排序

python 复制代码
from sentence_transformers import CrossEncoder

def rerank_results(query, documents, top_k=5):
    """
    使用Cross-Encoder重排序检索结果
    """
    # 加载重排序模型
    reranker = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')

    # 构建查询-文档对
    pairs = [(query, doc.page_content) for doc in documents]

    # 计算相关性分数
    scores = reranker.predict(pairs)

    # 按分数排序
    scored_docs = list(zip(documents, scores))
    scored_docs.sort(key=lambda x: x[1], reverse=True)

    return [doc for doc, score in scored_docs[:top_k]]

3. 自查询检索

python 复制代码
def self_query_retrieval(query, llm, vectorstore):
    """
    自查询:从问题中提取过滤条件
    """
    prompt = f"""分析以下问题,提取检索相关信息:

问题:{query}

请输出JSON格式:
{{
    "search_query": "核心检索内容",
    "filters": {{
        "author": "作者名(如果有)",
        "date_range": {{"start": "开始日期", "end": "结束日期"}},
        "category": "分类(如果有)"
    }}
}}"""

    response = llm(prompt)
    parsed = parse_json(response)

    # 使用过滤条件检索
    results = vectorstore.similarity_search(
        parsed["search_query"],
        filter=parsed.get("filters", {})
    )

    return results

4. 知识图谱增强RAG

python 复制代码
def graph_enhanced_rag(query, vectorstore, knowledge_graph, llm):
    """
    结合知识图谱的RAG
    """
    # 1. 向量检索
    vector_docs = vectorstore.similarity_search(query, k=5)

    # 2. 从知识图谱中检索相关实体和关系
    entities = extract_entities(query)
    graph_info = []

    for entity in entities:
        # 获取实体的邻居节点和关系
        neighbors = knowledge_graph.get_neighbors(entity)
        relations = knowledge_graph.get_relations(entity)
        graph_info.append({
            "entity": entity,
            "neighbors": neighbors,
            "relations": relations
        })

    # 3. 构建增强Prompt
    context = format_context(vector_docs, graph_info)

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

向量检索结果:
{format_docs(vector_docs)}

知识图谱信息:
{format_graph_info(graph_info)}

问题:{query}

回答:"""

    return llm(prompt)

六、RAG评估

评估指标

markdown 复制代码
RAG评估维度:

1. 检索质量
   ├── 召回率 (Recall)
   ├── 精确率 (Precision)
   └── MRR (Mean Reciprocal Rank)

2. 生成质量
   ├── 准确性:回答是否正确
   ├── 相关性:回答是否切题
   ├── 完整性:信息是否完整
   └── 忠实性:是否基于检索内容

3. 端到端指标
   ├── 响应时间
   └── 用户满意度

评估代码

python 复制代码
from ragas import evaluate
from ragas.metrics import (
    faithfulness,
    answer_relevancy,
    context_recall,
    context_precision
)

def evaluate_rag(test_data):
    """
    使用RAGAS框架评估RAG系统

    test_data: DataFrame包含
    - question: 问题
    - answer: 生成的回答
    - contexts: 检索的文档
    - ground_truth: 标准答案
    """
    results = evaluate(
        test_data,
        metrics=[
            faithfulness,      # 忠实性
            answer_relevancy,  # 回答相关性
            context_recall,    # 上下文召回率
            context_precision  # 上下文精确率
        ]
    )

    return results

七、RAG最佳实践

实践建议

markdown 复制代码
1. 文档处理
   ├── 选择合适的分块策略
   ├── 保留文档元数据
   └── 定期更新知识库

2. 检索优化
   ├── 使用混合检索
   ├── 添加重排序步骤
   └── 调整chunk数量

3. 提示工程
   ├── 明确指示基于检索内容回答
   ├── 处理"不知道"的情况
   └── 添加引用来源

4. 系统设计
   ├── 缓存常见查询
   ├── 异步处理长文档
   └── 监控检索和生成质量

Prompt模板

python 复制代码
rag_prompt_template = """
你是一个专业的问答助手。请根据提供的参考资料回答用户问题。

要求:
1. 仅基于参考资料回答,不要使用外部知识
2. 如果参考资料中没有相关信息,请明确说明
3. 引用具体的参考来源(如[文档1])
4. 回答要简洁、准确、完整

参考资料:
{context}

用户问题:{question}

回答:
"""

小结

组件 功能 关键技术
文档处理 知识库构建 分块、清洗、元数据
向量化 文本→向量 Embedding模型
向量存储 向量索引与检索 Pinecone、Milvus等
检索 找到相关文档 向量检索、混合检索
生成 生成最终回答 LLM + Prompt

思考与练习

  1. 思考题

    • RAG和微调各适合什么场景?
    • 如何选择合适的分块大小?
  2. 动手练习

    • 使用LangChain构建一个简单的RAG系统
    • 尝试不同的分块策略,比较检索效果
  3. 延伸阅读


下期预告

下一篇文章,我们将深入探讨:Agent智能体:大模型的"手"和"脚"

会解答这些问题:

  • 什么是AI Agent?
  • Agent如何规划和执行任务?
  • 有哪些主流的Agent框架?

关注专栏,不错过后续更新!


作者:ECH00O00 本文首发于掘金专栏《AI科普实验室》 欢迎评论区交流讨论,点赞收藏就是最大的鼓励 ❤️

相关推荐
zhangfeng11332 小时前
unsloth 安装的时候会 自动升级torch版本,解决办法
人工智能·pytorch
HAREWORK_FFF2 小时前
用CAIE认证为简历加分:AI学习者的标准学习周期与规划
人工智能·学习·百度
郝学胜-神的一滴2 小时前
深度学习入门全解析:从核心概念到实战基础 | 技术研讨会精华总结
人工智能·python·深度学习·算法·cnn
简单光学2 小时前
深度学习相位解包裹研究进展
人工智能·深度学习
ECH00O002 小时前
09-Pre-training/预训练:AI的"通识教育"
人工智能
云道轩2 小时前
Langflow 1.8 正式发布:集中式提供商配置、更可预测的工作流 API,以及 UI 中更快速的调试与迭代。
人工智能·智能体·langflow
火山引擎开发者社区2 小时前
ArkClaw让“养虾”更安全!火山引擎AI助手安全解决方案全面升级
人工智能
中杯可乐多加冰2 小时前
说实话,我建议还是自己写小龙虾的Skills,手把手教你打造阅读文献和生成阅读笔记的OpenClaw Skills
人工智能
道可云2 小时前
AI全球快讯(3月12日)丨Meta收购AI智能体社交平台Moltbook
人工智能