
目录一、检索器Retrievers检索系统检索器二、使用向量数据库作为检索器基本使用三、使用 chain 创建 “检索器”RAG 案例一、检索器Retrievers检索系统检索系统Information Retrieval SystemIR System是一个为了满足用户信息需求从大规模、非结构化的数据集合中自动、高效地查找、排序并返回相关信息的计算机系统。它的核心任务是在正确的时间以正确的方式将正确的信息传递给正确的人。最常见的例子就是搜索引擎如 Google、百度。随着大型语言模型的流行检索系统已成为人工智能应用例如 RAG的重要组成部分。且存在多种【不同类型】的检索系统包括关系数据库关系数据库是许多应用程序中使用的结构化数据存储的基本类型。数据存储在行记录和列属性中可以通过 SQL结构化查询语言进行高效的查询和作。关系数据库擅长维护数据完整性、支持复杂查询以及处理不同数据实体之间的关系。词法搜索索引许多搜索引擎基于将查询中的单词与每个文档中的单词进行匹配这种方法称为词法检索。即一个单词经常出现在用户的查询和特定文档中那么这个文档可能是一个很好的匹配。这通常使用【倒排索引】实现。向量数据库向量存储不使用字频而是使用【嵌入模型】将文档转换为高维向量表示。这允许使用余弦相似度等数学运算对嵌入向量进行有效的相似性搜索。检索器检索器是检索系统中的一个核心组件它接收来自用户接口的查询Query检索出包含查询关键词的候选文档集合。我们可以使用上面提到的任何检索系统实现方式创建检索器如关系数据库、向量数据库等。由于其重要性和多样性LangChain 提供了一个统一的接口来与不同类型的检索系统进行交互。LangChain 的检索器接口非常简单输入查询字符串输出文档列表标准化的 LangChain 文档对象 Document例如使用关系数据库的检索系统检索器可以将问题转换为 SQL 语句并执行查询最后将查询结果响应用户。该示例将来会在 LangGraph 中讲解在这里帮助理解概念即可流程 用户 Question 交给 LLMLLM 生成 SQL 语句SQL 语句查询数据库结果再次交给 LLM输出 Answer还可以搭配可选的 SQL Agent。下面我们来演示如何使用向量存储作为检索器。二、使用向量数据库作为检索器基本使用向量存储是索引和检索非结构化数据的一种强大而有效的方法。可以通过调用向量数据库的as_retriever 方法将向量存储用作检索器。在这里我们使用 Redis 向量存储。from langchain_openai import OpenAIEmbeddings导入嵌入模型负责文本向向量转换。from langchain_redis import RedisConfig, RedisVectorStore导入 Redis 向量库相关类RedisConfig 用来做配置RedisVectorStore 是向量存储实例。embeddings OpenAIEmbeddings (modeltext‑embedding‑3‑large)初始化嵌入模型对象。redis_url redis://192.168.100.238:6379填写 Redis 服务的连接地址。config RedisConfig构造 Redis 向量库配置对象。index_name指定向量索引名称。redis_urlRedis 连接地址。metadata_schema元数据结构定义声明每个元数据字段的类型tag 代表标签类型numeric 代表数字类型用于后续元数据过滤。vector_store RedisVectorStore (embeddings, configconfig)初始化 Redis 向量存储实例绑定嵌入模型和配置。retriever vector_store.as_retriever ()调用 as_retriever 方法把向量存储转换成检索器对象符合 LangChain 统一 Retriever 接口。docs retriever.invoke (数据库表怎么设计的)调用 invoke 方法执行检索传入查询字符串返回 Document 文档对象列表。for doc in docs循环遍历检索返回的文档结果。print (* * 30)打印分隔符号用来分割不同文档。print (doc.page_content [:30])打印文档内容截取前 30 个字符预览。LangChain 检索器是一个 Runnable 的对象它是 LangChain 组件的标准接口。这意味着它有一些常用方法包括 invoke用于与其交互。默认情况下向量存储检索器使用相似性搜索。输出结果如下输出多条召回文档每条之间用星号分隔。每段只打印文档内容前 30 字符做简短预览。检索器内部完成查询向量化、向量相似度计算直接返回原始文档对象不需要手动调用 similarity_search。as_retriever 方法也支持我们传递相关参数修改搜索结果如search_type设置相似算法包括similarity默认、mmr 或 similarity_score_threshold相似性分数阈值search_kwargsk限制检索器返回的文档 k 数量。vector_store.as_retriever生成检索器。search_kwargs{k: 2}传入搜索参数字典k 等于 2代表最多返回 2 条相关文档。fetch_k要传递给 MMR 算法的文档量。search_typemmr指定检索算法为最大边际相关性算法兼顾相关性和多样性。search_kwargs 字典配置参数k最终返回给用户的文档条数为 2。fetch_k先从向量库取出 10 条候选文档再在 10 条里面做 MMR 筛选输出 2 条。要注意的是Retrievers 检索器虽然是 Runnable 对象但其不提供任何流式处理因为它本身通常是同步的、阻塞的操作。如下所示chunks []定义空列表准备接收返回数据。retriever.stream调用检索器的 stream 流式接口。for chunk in 循环接收返回结果。append 把结果存入列表print 打印输出。调用可以发现它是一次性返回与 invoke 调用无异。三、使用 chain 创建 “检索器”除了使用 as_retriever 方法我们还可以自行创建一个 “检索器”。回想一下检索器的特点LangChain 检索器是一个 Runnable 的对象LangChain 检索器输入为查询字符串输出为文档列表标准化的 LangChain 文档对象 Document综上所述我们可以from langchain_core.runnables import chain导入 chain 装饰器可以把普通函数包装成 Runnable 对象。from typing import List导入 List 类型注解用来做返回值类型声明。from langchain_core.documents import Document导入文档对象类型。chain装饰器把下方普通函数转换成 Runnable就可以使用 invoke、stream 等标准方法。def retriever (query: str) - List [Document]自定义检索函数接收字符串查询返回 Document 列表。return vector_store.similarity_search (query, k2)内部调用向量库相似度搜索返回 2 条文档。docs retriever.invoke (数据库表怎么设计的)使用 invoke 调用自定义的 Runnable 检索器。for doc in docs遍历返回文档列表。print (* * 30)打印分隔线。print (doc.page_content [:30])截取文档前 30 字符打印预览。上面定义了一个函数使用 chain 修饰该修饰可以使其成为 Runnable 函数且满足检索器输入输出的要求。在函数中我们依旧使用向量数据库的相似性搜索方法这样灵活性也更高想要进行元数据筛选也更方便。注意这并不是真正的检索器检索器是一个 Runnable 对象而我们定义的只是一个函数具备其特点罢了。RAG 案例RAG 是当前大语言模型应用的核心模式。当用户向 LLM 提问时系统首先使用嵌入模型在知识库中进行语义搜索找到最相关的内容然后将这些内容和问题一起交给 LLM 来生成答案。这极大地提高了答案的准确性和时效性。由于 LangChain 检索器是一个 Runnable 的对象我们便可以方便的使用链完成相关的调用。下面我们来完成一个最简单的 RAG 案例将会完成根据 Query 搜索最相关的 4 篇文档。将相关的文档转换为字符串以便后续发送给聊天模型。将 Query 与文档字符串发送给聊天模型。聊天模型依据输出解析器格式输出内容。from langchain_openai import OpenAIEmbeddings, ChatOpenAI导入嵌入模型、聊天大模型。from langchain_redis import RedisConfig, RedisVectorStore导入 Redis 向量存储相关配置与类。from langchain_core.output_parsers import StrOutputParser导入字符串输出解析器提取模型文本内容。from langchain_core.prompts import ChatPromptTemplate导入聊天提示词模板。from langchain_core.runnables import RunnablePassthrough导入透传组件原样传递输入参数。model ChatOpenAI (modelgpt‑4o‑mini)初始化聊天大模型实例。embeddings OpenAIEmbeddings (modeltext‑embedding‑3‑large)初始化嵌入模型。redis_urlRedis 服务连接地址。config RedisConfig向量库配置指定索引名、连接地址、元数据字段定义。vector_store RedisVectorStore (embeddings, configconfig)初始化 Redis 向量存储。retriever vector_store.as_retriever ()把向量存储转为检索器对象。prompt ChatPromptTemplate.from_messages构造提示模板模板内部占位符 context 存放检索到的文档question 存放用户问题。def format_docs (docs)工具函数接收文档列表把多篇文档的 page_content 用换行拼接成一段字符串。rag_chain (...)使用 LCEL 语法组装 RAG 完整链路。{context: retriever | format_docs, question: RunnablePassthrough ()}输入进来的查询一路交给检索器做召回再经过 format_docs 拼接成上下文另一路直接透传保留原始问题。| prompt把组装好的 context、question 填入提示词模板。| model交给大模型推理。| StrOutputParser ()解析模型输出拿到纯文本。for chunk in rag_chain.stream (数据库表怎么设计的)以流式方式执行整条 RAG 链逐块拿到返回内容。print (chunk, end||, flushTrue)边接收边打印输出。上述代码中RunnablePassthrough 我们之前还没有见过简单来说RunnablePassthrough 是一个 “伪” Runnable它的主要作用是在链Chain中透明地传递输入数据而不做任何修改。当我们需要将原始输入和另一个处理过程的输出一起传递给下一个步骤时就需要 RunnablePassthrough。就例如代码中我们需要将【Query】与【通过检索出来的文档转换的字符串】同时发送给提示词模板。最终打印结果如下输出里面的 | 是代码中 print(chunk, end||, flushTrue) 设置的分隔标记用来区分每一个流式返回块。内容就是 RAG 根据检索出来的知识库文档针对 “数据库表怎么设计的” 给出的回答。回答包含水平分表优化、状态字段独立建表、关联查询、MySQL MyBatis 存储访问、Redis 缓存提升性能这些要点。流式是一块一块吐出最终答案文本不是一次性全部打印。上面讲过检索器不提供任何流式处理。但这里可以使用流式输出。我们可以把像 RAG 这样的链的执行过程想象成两个阶段阶段一准备阶段同步、阻塞接收输入、检索文档、构建提示阶段二生成阶段可流式调用 LLM流式输出的是最终答案而非中间过程。知识点总结检索本身是同步阻塞不会流式但后面大模型生成阶段支持流式所以整条 RAG 链可以看到流式效果检索文档这个中间步骤不会分片流出。