LangChain文本向量与检索器实践

LangChain文本向量与检索器实践
让大模型真正懂你的数据LangChain 文本向量、检索器与 RAG 实践大语言模型擅长理解和生成语言却不会自动知道企业内部的制度、项目文档或昨天刚更新的数据。要让模型回答这些问题关键不是继续堆叠提示词而是建立一条稳定的数据通路把文档变成可比较的向量存入适合相似度检索的存储再把检索结果交给模型生成答案。LangChain 将这条通路拆成了几个清晰的组件嵌入模型负责把文本转换为向量向量存储负责索引和搜索检索器负责提供统一的查询接口最后由链把上下文和问题组装后交给聊天模型。理解这些组件的边界才能把 RAG 应用从“能跑”做成“可维护、可扩展”。一、文本为什么要变成向量计算机可以高效处理数字却不能直接根据一段文字的含义判断两篇文档是否相近。嵌入Embedding做的事情就是把单词、句子、商品、用户或图片转换为一个固定长度的数字列表并尽量保留它们的语义关系。例如一段文本经过模型处理后可能得到这样的结果[0.023, 0.487, -0.129, ..., 0.325]这个列表的长度叫向量维度。维度越高通常能表达更细的语义差异但计算、存储和索引成本也会增加。向量可以看作高维空间中的一个点文本含义相近时它们在这个空间里往往更接近。常见的相似度度量包括欧氏距离两点之间的直线距离距离越小通常越相似。余弦相似度比较两个向量的方向弱化文本长度带来的影响。语义检索中更常用这种方式因为它更关注“表达的含义是否一致”。向量检索解决的是传统数据库不擅长的问题。SQL 的等值查询适合查找明确的字段值而向量检索可以找到“关于数据库分表的内容”即使原文没有出现完全相同的关键词。二、嵌入模型的职责与调用方式嵌入模型是表示型模型目标是生成语义表示而不是直接写出一段回答。聊天模型负责“生成”嵌入模型负责“表示”二者承担的任务不同不能互相替代。LangChain 为不同模型提供方提供了独立的集成包。以 OpenAI 为例pipinstall-Ulangchain-openai初始化模型时密钥应放在环境变量中importosfromlangchain_openaiimportOpenAIEmbeddings os.environ[OPENAI_API_KEY]your-api-keyembeddingsOpenAIEmbeddings(modeltext-embedding-3-large)基础嵌入接口有两个重要方法documents_vectorembeddings.embed_documents([向量数据库用于语义检索,检索器把查询转换为文档列表,])query_vectorembeddings.embed_query(如何做语义检索)embed_documents接收多个文档返回二维列表适合离线建立索引embed_query接收一个查询字符串返回一维向量适合用户提问时实时调用。模型提供方可能对文档和查询使用不同的预处理策略因此这两个接口不应混用。三、从原始文档到可搜索索引一个可复用的索引流程通常是加载文档、切分文本、生成嵌入、写入向量存储。fromlangchain_community.document_loadersimportUnstructuredMarkdownLoaderfromlangchain_text_splittersimportCharacterTextSplitterfromlangchain_openaiimportOpenAIEmbeddings loaderUnstructuredMarkdownLoader(docs/knowledge.md)raw_documentsloader.load()splitterCharacterTextSplitter.from_tiktoken_encoder(encoding_namecl100k_base,chunk_size500,chunk_overlap80,)documentssplitter.split_documents(raw_documents)embeddingsOpenAIEmbeddings(modeltext-embedding-3-large)texts[doc.page_contentfordocindocuments]vectorsembeddings.embed_documents(texts)print(len(documents),len(vectors[0]))切分参数直接影响检索质量。块太大检索结果会夹带大量无关内容增加上下文成本块太小语义可能被截断。适度重叠可以减少关键信息刚好落在边界两侧的情况。代码、表格和标题明显的文档还可以使用针对结构的分割器尽量保持一个逻辑单元的完整性。索引是离线、批量的准备过程。生产系统通常会在文档新增或更新时增量处理而不是每次用户提问都重新计算全部向量。四、向量存储把向量变成可用的数据服务手动保存向量并逐个计算距离很快就会遇到性能和管理问题。向量存储一般会提供专用索引使用近似最近邻等算法缩小候选范围在规模和精度之间取得平衡。高效计算利用底层向量库、SIMD 或 GPU 并行执行相似度计算。数据管理支持新增、获取、删除、持久化、分布式扩展和元数据过滤。LangChain 把不同后端统一成向量存储接口。开发阶段可以先使用内存存储验证链路fromlangchain_core.vectorstoresimportInMemoryVectorStore vector_storeInMemoryVectorStore(embeddingembeddings)idsvector_store.add_documents(documents)foundvector_store.get_by_ids(ids[:2])vector_store.delete(idsids[:1])内存存储适合测试和小规模实验进程重启后数据会消失。需要持久化或多实例部署时可以接入 Redis、Pinecone、Chroma、Qdrant、Milvus 等后端。选择后端时应同时考虑数据规模、延迟、过滤需求、运维方式和成本而不是只看向量检索速度。元数据决定检索是否可控每个 LangChainDocument至少包含文本内容和可选的元数据fromlangchain_core.documentsimportDocument docDocument(page_content向量检索根据语义返回相关文档,metadata{source:handbook.md,category:engineering,version:3},)元数据可以先于向量相似度做过滤例如只搜索某个产品、某个版本或某个时间范围的资料。它能缩小候选集也能避免把不同业务域的内容混在一起。使用 Redis 等后端时应在初始化配置中声明字段类型例如标签字段用tag数值字段用numeric否则后续过滤可能无法建立正确的索引。五、相似性搜索与 MMR最基本的搜索方式是similarity_search把查询嵌入成向量在向量库中找到最相近的k个文档。docsvector_store.similarity_search(query如何设计数据库表,k4,)需要调试召回质量时可以同时返回分数scored_docsvector_store.similarity_search_with_score(query如何设计数据库表,k4,)fordoc,scoreinscored_docs:print(score,doc.metadata)不同后端的分数定义可能不同必须先确认“分数越高越相似”还是“分数越低越相似”不能直接跨数据库比较阈值。单纯按相似度取前几名可能得到内容高度重复的片段。最大边际相关性MMR会先取一个较大的候选集再兼顾查询相关性和结果之间的差异性docsvector_store.max_marginal_relevance_search(query如何提升服务性能,k4,fetch_k16,)fetch_k是第一阶段候选池大小k是最终返回数量。候选池太小MMR 没有足够空间做多样化候选池太大则会增加计算量。MMR 特别适合摘要、推荐和 RAG因为它能减少上下文重复让模型看到更多互补信息。六、检索器统一不同数据源的查询入口信息检索系统的目标是从大规模数据中快速找到与用户需求相关的内容。关系数据库擅长结构化条件和关联查询倒排索引擅长词法匹配向量数据库擅长语义相似度。它们都可以被包装成检索器。LangChain 检索器的契约很简单输入查询字符串输出标准化的Document列表。向量存储可以直接转换为检索器retrievervector_store.as_retriever(search_typesimilarity,search_kwargs{k:4},)docsretriever.invoke(如何设计数据库表)还可以切换搜索策略retrievervector_store.as_retriever(search_typemmr,search_kwargs{k:4,fetch_k:16},)检索器本身是一个Runnable因此可以使用invoke也能参与表达式组合。不过检索通常是同步、阻塞的retriever.stream()不会把检索过程拆成逐块输出真正可流式传输的通常是后续聊天模型生成的答案。当内置检索器不够灵活时也可以用chain把自定义查询逻辑包装成相同的输入输出形态fromtypingimportListfromlangchain_core.documentsimportDocumentfromlangchain_core.runnablesimportchainchaindefcustom_retriever(query:str)-List[Document]:returnvector_store.similarity_search(query,k4)这样可以把元数据筛选、权限判断或多路召回写进函数同时继续复用 LangChain 的链式组合能力。七、把检索器接入 RAGRAG 的基本流程可以概括为用查询生成向量并召回相关文档。把文档内容拼接成上下文。将原始问题和上下文一起交给聊天模型。通过输出解析器得到最终答案。下面是一条完整但精简的 LCEL 链fromlangchain_openaiimportChatOpenAIfromlangchain_core.output_parsersimportStrOutputParserfromlangchain_core.promptsimportChatPromptTemplatefromlangchain_core.runnablesimportRunnablePassthrough modelChatOpenAI(modelgpt-4o-mini)promptChatPromptTemplate.from_messages([(human, 你是一个严谨的问答助手。只根据上下文回答问题上下文没有答案时直接说不知道。 问题{question} 上下文{context} 回答 ),])defformat_docs(docs):return\n\n.join(doc.page_contentfordocindocs)rag_chain({context:retriever|format_docs,question:RunnablePassthrough(),}|prompt|model|StrOutputParser())forchunkinrag_chain.stream(如何设计数据库表):print(chunk,end,flushTrue)RunnablePassthrough会把原始问题原样传给提示词模板同时让另一条分支负责检索和格式化上下文。这样问题与上下文可以在同一个模板中汇合。需要注意检索阶段仍然是准备工作最终看到的流式输出来自模型生成阶段而不是检索器本身。八、让结果稳定的工程要点保证维度一致。创建向量索引时的维度必须与嵌入模型输出维度一致更换模型时通常需要重建索引不能把不同维度或不同语义空间的向量混在一起。固定模型与版本。文档索引和在线查询必须使用同一套嵌入模型及兼容配置。模型升级后应通过离线评测确认召回质量再决定是否迁移全部数据。把切分当成数据建模。先确定文档的逻辑结构再选择块大小、重叠长度和分割器。对标题、代码、表格进行特殊处理往往比盲目增大k更有效。让元数据可用。来源、租户、权限、版本、更新时间等信息应在入库时写入并在检索前过滤。多租户系统尤其不能只依赖向量相似度来隔离数据。区分召回和生成。检索器返回的是证据片段不是最终答案。提示词要明确要求模型只使用给定上下文并在证据不足时拒答避免把“相似”误当成“事实”。用评测而不是感觉调参。记录问题、召回文档、相似度分数和最终回答分别评估召回率、上下文相关性、答案准确性和延迟。k、fetch_k、过滤条件和切分参数都应通过一组固定问题对比验证。结语文本向量把“含义”变成了可以计算的坐标向量存储把这些坐标组织成可扩展的索引检索器则把不同后端统一成简单的查询接口。再借助 LCEL将检索结果与原始问题交给聊天模型便形成了一条清晰的 RAG 数据通路。真正可靠的应用并不取决于某个单独组件而取决于整条链路是否闭环数据切分合理、嵌入空间稳定、检索结果相关且多样、元数据过滤正确最后让模型在有证据的范围内生成答案。掌握这套组件化思路后无论后端使用内存存储、Redis 还是托管向量数据库都可以在同一个编程模型下逐步演进。

最新新闻

日新闻

周新闻

月新闻