第三阶段_大模型应用开发-Day 4: RAG检索增强生成技术

Day 4: RAG检索增强生成技术

学习目标

  • 理解RAG(检索增强生成)的基本原理和重要性
  • 掌握知识库构建和向量检索的方法
  • 学习RAG系统的设计和实现步骤
  • 了解提示工程在RAG中的应用
  • 掌握RAG系统的评估和优化方法

1. RAG基础概念

1.1 什么是RAG

RAG(Retrieval-Augmented Generation,检索增强生成)是一种结合检索系统和生成模型的技术,通过从外部知识库检索相关信息来增强大语言模型的回答质量。

RAG的核心思想

  • 不依赖模型参数中存储的知识
  • 从外部知识库检索相关信息
  • 将检索到的信息与用户查询结合
  • 生成基于事实、最新且可溯源的回答

RAG的优势

  • 知识更新:无需重新训练模型即可更新知识
  • 减少幻觉:提供事实依据,减少模型生成虚假信息
  • 可溯源性:回答可以追溯到具体信息来源
  • 领域适应:通过更换知识库快速适应不同领域
  • 成本效益:比完全微调模型更经济高效

1.2 RAG的工作流程

RAG系统通常包含以下核心步骤:

  1. 索引构建

    • 收集和处理文档
    • 将文档分割成适当大小的块
    • 使用嵌入模型生成文本块的向量表示
    • 将向量存储在向量数据库中
  2. 查询处理

    • 接收用户查询
    • 使用相同的嵌入模型生成查询的向量表示
    • 在向量数据库中检索与查询最相似的文本块
    • 根据相似度排序检索结果
  3. 上下文增强

    • 将检索到的文本块与原始查询结合
    • 构建提示模板,指导模型如何使用检索信息
  4. 生成回答

    • 将增强的提示发送给大语言模型
    • 模型生成基于检索内容的回答
    • 可能包含引用或来源信息

1.3 RAG与传统方法对比

RAG vs 微调

特性 RAG 微调
知识更新 简单(更新知识库) 复杂(重新训练)
内存需求 低(模型参数不变) 高(存储完整模型)
计算需求 中等(检索+推理) 高(训练过程)
可解释性 高(可提供来源) 低(黑盒知识)
领域适应 快速(更换知识库) 慢速(领域微调)
实时性 高(可包含最新信息) 低(受训练数据限制)

RAG vs 提示工程

特性 RAG 提示工程
知识范围 广泛(外部知识库) 有限(上下文窗口)
提示长度 可动态扩展 受上下文窗口限制
信息准确性 高(基于检索) 中(依赖模型记忆)
实现复杂度 高(需要检索系统) 低(仅需提示设计)
运行成本 高(检索+生成) 低(仅生成)

1.4 RAG的应用场景

适合RAG的场景

  • 问答系统:基于企业文档、产品手册等回答用户问题
  • 客户支持:根据支持文档提供准确的技术支持
  • 内容创作:基于参考资料生成文章、报告或摘要
  • 研究助手:检索和综合学术论文或研究资料
  • 个性化推荐:基于用户历史和偏好提供建议
  • 事实核查:验证信息的准确性并提供来源

不太适合RAG的场景

  • 创意写作(如小说、诗歌)
  • 纯主观意见表达
  • 简单的指令执行任务
  • 实时交互式对话(延迟要求极高)

2. 知识库构建

2.1 数据收集与处理

构建高质量知识库的第一步是收集和处理相关数据:

数据来源

  • 企业内部文档(手册、报告、政策)
  • 网站内容(FAQ、博客、产品描述)
  • 公开数据集(维基百科、学术论文)
  • API和数据库(结构化数据)
  • 用户生成内容(问答、评论)

数据处理步骤

python 复制代码
import os
import re
from typing import List, Dict, Any

def load_documents(directory: str) -> List[Dict[str, Any]]:
    """从目录加载文档"""
    documents = []
    
    for root, _, files in os.walk(directory):
        for file in files:
            if file.endswith(('.txt', '.md', '.pdf', '.docx')):
                file_path = os.path.join(root, file)
                
                # 根据文件类型选择适当的加载方法
                if file.endswith('.txt') or file.endswith('.md'):
                    with open(file_path, 'r', encoding='utf-8') as f:
                        content = f.read()
                elif file.endswith('.pdf'):
                    content = extract_text_from_pdf(file_path)
                elif file.endswith('.docx'):
                    content = extract_text_from_docx(file_path)
                
                # 创建文档对象
                doc = {
                    'content': content,
                    'metadata': {
                        'source': file_path,
                        'title': os.path.splitext(file)[0],
                        'file_type': file.split('.')[-1],
                        'created_at': os.path.getctime(file_path),
                        'modified_at': os.path.getmtime(file_path)
                    }
                }
                documents.append(doc)
    
    return documents

def clean_text(text: str) -> str:
    """清理文本内容"""
    # 移除多余的空白字符
    text = re.sub(r'\s+', ' ', text).strip()
    
    # 移除特殊字符
    text = re.sub(r'[^\w\s.,?!:;()\[\]{}\-\'\"]+', '', text)
    
    # 规范化引号
    text = text.replace(''', "'").replace(''', "'")
    text = text.replace('"', '"').replace('"', '"')
    
    return text

def preprocess_documents(documents: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
    """预处理文档集合"""
    processed_docs = []
    
    for doc in documents:
        # 清理文本
        cleaned_content = clean_text(doc['content'])
        
        # 创建处理后的文档
        processed_doc = {
            'content': cleaned_content,
            'metadata': doc['metadata']
        }
        
        processed_docs.append(processed_doc)
    
    return processed_docs

2.2 文本分块

将长文档分割成适当大小的文本块是构建高效RAG系统的关键步骤:

分块策略

  • 固定大小分块:按字符或标记数量分割
  • 语义分块:按段落、句子或语义单元分割
  • 递归分块:根据文档结构递归分割
  • 滑动窗口分块:使用重叠窗口确保上下文连贯性

分块实现

python 复制代码
from typing import List, Dict, Any

def split_by_character_count(text: str, chunk_size: int = 1000, overlap: int = 200) -> List[str]:
    """按字符数分块,带重叠"""
    chunks = []
    start = 0
    text_length = len(text)
    
    while start < text_length:
        # 确定当前块的结束位置
        end = min(start + chunk_size, text_length)
        
        # 如果不是最后一块且没有到达文本末尾,尝试在句子边界分割
        if end < text_length:
            # 查找最后一个句子结束符
            last_period = text.rfind('.', start, end)
            if last_period > start + chunk_size // 2:  # 确保块不会太小
                end = last_period + 1
        
        # 提取当前块
        chunk = text[start:end]
        chunks.append(chunk)
        
        # 更新下一块的起始位置,考虑重叠
        start = end - overlap
    
    return chunks

def split_by_markdown_structure(text: str) -> List[str]:
    """按Markdown结构分块"""
    # 按标题分割
    import re
    
    # 匹配Markdown标题
    header_pattern = r'^#{1,6}\s+.+$'
    
    # 分割文本
    lines = text.split('\n')
    chunks = []
    current_chunk = []
    
    for line in lines:
        if re.match(header_pattern, line) and current_chunk:
            # 遇到新标题,保存当前块
            chunks.append('\n'.join(current_chunk))
            current_chunk = [line]
        else:
            current_chunk.append(line)
    
    # 添加最后一个块
    if current_chunk:
        chunks.append('\n'.join(current_chunk))
    
    return chunks

def create_document_chunks(documents: List[Dict[str, Any]], 
                          chunk_size: int = 1000, 
                          overlap: int = 200) -> List[Dict[str, Any]]:
    """将文档集合分块"""
    chunked_documents = []
    
    for doc in documents:
        content = doc['content']
        metadata = doc['metadata']
        
        # 根据文件类型选择分块策略
        if metadata['file_type'] == 'md':
            chunks = split_by_markdown_structure(content)
        else:
            chunks = split_by_character_count(content, chunk_size, overlap)
        
        # 为每个块创建新的文档对象
        for i, chunk in enumerate(chunks):
            chunk_doc = {
                'content': chunk,
                'metadata': {
                    **metadata,
                    'chunk_id': i,
                    'chunk_total': len(chunks)
                }
            }
            chunked_documents.append(chunk_doc)
    
    return chunked_documents

2.3 向量嵌入

向量嵌入是将文本转换为数值向量的过程,使计算机能够理解和比较文本的语义相似性:

嵌入模型选择

  • 通用模型:Sentence-BERT、OpenAI Embeddings、BERT等
  • 领域特定模型:针对特定领域微调的嵌入模型
  • 多语言模型:支持多语言的嵌入模型
  • 轻量级模型:适合资源受限环境的小型模型

生成嵌入

python 复制代码
from sentence_transformers import SentenceTransformer
import numpy as np
from typing import List, Dict, Any

class EmbeddingGenerator:
    def __init__(self, model_name: str = 'all-MiniLM-L6-v2'):
        """初始化嵌入生成器"""
        self.model = SentenceTransformer(model_name)
    
    def generate_embeddings(self, texts: List[str]) -> np.ndarray:
        """为文本列表生成嵌入"""
        embeddings = self.model.encode(texts, show_progress_bar=True)
        return embeddings
    
    def embed_documents(self, documents: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """为文档集合生成嵌入"""
        # 提取文本内容
        texts = [doc['content'] for doc in documents]
        
        # 生成嵌入
        embeddings = self.generate_embeddings(texts)
        
        # 将嵌入添加到文档中
        embedded_documents = []
        for i, doc in enumerate(documents):
            embedded_doc = {
                **doc,
                'embedding': embeddings[i]
            }
            embedded_documents.append(embedded_doc)
        
        return embedded_documents

# 使用OpenAI的嵌入API
import openai

def generate_openai_embeddings(texts: List[str], model: str = "text-embedding-ada-002") -> List[List[float]]:
    """使用OpenAI API生成嵌入"""
    embeddings = []
    
    # 由于API限制,可能需要分批处理
    batch_size = 100
    for i in range(0, len(texts), batch_size):
        batch = texts[i:i+batch_size]
        
        response = openai.Embedding.create(
            input=batch,
            model=model
        )
        
        batch_embeddings = [item["embedding"] for item in response["data"]]
        embeddings.extend(batch_embeddings)
    
    return embeddings

2.4 向量存储

向量存储是专门设计用于高效存储和检索向量数据的数据库系统:

常见向量数据库

  • Faiss:Facebook AI开发的高效相似性搜索库
  • Pinecone:专为向量搜索设计的托管服务
  • Milvus:开源向量数据库,支持大规模向量检索
  • Weaviate:语义搜索引擎,支持向量和结构化数据
  • Chroma:专为RAG设计的嵌入式向量数据库
  • Qdrant:高性能向量搜索引擎

使用Faiss存储向量

python 复制代码
import faiss
import numpy as np
import pickle
from typing import List, Dict, Any, Tuple

class FaissVectorStore:
    def __init__(self, dimension: int):
        """初始化Faiss向量存储"""
        self.dimension = dimension
        self.index = faiss.IndexFlatL2(dimension)  # L2距离索引
        self.documents = []
    
    def add_documents(self, documents: List[Dict[str, Any]]) -> None:
        """添加文档到向量存储"""
        # 提取嵌入
        embeddings = np.array([doc['embedding'] for doc in documents], dtype=np.float32)
        
        # 添加到索引
        self.index.add(embeddings)
        
        # 存储文档(不包括嵌入)
        for doc in documents:
            doc_without_embedding = {k: v for k, v in doc.items() if k != 'embedding'}
            self.documents.append(doc_without_embedding)
    
    def search(self, query_embedding: np.ndarray, top_k: int = 5) -> List[Dict[str, Any]]:
        """搜索最相似的文档"""
        # 确保查询嵌入是二维数组
        if query_embedding.ndim == 1:
            query_embedding = np.expand_dims(query_embedding, axis=0)
        
        # 执行搜索
        distances, indices = self.index.search(query_embedding, top_k)
        
        # 获取结果
        results = []
        for i, idx in enumerate(indices[0]):
            if idx < len(self.documents):  # 确保索引有效
                result = {
                    **self.documents[idx],
                    'score': float(distances[0][i])
                }
                results.append(result)
        
        return results
    
    def save(self, index_path: str, documents_path: str) -> None:
        """保存索引和文档"""
        # 保存Faiss索引
        faiss.write_index(self.index, index_path)
        
        # 保存文档
        with open(documents_path, 'wb') as f:
            pickle.dump(self.documents, f)
    
    @classmethod
    def load(cls, index_path: str, documents_path: str) -> 'FaissVectorStore':
        """加载索引和文档"""
        # 加载Faiss索引
        index = faiss.read_index(index_path)
        
        # 加载文档
        with open(documents_path, 'rb') as f:
            documents = pickle.load(f)
        
        # 创建实例
        vector_store = cls(index.d)
        vector_store.index = index
        vector_store.documents = documents
        
        return vector_store

使用Chroma存储向量

python 复制代码
import chromadb
from typing import List, Dict, Any

class ChromaVectorStore:
    def __init__(self, collection_name: str, persist_directory: str = None):
        """初始化Chroma向量存储"""
        # 创建客户端
        if persist_directory:
            self.client = chromadb.PersistentClient(path=persist_directory)
        else:
            self.client = chromadb.Client()
        
        # 获取或创建集合
        self.collection = self.client.get_or_create_collection(name=collection_name)
    
    def add_documents(self, documents: List[Dict[str, Any]]) -> None:
        """添加文档到向量存储"""
        # 准备数据
        ids = [str(i) for i in range(len(documents))]
        texts = [doc['content'] for doc in documents]
        embeddings = [doc['embedding'].tolist() for doc in documents]
        metadatas = [doc['metadata'] for doc in documents]
        
        # 添加到集合
        self.collection.add(
            ids=ids,
            documents=texts,
            embeddings=embeddings,
            metadatas=metadatas
        )
    
    def search(self, query_embedding: List[float], top_k: int = 5) -> List[Dict[str, Any]]:
        """搜索最相似的文档"""
        # 执行搜索
        results = self.collection.query(
            query_embeddings=[query_embedding],
            n_results=top_k
        )
        
        # 处理结果
        documents = []
        for i in range(len(results['ids'][0])):
            doc = {
                'content': results['documents'][0][i],
                'metadata': results['metadatas'][0][i],
                'score': results['distances'][0][i] if 'distances' in results else None
            }
            documents.append(doc)
        
        return documents

3. RAG系统实现

3.1 基本RAG架构

RAG系统的基本架构包括以下组件:

核心组件

  • 文档处理器:处理和分块文档
  • 嵌入生成器:生成文本的向量表示
  • 向量存储:存储和检索向量
  • 检索器:根据查询检索相关文档
  • 提示构建器:构建包含检索内容的提示
  • 生成模型:生成最终回答
  • 后处理器:格式化和增强回答

简单RAG实现

python 复制代码
from typing import List, Dict, Any
import numpy as np
from transformers import AutoTokenizer, AutoModelForCausalLM

class SimpleRAG:
    def __init__(self, 
                vector_store,
                embedding_generator,
                model_name: str = "gpt2"):
        """初始化简单RAG系统"""
        self.vector_store = vector_store
        self.embedding_generator = embedding_generator
        self.tokenizer = AutoTokenizer.from_pretrained(model_name)
        self.model = AutoModelForCausalLM.from_pretrained(model_name)
    
    def retrieve(self, query: str, top_k: int = 3) -> List[Dict[str, Any]]:
        """检索相关文档"""
        # 生成查询嵌入
        query_embedding = self.embedding_generator.generate_embeddings([query])[0]
        
        # 检索相关文档
        documents = self.vector_store.search(query_embedding, top_k=top_k)
        
        return documents
    
    def generate_prompt(self, query: str, documents: List[Dict[str, Any]]) -> str:
        """构建提示"""
        # 构建上下文
        context = "\n\n".join([doc['content'] for doc in documents])
        
        # 构建提示
        prompt = f"""请基于以下信息回答问题。如果无法从提供的信息中找到答案,请说明你不知道。

信息:
{context}

问题: {query}

回答:"""
        
        return prompt
    
    def answer(self, query: str, top_k: int = 3) -> str:
        """回答问题"""
        # 检索相关文档
        documents = self.retrieve(query, top_k=top_k)
        
        # 构建提示
        prompt = self.generate_prompt(query, documents)
        
        # 生成回答
        inputs = self.tokenizer(prompt, return_tensors="pt")
        outputs = self.model.generate(
            inputs["input_ids"],
            max_length=512,
            num_return_sequences=1,
            do_sample=True,
            temperature=0.7
        )
        
        # 解码回答
        answer = self.tokenizer.decode(outputs[0], skip_special_tokens=True)
        
        # 提取生成的回答部分
        answer = answer[len(prompt):]
        
        return answer

3.2 使用LangChain实现RAG

LangChain是一个用于构建LLM应用的框架,提供了丰富的组件和工具,特别适合构建RAG系统:

python 复制代码
from langchain.embeddings import HuggingFaceEmbeddings
from langchain.vectorstores import FAISS
from langchain.document_loaders import TextLoader, DirectoryLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.chains import RetrievalQA
from langchain.llms import HuggingFacePipeline
from langchain.prompts import PromptTemplate
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline

# 1. 加载文档
loader = DirectoryLoader('./documents', glob="**/*.txt", loader_cls=TextLoader)
documents = loader.load()

# 2. 分割文档
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=200
)
chunks = text_splitter.split_documents(documents)

# 3. 创建嵌入
embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")

# 4. 创建向量存储
vectorstore = FAISS.from_documents(chunks, embeddings)

# 5. 设置检索器
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})

# 6. 设置语言模型
tokenizer = AutoTokenizer.from_pretrained("gpt2")
model = AutoModelForCausalLM.from_pretrained("gpt2")
pipe = pipeline(
    "text-generation",
    model=model,
    tokenizer=tokenizer,
    max_length=512
)
llm = HuggingFacePipeline(pipeline=pipe)

# 7. 设置提示模板
template = """请基于以下信息回答问题。如果无法从提供的信息中找到答案,请说明你不知道。

信息:
{context}

问题: {question}

回答:"""

prompt = PromptTemplate(
    template=template,
    input_variables=["context", "question"]
)

# 8. 创建QA链
qa_chain = RetrievalQA.from_chain_type(
    llm=llm,
    chain_type="stuff",
    retriever=retriever,
    chain_type_kwargs={"prompt": prompt}
)

# 9. 回答问题
query = "什么是向量数据库?"
result = qa_chain({"query": query})
print(result["result"])

3.3 使用LlamaIndex实现RAG

LlamaIndex是另一个专门为RAG设计的框架,提供了更多针对索引和检索的优化:

python 复制代码
from llama_index import SimpleDirectoryReader, VectorStoreIndex, ServiceContext
from llama_index.llms import HuggingFaceLLM
from llama_index.embeddings import HuggingFaceEmbedding
import torch

# 1. 加载文档
documents = SimpleDirectoryReader('./documents').load_data()

# 2. 设置嵌入模型
embed_model = HuggingFaceEmbedding(model_name="all-MiniLM-L6-v2")

# 3. 设置语言模型
llm = HuggingFaceLLM(
    model_name="gpt2",
    tokenizer_name="gpt2",
    context_window=2048,
    max_new_tokens=256,
    generate_kwargs={"temperature": 0.7, "do_sample": True}
)

# 4. 创建服务上下文
service_context = ServiceContext.from_defaults(
    llm=llm,
    embed_model=embed_model
)

# 5. 创建索引
index = VectorStoreIndex.from_documents(
    documents,
    service_context=service_context
)

# 6. 创建查询引擎
query_engine = index.as_query_engine()

# 7. 回答问题
query = "什么是向量数据库?"
response = query_engine.query(query)
print(response)

3.4 高级RAG技术

基本RAG系统可以通过多种技术进行增强:

1. 混合检索

python 复制代码
from langchain.retrievers import BM25Retriever
from langchain.retrievers.ensemble import EnsembleRetriever

# 创建BM25检索器(基于关键词)
bm25_retriever = BM25Retriever.from_documents(chunks)
bm25_retriever.k = 3

# 创建向量检索器(基于语义)
vector_retriever = vectorstore.as_retriever(search_kwargs={"k": 3})

# 创建集成检索器
ensemble_retriever = EnsembleRetriever(
    retrievers=[bm25_retriever, vector_retriever],
    weights=[0.5, 0.5]
)

# 使用集成检索器
docs = ensemble_retriever.get_relevant_documents(query)

2. 重排序

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

# 创建基础检索器
base_retriever = vectorstore.as_retriever(search_kwargs={"k": 5})

# 创建LLM压缩器
compressor = LLMChainExtractor.from_llm(llm)

# 创建上下文压缩检索器
compression_retriever = ContextualCompressionRetriever(
    base_compressor=compressor,
    base_retriever=base_retriever
)

# 使用压缩检索器
compressed_docs = compression_retriever.get_relevant_documents(query)

3. 查询转换

python 复制代码
from langchain.retrievers import SelfQueryRetriever
from langchain.chains.query_constructor.base import AttributeInfo

# 定义元数据结构
metadata_field_info = [
    AttributeInfo(
        name="source",
        description="文档的来源文件",
        type="string",
    ),
    AttributeInfo(
        name="title",
        description="文档的标题",
        type="string",
    ),
]

# 创建自查询检索器
self_query_retriever = SelfQueryRetriever.from_llm(
    llm,
    vectorstore,
    "文档集合包含各种技术文档",
    metadata_field_info,
    verbose=True
)

# 使用自查询检索器
docs = self_query_retriever.get_relevant_documents(
    "查找关于Python的文档,但不包括教程"
)

4. 多步检索

python 复制代码
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate

# 查询分解提示
decompose_prompt = PromptTemplate(
    input_variables=["question"],
    template="""请将以下复杂问题分解为2-3个简单的子问题,这些子问题的答案可以帮助回答原始问题。

问题: {question}

子问题:"""
)

# 创建查询分解链
decompose_chain = LLMChain(llm=llm, prompt=decompose_prompt)

# 分解查询
result = decompose_chain.run(query)
sub_questions = [q.strip() for q in result.split('\n') if q.strip()]

# 对每个子问题进行检索和回答
sub_answers = []
for sub_q in sub_questions:
    sub_docs = retriever.get_relevant_documents(sub_q)
    sub_context = "\n\n".join([doc.page_content for doc in sub_docs])
    
    sub_prompt = f"""基于以下信息回答问题:

信息:
{sub_context}

问题: {sub_q}

回答:"""
    
    sub_answer = llm(sub_prompt)
    sub_answers.append({"question": sub_q, "answer": sub_answer})

# 合成最终答案
synthesis_prompt = f"""基于以下子问题的答案,回答原始问题:

原始问题: {query}

子问题和答案:
"""

for item in sub_answers:
    synthesis_prompt += f"问题: {item['question']}\n"
    synthesis_prompt += f"答案: {item['answer']}\n\n"

synthesis_prompt += "最终答案:"

final_answer = llm(synthesis_prompt)

4. 提示工程与RAG

4.1 提示模板设计

提示模板是RAG系统的关键组件,影响着生成回答的质量和相关性:

基本提示模板

python 复制代码
basic_template = """请基于以下信息回答问题。如果无法从提供的信息中找到答案,请说明你不知道。

信息:
{context}

问题: {question}

回答:"""

引用来源的提示模板

python 复制代码
citation_template = """请基于以下信息回答问题。对于回答中的每个事实,请在方括号中引用相应的来源编号。如果无法从提供的信息中找到答案,请说明你不知道。

信息:
{context_with_sources}

问题: {question}

回答:"""

思维链提示模板

python 复制代码
cot_template = """请基于以下信息回答问题。首先分析问题,思考如何从提供的信息中找到答案,然后给出最终回答。如果无法从提供的信息中找到答案,请说明你不知道。

信息:
{context}

问题: {question}

思考过程:"""

角色扮演提示模板

python 复制代码
role_template = """你是一位专业的研究助手,擅长基于提供的信息回答问题。请基于以下信息回答问题。如果无法从提供的信息中找到答案,请诚实地说明你不知道,而不是猜测。

信息:
{context}

问题: {question}

专业回答:"""

4.2 上下文增强技术

上下文增强技术可以提高RAG系统的性能:

1. 元数据增强

python 复制代码
def enhance_context_with_metadata(documents):
    """使用元数据增强上下文"""
    enhanced_context = ""
    
    for i, doc in enumerate(documents):
        # 添加来源信息
        source = doc['metadata'].get('source', 'Unknown')
        title = doc['metadata'].get('title', 'Untitled')
        date = doc['metadata'].get('created_at', 'Unknown date')
        
        # 格式化增强上下文
        enhanced_context += f"[文档 {i+1}] 来源: {source}, 标题: {title}, 日期: {date}\n"
        enhanced_context += f"内容: {doc['content']}\n\n"
    
    return enhanced_context

2. 相关性排序

python 复制代码
def sort_by_relevance(documents, query, embedding_generator):
    """按相关性排序文档"""
    # 生成查询嵌入
    query_embedding = embedding_generator.generate_embeddings([query])[0]
    
    # 计算相似度分数
    for doc in documents:
        if 'embedding' in doc:
            doc_embedding = doc['embedding']
            # 计算余弦相似度
            similarity = np.dot(query_embedding, doc_embedding) / (
                np.linalg.norm(query_embedding) * np.linalg.norm(doc_embedding)
            )
            doc['relevance_score'] = similarity
    
    # 按相关性排序
    sorted_docs = sorted(documents, key=lambda x: x.get('relevance_score', 0), reverse=True)
    
    return sorted_docs

3. 信息去重

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

def remove_redundant_information(documents, threshold=0.85):
    """移除冗余信息"""
    if not documents:
        return []
    
    # 提取嵌入
    embeddings = np.array([doc['embedding'] for doc in documents if 'embedding' in doc])
    
    # 计算相似度矩阵
    similarity_matrix = cosine_similarity(embeddings)
    
    # 标记要保留的文档
    to_keep = [True] * len(documents)
    
    # 检查相似度
    for i in range(len(documents)):
        if not to_keep[i]:
            continue
        
        for j in range(i + 1, len(documents)):
            if similarity_matrix[i, j] > threshold:
                # 标记为冗余
                to_keep[j] = False
    
    # 过滤文档
    unique_docs = [doc for i, doc in enumerate(documents) if to_keep[i]]
    
    return unique_docs

4.3 回答生成策略

不同的回答生成策略可以满足不同的需求:

1. 直接回答

python 复制代码
def generate_direct_answer(llm, prompt, query, context):
    """生成直接回答"""
    full_prompt = prompt.format(context=context, question=query)
    return llm(full_prompt)

2. 带引用的回答

python 复制代码
def generate_answer_with_citations(llm, prompt, query, documents):
    """生成带引用的回答"""
    # 准备带来源的上下文
    context_with_sources = ""
    for i, doc in enumerate(documents):
        source = doc['metadata'].get('source', 'Unknown')
        context_with_sources += f"[{i+1}] 来源: {source}\n{doc['content']}\n\n"
    
    # 生成回答
    full_prompt = prompt.format(context_with_sources=context_with_sources, question=query)
    return llm(full_prompt)

3. 思维链回答

python 复制代码
def generate_chain_of_thought_answer(llm, prompt, query, context):
    """生成思维链回答"""
    full_prompt = prompt.format(context=context, question=query)
    
    # 生成思维过程
    thinking = llm(full_prompt)
    
    # 添加最终回答提示
    full_response = full_prompt + "\n" + thinking + "\n\n基于以上思考,最终回答是:"
    
    # 生成最终回答
    return full_response + "\n" + llm(full_response)

4. 多模型协作

python 复制代码
def generate_collaborative_answer(retrieval_model, reasoning_model, generation_model, query, documents):
    """多模型协作生成回答"""
    # 1. 检索模型选择最相关的文档
    context = "\n\n".join([doc['content'] for doc in documents])
    
    # 2. 推理模型分析信息
    reasoning_prompt = f"""分析以下信息,找出与问题"{query}"最相关的关键点:

{context}

关键点:"""
    key_points = reasoning_model(reasoning_prompt)
    
    # 3. 生成模型创建最终回答
    generation_prompt = f"""基于以下关键点回答问题:

关键点:
{key_points}

问题: {query}

回答:"""
    
    return generation_model(generation_prompt)

5. RAG系统评估

5.1 评估指标

评估RAG系统需要考虑多个方面:

1. 检索质量指标

  • 召回率(Recall):检索到的相关文档占所有相关文档的比例
  • 精确率(Precision):检索到的相关文档占检索结果的比例
  • F1分数:精确率和召回率的调和平均
  • 平均倒数排名(MRR):相关文档排名的倒数的平均值
  • 归一化折扣累积增益(NDCG):考虑排序质量的指标

2. 回答质量指标

  • 正确性:回答是否准确
  • 相关性:回答是否与问题相关
  • 完整性:回答是否完整覆盖问题
  • 简洁性:回答是否简洁明了
  • 引用准确性:引用是否准确

3. 系统性能指标

  • 延迟:生成回答的时间
  • 吞吐量:单位时间处理的查询数
  • 资源使用:CPU、GPU、内存使用情况

5.2 自动评估方法

自动评估可以提高评估效率:

python 复制代码
from rouge import Rouge
from bert_score import score
import numpy as np
import time

def evaluate_retrieval(retrieved_docs, relevant_docs):
    """评估检索质量"""
    # 计算精确率
    precision = len(set(retrieved_docs) & set(relevant_docs)) / len(retrieved_docs)
    
    # 计算召回率
    recall = len(set(retrieved_docs) & set(relevant_docs)) / len(relevant_docs)
    
    # 计算F1分数
    f1 = 2 * precision * recall / (precision + recall) if (precision + recall) > 0 else 0
    
    return {
        "precision": precision,
        "recall": recall,
        "f1": f1
    }

def evaluate_answer_quality(generated_answer, reference_answer):
    """评估回答质量"""
    # 使用ROUGE评估
    rouge = Rouge()
    rouge_scores = rouge.get_scores(generated_answer, reference_answer)[0]
    
    # 使用BERTScore评估
    P, R, F1 = score([generated_answer], [reference_answer], lang="zh")
    
    return {
        "rouge-1": rouge_scores["rouge-1"]["f"],
        "rouge-2": rouge_scores["rouge-2"]["f"],
        "rouge-l": rouge_scores["rouge-l"]["f"],
        "bert_score": F1.item()
    }

def evaluate_rag_system(rag_system, test_queries, ground_truth):
    """评估RAG系统"""
    results = []
    
    for i, query in enumerate(test_queries):
        # 记录开始时间
        start_time = time.time()
        
        # 生成回答
        answer = rag_system.answer(query)
        
        # 计算延迟
        latency = time.time() - start_time
        
        # 评估回答质量
        quality_scores = evaluate_answer_quality(answer, ground_truth[i]["answer"])
        
        # 评估检索质量
        retrieved_docs = [doc["metadata"]["id"] for doc in rag_system.retrieve(query)]
        retrieval_scores = evaluate_retrieval(retrieved_docs, ground_truth[i]["relevant_docs"])
        
        # 记录结果
        results.append({
            "query": query,
            "answer": answer,
            "reference": ground_truth[i]["answer"],
            "latency": latency,
            "quality_scores": quality_scores,
            "retrieval_scores": retrieval_scores
        })
    
    # 计算平均分数
    avg_results = {
        "avg_latency": np.mean([r["latency"] for r in results]),
        "avg_rouge_1": np.mean([r["quality_scores"]["rouge-1"] for r in results]),
        "avg_rouge_2": np.mean([r["quality_scores"]["rouge-2"] for r in results]),
        "avg_rouge_l": np.mean([r["quality_scores"]["rouge-l"] for r in results]),
        "avg_bert_score": np.mean([r["quality_scores"]["bert_score"] for r in results]),
        "avg_precision": np.mean([r["retrieval_scores"]["precision"] for r in results]),
        "avg_recall": np.mean([r["retrieval_scores"]["recall"] for r in results]),
        "avg_f1": np.mean([r["retrieval_scores"]["f1"] for r in results])
    }
    
    return results, avg_results

5.3 人工评估方法

人工评估可以提供更全面的质量评估:

评估维度

  • 事实准确性:回答中的事实是否准确
  • 相关性:回答是否与问题相关
  • 完整性:回答是否完整覆盖问题
  • 有用性:回答是否对用户有帮助
  • 清晰度:回答是否清晰易懂

评分标准示例

  1. 不满足(1分):回答完全不准确或不相关
  2. 部分满足(2分):回答部分准确或相关,但有明显缺陷
  3. 基本满足(3分):回答基本准确和相关,但可能不完整
  4. 良好(4分):回答准确、相关且相当完整
  5. 优秀(5分):回答完全准确、高度相关且全面

人工评估表格

问题ID 问题 系统回答 事实准确性(1-5) 相关性(1-5) 完整性(1-5) 有用性(1-5) 清晰度(1-5) 总分 评价意见
1 ... ... 4 5 3 4 5 21 ...
2 ... ... 3 4 4 3 4 18 ...

5.4 错误分析与改进

错误分析是改进RAG系统的关键步骤:

常见错误类型

  • 检索错误:未检索到相关文档
  • 排序错误:相关文档排序不佳
  • 理解错误:模型未正确理解问题
  • 生成错误:生成的回答不准确或不相关
  • 幻觉:生成不在检索内容中的信息

错误分析流程

python 复制代码
def analyze_errors(evaluation_results):
    """分析错误案例"""
    # 按总分排序
    sorted_results = sorted(evaluation_results, key=lambda x: sum(x["quality_scores"].values()))
    
    # 选择最差的案例
    worst_cases = sorted_results[:10]
    
    error_analysis = []
    for case in worst_cases:
        # 检索错误分析
        retrieval_error = case["retrieval_scores"]["recall"] < 0.5
        
        # 生成错误分析
        generation_error = case["quality_scores"]["bert_score"] < 0.7
        
        # 延迟分析
        latency_issue = case["latency"] > 2.0  # 超过2秒
        
        error_type = []
        if retrieval_error:
            error_type.append("检索错误")
        if generation_error:
            error_type.append("生成错误")
        if latency_issue:
            error_type.append("延迟问题")
        
        error_analysis.append({
            "query": case["query"],
            "answer": case["answer"],
            "reference": case["reference"],
            "error_type": error_type,
            "retrieval_scores": case["retrieval_scores"],
            "quality_scores": case["quality_scores"],
            "latency": case["latency"]
        })
    
    return error_analysis

改进策略

  • 检索改进:使用混合检索、查询扩展、重排序等
  • 提示优化:改进提示模板、添加示例、使用思维链等
  • 模型升级:使用更强大的基础模型或领域适应模型
  • 知识库增强:扩充知识库、提高知识质量、优化分块策略
  • 后处理优化:添加事实检查、引用验证、格式优化等

6. 从JAVA开发者视角理解RAG

6.1 概念对比

JAVA概念与RAG概念对比

JAVA概念 RAG概念 说明
数据库查询 向量检索 从存储中获取信息
索引优化 向量索引 提高检索效率
缓存机制 结果缓存 提高响应速度
数据预处理 文本分块 准备数据以便处理
模板引擎 提示模板 生成结构化输出
微服务架构 RAG组件 系统架构设计
日志分析 错误分析 系统改进方法

6.2 技能迁移

可迁移的JAVA技能

  1. 数据处理经验

    • 文本解析和处理
    • 数据清洗和转换
    • 批处理和流处理
  2. 系统设计能力

    • 模块化设计
    • 接口定义
    • 可扩展架构
  3. 性能优化技巧

    • 缓存策略
    • 并行处理
    • 资源管理
  4. 质量保证实践

    • 单元测试
    • 集成测试
    • 性能测试

6.3 开发流程对比

JAVA应用开发流程

  1. 需求分析
  2. 系统设计
  3. 数据库设计
  4. 编码实现
  5. 测试验证
  6. 部署维护

RAG系统开发流程

  1. 需求分析
  2. 知识库设计
  3. 向量存储设计
  4. 检索和生成实现
  5. 评估优化
  6. 部署维护

6.4 实践建议

从JAVA到RAG的过渡

  1. 利用已有知识

    • 应用数据处理经验到文本处理
    • 使用系统设计原则构建RAG架构
    • 应用性能优化技巧提高RAG效率
  2. 重点学习领域

    • 向量嵌入和相似度计算
    • 提示工程技术
    • 大语言模型特性和限制
    • Python生态系统工具
  3. 开发习惯调整

    • 从确定性结果到概率性输出
    • 从精确匹配到语义相似度
    • 从规则逻辑到模型推理
    • 从静态数据到动态知识库
  4. 工具链转换

    • Maven/Gradle → pip/conda
    • Spring Boot → FastAPI/Flask
    • Hibernate → LangChain/LlamaIndex
    • JUnit → pytest

7. 实践练习

练习1:基本RAG系统构建

  1. 收集5-10篇关于特定主题的文档(如Python编程、机器学习等)
  2. 实现文本分块和向量嵌入
  3. 构建简单的向量存储
  4. 实现基本的检索和回答生成
  5. 测试系统回答相关问题的能力

练习2:使用LangChain构建RAG

  1. 使用LangChain加载和处理文档
  2. 创建向量存储并实现检索
  3. 设计提示模板
  4. 构建完整的RAG问答链
  5. 评估系统性能并进行优化

练习3:高级RAG技术实践

  1. 实现混合检索(关键词+向量)
  2. 添加查询转换或分解功能
  3. 实现回答引用来源功能
  4. 添加结果缓存机制
  5. 进行系统评估和错误分析

8. 总结与反思

  • RAG(检索增强生成)是一种结合检索系统和生成模型的技术,通过从外部知识库检索相关信息来增强大语言模型的回答质量
  • 知识库构建是RAG系统的基础,包括数据收集、文本分块、向量嵌入和向量存储
  • RAG系统实现可以从基本架构开始,逐步添加高级功能,如混合检索、重排序、查询转换等
  • 提示工程在RAG中扮演重要角色,良好的提示模板和上下文增强技术可以显著提高系统性能
  • RAG系统评估需要考虑检索质量、回答质量和系统性能等多个方面,通过错误分析可以持续改进系统
  • JAVA开发者可以迁移许多已有技能到RAG开发中,同时需要学习新的概念和工具

9. 预习与延伸阅读

预习内容

  • 大模型应用开发模式
  • 大模型应用安全性考虑
  • 多模态模型应用开发
  • 大模型应用部署和监控

延伸阅读

  1. Jerry Liu等,《LlamaIndex: A Comprehensive Guide》
  2. Harrison Chase等,《LangChain: Building applications with LLMs through composability》
  3. Lewis Tunstall等,《Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks》
  4. Pinecone,《Vector Database Guide》
  5. Chip Huyen,《Designing Machine Learning Systems》(第7章:Data Distribution Shifts and Monitoring)

10. 明日预告

明天我们将学习大模型应用开发模式,包括Agent架构、工具使用、多模态应用等高级主题。我们将探讨如何设计和实现更复杂的大模型应用,如何让模型使用工具和API,以及如何处理多模态输入和输出。我们还将讨论大模型应用的安全性考虑,包括提示注入防御、输出过滤和隐私保护等内容。

相关推荐
Java中文社群9 分钟前
抱歉!Java面试标准答案最不重要
java·后端·面试
jiguanghover21 分钟前
n8n 创建多维表格犯的错误
前端·后端
dylan_QAQ22 分钟前
【附录】Spring 配置属性绑定 基础及应用
后端·spring
泡海椒22 分钟前
jquick Path:让JSON数据处理像呼吸一样简单
后端
鹿鹿的布丁25 分钟前
freeswitch+freeswitch+语音网关拨打电话
后端
dylan_QAQ27 分钟前
【附录】Spring 缓存支持 基础及应用
后端·spring
XiangCoder2 小时前
🔥Java核心难点:对象引用为什么让90%的初学者栽跟头?
后端
二闹2 小时前
LambdaQueryWrapper VS QueryWrapper:安全之选与灵活之刃
后端
得物技术2 小时前
Rust 性能提升“最后一公里”:详解 Profiling 瓶颈定位与优化|得物技术
后端·rust
XiangCoder2 小时前
Java编程案例:从数字翻转到成绩统计的实用技巧
后端