news 2026/10/21 10:00:10

向量检索结果重复:多取几条为什么反而更差

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
向量检索结果重复:多取几条为什么反而更差

版权与内容来源声明
本文为原创整理。文中涉及官方文档、开源仓库、论文与公开报道的内容,均在附表 A 中标注来源;引用官方原文保持原样,不作改写。文中命令、版本号与界面截图以本文成文时的实测/核验结果为准,标注「待验证」的部分请以你本地环境实际输出为判断依据。本文不推荐任何不合规的软件获取方式,也不对任何收益结果作承诺。转载请注明出处。

一、「多取几条」是第一反应,也是最容易踩空的一步

1.1 现场长什么样

一个常见的现场:知识库里同一套报销流程有三个版本,2024 年的旧流程、2025 年补了「票据留存」的版本、还有某位同事按自己理解重写的一版。用户问「报销单据怎么提交和审批」,模型答得四平八稳,唯独没提「差旅报销要七个工作日内提单」——而用户恰恰是出差回来要报销的那位。

第一反应几乎都是同一个动作:把 Top-K 调大。k=4 改成 k=8,不够再改 12。这个动作看起来成本为零,不动模型、不动提示词、不动知识库,只改一个数字。

实测下来,这个动作的收益比想象中窄得多,副作用却有两种很难察觉的形态:一是重复内容持续挤占上下文,二是你以为取到了 4 条,代码其实只给了你 2 条,而且不报错。这篇就用本机实测的数字说明「多取几条」买到了什么、又付出了什么。

1.2 为什么自己造一个能复现的小实验

真跑一套语义嵌入模型需要模型文件和算力,不是每台机器都备着。这里换了个取巧但完全可复现的办法:字符 2-gram 配 crc32 哈希到 256 维,再做 L2 归一化。它不懂语义,但结果是确定的——同样的输入永远得到同样的向量,而排序、打分、多样性重排走的仍然是向量库里那段真实的代码路径。

importzlib,mathfromlangchain_core.documentsimportDocumentfromlangchain_core.embeddingsimportEmbeddingsfromlangchain_core.vectorstoresimportInMemoryVectorStore DIM=256classHashEmbeddings(Embeddings):def_vec(self,text):v=[0.0]*DIMforiinrange(len(text)-1):v[zlib.crc32(text[i:i+2].encode("utf-8"))%DIM]+=1.0n=math.sqrt(sum(x*xforxinv))or1.0return[x/nforxinv]defembed_documents(self,texts):return[self._vec(t)fortintexts]defembed_query(self,text):returnself._vec(text)rows=[("报销流程:先在系统提交单据,再由直属主管审批,最后财务打款。","finance",2024,1),("报销流程:先在系统提交单据,主管审批后财务打款,票据要留存。","finance",2025,1),("报销单据提交后由主管审批,财务复核票据再打款。","finance",2025,2),("差旅报销:出差回来七个工作日内提单,超期需要说明。","finance",2025,2),("年假规则:入职满一年五天,满三年十天,需提前三天申请。","hr",2025,1),("调休规则:加班可换调休,三个月内使用,过期作废。","hr",2024,1),("采购申请:单笔超过两万需要部门负责人和财务双重审批。","admin",2025,1),]docs=[Document(page_content=t,metadata={"dept":d,"year":y,"level":l})fort,d,y,linrows]vs=InMemoryVectorStore(embedding=HashEmbeddings())vs.add_documents(docs)q="报销单据怎么提交和审批"fordoc,scoreinvs.similarity_search_with_score(q,k=7):print(f"{score:.4f}",doc.metadata["dept"],doc.page_content[:12])

这个方法的边界要说清楚:它不适合用来讨论「真实嵌入模型的分数是多少」,只适合观察「排序怎么变、重复怎么挤、参数怎么起作用」。绝对分数别照抄,行为可以照抄。样本是 7 条虚构的人事与财务制度片段,其中 3 条是同一套报销流程的不同说法。环境是langchain-core 1.6.6、numpy 2.5.2、Python3.13.14,运行在 Windows 上。

1.3 把分数摊开看,问题一目了然

先看纯相似度检索把 7 条文档排成什么样。

排名分数主题归属内容摘要
10.3371finance / 2025 / L2报销单据提交后由主管审批,财务复核票据再打款
20.2840finance / 2025 / L1报销流程:先在系统提交单据,主管审批后财务打款
30.2752finance / 2024 / L1报销流程:先在系统提交单据,再由直属主管审批
40.1265hr / 2024 / L1调休规则:加班可换调休,三个月内使用
50.1155hr / 2025 / L1年假规则:入职满一年五天,满三年十天
60.0632admin / 2025 / L1采购申请:单笔超过两万需双重审批
70.0620finance / 2025 / L2差旅报销:出差回来七个工作日内提单

表里最刺眼的不是前三条有多高,而是 0.2752 到 0.1265 这道断崖——掉了 0.1487,比前三条之间的全部差距加起来还大。断崖在说一件事:能高分匹配这个问题的内容,只有「报销流程」这一个主题,从第 4 名开始都是勉强沾边。

更值得盯住的是最后一行。差旅报销在语义上是最该被补进来的那条,同属报销、同属财务、用户真实需求就在这儿,可它排在采购申请后面,分数只有 0.0620。原因很朴素:它的措辞里没有「提交」「审批」,说的是「提单」「超期说明」。字符层面重合度低,排序就把它压到最后。

这就是「答不全」的根:不是 K 不够大,是排序信号本身不认「互补」。相似度衡量的是「像不像问题」,而用户缺的那条,往往恰恰是「不像问题、但有用」。

二、重复挤占:K 越大,你替重复付的账越多

2.1 三条近似重复是怎么长出来的

多数人第一反应是「谁把重复文档放进库里的」,其实几乎没人主动放。三种来源都很正常:

一是版本迭代。2024 版和 2025 版都留在库里,谁也不敢删旧的,怕有人还在引用。二是分块重叠。切片时留了重叠区,同一段话被切进相邻两个块,两块都进了库。三是多人各自总结。同一份制度被三个人写成三份问答,措辞不同、意思一样。

三种做法单看都没错,合起来的效果是:同一段信息在 Top-K 里出现多次,占掉多个名额。

2.2 K 从 1 拉到 7,进来的到底是什么

把 K 依次调大,数一数重复条数和命中的主题数。

K返回条数其中报销重复条数命中主题数最低分
11110.3371
22210.2840
33310.2752
44320.1265
55320.1155
66330.0632
77330.0620

两件事同时成立,得分开说。

第一件:K 小的时候你并没躲开重复。K=3 时三条全是重复,占比百分之百。重复不是 K 变大才出现的,它一开始就站在最前排,因为近似重复的向量本来就离查询最近。

第二件:K 变大之后,新增的几条分数一路走低。0.3371、0.2840、0.2752 之后直接掉到 0.1265,再往后是 0.1155、0.0632、0.0620。到 K=6 以后,新进来的条目分数只有 0.06 上下,基本等于噪声。

这里要老实交代这个样本的局限:7 条里重复项只有 3 条,所以 K 从 3 涨到 7,重复条数锁在 3 不动。换到几十上百条的真实语料里不是这样,同一制度的重复可能有十几条,K 越大进来的重复越多。这个 7 条的小实验能证明的是另一半:K 变大换来的新增条目,边际价值几乎为零。

2.3 代价不只是 token

第一层是上下文预算,好理解:三条说同一件事的文档占掉三个名额,本来能放三个不同主题的位置只剩一个。

第二层代价是注意力稀释。同一说法出现三次,模型很容易把它当成「更重要」或「更权威」的信号——重复在人类写作里确实常常意味着强调,但在检索结果里,重复只说明向量近。

第三层最要命:错误会被复制。三份重复文档里如果两份是旧版流程、一份是新版,模型按「多数」答旧流程的概率不低,而且答得理直气壮,因为它看到的是二比一。这不是模型的问题,是喂进去的东西本来就在投票。

所以判据不是「K 该多大」,而是先看一件事:同一主题在 Top-K 里占了几条。占比超过三分之一,就该去处理重复,而不是继续调 K。

三、MMR 在做什么:把「像问题」和「像前面挑出的」分开算

3.1 一句话版本

MMR 全称 Maximal Marginal Relevance,中文常译作最大边际相关性。它不是换一种相似度算法,而是换一种「挑下一条」的规则。

普通的 Top-K 是一条一条独立打分,谁分高谁上,彼此之间不发生关系。MMR 是串行挑:每挑一条之前,既看它和问题有多像,也看它和前面挑出来的那几条有多像,第二个「像」要扣分。

下一条的得分 = λ ×(与问题的相似度)− (1 − λ) ×(与前面挑出集合中最像的那条的相似度)

这个式子的结构来自 MMR 原论文(Carbonell 与 Goldstein,SIGIR 1998),本文按公式的组成方式复述,没有逐字照抄原文。λ 就是代码里的lambda_mult,它把两个目标拧在同一根轴上:λ 越大越偏「相关」,越小越偏「不同」。

它要解决的问题正是上一章那道断崖——如果三条重复占住前三个位置,靠调 K 是救不回来的,得有个机制在挑第二条的时候就问一句:这条和第一条是不是在说同一件事。

3.2 四档实测:λ 变一点,结果变一截

固定k=4、fetch_k=7(7 条即全库),只改lambda_mult。

forlamin(1.0,0.7,0.5,0.0):picked=vs.max_marginal_relevance_search(q,k=4,fetch_k=7,lambda_mult=lam)print(lam,len(picked),[doc.page_content[:4]fordocinpicked])
lambda_mult返回构成报销重复条数补进差旅补进年假
1.03 条报销 + 调休3否否
0.72 条报销 + 调休 + 年假2否是
0.51 条报销 + 调休 + 差旅 + 年假1是是
0.01 条报销 + 年假 + 差旅 + 调休1是是

这张表把「λ 越大越好」这个直觉直接推翻。1.0 的时候前三条还是那三条重复,和第一章纯相似度的结果几乎一样;0.7 保留两条重复,换进来一条年假;0.5 只剩一条报销,差旅和年假都进来了;0.0 多样性最大,四条覆盖四个主题,但相关性明显掉了。

最有价值的对比是 0.7 和 0.5 之间。0.7 虽然挤掉了一条重复,但补进来的是年假,而用户真正缺的是差旅报销。年假和报销的关系比差旅远得多,它被选中只是因为「和前面挑出的不像」。这说明 MMR 只保证「不一样」,不保证「有用」。λ 调得太低,换进来的就是这种「不一样但没关系」的内容。

3.3 为什么 λ=1.0 等于没开

λ=1 的时候,后面那个惩罚项的系数变成 0,整个式子退化成纯相似度重排。代码跑了一遍 MMR 的完整流程,算了半天,结果和直接取 Top-K 一样。

这是「开了 MMR 但没效果」最常见的原因:参数留在默认值附近,或者有人觉得「1.0 最相关、肯定最好」。反方向的 0.0 是另一个极端,相关性项归零,排序完全由差异决定,返回的四条里「最相关」和「最不相关」混在一起,交给模型反而更难判断该信谁。

两端都不能用,能用的是中间那一段。下一章就来找这一段在哪儿。

四、lambda_mult 从几开始试:0.5 起步,0.4 到 0.6 是可操作区间

4.1 细扫一遍才发现它不是连续可调

只看四个点,容易以为 λ 是根平滑的旋钮。多取几个点再看。

forlamin(1.0,0.9,0.8,0.7,0.6,0.5,0.4,0.3):picked=vs.max_marginal_relevance_search(q,k=4,fetch_k=7,lambda_mult=lam)dup=sum(1fordocinpickedif"报销"indoc.page_contentand"差旅"notindoc.page_content)print(lam,dup)
lambda_mult报销重复条数结果是否与上一档不同
1.03基准
0.93否,与 1.0 一致
0.83顺序变了,条数没变
0.72是,掉一条重复
0.62否,与 0.7 同为 2 条
0.51是,再掉一条
0.41否,与 0.5 一致
0.31否,与 0.5 一致

它是一级一级的台阶,不是斜坡。0.8 到 0.7 掉一条,0.6 到 0.5 再掉一条,0.4 以下不再变化。

台阶现象解释了一件容易被误解的事:很多人调了参数、跑出来结果没变,就以为参数没用或者代码没生效。实际上是这个语料在这个区间里本来就没有可换的候选——重复项只有三条,掉到一条之后,剩下的全是不相干文档,再怎么降 λ 也只能打乱它们的顺序。

4.2 为什么默认值是 0.5

这版langchain-core里lambda_mult的默认值是 0.5(下一章会把签名打出来看)。默认值代表的是「通用场景的折中」,不是「你的场景的最优」。

0.5 的含义是相关性和差异性各占一半。语料重复度高的时候,这个折中刚好够用,既能保住最相关的那条,又能把重复挤出去。语料本身很干净、每条一个独立主题的时候,0.5 反而会把本该保留的好内容换掉,因为「不一样」这件事在你库里根本不稀缺。

4.3 判据:三步停手

第一步,先用 0.5 跑一遍,数重复条数。这是起点,不是答案。

第二步,重复条数大于等于 2、并且你有明确想补进来的主题时,降到 0.4 再看。降完如果结果没变,说明台阶走到头了,回到 0.5 就行,继续往下只会打乱顺序。

第三步,看补进来的东西对不对。如果新进来的条目和问题明显无关(像 0.7 换进年假那样),说明 λ 降过头了,往回调一档。MMR 保证的是「不一样」,能不能用还得你自己看内容。

还有一个更省事的选择:语料本来就干净、每条一个独立主题时,别开 MMR。纯相似度配一个合理的 K,比开一个调不明白的 MMR 更可控。

五、fetch_k 小于 k:不报错,只是少给你几条

5.1 实测就一行输出

把k设成 4、fetch_k设成 2,沿用第一章那个vs与同一个查询:

print(len(vs.max_marginal_relevance_search(q,k=4,fetch_k=7)))# 4 条print(len(vs.max_marginal_relevance_search(q,k=4,fetch_k=2)))# 2 条

返回 2 条。没有异常,没有警告,日志里就一行「返回 2 条」。你以为取到 4 条,实际只有 2 条。

5.2 为什么会静默

MMR 是两段式:先从库里按相似度取fetch_k条候选,再在这批候选里做多样性重排,最后取前k条。候选池只有 2 条,重排再怎么算也只能给你 2 条。

代码逻辑上这是对的,不能凭空造出文档。错的是调用方的预期:k=4看起来像「给我 4 条」,实际含义是「最多给我 4 条」。

危险的地方在于,它的症状和「检索质量差」完全一样。模型没答全,你以为是召回不行,于是去调 K、调 λ、换嵌入模型,折腾一圈回来,真正的原因是自己把候选池设小了。

5.3 怎么定 fetch_k

规则很短:fetch_k至少是k的 3 倍,语料小的时候直接等于库的总条数。这个实验里 7 条文档、k=4,fetch_k=7就是全库,不会漏。

fetch_k的默认值是 20。语料少于 20 条时它等于全库,所以你感觉不到问题;语料上千之后,k=10配默认的fetch_k=20就危险了,候选只有 20 条,重排要在 20 条里挑 10 条,多样性的空间被压得很窄,重复照样挤得进来。

还有一层连带关系:fetch_k是在元数据过滤之后取的候选。过滤条件越严,能进候选的文档越少,fetch_k就得相应放大,否则又回到「候选不够」的老问题上。下一章会把过滤和 MMR 放在一起跑一遍。

检索参数速查表:把 Top-K、fetch_k、lambda_mult 三者的先后顺序和取值范围画成一页图,配合本章的调用链看更容易记住。放在资料包里,扫码即可获取:

六、元数据过滤:这版签名换了,照旧教程写会直接炸

6.1 先用 inspect 把签名打出来

参数怎么传别猜,直接从装好的包里读。

importinspectfromlangchain_core.vectorstoresimportInMemoryVectorStoreprint(inspect.signature(InMemoryVectorStore.max_marginal_relevance_search_by_vector))print(inspect.signature(InMemoryVectorStore.similarity_search_with_score))

打出来的关键字参数是k=4、fetch_k=20、lambda_mult=0.5,后面跟着一个filter,类型标注是Callable[[Document], bool] | None。源码里这一行出现在langchain_core/vectorstores/in_memory.py的第 295、338、426 行,三个方法各一处。

这句话翻译成人话:filter要的是一个函数,吃进一个Document,吐出一个布尔值。不是字典,不是字符串,不是查询表达式。

6.2 传字典会怎样

网上大量教程写的是filter={"dept": "hr"}这种字典形式,那种写法在别的向量库实现里成立,在这一版的InMemoryVectorStore里不成立。传进去之后,库会把这个字典当成函数去调用,于是抛出一个类型错误,提示字典对象不可调用。

要把这件事说准:不是过滤器坏了,也不是功能被删了,是接口从「声明式条件」换成了「谓词函数」。谓词函数更灵活,任意逻辑都能写;代价是不能序列化、不能下推给数据库执行,只能把文档拉到本地一条条判断。

6.3 六个条件实测

换成函数之后,逐个验证。

过滤条件写法返回条数返回的部门
部门等于人事2人事、人事
年份大于等于 20255财务、财务、人事、行政、财务
层级等于 22财务、财务
部门属于人事或行政3人事、人事、行政
部门不等于财务3人事、人事、行政
恒为假0空

前五行都正常。要盯住的是最后一行:恒为假的条件返回 0 条,不报错,不警告。

这意味着「过滤条件写错了」和「库里确实没有这类数据」在返回值上完全一样,都是空列表。线上排查时这两件事的处理方式截然不同,但代码层面没有任何区分。

6.4 字段缺失:下标会当场炸,get 会安静地返回空

同一个过滤函数,取字段的方式不同,出错时机完全不同。

用下标d.metadata["level"]取一个确实存在的字段,正常返回 2 条。把字段名换成不存在的,运行期抛键错误——过滤函数是对每条文档逐个调用的,只要有一条缺这个键,整次检索就中断。

改成d.metadata.get("nosuch")之后不再抛错,返回 0 条,安静得像什么都没发生。

两种都不可爱,但危险等级差得远。下标写法当场炸,你立刻知道参数写错了;get写法静默返回空,你会以为是数据没入库、是索引没建、是过滤条件太严,排查方向一开始就偏了。

判据可以很简单:这条文档一定要有这个字段才允许参与检索,就用下标,宁可炸;这个字段只是可选条件,就用get并写明确的默认值,让缺失的文档按「不满足」处理。

6.5 过滤和 MMR 一起用

两件事可以同时做:先在函数里筛部门,再在筛完的候选里做多样性重排。

defonly_finance(doc):"""只允许财务部文档进入候选池。"""returndoc.metadata.get("dept")=="finance"print("过滤后条数:",len(vs.similarity_search(q,k=10,filter=only_finance)))picked=vs.max_marginal_relevance_search(q,k=3,fetch_k=5,filter=only_finance)print("过滤 + MMR:",len(picked),[doc.metadata["dept"]fordocinpicked])

结果是 3 条,全部来自财务部,其中包含那条差旅报销,这是纯相似度在前四名里给不了的东西。注意这里fetch_k=5是在过滤之后生效的,财务部一共 4 条文档,所以 5 就够把候选池开满。

顺序要记牢:先过滤、再取候选、最后重排。过滤越严,候选越少,fetch_k越要给足。

七、判据表:四个参数各自的停手条件

7.1 四个问题,四组答案

把前六章的结论收成一张能照着做的表。

要定的事默认值起步值什么时候动停手信号
Top-K44 到 6分数出现断崖、且断崖后仍缺主题新增条目分数低于断崖前的一半
是否上 MMR不开语料有重复就开Top-K 里同主题占比超过三分之一重复条数降到 1、且不缺主题
lambda_mult0.50.5重复仍有 2 条以上再降一档结果不变
fetch_k20不小于 3 倍 k语料条数少于 3 倍 k 时fetch_k 等于全库
过滤写法无谓词函数需要按部门、年份、层级收窄返回条数等于候选总数

最后一行那个「停手信号」值得多说一句:如果过滤之后返回的条数正好等于符合条件的所有文档数,说明过滤没起作用,条件写宽了或者写错了。这时候该回头查条件,而不是继续调 K。

7.2 什么时候不该用 MMR

MMR 不是万能钥匙,三种场景用它反而更差。

第一种,你要的是唯一权威版本。制度类问答里「以现行版本为准」是硬要求,MMR 的多样性偏好会把旧版本换进来,它不判断哪条更新,只知道哪条不一样。这种场景该用元数据过滤加时间排序,把旧版本挡在候选池外面。

第二种,语料本身每条独立。一份手册切成 50 个互不重叠的段落,段与段之间本来就没有重复,MMR 能做的只有打乱相关性顺序。这时候它就是纯粹的负担。

第三种,k 很小。k=2、k=3 的时候多样性几乎没有腾挪空间,MMR 挑完第一条,后面可选的本来就不多,调 λ 的收益趋近于零。

7.3 上线前的三条自检

第一条,给每次检索打一条日志:返回条数、请求的 k。两个数不相等就去查fetch_k。这一条能挡住第五章那个静默少返回的坑。

第二条,给每次检索打一条日志:Top-K 里同来源或同标题的条数占比。这个数字长期偏高,说明该处理重复文档或者开 MMR 了,而不是继续调 K。

第三条,把过滤函数里用到的每个字段列出来,逐条问一句「这条文档一定会有这个字段吗」。答案是否定的,就改成get加默认值;答案是肯定的,就保留下标写法,让错误在第一时间暴露。

这三条都不涉及模型,全是代码侧的观测点。做完之后再去调参数,才有依据可循。

向量库检索调参清单:把本章这四条判据和三条自检整理成一页可打印的核对表,选型或排障时对着勾。放在资料包里,扫码即可获取:

附表 A:本文引用事实与出处对照表

序号事实出处本文位置
1MMR 论文标题为 The use of MMR, diversity-based reranking for reordering documents and producing summaries,作者 Jaime Carbonell、Jade Goldstein,收录于第 21 届 ACM SIGIR 会议论文集,1998 年,页码 335–336,DOI 10.1145/290941.291025ACM Digital Library 该 DOI 落地页(标题与会议名)+ Semantic Scholar 书目记录(作者、页码、年份)第 3 章 3.1
2MMR 的选取规则为「λ 加权的问题相似度 减去 (1−λ) 加权的候选集合最大相似度」同上论文;本文按公式组成方式复述,未逐字引用原文第 3 章 3.1
3lambda_mult默认 0.5、fetch_k默认 20、k默认 4本机实测(inspect打印max_marginal_relevance_search_by_vector签名,环境见第 15 条)第 4 章 4.2、第 5 章 5.3
4filter参数类型标注为「接收 Document、返回布尔值」的可调用对象,可为空;源码位置langchain_core/vectorstores/in_memory.py第 295、338、426 行本机实测(脚本路径work/2026-10-20_向量库过滤与MMR实测.py;签名另用inspect.signature单独打印核对)第 6 章 6.1
5纯相似度 Top4 分数为 0.3371、0.2840、0.2752、0.1265,前三条为同一报销流程的近似重复本机实测(脚本路径work/2026-10-20_向量库过滤与MMR实测.py,日志work/2026-10-20_向量库过滤与MMR实测.log)第 1 章 1.3
6第 5 至 7 名分数为 0.1155(年假)、0.0632(采购)、0.0620(差旅),差旅报销排名最后本机实测(脚本路径work/2026-10-20_向量库过滤与MMR实测.py,K=7 全量排序补充扫描,同一脚本同一向量构造)第 1 章 1.3
7K 从 1 到 7 时,报销重复条数为 1、2、3、3、3、3、3,命中主题数为 1、1、1、2、2、3、3本机实测(脚本路径work/2026-10-20_向量库过滤与MMR实测.py,K 递增补充扫描)第 2 章 2.2
8lambda_mult四档结果:1.0 为 3 条重复加调休;0.7 为 2 条重复加调休与年假;0.5 为 1 条重复加调休、差旅、年假;0.0 为 1 条重复加年假、差旅、调休本机实测(脚本路径work/2026-10-20_向量库过滤与MMR实测.py,日志同名.log实验 5)第 3 章 3.2
9lambda_mult细扫:0.9 为 3 条重复、0.8 为 3 条仅顺序变化、0.7 与 0.6 为 2 条、0.5 与 0.4 与 0.3 为 1 条本机实测(脚本路径work/2026-10-20_向量库过滤与MMR实测.py,lambda_mult细粒度补充扫描)第 4 章 4.1
10fetch_k=2, k=4时只返回 2 条,不抛异常本机实测(脚本路径work/2026-10-20_向量库过滤与MMR实测.py,日志同名.log实验 6)第 5 章 5.1
11传字典作为filter抛出类型错误,提示字典对象不可调用本机实测(脚本路径work/2026-10-20_向量库过滤与MMR实测.py,日志同名.log实验 2)第 6 章 6.2
12函数式过滤六种条件返回条数:人事 2 条、年份大于等于 2025 为 5 条、层级等于 2 为 2 条、人事或行政 3 条、非财务 3 条、恒假 0 条本机实测(脚本路径work/2026-10-20_向量库过滤与MMR实测.py,日志同名.log实验 3)第 6 章 6.3
13过滤函数用下标取不存在的字段抛键错误;改用get后静默返回 0 条本机实测(脚本路径work/2026-10-20_向量库过滤与MMR实测.py,日志同名.log实验 4)第 6 章 6.4
14过滤与 MMR 同时使用时k=3, fetch_k=5返回 3 条,全部来自财务部本机实测(脚本路径work/2026-10-20_向量库过滤与MMR实测.py,日志同名.log实验 7)第 6 章 6.5
15实测环境为langchain-core 1.6.6、numpy 2.5.2、Python3.13.14(Windows)本机实测(脚本路径work/2026-10-20_向量库过滤与MMR实测.py首段版本打印;日志work/2026-10-20_向量库过滤与MMR实测.log首行)全文
16字符 2-gram 配 crc32 哈希构造的向量不懂语义,只可用于观察排序与多样性行为,不代表真实嵌入模型的分数分布本文自述的方法学局限(作者说明,非外部来源)第 1 章 1.2

附表 B:术语速查表

术语一句话解释在本文哪里用到
Top-K按相似度取前 K 条,K 是条数不是阈值第 1、2 章
相似度分数查询向量与文档向量的接近程度,同一批数据里可比,跨模型不可比第 1 章
重复挤占同一主题的多条近似文档占掉多个返回名额,本文自述的观察角度,非标准术语第 2 章
语义断崖相邻两名分数突然大幅下降,说明可用内容到此处为止第 1 章
MMR最大边际相关性,挑下一条时同时惩罚「与前面挑出的太像」第 3 章
lambda_multMMR 里相关性与多样性的权重,1.0 纯相关,0.0 纯差异第 3、4 章
fetch_kMMR 的候选池大小,先取这么多条再重排第 5 章
静默失败不抛错、不警告,但结果不是你要的第 5、6 章
元数据过滤按部门、年份等字段先筛文档,再做向量检索第 6 章
谓词函数吃一个对象、返回真或假的函数,用来表达过滤条件第 6 章
分块重叠切片时相邻块共享一段文字,容易在库里制造近似重复第 2 章

写在最后:这篇用到的资料

写这篇文章时,把相关的官方文档和源码又翻了一遍,顺手也整理了几份配套的东西:

  • 大模型学习路线图:从零基础到能自己动手做 Agent,按阶段说明每一步该学什么、哪些可以先跳过
  • 《LangChain + LangGraph + MCP 智能体开发实战》视频课:7 个模块,从私有化部署、Embedding+RAG 到 MCP+Agent 全流程
  • AI 大模型知识库(在线可查):Agent Skills 从入门到落地、Claude Skills 完全指南等专题,按目录浏览即可
  • 640 套 AI 大模型行业报告 + 经典 PDF 书籍:看行业落地案例和别人怎么做的时候用得上
  • 大模型零基础到精通教学视频:跟着敲一遍,比只读文档快得多

资料是我自己整理的,放在下面这个码上,扫码即可获取:






添加时备注「AI」,优先通过。

资料按「先路线、再动手、最后查漏」的顺序整理好了,建议先看学习路线那一份,照着它挑一条适合自己当前基础的路径再往下看。

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

SeleniumBase页面等待机制全解析:从自动等待到CDP事件监听

做自动化测试这几年,我踩得最多、也最不想再踩的坑,就是“元素明明存在,代码却一直报找不到”。尤其在 SeleniumBase 这类现代测试框架里,如果没把页面等待机制吃透,所有脚本都会在相似的地方翻车:要么等待…

作者头像 李华
网站建设 2026/10/21 9:58:44

Python递归求解排列组合:剪枝、去重与迭代化工程实践

Python求解排列组合,这个话题写到第四篇,基本进入了递归程序设计艺术里最见功底的部分:剪枝、去重和工程化改造。前面几篇已经把递归树怎么画、全排列和组合的基础回溯怎么写讲透了,但很多朋友会在真实场景里碰壁:数据…

作者头像 李华
网站建设 2026/10/21 9:56:12

Spring Boot+Spark旅游推荐系统设计与实现

1. 这个选题到底值不值得做:旅游推荐背后的业务痛点每年毕业设计季,都有同学对着题目列表发愁。前阵子有位学弟跟我聊起他的开题方向——"基于Spring Boot和Spark的旅游推荐系统",问我这个题目是不是只是把几个热门技术名词堆在一起…

作者头像 李华
网站建设 2026/10/21 9:54:15

GCC 7.3.0 配 SFML:DevC++ 链接错误解决与配置指南

简介:这份资源面向在 Windows 平台使用 DevC 开发 C 项目的初学者与进阶开发者,核心是提供 GCC 7.3.0 编译器与 SFML 多媒体库的配套安装包,解决图形、音频、窗口与输入等 2D 游戏开发环境搭建繁琐的问题。压缩包为 zip 格式,整体…

作者头像 李华
网站建设 2026/10/21 9:53:42

递归思维实战:汉诺塔与青蛙跳台阶的深度拆解

1. 从两个老生常谈的递归题说起汉诺塔和青蛙跳台阶,几乎是每一本数据结构与算法教材里绕不开的两个递归入门案例。很多人第一次接触递归这个概念,就是从这两道题开始的。但有意思的是,我面试过不少工作两三年的Java开发,让他们手写…

作者头像 李华
网站建设 2026/10/21 9:53:42

artcraft手工方法论:参数驱动的手工创作体系

1. 项目概述:这不是一个软件,而是一套可落地的手工创作方法论“artcraft”这个词乍看像某个新出的AI绘画工具或设计平台,但实际在最近三个月的行业交流中,它高频出现在高校美育工作坊、社区手作市集和独立创作者的物料清单里——它…

作者头像 李华