游乐游手机版
首页/AI热点日报/热点详情

利用混合搜索与重排序改进RAG检索效果(含代码示例)

类型:热点整理2026-07-07
RAG 在实际应用中,检索环节到底存在哪些“坑”?一个典型的场景是:精确匹配特定标识符(ID)。很多业务数据都带有唯一 ID,比如包含数字和字母的复杂组合,长度轻轻松松超过 10 个字符。这时候,如果只依赖基于向量的语义搜索(比如用 PGVector 存嵌入向量做相似性检索),结果往往差强人意。 语

RAG 在实际应用中,检索环节到底存在哪些“坑”?一个典型的场景是:精确匹配特定标识符(ID)。很多业务数据都带有唯一 ID,比如包含数字和字母的复杂组合,长度轻轻松松超过 10 个字符。这时候,如果只依赖基于向量的语义搜索(比如用 PGVector 存嵌入向量做相似性检索),结果往往差强人意。

语义搜索靠的是向量之间的软相似性来排序——计算余弦相似度,返回前 k 个最接近的文本块。处理语义相近的文本时确实好用,但碰上 ID 这种需要严格一致的活儿,就不灵了。举个具体例子:查询“1234567890XYZ”时,语义搜索可能把“1234567890ABC”排在前面,因为它俩在向量空间里距离不远;真正要的那个反而因为嵌入时的细微差异被挤到后面,甚至直接出局。这个问题在医疗记录查询(根据患者 ID 查病历)、金融交易追溯(按交易 ID 拿详情)等场景里,后果相当严重——检索结果错了,后续生成就全偏了。

怎么破?混合搜索是一条切实可行的路——把多种搜索技术组合起来,取长补短。具体分几步走:

基于 PGVector 的语义搜索

PGVector 是 PostgreSQL 里专门存向量和做相似性查询的扩展。第一步用它做语义搜索,能快速找到和查询语义相近的文本块。可以选一个合适的嵌入模型(比如 JinaAI 的“jina-embeddings-v2-base-en”),把文档和查询都转成向量,然后在数据库里高效检索。比如在新闻文章库里搜“科技行业最新动态”,PGVector 能迅速返回语义上相关的片段。但正如前面说的,它对精确 ID 匹配力不从心,所以需要其他搜索来补位。

PostgreSQL 全文搜索

这一步专门对付精确匹配和部分匹配。PostgreSQL 自带的全文搜索功能很强,正好弥补语义搜索的短板。

  • 精确匹配:用 to_tsvectorplainto_tsquery 函数,确保查询的 ID 在文本块里一个字符都不差地出现。比如查“1234567890XYZ”,to_tsvector('english', content) @@ plainto_tsquery('english', '1234567890XYZ') 这条语句能在 documents 表的 content 字段里找到完全匹配的记录。
  • 部分匹配:如果精确匹配没结果,就用 ILIKE 操作符做模糊匹配,例如 content ILIKE '%1234567890XYZ%'。这样能处理 ID 带拼写错误或格式变体的情况,尽量扩大召回范围。

合并结果

语义搜索和全文搜索的结果可能有重叠,需要去重。具体实现上,在 Python 里可以用集合或字典来合并,按文档 ID 或内容去重。合并后的集合既包含语义相关的,也包含文本精确匹配的,给后续重排序提供更全面的素材。

使用 Flash Re-Ranker 重排序

混合搜索拿到的结果,顺序不一定最优——最相关的可能排在后面。Flash Re-Ranker 就是干这个的:它用更复杂的算法,综合考虑语义、词汇匹配度、上下文等因素,对合并后的结果重新排序,把最相关的文本块推上前列。比如在产品描述库里搜特定产品 ID,它能把精确命中并且语义最匹配的描述排在最前面。

基于 LangChain 的实现

LangChain 这个框架很适合整合检索和文本处理流程。下面是一套完整的实现示例:

(一)基于 PGVector 的语义搜索实现

from langchain.vectorstores import PGVector
from jinaai import Embedding

# 初始化PGVector
vector_store = PGVector(
    connection_string="postgresql://myuser:mypassword@localhost/mydb",
    embedding_function=Embedding(model="jina-embeddings-v2-base-en")
)

# 执行语义搜索
query = "1234567890XYZ"
semantic_results = vector_store.similarity_search(query, k=10)
print(semantic_results)

代码中先用 PGVector 类创建向量存储对象,指定连接字符串和嵌入函数,然后用 similarity_search 方法做语义搜索,k 控制返回的结果数量。

(二)PostgreSQL 全文搜索实现

import psycopg2

# 定义自定义停用词
STOP_WORDS = {
    "a", "an", "and", "are", "as", "at", "be", "but", "by", "for", "if",
    "in", "into", "is", "it", "no", "not", "of", "on", "or", "such", "that",
    "the", "their", "then", "there", "these", "they", "this", "to", "was",
    "will", "with", "me", "my", "you", "your", "we", "our", "us", "he",
    "him", "his", "she", "her", "hers", "it", "its", "them", "so", "too"
}

def filter_stop_words(query):
    """从输入查询中去除停用词。"""
    words = query.split()
    filtered_words = [word for word in words if word.lower() not in STOP_WORDS]
    return " ".join(filtered_words) if filtered_words else query

def perform_full_text_search(query):
    conn = psycopg2.connect("dbname=mydb user=myuser password=mypassword host=localhost")
    cursor = conn.cursor()

    filtered_query = filter_stop_words(query)
    print(f"原始查询: {query}")
    print(f"过滤后的查询(不含停用词): {filtered_query}")

    # 精确匹配(过滤停用词后)
    cursor.execute(
        "SELECT * FROM documents WHERE to_tsvector('english', content) @@ plainto_tsquery('english', %s);",
        (filtered_query,)
    )
    exact_results = cursor.fetchall()

    # 部分匹配(ILIKE,不过滤停用词)
    cursor.execute("SELECT * FROM documents WHERE content ILIKE %s;", (f"%{query}%",))
    partial_results = cursor.fetchall()

    cursor.close()
    conn.close()
    return exact_results + partial_results

query = "I want to see details of 1234567890XYZ"
retrieved_chunks = perform_full_text_search(query)
print("结果:", retrieved_chunks)

这段代码先定义停用词集合,写了一个过滤函数 filter_stop_words,然后 perform_full_text_search 函数里分别做精确匹配(过滤停用词后)和部分匹配(ILIKE),最后合并返回。

(三)合并和重排序结果

from flashrank import FlashRanker

def re_rank_results(semantic_results, text_results):
    ranker = FlashRanker()
    semantic_texts = [{"text": doc.page_content} for doc in semantic_results]
    text_texts = [{"text": row[1]} for row in text_results]  # 假设文本在第二列
    combined_results = semantic_texts + text_texts
    reranked_results = ranker.rank(combined_results)
    return reranked_results

final_results = re_rank_results(semantic_results, retrieved_chunks)
print(final_results)

re_rank_results 函数提取语义和文本搜索的结果,合并后调用 Flash Re-Ranker 的 rank 方法完成重排序。

重要考虑因素

整个流程里有几个细节值得注意:

自定义停用词

全文搜索里停用词处理很关键。停用词是那些出现频率高但对检索没帮助的词(a、the、and 等)。自定义停用词列表可以根据场景灵活调整:科技文献里某些常用词可能很有意义,就需要移除;代码注释里则可能要把特殊符号加进去。合理的停用词配置能明显提升检索准确率。

PostgreSQL 配置

PostgreSQL 内置了多种语言的停用词列表,还支持创建自定义词典来实现更精细的处理。根据文本语言和应用场景选择合适的内置列表,或者建自定义词典来标记特殊词汇或做词形转换,能进一步优化全文搜索性能。

性能优化

去除停用词不仅能提高准确率,还能加快搜索速度——尤其是在处理大规模数据集时。去掉那些低频词汇后,数据库要处理的词数减少,索引也可以更小更高效。实际应用中需要根据数据集规模和特性,在停用词处理复杂度和性能提升之间做好平衡。

回到检索精度这个核心问题:通过 PGVector 语义搜索 + PostgreSQL 全文搜索 + Flash Re-Ranker 重排序的混合方案,能有效解决特定 ID 匹配不准确的老大难问题,让 RAG 系统在真实业务场景中跑得更稳、更准。

来源:https://www.53ai.com/news/RAG/2025042186197.html

相关热点

继续查看同栏目近期热点。

延伸阅读

补充最近整理过的热点入口。