← 返回博客
·AI技术

RAG系统调优的血泪史:从50%到90%准确率的进化

一个从零搭建RAG系统的开发者,分享如何从"勉强能用"优化到"真正好用"的实战经验。没有理论堆砌,全是踩坑心得。

#RAG#LLM#向量检索

去年接了个活,给一家企业做内部知识库问答系统。客户要求不高:"能让员工问公司的规章制度就行"。

我一开始觉得这活简单——用现成的RAG框架,接个大模型,几天就能搞定。结果做了才发现,RAG的水深得离谱。

从最初的50%准确率(员工试了一圈都说"还不如自己翻文档"),到最后的90%(客户主动问"能不能扩展到其他业务"),中间踩的坑够写本书了。

第一版:能跑,但不好用

我的第一版架构很标准:

  • 文档切块:每块500字
  • 向量模型:`bge-large-zh-v1.5`
  • 向量数据库:Pinecone
  • 大模型:GPT-4
  • 跑起来之后,员工反馈:"经常答非所问,问A它答B,要不就是编造不存在的政策"。

    我做了个测试,拿了100个真实问题,手动评估答案质量。结果只有52个回答是准确或者基本准确的。

    这还叫RAG?这不就是个随机回答生成器吗。

    第一个优化:文档切块的方式不对

    我一开始的切块策略是:固定长度,每块500字,重叠50字。

    这看着很标准,但问题很大。因为我的文档是企业规章制度,里面有大量像这样的内容:

    第十条 员工请假须提前3个工作日提交申请,

    特殊情况可酌情放宽。

    第十一条 请假审批流程:

    (一)3天以内,部门主管审批;

    (二)3天以上,分管领导审批。

    按500字切的话,第十条和第十一条可能被分到两块里。当用户问"请假要提前多久",检索到的可能只有第十条前半部分,关键信息丢了。

    **改进方案**:我改成了按段落和章节边界来切。先识别文档的自然分段(通过换行、编号、标题等),然后在这些边界处切。每块尽量保持逻辑完整性。

    改完之后,准确率从52%提到了68%。

    第二个优化:向量检索不够用

    向量检索的问题是:它找的是"相似",不是"相关"。

    用户问"请假的审批流程是什么",向量检索可能找到的是另一条也带"流程"字样的规章,但和请假没关系。

    **改进方案**:加了关键词检索作为补充。具体做法是:

  • 用jieba提取问题中的关键词
  • 2. 用BM25做关键词检索

    3. 把向量检索和BM25的结果合并,按加权分数排序

    def hybrid_search(query, top_k=5):

    # 向量检索

    vec_results = vector_store.search(query, k=top_k * 2)

    # 关键词检索

    kw_results = bm25_store.search(query, k=top_k * 2)

    # 合并去重

    all_docs = {}

    for doc, score in vec_results:

    all_docs[doc.id] = {'doc': doc, 'vec_score': score, 'kw_score': 0}

    for doc, score in kw_results:

    if doc.id in all_docs:

    all_docs[doc.id]['kw_score'] = score

    else:

    all_docs[doc.id] = {'doc': doc, 'vec_score': 0, 'kw_score': score}

    # 加权计算最终分数

    for item in all_docs.values():

    item['final_score'] = 0.6 * item['vec_score'] + 0.4 * item['kw_score']

    # 排序返回

    sorted_docs = sorted(all_docs.values(), key=lambda x: x['final_score'], reverse=True)

    return [(item['doc'], item['final_score']) for item in sorted_docs[:top_k]]

    改完之后,准确率从68%提到了78%。

    第三个优化:重排序才是关键

    混合检索解决了"找得不够准"的问题,但还有个更隐蔽的问题:最相关的文档不一定排在最前面。

    比如用户问"出差报销的标准",混合检索可能把"出差管理规定"排在第一,"财务报销标准"排在第二。但实际上,用户要的答案在第二个文档里。

    **改进方案**:加了一个重排序模型。具体是用一个cross-encoder模型,把问题和检索到的文档一起喂进去,让它重新打分排序。

    from sentence_transformers import CrossEncoder

    reranker = CrossEncoder('BAAI/bge-reranker-large')

    def rerank(query, docs, top_k=5):

    pairs = [[query, doc.content] for doc in docs]

    scores = reranker.predict(pairs)

    ranked = sorted(zip(docs, scores), key=lambda x: x[1], reverse=True)

    return [doc for doc, _ in ranked[:top_k]]

    这一步改动最小,但效果最好。准确率从78%直接跳到88%。

    第四个优化:提示词不是小事

    前面的优化都在"找"这个环节,但"答"这个环节也有问题。

    我一开始的提示词是:

    请根据以下参考文档回答问题:

    {context}

    问题:{query}

    这个提示词太简单了,模型经常"发挥"——加上自己的理解、引用不存在的条文,甚至编造答案。

    **改进方案**:改成了更严格的提示词:

    你是企业知识库助手,必须严格基于提供的参考文档回答问题。

    规则:

  • 只使用参考文档中的信息,不要添加任何文档之外的内容
  • 2. 如果文档中没有答案,明确回答"根据现有资料无法回答该问题"

    3. 回答时标注信息来源,格式为"根据XX条..."

    4. 不要猜测或推断,即使你觉得合理

    参考文档:

    {context}

    问题:{query}

    请先判断参考文档中是否有足够信息回答该问题,然后再作答。

    改完之后,准确率从88%提到了92%。

    最终效果

    做了这四轮优化之后,系统终于到了"好用"的程度。

    员工反馈从"还不如自己翻文档"变成了"问一句能直接用"。客户也追加了需求,要把系统扩展到更多业务场景。

    踩坑心得

    回头看,RAG系统的优化不是一招鲜,而是各个环节都要打磨:

  • **文档处理**:切块不只是切长度,要切逻辑单元
  • 2. **检索环节**:单靠向量不够,得混合检索

    3. **排序环节**:重排序能解决相似和相关的区别

    4. **生成环节**:提示词要够严格,限制模型的"发挥"

    每一个环节都有坑,但每一个坑都有解。关键是别想着一步到位,一层一层优化,总会调出来的。