首页/新闻资讯/正文详情

Python后端AI专题23:Rerank 重排:为什么召回第一名不一定最会回答

发布时间:2026/9/26 3:13:48 来源:云帆数科 栏目:资讯中心
Python后端AI专题23:Rerank 重排:为什么召回第一名不一定最会回答
Python后端AI专题23Rerank 重排为什么召回第一名不一定最会回答向量召回用单个向量近似整段语义关键词召回看精确词RRF 再根据名次投票它们适合快速找候选却没有逐对阅读“问题 文档”。Rerank 的职责是在较小候选集上做更精细的相关性判断而不是替代第一阶段检索全库。RRF 手算与测试答案k60A 1/61 1/63 0.03227 B 1/62 1/61 0.03252 C 1/63 0.01587 D 1/62 0.01613顺序为 B、A、D、C。B 在两路都靠前所以超过向量第一的 A。完整测试还验证输入没有被修改deftest_rrf_hand_calculation_preserves_input_scores()-None:vector[RetrievedChunk(A,A,0.91),RetrievedChunk(B,B,0.82),RetrievedChunk(C,C,0.73),]keyword[RetrievedChunk(B,B,9.0),RetrievedChunk(D,D,7.0),RetrievedChunk(A,A,5.0),]resultreciprocal_rank_fusion(vector,keyword,k60)assert[item.idforiteminresult][B,A,D,C]by_id{item.id:itemforiteminresult}assertby_id[A].diagnostics[vector_rank]1assertby_id[A].diagnostics[keyword_rank]3assertkeyword_ranknotinby_id[C].diagnosticsassert[item.scoreforiteminvector][0.91,0.82,0.73]真实结果4 passed in 0.09s。为什么是两阶段漏斗假设有一百万 chunk。Cross-encoder/Rerank 若对每个候选都联合编码问题与正文计算量远大于向量点积。常见漏斗100 万 → 向量 Top 30 关键词 Top 30 → RRF 去重约 40 条 → Rerank Top 5 → 上下文预算再裁剪召回阶段追求别漏掉重排阶段追求前几名准确。候选太少Rerank 无法救回没被召回的答案候选太多延迟和费用上升。Fake Rerank 如何保证回归确定性课程 Fake 使用问题与正文的字符集合重叠并以原排名稳定打破平分scorelen(query_termsterms)/max(1,len(query_terms))scored.append(RerankResult(item.id,score,rank))scored.sort(keylambdaresult:(-result.score,result.original_rank))它不代表真实重排质量只让管线在无网络时可重复测试。生产适配器应调用真实 reranker并通过评测集比较不能把 Fake 的字符分数写进项目简历说“语义重排”。完整检索管线from__future__importannotationsfromtimeimportperf_counterfromapp.providers.contractsimportEmbeddingProvider,RerankItem,RerankProviderfromapp.providers.vector_storeimportVectorStorefromapp.services.retrieval.fusionimportreciprocal_rank_fusionfromapp.services.retrieval.keywordimportkeyword_searchfromapp.services.retrieval.typesimportRetrievalResultclassRetrievalPipeline:def__init__(self,*,embedder:EmbeddingProvider,vector_store:VectorStore,reranker:RerankProvider)-None:self.embedderembedder self.vector_storevector_store self.rerankerrerankerasyncdefretrieve(self,*,tenant_id:str,knowledge_base_id:str,query:str,limit:int5)-RetrievalResult:timings:dict[str,float]{}startedperf_counter()vector(awaitself.embedder.embed([query]))[0]timings[embedding](perf_counter()-started)*1000startedperf_counter()vector_resultsawaitself.vector_store.search(tenant_idtenant_id,knowledge_base_idknowledge_base_id,vectorvector,limitmax(limit*3,10),)timings[vector](perf_counter()-started)*1000startedperf_counter()recordsawaitself.vector_store.records_for(tenant_idtenant_id,knowledge_base_idknowledge_base_id)keyword_resultskeyword_search(query,records,limitmax(limit*3,10))timings[keyword](perf_counter()-started)*1000fusedreciprocal_rank_fusion(vector_results,keyword_results)startedperf_counter()try:rerankedawaitself.reranker.rerank(query,[RerankItem(item.id,item.text)foriteminfused],top_nlimit,)exceptTimeoutError:chunksfused[:limit]forchunkinchunks:chunk.diagnostics[rerank_degraded]Truetimings[rerank](perf_counter()-started)*1000timings[rerank_degraded]1.0timings[total]sum(valueforkey,valueintimings.items()ifkey!rerank_degraded)returnRetrievalResult(chunks,timings)by_id{item.id:itemforiteminfused}chunks[]forreranked_iteminreranked:chunkby_id[reranked_item.id]chunk.scorereranked_item.score chunk.diagnostics[rerank_score]reranked_item.score chunks.append(chunk)timings[rerank](perf_counter()-started)*1000timings[total]sum(timings.values())returnRetrievalResult(chunks,timings)limitmax(limit*3,10)保证即使最终只要 2 条也给重排至少 10 个向量/关键词候选。真实生产中两个召回大小应独立配置并在评测中寻找成本曲线。diagnostics 不应在重排后丢失管线用 id 找回 RRF 对象只更新最终 score并增加rerank_score。因此输出还能看到vector_rank/keyword_rank/rrf_score。当用户投诉错误答案时可以判断是未召回、融合落后还是重排选错。Rerank 的输入也有安全与长度边界外部 Rerank 服务会看到候选正文仍需遵守租户与数据出境要求。超长 chunk 应按供应商限制截断但截断方式要保留关键段批量请求也应有超时、并发与降级策略。若 Rerank 暂时不可用可降级使用 RRF 顺序但必须在 diagnostics 标记而不是伪造rerank_score。本篇最终完整模块pipeline.py前面的代码片段用于解释本次改动下面是本篇结束时可直接核对和替换的磁盘完整版本。from__future__importannotationsfromtimeimportperf_counterfromapp.providers.contractsimportEmbeddingProvider,RerankItem,RerankProviderfromapp.core.metricsimportRETRIEVAL_STAGE_SECONDSfromapp.providers.vector_storeimportVectorStorefromapp.services.retrieval.fusionimportreciprocal_rank_fusionfromapp.services.retrieval.keywordimportkeyword_searchfromapp.services.retrieval.typesimportRetrievalResultclassRetrievalPipeline:def__init__(self,*,embedder:EmbeddingProvider,vector_store:VectorStore,reranker:RerankProvider,)-None:self.embedderembedder self.vector_storevector_store self.rerankerrerankerasyncdefretrieve(self,*,tenant_id:str,knowledge_base_id:str,query:str,limit:int5,)-RetrievalResult:timings:dict[str,float]{}startedperf_counter()vector(awaitself.embedder.embed([query]))[0]timings[embedding](perf_counter()-started)*1000RETRIEVAL_STAGE_SECONDS.labels(stageembedding).observe(timings[embedding]/1000)startedperf_counter()vector_resultsawaitself.vector_store.search(tenant_idtenant_id,knowledge_base_idknowledge_base_id,vectorvector,limitmax(limit*3,10),)timings[vector](perf_counter()-started)*1000RETRIEVAL_STAGE_SECONDS.labels(stagevector).observe(timings[vector]/1000)startedperf_counter()recordsawaitself.vector_store.records_for(tenant_idtenant_id,knowledge_base_idknowledge_base_id)keyword_resultskeyword_search(query,records,limitmax(limit*3,10))timings[keyword](perf_counter()-started)*1000RETRIEVAL_STAGE_SECONDS.labels(stagekeyword).observe(timings[keyword]/1000)fusedreciprocal_rank_fusion(vector_results,keyword_results)startedperf_counter()try:rerankedawaitself.reranker.rerank(query,[RerankItem(item.id,item.text)foriteminfused],top_nlimit,)exceptTimeoutError:chunksfused[:limit]forchunkinchunks:chunk.diagnostics[rerank_degraded]Truetimings[rerank](perf_counter()-started)*1000RETRIEVAL_STAGE_SECONDS.labels(stagererank).observe(timings[rerank]/1000)timings[rerank_degraded]1.0timings[total]sum(valueforkey,valueintimings.items()ifkey!rerank_degraded)returnRetrievalResult(chunks,timings)by_id{item.id:itemforiteminfused}chunks[]forreranked_iteminreranked:chunkby_id[reranked_item.id]chunk.scorereranked_item.score chunk.diagnostics[rerank_score]reranked_item.score chunks.append(chunk)timings[rerank](perf_counter()-started)*1000RETRIEVAL_STAGE_SECONDS.labels(stagererank).observe(timings[rerank]/1000)timings[total]sum(timings.values())returnRetrievalResult(chunks,timings)本篇练习给重排做失败降级设计写一个FailingRerankProvider总是抛TimeoutError。为管线设计两种策略并比较A. 整个搜索返回 503B. 返回 RRF Top N并在每个结果 diagnostics 写rerank_degradedTrue。选择一种适合“内部知识库只读搜索”的策略给出完整伪代码和至少两个测试降级顺序正确、非超时的协议错误不能被静默吞掉。下一篇会给出选择与可运行实现然后建立引用校验和无证据拒答让“搜到内容”不再自动等于“答案可信”。

相关推荐

Claude Code 模板实战:用可复用工作流提升 AI 编码的一致性与效率
Claude Code 模板实战:用可复用工作流提升 AI 编码的一致性与效率

说到底,Claude Code 这类 AI 编码工具本身已经不算新鲜了,真正让团队拉开效率差距的,是那些藏在 CLAUDE.md、slash command 和 agent 配置里的一套套模板。有人把 Claude Code 当成一次性聊天框,用完就忘;有人却把它当… · 2026/9/26 3:13:42

基于YOLOv5的明厨亮灶老鼠检测实战:数据、训练与部署
基于YOLOv5的明厨亮灶老鼠检测实战:数据、训练与部署

简介:目标检测是计算机视觉中的基础任务,旨在定位图像中的特定目标。YOLO系列算法凭借单阶段检测的实时性优势,成为工业落地的热门选择。基于YOLOv5的训练流程,涉及数据标注格式转换、目录组织、增强策略等关键环节,直… · 2026/9/26 3:13:42

羊草Leymus chinensis性染色体基因组--Plant Biotechnology Journal
羊草Leymus chinensis性染色体基因组--Plant Biotechnology Journal

High-Quality Chromosome-Level Genomes Reveal the Structure and Evolution of the S and Z Self-Incompatibility Loci in Leymus chinensis 高质量染色体水平基因组揭示羊草 S 与 Z 自交不亲和位点的结构与演化 羊草(Leymus chinensis)是欧亚草原的… · 2026/9/26 3:13:42

高并发基石:Reactor模型原理、架构演进与工程实战
高并发基石:Reactor模型原理、架构演进与工程实战

1. 阻塞IO的天花板:高并发问题的根源我最早接触到Reactor模型,是因为线上服务出现了一个非常棘手的故障:单机连接数不过两三百,CPU占用率却冲到百分之百,请求频繁超时。起初我以为是代码逻辑的问题,各种排查… · 2026/9/26 4:46:22

古城景区管理系统毕业设计:Java+Vue全栈开发实战指南
古城景区管理系统毕业设计:Java+Vue全栈开发实战指南

毕业设计做到一半才发现,很多同学不是不会写代码,而是不知道该把一个管理系统“做到什么程度”才算合格。就拿古城景区管理系统来说,题目热门、资料也多,但真正能把需求梳理清楚、把技术栈用出说服力、把数据库设计得经得起答辩追… · 2026/9/26 4:46:22

SpringBoot+Vue语言考试报名系统开发实战:从数据库设计到部署联调
SpringBoot+Vue语言考试报名系统开发实战:从数据库设计到部署联调

SpringBootVue 语言考试信息报名系统,这个标题放在毕业设计清单里确实很常见,但真正能把它做扎实、跑通前后端、交得出手的人,其实没那么多。我当年做类似项目的时候,也踩过不少坑——数据库字段设计得过于随意,导致后… · 2026/9/26 4:46:22

线上美容预约小程序开发实战:从排班数据模型到并发控锁
线上美容预约小程序开发实战:从排班数据模型到并发控锁

去年春天帮一家连锁美容院做预约系统的时候,我第一次被他们的运营后台惊到了:整整12家门店,所有预约居然靠一个微信群接龙加Excel排班表在撑。客人约了下午三点,技师手上的表记得是三点,前台的本子上写的是三点半&… · 2026/9/26 4:46:22

JavaScript前端加解密实战:从Web Crypto API到混合加密方案
JavaScript前端加解密实战:从Web Crypto API到混合加密方案

1. 为什么JavaScript需要加解密:先理清概念和应用场景搞前端开发这些年,经常有同事拿着一个需求过来问我:"帮我在前端把这个密码加密一下呗"。每次遇到这种诉求,我都得先拉把椅子坐下,问清楚他到底想防谁、防… · 2026/9/26 4:46:16

Midscene实战:AI视觉驱动的安卓UI自动化,告别脆弱定位符
Midscene实战:AI视觉驱动的安卓UI自动化,告别脆弱定位符

干测试的同学应该都有过这种体验:昨天还在正常跑的 UI 自动化,今天因为开发在页面上挪了一个控件,整个用例就废了。改 xpath、等元素、重新截图、维护数据依赖,一遍遍重复消耗时间,投入产出比低到让人怀疑自动化到底值… · 2026/9/26 4:46:16

数据库课后习题答案别硬背:当测试用例集刷,效率翻倍
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍

简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21

OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40

向下兼容与向上兼容:接口设计中的兼容性策略与工程实践
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践

一次版本升级事故,是很多团队绕不过去的坎。线上环境里,服务端明明已经上线了新版接口,老的移动端还在照着旧文档传参数。请求一到网关,校验直接拒绝,用户操作失败,客服群炸了锅,开发群里开始互… · 2026/9/26 0:00:46

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码