Soup's Blog

Back

RAG实战(一)构建QA系统Blur image

代码开源Github地址

构建本地Q&A系统#

本系列代码都来自博客,在博主的基础上实现了本地调用模型,同时基本跑通所有代码。实现了一个从本地构建知识库,并进行问答的系统。

这个实战可以帮助理解RAG从文本分块-构建向量数据库-检索-生成的整个流程以及优化技巧等。

加载文档#

在构建 RAG(Retrieval-Augmented Generation)系统时,第一步是将外部知识源(如网页、PDF、Word 文档等)加载为程序可处理的格式。LangChain 提供了丰富的 Document Loaders,用于从不同来源提取文本内容,并统一封装为 Document 对象。

什么是 Document?

在 LangChain 中,Document 是一个轻量级的数据结构,用于表示一段文本及其相关的元信息(metadata)。其基本结构如下:

from langchain_core.documents import Document

doc = Document(
    page_content="这是文档的正文内容。",
    metadata={"source": "example.pdf", "page": 1, "title": "示例标题"}
)
python
  • page_content:字符串类型,存储实际的文本内容。
  • metadata:字典类型,包含与该文档片段相关的附加信息,例如来源 URL、页码、标题、作者、创建时间等。

这种设计使得后续的文本分割、向量化、检索等步骤能够保留上下文和溯源信息,对于构建可解释、可追踪的 RAG 系统至关重要。


加载 Web 文档#

我们使用 WebBaseLoader 从指定的 CSDN 博客链接加载内容。该 loader 基于 requestsBeautifulSoup,能自动解析 HTML 并提取正文文本。

from langchain_community.document_loaders import WebBaseLoader

loader = WebBaseLoader("https://blog.csdn.net/weixin_44919384/article/details/154616759?spm=1001.2014.3001.5501")
docs = loader.load()

print(f"加载了 {len(docs)} 个文档")
title = docs[0].metadata.get('title', 'N/A')
print(f"第一个文档的标题:{title}")
print(f"第一个文档长度: {len(docs[0].page_content)} 字符")
python

输出:

加载了 1 个文档
第一个文档的标题:模型训练(四)梯度累计Gradient Accumulation-CSDN博客
第一个文档长度: 11642 字符
plaintext

💡 提示WebBaseLoader 默认会尝试提取页面的 <title> 和部分元标签作为 metadata,但具体效果取决于目标网站的 HTML 结构。对于复杂或动态渲染的网页,可能需要结合 SeleniumPlaywright 等工具。


加载 PDF 文档#

对于本地 PDF 文件,我们使用 PyPDFLoader。它基于 pypdf 库,按页读取 PDF 内容,每一页会被封装为一个独立的 Document 对象。

from langchain_community.document_loaders import PyPDFLoader

loader = PyPDFLoader("./Dataset/PDF/基于视-触觉融合感知的机器人抓取滑动检测与力控研究_闫腾.pdf")
docs = loader.load()

first_doc = docs[0]
print("meta_data:", first_doc.metadata)
print("content:", first_doc.page_content[:500] + "..." if len(first_doc.page_content) > 500 else first_doc.page_content)
python

输出:

meta_data: {'producer': 'TTKN', 'creator': 'ReaderEx_DIS 2.5.0 Build 4088', 'creationdate': '2025-11-03T14:24:27-08:00', 'author': 'CNKI', 'source': './Dataset/PDF/基于视-触觉融合感知的机器人抓取滑动检测与力控研究_闫腾.pdf', 'total_pages': 79, 'page': 0, 'page_label': '1'}
content: 硕士学位论 文
学位申请人姓名 闫腾
学位申请人学号 2200411007
专 业 名 称 机械工程
学 科 门 类 工学
学院(部、研究院) 应用技术学院
导 师 姓 名 李文贤
二〇二五年六月
分类号 学校代码 10590
UDC 密 级 公开
基于视-触觉融合感知的机器人
抓取滑动检测与力控研究
ധᎪն࿐
plaintext

📌 注意

  • PyPDFLoader 对扫描版 PDF(即图片型 PDF)无效,仅适用于文字可复制的 PDF。
  • 每个 Documentmetadata 中通常包含 "source"(文件路径)和 "page"(页码),便于后续定位原文位置。

通过上述步骤,我们成功将异构数据源统一转换为 LangChain 的 Document 格式,为下一步的文本分割向量嵌入做好了准备。


Part1: 文本分块(Text Chunking)#

在将原始文档加载为 Document 对象后,下一步是将其切分为更小的“文本块”(chunks)。这一步对 RAG 系统的性能和效果至关重要。

为什么需要分块?

  1. LLM 上下文长度限制
    当前主流大语言模型(如 GPT-4、Claude、Llama 等)都有最大 token 输入限制(例如 8K、32K 或 128K)。若直接将整篇长文档送入模型,会超出上下文窗口,导致截断或报错。

  2. 提升检索精度
    向量数据库在检索时,会将用户查询与每个文本块的嵌入向量进行相似度匹配。较小且语义完整的文本块更容易与特定问题对齐,避免无关信息干扰。

  3. 降低计算与推理成本
    RAG 只需将最相关的几个文本块送入 LLM 进行生成,而非整篇文档。这显著减少了 token 消耗和响应延迟,尤其在调用付费 API 时能有效控制成本。


1.1 创建文本分块器#

LangChain 提供了多种文本分块策略。我们首先使用推荐的 递归字符分块器(RecursiveCharacterTextSplitter)

# 创建文本分块器
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,        # 每块最大字符数
    chunk_overlap=200,      # 块之间的重叠
    length_function=len,    # 长度计算函数
    is_separator_regex=False,
)

# 分块文档
splits = text_splitter.split_documents(docs)

print(f"原始文档: {len(docs)} 个")
print(f"分块后: {len(splits)} 个")
print(f"\n第一个分块示例:\n{splits[0].page_content[:200]}...")
python

输出示例:

💡 chunk_overlap 的作用:通过保留相邻块的部分重叠内容,可减少因切分导致的关键信息丢失(例如一个句子被切成两半),提升后续检索与生成的连贯性。


1.2 分块策略对比#

LangChain 支持多种分块方式,适用于不同场景。下面我们对比四种常见策略:

1. 字符分块(CharacterTextSplitter)#

  • 原理:按固定字符数硬切分。
  • 优点:实现简单、速度快。
  • 缺点:极易切断句子或段落,破坏语义完整性。
  • 适用场景:对语义要求不高的粗略处理。
from langchain_text_splitters import CharacterTextSplitter
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
python

2. 递归分块(RecursiveCharacterTextSplitter)✅ 推荐默认#

  • 原理:按优先级尝试在语义边界(如 \n\n\n → 空格 → 任意字符)处分割,尽可能保持段落完整。
  • 优点:兼顾效率与语义,适用于大多数文本(论文、网页、报告等)。
  • 缺点:仍基于字符长度,无法精确控制 token 数。
  • 适用场景:通用 RAG 项目首选。
from langchain_text_splitters import RecursiveCharacterTextSplitter

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=200,
    separators=["\n\n", "\n", " ", ""]
)
python

3. Token 分块(TokenTextSplitter)#

  • 原理:使用指定 tokenizer(如 GPT 的 tiktoken)精确按 token 切分。
  • 优点:严格控制输入长度,避免超限。
  • 缺点:依赖具体模型的 tokenizer,速度较慢;仍可能切断句子。
  • 适用场景:对接特定 LLM 且对 token 预算敏感的任务。
from langchain_text_splitters import TokenTextSplitter
text_splitter = TokenTextSplitter(chunk_size=256, chunk_overlap=50)
python

4. 语义分块(SemanticChunker)#

  • 原理:利用嵌入模型计算句子间语义相似度,在“语义突变点”处分割。
  • 优点:块内语义高度一致,检索质量高。
  • 缺点:计算开销大,需调用 embedding 模型;分块大小不固定。
  • 适用场景:对检索精度要求极高的专业领域(如法律、医疗)。
from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai import OpenAIEmbeddings

embeddings = OpenAIEmbeddings()
text_splitter = SemanticChunker(embeddings)
python

1.3 分块策略对比总结#

策略优点缺点适用场景
字符分块简单、快速易切断语义,质量差快速原型、非关键任务
递归分块语义友好、高效、通用基于字符,非 token 精确大多数 RAG 项目推荐
Token 分块精确控制 token 长度依赖 tokenizer,可能断句对接特定 LLM,严格 token 限制
语义分块语义连贯性最佳计算开销大,速度慢高精度检索(法律、科研等)

接下来,我们将把分块后的文本转换为向量,并存入向量数据库,为检索阶段做准备。


Part2: 向量化:将文本转化为语义向量#

在 RAG 系统中,向量化(Embedding) 是实现语义检索的核心步骤。其目标是将分块后的文本转换为高维数学向量,使得语义相近的文本在向量空间中距离更近,从而支持高效的相似性搜索。

2.1 为什么需要向量化?#

  • 超越关键词匹配:传统关键词检索无法理解“苹果”和“水果”的语义关系,而向量嵌入能捕捉深层语义。
  • 支持语义搜索:通过计算查询与文档块之间的向量相似度(如余弦相似度),可返回最相关的内容,即使措辞不同。
  • 为向量数据库提供输入:后续我们将这些向量存入 FAISS、Chroma、Milvus 等向量数据库,实现毫秒级检索。

2.2 常用嵌入模型对比#

选择合适的嵌入模型对 RAG 效果至关重要。以下是主流模型的横向对比:

模型提供商维度成本性能特点
text-embedding-3-smallOpenAI1536$⭐ 高性价比通用场景首选,速度快、效果好
text-embedding-3-largeOpenAI3072$$最高质量适合高精度要求任务
text-embedding-ada-002OpenAI1536$上一代兼容旧系统,逐渐被 small 替代
all-MiniLM-L6-v2Hugging Face384免费轻量快速适合本地部署,英文为主
bce-embedding-base_v1百度 / ModelScope768免费⭐ 中文优化中文任务表现优异,推荐本地使用
bce-reranker-base_v1百度-免费重排序专用用于检索后精排,非嵌入模型

💡 中文项目建议
若你的数据以中文为主(如学位论文、技术文档),bce-embedding-base_v1 是目前开源免费模型中表现最出色的之一,专为中文语义理解优化,且支持本地 GPU 加速。


2.3 使用 BCE 嵌入模型进行本地向量化#

我们通过 ModelScope 下载百度开源的 bce-embedding-base_v1 模型,并使用 LangChain 封装调用:

输出结果

文本: RAG是一种强大的AI技术
向量维度: 768
向量前5个值: [0.0048, 0.0216, -0.005, 0.02, -0.0113]
plaintext

✅ 成功生成 768 维的语义向量!


2.4 向量相似度验证:语义是否被正确捕捉?#

我们通过余弦相似度验证模型的语义理解能力:

输出结果

「苹果是一种水果」 vs 「香蕉是一种水果」 相似度: 0.7481
「苹果是一种水果」 vs 「苹果是一种好吃的水果」 相似度: 0.9040
plaintext

🔍 分析

  • 两者都提到“水果”,因此相似度较高(>0.7);
  • text1 与 text3 主体完全一致(“苹果”),仅增加形容词“好吃”,语义更接近,相似度达 0.904,说明模型有效捕捉了中文语义细微差别。

下一步:构建向量数据库 现在,我们已将每个文本块转换为 768 维向量。接下来,我们将把这些向量与原始文本一起存入 向量数据库(如 FAISS 或 Chroma),为用户查询提供高效、精准的语义检索能力。


Part3: 存储到向量数据库#

在完成文档加载、分块和向量化后,下一步是将这些文本块及其对应的嵌入向量持久化存储到向量数据库中。这是 RAG 系统实现高效语义检索的关键基础设施。

为什么需要向量数据库?

  • 快速相似性搜索:面对成千上万的文本块,暴力计算余弦相似度效率极低。向量数据库通过近似最近邻(ANN)算法(如 HNSW、IVF)实现毫秒级检索。
  • 元数据关联:除了向量,还能存储原始文本、来源文件、页码等 metadata,便于溯源和结果展示。
  • 持久化与复用:一次构建,多次查询,避免重复加载和嵌入计算。

3.1 主流向量数据库对比#

数据库类型优势适用场景
Chroma嵌入式简单易用,无需额外服务,LangChain 深度集成✅ 开发、小规模应用、本地原型
Pinecone云服务高性能、全托管、自动扩缩容生产环境(需网络 & 账号)
Weaviate自建/云功能丰富(支持 GraphQL、分类、生成),开源大规模部署、企业级应用
FAISS库(非数据库)速度快,Meta 开源,适合研究离线实验、临时索引

🚀 本项目选择 Chroma:因其轻量、本地运行、与 LangChain 无缝对接,非常适合学术论文类 RAG 应用的开发与测试。


3.2 批量加载目录下所有 PDF 并预处理#

我们的目标是将 ./Dataset/PDF/ 目录下的 10 篇机器人抓取相关硕士论文统一加载、分块、清洗并存入向量库。

步骤 1:递归加载所有 PDF#

实际输出

📁 找到 10 个PDF文件:
✅ ...(略)
🎉 总共加载 12729 个文本分块(均已添加 id)
plaintext

⚠️ 注意:chunk_size=100 在演示中偏小(导致分块数多),实际建议设为 500–1000 字符 以平衡粒度与上下文完整性。


步骤 2:清洗非法 Unicode 字符(关键!)#

在尝试保存到 Chroma 时,我们遇到了以下错误:

UnicodeEncodeError: 'utf-8' codec can't encode characters in position 795-798: surrogates not allowed
plaintext

这是因为某些 PDF 解析后包含 非法代理字符(surrogate characters),而 ChromaDB(底层基于 Rust)要求所有字符串必须是合法 UTF-8。

解决方案:文本清洗函数#
def clean_text(text: str) -> str:
    """移除无法编码为 UTF-8 的非法字符,并清理首尾空白"""
    return text.encode('utf-8', errors='ignore').decode('utf-8').strip('\n').strip()

# 对所有分块内容进行清洗
for doc in splits:
    doc.page_content = clean_text(doc.page_content)
python

✅ 清洗后即可安全写入 Chroma。


步骤 3:智能创建并填充 Chroma 向量库#

为避免重复创建或覆盖问题,我们设计一个“智能初始化”函数:

执行日志

🔧 处理集合: robot_grasping_rag
✅ 集合为空,无需清理
📦 添加批次 1/13: 1000 个文档
...
📦 添加批次 13/13: 729 个文档
✅ 完成!集合 'robot_grasping_rag' 现有 12729 个文档
plaintext

💾 数据已持久化到 ./chroma_db/ 目录,下次可直接加载复用,无需重新嵌入!

下一步:语义检索与问答

现在,我们的 10 篇中文论文已被切分为 12,729 个清洗后的文本块,并成功存入 Chroma 向量数据库。接下来,我们将实现:

  • 用户自然语言查询的向量化
  • Top-K 语义相似块检索
  • 将检索结果注入 LLM 生成最终答案

这是 RAG 的核心流程!


Part4: 检索#

向量数据库构建完成后,RAG 系统的核心能力之一——语义检索——正式启用。这一步的目标是:根据用户自然语言问题,从海量文档块中精准召回最相关的上下文片段,为后续大模型生成答案提供高质量依据。

4.1 常见检索策略对比#

LangChain 支持多种检索方式,适用于不同场景:

策略配置示例优势劣势适用场景
相似度搜索 (Similarity)search_type="similarity", k=5简单快速,直接返回最相关结果可能返回高度重复内容默认首选,通用问答
最大边际相关性 (MMR)search_type="mmr", k=5, fetch_k=20, lambda_mult=0.5平衡相关性与多样性计算稍慢需要多角度信息(如综述类问题)
相似度阈值过滤search_type="similarity_score_threshold", score_threshold=0.5过滤低质量结果,保证精度可能无结果返回对答案可靠性要求极高

MMR 工作原理示意

以查询 “机器学习算法” 为例:

  1. 第一步:先用相似度搜索获取 top-20 候选:

    • #1: “深度学习是机器学习的一个分支…” (相似度 0.95)
    • #2: “深度学习使用神经网络…” (0.94,与 #1 高度重合)
    • #3: “决策树是一种机器学习算法…” (0.90)
    • #4: “支持向量机(SVM)用于分类…” (0.88)
  2. 第二步:MMR 选择时,优先选 #1(最相关),然后跳过 #2(太相似),转而选择 #3 和 #4 以增加主题多样性

💡 在学术论文检索中,MMR 尤其有用——避免只返回同一章节的重复段落。


4.2 加载向量库并初始化检索器#

我们首先从磁盘加载已持久化的 Chroma 数据库:

import os
from langchain_chroma import Chroma

if os.path.exists("./chroma_db"):
    vectorstore = Chroma(
        persist_directory="./chroma_db",
        embedding_function=embeddings
    )
    collection = vectorstore._collection
    print(f"✅ 向量数据库加载成功!")
    print(f"   已存在 {collection.count()} 个文档块")
    print(f"   Collection 名称: {collection.name}")
python

输出

✅ 向量数据库加载成功!
   已存在 12721 个文档块
   Collection 名称: langchain
plaintext

📌 向量数据库 vs Collection 类比

  • 向量数据库 ≈ MySQL 实例(如 my_company_db
  • Collection ≈ 数据表(如 papers 表)
  • 每条记录 = 文本块 + 向量 + 元数据(来源、页码、ID)

接着创建基础检索器(返回 top-5):

retriever = vectorstore.as_retriever(
    search_type="similarity",
    search_kwargs={"k": 5}
)
python

4.3 执行检索并清理乱码文本#

PDF 解析常引入排版残留字符(如 , , 多余换行等),需在展示前清洗。

增强版中文文本清洗函数

import re

def clean_text(text: str) -> str:
    """
    移除中文字符之间的任意空白(包括全角空格、不间断空格等),
    同时保留英文/数字间的正常空格。
    """
    chinese_char = r'[\u4e00-\u9fff]'
    any_whitespace = r'[\s\u00A0\u2000-\u200F\u2028-\u202F\u3000]+'
    pattern = f'({chinese_char}){any_whitespace}(?={chinese_char})'
    result = re.sub(pattern, r'\1', text)  # 中文间空白直接删除
    result = re.sub(r'\s+', ' ', result)   # 其他区域合并多余空格
    return result.strip()
python

检索示例:查询“什么是视触觉?”

query = "什么是视触觉?"
docs = retriever.invoke(query)

for i, doc in enumerate(docs, 1):
    cleaned_content = clean_text(doc.page_content)
    source = doc.metadata.get('source_file', 'N/A')
    doc_id = doc.metadata.get('id', 'N/A')
    print(f"\n📄 结果 {i}:")
    print(f"内容: {cleaned_content[:300]}...")
    print(f"来源: {source}")
    print(f"ID: {doc_id}")
python

实际输出节选

📄 结果 1:
内容: perception 皮肤。使用一种被称为Gelsight的触觉传感器作为机器人的指尖触觉感受器...
来源: 基于触觉图像序列的机器人抓取目标状态感知_韩筱.pdf
ID: 基于触觉图像序列的机器人抓取目标状态感知_韩筱_p23_c383

📄 结果 4:
内容: 人们对一个物体的描述往往是从多个角度去描述的,依靠的是多个器官的共同感知,其中最重要的是视觉感知和触觉感知...
来源: 基于视触觉融合的机械手分类抓取方法研究_余航.pdf
plaintext

✅ 成功召回多篇论文中关于“视触觉融合”的定义与技术描述!


5.4 高级检索:自定义检索器#

基础检索有时不够精准。我们可通过以下四步构建高级检索流水线

  1. 扩大候选范围(如 k=10)
  2. 按元数据过滤(如仅限某作者论文)
  3. 重排序(用更精细打分模型)
  4. 返回 Top-K

🔍 进阶建议:可集成 bce-reranker-base_v1(百度开源重排序模型)对初检结果重新打分,显著提升 top-1 准确率。


5.5 评估检索质量#

仅靠人工判断不够客观。我们设计简单指标评估检索器性能:

输出

平均精确率: 40.00%
平均召回率: 87.50%
plaintext

🔍 分析

  • 高召回率:说明系统能覆盖大部分相关文档(不错过重要信息);
  • 较低精确率:部分返回结果不相关,可能因 chunk_size 过小或 PDF 噪声干扰。

优化方向:增大分块粒度、引入重排序、添加元数据过滤(如排除目录页)。

下一步:整合 LLM 生成答案

现在,我们已能从 10 篇中文论文中高效检索相关内容。下一步将把这些上下文注入大语言模型(如 Qwen、ChatGLM),让其基于真实文献回答用户问题,真正实现 “有据可依” 的智能问答系统


Part5: 生成:基于检索结果让 LLM 生成精准答案#

检索到相关文档后,RAG 系统进入最终环节——生成。这一步的目标是:将检索到的上下文信息与大语言模型的知识相结合,生成准确、流畅且基于事实的答案

5.1 本地问答系统架构设计#

我们构建了一个完整的本地问答系统 LocalQASystem,核心组件包括:

组件技术选型作用关键配置
对话模型Qwen-7B-Chat-Int8 + vLLM生成答案GPTQ量化,推理加速
嵌入模型bce-embedding-base_v1查询向量化与构建时保持一致
向量数据库ChromaDB存储和检索持久化存储

系统初始化流程

class LocalQASystem:
    def __init__(self, model_dir, chroma_db_path="./chroma_db", embeddings_model_path=None):
        self.model_dir = self._setup_model_dir(model_dir)  # 模型路径处理
        self.chroma_db_path = chroma_db_path
        self.embeddings_model_path = embeddings_model_path
        self._setup_model()           # 初始化vLLM
        self._setup_exact_embeddings() # 关键:使用相同嵌入模型
        self._setup_vectorstore()     # 加载向量库
python

初始化输出

📁 使用本地模型: ../Qwen-vllm/Models/Qwen/Qwen-7B-Chat-Int8
🤖 vLLM模型初始化完成
✅ 使用嵌入模型: ./Models/maidalun/bce-embedding-base_v1
🔢 嵌入模型维度: 768
🗂️  向量数据库加载成功: ./chroma_db
📄 文档数量: 12721
✅ 问答系统初始化完成
plaintext

⚠️ 关键点:必须使用与构建向量库时完全相同的嵌入模型,否则向量空间不一致会导致检索失败!


5.2 问答流程四步走#

步骤1:检索相关文档#

def retrieve_with_exact_embedding(self, query, n_results=5):
    """使用精确匹配的嵌入模型进行检索"""
    query_embedding = self.embeddings.embed_query(query)  # 关键步骤!
    results = self.collection.query(
        query_embedding=[query_embedding],  # 使用相同模型生成向量
        n_results=n_results
    )
    return results
python

步骤2:上下文清洗与预处理#

PDF解析常产生格式问题,需专门清洗:

def _clean_context(self, text):
    """清洗PDF解析产生的格式问题"""
    # 1. 合并被错误分割的文字(如:\n运\n动\n → 运动)
    cleaned = re.sub(r'(?<=[^\s])\n(?=[^\s])', '', text)
    # 2. 处理多余空白和空行
    cleaned = re.sub(r'\n\s+\n', '\n\n', cleaned)
    return cleaned.strip()
python

清洗效果对比

  • 清洗前机\n器\n人\n 抓\n取\n 技\n术\n 研\n究
  • 清洗后机器人抓取技术研究

步骤3:构建精准提示词(Prompt Engineering)#

采用ChatML格式,明确约束LLM行为:

💡 提示词设计原则:明确角色、限定知识范围、设定回答规则、防止幻觉。

步骤4:vLLM高效生成答案#

def _generate_answer(self, prompt, max_tokens, temperature):
    sampling_params = SamplingParams(
        max_tokens=max_tokens,      # 控制生成长度
        temperature=temperature,    # 控制随机性(0.1-0.3更确定)
        top_p=0.8,                  # 核采样,提高相关性
        stop=["<|im_end|>", "<|endoftext|>"]  # 停止标记
    )
    
    outputs = self.llm.generate([prompt], sampling_params)
    return outputs[0].outputs[0].text
python

5.3 实战测试:验证系统效果#

questions = [
    "什么是机械臂?它有什么能力?",
    "解释一下滑动检测的基本概念",
]

print("🚀 开始测试问答系统")
print("=" * 70)

for i, question in enumerate(questions, 1):
    print(f"\n🎯 问题 {i}: {question}")
    print("-" * 50)
    
    result = qa_system.ask(question, max_tokens=400, temperature=0.3)
    print(f"💡 答案: {result['answer']}")
    print(f"📚 参考文档: {result['sources']} 个")
python

实际输出

成功指标

  • 答案准确基于论文内容(非模型固有知识)
  • 回答简洁专业,符合学术规范
  • 检索到多个相关文档作为支撑

5.4 进阶话题:Chain Types 详解#

LangChain 提供了多种文档处理链类型,适用于不同场景:

5.4.1 四种链类型对比#

链类型工作原理优点缺点适用场景
Stuff所有文档拼接后一次性提问简单高效,一次LLM调用文档多时会超长文档少(<10),默认首选
Map-Reduce先分别处理每个文档,再合并答案可处理大量文档,支持并行成本高,丢失文档关联文档非常多时
Refine迭代处理,用后续文档改进答案答案质量高,保持关联顺序敏感,不能并行需要高质量答案
Map-Rerank对每个文档生成答案并评分,选最佳自动选择最相关答案每个文档都需LLM调用找最准确答案

5.4.2 链类型选择指南#

🔍 实践建议:从 stuff 开始测试,如遇上下文长度问题再切换到 map_reduce


5.5 生成质量评估与优化#

评估指标#

  • 相关性:答案是否直接回应问题
  • 准确性:是否基于提供的上下文
  • 完整性:是否覆盖问题的关键方面
  • 可读性:语言是否流畅自然

常见问题与解决方案#

问题现象可能原因解决方案
答案与上下文无关提示词约束不够强加强system提示词约束
出现”幻觉”信息温度参数过高降低temperature(0.1-0.3)
答案过于简短max_tokens设置太小适当增加生成长度
包含无关内容检索文档不相关优化检索策略,增加重排序
# 质量优化配置
optimized_params = {
    "temperature": 0.2,      # 降低随机性,提高确定性
    "top_p": 0.85,           # 平衡相关性和多样性
    "max_tokens": 512,        # 保证答案完整
    "stop_tokens": ["<|im_end|>", "\n\n"]  # 合理终止
}
python

总结:RAG 流程闭环#

至此,我们完成了完整的 RAG 流水线:

  1. 文档处理 → PDF解析、文本分块、向量化
  2. 向量存储 → ChromaDB持久化存储
  3. 语义检索 → 相似度搜索、MMR多样性优化
  4. 答案生成 → 提示词工程、vLLM高效推理

核心成就:构建了一个能够基于10篇中文学术论文进行有据可查、准确可靠的智能问答系统。系统完全在本地运行,保障数据安全,且答案可追溯至具体文献来源。

RAG实战(一)构建QA系统
http://www.soupcola.top/blog/rag_blogs/rag_blogs-1
Author Soup Cola
Published at 2026年1月31日