RAG效果不佳?深究五大关键环节,优化不必急于微调模型

RAG优化别急着微调!聚焦分块、索引、编码、混合检索与重排五大关键节点,提升AI应用性能。

原文标题:RAG效果不佳?先别急着微调模型,这几个关键节点才是优化重点

原文作者:阿里云开发者

冷月清谈:

本文深入剖析了RAG(Retrieval Augmented Generation)技术在AI应用开发中的优化策略。文章指出,RAG效果不佳时,不应盲目微调模型,而应聚焦于检索链路中的多个关键节点。主要优化点包括:文档分块的精细化处理,如语义、多模态及Agentic Chunking等,以确保知识的有效组织;索引增强,通过大模型生成摘要或假想问题来提升后续检索的召回与精确性;对编码(Embedding)环节,需考量模型语言、词汇表及特定领域语义空间的影响;在检索阶段,推荐采用结合关键词匹配与语义相似度的混合搜索(Hybrid Search),以平衡召回率与精确率;最后,通过重排序(Re-Ranking)利用交叉编码器对初步检索结果进行二次筛选,确保最终提供的上下文高度相关。文章倡导从快速使用走向深度优化的实践路径,强调须结合具体场景综合调优,而非将RAG视为黑盒。

怜星夜思:

1、关于分块策略:文章提到好几种分块方法,比如语义分块、多模态分块,甚至还有Agentic Chunking。如果我现在要处理的知识库特别“花”,比如既有很长的技术文档,又有不少图表、代码片段,甚至还有视频链接,我该怎么去选择或者组合这些分块策略,才能效率最高、效果最好呢?是不是没有“万能药”?
2、混合检索的边界:混合检索听起来很高级,结合了关键词和语义。但在实际应用中,有没有那种场景是纯语义检索就足够了,或者纯关键词检索反而更优,根本不需要混合检索?什么时候这个“混合”是锦上添花,什么时候是必不可少救命稻草?
3、大模型+RAG的未来:文章强调RAG优化是提升AI应用效果的关键,甚至建议先别急着微调大模型。那是不是说,对于“开箱即用”的大模型,只要把RAG链路优化好,就几乎能满足所有需求了?或者说,未来RAG技术再怎么进化,大模型微调的价值和必要性依然不可替代,只是大家分工不同了?

原文内容

阿里妹导读


本文深入探讨了RAG(Retrieval Augmented Generation)技术的实现细节与优化策略,指出在AI应用开发中,RAG常被视为黑盒导致问题定位困难。文章从文档分块(Chunking)、索引增强(语义增强与反向HyDE)、编码(Embedding)、混合检索(Hybrid Search)到重排序(Re-Ranking)等关键环节进行了详细解析,强调需结合具体场景对各模块进行调优,以提升召回率与精确率的平衡,并倡导从快速使用走向深度优化的实践路径。

写在前面

随着AI应用开发的普及,RAG成了一个家喻户晓的词,非常朴实且出镜率极高,不过在平时也会经常听到一些声音,“RAG效果不好,可能需要微调模型”,“xxx上的RAG产品不好用,召回不精准”,“我需要更强大的RAG工具,不然这个效果很难提升”等等,首先可以肯定,光从上述一些话术中不能说明大家对于RAG的实践和结论是有问题的,但是当进一步沟通的时候,比较多的case中会发现比较那回答出“为什么你觉得RAG不好用?”,“有实际case吗?比如什么样的query召回了什么样的知识?”,“我们的文档是怎么组织的?如何编写的?有做一些结构和分块上的处理吗?”。

日常我们会比较多的把RAG当成一个黑盒,输入是我们沉淀的文档,输出可能是整个AI应用反馈的最终结果(如下图所示),这样的方式下,我们可能可以收获一定的初期收益,但是当要持续优化或者扩展使用场景的时候,可能会缺乏评估和应对的方式,比较难去定位问题,因此也不太能说清楚当下链路的诉求,最后所对应的action也可能会偏离比较大。

Fig1.RAG链路图-粗粒度版,有缺失

下面想稍微深入探索下RAG链路和涉及的一些技术细节,希望可以给到实践中的小伙伴一些参考,从而可以更好的诊断问题、找到可优化的节点,做出更合理的迭代设计。

重新介绍下RAG

RAG核心的功能就是针对用户Query,补充和Query相关的且模型没有的信息,同时要在两个维度上做要求:1.召回率:能够找到最相关的信息;2.精确率:不相关的信息不要;RAG技术以及我们针对实践的设计,主要就是锚定这两个维度指标的提升去的,同时这两个指标在现实实践中是个权衡,需要找到一个相对适合的值,追求两者都很高,不太现实,或者说可能需要付出的代价不足以让我们这么去做。

RAG是RetrievalAugmentationGeneration三个词的缩写,代表了三个核心的行为Retrieve-检索、Augment-增强以及Generate-生成,同时在三个核心动作之外,还有一个embedding-编码,具体见下图:

Fig2.RAG链路图-细粒度版

下面分别针对笔者觉得可能影响RAG实践中效果的一些技术点(图中标有编号的部分),做进一步的描述。

1. 文档分块-Chunking

所谓兵马未动粮草先行,要有一个好的检索结果,首先要从我们的知识文档的优化开始,我们实践中比较重视知识文档的内容沉淀,但是在一些文档结构组织,段落划分,以及一些知识点的内聚性和正交性上会涉及少一点。我们来看下一份文档在语义chunking(基础的按照token、字符、语句、段落切分大部分情况下效果都比较局限)下是被如何处理的。

# 设置本地数据保存目录
local_data_dir = pathlib.Path("/Users/jiangdanyang/workspaces/python/MarioPython/src/RAG/dataset/ai-arxiv2")
# 加载数据集(如果本地存在则从本地加载,否则下载)
dataset = load_dataset("jamescalam/ai-arxiv2", split="train", cache_dir=str(local_data_dir))

初始化编码器

encoder = OpenAIEncoder(
   name=“text-embedding-3-small”,
   openai_api_key=os.getenv(“AI_API_KEY”),
   openai_base_url=os.getenv(“AI_API_BASE_URL”)
)

chunker = StatisticalChunker(
    encoder=encoder,
    min_split_tokens=100,
    max_split_tokens=500,
    plot_chunks=True,
    enable_statistics=True,
)

chunks_0 = chunker(docs=[dataset[“content”][0]], batch_size=500)

例子中是针对一篇论文做chunking,chunking会设置min_split_tokens(最小chunk的tokens数)和max_split_tokens(最大chunk的tokens数),chunking完之后的统计结果可见下面的图和表:

Chunking Statistics:
  - Total Documents: 474
  - Total Chunks: 46
  - Chunks by Threshold: 41
  - Chunks by Max Chunk Size: 4
  - Last Chunk: 1
  - Minimum Token Size of Chunk: 54
  - Maximum Token Size of Chunk: 495
  - Similarity Chunk Ratio: 0.89

可以先看下统计的结果文字描述,简单做下解释:

  • 整体Documents(可以简单理解为句子数,本部分设计的document都是该含义):474;

  • 整体切分的文件块chunk:46个,其中41chunk的切分是基于相似度的阈值(可以理解为是按照语义正常划分出来的),有4个是因为达到了500tokens数量被切分的,还有最后1个chunk是到文章结尾了;

  • 最大的chunk的token数495个,最小的chunk的token数54个,因为是lastchunk,所以会出现小于min_split_tokens的情况;

  • 最后SimilarityChunkRatio是统计这次切分的chunk,89%的chunk是按照语义切分出来的(41/46);

SimilarityChunkRatio可以比较好的说明当前外部文档的chunking的结果,因为试想都是被max_split_tokens卡主划分的chunk,后续在语义检索的时候,结果也不会太好。实践中需要针对你的文档情况,调整split的token大小,在chunk的数量和相关性比例上达到一个平衡;除了chunk的大小,还有两个值需要关注:

  • Threshold,就是所谓的相似度的下限,上面例子中threshold是0.31,该值越大,chunk内的相关性越好;

  • WindowSize,是被用于计算的document的数量大小,默认是5,即每次是选择连续的5个document计算相似度,windowsize设置越大,上下文切分的相关性越好,但是同时chunking过程的计算量和耗时也更高,chunk大小相对要大;

这篇论文Meta-Chunking: Learning Text Segmentation and Semantic Completion via Logical Perception也给出了一个基于逻辑和语义的chunking的方法,有完整的效果评测,可参考;除了semantic Chunking之外,还有面向多模态数据文档类型的Modality-Specific Chunking(可以比较好的区分不同内容类型的文档块,并面向文本、表格、代码、图使用不同的chunking策略)和Agentic Chunking(让能力强的LLM阅读全文,判断给出切分策略),上述都是工具箱里面的工具,实践中需要结合自身的场景、知识现状、成本综合去权衡选择,并且面向效果进行调优或者切换更适合的方式。

2. 索引增强-Indexing

索引增强,这里介绍两种类型:1.语义增强;2.反向HyDE

语义增强

语义增强就是将chunk和该chunk所在的文档内容(这里是整片论文)传给LLM,让LLM结合整个文档对这段chunk作个概述,然后把这个概述的信息append到chunk的内容中,从而增强在后续进行语义检索时的精确性。

DOCUMENT_CONTEXT_PROMPT = """
<document>
{doc_content}
</document>
"""

CHUNK_CONTEXT_PROMPT = “”"
Here is the chunk we want to situate within the whole document
<chunk>
{chunk_content}
</chunk>

Please give a short succinct context to situate this chunk within the overall document for the purposes of improving search retrieval of the chunk.
Answer only with the succinct context and nothing else.
“”"

这里的LLM选择需要能力比较强的大模型,最好可以有promptcache功能,这样可以大大节省这一部的模型调用开销;同时也有一些做法是可以增加前后两个chunk的内容,对于整体文档比较长且前后文本关联度比较大的场景会有一些增强的效果。

反向HyDE

HyDE,Hypothetical Document Embeddings,是正向query检索增强的一种方式,即可以针对用户的query,生成一些假设的答案或者做query扩写,然后通过这些中间内容去做检索召回;反向HyDE的意思是针对chunk(可以视为answer),生成这块chunk可能的question,然后针对这些quetion进行索引构建,关联到具体的chunk内容;反向HyDE相比HyDE的优势是可以离线处理,不影响实时调用的rt。

Given the following text chunk, generate {n} different questions that this chunk would be a good answer to:

Chunk: {chunk}

Questions (enumarate the questions with 1.2., etc.):

该方法比较适合这类问答型的知识,比如一些答疑内容,有明确的A和Q的,或者可以作为后面hybridsearch中的关键词扩写,提升后续混合检索的效果。

3. 编码-Embedding

Embedding大家应该都很熟悉,就是将输入的文本(多模态内容)转换成向量,主要过程包含文字到token的切分,然后每个token在词汇表中有对应的id,每个tokenid都会对应同等维度(不同embedding模型维度不同)的向量,可以看个简单的例子。

first_sentence = "直播分享会AI Living第一场"
second_sentence = "直播鲁班小组分享第77期"

model = SentenceTransformer(“/Users/jiangdanyang/workspaces/python/Model/all-MiniLM-L6-v2”)
tokenized_first_sentence = model.tokenize([first_sentence])
tokenized_second_sentence = model.tokenize([second_sentence])

编码之后是当前文本对应的tokens的tokenid列表,这里影响编码的原因有这些:

1. 编码模型的语言问题,不同语言会有不同的分词和词汇表,比如例子中使用的这个编码模型all-MiniLM-L6-v2在处理中文的文本时候,就比较差,可以看到返回的id有好些100(不可识别的token),中文的处理可以找相应的中文embedding模型,但是不是所有语言都有对应的编码模型,因为语种太多,同时如果一些语种对应的数据语料太少,不足以训练这样的一个模型。

2. 编码模型的词汇表大小,例子中的all-MiniLM-L6-v2的词汇表大小是30522,有些主流模型的词汇表大小基本都在5w以上,有些10w以上,词汇表小会导致一些词无法表示,只能用一个兜底tokenid来代替,会影响后续处理的效果;词汇表大能精准标识文本的输入,但是间接也会增加文本编码完之后的token大小。

3. 编码模型的语义空间,不同的编码模型有自己的词汇表,以及自己对应的向量语义空间,向量语义空间的效果决定于该模型训练基于的数据集,目前用于文本编码的模型,基本都是现有世界知识的通用语义空间,偏日常、大众化的关联,如果我们需要在一个特定领域下,有一个特殊的语义空间,可能就需要找一个使用该领域下的数据训练的embedding模型,或者需要自己SFT一个,不然预期想要的效果和实际效果可能会有比较大的gap。(顺便说下图知识的问题,直接拿图片当知识,处理过程可能就是OCR的文本提取,或者是LLM对于图片的理解描述,但是这里的干扰会很大,比如中间过程的文本,是不是你期望的样子和描述的维度,这些都需要把握下,不然后续的检索召回肯定也是一团浆糊)

4. 检索-HybridSearch

HyBridSearch,混合搜索,本质上是结合了Term-based和Semantic-based两种模式的检索特性,通过融合两种形式的算法,来提升检索的准确性和相关性;HybridSearch结合了SparseVector(稀疏向量)相似度计算----关键词匹配和DenseVector稠密向量相似度计算----语义匹配,从而提升检索的效果,可见下图:

Sparse向量主要是通过BM25为代表的算法生成,BM25核心就是TF-IDF算法(词频-反向文档频率),返回是某个query相对每个文档编号的分数值(具体算法如下)。

图片
# Load the chunks
corpus_json = json.load(open('/Users/jiangdanyang/workspaces/python/MarioPython/src/RAG/dataset/corpus.json'))
corpus_text = [doc["text"] for doc in corpus_json]

optional: create a stemmer

english_stemmer = snowballstemmer.stemmer(“english”)

Initialize the Tokenizer with the stemmer

sparse_tokenizer = Tokenizer(
    stemmer=english_stemmer,
    lower=True, # lowercase the tokens
    stopwords=“english”,# or pass a list of stopwords
    splitter=r"\w+“,# by default r”(?u)\b\w\w+\b", can also be a function
)

Tokenize the corpus

corpus_sparse_tokens = (
    sparse_tokenizer
    .tokenize(
        corpus_text, 
        update_vocab=True, # update the vocab as we tokenize
        return_as=“ids”
    )
)

Create the BM25 retriever and attach your corpus_json to it

sparse_index = bm25s.BM25(corpus=corpus_json)

Now, index the corpus_tokens (the corpus_json is not used yet)

sparse_index.index(corpus_sparse_tokens)

Return 10 the most relevant docs according to the query

sparse_results, sparse_scores = sparse_index.retrieve(query_tokens, k=10)

Dense向量主要是通过基于Transformer架构的embedding模型来进行编码生成,同时针对查询query,使用同样的embedding模型进行编码,然后再进行向量的相似度比对,找出最相似的n个结果。

#Dense Index
# create the vector database client
qdrant = 
QdrantClient(path="/Users/jiangdanyang/workspaces/python/MarioPython/src/RAG/dataset/qdrant_data")
# Create the embedding encoder
dense_encoder = SentenceTransformer('/Users/jiangdanyang/workspaces/python/Model/all-MiniLM-L6-v2')

collection_name = “hybrid_search”
qdrant.recreate_collection(
    collection_name=collection_name,
        vectors_config=models.VectorParams(
        size=dense_encoder.get_sentence_embedding_dimension(), 
        distance=models.Distance.COSINE
    )
)

vectorize!

qdrant.upload_points(
    collection_name=collection_name,
    points=[
        models.PointStruct(
            id=idx,
            vector=dense_encoder.encode(doc[“text”]).tolist(),
            payload=doc
        ) for idx, doc in enumerate(corpus_json) # data is the variable holding all the enriched texts
    ]
)

query_vector = dense_encoder.encode(query).tolist()
dense_results = qdrant.search(
    collection_name=collection_name,
    query_vector=query_vector,
    limit=10
)

最后针对上述两种方式找出的chunk做综合筛选,这里可以有多种方式,比如比较常用的就是先分别对Sparse向量和Dense向量计算出来的topn个结果的分值做归一化,然后针对统一个Chunk,按照一定的权重(比如Sparse向量计算结果权重0.2,Dense向量计算结果权重0.8)计算一个最终分值,最后返回topn个chunk列表给到下个节点:

# Normalize the two types of scores
dense_scores = np.array([doc.get("dense_score", 0) for doc in documents_with_scores])
sparse_scores = np.array([doc.get("sparse_score", 0) for doc in documents_with_scores])

dense_scores_normalized = (dense_scores - np.min(dense_scores)) / (np.max(dense_scores) - np.min(dense_scores))
sparse_scores_normalized = (sparse_scores - np.min(sparse_scores)) / (np.max(sparse_scores) - np.min(sparse_scores))

alpha = 0.2
weighted_scores = (1 - alpha) * dense_scores_normalized + alpha * sparse_scores_normalized

如果当前场景的检索需要兼顾关键词和语义的时候,可以考虑混合搜索(需要结合文档内容、chunking和关键字词构建等环节);相对于关键字词匹配检索,混合搜索可以降低查询编写的规范性(不一定要有特定的关键词出现)以及提升查询的容错性(可能会有拼写错误或者不恰当的描述);相对于语义相似检索,混合搜索可以增加一些领域专有信息的更精准匹配,提升检索结果的准确性。

5. 重排-ReRanking

检索的优点是可以在海量的知识里面快速找到和用户query相关的内容块docs,但是检索所返回出来的docs,实际上可能部分和用户query关联度并不大,这个时候就需要通过re-rank这一步,对于检索返回出来的docs做关联度排序,最终选取最相关的topk个doc,做后续的上下文补充。

在RAG链路中,ReRanking的常用技术是Cross-Encoder(交叉编码器),本质一个Bert模型(Encode-only的transformer架构),计算query和每一个doc相关性,返回0~1之间的结果(1代表最相关),示意图和代码示例如下:

from sentence_transformers import CrossEncoder 

cross_encoder = CrossEncoder(“/Users/jiangdanyang/workspaces/python/Model/jina-reranker-v1-tiny-en”)
hybrid_search_results = {}
with open(‘/Users/jiangdanyang/workspaces/python/MarioPython/src/RAG/dataset/dense_results.json’) as f:
 dense_results = json.load(f)
    for doc in dense_results:
        hybrid_search_results[doc[‘id’]] = doc

with open(‘/Users/jiangdanyang/workspaces/python/MarioPython/src/RAG/dataset/sparse_results.json’) as f:
    sparse_results = json.load(f)
    for doc in sparse_results:
        hybrid_search_results[doc[‘id’]] = doc

console.print(hybrid_search_results)

This is the query that we used for the retrieval of the above documents

query = “What is context size of Mixtral?”
pairs = [[query, doc[‘text’]] for doc in hybrid_search_results.values()] 
scores = cross_encoder.predict(pairs) 

图片

最后进行排序,选择topk个结果补充到context中,然后调用模型拿最后的结果:

client = OpenAI(
   api_key=os.getenv("AI_API_KEY"),
   base_url=os.getenv("AI_API_BASE_URL")
)
completion = client.chat.completions.create(
    model="qwen_max",
    messages=[
        {"role": "system", "content": "You are chatbot, an research expert. Your top priority is to help guide users to understand reserach papers."},
        {"role": "user", "content": query},
        {"role": "assistant", "content": str(search_results)}
    ]
)

结语

AI应用的开发实践进行得非常火热,现阶段可能更多的是对已有的一些基建平台、开发编排工具、现成的横向基础产品做整合使用,结合使用场景做链路设计。但是随着时间推移,还是需要慢慢深入到部分细节,往深水区慢慢前行,本文讲述的RAG只是AI架构中的一块,其他相关的技术,在对待方式上也雷同,都需要经历快速使用、技术细节了解、使用产品实现了解、应用中的设计实现迭代、面向效果的循环优化,快速上手有捷径,得益于比较好的基础设施建设,成本比较低,但是深入追寻效果,切实提升效率或幸福感,需要更深入的探寻,希望对读到这里的小伙伴有帮助。

参考资料

  • https://weaviate.io/blog/hybrid-search-explained

  • https://www.sbert.net/examples/sentence_transformer/applications/retrieve_rerank/README.html

  • https://learning.oreilly.com/videos/advanced-rag/11122024VIDEOPAIML/

创意加速器:AI 绘画创作


本方案展示了如何利用自研的通义万相 AIGC 技术在 Web 服务中实现先进的图像生成。其中包括文本到图像、涂鸦转换、人像风格重塑以及人物写真创建等功能。这些能力可以加快艺术家和设计师的创作流程,提高创意效率。


点击阅读原文查看详情。


关于大模型+RAG的未来:这个问题简直是“大模型选秀大赛”的终极拷问!RAG就像是给一个天赋异禀但偶尔“掉线”的选手配了个“题词器”和“百科全书”,让它能稳住场子、回答准确。而微调嘛,就像是请了个专业的“形体老师”和“声乐老师”,把选手的气质、腔调、舞台表现力都给打磨出来。如果你只是想让选手不跑调,RAG可以帮你。但如果你想让选手有“灵魂”,能成为“顶流”,那微调的个性化定制是必须的。所以说,RAG是保底,微调是冲顶!未来一定是“实力(RAG)+魅力(微调)”的组合出道!

哈哈,这个问题太真实了!我司之前也遇到过,那种PDF里代码、图、文字混排的文档最头疼!我们走了不少弯路。我的经验是,一开始别想着一步到位搞出“完美”方案。先用最基础的分段+固定长度分块跑一下,看看效果。然后,根据实际检索失败的案例,倒推我们分块是不是有问题。比如,看到一个表格的关键信息被分到两个chunk里,那我就知道表格需要特殊处理。代码我们一般会单独抽出来,甚至用代码解析工具先解析成AST再分块。图像的话,如果只是插图,简单描述一下图片内容做索引就行;如果是信息图,可能需要VLM抽取出信息点。总之,是“边用边调”,没有银弹,得根据你内容的核心价值和检索目标来决定。

针对复杂的知识库,单一分块策略确实难以完美应对。在处理多模态、混合结构数据时,一种有效方法是采用分层或组合策略。例如,对技术文档可首先通过Modality-Specific Chunking识别和分离文本、代码、表格、图像等不同元素。文本部分再结合Agentic Chunking,利用更强大的LLM进行“逻辑感知”的语义切分,确保完整概念不被割裂。代码片段可按功能模块或文件进行分块,并提取关键注释作为元数据。图表则需要结合OCR或VLM(Vision-Language Model)提取信息,并关联到其所属文本。最终,这些不同来源的Chunk需要通过元数据管理系统进行关联,形成一个内聚且可追溯的知识图谱,以支持后续的融合检索。

我理解RAG和微调就像“喂饭”和“教做饭”。RAG是给大模型喂最新的、最精准的“饭”(知识),让它吃得饱,不至于说胡话。而微调是教大模型“如何做好一顿饭”,让它不仅能吃饱,还能做出符合你口味(输出风格、逻辑、对特定指令的理解)的菜。如果只是想回答事实性问题,RAG搞得好基本就够了。但如果像我们公司内部需要模型用特定的术语、语气和用户沟通,或者需要它理解一些非常特殊的业务流程,那微调就是绕不开的。特别是在模型犯了原则性错误,或者输出风格和品牌调性不符时,微调的效果更直接。所以,两者不是替代关系,而是相互成就,看你把应用做到多深。

对于“开箱即用”的大模型而言,RAG链路的深度优化确实能极大提升其在特定领域知识下的表现,使其在信息准确性和时效性上达到接近甚至优于通过微调小范围数据训练的模型的效果。然而,大模型微调的价值在于引导模型习得特定风格、语气、安全边界,或使其更好地理解和响应领域特定的复杂指令,这些并非单纯依靠RAG补充上下文就能完全解决。例如,若需模型以科研论文的严谨风格进行输出,或严格遵循企业内部的沟通规范,微调是不可或缺的手段。未来趋势可能是RAG与微调的协同发展,RAG负责知识的更新与事实性补充,而微调则负责模型行为与风格的定制化。

混合检索的核心价值在于克服单一检索模式的局限性。纯关键词检索在用户查询与文档存在精确词汇匹配时效率高且可控,例如查询特定API名称或法规条文。但其对同义词、近义词,以及语义理解能力不足。纯语义检索则擅长处理泛化性、描述性查询,理解用户意图,但可能在特定实体或精确命名匹配时因其高维度泛化性而稀释特异性。混合检索在大多数复杂场景下是必要的,尤其当用户查询模糊性高、文档内容涵盖多领域词汇、且对检索结果的精确度和召回率均有较高要求时。例如,在技术支持场景中,用户可能用不同术语描述同一问题(语义需求),也可能查询具体错误码(关键词需求),此时混合检索能提供更鲁棒的结果。

关于分块策略:这不就是LSD(Large-Scale Data)的日常吗?我的建议是,先搞清楚你家数据到底有多“花”。是不是那种长得像说明书,实际上又像百科全书,偶尔还夹带着几行代码和表情包的奇葩文档?如果真是这样,我觉得可以尝试“放养”策略:让几个不同特长的“孩子”(不同分块器)各自去“领养”一部分它们擅长处理的数据。文本的分给语义小能手,图片的分给视觉侦探,代码的交给语法高手。最后再让一个“管家”(LLM)来协调,看看哪个“孩子”分得最合理。毕竟,一个分块器想通吃,那真是“强人所难”啊!

咱们做产品,最怕的就是用户问一个词,结果出来一堆不相关的。我们碰到过好几次,用户搜“如何重启服务”,结果因为“重启”这个词在文档里出现频率太高,导致搜出来全是日志文件、配置说明啥的。这时候纯关键词肯定不行,得靠语义。但反过来,如果用户搜的是某个产品型号“GTX3080的驱动下载”,纯语义可能因为“GTX3080”这个词很具体,反而不如直接关键词匹配来得快准狠。所以我的经验是,如果你的用户大部分是“小白”,查询比较模糊,那就多加点语义权重;如果用户都是“老司机”,知道自己要啥,那关键词权重可以考虑高一点。混合检索更适合那种“啥用户都有、啥问题都得答”的通用型知识库。

关于混合检索的边界:什么时候混合检索是救命稻草?大概就是你问了一个问题,但你自己都不知道想问啥,或者你问得太“优雅”了(比如“这个系统怎么好像不太顺滑?”)!这时候光靠关键词,模型可能得给你找一堆润滑油广告。光靠语义又可能给你一堆哲学思考。所以混合检索就像一个“超级侦探”,既能听懂你的弦外之音(语义),又能抓住你偶尔冒出来的“暗号”(关键词)。如果你的信息是“1+1=2”这种铁板钉钉的事实,那一个“小学生”(纯关键词)就能搞定。但如果你的信息是“人生的意义”,那大概只有“哲学家”(混合检索)能给你一些靠谱的“线索”了。