

























一. 摘要索引
1. 功能
实现了基于摘要检索的高级 RAG 智能问答,通过生成文档摘要用于向量检索、原始全文用于回答,大幅提升检索准确率与召回率,支持 Chroma 本地持久化,可根据文档原文精准回答问题。解决了处理大量文档时,如何快速准确地找到所需信息这一痛点。
2. 特点
检索精度高、语义匹配强、回答更完整、不编造内容、单库持久化无依赖报错、可落地生产、支持批量高速构建、重启自动加载向量库。
3. 核心流程
(一)、存储阶段(入库)
① 原始文档(Word + 网页)→ 切分为标准大小的文档块
② 大模型为每个文档块生成精简摘要
③ vectorstore(Chroma):
A. 摘要文本做向量化(用于检索匹配) Document(page_content=summary) 自动向量化了
B. 原始全文明文存在 metadata 中(用于最终回答)
④ 所有的内容持久化存入本地 Chroma
⑤ 通过 doc_id 唯一标识每个文档
(二)、问答阶段(检索)
① 用户提问 → 转换为向量
② 遍历 vectorstore,匹配相似度最高的摘要
③ 直接从摘要对应的 metadata 中取出原始全文
④ 原始全文输入大模型
⑤ 大模型基于原文生成最终回答
3. 重点补充
(1) 如何存储?
new_doc = Document(
page_content=summary, # 👈 这是 **摘要**
metadata={
"original_content": 原文, # 👈 明文存在 metadata
"doc_id": uuid
}
)
当执行入库存储的时候,即 db = Chroma.from_documents(final_docs, embeddings, persist_directory=CHROMA_PATH)干了三件事
① 把 page_content 摘要 当作明文入库保存
② 把 page_content 摘要 丢给 Embedding 模型 → 生成向量 入库保存
③ 把 metadata 里的原文原样明文存,不做任何向量化
记住结论:构建的Documnet格式存储的时候:适用于任何场景
page_content:明文 + 向量 双存
metadata:只存明文,不存向量
(2) 为什么要存摘存了向量还要存明文?
① 检索命中后,你可以看到匹配的摘要是什么
② Chroma 底层必须保留原始文本,向量只是用来做相似度匹配
③ 向量只能用来算相似度,不能直接读给人看、也不能给 LLM 用
4. 代码分享

import os import uuid from pathlib import Path from langchain_chroma import Chroma from langchain_community.document_loaders import UnstructuredWordDocumentLoader, WebBaseLoader from langchain_text_splitters import RecursiveCharacterTextSplitter from langchain_core.documents import Document from langchain_core.output_parsers import StrOutputParser from langchain_core.prompts import ChatPromptTemplate from langchain_core.runnables import RunnableMap # 你的模型配置 from model_config import llm, get_local_embeddings # ===================== 1. 配置 ===================== CHROMA_PATH = "./full_rag_chroma" # ===================== 2. 获取检索器 ===================== def get_retriever(): embeddings = get_local_embeddings() # ===================== ✅ 判断本地库是否存在 ===================== if Path(CHROMA_PATH).exists() and len(os.listdir(CHROMA_PATH)) > 0: print("✅ 加载本地向量库") db = Chroma(persist_directory=CHROMA_PATH, embedding_function=embeddings) return db.as_retriever(search_kwargs={"k": 2}) # ===================== 第一次运行:创建库 ===================== print("🔍 首次运行,构建向量库...") # 1. 加载文档 和 网页 url = "https://review.jschina.com.cn/suwangyuanchuang/202604/t20260424_s69eae060e4b05172eb1e94cd.shtml" loaders = [UnstructuredWordDocumentLoader("人事管理流程.docx"), WebBaseLoader(url)] raw_docs = [] for loader in loaders: raw_docs.extend(loader.load()) # 2. 切分 splitter = RecursiveCharacterTextSplitter(chunk_size=1024, chunk_overlap=100) split_docs = splitter.split_documents(raw_docs) # # 3. 生成摘要 【逐一生成】 # summary_chain = ChatPromptTemplate.from_template("总结:{page_content}") | llm | StrOutputParser() # # # 4. 摘要做检索文本,原文存入 metadata # final_docs = [] # for doc in split_docs: # summary = summary_chain.invoke({"page_content": doc.page_content}) # new_doc = Document(page_content=summary, metadata={"original_content": doc.page_content, "doc_id": str(uuid.uuid4())}) # final_docs.append(new_doc) # # print(final_docs) # quit() # 3. 批量生成摘要(🚀 速度极快) print("📝 开始批量生成文档摘要...") summary_prompt = ChatPromptTemplate.from_template("总结下面的内容:\n{page_content}") summary_chain = summary_prompt | llm | StrOutputParser() # 🚀🚀🚀 批量并发生成,速度提升 8 倍 summaries = summary_chain.batch([{"page_content": doc.page_content} for doc in split_docs], max_concurrency=8) # 4. 组装最终文档(摘要 + 原文) final_docs = [] for summary, doc in zip(summaries, split_docs): new_doc = Document(page_content=summary, metadata={"original_content": doc.page_content, "doc_id": str(uuid.uuid4())}) final_docs.append(new_doc) # 5. 存入 Chroma(干3件事情) # ① 把 page_content 摘要 当作明文入库保存 # ② 把 page_content 摘要 丢给 Embedding 模型 → 生成向量 入库保存 # ③ 把 metadata 里的原文原样明文存,不做任何向量化 db = Chroma.from_documents(final_docs, embeddings, persist_directory=CHROMA_PATH) print("✅ 向量库构建完成!") return db.as_retriever(search_kwargs={"k": 2}) # ===================== 3. RAG 问答链 ===================== def build_rag_chain(): retriever = get_retriever() prompt = ChatPromptTemplate.from_template( """ 请根据原文回答问题: {context} 问题:{question} """ ) def format_docs(docs): return "\n".join([d.metadata["original_content"] for d in docs]) rag_chain = ( RunnableMap( { "context": lambda x: format_docs(retriever.invoke(x["question"])), "question": lambda x: x["question"], } ) | prompt | llm | StrOutputParser() ) return rag_chain # ===================== 运行 ===================== if __name__ == "__main__": chain = build_rag_chain() ans = chain.invoke({"question": "病假的请假流程?"}) print("回答:\n", ans)
View Code
二. 父子索引
1. 功能
实现了基于父子分块检索的高级 RAG 智能问答,通过子文档碎片做向量检索、父文档完整原文做回答,兼顾检索精度与上下文完整性,支持内存 / MySQL 两种持久化存储,可根据网页 / 文档原文精准回答问题。解决了长文本分块后信息碎片化、检索丢失上下文、回答不完整的痛点。
2. 特点
检索更精准、上下文更完整、不丢失段落信息、不编造内容、支持持久化存储、可调试、可落地生产、支持大文件长文本处理。
①子文档:只用于向量检索匹配,不直接给模型看
②父文档:不向量化,只用于给大模型提供完整上下文
③检索用子块,回答用父块 → 这是父子分块 RAG 的核心思想
3. 核心流程
(一)、存储阶段(入库)
① 原始网页文本 → 分割为父文档(1000 字,完整长文本)
② 父文档 → 二次分割为子文档(400 字,短片段)
③ vectorstore:
A.存储「子文档文本 + 子文档向量 Embedding」(用于检索匹配)
B.子文档绑定父文档 ID
④ docstore(内存 / MySQL):
A. 存储「父文档完整原文」(纯文本,无向量)
B. 通过 doc_id 与子文档关联
⑤ 自动完成父子文档关联与存储
(二)、问答阶段(检索)
① 用户提问 → 转换为向量
② 遍历 vectorstore,匹配相似度最高的子文档碎片
③ 提取子文档中绑定的父文档 ID
④ 依据 doc_id 去 docstore 调取完整父文档原文
⑤ 完整原文输入大模型
⑥ 大模型基于完整上下文生成回答
4. 重点分析
(1). 为什么代码中没有看到父子文档通过id绑定的代码?
这句代码retriever.add_documents(raw_docs)内部的ParentDocumentRetriever自动干了五件事
① 把原文切成父块
② 给每个父块生成一个 uuid
③ 把父块切成子块
④ 给每个子块自动加上 metadata: {"doc_id": 父块 ID}
⑤ 子块存入向量库vectorstore,父块存入 docstorestore
格式如下:
#父文档(存在 docstorestore)
父文档1: id=uuid1, content="完整长文本"
#子文档(存在 vectorstore)
子文档1: page_content="碎片1", metadata={"doc_id": uuid1}
5. 代码分享

# ===================== 导入依赖包 ===================== from model_config import llm, get_local_embeddings from langchain_community.document_loaders import WebBaseLoader from langchain_text_splitters import RecursiveCharacterTextSplitter from langchain_chroma import Chroma from langchain_classic.retrievers import ParentDocumentRetriever from langchain_core.prompts import ChatPromptTemplate from langchain_core.runnables import RunnableMap from langchain_core.output_parsers import StrOutputParser from mysql_store import MySQLStore from langchain_core.stores import InMemoryByteStore # ===================== 1. 全局配置 ===================== # 要抓取的目标网页(必须保证可正常访问) TARGET_URL = "https://news.pku.edu.cn/mtbdnew/15ac0b3e79244efa88b03a570cbcbcaa.htm" # 向量库集合名称 CHROMA_COLLECTION = "split_parents" # 父子分块大小配置 # 父文档:大段落,用于最终回答 PARENT_CHUNK_SIZE = 1000 # 子文档:小碎片,用于向量检索 CHILD_CHUNK_SIZE = 400 # ===================== 2. 加载网页数据 ===================== loader = WebBaseLoader(TARGET_URL) raw_docs = loader.load() # ===================== 3. 定义文档分块器 ===================== # 父文档分割器 parent_splitter = RecursiveCharacterTextSplitter(chunk_size=PARENT_CHUNK_SIZE) # 子文档分割器 child_splitter = RecursiveCharacterTextSplitter(chunk_size=CHILD_CHUNK_SIZE) # ===================== 4. 初始化向量库与嵌入模型 ===================== embedding_model = get_local_embeddings() # 存放:文本的 embedding 向量 + 子文本(小碎片) vectorstore = Chroma(collection_name=CHROMA_COLLECTION, embedding_function=embedding_model) # ===================== 6. 初始化文档存储 ===================== # 方式1:内存存储(重启丢失) # 专门用来存 “完整原文”, 检索完成后,把完整原文拿给大模型读 docstore = InMemoryByteStore() # 方式2:MySQL持久化存储(如需开启,注释掉上面一行,取消注释下面内容) # 需要事先建立好langchain_db库 # docstore = MySQLStore(host="xxx", user="root", password="xxx", database="langchain_db") # ===================== 7. 初始化父子分块检索器 ===================== retriever = ParentDocumentRetriever( vectorstore=vectorstore, docstore=docstore, parent_splitter=parent_splitter, child_splitter=child_splitter, search_kwargs={"k": 1}, ) # 下面这句话,干了5件事情 # ① 把原文切成父块 # ② 给每个父块生成一个 uuid # ③ 把父块切成子块 # ④ 给每个子块自动加上 metadata: {"doc_id": 父块 ID} # ⑤ 子块存入向量库vectorstore,父块存入 docstorestore retriever.add_documents(raw_docs) print("父文档存储数量:", len(list(docstore.yield_keys()))) # ===================== 8. 构建 RAG 问答链 ===================== prompt_template = """ 请根据下面给出的上下文来回答问题: {context} 问题: {question} """ # 构建提示词模板 prompt = ChatPromptTemplate.from_template(prompt_template) # 构建完整问答链 rag_chain = ( RunnableMap({"context": lambda x: retriever.invoke(x["question"]), "question": lambda x: x["question"]}) | prompt | llm | StrOutputParser() ) # ===================== 9. 执行问答 ===================== print("\n------------ 模型回答 ------------------------") question = "天才AI少女是谁?" response = rag_chain.invoke({"question": question}) print(response) # ===================== 10. 调试代码(查看检索效果) ===================== # # 作用:直接从向量库检索【子文档碎片】,查看检索精度 # sub_docs = vectorstore.similarity_search("天才AI少女是谁?", k=1) # print("\n------------ 检索到的子文档(用于匹配) ------------------------") # print(sub_docs) # # # 作用:通过检索器获取【完整父文档】,查看最终给模型的上下文 # ret_docs = retriever.invoke("天才AI少女是谁?") # print("\n------------ 检索到的父文档(用于回答) ------------------------") # print(ret_docs)
View Code
三. 假设性问题索引
1. 功能
实现了基于假设性问题索引的高级多向量 RAG 智能问答。通过大模型为原文生成模拟人类提问的假设问题,以假设问题做向量检索、 原始文档做回答,大幅解决原文语义偏僻、专业术语多、用户提问表达方式和原文不一致导致检索不到的问题,支持内存存储,可根据本地文档原文精准回答问题。
2. 特点
检索精度极高、适配人类自然口语提问、解决原文难检索问题、不编造内容、本地文件运行、批量生成效率高、结构清晰、可调试、可直接落地使用
- 假设问题 page_content:明文 + 向量 双存(存入Chroma,用于检索)
- metadata:只存明文doc_id,不向量化
- 原始文档:只明文存入内存,不向量化、不进向量库
3. 核心流程
(一)、存储阶段(入库)
① 加载本地TXT文档 → 切分为标准大小的原始文档块
② 大模型为每个文档块生成 3 个假设性问题
③ vectorstore:
A. 存储「假设性问题明文 + 假设性问题向量 + 绑定原始文档doc_id」
B. 作用:专门用于向量相似度检索匹配
④ store(内存):
A. 存储「未修改的原始文档明文」
B. 作用:专门用于最终给大模型生成回答
⑤ 人工循环绑定:假设问题通过metadata携带doc_id,一对一关联对应原始文档块
(二)、问答阶段(检索)
① 用户提问 → 问题转换为向量
② 遍历向量库,优先匹配相似度最高的假设性问题(不是原文)
③ 提取假设问题metadata中绑定的原始文档doc_id
④ 根据doc_id去内存store中调取完整原始原文块
⑤ 原始原文送入大模型,严格依据原文生成最终答案
4. 重点分析
(1). 为什么要用假设问题检索?
① 原文晦涩、专业词多、生硬,人类很少这样提问;
② 假设问题贴近人类口语、通俗易懂,用户提问更容易命中;
③ 向量匹配假设问题,匹配成功率远高于直接匹配原文;
④ 检索用问题,回答用原文,兼顾检索准确率和回答真实性。
5. 代码分享

# ===================== 导入依赖包 ===================== from model_config import llm, get_local_embeddings from typing import List from langchain_core.stores import InMemoryByteStore from langchain_chroma import Chroma from langchain_community.document_loaders import TextLoader from langchain_text_splitters import RecursiveCharacterTextSplitter from langchain_classic.retrievers import MultiVectorRetriever from langchain_core.documents import Document from langchain_core.output_parsers import StrOutputParser from langchain_core.prompts import ChatPromptTemplate from langchain_core.runnables import RunnableMap import uuid from pydantic import BaseModel, Field # ===================== 1. 加载并切分本地文档 ===================== # 加载本地txt文本数据 loader = TextLoader("deepseek介绍.txt", encoding="utf-8") raw_docs = loader.load() # 文档分块:将长文本切分为1024字符的片段 text_splitter = RecursiveCharacterTextSplitter(chunk_size=1024, chunk_overlap=100) split_docs = text_splitter.split_documents(raw_docs) # ===================== 2. 结构化输出:为每个文档生成假设性问题 ===================== # 结构化输出模型:限定输出格式为 问题列表 class HypotheticalQuestions(BaseModel): questions: List[str] = Field(..., description="假设性问题列表") # 提示词模板:要求根据文档内容生成3个规范JSON格式的问题 prompt = ChatPromptTemplate.from_template( """请基于以下文档生成3个假设性问题(必须使用JSON格式): {doc} 要求: 1. 输出必须为合法JSON格式,包含questions字段 2. questions字段的值是包含3个问题的数组 3. 使用中文提问 示例格式: {{ "questions": ["问题1", "问题2", "问题3"] }}""" ) # 构建问题生成链:输入文档 → 生成问题 → 结构化输出 question_chain = ( {"doc": lambda x: x.page_content} | prompt | llm.with_structured_output(HypotheticalQuestions) | (lambda x: x.questions) ) # 批量并发生成问题(提升速度) hypothetical_questions = question_chain.batch(split_docs, {"max_concurrency": 5}) # print(hypothetical_questions) #[[question1,question2,question3],[]] # quit() # ===================== 3. 多向量检索:存储与关联数据 ===================== # 向量库:存储【生成的假设问题】的向量(用于检索匹配) embeddings_model = get_local_embeddings() vectorstore = Chroma(collection_name="hypo-questions", embedding_function=embeddings_model) # 文档存储:内存存储【原始文档明文】(用于最终回答) # 基于内存 store = InMemoryByteStore() id_key = "doc_id" # 初始化多向量检索器 retriever = MultiVectorRetriever( vectorstore=vectorstore, byte_store=store, id_key=id_key, ) # 为每个原始文档生成唯一ID doc_ids = [str(uuid.uuid4()) for _ in split_docs] # 把生成的问题封装成Document,绑定对应原始文档ID question_docs = [] for i, question_list in enumerate(hypothetical_questions): for question in question_list: question_docs.append(Document(page_content=question, metadata={id_key: doc_ids[i]})) # 向量库:存储「假设性问题明文 + 假设性问题向量 + 关联文档doc_id」 # page_content = "deepseek受到哪些攻击?" 和 metadata = { "doc_id": "uuid-123456" } 都是明文存储, # 同时对 page_content(假设问题)做 embedding,再存一份 retriever.vectorstore.add_documents(question_docs) # 文档库:存入【原始文档】(通过doc_id关联) retriever.docstore.mset(list(zip(doc_ids, split_docs))) # ===================== 4. 构建RAG问答链并执行 ===================== # 问答提示词模板 answer_prompt = ChatPromptTemplate.from_template("根据下面的文档回答问题:\n\n{doc}\n\n问题: {question}") # 构建完整问答链 rag_chain = ( RunnableMap({"doc": lambda x: retriever.invoke(x["question"]), "question": lambda x: x["question"]}) | answer_prompt | llm | StrOutputParser() ) # 执行提问 query = "deepseek受到哪些攻击?" result = rag_chain.invoke({"question": query}) # 输出结果 print("回答:") print(result) # 调试 # sub_docs = retriever.vectorstore.similarity_search(query, k=2) # 寻找相似性问题 # print(sub_docs)
View Code
四. 元数据索引
1. 功能
让大模型自动理解用户问题中的「过滤条件」,先按元数据精准过滤,再做语义检索,实现更精准的智能检索, 解决传统向量检索无法精准筛选结构化字段的痛点
2. 核心流程
用户提问 → 大模型解析 → 元数据过滤 → 语义检索 → 返回结果
① 用户提问: 作者A发布的一篇论文
② 大模型解析【唯一的一次调用大模型】
大模型根据你定义的元数据字段,自动拆解为json格式
{ "query": "论文", "filter": { "author": "A" }, "limit": 1 }
③ 元数据过滤(本地执行,不调用模型)
在所有文档中过滤出:author = A 的文档
④ 语义检索(向量匹配)
在过滤后的结果里,检索与「论文」最相似的内容
⑤ 返回最终结果
返回最匹配的 1 条文档
3. 重点分析
元数据自查询检索 LLM 调用次数
总共调用:1 次
调用位置:解析用户问题,自动生成 元数据过滤条件(filter) + 语义检索词(query)
后续步骤:向量检索、过滤、排序 → 全部本地执行,不调用模型
4. 代码分享

from langchain_chroma import Chroma from meta_data import docs, metadata_field_info from model_config import llm, get_local_embeddings from langchain_classic.retrievers.self_query.base import SelfQueryRetriever # ===================== 1. 配置 ===================== # 文档内容描述(告诉大模型:这些文档是什么内容) document_content_description = "科技文章简介" # 向量库 + 嵌入模型 embeddings_model = get_local_embeddings() # 下面这句话干了3件事情 # ① 读取 docs 中的 page_content(正文),保存明文 # ② 调用 embeddings_model 嵌入模型 对page_content文本生成向量 # ③ 原文明文 + 向量 + metadata 元数据 全部存入 Chroma 向量库 vectorstore = Chroma.from_documents(docs, embeddings_model) # ===================== 2. 创建自查询检索器 ===================== retriever = SelfQueryRetriever.from_llm( llm=llm, # 大模型 vectorstore=vectorstore, # 向量库 document_contents=document_content_description, # 文档描述 metadata_field_info=metadata_field_info, # 元数据字段 enable_limit=True, # 允许限制返回数量 verbose=True, ) # ===================== 3. 测试检索 ===================== if __name__ == '__main__': # 测试1:作者A的文章 # res1 = retriever.invoke('作者A发布的一篇论文') #无数据 (作者、论文导致的) # res1 = retriever.invoke("我想了解一篇评分在9分以上的文章") # res1 = retriever.invoke("2024年发布的文章") # res1 = retriever.invoke("A的文章,且是2025年发布的") res1 = retriever.invoke('A发布的一篇论文') for doc in res1: print(doc.page_content, " | 元数据:", doc.metadata) # 测试2:作者B在2024年发布的文章 # res1 = retriever.invoke("作者B在2024年发布的文章") # 测试3:评分9分以上的文章 # res1 = retriever.invoke("我想了解一篇评分在9分以上的文章")
View Code
五. 混合索引
1. 功能
实现了BM25关键词检索 + 向量语义检索融合的混合索引RAG。同时保留传统关键词精准匹配能力与向量语义理解能力,通过权重加权融合排序,弥补单一检索缺陷,大幅度提升检索稳定性与准确率,适合口语模糊、关键词混杂、语义模糊的用户提问。
2. 特点
双检索互补、兼顾关键词+语义、抗干扰能力强、检索鲁棒性高、权重可自由调节、逻辑简单易懂、无复杂算法、落地简单、适合通用知识库
3. 检索原理
(1) 向量检索
理解问题含义、匹配语义相似的内容。
优点:语义理解强、模糊提问也能匹配。
缺点:精准关键词匹配弱、容易跑偏。
(2) BM25关键词检索
基于词频统计、纯文本字面严格匹配关键词。
优点:精准命中关键词、专业词汇不会丢失。
缺点:完全不理解语义,只能字面匹配。
(3) 混合检索
融合向量检索与BM25关键词检索,双通路并行检索、加权合并重排。
优点:同时具备语义理解+关键词精准匹配能力,抗干扰性强、检索鲁棒性高,弥补单一检索缺陷。
缺点:权重需要人工调优,不合理权重会降低检索效果。
补充特有规则:
① 同时执行 向量检索 + BM25 检索;
② 各自返回 top-k 文档;
③ 按权重加权融合排序,示例权重 weights=[0.5, 0.5](BM25权重0.5,向量权重0.5);
④ 去重排序后返回最优结果给大模型回答;
⑤ 权重调优:关键词要求高 → 调大BM25权重;语义模糊 → 调大向量权重。
4. 其它
- 检索阶段:0次大模型调用(纯本地算法)。
- 生成回答:1次大模型调用。
5. 代码分享

# ===================== 导入依赖包 ===================== from model_config import llm, get_local_embeddings from langchain_community.document_loaders import TextLoader from langchain_chroma import Chroma from langchain_community.retrievers import BM25Retriever from langchain_classic.retrievers import EnsembleRetriever from langchain_core.output_parsers import StrOutputParser from langchain_core.prompts import ChatPromptTemplate from langchain_core.runnables import RunnableMap from langchain_text_splitters import RecursiveCharacterTextSplitter # 待检索问题 question = "社会影响" # ===================== 1. 加载并切分文档 ===================== # 加载本地文本数据 loader = TextLoader("deepseek介绍.txt", encoding="utf-8") raw_docs = loader.load() # 文本分块 text_splitter = RecursiveCharacterTextSplitter(chunk_size=512, chunk_overlap=50) split_docs = text_splitter.split_documents(raw_docs) # ===================== 2. 初始化向量检索器[语义相似度检索] ===================== embeddings_model = get_local_embeddings() vectorstore = Chroma.from_documents(documents=split_docs, embedding=embeddings_model) vector_retriever = vectorstore.as_retriever(search_kwargs={"k": 3}) # print("------------------- 向量检索(语义) -------------------------") # vector_docs = vector_retriever.invoke(question) # print(vector_docs) # ===================== 3. 初始化 BM25 关键词检索器 ===================== # BM25:关键词匹配检索,基于词频统计的传统检索算法 bm25_retriever = BM25Retriever.from_documents(split_docs, k=3) # print("------------------- BM25 检索(关键词) -------------------------") # bm25_docs = bm25_retriever.invoke(question) # print(bm25_docs) # ===================== 4. 混合检索(EnsembleRetriever) ===================== # weights=[0.5, 0.5]:BM25权重0.5,向量权重0.5。 # 关键词要求高 → 调大BM25权重;语义模糊 → 调大向量权重。 ensemble_retriever = EnsembleRetriever(retrievers=[bm25_retriever, vector_retriever], weights=[0.5, 0.5]) # print("------------------- 混合检索(融合结果) -------------------------") # ensemble_docs = ensemble_retriever.invoke(question) # print(ensemble_docs) # ===================== 5. 构建提示词模板===================== prompt_template = """ 请根据下面给出的上下文来回答问题: {context} 问题: {question} """ prompt = ChatPromptTemplate.from_template(prompt_template) # ===================== 6. 构建问答链 ===================== # 混合检索问答链 hybrid_chain = ( RunnableMap({"context": lambda x: ensemble_retriever.invoke(x["question"]), "question": lambda x: x["question"]}) | prompt | llm | StrOutputParser() ) # 纯向量检索问答链(对比实验) vector_chain = ( RunnableMap({"context": lambda x: vector_retriever.invoke(x["question"]), "question": lambda x: x["question"]}) | prompt | llm | StrOutputParser() ) # ===================== 7. 执行测试 ===================== if __name__ == '__main__': print("\n------------ 纯向量检索 ------------------------") print(vector_chain.invoke({"question": question})) print("------------ 混合检索(向量+BM25权重0.5) ------------------------") print(hybrid_chain.invoke({"question": question}))
View Code
六. 总结
统一约定:
page_content:明文+向量 双存|metadata:只存明文、不向量化
原理:原文直接切分块,文档原文向量化。
存储:向量库存切分文本。
检索:用户问题向量匹配原文碎片。
优点:最简单、代码最少、通俗易懂。
缺点:原文生硬、用户口语难命中、容易断上下文。
原理:大块做回答、小块做检索,内部自动绑定父子ID。
存储:向量库存子块;内存/MySQL存父块。
检索:子块向量匹配 → 找父ID → 返回完整父文档。
优点:检索精细、回答上下文完整、不割裂。
缺点:占用内存大、存储两份文档。
原理:LLM生成摘要,摘要用来检索,原文用来回答。
存储:向量库存摘要;metadata存原始全文。
检索:问题匹配摘要向量 → 取出metadata原文回答。
优点:摘要简洁、匹配准确率极高、噪声少。
缺点:需要调用LLM生成摘要、耗费token。
原理:LLM为原文生成多条假设问题,用问题建索引。
存储:向量库存假设问题;内存存原始文档,手动绑定uuid。
检索:用户提问匹配假设问题 → 映射原始文档。
优点:贴合人类口语、解决原文晦涩难检索问题。
缺点:生成问题消耗LLM、生成质量不稳定。
原理:大模型自动拆分自然语言:过滤条件+语义查询。
存储:向量库存正文,metadata存标签(作者/年份/评分)。
检索:先元数据过滤、后向量语义检索。
优点:无需手写筛选代码、结构化数据筛选极强。
缺点:如果检索词文档没有,直接返回空数据。
原理:融合向量语义检索与BM25关键词检索,加权融合排序。
存储:向量库存向量;BM25只存明文词频。
检索:两路并行检索 → 加权融合 → 去重重排。
优点:关键词+语义双兼顾、鲁棒性最强、无明显短板。
缺点:权重需要人工调优。
特有规则:
① 同时执行向量+BM25;② 各自返回top-k;③ weights=[0.5,0.5];
④ 融合排序返回最优;⑤ 关键词调高BM25权重,语义模糊调高向量权重。
1、需要手动绑定ID:假设问题RAG
2、自动绑定ID:父子分块RAG
3、不需要LLM入库:普通、混合、元数据
4、必须LLM入库:摘要、假设问题
5、唯一双检索算法:混合检索
!
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。