RAG 核心链路深挖检索 → 重排序 → 生成的技术细节标签RAG | 向量检索 | Rerank | 相似度计算 | 流式输出 | Prompt 工程一、从一个玄学问题开始做 RAG 的同学应该都有过这种经历同样的文档、同样的问题昨天回答得挺好今天突然就不准了。你啥也没改但它就是抽风了。这种玄学现象的背后其实是有技术原因的。今天咱们就把 RAG 的核心链路拆开看看每个环节到底在干什么以及为什么会影响最终效果。二、RAG 链路全景图先把完整链路画清楚用户提问 ↓ [查询向量化] ──→ Query Embedding ↓ [向量检索] ──→ 粗排召回 Top-K比如 100 条 ↓ [重排序] ──→ 精排选出 Top-N比如 5 条← 可选环节 ↓ [上下文拼接] ──→ 把选中的文档 问题拼成 Prompt ↓ [LLM 生成] ──→ 流式输出回答 ↓ [来源标注] ──→ 标注引用的文档咱们逐个环节深挖。三、环节 1查询向量化3.1 Embedding 的本质Embedding 就是把文本变成向量一堆数字。退款政策是什么 ↓ Embedding 模型 [0.023, -0.156, 0.891, ..., -0.034] ← 1024 维向量关键特性语义相近的文本向量距离也近。退款政策是什么 ≈ 怎么申请退货 ≈ 钱怎么退回来 ↓ ↓ ↓ 向量 A 向量 B 向量 C distance(A, B) 很小 distance(A, D) 很大 D 公司成立于 2020 年3.2 查询向量化的坑坑 1查询和文档的语言风格不一致文档里写的是“退货流程说明用户需在收货后 7 天内提出申请…”用户问的是“我买了东西不想要了咋办”如果 Embedding 模型不够强这两种表达可能匹配不上。解决方案用更强的 Embedding 模型bge-large-zh m3e-base加 Rerank 模型做精排混合检索向量 关键词互补坑 2短查询的信息量不足用户只问了一个词“退款”这个词的向量可能跟很多不相关的内容也有点像。解决方案查询扩展把退款扩展成退款政策、退款流程、退货退款HyDEHypothetical Document Embedding让 LLM 先写一个假设的回答再对这个回答做向量化# HyDE 伪代码asyncdefhyde_retrieval(query):# 1. 让 LLM 写一个假设的回答hypothetical_answerawaitllm.generate(f请简要回答这个问题{query})# 2. 对假设回答做向量化信息更丰富query_embeddingembed(hypothetical_answer)# 3. 用假设回答的向量去检索docsvector_store.search(query_embedding)returndocs四、环节 2向量检索4.1 相似度计算向量检索的核心是相似度计算常用两种方法余弦相似度Cosine Similarityimportnumpyasnpdefcosine_similarity(a,b):计算两个向量的余弦相似度范围 [-1, 1]returnnp.dot(a,b)/(np.linalg.norm(a)*np.linalg.norm(b))# 实际使用中FAISS 等库已经优化好了点积Dot Productdefdot_product(a,b):简单向量点积returnnp.dot(a,b)区别余弦相似度只关心方向不关心长度适合语义匹配点积同时考虑方向和长度适合需要区分重要性的场景4.2 近似最近邻ANN算法当向量库里有几百万条数据时暴力计算每条的相似度太慢了。需要用近似算法┌─────────────────────────────────────────┐ │ 暴力搜索 (Flat) │ │ - 精确但 O(N) 复杂度 │ │ - 适合数据量 10万 │ ├─────────────────────────────────────────┤ │ IVF (Inverted File Index) │ │ - 把向量空间分成多个区域 │ │ - 先找最近的几个区域再区域内搜索 │ │ - 速度快精度略有损失 │ ├─────────────────────────────────────────┤ │ HNSW (Hierarchical Navigable Small World)│ │ - 构建多层图结构 │ │ - 贪心搜索层层逼近 │ │ - 速度和精度的最佳平衡 │ └─────────────────────────────────────────┘Chatchat 默认用 FAISS推荐配置# 小数据量 10万条indexfaiss.IndexFlatIP(dimensions)# 暴力搜索精确# 中数据量10万 ~ 100万indexfaiss.IndexIVFFlat(quantizer,dimensions,nlist)# 大数据量 100万或要求高速度indexfaiss.IndexHNSWFlat(dimensions,M32)4.3 检索参数调优在kb_settings.yaml中# 向量检索返回数量VECTOR_SEARCH_TOP_K:10# 相似度阈值低于这个分的不要SCORE_THRESHOLD:0.5# 是否启用混合检索DEFAULT_SEARCH_TYPE:mix参数调优建议场景TOP_KSCORE_THRESHOLD说明精确问答50.6只要最相关的开放式问答100.4多给点上下文文档内容少30.5避免引入噪声文档内容多150.5增加召回率五、环节 3重排序Rerank5.1 为什么需要 Rerank向量检索有个问题它只关心语义相似不关心是否真正回答了问题。举个例子问题怎么申请退款 向量检索 Top 3 1. 退款政策说明用户可在收货后 7 天内申请退款... ← 相关 ✓ 2. 退款金额将在 3-5 个工作日内原路返回... ← 相关 ✓ 3. 公司成立于 2020 年主营电子产品... ← 不相关 ✗但可能含退款字样Rerank 的作用就是在召回的候选集里重新判断哪些真正相关。5.2 Rerank 的原理Rerank 模型通常是一个交叉编码器Cross-Encoder向量检索双编码器 查询 ──→ Embedding ──→ 向量 文档 ──→ Embedding ──→ 向量 相似度 cosine(查询向量, 文档向量) 问题查询和文档是独立编码的没有交互 Rerank交叉编码器 [查询 文档] ──→ 联合编码 ──→ 相关性分数 优势查询和文档可以互相看判断更准确 代价计算量大只能对少量候选做精排5.3 Chatchat 中启用 Rerank# model_settings.yamlDEFAULT_RERANK_MODEL:bge-reranker-large# kb_settings.yaml# 向量检索召回数量给 Rerank 的候选集VECTOR_SEARCH_TOP_K:20# Rerank 后最终保留数量RERANK_TOP_K:5流程变成用户提问 ↓ 向量检索召回 Top 20 ↓ Rerank 模型精排选出 Top 5 ↓ 送给 LLM 生成回答5.4 Rerank 的效果实测数据同一批测试集配置准确率延迟无 Rerank72%500ms Rerank (base)81%800ms Rerank (large)86%1200ms结论Rerank 能显著提升准确率但有延迟成本。对精度要求高的场景建议开启。六、环节 4上下文拼接6.1 为什么需要拼接LLM 的输入是一个字符串但咱们有检索到的文档 用户问题需要拼成一个完整的 Prompt。6.2 拼接策略Chatchat 默认用“Stuff”策略——把所有文档直接塞进 Prompt【系统提示】 你是一个专业的客服助手请基于参考资料回答问题。 【参考资料】 文档 1退款政策说明用户可在收货后 7 天内... 文档 2退款金额将在 3-5 个工作日内... 文档 3... 【用户问题】 怎么申请退款 【要求】 1. 基于参考资料回答 2. 标注信息来源 3. 不要编造Stuff 策略的问题文档太多时超出 LLM 的上下文长度无关文档会干扰 LLM 的判断其他策略策略原理适用场景Stuff全部塞进去文档少、上下文够Map-Reduce每篇文档单独问再汇总文档多、需要综合Refine逐篇迭代优化答案需要高精度综合Chatchat 默认用 Stuff因为 RAG 场景下检索到的文档通常已经经过筛选数量可控。6.3 上下文长度管理# 伪代码上下文长度控制defbuild_prompt(docs,query,max_context_length3000): 把文档拼进 Prompt但不超过最大长度 contextused_docs[]fordocindocs:# 预估加上这篇文档后的长度candidatecontextf\n文档{doc.page_content}\niflen(candidate)max_context_length:break# 超长了停止添加contextcandidate used_docs.append(doc)promptf基于以下资料回答问题\n{context}\n\n问题{query}returnprompt,used_docs七、环节 5LLM 生成7.1 生成参数详解# model_settings.yamlLLM_MODEL_CONFIG:qwen2-instruct:model:qwen2-instructtemperature:0.7# 创造性 vs 确定性max_tokens:4096# 最大输出长度top_p:0.9# 核采样frequency_penalty:0# 重复惩罚presence_penalty:0# 新颖性惩罚参数调优指南参数作用调大调小temperature随机性更有创意更确定top_p采样范围更多样更集中max_tokens输出长度回答更长回答更短frequency_penalty重复惩罚减少重复允许重复RAG 场景推荐temperature: 0.3~0.5RAG 需要确定性不要发挥max_tokens: 2048根据回答长度调整7.2 流式输出SSEChatchat 支持流式输出用户体验更好# 伪代码SSE 流式输出fromfastapiimportStreamingResponseasyncdefstream_chat(request):asyncdefgenerate():# 调用 LLM 的流式接口asyncforchunkinllm.astream(prompt):yieldfdata:{json.dumps({content:chunk})}\n\nyielddata: [DONE]\n\nreturnStreamingResponse(generate(),media_typetext/event-stream)SSEServer-Sent Events原理HTTP 长连接服务器持续推送数据前端逐字显示像打字机效果比 WebSocket 简单单向通信足够八、环节 6来源标注8.1 为什么需要来源标注RAG 的回答可能出错让用户知道这个回答是从哪来的可以增加可信度也便于人工复核。8.2 Chatchat 的来源标注实现# 伪代码来源标注asyncdefknowledge_base_chat_with_source(request):# 1. 检索文档docsretrieve_documents(request.query)# 2. 构建 Prompt要求模型标注来源promptf 基于以下资料回答问题并在回答中标注信息来源格式[来源: 文档名]。 资料{format_docs_with_source(docs)}问题{request.query}# 3. 生成回答answerawaitllm.generate(prompt)# 4. 返回前端高亮显示来源return{answer:answer,source_documents:[{title:doc.metadata[source],content:doc.page_content}fordocindocs]}九、完整链路的效果调优9.1 调优优先级按效果影响从大到小排序1. Embedding 模型质量影响检索⭐⭐⭐⭐⭐ 2. 文本分块策略影响检索⭐⭐⭐⭐⭐ 3. Rerank 模型影响精排⭐⭐⭐⭐ 4. Prompt 模板影响生成⭐⭐⭐⭐ 5. LLM 质量影响生成⭐⭐⭐ 6. 检索参数 TOP_K影响召回⭐⭐⭐ 7. 生成参数 temperature影响风格⭐⭐9.2 诊断流程回答不好时按这个顺序排查Step 1: 检查检索结果 └─ 打印 retrieved_docs看是否包含正确答案 └─ 如果不包含 → 调分块、换 Embedding、加 Rerank Step 2: 检查 Prompt └─ 打印最终 Prompt看上下文是否完整 └─ 如果上下文不对 → 调分块、调 TOP_K Step 3: 检查 LLM 输出 └─ 看 LLM 是否胡编 └─ 如果胡编 → 加强 Prompt 约束、降低 temperature十、小结这篇咱们把 RAG 的核心链路彻底拆开了✅ 查询向量化Embedding 原理、HyDE 查询扩展✅ 向量检索相似度计算、ANN 算法、参数调优✅ 重排序Cross-Encoder 原理、效果与成本权衡✅ 上下文拼接Stuff/Map-Reduce/Refine 策略✅ LLM 生成参数详解、流式输出 SSE✅ 来源标注实现原理和用户体验✅ 效果调优优先级排序和诊断流程核心认知RAG 不是检索 生成的简单拼接而是一个系统工程。检索质量决定了上限LLM 质量决定了下限。优化要抓重点先搞定 Embedding 和分块再考虑其他。你在调优 RAG 效果时哪个环节给你带来的提升最大Embedding、Rerank 还是 Prompt欢迎分享经验
LangChain-Chatchat 开发与应用(五) RAG核心链路深挖-检索到重排序到生成的技术细节
发布时间:2026/5/20 5:41:29
RAG 核心链路深挖检索 → 重排序 → 生成的技术细节标签RAG | 向量检索 | Rerank | 相似度计算 | 流式输出 | Prompt 工程一、从一个玄学问题开始做 RAG 的同学应该都有过这种经历同样的文档、同样的问题昨天回答得挺好今天突然就不准了。你啥也没改但它就是抽风了。这种玄学现象的背后其实是有技术原因的。今天咱们就把 RAG 的核心链路拆开看看每个环节到底在干什么以及为什么会影响最终效果。二、RAG 链路全景图先把完整链路画清楚用户提问 ↓ [查询向量化] ──→ Query Embedding ↓ [向量检索] ──→ 粗排召回 Top-K比如 100 条 ↓ [重排序] ──→ 精排选出 Top-N比如 5 条← 可选环节 ↓ [上下文拼接] ──→ 把选中的文档 问题拼成 Prompt ↓ [LLM 生成] ──→ 流式输出回答 ↓ [来源标注] ──→ 标注引用的文档咱们逐个环节深挖。三、环节 1查询向量化3.1 Embedding 的本质Embedding 就是把文本变成向量一堆数字。退款政策是什么 ↓ Embedding 模型 [0.023, -0.156, 0.891, ..., -0.034] ← 1024 维向量关键特性语义相近的文本向量距离也近。退款政策是什么 ≈ 怎么申请退货 ≈ 钱怎么退回来 ↓ ↓ ↓ 向量 A 向量 B 向量 C distance(A, B) 很小 distance(A, D) 很大 D 公司成立于 2020 年3.2 查询向量化的坑坑 1查询和文档的语言风格不一致文档里写的是“退货流程说明用户需在收货后 7 天内提出申请…”用户问的是“我买了东西不想要了咋办”如果 Embedding 模型不够强这两种表达可能匹配不上。解决方案用更强的 Embedding 模型bge-large-zh m3e-base加 Rerank 模型做精排混合检索向量 关键词互补坑 2短查询的信息量不足用户只问了一个词“退款”这个词的向量可能跟很多不相关的内容也有点像。解决方案查询扩展把退款扩展成退款政策、退款流程、退货退款HyDEHypothetical Document Embedding让 LLM 先写一个假设的回答再对这个回答做向量化# HyDE 伪代码asyncdefhyde_retrieval(query):# 1. 让 LLM 写一个假设的回答hypothetical_answerawaitllm.generate(f请简要回答这个问题{query})# 2. 对假设回答做向量化信息更丰富query_embeddingembed(hypothetical_answer)# 3. 用假设回答的向量去检索docsvector_store.search(query_embedding)returndocs四、环节 2向量检索4.1 相似度计算向量检索的核心是相似度计算常用两种方法余弦相似度Cosine Similarityimportnumpyasnpdefcosine_similarity(a,b):计算两个向量的余弦相似度范围 [-1, 1]returnnp.dot(a,b)/(np.linalg.norm(a)*np.linalg.norm(b))# 实际使用中FAISS 等库已经优化好了点积Dot Productdefdot_product(a,b):简单向量点积returnnp.dot(a,b)区别余弦相似度只关心方向不关心长度适合语义匹配点积同时考虑方向和长度适合需要区分重要性的场景4.2 近似最近邻ANN算法当向量库里有几百万条数据时暴力计算每条的相似度太慢了。需要用近似算法┌─────────────────────────────────────────┐ │ 暴力搜索 (Flat) │ │ - 精确但 O(N) 复杂度 │ │ - 适合数据量 10万 │ ├─────────────────────────────────────────┤ │ IVF (Inverted File Index) │ │ - 把向量空间分成多个区域 │ │ - 先找最近的几个区域再区域内搜索 │ │ - 速度快精度略有损失 │ ├─────────────────────────────────────────┤ │ HNSW (Hierarchical Navigable Small World)│ │ - 构建多层图结构 │ │ - 贪心搜索层层逼近 │ │ - 速度和精度的最佳平衡 │ └─────────────────────────────────────────┘Chatchat 默认用 FAISS推荐配置# 小数据量 10万条indexfaiss.IndexFlatIP(dimensions)# 暴力搜索精确# 中数据量10万 ~ 100万indexfaiss.IndexIVFFlat(quantizer,dimensions,nlist)# 大数据量 100万或要求高速度indexfaiss.IndexHNSWFlat(dimensions,M32)4.3 检索参数调优在kb_settings.yaml中# 向量检索返回数量VECTOR_SEARCH_TOP_K:10# 相似度阈值低于这个分的不要SCORE_THRESHOLD:0.5# 是否启用混合检索DEFAULT_SEARCH_TYPE:mix参数调优建议场景TOP_KSCORE_THRESHOLD说明精确问答50.6只要最相关的开放式问答100.4多给点上下文文档内容少30.5避免引入噪声文档内容多150.5增加召回率五、环节 3重排序Rerank5.1 为什么需要 Rerank向量检索有个问题它只关心语义相似不关心是否真正回答了问题。举个例子问题怎么申请退款 向量检索 Top 3 1. 退款政策说明用户可在收货后 7 天内申请退款... ← 相关 ✓ 2. 退款金额将在 3-5 个工作日内原路返回... ← 相关 ✓ 3. 公司成立于 2020 年主营电子产品... ← 不相关 ✗但可能含退款字样Rerank 的作用就是在召回的候选集里重新判断哪些真正相关。5.2 Rerank 的原理Rerank 模型通常是一个交叉编码器Cross-Encoder向量检索双编码器 查询 ──→ Embedding ──→ 向量 文档 ──→ Embedding ──→ 向量 相似度 cosine(查询向量, 文档向量) 问题查询和文档是独立编码的没有交互 Rerank交叉编码器 [查询 文档] ──→ 联合编码 ──→ 相关性分数 优势查询和文档可以互相看判断更准确 代价计算量大只能对少量候选做精排5.3 Chatchat 中启用 Rerank# model_settings.yamlDEFAULT_RERANK_MODEL:bge-reranker-large# kb_settings.yaml# 向量检索召回数量给 Rerank 的候选集VECTOR_SEARCH_TOP_K:20# Rerank 后最终保留数量RERANK_TOP_K:5流程变成用户提问 ↓ 向量检索召回 Top 20 ↓ Rerank 模型精排选出 Top 5 ↓ 送给 LLM 生成回答5.4 Rerank 的效果实测数据同一批测试集配置准确率延迟无 Rerank72%500ms Rerank (base)81%800ms Rerank (large)86%1200ms结论Rerank 能显著提升准确率但有延迟成本。对精度要求高的场景建议开启。六、环节 4上下文拼接6.1 为什么需要拼接LLM 的输入是一个字符串但咱们有检索到的文档 用户问题需要拼成一个完整的 Prompt。6.2 拼接策略Chatchat 默认用“Stuff”策略——把所有文档直接塞进 Prompt【系统提示】 你是一个专业的客服助手请基于参考资料回答问题。 【参考资料】 文档 1退款政策说明用户可在收货后 7 天内... 文档 2退款金额将在 3-5 个工作日内... 文档 3... 【用户问题】 怎么申请退款 【要求】 1. 基于参考资料回答 2. 标注信息来源 3. 不要编造Stuff 策略的问题文档太多时超出 LLM 的上下文长度无关文档会干扰 LLM 的判断其他策略策略原理适用场景Stuff全部塞进去文档少、上下文够Map-Reduce每篇文档单独问再汇总文档多、需要综合Refine逐篇迭代优化答案需要高精度综合Chatchat 默认用 Stuff因为 RAG 场景下检索到的文档通常已经经过筛选数量可控。6.3 上下文长度管理# 伪代码上下文长度控制defbuild_prompt(docs,query,max_context_length3000): 把文档拼进 Prompt但不超过最大长度 contextused_docs[]fordocindocs:# 预估加上这篇文档后的长度candidatecontextf\n文档{doc.page_content}\niflen(candidate)max_context_length:break# 超长了停止添加contextcandidate used_docs.append(doc)promptf基于以下资料回答问题\n{context}\n\n问题{query}returnprompt,used_docs七、环节 5LLM 生成7.1 生成参数详解# model_settings.yamlLLM_MODEL_CONFIG:qwen2-instruct:model:qwen2-instructtemperature:0.7# 创造性 vs 确定性max_tokens:4096# 最大输出长度top_p:0.9# 核采样frequency_penalty:0# 重复惩罚presence_penalty:0# 新颖性惩罚参数调优指南参数作用调大调小temperature随机性更有创意更确定top_p采样范围更多样更集中max_tokens输出长度回答更长回答更短frequency_penalty重复惩罚减少重复允许重复RAG 场景推荐temperature: 0.3~0.5RAG 需要确定性不要发挥max_tokens: 2048根据回答长度调整7.2 流式输出SSEChatchat 支持流式输出用户体验更好# 伪代码SSE 流式输出fromfastapiimportStreamingResponseasyncdefstream_chat(request):asyncdefgenerate():# 调用 LLM 的流式接口asyncforchunkinllm.astream(prompt):yieldfdata:{json.dumps({content:chunk})}\n\nyielddata: [DONE]\n\nreturnStreamingResponse(generate(),media_typetext/event-stream)SSEServer-Sent Events原理HTTP 长连接服务器持续推送数据前端逐字显示像打字机效果比 WebSocket 简单单向通信足够八、环节 6来源标注8.1 为什么需要来源标注RAG 的回答可能出错让用户知道这个回答是从哪来的可以增加可信度也便于人工复核。8.2 Chatchat 的来源标注实现# 伪代码来源标注asyncdefknowledge_base_chat_with_source(request):# 1. 检索文档docsretrieve_documents(request.query)# 2. 构建 Prompt要求模型标注来源promptf 基于以下资料回答问题并在回答中标注信息来源格式[来源: 文档名]。 资料{format_docs_with_source(docs)}问题{request.query}# 3. 生成回答answerawaitllm.generate(prompt)# 4. 返回前端高亮显示来源return{answer:answer,source_documents:[{title:doc.metadata[source],content:doc.page_content}fordocindocs]}九、完整链路的效果调优9.1 调优优先级按效果影响从大到小排序1. Embedding 模型质量影响检索⭐⭐⭐⭐⭐ 2. 文本分块策略影响检索⭐⭐⭐⭐⭐ 3. Rerank 模型影响精排⭐⭐⭐⭐ 4. Prompt 模板影响生成⭐⭐⭐⭐ 5. LLM 质量影响生成⭐⭐⭐ 6. 检索参数 TOP_K影响召回⭐⭐⭐ 7. 生成参数 temperature影响风格⭐⭐9.2 诊断流程回答不好时按这个顺序排查Step 1: 检查检索结果 └─ 打印 retrieved_docs看是否包含正确答案 └─ 如果不包含 → 调分块、换 Embedding、加 Rerank Step 2: 检查 Prompt └─ 打印最终 Prompt看上下文是否完整 └─ 如果上下文不对 → 调分块、调 TOP_K Step 3: 检查 LLM 输出 └─ 看 LLM 是否胡编 └─ 如果胡编 → 加强 Prompt 约束、降低 temperature十、小结这篇咱们把 RAG 的核心链路彻底拆开了✅ 查询向量化Embedding 原理、HyDE 查询扩展✅ 向量检索相似度计算、ANN 算法、参数调优✅ 重排序Cross-Encoder 原理、效果与成本权衡✅ 上下文拼接Stuff/Map-Reduce/Refine 策略✅ LLM 生成参数详解、流式输出 SSE✅ 来源标注实现原理和用户体验✅ 效果调优优先级排序和诊断流程核心认知RAG 不是检索 生成的简单拼接而是一个系统工程。检索质量决定了上限LLM 质量决定了下限。优化要抓重点先搞定 Embedding 和分块再考虑其他。你在调优 RAG 效果时哪个环节给你带来的提升最大Embedding、Rerank 还是 Prompt欢迎分享经验