← 返回项目列表
cheshiYOLOv8

cheshi

水果识别图像分类

水果种类识别与分拣系统,支持多品类分类,适用于自动化分拣产线。

¥ 0.10
  • · 完整源码 + 训练数据,支持二次开发
  • · 详细使用文档与部署教程
  • · 下单后网盘链接即时显示,并发送至邮箱

商品详情

构建智能问答系统:深入解析 RAG(检索增强生成)技术与实战

在 Large Language Model (LLM) 爆发的今天,许多企业开始尝试将 AI 融入业务场景。然而,一个长期困扰开发者的痛点始终存在:幻觉(Hallucination)知识时效性。预训练模型虽然博学,但其知识截止于训练数据完成之时,且容易在缺乏事实依据时“胡编乱造”。

RAG(Retrieval-Augmented Generation,检索增强生成) 技术的出现,恰好解决了这一核心矛盾。它通过结合外部知识库的检索能力与 LLM 的生成能力,让 AI 回答更准确、更实时、更可信。本文将深入探讨 RAG 的架构原理,并通过 Python 代码实战,带你从零搭建一个基于私有文档的智能问答系统。


一、 什么是 RAG?

RAG 并非单一的技术,而是一种架构范式。它的核心思想可以概括为:在回答用户问题之前,先从外部知识源中检索相关信息,将这些信息作为上下文(Context)提供给 LLM,从而引导模型生成基于事实的答案。

传统的问答系统有两种主流方案:

  1. 微调(Fine-tuning):通过大量数据调整模型参数。但这成本高、周期长,且无法动态更新知识。
  2. 纯提示工程(Prompt Engineering):直接问模型。但这受限于上下文窗口和模型自身知识储备。

RAG 则是两者的折中与升华:

  • 检索(Retrieval):从向量数据库或搜索引擎中获取与问题最相关的片段。
  • 生成(Generation):将问题和检索到的片段拼接成 Prompt,喂给 LLM 生成最终答案。

RAG 的工作流程

  1. 数据摄取(Ingestion):将非结构化数据(如 PDF、Word、网页)解析成文本,切分为小块(Chunks)。
  2. 向量化(Embedding):利用 Embedding 模型将文本块转化为高维向量,存入向量数据库。
  3. 检索(Retrieval):用户提问时,将问题也转化为向量,在数据库中计算相似度,召回 Top-K 个相关片段。
  4. 生成(Generation):将问题和召回的片段组合成 Prompt,发送给 LLM 生成答案。

图片

二、 技术栈选型

为了快速搭建 RAG 系统,我们选用目前最流行的开源生态:

  • LangChain:LLM 应用开发框架,简化了 Prompt 管理、链式调用等流程。
  • ChromaDB:轻量级嵌入式向量数据库,适合开发和原型验证。
  • sentence-transformers:开源 Embedding 模型库,支持多种语言。
  • Hugging Face Transformers / OpenAI API:用于 Embedding 生成和 LLM 推理。

注意:本示例使用 OpenAI API 作为 LLM 后端,但在实际生产环境中,你可以根据需求替换为本地部署的 Llama3、Qwen 等模型。


三、 环境准备与依赖安装

首先,确保你的环境中安装了必要的 Python 库:

pip install langchain langchain-community langchain-openai chromadb sentence-transformers pypdf

同时,你需要设置 OpenAI 的 API Key:

import os
os.environ["OPENAI_API_KEY"] = "your-api-key-here"

四、 代码实战:从零构建 RAG 系统

我们将分步骤实现 RAG 的核心组件。假设我们有一份关于“公司考勤制度”的 PDF 文档。

1. 数据加载与切片

LLM 的上下文窗口有限,且过长的文本会稀释关键信息。因此,我们需要将文档切分为较小的片段(Chunks)。LangChain 提供了多种 Document Loader 和 Text Spliter。

from langchain.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter

# 1. 加载 PDF 文档
loader = PyPDFLoader("company_attendance_policy.pdf")
documents = loader.load()

# 2. 文本切片
# chunk_size: 每个块的最大字符数
# chunk_overlap: 块之间的重叠字符数,有助于保持上下文连贯性
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,
    chunk_overlap=50,
    length_function=len,
)
texts = text_splitter.split_documents(documents)

print(f"成功加载并切分为 {len(texts)} 个文本块。")

2. 向量化与存储

将文本转化为向量并存储到 ChromaDB 中。我们使用 OpenAI 的 text-embedding-ada-002 模型,它在语义理解方面表现优异。

from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Chroma

# 初始化 Embedding 模型
embeddings = OpenAIEmbeddings(model="text-embedding-ada-002")

# 将文本转换为向量并存入向量数据库
vectorstore = Chroma.from_documents(
    documents=texts,
    embedding=embeddings,
    persist_directory="./chroma_db"  # 持久化存储,重启后可复用
)

print("向量数据库初始化完成。")

3. 检索器配置

RAG 的核心在于“检索”。我们需要配置一个检索器,以便在用户提问时,能准确找到相关的文本片段。通常使用 similarity search(相似度搜索)。

# 初始化检索器
# k=4 表示每次检索返回最相关的 4 个片段
retriever = vectorstore.as_retriever(
    search_type="similarity",
    search_kwargs={"k": 4}
)

# 测试检索功能
query = "员工迟到超过3次会有什么惩罚?"
docs = retriever.invoke(query)
print(f"检索到 {len(docs)} 个相关文档。")
for i, doc in enumerate(docs):
    print(f"--- 文档 {i+1} ---")
    print(doc.page_content[:100] + "...") # 仅打印前100字

4. 构建 Prompt 模板

一个优秀的 Prompt 是 RAG 成功的关键。我们需要设计一个模板,清晰地告诉 LLM:你是谁你有哪些参考资料你的任务是什么以及回答的限制条件

from langchain.prompts import ChatPromptTemplate

template = """
你是一个专业的客户服务助手。请根据以下提供的上下文信息(Context)回答用户的问题。

**上下文信息:**
{context}

**用户问题:**
{question}

**回答要求:**
1. 请基于提供的上下文信息回答问题,不要编造事实。
2. 如果上下文中没有提到相关信息,请直接回答“抱歉,知识库中未找到相关信息”。
3. 回答要简洁、专业、友好。
4. 请用中文回答。
"""

prompt = ChatPromptTemplate.from_template(template)

5. 组装 RAG 链(Chain)

现在,我们将检索器、Prompt 和 LLM 组装在一起。LangChain 的 create_retrieval_chain 可以自动处理数据流:用户提问 -> 检索文档 -> 传入 Prompt -> LLM 生成答案。

from langchain_openai import ChatOpenAI
from langchain.chains import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain

# 初始化 LLM
llm = ChatOpenAI(model="gpt-4o", temperature=0)

# 创建组合文档链:将检索到的文档拼接后传入 LLM
combine_docs_chain = create_stuff_documents_chain(llm, prompt)

# 创建 RAG 链:结合检索器和组合文档链
rag_chain = create_retrieval_chain(retriever, combine_docs_chain)

# 6. 执行问答
response = rag_chain.invoke({"input": "员工迟到超过3次会有什么惩罚?"})

print("最终回答:")
print(response["answer"])

完整可运行代码示例

为了便于复制运行,以下是整合后的完整代码结构:

import os
from dotenv import load_dotenv

# 加载环境变量
load_dotenv()
os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY")

from langchain.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Chroma
from langchain.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain.chains import create_retrieval_chain, create_stuff_documents_chain

def build_rag_system(pdf_path):
    """构建 RAG 系统的核心函数"""
    print("步骤 1: 加载并切片文档...")
    loader = PyPDFLoader(pdf_path)
    documents = loader.load()
    
    text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
    texts = text_splitter.split_documents(documents)
    
    print("步骤 2: 向量化并存储...")
    embeddings = OpenAIEmbeddings()
    vectorstore = Chroma.from_documents(documents=texts, embedding=embeddings, persist_directory="./chroma_db")
    
    print("步骤 3: 初始化检索器和 LLM...")
    retriever = vectorstore.as_retriever(search_type="similarity", search_kwargs={"k": 4})
    llm = ChatOpenAI(model="gpt-4o", temperature=0)
    
    print("步骤 4: 构建 Prompt...")
    template = """
    请基于以下上下文回答问题:
    
    Context:
    {context}
    
    Question: {input}
    
    如果上下文未提供答案,请回答“不知道”。否则,请用中文简要回答。
    """
    prompt = ChatPromptTemplate.from_template(template)
    
    combine_docs_chain = create_stuff_documents_chain(llm, prompt)
    rag_chain = create_retrieval_chain(retriever, combine_docs_chain)
    
    return rag_chain

def ask_question(rag_chain, question):
    """向 RAG 系统提问"""
    result = rag_chain.invoke({"input": question})
    return result["answer"]

if __name__ == "__main__":
    # 假设当前目录下有一个 sample_policy.pdf
    # 如果没有,请创建一个简单的文本文件并重命名为 .pdf 或使用其他测试文档
    try:
        rag_chain = build_rag_system("sample_policy.pdf")
        answer = ask_question(rag_chain, "请简述请假的流程。")
        print(f"Q: 请简述请假的流程。")
        print(f"A: {answer}")
    except Exception as e:
        print(f"发生错误: {e}")
        print("请确保已安装依赖并配置了 OpenAI API Key。")

五、 进阶优化策略

上述基础 RAG 架构虽然简单有效,但在复杂场景下仍面临挑战。以下是几种常见的优化手段:

1. 重排序(Re-ranking)

向量相似度搜索(Semantic Search)虽然快,但精度有限。引入 Re-ranking 模型可以对初步检索回来的 Top-K 文档进行精排。Re-ranking 模型通常更复杂,计算量更大,但能显著提升相关性。

  • 实现方式:先进行向量检索召回 50 个文档,再使用 Cross-Encoder 模型(如 BGE-Reranker)对这 50 个文档与问题的相关性打分,取 Top-5 传入 LLM。

2. 元数据过滤(Metadata Filtering)

在实际业务中,数据往往带有元数据(如部门、日期、文档类型)。通过在检索时过滤特定元数据,可以减少噪声。

# 示例:只检索“人力资源部”发布的文档
docs = retriever.invoke(
    "请假流程",
    search_kwargs={"filter": {"source": "HR_Dept"}}
)

3. 多跳检索(Multi-hop Retrieval)

有些问题需要结合多个文档才能回答。例如:“张三属于哪个部门?该部门的年假是多少天?” 这需要系统进行分解提问,先查张三的部门,再查该部门的年假政策。LangChain 的 MultiQueryRetriever 或自定义链式结构可实现此功能。

4. 知识图谱增强(GraphRAG)

将向量检索与知识图谱结合。向量擅长语义匹配,图谱擅长逻辑推理和实体关系。GraphRAG 能在处理复杂实体关系和聚合信息时发挥巨大优势,尤其适合金融、医疗等领域。


六、 挑战与最佳实践

常见挑战

  1. 切分不当:Chunk 切得太碎,丢失上下文;切得太长,引入噪声。最佳实践是根据语义完整性(如段落、句子)进行切分,而非固定字符数。
  2. 上下文窗口溢出:检索到的文档过多,超出 LLM 上下文限制。需控制 k 值或引入压缩机制。
  3. 噪声干扰:检索到了无关文档,导致 LLM 产生误导。需加强 Prompt 中的“否定指令”(如:无关信息请忽略)。

最佳实践建议

  • 数据清洗:入库前务必清理 PDF 中的页眉、页脚、乱码。
  • 评估体系:不要仅凭感觉判断效果。使用 RAGAS 或 TruLens 等评估框架,从相关性(Context Relevance)、忠实度(Faithfulness)和答案相关性(Answer Relevancy)三个维度量化指标。
  • 人类反馈强化学习(RLHF)的轻量化应用:收集用户点赞/点踩的数据,定期优化检索策略或 Prompt。

七、 结语

RAG 技术是目前落地 LLM 应用最成熟、最具性价比的方案之一。它不仅仅是一个技术组件,更是一种思维范式:让 AI 在“思考”之前,先学会“查阅资料”

通过本文的代码实战,我们构建了一个基本的 RAG 系统。但在实际生产中,你需要根据具体的业务场景,在数据预处理、检索策略、Prompt 工程和评估体系上进行持续的迭代与优化。随着向量数据库和 Embedding 技术的不断进步,RAG 将会变得更加智能、高效,成为连接人类知识与机器智能的最稳固桥梁。

希望这篇文章能为你开启 RAG 开发之旅提供清晰的指引。如有任何疑问,欢迎深入探索 LangChain 官方文档及相关开源社区。