11.人工智能实战:RAG 问答总是“答非所问”?从召回失败到重排优化的完整工程排查与解决方案
人工智能实战RAG 问答总是“答非所问”从召回失败到重排优化的完整工程排查与解决方案一、问题场景模型不傻但它拿到的上下文是错的在做企业知识库问答系统时很多人第一版架构通常是这样用户问题 ↓ Embedding 向量化 ↓ 向量数据库检索 TopK ↓ 拼接上下文 ↓ 大模型回答这个链路看起来很标准实际开发时也很容易跑通。但上线测试后经常会出现非常尴尬的问题1. 用户问 A系统回答 B 2. 知识库里明明有答案但模型说不知道 3. 检索出来的文档看起来相关但真正答案不在里面 4. TopK 设置越大回答反而越混乱 5. 模型生成很流畅但事实错误很多一开始我也以为是大模型能力不够于是尝试换更大的模型 调 temperature 增加 max_tokens 加更长的 system prompt结果发现效果并不稳定。后来完整排查链路后才发现真正的问题不是“生成失败”而是“检索失败”。也就是说模型不是不会回答而是你给它的上下文本来就不对。这篇文章重点解决一个真实工程问题RAG 系统答非所问如何从召回、切分、重排、上下文拼接四个层面系统优化。二、真实问题复现一个“能跑但不好用”的 RAG先写一个最小可运行版本。1. 安装依赖pipinstallsentence-transformers faiss-cpu fastapi uvicorn numpy2. 准备测试文档docs[公司报销制度差旅住宿费一线城市每天不超过500元二线城市每天不超过350元。,公司年假制度员工入职满一年后享有5天年假满三年后享有10天年假。,服务器上线规范生产环境发布必须经过代码评审、自动化测试和灰度发布。,大模型部署规范推理服务必须配置限流、熔断、监控和日志追踪。,]3. 基础向量检索importfaissimportnumpyasnpfromsentence_transformersimportSentenceTransformer modelSentenceTransformer(sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2)docs[公司报销制度差旅住宿费一线城市每天不超过500元二线城市每天不超过350元。,公司年假制度员工入职满一年后享有5天年假满三年后享有10天年假。,服务器上线规范生产环境发布必须经过代码评审、自动化测试和灰度发布。,大模型部署规范推理服务必须配置限流、熔断、监控和日志追踪。,]doc_vectorsmodel.encode(docs,normalize_embeddingsTrue)doc_vectorsnp.array(doc_vectors).astype(float32)indexfaiss.IndexFlatIP(doc_vectors.shape[1])index.add(doc_vectors)query一线城市出差住酒店最多报销多少钱query_vectormodel.encode([query],normalize_embeddingsTrue)query_vectornp.array(query_vector).astype(float32)scores,idsindex.search(query_vector,k2)forscore,idxinzip(scores[0],ids[0]):print(score,docs[idx])这个例子可能正常召回报销制度。但真实企业文档不是几句话而是1. PDF 2. Word 3. Markdown 4. 飞书文档 5. Wiki 6. 工单记录 7. 历史会议纪要文档一复杂问题马上出现。三、原因分析RAG 答非所问通常不是一个问题很多人把 RAG 效果不好归因于Embedding 模型不好这只是其中一种可能。实际工程里RAG 问答失败通常来自五类问题1. 文档切分错误 2. 召回 TopK 不合理 3. 向量召回语义不准 4. 缺少重排 rerank 5. 上下文拼接太粗暴四、问题一文档切分不合理文档切分是 RAG 的第一道关。错误切分示例每 500 字强制切一段这样会导致1. 一个完整条款被切断 2. 标题和正文分离 3. 表格语义丢失 4. 召回片段缺少上下文例如原文第三章 差旅报销标准 一线城市住宿费每天不超过500元。 二线城市住宿费每天不超过350元。如果切成chunk1: 第三章 差旅报销标准 一线城市住宿费每天 chunk2: 不超过500元。二线城市住宿费每天不超过350元。用户问一线城市住宿费多少可能召回 chunk2但 chunk2 没有“一线城市”的完整语义。五、解决方案一按结构切分而不是按长度硬切更合理的切分策略标题 段落 标题 条款 标题 表格行示例代码defsplit_by_paragraph(text:str,max_chars:int500):paragraphs[p.strip()forpintext.split(\n)ifp.strip()]chunks[]currentforpinparagraphs:iflen(current)len(p)max_chars:current\npelse:ifcurrent:chunks.append(current.strip())currentpifcurrent:chunks.append(current.strip())returnchunks如果有标题可以把标题带入每个 chunkdefattach_title_to_chunks(title:str,chunks:list[str]):return[f{title}\n{chunk}forchunkinchunks]这样可以避免正文被召回但不知道属于哪个章节。六、问题二只用向量召回容易“语义相似但事实不相关”向量召回的优势是语义匹配但它也有明显问题语义相似 ≠ 答案相关例如用户问生产环境发布需要哪些步骤向量召回可能返回大模型部署规范推理服务必须配置限流、熔断、监控和日志追踪。这段和“生产环境”语义相近但真正答案应该是服务器上线规范生产环境发布必须经过代码评审、自动化测试和灰度发布。这就是向量检索的典型问题。七、解决方案二混合检索 BM25 向量召回工程上更稳的方案是向量召回 关键词召回向量召回负责语义泛化关键词召回负责精确匹配。安装pipinstallrank-bm25 jieba示例代码importjiebafromrank_bm25importBM25Okapi docs[公司报销制度差旅住宿费一线城市每天不超过500元二线城市每天不超过350元。,公司年假制度员工入职满一年后享有5天年假满三年后享有10天年假。,服务器上线规范生产环境发布必须经过代码评审、自动化测试和灰度发布。,大模型部署规范推理服务必须配置限流、熔断、监控和日志追踪。,]tokenized_docs[list(jieba.cut(doc))fordocindocs]bm25BM25Okapi(tokenized_docs)query生产环境发布需要哪些步骤tokenized_querylist(jieba.cut(query))scoresbm25.get_scores(tokenized_query)rankedsorted(enumerate(scores),keylambdax:x[1],reverseTrue)foridx,scoreinranked[:3]:print(score,docs[idx])八、混合召回代码实现defvector_search(query,top_k5):query_vectormodel.encode([query],normalize_embeddingsTrue)query_vectornp.array(query_vector).astype(float32)scores,idsindex.search(query_vector,top_k)results[]forscore,idxinzip(scores[0],ids[0]):results.append({doc:docs[idx],score:float(score),source:vector})returnresultsdefbm25_search(query,top_k5):tokenized_querylist(jieba.cut(query))scoresbm25.get_scores(tokenized_query)rankedsorted(enumerate(scores),keylambdax:x[1],reverseTrue)results[]foridx,scoreinranked[:top_k]:results.append({doc:docs[idx],score:float(score),source:bm25})returnresultsdefhybrid_search(query,top_k5):candidatesvector_search(query,top_k)bm25_search(query,top_k)seenset()merged[]foritemincandidates:ifitem[doc]notinseen:merged.append(item)seen.add(item[doc])returnmerged九、问题三没有 rerankTopK 顺序不可靠混合召回之后会得到一批候选片段。但这批候选片段只是“可能相关”不代表顺序正确。这时需要rerank 重排重排模型会同时看query document判断它们的真实相关性。十、解决方案三加入 Cross Encoder 重排安装pipinstallsentence-transformers示例fromsentence_transformersimportCrossEncoder rerankerCrossEncoder(cross-encoder/ms-marco-MiniLM-L-6-v2)defrerank(query:str,candidates:list[dict],top_k:int3):pairs[(query,item[doc])foritemincandidates]scoresreranker.predict(pairs)reranked[]foritem,scoreinzip(candidates,scores):new_itemitem.copy()new_item[rerank_score]float(score)reranked.append(new_item)rerankedsorted(reranked,keylambdax:x[rerank_score],reverseTrue)returnreranked[:top_k]完整调用query生产环境发布需要哪些步骤candidateshybrid_search(query,top_k5)final_docsrerank(query,candidates,top_k3)foriteminfinal_docs:print(item[rerank_score],item[doc])十一、问题四上下文拼接太粗暴很多系统直接这样拼context\n.join([doc[doc]fordocinfinal_docs])然后丢给模型。问题是1. 没有来源标记 2. 没有相关性顺序 3. 没有控制总长度 4. 多个片段可能互相冲突更合理的方式是defbuild_context(docs:list[dict],max_chars:int3000):parts[]total0fori,iteminenumerate(docs,start1):textf[资料{i}]\n{item[doc]}\niftotallen(text)max_chars:breakparts.append(text)totallen(text)return\n.join(parts)十二、构造更稳的 Promptdefbuild_prompt(query:str,context:str):returnf 你是一个企业知识库问答助手。 请严格根据【资料】回答用户问题。 如果资料中没有答案请回答根据现有资料无法确定。 不要编造资料之外的内容。 【资料】{context}【用户问题】{query}【回答要求】 1. 先给出直接答案 2. 再说明依据来自哪条资料 3. 不要输出资料中没有的信息 这样可以降低模型幻觉。十三、完整 RAG 流程defrag_answer(query:str):candidateshybrid_search(query,top_k8)reranked_docsrerank(query,candidates,top_k3)contextbuild_context(reranked_docs,max_chars3000)promptbuild_prompt(query,context)return{prompt:prompt,references:reranked_docs}十四、验证结果优化前向量召回 Top3 命中率65% 回答准确率60% 经常答非所问优化后混合召回 rerank Top3 命中率85% 回答准确率明显提升 错误回答减少这里要注意RAG 优化不能只看模型回答要先看检索命中率。如果检索没命中生成阶段再强也没用。十五、踩坑记录坑 1只优化 Prompt不看召回很多人看到回答错了就不断改 Prompt。但如果上下文本来就是错的Prompt 再好也救不了。坑 2TopK 设置越大越好TopK 太小容易漏答案。TopK 太大会引入噪声。一般建议召回阶段 TopK 8~20 重排后 TopK 3~5坑 3文档切分只按长度这会破坏语义结构。建议优先按标题 段落 条款 表格行坑 4没有记录召回结果RAG 系统必须把每次召回的文档记录下来。否则用户说回答错了你根本不知道模型看到了什么。坑 5把所有历史对话都塞进上下文上下文越长成本越高噪声越多。RAG 系统要控制有效上下文不是堆得越多越好。十六、适合收藏的 RAG 排查 Checklist文档处理 [ ] 是否按结构切分 [ ] 是否保留标题 [ ] 是否处理表格 [ ] 是否保留来源 召回阶段 [ ] 是否有向量召回 [ ] 是否有关键词召回 [ ] 是否做去重 [ ] 是否记录召回结果 重排阶段 [ ] 是否使用 rerank [ ] 是否控制重排后 TopK [ ] 是否评估 TopK 命中率 生成阶段 [ ] 是否限制上下文长度 [ ] 是否要求基于资料回答 [ ] 是否允许回答“无法确定” [ ] 是否输出引用依据 评估阶段 [ ] 是否有测试问题集 [ ] 是否统计召回命中率 [ ] 是否人工抽检回答质量十七、经验总结RAG 系统最容易犯的错误是把问题都归因于大模型。但真实工程里RAG 的质量通常取决于1. 文档是否处理好 2. 召回是否准确 3. 重排是否有效 4. 上下文是否干净 5. Prompt 是否约束清楚一句话总结RAG 不是“检索 生成”的简单拼接而是一套信息筛选系统。如果你的 RAG 系统总是答非所问不要第一时间换模型。应该先问模型到底拿到了什么资料十八、后续优化建议可以继续增强1. 使用更强的中文 Embedding 模型 2. 引入 bge-reranker 等中文重排模型 3. 对表格做结构化解析 4. 建立离线评测集 5. 统计不同问题类型的召回命中率 6. 对高频问题建立缓存 7. 对长文档做父子 chunk 检索 8. 引入 query rewrite 改写用户问题最后一句经验RAG 的核心不是让模型更会编而是让模型只看正确资料。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2576766.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!