news 2026/10/19 7:32:29

LangChain Retriever实战:六类检索器详解与代码示例

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LangChain Retriever实战:六类检索器详解与代码示例

看到“LangChain Retriver 例子”这个标题,第一反应是:拼写错了。Retriver 应该是 Retriever——但不怪你,这个组件在 RAG 项目里实在太常被提起了,搜出来的例子又碎又旧,很多还停留在 0.0.x 的老 API 上。这篇文章我想换个方式,用一个可能在你项目里真实出现的场景——售后知识库问答——把 LangChain 里值得掌握的 Retriever 例子一个一个跑给你看。

先说清楚这篇文章能解决什么问题:你大概已经知道 RAG 的大致流程——文档加载、切分、向量化、存库、问答时先检索再让大模型生成。但检索这一步具体怎么写,vectorstore.as_retriever() 之后还能不能加东西、什么时候该上 BM25、混合检索、父子文档检索,网上答案很分散。这篇文章会把基础检索、关键词检索、混合召回、父子检索、多查询改写、压缩过滤六类例子全部用代码落地,并且所有例子围绕同一份 demo 数据,方便你直接对比效果。

适合谁看?刚入门 LangChain 想搭一个能跑通的知识库问答的人,已经在做 RAG 但发现召回效果差、不知道怎么排查的人,都可以直接从第三节开始抄代码。如果你只是想搞明白 Retriever 在 LangChain 里到底有多少种玩法,那从头读一遍也不会浪费你时间。

1. Retriever 到底在解决什么问题

1.1 RAG 链路里,Retriever 是什么角色

先把背景对齐一下。RAG 的全称是检索增强生成,典型链路是:把知识库文档切片、向量化,用户提问后,先从库里检索出相关片段,再把片段塞给大模型生成回答。Retriever 就是中间做检索的那个组件,输入一个 query,输出一批候选文档。

我习惯把它比喻成图书管理员:用户问“灯不亮了怎么办”,管理员不会把整座图书馆搬过来,而是根据这句话去书架上挑几本最可能相关的资料。Retriever 就是这个“挑资料”的动作,挑得好不好,直接决定大模型吃进去的素材对不对。

为什么 Retrieval 这么关键?因为大模型本身不知道你私有知识库里的内容,你能给它的上下文窗口又是有限的,几百页的文档不可能全塞进去。检索质量差,后面的生成环节再怎么调 prompt 都没用。我做过几个知识库项目,粗略统计下来,最终回答里的大多数坏 case 都出在“该召回的内容没召回,不该召回的内容混进来了”,真正出在生成模型本身的问题反而没那么多。

1.2 新手最容易误解的一点:Retriever 不等于向量检索

很多人提到 Retriever,第一反应就是“向量相似度检索”,然后写一行:

retriever = vectorstore.as_retriever()

这没错,但只是 Retriever 世界里的一个分支。LangChain 的 Retriever 是一个抽象接口,凡是实现invoke(query)并返回文档列表的东西,都可以叫 Retriever。你可以用向量库实现,用 BM25 实现,也可以把多个 Retriever 组合成一个,甚至自己写一个纯规则逻辑的 Retriever 塞到链路里。

“Retriever”这个名字代表的是“检索这件事”,而不是“某一种检索技术”。这个认知一旦建立,后面看所有例子都不容易晕。

1.3 不同 Retriever 解决的不同痛点

实际项目里,单纯一个向量检索通常不够用。不同 Retriever 对应的是不同短板:

  • 向量检索能理解语义,但精确匹配能力弱。用户问“灯不亮了”,知识库片段里写的是“无法开机”,两者字面完全不同,向量检索能把它们关联起来。
  • BM25 这类关键词检索恰好相反,对“型号、编号、专有名词”非常敏感,但不懂语义变体。
  • 用户提问往往很短、很口语化,同一个问题有一百种问法,单个 query 检索容易漏。
  • 切分后的文档块可能只有一小段,语义不够完整,检索到了但上下文不足。
  • 召回结果里混入大量低相关片段,直接把噪声喂给大模型。

后面讲到的每种进阶 Retriever,本质上都是在给基础检索打补丁。与其遇到问题再临时搜代码,不如先把这些补丁的类型和适用场景搞清楚。

2. Retriever 选型:先搞清楚这些方案差在哪

2.1 关键词召回与语义召回的本质区别

先从源头看这两种召回策略的差异。BM25 是经典的关键词检索算法,核心逻辑是统计词频和文档频率:query 里的词在文档里出现得多、这个词在整个库里又比较稀有,那这篇文档的相关性就高。比如搜型号“M1 电源适配器”,只要文档里这些词真实存在,BM25 就能精准命中。

向量检索则是把文本映射到一个高维向量空间,语义相近的文本向量距离更近。它的优势是能处理“说法不一致但意思相同”的情况。比如用户说“灯光颜色怎么变”,知识库里写的是“色温可在 2700K 到 6500K 之间调节”,“颜色”和“色温”字面上对不上,但语义上高度相关,向量模型可以捕捉到这种关系。

一个更容易理解的类比:BM25 像按身份证号找人,精确但对不上号就完蛋;向量检索像按长相找人,看着眼熟就能找到,但可能把两个长得像的人搞混。所以生产环境里最稳的做法,通常是把两者组合起来。

2.2 五种常见 Retriever 形态一览

下面这张表直接对比我这次会用到的几种 Retriever,后面代码都会覆盖到。

Retriever解决的核心问题适用场景主要代价
VectorStoreRetriever语义召回大多数 RAG 的基线版本对精确型号、编号不敏感
BM25Retriever精确词召回技术文档、型号、术语、代码片段不懂语义变体
EnsembleRetriever兼顾语义与精确匹配生产知识库问答的常见选择多一路召回,排序逻辑复杂一点
ParentDocumentRetriever小块命中准但上下文不够回答需要完整背景说明的场景返回内容较大,索引构建偏慢
MultiQueryRetriever用户问法太杂口语化、短 query 多的场景每次检索都要多次调用大模型
ContextualCompressionRetriever召回结果噪声大对回答精确度要求高的场景链路变长,耗时和费用上升

2.3 项目里怎么选:我建议的优先级

有些朋友一上来就打算把 MultiQuery、压缩检索、重排全部接上,结果代码没跑通,先被延迟和费用搞崩溃了。我给的建议是:先把最基础的 VectorStoreRetriever 跑通,让整个 RAG 链路能回答问题,再根据坏 case 决定要不要加东西。

判断的逻辑很简单:如果发现用户说“灯不亮了”但检索不到“无法开机”这类语义相关片段,说明向量召回有问题,先查 embedding 模型是否匹配中文、chunk 是否切得合理;如果发现型号、编号、专有名词总是召不回,那加 BM25 混合召回;如果召回结果正确但 LLM 回答内容不完整,怀疑是文档块太小,可以考虑 ParentDocumentRetriever;如果召回结果里有一堆无关内容,再加压缩过滤。MultiQuery 属于锦上添花,适合 query 口语化严重的场景,放在性能有余量时再做。

3. 基础 Retriever 例子:三行代码跑通检索

3.1 准备示例数据和运行环境

为了让你拿过去就能跑,我这里用内存中的一组Document作为知识库,模拟一个智能灯具产品的售后知识库。内容包含快速开始、故障排查、功能说明三类,故意让片段之间有一些语义关联,方便观察不同 Retriever 的差异。

pip install langchain langchain-community langchain-openai faiss-cpu jieba

代码里会用到 OpenAI 的 Embedding 和 Chat 模型,你需要设置OPENAI_API_KEY环境变量。如果你暂时没有商用 API 的 key,也可以换成 HuggingFace 的本地 embedding 模型,后面代码里我会给出替换方式。

from langchain_core.documents import Document docs = [ Document( page_content="妙控智能灯支持语音控制和App远程控制。首次使用前请在应用商店下载妙控App并完成设备配网,配网时需确保智能灯处于通电状态且手机与设备连接同一个家庭Wi-Fi网络。", metadata={"source": "quick_start.md", "type": "说明书"}, ), Document( page_content="故障排查:如果智能灯无法开机,请先检查电源适配器是否连接正常,再尝试更换电源插座;如果仍无法开机,可能是电源模块损坏,请联系售后处理。", metadata={"source": "troubleshooting.md", "type": "说明书"}, ), Document( page_content="语音唤醒无响应时,先确认麦克风孔没有被遮挡,再检查设备是否处于静音模式;如果语音指令经常识别错误,可以在App内重新录制唤醒词。", metadata={"source": "troubleshooting.md", "type": "说明书"}, ), Document( page_content="场景联动功能允许将智能灯与其他智能设备关联,比如设置'回家模式'后,门锁打开时灯光自动亮起。定时任务支持按周重复,最大可设置20组定时规则。", metadata={"source": "features.md", "type": "功能说明"}, ), Document( page_content="色温调节范围在2700K到6500K之间,亮度调节范围是1%到100%。在App首页点击灯光卡片进入控制页,拖动亮度条和色温条即可实时调节。", metadata={"source": "features.md", "type": "功能说明"}, ), ] for i, doc in enumerate(docs): doc.metadata["id"] = f"d{i}"

这份数据有一个好处:每个片段都对应一类典型问题。比如“无法开机”对应 d1,“语音唤醒没反应”对应 d2,“定时任务”对应 d3,“色温亮度调节”对应 d4。后面构造评测集时可以直接拿来做期望答案。

初始化 embedding 和向量库:

from langchain_openai import OpenAIEmbeddings, ChatOpenAI # 方式一:OpenAI 的 embedding embeddings = OpenAIEmbeddings(model="text-embedding-ada-002") # 方式二:如果不想用云端 API,也可以换本地开源中文 embedding # from langchain_community.embeddings import HuggingFaceEmbeddings # embeddings = HuggingFaceEmbeddings( # model_name="BAAI/bge-small-zh-v1.5", # encode_kwargs={"normalize_embeddings": True}, # ) llm = ChatOpenAI(model="gpt-4o-mini", temperature=0) from langchain_community.vectorstores import FAISS vectorstore = FAISS.from_documents(docs, embeddings)

这里用 FAISS 是因为它简单、内存操作、适合 demo。真实项目如果要持久化,可以换支持文件落地的向量库,但基础 Retriever 的用法是一样的。

3.2 VectorStoreRetriever:最基础的向量检索

从向量库直接转出来的 Retriever 是最常用的:

retriever = vectorstore.as_retriever( search_type="similarity", search_kwargs={"k": 3}, ) for doc in retriever.invoke("灯不亮了怎么办"): print(doc.metadata["id"], doc.page_content[:50])

as_retriever()的本质是把“向量库查相似向量”包装成一个符合 LangChain 接口的 Retriever。search_type 有三种常用值:

  • similarity:按向量相似度取前 k 个,最常用。
  • similarity_score_threshold:在相似度基础上加一个分数阈值,低于阈值的直接过滤掉。
  • mmr:最大边际相关性,在“相关”和“多样”之间做平衡,避免召回的片段全是同一个意思。

mmr 在知识库由同一份文档切出很多块时特别有用。比如一份说明书里连续几段都在讲“安装步骤”,用普通相似度检索,前 3 个结果可能是内容高度重复的碎片。换成 mmr 后,会尽量让结果之间不太雷同,这样喂给大模型的上下文信息量更大。

retriever_mmr = vectorstore.as_retriever( search_type="mmr", search_kwargs={"k": 3, "fetch_k": 10, "lambda_mult": 0.7}, )

lambda_mult越大越保守,结果更贴近相关性;越小越强调多样性,我一般从 0.7 开始调。

Retriever 的好处是它能直接塞进 LangChain 的 LCEL 表达式里,作为链路的一环。下面这个就是最标准的 RAG 问答链路:

from langchain_core.output_parsers import StrOutputParser from langchain_core.prompts import ChatPromptTemplate from langchain_core.runnables import RunnablePassthrough prompt = ChatPromptTemplate.from_template( "你是售后助手。请根据以下资料回答问题。\n资料:{context}\n问题:{question}\n回答:" ) rag_chain = ( {"context": retriever, "question": RunnablePassthrough()} | prompt | llm | StrOutputParser() ) print(rag_chain.invoke("灯不亮了怎么办"))

这里有个细节值得注意:{"context": retriever}表示把传入的 question 自动交给 retriever 做检索,结果作为 context 传给 prompt。Retriever 之所以能这样直接用,是因为它实现了 Runnable 接口,有统一的invoke()方法。

3.3 BM25Retriever:关键词检索,中文先分词再跑

BM25Retriever 的 API 同样简单,但有一个中文场景的大坑:默认的预处理没有分词,对英文句子可以按空格切词,对中文会把整句话当成一个“词”,匹配效果几乎等于零。

from langchain_community.retrievers import BM25Retriever import jieba bm25_retriever = BM25Retriever.from_documents( docs, k=3, preprocess_func=lambda text: " ".join(jieba.cut(text)), ) for doc in bm25_retriever.invoke("定时自动开灯"): print(doc.metadata["id"], doc.page_content[:50])

preprocess_func会在文档入库和 query 检索时同时生效,保证两边用同一种分词方式。如果你处理的是英文内容,这个参数可以省略;但在中文项目里,这一步几乎是必须的。

BM25 的价值主要体现在精确词召回上。比如 query 是“色温”,知识库文档里恰好有“色温调节范围在 2700K 到 6500K”,BM25 对这种字面匹配非常稳。向量检索有时反而会把“色温”和“温度”这类词搞混,BM25 不会。

3.4 EnsembleRetriever:混合召回的正确打开方式

既然向量检索和 BM25 各有优势,项目里最自然的想法就是两个都要。LangChain 提供了 EnsembleRetriever 做混合召回:

from langchain.retrievers import EnsembleRetriever ensemble_retriever = EnsembleRetriever( retrievers=[bm25_retriever, retriever], weights=[0.3, 0.7], ) for doc in ensemble_retriever.invoke("灯不亮了怎么办"): print(doc.metadata["id"], doc.page_content[:60])

权重分配是个经验活,没有标准答案。如果领域术语多,BM25 权重可以提到 0.4~0.5;如果用户 query 普遍口语化、长尾表达多,向量权重可以高一些。我通常的做法是设成 0.3/0.7 起步,拿真实 query 跑一轮,看召回的坏 case 偏向哪边再调。

EnsembleRetriever 内部会对各路召回结果做分数归一化再按权重融合,然后重新排序,所以最终返回的是一个合并去重后的排序列表。前三种 Retriever 是生产环境里最常用的组合,熟练了就能解决大多数基础问题。

4. 进阶 Retriever 例子:让检索更聪明

4.1 ParentDocumentRetriever 父子文档检索

做 RAG 的时候有一个两难:文档块切小了,向量检索命中很精准,但一个片段只有一两句话,语义上下文不够,大模型读完还是答不全;文档块切大了,上下文是够了,但向量检索命中变得粗糙,还把一堆不相关的内容一起带进来。

ParentDocumentRetriever 的思路是:把同一个文档按大小两种粒度切。小的子块用来做向量检索,保证命中准确;检索到子块后,再找到它对应的父块,把父块返回给大模型作为上下文。一句话概括:用子块做索引,用父块做上下文。

from langchain.retrievers import ParentDocumentRetriever from langchain.storage import InMemoryStore from langchain_text_splitters import RecursiveCharacterTextSplitter parent_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100) child_splitter = RecursiveCharacterTextSplitter(chunk_size=250, chunk_overlap=30) vectorstore_for_parent = FAISS(embedding_function=embeddings) docstore = InMemoryStore() parent_retriever = ParentDocumentRetriever( vectorstore=vectorstore_for_parent, docstore=docstore, child_splitter=child_splitter, parent_splitter=parent_splitter, ) parent_retriever.add_documents(docs) for doc in parent_retriever.invoke("语音唤醒没反应"): print(doc.metadata["id"], len(doc.page_content), doc.page_content[:50])

需要注意几个点:docstore存的是父块内容,这里用的是内存版,重启会丢失;如果要持久化,可以换具体的存储实现。加了 parent splitter 后,结果会变成更大的块,LLM 输入 token 会相应增加,所以使用 ParentDocumentRetriever 时 k 值可以适当调小一点,不然一次检索回来好几大段文本,上下文很容易浪费。

4.2 MultiQueryRetriever 多查询改写

用户提问很多时候只说半句话,比如“自动开灯怎么弄”,知识库里可能有“场景联动”“定时任务”“回家模式”好几处相关描述,单个 query 向量检索只能命中其中一部分。MultiQueryRetriever 的做法是:调用大模型把原始 query 改写成多个不同角度的查询,然后分别去检索,最后合并结果。

from langchain.retrievers import MultiQueryRetriever multi_query_retriever = MultiQueryRetriever.from_llm( retriever=retriever, llm=llm, ) for doc in multi_query_retriever.invoke("自动开灯怎么弄"): print(doc.metadata["id"], doc.page_content[:50])

可以打印一下它生成的 queries 看看效果:

from langchain.retrievers.multi_query import _unique_documents from langchain_core.documents import Document

不过更简单的办法是调低 llm 的 temperature,避免改写出来的 query 太离谱。MultiQueryRetriever 的本质是用算力换召回率,每次检索都可能消耗多次 LLM 调用,所以它不适合所有请求都走这条路。我的建议是把它用在对召回要求高的模块里,或者在离线场景下用。

4.3 ContextualCompressionRetriever 压缩与过滤

当知识库变大、召回结果里出现大量低相关片段时,与其把 top 10 全部塞给 LLM,不如先加一步压缩。ContextualCompressionRetriever 的作用是在基础检索之上套一个 compressor:对每个召回的文档做一次判断或压缩,保留与问题真正相关的内容,丢掉无关段落。

from langchain.retrievers import ContextualCompressionRetriever from langchain.retrievers.document_compressors import LLMChainExtractor compressor = LLMChainExtractor.from_llm(llm) compression_retriever = ContextualCompressionRetriever( base_compressor=compressor, base_retriever=retriever, ) for doc in compression_retriever.invoke("色温怎么调"): print(doc.metadata["id"], doc.page_content[:100])

LLMChainExtractor 会从每个文档里抽取和问题相关的句子,相当于把原文“瘦身”一遍。如果只是想去掉完全无关的文档、不想做抽取,可以用 LLMChainFilter,速度更快。使用压缩检索最大的代价是延迟和费用,因为每个文档都要过一次 LLM,所以我不建议把它用在响应速度要求高的在线服务上,更适合做离线知识加工或精度优先的场景。

5. 给 Retriever 做体检:评测与调参

5.1 先建一个最小的评测集

很多新手只关心代码能不能跑,却忽略了“有没有比之前更好”这件事。我强烈建议在做 RAG 的第一天就建一个很小的测试集:几条 query + 每条对应的期望文档 id。不需要多,5 到 10 条就够给模型做快速体检。

test_set = [ ("灯不亮了怎么办", ["d1"]), ("语音唤醒没有反应", ["d2"]), ("能设置每天自动开灯吗", ["d3"]), ("亮度颜色怎么调", ["d4"]), ("如何配网连接设备", ["d0"]), ]

构造测试集的时候,尽量覆盖你产品里真实的高频提问,不要只选那些刚好能被检索到的。把用户最容易问、最容易答错的问题放进去,才有参考价值。

5.2 用 Recall@k 和 MRR 对比不同 Retriever

我常用两个指标,一个是召回率,一个是排序质量:

def quick_evaluate(retriever, test_set, k=3): hit = 0 rr_sum = 0.0 for query, gold_ids in test_set: retrieved = retriever.invoke(query)[:k] retrieved_ids = [doc.metadata.get("id") for doc in retrieved] if any(gid in retrieved_ids for gid in gold_ids): hit += 1 for rank, rid in enumerate(retrieved_ids, start=1): if rid in gold_ids: rr_sum += 1.0 / rank break return { "recall@k": hit / len(test_set), "mrr": rr_sum / len(test_set), }

Recall@k看的是“正确答案有没有出现在前 k 个结果里”,MRR看的是“第一个正确答案排得有多靠前”。这两个指标是 RAG 召回的基本功,比只看单个 query 的直观感受客观很多。

分别跑一下:

for name, r in [ ("vector", retriever), ("bm25", bm25_retriever), ("ensemble", ensemble_retriever), ]: print(name, quick_evaluate(r, test_set, k=3))

在我这份 demo 数据上跑出来的趋势一般是:向量检索对“灯不亮了”这类语义相关 query 表现好,BM25 对“定时”“色温”这类精确词表现好,混合检索整体最稳。具体数值会因 embedding 模型和文本切分方式而异,重点看相对差异。

真实的项目环境里,每次调完 chunk 参数或者换一个 embedding 模型,都建议把这条评测脚本重新跑一遍,用数据说话,别靠感觉。

5.3 调参笔记:k、score_threshold、chunk_size

Retriever 的调参,最重要的是下面几个参数:

参数典型值我的调参思路
k3~5 起步先看坏 case 是否在 top k 之外,如果正确答案根本不在,调大 k 也没用,先查检索质量
score_threshold视分布而定先打印一批真实 query 的分数分布,再设置阈值,不要盲设
chunk_size中文 200~600 字符按文档类型调整,参数型内容可以更小,背景介绍型可以更大
chunk_overlap50~100太小会切断关键句,太大会造成中间内容被重复索引
fetch_k / lambda_multfetch_k=10,lambda_mult=0.7mmr 时使用,按结果多样性需求调整

关于 score_threshold,我想多说一句。不同 embedding 模型产出的相似度分数范围完全不同,OpenAI ada-002 的分数和 bge 模型的分数不可直接比较。正确做法是拿真实 query 跑一次,把每个候选文档的相似度分数打出来,观察相关文档和不相关文档的分数分布,然后选一个能把大部分相关文档留住的分界线。

chunk_size 在中文项目里尤其值得调。网上默认配置经常是按英文文本的换行习惯设计的,中文段落没有明显的空格分词,如果直接拿默认 separators 去切,可能把“如果不……”和“请检查……”这类完整语义切断。我习惯在 RecursiveCharacterTextSplitter 的 separators 里加入中文标点:

text_splitter = RecursiveCharacterTextSplitter( separators=["\n\n", "\n", "。", "!", "?", ";", ","], chunk_size=500, chunk_overlap=80, )

这个细节对中文知识库的检索提升立竿见影,值得在项目初始化时就配好。

6. 常见问题与排查清单

6.1 检索结果为空,或者答非所问

遇到回答质量差,很多人第一反应是调 prompt、换大模型,但我建议反过来:先把 Retriever 单独调出来看原始结果。用一个最简单的方法:

for doc in retriever.invoke("你的问题"): print(doc.metadata, doc.page_content[:100])

如果这里召回的片段就跟问题无关,那问题就出在检索链路,不在生成环节。按这个顺序排查:

  1. 确认召回是否为空:如果为空,检查向量库是否建成功、query 是否为空、score_threshold 是否设得太高。
  2. 确认召回内容是否相关:不相关时,先看 embedding 模型是否匹配语言、文档切分是否把关键内容切碎。
  3. 确认召回内容是否有上下文:内容相关但太短,考虑 ParentDocumentRetriever 或调大 chunk_size。

一个小技巧:把召回结果里的 metadata 保留下来,下游回答时可以作为来源引用。这不仅能提升用户信任,排查问题时也能快速定位是哪篇文档出了问题。

6.2 中文环境检索效果差

如果你已经跑通流程,但中文检索效果明显拉胯,八成是下面这几个原因:

现象原因解决方案
BM25 几乎召不回内容中文没有分词,整句被当成一个 token用 jieba 分词,通过 preprocess_func 传给 BM25Retriever
向量检索把无关片段排前面embedding 模型是英文模型,中文语义空间不佳换国产开源中文 embedding 模型
切分后语义断裂,关键句只剩一半默认 separators 不适合中文在 separators 中加入中文标点
短 query 检索不稳定单个 query 信息量太少考虑 MultiQueryRetriever 做查询改写

中文 context 的版本还有一个容易忽略的点:很多开源 embedding 模型在输入时对文本长度有限制,超过长度会截断,导致语义信息丢失。如果切出来的 chunk 偏长,embedding 前最好还是控制一下长度,常见的做法是设置一个 max_length 参数,或者用支持长文本的 embedding 模型。

6.3 引入 LLM 后链路变慢变贵

MultiQueryRetriever 和 ContextualCompressionRetriever 都会在检索过程中多次调用大模型,优点是召回质量提升,代价是延迟和费用立刻上去了。如果你发现链路变得不可用,先别急着全部回退,可以按需启用:让链路默认只走向量 + BM25 混合检索,遇到需要深度分析的 query 或者置信度低的 query,再走进阶链路。

另一个思路是加缓存。相同或相似的 query 可以在 redis 或内存里缓存检索结果,能省掉大量重复计算。召回结果不像大模型生成结果那样变化多,缓存命中率通常不低。

6.4 LangChain 版本变化大,老代码跑不起来

这应该是所有 LangChain 新手最头疼的问题。网上教程经常有几个月前还能跑、现在直接 ImportError 的情况。核心原因:LangChain 在 0.1 之后把社区维护的组件拆到了独立的包里。

几个我实际踩过的坑:

  • langchain.vectorstores里的 FAISS 已经迁移到langchain_community.vectorstores,要装langchain-community。
  • LLM 和 Embedding 的 OpenAI 封装在langchain-openai,不再从langchain.llms导入。
  • 老的get_relevant_documents()方法已经被标记为过时,新版用invoke()。
  • FAISS 的load_local在新版本里需要显式传allow_dangerous_deserialization=True,这是因为 pickle 反序列化存在安全风险,官方要求显式确认。

遇到这种问题,我通常先把当前环境的包版本固定下来,用pip freeze导出一份 requirements,然后去对应版本的官方文档查 API。存疑的接口可以在 Python 里直接help()看签名,很多时候比搜索引擎上的老文章靠谱。

版本变动多确实是 LangChain 的常态,但 Retriever 背后的核心问题是一套固定思路:语义召回、精确召回、混合、上下文补全、噪声过滤。先把问题想清楚,再去看当前版本的 API,就不会被版本变化带偏了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 7:32:21

AI漫剧制作全流程:角色一致性与工作流工程实战拆解

【全66集】绝对是B站最全的AI漫剧零基础全套教程(含剧本脚本视频剪辑角色一致性所有干货)七天逼自己学完,全程通俗易懂,小白从入门到精通看这一套就够了如果你最近刷B站,大概率见过这样的标题:“零基础AI漫…

作者头像 李华
网站建设 2026/10/19 7:30:46

diagram-design:从草图到系统,如何画出一张可评审的架构图

1. 从一张草图到一套系统:diagram-design 到底在解决什么问题第一次看到 “diagram-design” 这个标题,很多人会以为它只是教你怎么画流程图、时序图或者架构图。但真正做过技术方案、写过设计文档、给团队做过架构评审的人都知道,画图这件事…

作者头像 李华
网站建设 2026/10/19 7:30:25

NSGA-II核心机制解析:Pareto前沿、非支配排序与拥挤距离

简介:本资源是一套完整的NSGA-II多目标优化算法Matlab实现代码包,面向高校学生、科研人员及工程优化从业者,适用于学习遗传算法原理、开展多目标决策建模或解决实际工程优化问题(如结构设计、参数调优、资源分配等)。压…

作者头像 李华
网站建设 2026/10/19 7:29:28

指针式仪表OCR识别:基于DeepLabV3+语义分割的工业级解决方案

简介:本资源是一份面向工业智能化巡检场景的Python图像识别系统设计论文,适用于自动化、仪器仪表、计算机视觉方向的本科生、研究生及现场工程师,解决指针式仪表人工读数效率低、无人巡检落地难的核心痛点。全文基于深度学习与数字图像处理技…

作者头像 李华
网站建设 2026/10/19 7:26:22

Java对接OPC Server:JOpc与JeasyOpc选型及避坑指南

简介:一份完整的Java工业通信学习包,面向需要在Java环境中对接OPC Server的开发者,解决Java原生不支持OPC协议、必须借助第三方库的痛点。资料适用于工业自动化、电力监控、智能制造等场景,适合系统集成商、MES开发人员以及有Java…

作者头像 李华
网站建设 2026/10/19 7:26:21

大模型学习路线图:从Transformer原理到RAG与Agent落地实践

去年年底开始,陆续有朋友问我同一个问题:大模型相关的岗位到底怎么准备,才能拿到高薪。问的人多了,我发现一个共性——大家不缺资料,缺的是把资料串成体系的那根线。所以我把这一年多来带人和自己做项目的经验&#xf…

作者头像 李华