【自学30天掌握AI开发】第18天 - RAG技术原理

Day18-RAG技术原理

封面图

欢迎来到《自学30天掌握AI开发》的第18天!在前面的课程中,我们学习了AI Agent技术和MCP技术的应用。今天,我们将深入探索检索增强生成(RAG)技术的原理。RAG技术是大语言模型应用的一次重要突破,它通过将外部知识融入生成过程,有效克服了大模型的知识局限性,使AI系统能够提供更准确、更可靠的回答。本课程将带你理解RAG的基本概念、技术架构和关键组件,为你后续开发基于RAG的应用打下坚实基础。

🎯 学习目标

完成今天的学习后,你将能够:

  1. 理解检索增强生成(RAG)的基本概念与工作原理
  2. 掌握RAG系统的核心组件与技术架构
  3. 了解RAG在解决大语言模型局限性方面的价值
  4. 能够分析不同RAG实现方案的优缺点
  5. 准备为构建自己的RAG系统奠定理论基础

⏱️ 学习建议

今天的内容涉及RAG技术的基础理论,建议按以下方式规划你的学习时间:

学习内容建议时间
RAG基础概念学习45分钟
文本处理与嵌入技术60分钟
向量存储与检索原理60分钟
上下文构建策略45分钟
生成与后处理技术45分钟
RAG系统评估方法30分钟
实践活动90分钟
自测检验30分钟

学习方法建议

  1. 概念关联法:将RAG技术与之前学习的大语言模型知识关联起来,理解其作为增强技术的价值
  2. 问题驱动法:思考大模型的局限性问题,以及RAG如何解决这些问题
  3. 可视化学习:绘制RAG系统的组件流程图,帮助理解各组件间的关系
  4. 主动思考:针对自己感兴趣的领域,思考RAG技术的潜在应用场景
  5. 动手实验:尝试使用简单的RAG框架进行基础实验,加深理解

🔑 核心知识点

1. 检索增强生成(RAG)基础概念

1.1 RAG的定义与发展

**检索增强生成(Retrieval-Augmented Generation, RAG)**是一种将信息检索与文本生成相结合的技术框架,它通过在生成过程中引入外部知识源,增强大语言模型的回答能力。

RAG的核心思想可以简单表述为:

最终输出 = 大语言模型能力 + 外部检索到的相关信息

RAG技术的发展历程

  • 2020年:Facebook AI Research(现Meta AI)发表了开创性论文《Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks》,首次提出RAG概念
  • 2021年:开源社区开始实现各种RAG框架,如LangChain、Haystack等
  • 2022年:RAG技术在企业级应用中开始广泛部署,特别是在知识密集型应用场景
  • 2023年:高级RAG架构出现,包括多查询RAG、递归RAG和混合检索策略等
  • 2024年:RAG与Agent结合,形成智能知识工作者,能够自主检索和推理

与传统问答系统的区别

维度传统问答系统RAG系统
回答生成方式直接检索预定义答案动态结合检索内容生成答案
表达灵活性有限,通常是模板化回答高度灵活,自然流畅
知识更新需要手动维护答案库只需更新知识库,生成能力不变
未见问题处理难以处理未预设的问题能基于相关信息推理出答案
实现复杂度相对简单架构较复杂,需要多个组件协同
1.2 RAG解决的核心问题

RAG技术主要解决了大语言模型在实际应用中面临的几个关键挑战:

知识局限性问题

  • 大模型知识在预训练截止日期后不再更新
  • 专业领域知识可能覆盖不足
  • 私有或组织特定信息无法获取

幻觉与事实错误

  • 大模型可能生成看似合理但实际不正确的内容
  • 缺乏对生成内容的事实依据
  • 无法区分真实知识与推测内容

透明度与可追溯性

  • 传统大模型难以提供信息来源
  • 无法验证回答的准确性与可靠性
  • 缺乏对结论的解释与支持证据

数据私密性与合规性

  • 直接向大模型提供敏感信息存在风险
  • 需要控制模型对专有信息的访问
  • 满足行业监管与合规要求
1.3 RAG的基本工作流程

RAG系统的典型工作流程包含以下关键步骤:

┌─────────────┐    ┌─────────────┐    ┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│  1.查询处理  │ -> │  2.知识检索  │ -> │ 3.上下文构建 │ -> │  4.增强生成  │ -> │ 5.后处理优化 │
└─────────────┘    └─────────────┘    └─────────────┘    └─────────────┘    └─────────────┘

各步骤详解

  1. 查询处理

    • 理解用户问题/查询的意图
    • 提取关键词和概念
    • 查询重写和扩展
    • 确定检索策略
  2. 知识检索

    • 在向量数据库中查找相关文档/段落
    • 执行语义搜索找到相关信息
    • 排序检索结果
    • 应用过滤和筛选逻辑
  3. 上下文构建

    • 选择最相关的检索结果
    • 组织检索内容形成连贯上下文
    • 处理令牌长度限制
    • 确保关键信息包含在上下文中
  4. 增强生成

    • 将查询和检索到的上下文提供给LLM
    • 应用优化的提示模板引导生成
    • 指导模型基于检索到的信息回答
    • 控制生成过程的参数
  5. 后处理优化

    • 验证生成内容的准确性
    • 添加引用和来源信息
    • 格式化和美化输出
    • 过滤不当或不相关内容

Python伪代码示例

def rag_pipeline(user_query):
    # 1. 查询处理
    processed_query = query_processor.process(user_query)
    
    # 2. 知识检索
    relevant_documents = vector_store.search(
        query_embedding=embedding_model.embed(processed_query),
        top_k=5
    )
    
    # 3. 上下文构建
    context = context_builder.build(relevant_documents, token_budget=3000)
    
    # 4. 增强生成
    prompt = f"""
    基于以下信息回答问题。如果信息中没有答案,请说明你不知道,不要编造信息。
    
    信息:
    {context}
    
    问题: {user_query}
    """
    
    response = llm.generate(prompt)
    
    # 5. 后处理优化
    final_response = post_processor.process(
        response=response,
        sources=relevant_documents,
        query=user_query
    )
    
    return final_response
1.4 RAG应用场景与优势

典型应用场景

  • 企业知识库与支持系统

    • 接入内部文档、产品手册、知识库
    • 提供精准的员工和客户支持
    • 保持知识的时效性和准确性
  • 专业领域问答系统

    • 法律、医疗、金融等专业咨询
    • 基于权威资料提供专业建议
    • 确保回答有可靠依据
  • 个性化教育与学习助手

    • 根据教材和课程内容提供解答
    • 适应特定学科和课程体系
    • 提供有针对性的学习资料
  • 研究与创新支持工具

    • 快速检索相关研究论文和专利
    • 辅助文献综述和研究分析
    • 促进跨领域知识发现

RAG相比纯LLM的关键优势

  1. 知识时效性:可随时更新知识库,无需重新训练模型
  2. 可控可靠性:回答基于特定知识源,减少幻觉和错误
  3. 可解释性:能够提供信息来源,增加透明度
  4. 成本效益:避免针对特定领域进行大规模微调
  5. 数据私密性:知识库完全可控,不会泄露给模型提供商
  6. 可定制性:可以根据具体需求定制知识库内容和范围
  7. 长尾知识:覆盖大模型训练数据中稀疏的专业内容

2. 文本处理与嵌入技术

RAG系统的基础在于高质量的文本处理和嵌入生成,这直接影响检索的准确性和系统整体性能。

2.1 文本预处理技术

在将文档加入RAG系统前,需要进行一系列预处理步骤,以提高后续检索的效果:

文本清洗与标准化

  • HTML/特殊格式清洗:移除HTML标签、特殊符号和格式标记
  • 统一编码与规范化:确保文本使用一致的编码(如UTF-8),处理特殊字符
  • 拼写纠正与规范化:修正明显的拼写错误,统一术语表达
  • 大小写与标点处理:根据需要统一大小写和标点符号

文本分段与结构化

  • 段落识别与边界检测:根据自然段落或主题边界分割文本
  • 标题与层次结构识别:识别文档的层次结构,如标题、子标题
  • 表格与列表提取:识别并适当处理表格、列表等结构化内容
  • 元数据提取:提取文档标题、作者、日期等元信息

语言学预处理

  • 分词(Tokenization):将文本拆分为词或子词单元
  • 停用词过滤:去除常见但意义不大的词(如"的"、“是”、“and”、“the”)
  • 词干提取/词形还原:将不同形式的词转换为基本形式
  • 词性标注:识别词的语法角色(名词、动词等)

实体识别与关系提取

  • 命名实体识别(NER):检测人名、地点、组织、日期等实体
  • 关系抽取:识别实体间的关系(如"X是Y的创始人")
  • 关键术语提取:识别文档中的专业术语和重要概念
  • 关联信息注解:增强实体与外部知识的关联

Python代码示例:使用NLTK和spaCy进行文本预处理

import nltk
import spacy
from nltk.corpus import stopwords
from nltk.stem import PorterStemmer
from bs4 import BeautifulSoup

# 加载必要的资源
nltk.download('punkt')
nltk.download('stopwords')
nlp = spacy.load("en_core_web_sm")
stop_words = set(stopwords.words('english'))
stemmer = PorterStemmer()

def preprocess_text(text, clean_html=True, remove_stopwords=True, perform_stemming=False):
    """文本预处理综合函数"""
    # HTML清洗
    if clean_html:
        soup = BeautifulSoup(text, "html.parser")
        text = soup.get_text(separator=" ")
    
    # 分词和基础清理
    tokens = nltk.word_tokenize(text)
    tokens = [token.lower() for token in tokens if token.isalpha()]
    
    # 停用词过滤
    if remove_stopwords:
        tokens = [token for token in tokens if token not in stop_words]
    
    # 词干提取
    if perform_stemming:
        tokens = [stemmer.stem(token) for token in tokens]
    
    # 实体识别示例
    doc = nlp(" ".join(tokens))
    entities = [(ent.text, ent.label_) for ent in doc.ents]
    
    processed_text = " ".join(tokens)
    return {
        "processed_text": processed_text,
        "entities": entities,
        "token_count": len(tokens)
    }

# 使用示例
sample_text = """
<p>Apple Inc. was founded by Steve Jobs, Steve Wozniak, and Ronald Wayne in April 1976. 
The company's first product was the Apple I, a computer designed and hand-built by Wozniak.</p>
"""

result = preprocess_text(sample_text)
print(f"处理后文本: {result['processed_text'][:100]}...")
print(f"识别到的实体: {result['entities']}")
print(f"标记数量: {result['token_count']}")
2.2 文档分块策略

文档分块(Chunking)是RAG系统中至关重要的一步,它直接影响检索的粒度和准确性:

分块的目的与重要性

  • 将长文档切分为适合嵌入和检索的较小单元
  • 确保语义完整性,避免关键上下文丢失
  • 控制嵌入模型输入大小,遵守模型限制
  • 提高检索粒度,精确定位相关信息

常见分块策略

  1. 固定大小分块

    • 按固定字符数、词数或标记数分割
    • 优点:实现简单,处理一致
    • 缺点:可能切分不当,破坏语义完整性
  2. 语义边界分块

    • 基于段落、句子或自然语义边界分割
    • 优点:保持语义完整性
    • 缺点:块大小不均匀,可能需要进一步处理
  3. 混合分块法

    • 首先按语义边界分割,然后应用大小限制
    • 优点:平衡语义完整性和大小控制
    • 实现:先按段落分,大段落再细分
  4. 层次分块

    • 创建多层次的分块,包含不同粒度
    • 优点:支持多级检索,灵活性高
    • 应用:先文档级,再章节,最后段落级检索

分块重叠技术

为避免关键信息在分块边界丢失,通常采用重叠分块技术:

块1: [.........]
块2:      [.........]
块3:           [.........]
  • 重叠量设置:通常为块大小的10%-20%
  • 重叠内容选择:优先确保句子完整性
  • 边界处理:特别注意实体和关系不被分割

Python代码示例:实现不同分块策略

def chunk_by_fixed_size(text, chunk_size=1000, overlap=200):
    """固定大小分块,带重叠"""
    chunks = []
    start = 0
    text_len = len(text)
    
    while start < text_len:
        # 计算当前块的结束位置
        end = min(start + chunk_size, text_len)
        
        # 如果不是最后一块,尝试找到更合适的结束位置(如句号)
        if end < text_len:
            # 在重叠范围内寻找句子边界
            sentence_boundary = text.rfind('. ', end - min(overlap, end-start), end) + 1
            if sentence_boundary > start:
                end = sentence_boundary
        
        # 添加当前块
        chunks.append(text[start:end].strip())
        
        # 移动到下一块的起始位置,考虑重叠
        start = end - overlap if end < text_len else text_len
    
    return chunks

def chunk_by_semantic_units(text, max_chunk_size=1500):
    """语义边界分块(段落为单位)"""
    # 按段落分割文本
    paragraphs = [p.strip() for p in text.split('\n\n') if p.strip()]
    
    chunks = []
    current_chunk = ""
    
    for paragraph in paragraphs:
        # 如果当前段落加上已有内容不超过最大尺寸,就添加到当前块
        if len(current_chunk) + len(paragraph) <= max_chunk_size:
            current_chunk += (paragraph + "\n\n")
        else:
            # 如果当前块非空,添加到结果
            if current_chunk:
                chunks.append(current_chunk.strip())
            
            # 开始新块
            # 如果单个段落超过最大尺寸,需要进一步分割
            if len(paragraph) > max_chunk_size:
                # 按句子分割过长段落
                sentences = paragraph.replace('. ', '.|').split('|')
                current_chunk = ""
                for sentence in sentences:
                    if len(current_chunk) + len(sentence) <= max_chunk_size:
                        current_chunk += (sentence + " ")
                    else:
                        chunks.append(current_chunk.strip())
                        current_chunk = sentence + " "
            else:
                current_chunk = paragraph + "\n\n"
    
    # 添加最后一个块
    if current_chunk:
        chunks.append(current_chunk.strip())
    
    return chunks

# 使用示例
text = """这是第一个段落,包含几个句子。这些句子应该被保持在一起。这是该段落的最后一句。

这是第二个段落,讨论不同的主题。它也有多个句子。这些句子形成一个连贯的思想单元。

这是一个很长的段落,可能需要被分割。它包含许多句子,讨论同一个主题的不同方面。这样的长段落在某些文档中很常见。如技术文档、学术论文或详细报告。在RAG系统中,适当处理这类长段落很重要。否则可能会丢失重要上下文。分块策略应该平衡语义完整性和大小限制。"""

fixed_chunks = chunk_by_fixed_size(text)
semantic_chunks = chunk_by_semantic_units(text)

print("固定大小分块结果:")
for i, chunk in enumerate(fixed_chunks):
    print(f"块 {i+1} ({len(chunk)} 字符): {chunk[:50]}...")

print("\n语义分块结果:")
for i, chunk in enumerate(semantic_chunks):
    print(f"块 {i+1} ({len(chunk)} 字符): {chunk[:50]}...")
2.3 嵌入模型选择

嵌入模型(Embedding Model)是RAG系统的核心组件之一,它将文本转换为向量表示,使语义检索成为可能:

嵌入模型的作用

  • 将自然语言文本转换为向量空间中的数值表示
  • 捕捉文本的语义信息,相似内容在向量空间中距离较近
  • 支持高效的语义相似度搜索和匹配

主流嵌入模型对比

嵌入模型维度上下文长度语言支持特点
OpenAI text-embedding-315368192多语言高质量通用嵌入,支持较长文本
Cohere embedding-english4096512英语为主高维度嵌入,适合精确检索
BAAI/bge-large1024512多语言开源模型,中英文表现均佳
Voyage/voyage-large10244096多语言支持长文本,开源可商用
Jina embeddings7688192多语言长文档支持,开源可本地部署
SentenceTransformers384-768512多语言轻量级,适合资源受限场景

选择考量因素

  1. 语义理解能力

    • 对相似概念的关联能力
    • 跨领域理解的泛化性
    • 专业术语的表示准确性
  2. 性能与资源需求

    • 向量维度与存储开销
    • 计算速度与硬件需求
    • 批处理效率与吞吐量
  3. 语言与领域支持

    • 多语言支持能力
    • 领域特定语言理解
    • 跨语言语义匹配
  4. 接口与集成

    • API可用性与稳定性
    • 本地部署可能性
    • 与其他组件的集成难度

嵌入测试与评估方法

评估嵌入模型性能的常用方法:

import numpy as np
from sklearn.metrics.pairwise import cosine_similarity

def evaluate_embedding_model(model, test_pairs, similarity_threshold=0.7):
    """
    评估嵌入模型在语义相似性任务上的表现
    
    参数:
    - model: 嵌入模型对象
    - test_pairs: 列表,每项为(text1, text2, expected_similar),expected_similar为布尔值
    - similarity_threshold: 判定为相似的余弦相似度阈值
    
    返回:
    - 准确率、精确率、召回率和F1分数
    """
    correct = 0
    true_positives = 0
    false_positives = 0
    false_negatives = 0
    
    results = []
    
    for text1, text2, expected in test_pairs:
        # 获取嵌入
        embedding1 = model.embed(text1)
        embedding2 = model.embed(text2)
        
        # 计算余弦相似度
        similarity = cosine_similarity([embedding1], [embedding2])[0][0]
        
        # 基于阈值判断相似性
        predicted_similar = similarity >= similarity_threshold
        
        # 评估预测
        if predicted_similar == expected:
            correct += 1
        
        if predicted_similar and expected:
            true_positives += 1
        elif predicted_similar and not expected:
            false_positives += 1
        elif not predicted_similar and expected:
            false_negatives += 1
            
        results.append({
            "text1": text1,
            "text2": text2,
            "expected": expected,
            "predicted": predicted_similar,
            "similarity": similarity
        })
    
    # 计算指标
    accuracy = correct / len(test_pairs)
    precision = true_positives / (true_positives + false_positives) if (true_positives + false_positives) > 0 else 0
    recall = true_positives / (true_positives + false_negatives) if (true_positives + false_negatives) > 0 else 0
    f1 = 2 * (precision * recall) / (precision + recall) if (precision + recall) > 0 else 0
    
    return {
        "accuracy": accuracy,
        "precision": precision,
        "recall": recall,
        "f1": f1,
        "detailed_results": results
    }

# 使用示例
test_pairs = [
    ("人工智能正在改变我们的生活方式", "AI技术给日常生活带来巨大变革", True),
    ("深度学习是机器学习的一个分支", "神经网络是深度学习的基础", True),
    ("Python是一种编程语言", "苹果是一种水果", False),
    ("向量数据库用于存储嵌入", "矢量型数据库专门设计用于高效管理向量嵌入", True),
    ("RAG技术解决了大语言模型的知识局限性", "股票市场今天下跌了2%", False)
]

# 假设我们有一个嵌入模型
# results = evaluate_embedding_model(embedding_model, test_pairs)
# print(f"准确率: {results['accuracy']:.2f}")
# print(f"F1分数: {results['f1']:.2f}")
2.4 嵌入生成最佳实践

在实际RAG系统中,嵌入生成需要考虑多种因素以优化检索效果:

嵌入粒度选择

  • 文档级嵌入:适合整体相关性判断和初筛
  • 段落级嵌入:平衡语义完整性和检索精度的常用选择
  • 句子级嵌入:适合精确答案搜索,但可能缺乏上下文
  • 多粒度混合:综合使用不同粒度嵌入,分层检索

嵌入缓存与批处理

  • 实现嵌入结果缓存,避免重复计算
  • 利用批处理提高嵌入生成吞吐量
  • 增量更新策略,仅处理新增或修改内容
  • 分布式处理大规模文档集合

领域适应技术

  • 使用领域内数据微调通用嵌入模型
  • 实施专业术语增强处理
  • 结合领域知识图谱改进嵌入质量
  • 开发领域特定相似度度量方法

多模态嵌入考虑

  • 文本与图像联合嵌入生成
  • 表格、图表等结构化内容特殊处理
  • 音频和视频内容的文本转写与嵌入
  • 跨模态检索支持

嵌入生成工程最佳实践

import time
import hashlib
import pickle
from typing import List, Dict, Any
import numpy as np

class EmbeddingManager:
    """高效嵌入生成和管理类"""
    
    def __init__(self, embedding_model, cache_path=None, batch_size=32):
        """
        初始化嵌入管理器
        
        参数:
        - embedding_model: 嵌入模型对象
        - cache_path: 缓存文件路径,None表示不使用缓存
        - batch_size: 批处理大小
        """
        self.embedding_model = embedding_model
        self.cache_path = cache_path
        self.batch_size = batch_size
        self.embedding_cache = {}
        
        # 加载缓存
        if cache_path:
            try:
                with open(cache_path, 'rb') as f:
                    self.embedding_cache = pickle.load(f)
                print(f"已加载 {len(self.embedding_cache)} 条嵌入缓存")
            except (FileNotFoundError, pickle.PickleError):
                print("未找到缓存文件或加载失败,将创建新缓存")
    
    def _get_cache_key(self, text: str) -> str:
        """生成文本的唯一缓存键"""
        return hashlib.md5(text.encode('utf-8')).hexdigest()
    
    def save_cache(self):
        """保存嵌入缓存到文件"""
        if self.cache_path:
            with open(self.cache_path, 'wb') as f:
                pickle.dump(self.embedding_cache, f)
            print(f"已保存 {len(self.embedding_cache)} 条嵌入缓存")
    
    def embed_texts(self, texts: List[str], show_progress=False) -> np.ndarray:
        """
        批量生成文本嵌入,利用缓存和批处理
        
        参数:
        - texts: 文本列表
        - show_progress: 是否显示进度
        
        返回:
        - 嵌入向量数组,形状为 (len(texts), embedding_dim)
        """
        embeddings = []
        texts_to_embed = []
        indices_to_embed = []
        
        # 检查哪些文本需要重新嵌入
        for i, text in enumerate(texts):
            cache_key = self._get_cache_key(text)
            if cache_key in self.embedding_cache:
                embeddings.append(self.embedding_cache[cache_key])
            else:
                texts_to_embed.append(text)
                indices_to_embed.append(i)
        
        # 如果有需要嵌入的文本
        if texts_to_embed:
            # 批处理生成嵌入
            total_batches = (len(texts_to_embed) + self.batch_size - 1) // self.batch_size
            new_embeddings = []
            
            for batch_idx in range(total_batches):
                start_idx = batch_idx * self.batch_size
                end_idx = min((batch_idx + 1) * self.batch_size, len(texts_to_embed))
                batch_texts = texts_to_embed[start_idx:end_idx]
                
                if show_progress:
                    print(f"处理批次 {batch_idx+1}/{total_batches},包含 {len(batch_texts)} 个文本")
                
                start_time = time.time()
                batch_embeddings = self.embedding_model.embed_batch(batch_texts)
                elapsed = time.time() - start_time
                
                if show_progress:
                    print(f"批次嵌入耗时: {elapsed:.2f}秒 ({len(batch_texts)/elapsed:.2f} 文本/秒)")
                
                new_embeddings.extend(batch_embeddings)
                
                # 更新缓存
                for i, text in enumerate(batch_texts):
                    cache_key = self._get_cache_key(text)
                    self.embedding_cache[cache_key] = batch_embeddings[i]
            
            # 将新生成的嵌入插入到正确位置
            for i, idx in enumerate(indices_to_embed):
                embeddings.insert(idx, new_embeddings[i])
        
        return np.array(embeddings)
    
    def embed_text(self, text: str) -> np.ndarray:
        """单文本嵌入生成,使用缓存"""
        return self.embed_texts([text])[0]

# 使用示例(伪代码)
# embedding_model = SomeEmbeddingModel()
# manager = EmbeddingManager(embedding_model, cache_path="embeddings_cache.pkl")
# documents = ["文档1", "文档2", "文档3", ...]
# embeddings = manager.embed_texts(documents, show_progress=True)
# manager.save_cache()

3. 向量存储与检索技术

在RAG系统中,向量存储和检索是连接文档处理与生成模型的核心环节,它决定了系统能否找到与用户查询最相关的信息。

3.1 向量数据库基础

向量数据库的定义与作用

向量数据库是专门设计用于存储、管理和检索高维向量数据的数据库系统。在RAG中,它用于存储文档嵌入向量并支持高效的相似性搜索。

向量数据库与传统数据库的区别

特性传统关系型数据库向量数据库
主要数据类型结构化表格数据高维向量数据
查询方式精确匹配(SQL)近似相似度搜索
索引机制B树、哈希等HNSW、IVF、LSH等
优化目标ACID事务、连接效率ANN搜索、向量操作
扩展性垂直扩展为主水平扩展友好

主流向量数据库对比

数据库类型开源存储方式索引算法特点
Pinecone托管服务云端HNSW变体易用性强,无需维护
Weaviate全栈数据库本地/云端HNSW支持多模态,GraphQL接口
Milvus分布式系统本地/云端多算法支持高性能,分布式架构
Qdrant向量搜索引擎本地/云端HNSW高度可配置,过滤功能强大
Chroma轻量级库内存/持久化基础算法开发友好,易于集成
FAISS库(非数据库)内存多算法支持高性能,Facebook开发

选择向量数据库的考量因素

  1. 规模需求:数据量大小和查询频率
  2. 部署要求:本地、云端或混合部署
  3. 性能需求:查询延迟和吞吐量要求
  4. 功能需求:元数据过滤、多模态支持等
  5. 预算与资源:托管服务成本vs自建维护
3.2 向量索引原理

向量索引是向量数据库高效检索的核心,其目的是避免在查询时与所有向量进行计算,从而加速相似性搜索:

常见索引算法

  1. HNSW (Hierarchical Navigable Small World)

    • 原理:构建多层图结构,支持对数时间复杂度的搜索
    • 优势:搜索速度快,精度高
    • 劣势:内存占用大,构建时间长
    • 应用:Qdrant、Weaviate、Pinecone等广泛采用
  2. IVF (Inverted File Index)

    • 原理:将向量空间分割为多个单元,搜索时仅检查相关单元
    • 优势:平衡速度和内存占用
    • 劣势:精度受聚类质量影响
    • 应用:FAISS的基础索引之一
  3. PQ (Product Quantization)

    • 原理:将高维向量分解成低维子向量,每个子向量独立量化
    • 优势:极大减小存储空间,加快搜索
    • 劣势:降低了精度
    • 应用:常与IVF结合使用(IVF-PQ)
  4. LSH (Locality-Sensitive Hashing)

    • 原理:使用特殊哈希函数,相似向量映射到相同桶
    • 优势:理论基础扎实,易于理解
    • 劣势:精度较低,需要多个哈希表
    • 应用:较早的ANN实现方式

索引参数调优

HNSW索引的关键参数及其影响:

# HNSW索引参数示例(Qdrant格式)
hnsw_config = {
    "m": 16,              # 每个节点的最大连接数,影响图的连通性,通常8-16
    "ef_construct": 100,  # 构建时的搜索宽度,影响构建质量,通常50-200
    "ef_search": 128,     # 搜索时的队列大小,影响搜索精度,通常50-500
    "max_elements": 1000000  # 索引最大元素数量估计
}

# 使用示例(伪代码)
# vector_db.create_collection(
#    name="documents",
#    vectors_config={"size": 1536, "distance": "Cosine"},
#    hnsw_config=hnsw_config
# )

向量索引的性能权衡

构建向量索引时,需要在以下几个维度进行权衡:

  1. 查询速度 vs. 召回率

    • 更快的查询通常以降低召回率为代价
    • 参数调整:降低ef_search加速查询但降低精度
  2. 构建时间 vs. 索引质量

    • 更高质量的索引需要更长的构建时间
    • 参数调整:增加ef_construct提高质量但延长构建时间
  3. 内存占用 vs. 搜索性能

    • 更好的性能通常需要更多内存
    • HNSW比其他算法内存消耗更大,但查询更快
  4. 索引大小 vs. 向量维度

    • 高维向量索引占用更多空间
    • 量化技术(如PQ)可以压缩高维向量
3.3 相似性搜索与距离度量

向量搜索的核心是相似性计算,不同的距离度量方法适用于不同场景:

常用距离度量方法

  1. 余弦相似度 (Cosine Similarity)

    • 计算公式:cos(θ) = (A·B)/(||A||·||B||)
    • 适用场景:关注向量方向而非绝对大小的文本语义搜索
    • 特点:值域为[-1,1],常用于文本嵌入
    • 使用时需注意:对于L2归一化的向量,等价于欧氏距离
  2. 欧氏距离 (Euclidean Distance)

    • 计算公式:sqrt(Σ(ai-bi)²)
    • 适用场景:考虑向量的绝对位置和大小
    • 特点:值域为[0,∞),常用于图像特征
    • 使用时需注意:对向量长度敏感,通常需要归一化
  3. 点积 (Dot Product)

    • 计算公式:Σ(ai×bi)
    • 适用场景:适用于归一化向量的相似度计算
    • 特点:计算简单高效
    • 使用时需注意:仅适用于已归一化的向量
  4. 曼哈顿距离 (Manhattan Distance)

    • 计算公式:Σ|ai-bi|
    • 适用场景:特征具有正交独立性的场合
    • 特点:受异常值影响较小
    • 使用时需注意:很少用于高维语义空间

Python代码示例:不同相似度计算

import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
from scipy.spatial.distance import euclidean, cityblock

def calculate_similarities(vec1, vec2):
    """计算不同相似性度量"""
    # 确保向量是numpy数组
    vec1 = np.array(vec1)
    vec2 = np.array(vec2)
    
    # 余弦相似度
    cos_sim = cosine_similarity([vec1], [vec2])[0][0]
    
    # 欧氏距离
    euc_dist = euclidean(vec1, vec2)
    
    # 点积 (内积)
    dot_prod = np.dot(vec1, vec2)
    
    # 曼哈顿距离
    man_dist = cityblock(vec1, vec2)
    
    # L2归一化向量的余弦相似度
    vec1_norm = vec1 / np.linalg.norm(vec1)
    vec2_norm = vec2 / np.linalg.norm(vec2)
    norm_cos_sim = cosine_similarity([vec1_norm], [vec2_norm])[0][0]
    
    return {
        "cosine_similarity": cos_sim,
        "euclidean_distance": euc_dist,
        "dot_product": dot_prod,
        "manhattan_distance": man_dist,
        "normalized_cosine": norm_cos_sim
    }

# 使用示例
query_vec = [0.2, 0.5, 0.3, 0.8]
doc_vec = [0.1, 0.6, 0.2, 0.7]

similarities = calculate_similarities(query_vec, doc_vec)
for metric, value in similarities.items():
    print(f"{metric}: {value:.4f}")

为RAG选择合适的距离度量

嵌入类型推荐距离度量原因
文本语义嵌入余弦相似度关注语义方向而非量级
已归一化嵌入点积或余弦计算效率更高
多模态嵌入余弦或欧氏取决于嵌入空间特性
稀疏嵌入杰卡德或余弦处理稀疏性更好
3.4 向量检索优化策略

高效的向量检索需要多方面的优化,以下是提升RAG检索质量和性能的关键策略:

检索参数优化

  1. K值选择

    • 控制返回的相似文档数量
    • 过小:可能丢失相关信息
    • 过大:包含过多不相关内容,增加后续处理量
    • 建议:通常从5-10开始,根据需求调整
  2. 相似度阈值

    • 设置最小相似度要求,过滤低相关性结果
    • 动态阈值:根据查询特性自适应调整
    • 百分比阈值:仅保留相似度在前X%的结果
  3. 搜索深度(ef_search)

    • 控制检索时探索的节点数量
    • 增加可提高召回率,但降低速度
    • 根据精度要求和性能预算调整

混合检索策略

  1. 关键词与语义混合检索
    • 结合BM25等关键词匹配与向量搜索
    • 优势:兼顾确切词汇匹配和语义理解
    • 实现:两种搜索结果加权融合
def hybrid_search(query, vector_db, text_index, alpha=0.7):
    """混合检索函数
    
    参数:
    - query: 查询文本
    - vector_db: 向量数据库连接
    - text_index: 文本索引连接
    - alpha: 向量搜索权重(0-1)
    
    返回:
    - 混合排序的结果列表
    """
    # 向量语义搜索
    vector_results = vector_db.search(
        query_text=query,
        top_k=20,
        include_metadata=True
    )
    
    # 关键词搜索(如BM25)
    keyword_results = text_index.search(
        query=query,
        top_k=20
    )
    
    # 结果合并与重排序
    combined_results = {}
    
    # 处理向量搜索结果
    for item in vector_results:
        doc_id = item.id
        score = item.score * alpha
        combined_results[doc_id] = {
            "document": item.metadata,
            "score": score
        }
    
    # 处理关键词搜索结果
    for item in keyword_results:
        doc_id = item.id
        if doc_id in combined_results:
            # 如果已存在,融合得分
            combined_results[doc_id]["score"] += item.score * (1-alpha)
        else:
            # 添加新结果
            combined_results[doc_id] = {
                "document": item.metadata,
                "score": item.score * (1-alpha)
            }
    
    # 按得分排序
    sorted_results = sorted(
        combined_results.items(),
        key=lambda x: x[1]["score"],
        reverse=True
    )
    
    return [item[1]["document"] for item in sorted_results[:10]]
  1. 多索引检索

    • 为不同长度或类型的内容建立不同索引
    • 如:段落级索引+句子级索引
    • 优势:同时获取宏观语境和精准匹配
  2. 多查询技术

    • 从一个用户查询生成多个不同角度的查询
    • 聚合多个查询的检索结果
    • 优势:提高召回率,减少单一视角限制

元数据过滤与加权

  1. 元数据过滤
    • 基于时间、来源、主题等元数据进行预过滤
    • 减少搜索空间,提高相关性
    • 示例:仅检索最近一年的文档,或特定类别的内容
# 元数据过滤示例(Qdrant格式)
filter_condition = {
    "must": [
        {"key": "document_type", "match": {"value": "technical_report"}},
        {"key": "created_date", "range": {"gte": "2023-01-01"}}
    ],
    "should": [
        {"key": "source", "match": {"value": "official_documentation"}},
        {"key": "author", "match": {"value": "expert_committee"}}
    ]
}

# 使用示例(伪代码)
# results = vector_db.search(
#    query_vector=query_embedding,
#    filter=filter_condition,
#    limit=10
# )
  1. 检索结果加权
    • 根据文档新鲜度、权威性调整相似度分数
    • 时间衰减:较新文档获得更高权重
    • 来源权重:权威来源获得更高优先级

分布式与并行检索

  1. 数据分片

    • 将向量数据分散到多个节点,支持水平扩展
    • 每个节点负责子集的索引和搜索
    • 结果合并和排序在聚合层完成
  2. 查询并行处理

    • 并行执行多个不同的查询策略
    • 不同参数配置的搜索同时进行
    • 聚合各路结果,去重并排序
  3. 缓存机制

    • 热门查询结果缓存
    • 部分结果缓存(如常用文档的嵌入)
    • 多级缓存策略(内存、磁盘、分布式)

### 4. 上下文构建策略

上下文构建是RAG系统成功的关键环节。在向量检索返回相关文档后,我们需要将这些检索结果有效地整合为一个连贯的上下文,以供大语言模型生成准确的回答。

#### 4.1 上下文窗口设计

**上下文窗口的概念与重要性**:

上下文窗口(Context Window)是指提供给大语言模型的检索信息与用户查询的组合。设计合理的上下文窗口对RAG系统性能至关重要:

- 窗口过小:可能缺失关键信息,导致不完整回答
- 窗口过大:可能引入噪音,稀释关键信息,并增加token开销
- 丢失关键内容:如果重要信息不在窗口内,模型无法参考使用

**上下文窗口大小优化**:

选择最佳上下文窗口大小需要平衡多种因素:

1. **模型上下文长度限制**:
   - 不同模型支持的最大token数不同(如GPT-4支持8K-128K)
   - 需要为查询和生成保留足够空间
   - 通常检索内容可占用50-70%的token预算

2. **信息密度控制**:
   - 确保高相关性内容被优先包含
   - 避免重复和低价值信息占用空间
   - 保持充分但精简的内容

3. **令牌分配策略**:
   - 分配合理比例给检索内容、指令、示例、查询等
   - 根据任务复杂性动态调整分配
   - 预留足够空间给模型生成回答

**上下文窗口结构示例**:

典型的RAG上下文窗口结构:

┌─────────────────────────────────────────────┐
│ 系统指令 (10-15% token) │
│ - 角色设定、行为指导 │
│ - 输出格式要求 │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ 检索内容 (50-70% token) │
│ - 来源1: [相关文档片段…] │
│ - 来源2: [相关文档片段…] │
│ - 来源N: [相关文档片段…] │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ 用户查询 (5-10% token) │
│ - 原始问题或指令 │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ 预留生成空间 (15-30% token) │
└─────────────────────────────────────────────┘


**上下文窗口模板示例**:

```python
def create_context_window(query, retrieved_documents, max_tokens=3000):
    """创建RAG上下文窗口
    
    参数:
    - query: 用户查询
    - retrieved_documents: 检索到的文档列表,每项包含内容和元数据
    - max_tokens: 上下文窗口的最大token数
    
    返回:
    - 格式化的上下文窗口
    """
    # 系统指令部分(约500 tokens)
    system_instruction = """你是一个基于检索的AI助手。请基于提供的信息回答问题。
如果在提供的信息中找不到答案,请坦诚说明你不知道,不要编造信息。
回答时,请引用信息来源(用[来源X]标记)。确保你的回答准确、全面、有条理。"""
    
    # 为检索内容分配token预算(约70%的剩余空间)
    retrieval_token_budget = int((max_tokens - len(system_instruction) - len(query)) * 0.7)
    
    # 选择和格式化检索内容
    context_content = ""
    for i, doc in enumerate(retrieved_documents):
        source_tag = f"[来源{i+1}]: {doc.get('source', '未知来源')}"
        formatted_doc = f"{source_tag}\n{doc['content']}\n\n"
        
        # 检查是否超出token预算
        if len(context_content + formatted_doc) > retrieval_token_budget:
            # 如果这个文档太大,可能需要截断
            remaining_budget = retrieval_token_budget - len(context_content)
            if remaining_budget > 100:  # 确保至少有足够空间添加一些内容
                truncated_doc = formatted_doc[:remaining_budget] + "..."
                context_content += truncated_doc
            break
        
        context_content += formatted_doc
    
    # 构建完整上下文窗口
    full_context = f"""{system_instruction}

参考信息:
{context_content}

用户问题: {query}
"""
    return full_context
4.2 相关性排序与过滤

从向量数据库获取检索结果后,需要进一步处理这些结果,确保最相关、最有价值的信息被优先包含:

检索结果重排序技术

  1. 基础相似度排序

    • 基于向量相似度分数的初步排序
    • 缺点:仅考虑语义相似性,忽略其他因素
  2. 多特征重排序

    • 结合多种特征进行排序:语义相似度、关键词匹配度、信息新鲜度等
    • 使用机器学习模型(如LambdaMART)训练重排序器
    • 优势:综合考量文档相关性的多个维度
  3. 交叉编码器重排序

    • 使用交叉编码器(Cross-encoder)直接评估查询-文档对的相关性
    • 比双编码器(Bi-encoder)更准确但计算成本更高
    • 适用于检索结果的精细重排序(通常处理少量初筛结果)
from sentence_transformers import CrossEncoder

def rerank_with_cross_encoder(query, retrieved_documents, top_k=5):
    """使用交叉编码器重排序检索结果
    
    参数:
    - query: 用户查询
    - retrieved_documents: 初步检索的文档列表
    - top_k: 返回的最终文档数量
    
    返回:
    - 重排序后的文档列表
    """
    # 加载交叉编码器模型
    cross_encoder = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
    
    # 准备查询-文档对
    query_doc_pairs = [(query, doc['content']) for doc in retrieved_documents]
    
    # 使用交叉编码器计算相关性分数
    relevance_scores = cross_encoder.predict(query_doc_pairs)
    
    # 将分数与文档关联并排序
    scored_docs = [(score, doc) for score, doc in zip(relevance_scores, retrieved_documents)]
    scored_docs.sort(reverse=True, key=lambda x: x[0])
    
    # 返回排序后的前top_k个文档
    return [doc for _, doc in scored_docs[:top_k]]

冗余信息处理

检索结果中常包含相似或重复信息,需要进行去重处理:

  1. 简单去重

    • 基于内容哈希或精确匹配识别完全相同的段落
    • 限制:无法处理部分重复或表述不同但内容相似的情况
  2. 语义去重

    • 计算段落间的语义相似度,合并或过滤高度相似内容
    • 算法:最大边际相关性(MMR),平衡相关性与多样性
    • 挑选与查询相关且互相差异较大的文档
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity

def mmr_diversification(query_vector, doc_vectors, doc_contents, 
                        lambda_param=0.5, k=5):
    """
    使用最大边际相关性(MMR)算法进行多样化排序
    
    参数:
    - query_vector: 查询的向量表示
    - doc_vectors: 文档向量列表
    - doc_contents: 文档内容列表
    - lambda_param: 多样性权重(0-1),越小多样性越高
    - k: 返回的文档数量
    
    返回:
    - 多样化排序后的文档列表
    """
    # 确保输入是numpy数组
    query_vector = np.array(query_vector).reshape(1, -1)
    doc_vectors = np.array(doc_vectors)
    
    # 文档与查询的相似度
    sim_docs_query = cosine_similarity(doc_vectors, query_vector).flatten()
    
    # 已选择和未选择的文档索引
    unselected = list(range(len(doc_vectors)))
    selected = []
    
    # 先选择与查询最相似的文档
    current_idx = np.argmax(sim_docs_query)
    selected.append(current_idx)
    unselected.remove(current_idx)
    
    # 选择剩余文档
    while len(selected) < k and unselected:
        # 未选择文档与已选择文档的相似度矩阵
        sim_between_docs = cosine_similarity(
            doc_vectors[unselected], 
            doc_vectors[selected]
        )
        
        # 每个未选择文档与已选择文档的最大相似度
        max_sim_between = np.max(sim_between_docs, axis=1)
        
        # 计算MMR分数: λ*sim(d,q) - (1-λ)*max(sim(d,dj))
        mmr_scores = lambda_param * sim_docs_query[unselected] - \
                     (1 - lambda_param) * max_sim_between
        
        # 选择MMR分数最高的文档
        next_idx = unselected[np.argmax(mmr_scores)]
        selected.append(next_idx)
        unselected.remove(next_idx)
    
    # 返回选定的文档
    return [doc_contents[idx] for idx in selected]

信息质量评估

过滤低质量或干扰性内容,提高上下文质量:

  1. 信息密度评估

    • 计算每个段落的信息密度(信息量/长度)
    • 优先选择信息密度高的段落
    • 技术:关键词密度分析、实体密度评估
  2. 相关段落抽取

    • 仅提取文档中与查询高度相关的段落
    • 避免将整个文档填入上下文
    • 方法:段落级相似度计算、句子级相关性评分
  3. 格式适应性处理

    • 处理特殊格式内容(表格、列表、代码)使其适合LLM处理
    • 转换复杂格式为文本表示
    • 保留关键格式元素以维持信息完整性
4.3 查询增强技术

查询改写与扩展

通过修改和增强原始查询,提高检索相关性:

  1. 查询改写(Query Rewriting)

    • 使用LLM改写用户查询,使其更精确、更完整
    • 修复语法错误、消除歧义、添加上下文信息
    • 转换成更接近知识库用语的表述
  2. 查询扩展(Query Expansion)

    • 添加同义词、相关术语扩展原始查询
    • 利用领域知识图谱丰富查询
    • 融合历史交互上下文中的相关信息
def enhance_query(original_query, conversation_history=None):
    """
    使用LLM增强原始查询
    
    参数:
    - original_query: 用户原始查询
    - conversation_history: 可选的对话历史
    
    返回:
    - 增强后的查询
    """
    # 构建提示
    prompt = """请对以下搜索查询进行改写和扩展,使其更加清晰、准确,包含更丰富的相关术语。
保持原始意图不变,但使查询更适合信息检索系统。

原始查询: "{query}"
"""
    
    # 如果有对话历史,添加到提示中
    if conversation_history:
        history_context = "\n\n对话历史上下文:\n" + "\n".join(conversation_history[-3:])
        prompt += history_context
    
    prompt = prompt.format(query=original_query)
    
    # 调用LLM进行查询增强
    enhanced_query = llm.generate(prompt).strip()
    
    return enhanced_query

多查询策略

生成多个不同视角的查询,以覆盖更广泛的相关信息:

  1. 分解式多查询

    • 将复杂问题分解为多个简单问题
    • 每个子问题生成一组检索结果
    • 合并不同子问题的检索结果
  2. 角度多样化查询

    • 从不同角度、不同层次生成多个查询变体
    • 例如:定义性查询、举例性查询、比较性查询等
    • 提高检索信息的多样性和全面性
def generate_multi_queries(user_query, num_queries=3):
    """
    为用户查询生成多个不同视角的查询变体
    
    参数:
    - user_query: 原始用户查询
    - num_queries: 生成的查询变体数量
    
    返回:
    - 查询变体列表
    """
    prompt = f"""请从不同角度和层次,为以下用户问题生成{num_queries}个不同的搜索查询。
这些查询应该覆盖相关信息的不同方面,帮助检索系统找到全面的回答。
每个查询都应该是一个独立、完整的问题或短语。

原始问题: "{user_query}"

请生成以下类型的查询:
1. 直接相关的基础查询
2. 探索定义或概念的查询
3. 寻找具体例子或应用的查询

以"查询1:"、"查询2:"等格式输出。
"""
    
    # 调用LLM生成多个查询
    response = llm.generate(prompt)
    
    # 解析生成的查询
    query_variants = []
    for line in response.strip().split("\n"):
        if line.startswith("查询") and ":" in line:
            query_text = line.split(":", 1)[1].strip()
            if query_text:
                query_variants.append(query_text)
    
    # 如果解析失败,至少返回原始查询
    if not query_variants:
        return [user_query]
    
    return query_variants

HyDE技术

HyDE(Hypothetical Document Embeddings)是一种特殊的查询增强技术:

  1. 基本原理

    • 先用LLM生成假设性的"理想回答文档"
    • 将这个假设文档而非原始查询进行嵌入
    • 用假设文档的嵌入向量进行检索
  2. 实施步骤

    • 将用户查询发送给LLM生成假设性答案
    • 对假设性答案进行嵌入
    • 使用此嵌入在向量库中检索真实文档
    • 使用检索到的真实文档构建上下文
  3. 优势

    • 桥接查询意图与实际文档表述的差距
    • 减轻词汇不匹配问题
    • 增强语义检索能力
def hyde_retrieval(query, embedding_model, vector_db, llm, top_k=5):
    """
    使用HyDE(Hypothetical Document Embeddings)技术进行检索
    
    参数:
    - query: 用户查询
    - embedding_model: 嵌入模型
    - vector_db: 向量数据库
    - llm: 大语言模型
    - top_k: 返回的文档数量
    
    返回:
    - 检索到的文档列表
    """
    # 1. 生成假设性文档
    hyde_prompt = f"""请为以下问题生成一个简洁但信息丰富的假设回答。
这个回答应该像是从一个专业知识库中摘录的片段,包含与问题直接相关的事实和概念。
不需要引用来源,只需提供清晰、准确的内容。内容不超过250字。

问题: {query}
假设性回答:"""
    
    hypothetical_document = llm.generate(hyde_prompt)
    
    # 2. 对假设性文档进行嵌入
    hyde_embedding = embedding_model.embed(hypothetical_document)
    
    # 3. 使用假设性文档的嵌入进行检索
    retrieved_docs = vector_db.search(
        query_vector=hyde_embedding,
        top_k=top_k
    )
    
    return retrieved_docs
4.4 多文档整合策略

当RAG系统需要从多个文档中获取信息回答复杂问题时,如何有效整合这些信息是关键挑战:

信息融合与排序

  1. 主题聚类排序

    • 将检索的段落按主题或概念聚类
    • 在上下文中保持相关内容的连贯性
    • 方法:对段落进行嵌入并聚类,按主题组织
  2. 信息完整性重组

    • 识别互补的信息片段并合理组织
    • 确保关键概念的所有方面都被覆盖
    • 技术:关系抽取、概念映射
  3. 时序与逻辑排序

    • 按时间顺序或逻辑关系排序信息
    • 适用于过程描述、历史事件、因果关系问题
    • 方法:元数据利用、文本中的时间线和逻辑标记提取

跨文档一致性处理

处理多文档中可能出现的冲突或不一致信息:

  1. 冲突检测

    • 识别不同来源中的矛盾信息
    • 基于实体关系比较、数值比较等方法
    • 标记不确定或存在争议的内容
  2. 来源可信度加权

    • 基于来源可靠性、时效性对信息加权
    • 优先选择更权威、更新的信息源
    • 技术:来源信誉评分系统
  3. 模糊证据整合

    • 当信息不完全一致时,保留多种可能性
    • 表明证据强度和确定性级别
    • 避免简单取舍,保留信息丰富度
def integrate_multi_documents(query, retrieved_chunks, max_token_limit=3000):
    """
    整合多文档信息为连贯上下文
    
    参数:
    - query: 用户查询
    - retrieved_chunks: 检索到的文档片段列表
    - max_token_limit: 上下文的最大token限制
    
    返回:
    - 整合后的上下文文本
    """
    # 1. 对文档片段进行分组和聚类(简化示例)
    # 实际实现可能使用更复杂的聚类算法
    topic_groups = {}
    for chunk in retrieved_chunks:
        # 简化:使用文档来源或类别作为分组依据
        topic = chunk.get('category', 'general')
        if topic not in topic_groups:
            topic_groups[topic] = []
        topic_groups[topic].append(chunk)
    
    # 2. 准备整合的上下文
    integrated_context = []
    token_count = 0
    
    # 3. 按相关性将各主题组的内容添加到上下文
    for topic, chunks in sorted(topic_groups.items(), 
                                key=lambda x: query_topic_relevance(query, x[0]), 
                                reverse=True):
        # 对组内文档按相关性排序
        sorted_chunks = sorted(chunks, 
                              key=lambda x: chunk_relevance(query, x), 
                              reverse=True)
        
        topic_header = f"== {topic.upper()} 相关信息 =="
        integrated_context.append(topic_header)
        token_count += len(topic_header.split())
        
        # 添加组内文档,注意token限制
        for chunk in sorted_chunks:
            chunk_text = f"{chunk['content']} [来源: {chunk.get('source', '未知')}]"
            chunk_tokens = len(chunk_text.split())
            
            if token_count + chunk_tokens > max_token_limit:
                break
                
            integrated_context.append(chunk_text)
            token_count += chunk_tokens
    
    return "\n\n".join(integrated_context)

# 辅助函数(简化实现)
def query_topic_relevance(query, topic):
    """评估查询与主题的相关性(简化)"""
    # 实际实现可能使用嵌入相似度或分类器
    return 1.0  # 示例返回值
    
def chunk_relevance(query, chunk):
    """评估查询与文档块的相关性(简化)"""
    # 实际实现基于向量相似度或其他相关性指标
    return chunk.get('relevance_score', 0.5)  # 示例返回值

5. 生成与后处理技术

在RAG系统中,检索完成并构建好上下文后,我们需要引导大语言模型生成高质量回答,并对生成的结果进行适当的后处理,确保回答的准确性和可用性。

5.1 提示工程技术

针对RAG系统,我们需要设计特殊的提示模板,以最大化检索内容的利用效果:

RAG专用提示模板设计

  1. 明确指令与角色定义

    • 为模型定义明确的角色(如"检索增强助手")
    • 提供清晰的行为指南和输出要求
    • 说明如何处理检索信息中的冲突或不确定性
  2. 检索内容引导

    • 明确指导模型如何利用检索到的信息
    • 设定优先级策略(如优先使用检索内容而非内部知识)
    • 要求引用信息来源,增强透明度
  3. 事实边界管理

    • 明确指导模型识别知识边界
    • 当检索内容不足时如何响应
    • 避免虚构内容的策略指导

RAG提示模板示例

def create_rag_prompt(query, context, output_format=None):
    """
    创建RAG系统的标准提示模板
    
    参数:
    - query: 用户查询
    - context: 检索到的上下文信息
    - output_format: 可选的输出格式要求
    
    返回:
    - 格式化的提示文本
    """
    system_instruction = """你是一个专业的检索增强型AI助手。你的任务是基于提供的参考信息回答用户问题。
请遵循以下规则:
1. 仅使用提供的参考信息回答问题。如果参考信息不足以完整回答问题,坦率地表明你只能回答部分问题或无法回答。
2. 不要使用你的内部知识编造未在参考信息中出现的内容。
3. 保持回答的准确性、相关性和有条理。
4. 在回答中适当引用参考信息来源(例如[来源1])。
5. 如果参考信息中存在矛盾,请指出这些矛盾并解释可能的原因。
6. 回答应当简洁、清晰,但要包含足够的细节。"""

    # 添加特定的输出格式要求
    if output_format:
        system_instruction += f"\n\n回答必须按照以下格式:\n{output_format}"
    
    # 构建完整提示
    full_prompt = f"""{system_instruction}

参考信息:
{context}

用户问题: {query}

请基于以上参考信息回答问题:"""

    return full_prompt

少样本提示设计

在复杂的RAG应用中,可以使用"少样本学习"(Few-shot Learning)提示技术增强生成质量:

def create_few_shot_rag_prompt(query, context, examples):
    """
    创建包含示例的少样本RAG提示
    
    参数:
    - query: 用户查询
    - context: 检索到的上下文信息
    - examples: 问答示例列表,每个元素包含'question'、'context'和'answer'
    
    返回:
    - 包含示例的提示文本
    """
    # 基础指令
    system_instruction = """你是一个专业的检索增强型AI助手。你的任务是基于提供的参考信息回答用户问题。
请严格按照以下示例的风格和格式回答问题。特别注意如何仅基于提供的参考信息生成答案,并适当引用信息来源。"""
    
    # 构建示例部分
    examples_text = ""
    for i, example in enumerate(examples):
        examples_text += f"""
示例 {i+1}:
参考信息:
{example['context']}

问题: {example['question']}

回答: {example['answer']}

---
"""
    
    # 构建当前问题部分
    current_query_part = f"""
现在请回答新问题:

参考信息:
{context}

问题: {query}

回答:"""
    
    # 组合完整提示
    full_prompt = system_instruction + examples_text + current_query_part
    
    return full_prompt
5.2 生成控制参数优化

大语言模型的生成过程可以通过各种参数控制,为不同类型的RAG应用选择合适的参数至关重要:

关键生成参数

  1. 温度(Temperature)

    • 控制生成的随机性与创造性
    • RAG系统通常使用较低温度(0.0-0.3)以增加确定性
    • 事实型查询: 0.0-0.1,解释性内容: 0.2-0.4
  2. top_p与top_k

    • 控制token选择范围,影响输出多样性
    • 事实回答通常设置较低top_p(0.1-0.3)
    • 综合性问题可适当增加(0.5-0.7)
  3. 最大生成长度

    • 根据任务类型和需求设置适当的输出长度
    • 考虑用户耐心和信息密度的平衡
    • 短回答:150-300 tokens,详细回答:500-1000 tokens

参数动态调整

为不同类型的查询动态调整生成参数:

def optimize_generation_parameters(query, context_length, query_type):
    """
    根据查询特征动态优化生成参数
    
    参数:
    - query: 用户查询文本
    - context_length: 上下文长度(tokens)
    - query_type: 查询类型分类("factual", "explanatory", "creative", "procedural")
    
    返回:
    - 优化的生成参数字典
    """
    # 基础参数设置
    params = {
        "temperature": 0.2,
        "top_p": 0.4,
        "top_k": 40,
        "max_tokens": 500,
        "presence_penalty": 0.0,
        "frequency_penalty": 0.0
    }
    
    # 根据查询类型调整
    if query_type == "factual":
        # 事实型查询(如"谁发明了电灯")需要高确定性
        params.update({
            "temperature": 0.0,
            "top_p": 0.1,
            "max_tokens": min(300, 2000 - context_length),  # 简短回答
            "presence_penalty": 0.0
        })
    
    elif query_type == "explanatory":
        # 解释型查询(如"为什么天空是蓝色的")需要详细说明
        params.update({
            "temperature": 0.3,
            "top_p": 0.4,
            "max_tokens": min(800, 3000 - context_length),  # 详细回答
            "presence_penalty": 0.1  # 鼓励覆盖更多方面
        })
    
    elif query_type == "creative":
        # 创意型查询需要更多多样性
        params.update({
            "temperature": 0.7,
            "top_p": 0.9,
            "frequency_penalty": 0.5  # 减少重复
        })
    
    elif query_type == "procedural":
        # 程序性查询(如"如何做面包")需要结构化步骤
        params.update({
            "temperature": 0.2,
            "max_tokens": min(1000, 3000 - context_length),
            "presence_penalty": 0.2  # 确保覆盖所有步骤
        })
    
    # 智能调整最大长度
    query_length = len(query.split())
    if query_length > 20:  # 长问题可能需要更详细的回答
        params["max_tokens"] = min(params["max_tokens"] * 1.5, 3000 - context_length)
    
    return params

批量生成与选择

对于关键应用,可以使用批量生成策略提高回答质量:

  1. 多样性采样

    • 使用不同参数生成多个候选回答
    • 通过评估指标选择最佳回答
    • 或将多个回答进行整合提炼
  2. 自我一致性检查

    • 生成多个回答并检查一致性
    • 保留在多数回答中出现的信息
    • 标记不确定或矛盾的部分
5.3 引用与溯源机制

RAG系统的一个重要优势是能够提供信息来源,增强可信度和透明度:

引用生成策略

  1. 内联引用

    • 在回答正文中直接添加引用标记
    • 格式如[1]、[来源A]等简洁标记
    • 便于阅读的同时提供溯源能力
  2. 脚注引用

    • 在回答末尾提供详细来源信息
    • 包含文档标题、作者、日期等元数据
    • 适合正式或学术场景
  3. 链接引用

    • 为Web应用提供可点击的源文档链接
    • 允许用户直接访问原始信息
    • 增强用户体验和信任度

引用实现示例

def process_citations(generated_text, source_documents):
    """
    处理生成文本中的引用并添加来源信息
    
    参数:
    - generated_text: LLM生成的回答文本
    - source_documents: 源文档列表,包含元数据
    
    返回:
    - 带格式化引用的最终文本
    """
    # 1. 解析文本中的引用标记
    citation_pattern = r'\[(?:来源|source)?[ ]*(\d+)\]'
    citations = re.findall(citation_pattern, generated_text)
    
    # 如果没有检测到引用标记,尝试添加基于内容匹配的引用
    if not citations:
        generated_text = add_automatic_citations(generated_text, source_documents)
        citations = re.findall(citation_pattern, generated_text)
    
    # 2. 准备引用脚注
    footnotes = []
    used_sources = set()
    
    for cite_id in citations:
        idx = int(cite_id) - 1
        if 0 <= idx < len(source_documents):
            source = source_documents[idx]
            if idx not in used_sources:
                footnote = format_source_reference(source)
                footnotes.append(f"[{cite_id}] {footnote}")
                used_sources.add(idx)
    
    # 3. 将脚注添加到文本末尾
    if footnotes:
        generated_text += "\n\n来源:\n" + "\n".join(footnotes)
    
    return generated_text

def format_source_reference(source):
    """格式化源文档为引用格式"""
    title = source.get('title', '未知标题')
    author = source.get('author', '未知作者')
    date = source.get('date', '未知日期')
    url = source.get('url', '')
    
    reference = f"{title}"
    if author != '未知作者':
        reference += f", {author}"
    if date != '未知日期':
        reference += f", {date}"
    if url:
        reference += f". {url}"
    
    return reference

交互式溯源功能

为交互式应用设计的高级溯源功能:

  1. 高亮显示

    • 突出显示回答中直接引用检索内容的部分
    • 提供视觉区分原创内容与引用内容
    • 增强用户对信息来源的感知
  2. 证据展示

    • 允许用户点击查看特定声明的证据
    • 展示相关的原始文档段落
    • 支持原文比对,增强透明度
  3. 置信度指示

    • 为不同部分的回答提供置信度评分
    • 基于检索结果的相关性和一致性
    • 帮助用户评估信息可靠性
5.4 事实一致性检查

确保RAG系统输出的事实准确性是关键挑战,需要多层次验证机制:

自动验证技术

  1. 答案验证模型
    • 使用专门训练的模型验证生成内容与检索内容的一致性
    • 检测添加、遗漏或扭曲的信息
    • 标记低置信度或可能错误的内容
def verify_factual_consistency(generated_answer, retrieval_context):
    """
    验证生成答案与检索上下文的事实一致性
    
    参数:
    - generated_answer: 生成的回答
    - retrieval_context: 检索的上下文内容
    
    返回:
    - 验证结果和修正建议
    """
    verification_prompt = f"""
你的任务是验证AI生成回答的事实准确性。对照提供的参考信息,评估回答中的每个事实性陈述。
标记以下几类问题:
1. 虚构事实:回答中包含参考信息中没有的内容
2. 矛盾:回答中与参考信息冲突的内容
3. 断章取义:回答中对参考信息的错误解读

参考信息:
{retrieval_context}

AI生成的回答:
{generated_answer}

请提供详细分析,标识每个问题,并给出1-10的整体一致性评分(10表示完全一致)。
"""
    
    # 调用验证模型
    verification_result = verification_model.generate(verification_prompt)
    
    # 解析验证结果(简化示例)
    # 实际实现可能使用更结构化的输出格式和解析逻辑
    consistency_score = extract_consistency_score(verification_result)
    issues = extract_issues(verification_result)
    
    return {
        "consistency_score": consistency_score,
        "issues": issues,
        "full_analysis": verification_result
    }
  1. 关键事实提取与验证

    • 从生成的回答中提取关键事实声明
    • 针对每个事实在检索内容中寻找支持证据
    • 对缺乏支持的事实进行标记或重写
  2. 自动重写与修正

    • 检测到不一致后自动重写问题部分
    • 移除未得到支持的断言
    • 增加对不确定内容的限定词

人机协作验证

  1. 可疑内容标记

    • 自动标记可能存在问题的内容部分
    • 提供给人类审核员重点检查
    • 减轻人工验证的工作量
  2. 用户反馈收集

    • 允许最终用户报告可能的错误
    • 收集这些反馈用于系统改进
    • 建立持续的质量优化循环
  3. 反例学习

    • 从验证失败的案例中学习模式
    • 改进提示模板和过滤机制
    • 建立错误模式库进行预防

6. RAG系统评估方法

构建高质量的RAG系统需要全面的评估框架,以确保检索质量和回答准确性。

6.1 评估维度与指标

RAG系统的评估应该覆盖多个关键维度:

检索评估指标

  1. 准确率(Precision)与召回率(Recall)

    • 准确率:检索结果中相关文档的比例
    • 召回率:成功检索到的相关文档占所有相关文档的比例
    • F1分数:准确率和召回率的调和平均值
  2. 排序质量指标

    • 平均倒数排名(MRR):相关文档倒数排名的平均值
    • 规范化衰减累积收益(NDCG):考虑排序位置的相关性度量
    • 前K位准确率(P@K):前K个结果中相关文档的比例
  3. 语义相关性

    • 检索结果与查询的语义相关度
    • 使用人工评分或自动相关性模型
    • BERTScore等基于嵌入的相关性指标

生成评估指标

  1. 事实准确性

    • 生成内容中正确事实陈述的比例
    • 幻觉率:生成内容中未在检索内容出现的断言比例
    • 支持率:能从检索内容中找到支持的陈述比例
  2. 内容质量

    • 回答完整性:覆盖问题所有相关方面
    • 简洁性:无不必要冗余或重复
    • 连贯性:逻辑流畅,结构合理
  3. 效用指标

    • 直接回答率:直接回答问题而非周围讨论
    • 相关性:回答与原始问题的相关程度
    • 可执行性:对操作类问题,提供可执行的步骤

端到端评估指标

  1. 用户满意度

    • 用户评分(1-5星等)
    • 满意率:认为回答满足需求的用户比例
    • 首次解决率:首次回答即解决问题的比例
  2. 任务成功指标

    • 查询解决率:成功回答查询的比例
    • 任务完成时间:完成特定任务所需交互次数
    • 额外查询需求:需要后续澄清的查询比例
  3. 系统性能指标

    • 延迟:从查询到回答的时间
    • 吞吐量:单位时间内可处理的查询数
    • 资源使用效率:计算和存储需求

评估方法综合框架

def evaluate_rag_system(test_queries, ground_truth, rag_system):
    """
    综合评估RAG系统性能
    
    参数:
    - test_queries: 测试查询列表
    - ground_truth: 参考答案和相关文档
    - rag_system: 待评估的RAG系统
    
    返回:
    - 多维度评估结果
    """
    results = {
        "retrieval_metrics": {},
        "generation_metrics": {},
        "end_to_end_metrics": {},
        "query_level_results": []
    }
    
    # 检索相关指标
    precision_sum = 0
    recall_sum = 0
    mrr_sum = 0
    
    # 生成相关指标
    factual_accuracy_sum = 0
    hallucination_sum = 0
    completeness_sum = 0
    
    # 端到端指标
    success_count = 0
    avg_latency = 0
    
    for i, query in enumerate(test_queries):
        # 获取参考信息
        reference_answer = ground_truth[i]["answer"]
        relevant_docs = ground_truth[i]["relevant_documents"]
        
        # 记录开始时间
        start_time = time.time()
        
        # 执行RAG流程
        retrieved_docs = rag_system.retrieve(query)
        generated_answer = rag_system.generate(query, retrieved_docs)
        
        # 计算延迟
        latency = time.time() - start_time
        avg_latency += latency
        
        # 评估检索性能
        retrieval_metrics = evaluate_retrieval(retrieved_docs, relevant_docs)
        precision_sum += retrieval_metrics["precision"]
        recall_sum += retrieval_metrics["recall"]
        mrr_sum += retrieval_metrics["mrr"]
        
        # 评估生成性能
        generation_metrics = evaluate_generation(
            generated_answer, reference_answer, retrieved_docs)
        factual_accuracy_sum += generation_metrics["factual_accuracy"]
        hallucination_sum += generation_metrics["hallucination_rate"]
        completeness_sum += generation_metrics["completeness"]
        
        # 端到端成功判断
        if is_successful_answer(generated_answer, reference_answer):
            success_count += 1
        
        # 记录查询级结果
        results["query_level_results"].append({
            "query": query,
            "retrieval_metrics": retrieval_metrics,
            "generation_metrics": generation_metrics,
            "latency": latency,
            "retrieved_docs": retrieved_docs,
            "generated_answer": generated_answer
        })
    
    # 计算平均指标
    n = len(test_queries)
    results["retrieval_metrics"] = {
        "precision": precision_sum / n,
        "recall": recall_sum / n,
        "mrr": mrr_sum / n,
        "f1": 2 * (precision_sum/n) * (recall_sum/n) / ((precision_sum/n) + (recall_sum/n))
    }
    
    results["generation_metrics"] = {
        "factual_accuracy": factual_accuracy_sum / n,
        "hallucination_rate": hallucination_sum / n,
        "completeness": completeness_sum / n
    }
    
    results["end_to_end_metrics"] = {
        "success_rate": success_count / n,
        "avg_latency": avg_latency / n
    }
    
    return results
6.2 评估数据集构建

高质量的评估数据集是准确评估RAG系统的基础:

测试集设计原则

  1. 多样性覆盖

    • 包含不同类型的查询(事实型、解释型、指导型等)
    • 覆盖不同难度级别的问题
    • 包括各种主题领域和知识深度
  2. 现实代表性

    • 反映真实用户可能提出的查询
    • 包含自然语言变体和不同表达方式
    • 包含模糊、不完整或复杂的查询
  3. 挑战性查询

    • 包含需要多跳推理的问题
    • 包含需要整合多个信息片段的问题
    • 包含需要识别矛盾信息的问题

测试集构建方法

  1. 手动精选

    • 由领域专家构建核心测试集
    • 确保关键场景和边缘情况的覆盖
    • 创建预期答案和检索结果
  2. 用户查询采样

    • 从真实用户查询中抽样
    • 移除个人识别信息
    • 按类型分类并平衡分布
  3. 自动合成

    • 使用LLM生成多样化问题
    • 基于知识库内容自动生成查询
    • 对生成的查询进行人工验证和筛选

参考答案创建

  1. 多专家合作

    • 多名专家独立创建参考答案
    • 通过讨论解决分歧
    • 确保答案的准确性和完整性
  2. 结构化标注

    • 标注必须包含的关键事实点
    • 标注每个事实点的重要性权重
    • 创建多级评分标准
  3. 相关文档标注

    • 标识应当被检索到的关键文档
    • 为每个文档标记相关性等级
    • 标注文档中支持答案的具体段落
6.3 自动与人工评估平衡

RAG系统评估需要结合自动评估和人工评估的优势:

自动评估方法

  1. 基于参考的自动评估

    • 使用BLEU、ROUGE等文本相似度指标
    • BERTScore等语义相似度指标
    • METEOR等考虑同义词的指标
  2. LLM辅助评估

    • 使用能力更强的LLM评估答案质量
    • 评估事实准确性、相关性、完整性等维度
    • 提供结构化评分和失败原因分析
def llm_based_evaluation(query, generated_answer, ground_truth, retrieval_context):
    """
    使用大语言模型进行答案质量评估
    
    参数:
    - query: 用户查询
    - generated_answer: RAG系统生成的答案
    - ground_truth: 标准参考答案
    - retrieval_context: 检索到的上下文内容
    
    返回:
    - 详细的评估结果
    """
    evaluation_prompt = f"""作为专业的AI评估专家,你的任务是评估一个检索增强型AI系统的回答质量。
请按照以下维度进行分析,使用1-5分进行评分(5分为最高):

1. 事实准确性: 回答中的事实陈述是否与参考信息和标准答案一致
2. 相关性: 回答是否直接相关并解决了用户问题
3. 完整性: 回答是否涵盖了所有相关方面和关键信息
4. 简洁性: 回答是否简洁明了,没有不必要的冗余
5. 可用性: 回答的格式和结构是否利于用户理解和使用

用户问题:
{query}

检索到的参考信息:
{retrieval_context}

系统生成的回答:
{generated_answer}

标准参考答案:
{ground_truth}

请提供详细分析,指出优点和问题,并给出每个维度的分数。最后计算总分(满分25分)。
"""

    # 调用评估模型
    evaluation_result = evaluation_llm.generate(evaluation_prompt)
    
    # 解析评估结果
    scores = extract_scores(evaluation_result)
    analysis = extract_analysis(evaluation_result)
    
    return {
        "scores": scores,
        "total_score": sum(scores.values()),
        "analysis": analysis,
        "full_evaluation": evaluation_result
    }
  1. 无参考评估
    • 事实一致性检测(检测与检索内容不一致的陈述)
    • 自我矛盾检测(检测回答内部的逻辑矛盾)
    • 语言质量评估(流畅度、连贯性等)

人工评估方法

  1. 主观评分

    • 人类评估人员对回答进行多维度评分
    • 主要关注准确性、有用性、完整性等
    • 创建标准化评分指南确保一致性
  2. 偏好比较

    • A/B测试比较不同系统或配置的回答
    • 双盲评估,评估者不知道哪个是哪个系统生成
    • 记录偏好选择和偏好强度
  3. 细粒度标注

    • 标注回答中的每个事实断言
    • 评估其准确性、相关性和来源
    • 标注错误类型和严重程度

评估工作流程

结合自动和人工评估的综合工作流程:

  1. 初步筛选阶段

    • 使用自动指标进行大规模系统评估
    • 筛选出表现最好的配置和参数
    • 标识需要深入人工评估的样本
  2. 深入评估阶段

    • 对关键样本进行人工专家评估
    • 特别关注自动评估难以判断的维度
    • 提供详细的质量分析和改进建议
  3. 持续改进循环

    • 收集评估结果并识别共同问题
    • 改进系统组件或配置
    • 重复评估验证改进效果
6.4 在线评估与监控

将评估融入到生产环境中,实现持续改进:

在线评估机制

  1. A/B测试

    • 将用户流量分配给不同系统配置
    • 比较用户参与度和任务成功率
    • 统计显著性分析确保结果可靠
  2. 用户反馈收集

    • 在回答后提供简单的点赞/点踩机制
    • 选择性的详细反馈收集
    • 自动分类反馈类型和严重程度
  3. 交互行为分析

    • 跟踪用户查询模式和变化
    • 分析跟进问题和重新查询模式
    • 识别用户满意和不满意的信号

系统监控指标

  1. 质量监控

    • 幻觉率趋势监控
    • 回答拒绝率(无法回答的比例)
    • 用户报告的错误率
  2. 性能监控

    • 检索延迟和生成延迟
    • 系统负载和资源使用
    • 查询队列长度和等待时间
  3. 使用模式监控

    • 热门查询主题和领域
    • 查询复杂度分布
    • 用户会话长度和深度

持续学习循环

  1. 失败案例收集

    • 自动识别和收集系统失败案例
    • 创建失败模式数据库
    • 优先处理高频和高影响失败
  2. 知识库改进

    • 基于查询模式优化知识库内容
    • 填补知识库中被识别的空白
    • 更新过时或不准确的信息
  3. 系统自适应

    • 基于用户互动自动调整参数
    • 学习用户偏好和行为模式
    • 持续优化提示模板和检索策略

💻 实践活动

实践1:基础RAG系统实现

在本实践活动中,我们将构建一个简单但功能完整的RAG系统,帮助你理解关键组件如何协同工作。

步骤1:环境准备

首先,我们需要安装必要的库和依赖:

# 安装必要的库
!pip install langchain sentence-transformers chromadb openai

然后创建一个基本的工作目录:

import os

# 创建工作目录
work_dir = "rag_workshop"
data_dir = os.path.join(work_dir, "data")
db_dir = os.path.join(work_dir, "db")

os.makedirs(data_dir, exist_ok=True)
os.makedirs(db_dir, exist_ok=True)

print(f"工作目录已创建: {work_dir}")
步骤2:准备示例文档

让我们准备一些示例文档用于构建知识库:

# 创建一些示例文档
documents = [
    {
        "title": "RAG技术概述",
        "content": """检索增强生成(RAG)是一种将信息检索与文本生成相结合的技术。它通过在生成过程中引入外部知识,增强大语言模型的回答能力。
RAG技术解决了大模型的知识时效性问题,因为知识库可以随时更新,无需重新训练模型。此外,它还降低了幻觉产生的可能性,因为生成过程基于检索到的事实信息。"""
    },
    {
        "title": "嵌入模型简介",
        "content": """嵌入模型是RAG系统的核心组件之一,它将文本转换为向量表示。优质的嵌入模型能够捕捉文本的语义信息,使相似内容在向量空间中距离较近。
常见的嵌入模型包括OpenAI的text-embedding-3、Cohere的embedding模型以及开源的BAAI/bge系列模型等。选择模型时需要考虑语义理解能力、性能与资源需求、语言支持等因素。"""
    },
    {
        "title": "向量数据库基础",
        "content": """向量数据库是专门设计用于存储、管理和检索高维向量数据的数据库系统。在RAG中,它用于存储文档嵌入向量并支持高效的相似性搜索。
主流向量数据库包括Pinecone、Weaviate、Milvus、Qdrant和Chroma等。这些系统通常使用HNSW或IVF等索引算法来加速向量搜索过程,避免在查询时与所有向量进行计算。"""
    },
    {
        "title": "提示工程在RAG中的应用",
        "content": """在RAG系统中,提示工程至关重要。有效的提示模板应该明确指导模型如何利用检索到的信息,并设定优先级策略。
典型的RAG提示模板包括系统指令、检索内容、用户查询三个部分。系统指令定义模型角色并提供行为指南;检索内容部分包含从知识库中获取的相关文档;用户查询部分则包含原始问题。"""
    }
]

# 将文档保存到文件中
import json
for i, doc in enumerate(documents):
    with open(os.path.join(data_dir, f"doc_{i}.json"), "w", encoding="utf-8") as f:
        json.dump(doc, f, ensure_ascii=False, indent=2)

print(f"已创建 {len(documents)} 个示例文档")
步骤3:文本处理与嵌入生成

实现文档处理和嵌入生成流程:

from langchain_community.document_loaders import TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_community.vectorstores import Chroma

# 文档加载和处理
def process_documents(data_dir):
    # 从保存的文件加载文档
    docs = []
    for filename in os.listdir(data_dir):
        if filename.endswith(".json"):
            with open(os.path.join(data_dir, filename), "r", encoding="utf-8") as f:
                doc = json.load(f)
                # 添加元数据
                docs.append({
                    "content": doc["content"],
                    "metadata": {"title": doc["title"], "source": filename}
                })
    
    # 文本分块
    text_splitter = RecursiveCharacterTextSplitter(
        chunk_size=300,
        chunk_overlap=50,
        separators=["\n\n", "\n", "。", ",", " ", ""]
    )
    
    # 处理每个文档并分块
    all_chunks = []
    for doc in docs:
        chunks = text_splitter.split_text(doc["content"])
        # 为每个块添加元数据
        doc_chunks = [{"content": chunk, "metadata": doc["metadata"]} for chunk in chunks]
        all_chunks.extend(doc_chunks)
    
    return all_chunks

# 嵌入生成与向量存储
def create_vector_store(chunks, db_dir):
    # 初始化嵌入模型
    embeddings = HuggingFaceEmbeddings(model_name="shibing624/text2vec-base-chinese")
    
    # 提取内容和元数据
    texts = [chunk["content"] for chunk in chunks]
    metadatas = [chunk["metadata"] for chunk in chunks]
    
    # 创建向量存储
    vectorstore = Chroma.from_texts(
        texts=texts,
        embedding=embeddings,
        metadatas=metadatas,
        persist_directory=db_dir
    )
    
    return vectorstore

# 执行处理流程
chunks = process_documents(data_dir)
vectorstore = create_vector_store(chunks, db_dir)

print(f"处理完成,共生成 {len(chunks)} 个文本块")
步骤4:构建RAG查询流程

实现完整的RAG查询流程:

from langchain_openai import ChatOpenAI

# 请替换为你的API密钥
import os
os.environ["OPENAI_API_KEY"] = "你的OpenAI API密钥"

# 初始化大语言模型
llm = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0)

# 创建RAG提示模板
from langchain.prompts import ChatPromptTemplate

template = """你是一个专业的AI助手。请基于以下提供的信息回答用户问题。
如果提供的信息不足以回答问题,请说明你不知道,不要编造内容。
请引用信息来源。

信息:
{context}

用户问题: {query}
"""

prompt = ChatPromptTemplate.from_template(template)

# RAG查询函数
def rag_query(query, vectorstore, top_k=3):
    # 检索相关文档
    results = vectorstore.similarity_search(query, k=top_k)
    
    # 构建上下文
    contexts = []
    for i, doc in enumerate(results):
        source = doc.metadata.get("title", "未知文档")
        contexts.append(f"[文档{i+1}: {source}]\n{doc.page_content}")
    
    context_text = "\n\n".join(contexts)
    
    # 生成提示
    formatted_prompt = prompt.format(context=context_text, query=query)
    
    # 生成回答
    response = llm.invoke(formatted_prompt)
    
    return {
        "query": query,
        "response": response.content,
        "source_documents": results
    }

# 测试RAG系统
test_queries = [
    "什么是RAG技术?它有什么优势?",
    "嵌入模型在RAG系统中的作用是什么?",
    "向量数据库有哪些常见选择?"
]

for query in test_queries:
    result = rag_query(query, vectorstore)
    print(f"\n问题: {query}")
    print(f"\n回答: {result['response']}")
    print("\n--------------------------------------------------")
步骤5:评估与优化

设计简单的评估机制:

def evaluate_rag_response(response, ground_truth):
    """简单评估RAG系统回答质量"""
    from langchain_openai import ChatOpenAI
    
    eval_llm = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0)
    
    eval_prompt = f"""请评估以下AI助手回答的质量,根据以下标准打分(1-5分):
1. 事实准确性: 回答中的事实是否正确
2. 相关性: 回答是否与问题相关
3. 完整性: 回答是否涵盖了问题的所有方面

问题: {response['query']}
AI回答: {response['response']}
参考标准答案: {ground_truth}

请给出每项的评分和简要分析。
"""
    
    evaluation = eval_llm.invoke(eval_prompt)
    return evaluation.content

# 示例用法(需要标准答案)
# ground_truth = "RAG(检索增强生成)是..."
# eval_result = evaluate_rag_response(result, ground_truth)
# print(f"\n评估结果:\n{eval_result}")

实践2:RAG优化实验

在这个实践中,我们将探索不同的RAG优化技术,并比较它们的效果:

# 1. 查询重写技术
from langchain_openai import ChatOpenAI

def rewrite_query(original_query):
    """使用LLM重写原始查询以提高检索效果"""
    rewrite_llm = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0.2)
    
    rewrite_prompt = f"""请对以下搜索查询进行改写,使其更加清晰、具体,并包含更多可能的关键词,以便更好地进行文档检索。
保持原始查询的核心意图不变,但使其更适合信息检索系统。

原始查询: {original_query}

改写后的查询:"""
    
    response = rewrite_llm.invoke(rewrite_prompt)
    return response.content.strip()

# 2. 实现多查询RAG
def multi_query_rag(original_query, vectorstore, llm, top_k=2):
    """生成多个查询变体并合并检索结果"""
    # 生成查询变体
    variant_prompt = f"""为以下用户问题生成3个不同的搜索查询变体,以便从文档库中检索信息。
这些变体应该从不同角度表达相同的信息需求,使用不同的关键词或表述方式。

原始问题: {original_query}

请直接输出三个查询变体,每行一个,不要有编号或其他说明。"""
    
    variants_response = llm.invoke(variant_prompt)
    query_variants = variants_response.content.strip().split('\n')
    query_variants = [q.strip() for q in query_variants if q.strip()]
    query_variants.append(original_query)  # 添加原始查询
    
    # 从每个变体中检索文档
    all_docs = []
    for query in query_variants:
        results = vectorstore.similarity_search(query, k=top_k)
        all_docs.extend(results)
    
    # 去重
    unique_docs = []
    seen_contents = set()
    for doc in all_docs:
        if doc.page_content not in seen_contents:
            unique_docs.append(doc)
            seen_contents.add(doc.page_content)
    
    # 排序(简化版,实际可能需要更复杂的排序逻辑)
    sorted_docs = unique_docs[:top_k*2]  # 限制最终文档数量
    
    # 构建上下文和生成回答的过程与之前相同
    contexts = []
    for i, doc in enumerate(sorted_docs):
        source = doc.metadata.get("title", "未知文档")
        contexts.append(f"[文档{i+1}: {source}]\n{doc.page_content}")
    
    context_text = "\n\n".join(contexts)
    formatted_prompt = prompt.format(context=context_text, query=original_query)
    response = llm.invoke(formatted_prompt)
    
    return {
        "query": original_query,
        "response": response.content,
        "source_documents": sorted_docs
    }

# 比较不同RAG策略的效果
def compare_rag_strategies(query):
    print(f"原始查询: {query}\n")
    
    # 标准RAG
    standard_result = rag_query(query, vectorstore)
    print(f"标准RAG回答:\n{standard_result['response']}\n")
    
    # 查询重写RAG
    rewritten_query = rewrite_query(query)
    print(f"重写后查询: {rewritten_query}\n")
    rewritten_result = rag_query(rewritten_query, vectorstore)
    print(f"查询重写RAG回答:\n{rewritten_result['response']}\n")
    
    # 多查询RAG
    multi_result = multi_query_rag(query, vectorstore, llm)
    print(f"多查询RAG回答:\n{multi_result['response']}\n")
    
    print("--------------------------------------------------")

# 测试不同的查询
test_query = "RAG和传统问答系统有什么区别?"
compare_rag_strategies(test_query)

🧠 自测问题

  1. 概念理解:解释RAG系统的核心工作流程,并说明每个环节的主要功能。

  2. 技术选择:在设计RAG系统时,如何选择合适的嵌入模型和向量数据库?列出至少三个考量因素。

  3. 优化策略:描述至少三种提高RAG系统检索准确性的方法,并解释它们的工作原理。

  4. 应用场景:分析RAG技术在哪些场景中比纯LLM解决方案更有优势,并解释原因。

  5. 评估方法:如何全面评估RAG系统的性能?设计一个评估框架,包括关键指标和测试方法。

📚 拓展资源

学习资料

  1. 《构建基于大语言模型的RAG应用》
  2. 《Retrieval Augmented Generation: 原理与实践》
  3. 《向量数据库选择指南》
  4. 《高级RAG: 超越基础检索》

开源工具

  1. LangChain - 构建RAG应用的流行框架
  2. LlamaIndex - 数据框架,简化RAG开发
  3. Chroma - 轻量级开源向量数据库
  4. FAISS - 高效向量搜索库

视频教程

  1. RAG系统从零到一
  2. 向量搜索原理解析
  3. RAG应用最佳实践

📝 课后作业

  1. RAG系统设计与实现:基于本课程所学知识,设计并实现一个针对特定领域(如技术文档、法律文本、教育资料等)的RAG系统。记录实现过程中遇到的挑战和解决方案。

  2. 性能对比实验:比较至少三种不同的检索策略(如基础向量检索、混合检索、多查询检索等)对RAG系统性能的影响。使用标准测试集评估并分析结果。

  3. RAG系统优化:针对自己实现的RAG系统,应用至少两种优化技术(如上下文处理优化、提示工程改进等),并量化评估优化效果。

  4. RAG与纯LLM对比:选择一个专业领域的问答任务,比较RAG系统与纯LLM(不使用外部知识)的回答质量差异。分析在哪些类型的问题上差异最显著。

  5. RAG评估框架开发:设计并实现一个自动化评估RAG系统的框架,包括检索质量和生成质量的多维度评估。测试该框架在实际RAG系统上的应用效果。


明日预告:明天我们将学习如何构建完整的RAG应用,实践今天所学的理论知识,开发能够处理私有数据的智能问答系统。


点击链接加入群聊【Aries - AIGC自学交流群】:https://qm.qq.com/q/q88ZpofKLY

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Aries.H

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值