RAG(检索增强生成,Retrieval-Augmented Generation)技术在过去一年中迅速崛起,成为AI落地应用领域最受关注的热门词汇之一。然而,要让这一技术流程在企业制度问答这类实际场景中真正稳定运行并发挥价值,中间仍有许多细节值得深入推敲。本文将从建库到问答、从数据增强到精排重排的完整链路出发,逐层拆解,系统分享实践经验。
本次实践的核心目标是:以一份公司制度PDF为原始素材,在此基础上实现增量同步、相似度过滤、CLI交互式问答,并重点攻克两大常见难点——其一是“同义不同问法”引发的召回漂移问题,其二是“向量排序并不等同于语义排序”所导致的精度瓶颈。
首先,让我们整体了解所采用的技术路线:
| 维度 | 内容 |
|---|---|
| 主线 | 基于LangChain + FAISS + DashScope(Embedding/对话/精排),完整实现RAG全流程 |
| 场景 | 公司制度PDF,附带溯源元数据、增量同步、相似度过滤、CLI交互问答 |
| 今日加码① | 数据增强:写入向量库前,把“身份”信息拼入page_content,有效缓解召回漂移 |
| 今日加码② | Rerank:向量Top-K初筛后,调用gte-rerank-v2进行重排,将最贴合问意的文本块送入Prompt |
RAG核心原理:检索与生成的两步接力
RAG的基本流程可以用一句话清晰概括:
数据入库:Load(读取)→ Split(分块)→ Embedding(向量化)→ Vector Store(存入向量库)
检索生成:Similarity Search(相似度检索,可设阈值或多取再过滤)→ Context + Prompt → LLM(生成答案)
分块参数的设定是调优过程中的第一个关键环节。在本次实践中,具体参数配置如下:
| 参数 | 取值 | 作用 |
|---|---|---|
| Chunk Size | ≈ 500 | 控制文本粒度,过大容易导致语义稀释 |
| Chunk Overlap | ≈ 50 | 减轻边界截断造成的语义断层 |
这一参数组合并非固定不变,针对不同领域的文档,可能需要做针对性调整。但对于制度类文本而言,这无疑是一个比较稳妥且高效的起步配置。
此外,典型问题及其应对策略同样值得提前梳理与关注:
| 问题 | 说明 | 方向 |
|---|---|---|
| 语义偏移/召不回 | 用户问法与正文表述不一致 | 数据增强、Query改写、混合检索 |
| 幻觉 | 检索能力弱时,模型仍会“编造”答案 | 相似度阈值、拒答式Prompt |
| 排序不准 | 向量“形似”但并非最相关段落 | Rerank精排 |
从“点状知识”到LangChain + FAISS的架构理解
很多初学者在接触RAG时,容易把向量库简单理解成一个“黑盒搜索引擎”。实际上,将其拆解开来看会非常清晰:
| 概念 | 一句话 |
|---|---|
| FAISS | 向量 → 邻居ID(寻找最相似的向量) |
| Docstore(pkl) | ID → Document(page_content, metadata) |
| metadata | 记录source_file、page_number、file_hash等,服务于溯源和按文件增量更新 |
| 增量 | 通过processed_files.json(MD5)管理;变更时先按source_file删除旧数据,再执行add_texts;源文件删除时清理孤儿向量 |
| 清洗 | 控制字符、多余换行等,有效降低PDF噪声对Embedding效果的干扰 |
这一“向量+文档”双存储结构,正是LangChain中FAISS集成的标准实现方式,也是企业场景下实现增量维护的核心基础。
企业级制度RAG:设计与实现要点
技术栈与代码一致性
| 层 | 选型 |
|---|---|
| 向量化 | DashScope EMBEDDING_MODEL(例如text-embedding-v2) |
| 对话 | DashScope Generation(例如qwen-turbo) |
| 精排 | dashscope.TextReRank,采用gte-rerank-v2(独立API调用) |
| 向量库 | 本地index.faiss + index.pkl;PDF解析使用PyPDF2,页码信息写入metadata |
同步阶段的数据增强(写入向量前)
整个流程中,一个关键的操作为_enrich_page_content_for_embedding函数。其核心做法是:在写入向量库之前,将“来源信息”拼接到文本正文中。内容模板大致为:资料来源(source_file) + 可选所属部门、页码 + 正文内容(原始分块正文)。而metadata依然保持为结构化字段。
这里需要特别留意几点:
- 冷启动阶段,
process_text_with_splitter→FAISS.from_texts,使用的是增强后的字符串。 - 增量同步时,
sync_vector_store中的add_texts同样先进行增强再写入向量库。 - index.pkl中的page_content是增强版,如果从FAISS导出JSON,可能会与“仅正文”的预期不一致,这一点要特别关注。
这样做的好处显而易见:当用户提问时,即使问法没有明确提到文件名,增强后的文本也能帮助模型锚定到具体的制度和所在位置。这在很大程度上缓解了“同义不同问法”带来的召回漂移问题。
向量召回与相似度阈值
| 项 | 说明 |
|---|---|
| 相似度标尺 | similarity = 1 / (1 + distance),基于L2距离;非概率值,仅在同一索引内具有可比性 |
| _DEFAULT_SIMILARITY_THRESHOLD | 当前设置为0.3,可通过--no-min-score参数进行调试 |
| _DEFAULT_TOP_K | 设置为20,这个规模与后续Rerank的输入保持一致 |
Rerank精排:gte-rerank-v2
这一步是本次实践的重点加码内容。相关实现集中在rerank_retrieval_hits和_call_dashscope_rerank中:
- 输入:由
similarity_search检索、经过阈值过滤后得到的候选列表。 - 调用:
TextReRank.call,模型指定为gte-rerank-v2,top_n与候选条数对齐。 - 输出:每条结果新增
rerank_score,同时保留原有的similarity和distance,便于对照分析和调试。 - 失败处理:网络原因或非200返回时,记录日志并回退到向量排序,不影响整体流程的稳定性。
- 默认配置:
generate_rag_answer(..., use_rerank=True);在ask时可以通过--no-rerank参数跳过精排。 - 纯检索模式:
search默认不执行精排,加--rerank参数才会触发一次DashScope调用。
为什么需要Rerank?向量的相似度检索本质上是“形似”,但语义层面的“最相关”并不一定排在检索结果的最前面。Rerank相当于在候选池中再做一次语义级别的精排,将最贴合用户问意的段落提升到最靠前的位置。
问答Prompt与CLI设计
System Prompt采用专业模板(PROFESSIONAL_SYSTEM_TEMPLATE),核心要求是:仅依据给定片段进行回答,遇到冲突信息时要并列呈现,涉及比例推算时需注明依据。在上下文展示时,如果存在rerank_score,则同时标注精排分和向量similarity,方便排查和定位问题。
CLI命令行工具的具体设计如下:
| 命令 | 作用 |
|---|---|
python langchain_rag.py |
交互式RAG,默认使用Top-20 + Rerank |
python langchain_rag.py sync |
PDF → FAISS 增量同步 |
python langchain_rag.py search … |
纯检索模式,加--rerank开启精排 |
python langchain_rag.py ask … |
单次问答模式,加--no-rerank跳过精排 |
python langchain_rag.py help |
获取帮助信息 |
能力阶梯对照
不同阶段的RAG方案,在成本和稳定性方面各有差异,以下是一个可供参考的对照表:
| 阶段 | 方案 | 核心操作 | 主要解决的问题 | 成本 | 稳定性 |
|---|---|---|---|---|---|
| 1 | 原生RAG | Chunking → 向量库 | 跑通基础流程 | 低 | ⭐ |
| 2 | 参数调优 | Top-K、Chunk / Overlap | 截断与粒度优化 | 低 | ⭐⭐ |
| 3 | 数据增强 | 同步时拼接来源、页码再嵌入 | 召回漂移、出处弱化 | 中 | ⭐⭐⭐⭐ |
| 4 | 查询改写 | Pre-LLM转检索词 | 口语化表达、多轮指代 | 高 | ⭐⭐⭐⭐⭐ |
| 5 | Rerank | Top-K → gte-rerank-v2 | 向量序≠语义最相关 | 中(API) | ⭐⭐⭐⭐⭐ |
| 6 | 混合检索 + 长上下文 | 向量 + BM25 + 长窗口 | 长文、表格、复杂逻辑 | 极高 | ? |
数据增强 + Rerank:四层链路
存储层:头部带资料来源和页码,即使问法不提到文件名,也能锚定制度和具体位置。
检索层:在阈值内适度拉宽候选池,尽可能避免“正确答案进不了前K”的情况。
精排层:对同一批候选进行语义级重排,把最贴合问句的文本片段前置。
生成层:在相对干净的上下文基础上,进行合规表述和拒答边界的把控。
开发者避坑摘要
| 主题 | 要点 |
|---|---|
| macOS OpenMP | 设置KMP_DUPLICATE_LIB_OK=TRUE,缓解FAISS与libomp重复链接导致的崩溃问题 |
| 网络/SSL | Embedding、Generation、Rerank均可能出现断连,dashscope_generation.py与Rerank侧均有退避重试机制 |
| Pickle | allow_dangerous_deserialization=True仅用于可信索引,注意安全性 |
| 索引与增强 | 如果先建库后改增强逻辑,需要sync重建或全量更新,否则仍是旧的向量文本 |
实操进展与仓库记录
| 模块 | 记录 |
|---|---|
| langchain_rag.py | 实现了_enrich_page_content_for_embedding、process_text_with_splitter/增量add_texts、rerank_retrieval_hits、_RERANK_MODEL、search --rerank / ask --no-rerank |
| 增量与清理 | MD5、processed_files.json、按source_file更新、孤儿清理、文本清洗 |
| 检索与问答 | similarity_search、generate_rag_answer、相似度标尺、交互与help |
| 公共模块 | dashscope_generation.py等统一调用与重试 |
| 学习材料 | 本文Day10定稿,与练习代码、向量库一并纳入版本管理 |
今日收束
数据增强的核心价值在于夯实召回与溯源的地基,让模型在检索阶段就能“站在更有利的位置”。而Rerank则是在可控成本范围内,把“最应该让模型看到的段落”尽可能排到靠前位置。两者叠加使用,比单纯更换更大的对话模型,更能贴近工业级RAG常见的迭代节奏——先用工程手段解决性价比最高的瓶颈,再考虑引入更重的模型。
相关代码已更新至:
课程练习 RAG技术与应用 目录(含langchain_rag.py等):Cyning12/auto-gpt-work-demo · data/课程练习/RAG技术与应用
