你好,我是方可乐。
一个正在深耕AI编程的30+的AI应用开发工程师。
用AI编程开发过Web应用网站,浏览器插件,微信小程序,AI智能体,RAG系统。
关注公众号&添加微信:ThinkFun666
领取【AI编程资料包】
去年6月份,我做了一个连自己都觉得疯狂的决定:裸辞,All in AI。
现在已经换赛道成功,继续在工作中探索。
上一篇文章分享了3个主流的检索方案,细心的同学去跑了代码并且进行更多的实践后,可能会发现这样一个问题。
一次检索出来的结果,有时候准,有时候很不准。
是的,仅靠向量相似度,结果可能“相关性不够精准”或“信息重复”。
这个时候,我们就需要用到一个叫做Rerank的东西,来提升检索的精准度。
Rerank 是什么
就是字面的意思,重排序。在初步检索得到一批候选后,使用更复杂模型或者算法进行“第二道筛选”与重排序。
-
• 常见方式: -
• Cross-Encoder Rerank:提升相关性与回答准确度。 -
• MMR(最大边际相关性):提升多样性与覆盖面。
Cross-Encoder Rerank
-
• 是什么 -
• 一种“模型方法”。它用一个训练好的模型,把 问题(query) 和 候选文档 拼在一起,逐对打分,算出它们的相关性。 -
• 怎么理解 -
• 可以想象成:你问了一个问题,把答案候选一个个拿出来,让“专家”逐一打分,哪个更靠谱,哪个更不相关。 -
• 优缺点 -
• ✅ 优点:最准确。它能很好地理解上下文,问答效果提升明显。 -
• ❌ 缺点:算得慢。因为要给每个候选都算一次分,耗时和算力都高。 -
• 什么时候用 -
• 通常先用向量检索筛一批候选(比如前 20 个),再用 Cross-Encoder 精排,挑出 3~5 个最相关的结果。 -
• 常见模型 -
• 中文: BAAI/bge-reranker-base、BAAI/bge-reranker-large、BAAI/bge-reranker-v2-m3 -
• 英文: cross-encoder/ms-marco-MiniLM-L-6-v2
MMR
-
• 是什么 -
• 一种“算法方法”。它不依赖模型,而是用公式对检索结果重新排序。 -
• 怎么理解 -
• 你问了一个问题,搜出来的 10 篇文章,有的内容差不多。MMR 的目标是:既给你相关的答案,又保证答案之间不重复。就像点外卖,不能全推荐“黄焖鸡”,要多来几个口味。 -
• 原理 -
• 打分分成两部分: -
• 要和问题相关(保证答案靠谱)。 -
• 不能和已经选过的内容太像(保证多样化)。 -
• 通过一个参数 λ(lambda) 来平衡:越大越偏向相关性,越小越偏向多样性。 -
• 优缺点 -
• ✅ 优点:能覆盖不同角度的信息,避免答案雷同。 -
• ❌ 缺点:相关性没 Cross-Encoder 判断得准。 -
• 什么时候用 -
• 适合场景:科研文献检索、知识推荐、去重、或者你希望答案覆盖多个角度的时候。 -
• 怎么用 -
• 在 LangChain 里直接用 MaximalMarginalRelevanceRetriever,不用额外模型就能跑。
代码示例
老规矩,下面我们直接上代码。
新建代码文件:17_rerank_mmr_practice.py
import os
from dotenv import load_dotenv
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_community.vectorstores import Chroma
from langchain_openai import ChatOpenAI
from langchain.chains import RetrievalQA
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import CrossEncoderReranker
from langchain_community.cross_encoders import HuggingFaceCrossEncoder
def _trim(text: str, max_len: int = 80) -> str:
return (text[: max_len] + "...") if len(text) > max_len else text
def main():
load_dotenv()
# ========== 1. 准备环境 ==========
# 使用中文嵌入模型以更好地处理中文文本
embedding_model = HuggingFaceEmbeddings(model_name="BAAI/bge-small-zh-v1.5")
texts = [
"公司人事制度规定员工需提前申请年假。",
"财务制度要求报销需提供完整发票。",
"技术部门采用敏捷开发流程。",
"公司提供免费午餐。",
"员工请假需提前 3 天申请。",
"公司每年提供 10 天带薪年假。",
]
# 构建向量数据库
vectorstore = Chroma.from_texts(
texts,
embedding=embedding_model,
persist_directory="./chroma_db_17",
)
# 切换到 DeepSeek(OpenAI 兼容接口)
llm = ChatOpenAI(
model="deepseek-chat",
base_url=os.getenv("DEEPSEEK_BASE_URL", "https://api.deepseek.com/v1"),
api_key=os.getenv("DEEPSEEK_API_KEY"),
temperature=0,
)
query = "公司请假制度是什么?"
# ========== 2. 普通相似度(Baseline) ==========
baseline_retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
qa_baseline = RetrievalQA.from_chain_type(llm=llm, retriever=baseline_retriever)
print("\n=== 普通相似度检索(候选片段 + 得分 + 答案) ===")
baseline_results = vectorstore.similarity_search_with_score(query, k=5)
for i, (doc, score) in enumerate(baseline_results[:3], start=1):
try:
score_val = float(score)
except Exception:
score_val = score
print(f"[S{i}] score={score_val:.4f} content={_trim(doc.page_content)}")
print("→ LLM 回答:")
print(qa_baseline.invoke(query))
# ========== 3. Cross-Encoder Rerank(提升相关性) ==========
# 先用向量检索取较多候选,再用交叉编码器精排到 top_n
base_retriever = vectorstore.as_retriever(search_kwargs={"k": 8})
model = HuggingFaceCrossEncoder(model_name="BAAI/bge-reranker-base")
compressor = CrossEncoderReranker(model=model, top_n=3)
compression_retriever = ContextualCompressionRetriever(
base_compressor=compressor,
base_retriever=base_retriever,
)
qa_rerank = RetrievalQA.from_chain_type(llm=llm, retriever=compression_retriever)
print("\n=== Cross-Encoder Rerank(初筛候选 + 精排得分 + 答案) ===")
initial_docs = base_retriever.invoke(query)
for i, doc in enumerate(initial_docs, start=1):
print(f"[C0-{i}] content={_trim(doc.page_content)}")
compressed_docs = compression_retriever.invoke(query)
# 为透明打印,显式计算 rerank 分数
pairs = [(query, d.page_content) for d in compressed_docs]
scores = model.score(pairs)
for i, (doc, sc) in enumerate(zip(compressed_docs, scores), start=1):
print(f"[CR{i}] rerank_score={sc:.4f} content={_trim(doc.page_content)}")
print("→ LLM 回答:")
print(qa_rerank.invoke(query))
# ========== 4. MMR(提升多样性) ==========
# 通过 lambda_mult 控制相关性与多样性的权衡
mmr_retriever = vectorstore.as_retriever(
search_type="mmr",
search_kwargs={"k": 3, "fetch_k": 20, "lambda_mult": 0.5},
)
qa_mmr = RetrievalQA.from_chain_type(llm=llm, retriever=mmr_retriever)
print("\n=== MMR 检索(多样性候选 + 答案) ===")
mmr_docs = mmr_retriever.invoke(query)
for i, doc in enumerate(mmr_docs, start=1):
print(f"[M{i}] content={_trim(doc.page_content)}")
print("→ LLM 回答:")
print(qa_mmr.invoke(query))
if __name__ == "__main__":
main()
运行结果:
可以从运行结果看到,三种不同的排序策略的差异与对回答质量的影响。
具体怎么选择,还是需要具体场景具体分析。
对AI应用开发感兴趣的同学,欢迎加微信申请入群交流学习。
想要学习AI应用开发的同学,可以参照我的代码跑起来,举一反三,一天一个脚印的进步,我相信,会足够坚实。
接下来100天,我会记录从Java程序员到AI应用开发工程师的完整转型路:每一个困惑、每一次突破、每一个真实瞬间。
当前是36/100。
如果你也想了解AI应用开发到底是什么,如果你也在考虑转型但还在犹豫,那就跟着我的记录,一起探索。
继续折腾中,有问题随时交流 🤝
微信号:ThinkFun666
我的免费AI编程交流群,欢迎加入讨论AI编程玩法(说明需求和来意,申请入群)
推荐阅读:
我就喜欢干这种傻事!为什么转型AI应用开发,我不建议你直接从看书开始学起?

