实现RAG:使用LangChain实现图检索查询

你是不是有时会遇到这样的问题:你可能遇到的任何主题或问题,都有大量的文档,但是当尝试将某些内容应用于自己的用途时,突然发现很难找到所需的内容。
在这篇博文中,我们将看一下LangChain是如何实现RAG的,这样你就可以将相同的原理应用到任何带有LangChain和LLM的应用程序中。

什么是RAG

该术语在当今的技术领域中使用得很多,但它的实际含义是什么呢?以下是来自不同来源的一些定义:

“检索增强生成(RAG)是优化大型语言模型输出的过程,因此在生成响应之前,它会参考训练数据源之外的权威知识库。” — AWS

“检索增强生成(RAG)是一种利用从外部来源获取的事实,提高生成式人工智能模型的准确性和可靠性的技术。” — 英伟达

“检索增强生成(RAG)是一个人工智能框架,通过将模型建立在外部知识来源上,以补充法学硕士对信息的内部表示,从而提高法学硕士生成的响应的质量。” — IBM研究院

在本篇博文中,我们将关注如何编写检索查询来补充或支撑LLM的答案。我们将使用Python和LangChain,一个用于编写与llm交互的生成式AI应用程序的框架。

数据集

首先,让我们快速浏览一下我们的数据集。我们将处理来自EDGAR(电子数据收集、分析和检索系统)数据库的SEC(证券交易委员会)文件。美国证券交易委员会提交的文件是一个信息宝库,包含财务报表、披露信息和其他有关上市公司的重要信息。

the data set

这些数据包含已向SEC提交财务报表(10k、13等)的公司。不同的经理持有这些公司的股票,这些公司属于不同的行业。在财务表格本身中,文本中提到了各种各样的人,我们将文本分解为更小的块,以便向量搜索查询处理。我们将每个文本块放在一个表单中,并创建了一个向量嵌入,该向量嵌入也存储在chunk节点上。当我们运行向量搜索查询时,我们将查询的向量与CHUNK节点的向量进行比较,以找到最相似的文本。 

检索查询示例

我使用了一些资源来帮助我理解如何在LangChain中编写检索查询。第一篇是Tomaz Bratanic的博客文章,他写了一篇关于如何使用维基百科文章数据在LangChain中使用Neo4j矢量索引的文章。第二个是来自GenAI Stack的查询,它是使用Docker构建的演示应用程序的集合,并利用包含技术问题和答案的StackOverflow数据集。

所有查询都包含在下面:

# Tomaz's blog post retrieval query
retrieval_query = """
	OPTIONAL MATCH (node)<-[:EDITED_BY]-(p)
	WITH node, score, collect(p) AS editors
	RETURN node.info AS text,
    	score, 
        node {.*, vector: Null, info: Null, editors: editors} AS metadata
"""

# GenAI Stack retrieval query
retrieval_query="""
	WITH node AS question, score AS similarity
    CALL  { with question
    	MATCH (question)<-[:ANSWERS]-(answer)
		WITH answer
		ORDER BY answer.is_accepted DESC, answer.score DESC
		WITH collect(answer)[..2] as answers
		RETURN reduce(str='', answer IN answers | str + 
			'\n### Answer (Accepted: '+ answer.is_accepted +
			' Score: ' + answer.score+ '): '+  answer.body + '\n') as answerTexts
    }
    RETURN '##Question: ' + question.title + '\n' + question.body + '\n' 
    	+ answerTexts AS text, similarity as score, {source: question.link} AS metadata
    ORDER BY similarity ASC // so that best answers are the last
"""

现在,请注意,这些查询看起来并不完整。我们不会用可选的MATCH或with子句启动Cypher查询。这是因为检索查询被添加到向量搜索查询的末尾。

read_query = (
    "CALL db.index.vector.queryNodes($index, $k, $embedding) "
    "YIELD node, score "
) + retrieval_query

因此,LangChain首先调用db.index.vector.queryNodes()(更多信息见文档)来查找最相似的节点,并传递(YIELD)相似节点和相似度分数,然后将检索查询添加到向量搜索查询的末尾,以提取额外的上下文。了解这一点非常有帮助,特别是当我们构造检索查询时,以及当我们开始测试结果时!

第二件要注意的事情是,这两个查询都返回相同的三个变量:文本、分数和元数据。这是LangChain所期望的,所以如果没有返回,您将得到错误。文本变量包含相关文本,分数是块与搜索文本的相似度分数,元数据可以包含我们想要的上下文的任何其他信息。

构造检索查询

让我们构建检索查询!我们知道相似性搜索查询将返回节点和分数变量,因此我们可以将这些变量传递到检索查询中,以提取这些相似节点的连接数据。我们还必须返回文本、分数和元数据变量。

retrieval_query = """
	WITH node AS doc, score as similarity
	# some more query here
	RETURN <something> as text, similarity as score, 
		{<something>: <something>} AS metadata
"""

好了,这就是我们的骨架。现在我们想要中间的是什么?我们知道我们的数据模型将在相似性搜索中提取CHUNK节点(这些将是上面WITH子句中的节点AS文档值)。文本块不能提供大量上下文,因此我们想要拉入连接到CHUNK节点的Form、Person、Company、Manager和Industry节点。我们还在NEXT关系中包含一系列文本块,因此我们可以将下一个和上一个文本块拉到相似的文本块周围。我们还将提取所有具有相似分数的块,我们希望缩小范围。

retrieval_query = """
    WITH node AS doc, score as similarity
	ORDER BY similarity DESC LIMIT 5
	CALL { WITH doc
		OPTIONAL MATCH (prevDoc:Chunk)-[:NEXT]->(doc)
		OPTIONAL MATCH (doc)-[:NEXT]->(nextDoc:Chunk)
		RETURN prevDoc, doc AS result, nextDoc
	}
	# some more query here
	RETURN coalesce(prevDoc.text,'') + coalesce(document.text,'') + coalesce(nextDoc.text,'') as text,
		similarity as score, 
		{<something>: <something>} AS metadata
"""

现在我们保留5个最相似的块,然后在CALL{}子查询中提取前一个和下一个文本块。我们还更改了RETURN,将前一个、当前和下一个块的文本都连接到文本变量中。coalesce()函数用于处理空值,因此如果没有前一个块或下一个块,它将只返回一个空字符串。

让我们添加更多的上下文来拉入图中的其他相关实体。

retrieval_query = """
    WITH node AS doc, score as similarity
	ORDER BY similarity DESC LIMIT 5
	CALL { WITH doc
		OPTIONAL MATCH (prevDoc:Chunk)-[:NEXT]->(doc)
		OPTIONAL MATCH (doc)-[:NEXT]->(nextDoc:Chunk)
		RETURN prevDoc, doc AS result, nextDoc
    }
    WITH result, prevDoc, nextDoc, similarity
	CALL {
		WITH result
		OPTIONAL MATCH (result)-[:PART_OF]->(:Form)<-[:FILED]-(company:Company), (company)<-[:OWNS_STOCK_IN]-(manager:Manager)
		WITH result, company.name as companyName, apoc.text.join(collect(manager.managerName),';') as managers
		WHERE companyName IS NOT NULL OR managers > ""
		WITH result, companyName, managers
		ORDER BY result.score DESC
		RETURN result as document, result.score as popularity, companyName, managers
    }
	RETURN coalesce(prevDoc.text,'') + coalesce(document.text,'') + coalesce(nextDoc.text,'') as text,
		similarity as score, 
		{documentId: coalesce(document.chunkId,''), company: coalesce(companyName,''), managers: coalesce(managers,''), source: document.source} AS metadata
"""

第二个CALL{}子查询拉入任何相关的Form、Company和Manager节点(如果存在,则为OPTIONAL MATCH)。我们将经理收集到一个列表中,并确保公司名称和经理列表不为空或空。然后我们按分数对结果排序(目前不提供价值,但可以跟踪文档被检索了多少次)。

由于只返回文本、分数和元数据属性,因此我们需要在元数据字典字段中映射这些额外的值(documentId、company和managers)。这意味着要更新最终的RETURN语句以包含这些语句。

总结

本文中,我们了解了RAG是什么,以及在LangChain中检索查询是如何工作的。我们还查看了一些用于Neo4j的Cypher检索查询示例,并构建了我们自己的示例。我们在查询中使用了SEC文件数据集,并了解了如何提取额外的上下文并将其映射到LangChain期望的三个属性。

<think>好的,我现在需要帮助用户了解如何使用LangChain实现RAG检索增强生成)的示例教程。根据用户的问题,我需要整理出一个清晰的步骤指南,并参考提供的引用内容。 首先,用户提到了三个引用来源,都是关于使用Astra DB、LangChainWeaviate实现RAG的。我需要确保回答中正确引用这些资料,并在适当的位置添加引用标识。同时,用户要求回答结构清晰,分步骤解决问题,并保证真实可靠。 接下来,我应该分解RAG实现步骤。通常,RAG包括数据准备、检索、生成三个主要阶段。根据引用内容,可能需要涉及文档加载、文本分割、向量化存储、检索以及与大模型集成生成回答等步骤。 需要确定每个步骤使用的工具库。引用中提到了LangChain用于协调操作,Weaviate或Astra DB作为向量数据库,以及OpenAI的语言模型。因此,在示例代码中需要包含这些组件的使用。 然后,我需要确保代码示例符合用户的要求,即正确使用$$...$$格式行内公式。不过这个问题主要是代码实现,可能不需要数学公式,但如果有相关参数或算法解释,可能需要用LaTeX格式。 另外,用户要求生成相关问题,我需要根据回答内容提出几个相关问题,可能涉及优化方法、性能分析或应用场景等。 现在,整理步骤: 1. 数据准备:加载文档,分割文本。 2. 向量化与存储:使用Embedding模型生成向量,存入向量数据库。 3. 检索流程:构建检索器,获取相关文档。 4. 生成答案:将检索结果输入大模型生成回答。 5. 完整代码示例。 6. 引用标识的正确放置。 7. 生成相关问题。 需要检查每个步骤是否符合引用内容。例如,引用3提到使用LangChain协调操作、OpenAI模型Weaviate数据库,所以在步骤中应体现这些组件。引用12也提到了类似流程,可以综合参考。 在代码部分,需要确保语法正确,使用正确的LangChain模块,比如`RecursiveCharacterTextSplitter`用于文本分割,`Weaviate`或`AstraDB`作为向量存储,以及`RetrievalQA`链来整合检索生成。 可能需要注意的地方是,用户提供的引用中并没有具体的代码片段,但根据常规的LangChain RAG实现,可以构造一个示例代码,并标注引用来源。 最后,生成的相关问题应该围绕实现细节、优化、应用场景等,确保问题与回答内容相关,并帮助用户进一步学习。 现在开始撰写回答,确保结构清晰,分步骤说明,正确引用,并附上代码相关问题。</think>以下是使用LangChain实现RAG检索增强生成)的逐步指南及示例代码: ### 一、RAG实现步骤 1. **数据准备** - 加载文档:使用LangChain的`WebBaseLoader`或`TextLoader`加载本地/网页文档 - 文本分割:采用递归字符分割器处理长文本 ```python from langchain_community.document_loaders import WebBaseLoader from langchain_text_splitters import RecursiveCharacterTextSplitter loader = WebBaseLoader(["https://example.com/doc"]) documents = loader.load() text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200) splits = text_splitter.split_documents(documents) ``` 2. **向量化与存储** - 使用OpenAI的Embedding模型生成文本向量 - 将向量存储至Weaviate或Astra DB等向量数据库[^3] ```python from langchain_openai import OpenAIEmbeddings from langchain_community.vectorstores import Weaviate embeddings = OpenAIEmbeddings() vectorstore = Weaviate.from_documents( splits, embeddings, weaviate_url="http://localhost:8080" ) ``` 3. **检索流程** - 构建检索器,支持相似度搜索与相关性过滤 ```python retriever = vectorstore.as_retriever(search_kwargs={"k": 3}) ``` 4. **生成答案** - 集成大语言模型(如GPT-3.5/4)生成最终回答 ```python from langchain_openai import ChatOpenAI from langchain.chains import RetrievalQA llm = ChatOpenAI(model="gpt-3.5-turbo") qa_chain = RetrievalQA.from_chain_type( llm, retriever=retriever, chain_type_kwargs={"verbose": True} ) response = qa_chain.invoke({"query": "什么是RAG?"}) print(response["result"]) ``` ### 二、完整代码示例 ```python # 环境准备 !pip install langchain-openai weaviate-client # 完整流程实现 from langchain_community.document_loaders import WebBaseLoader from langchain_text_splitters import RecursiveCharacterTextSplitter from langchain_openai import OpenAIEmbeddings, ChatOpenAI from langchain_community.vectorstores import Weaviate from langchain.chains import RetrievalQA # 1.加载并分割文档 loader = WebBaseLoader(["https://example.com/rag-paper"]) docs = loader.load() text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200) splits = text_splitter.split_documents(docs) # 2.创建向量存储 embeddings = OpenAIEmbeddings() vectorstore = Weaviate.from_documents( splits, embeddings, weaviate_url="http://localhost:8080" ) # 3.构建检索增强生成链 llm = ChatOpenAI(temperature=0) qa_chain = RetrievalQA.from_chain_type( llm, retriever=vectorstore.as_retriever(), return_source_documents=True ) # 4.执行查询 query = "请解释RAG的工作原理" result = qa_chain({"query": query}) print(f"答案:{result['result']}\n来源:{result['source_documents'][0].page_content}") ``` ### 三、关键技术解析 1. **向量检索优化** 通过调整`search_kwargs`参数实现: - `k`: 控制返回结果数量 - `score_threshold`: 设置相似度阈值 $$ \text{similarity\_score} \geq \theta $$ 其中$\theta$根据具体场景调整[^2] 2. **混合检索策略** 结合关键词检索与向量检索,提升召回率: ```python from langchain.retrievers import BM25Retriever, EnsembleRetriever bm25_retriever = BM25Retriever.from_documents(splits) ensemble_retriever = EnsembleRetriever( retrievers=[bm25_retriever, vector_retriever], weights=[0.4, 0.6] ) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值