15|RAG 优化:为什么检索到了,回答仍然不对? - eino教程

15|RAG 优化:为什么检索到了,回答仍然不对?

《Eino 实战:用 Go 构建 AI 应用与智能体》· 第 15 篇
关键词:召回质量、MultiQuery、检索路由、重排序、评测
前置条件:已完成 13|知识入库 与 14|RAG 入门。

第 14 篇的流程跑通了,但上线后常见的情况是:检索确实返回了资料,回答却不对。

这可能发生在四个不同的环节。如果不先定位是哪一环,调参就只是在碰运气。这一篇按顺序给出排查路径,并介绍 Eino 提供的优化手段。


一、先定位:问题出在哪一环

RAG 有四个环节,每个环节都有各自的失效方式:

用户问题 ↓ ① 查询理解:问题表达与资料措辞不一致 检索 ↓ ② 召回质量:该被召回的没召回,或召回了无关的 重排与筛选 ↓ ③ 上下文组装:关键信息被淹没、被截断、被冲突内容干扰 生成 ↓ ④ 生成与引用:模型误读资料,或引用不支持结论 回答

排查顺序建议从后往前,或者从最容易验证的环节开始:

  1. 打印最终发给模型的完整消息(第 14 篇示例里的做法)。这一步能立刻区分是“资料没进去”还是“资料进去了但模型没用好”。
  2. 打印召回结果和分数。看该有的资料是否在里面。
  3. 打印切分后的片段。看关键信息是否被切碎或丢失。

下面这段程序模拟了“检索命中但召回质量不稳定”的情况——这正是最常见的一种(实机运行):

packagemainimport("context""fmt""log""sort""strings""github.com/cloudwego/eino/schema")// chunk 表示一个已入库的知识片段。typechunkstruct{IDstringContentstringSourcestring}varcorpus=[]chunk{{ID:"printer#0",Content:"打印机无法联网时,先检查网线是否插紧,再确认网段是否允许办公设备接入。",Source:"handbook/printer.md"},{ID:"vpn#0",Content:"远程访问需要先申请 VPN 账号,审批通过后由网络组开通。",Source:"handbook/vpn.md"},{ID:"ticket#0",Content:"工单超过 24 小时未分派,可申请催办。",Source:"handbook/ticket.md"},}// bigrams 生成字符二元组,用于中文的粗略相似度计算。funcbigrams(sstring)map[string]struct{}{r:=[]rune(s)set:=make(map[string]struct{})fori:=0;i+1<len(r);i++{set[string(r[i:i+2])]=struct{}{}}returnset}// retrieve 用 bigram 重叠度做模拟检索,便于在无向量库时观察召回质量。funcretrieve(querystring,kint)[]chunk{q:=bigrams(query)typescoredstruct{c chunk scorefloat64}varall[]scoredfor_,c:=rangecorpus{d:=bigrams(c.Content)iflen(d)==0{continue}hit:=0forg:=rangeq{if_,ok:=d[g];ok{hit++}}all=append(all,scored{c:c,score:float64(hit)/float64(len(d))})}sort.Slice(all,func(i,jint)bool{returnall[i].score>all[j].score})varhits[]chunkfor_,s:=rangeall{ifs.score>0&&len(hits)<k{hits=append(hits,s.c)}}returnhits}funcmain(){iferr:=run();err!=nil{log.Fatal(err)}}funcrun()error{_=context.Background()query:="打印机离线怎么处理"hits:=retrieve(query,3)fmt.Printf("查询: %q 命中 %d 条\n",query,len(hits))for_,h:=rangehits{fmt.Printf(" 来源=%s 内容=%s\n",h.Source,h.Content)}// 组装上下文:带上来源编号,便于回答引用。varb strings.Builderfori,h:=rangehits{fmt.Fprintf(&b,"[%d] 来源:%s\n%s\n",i+1,h.Source,h.Content)}systemPrompt:="你是工单助手。只依据参考资料回答,结论后标注来源编号;资料不足时明确说明。"messages:=[]*schema.Message{schema.SystemMessage(systemPrompt),schema.UserMessage("参考资料:\n"+b.String()+"\n问题:"+query),}fmt.Println("--- 最终发送给模型的用户消息 ---")fmt.Println(messages[len(messages)-1].Content)// 反例:无相关资料时必须显式处理,否则模型容易自由发挥。empty:=retrieve("报销流程是什么",3)fmt.Printf("\n无相关资料时命中数: %d\n",len(empty))iflen(empty)==0{fmt.Println("正确做法:明确说明没有依据,并转人工或补充信息,而不是让模型猜测。")}returnnil}

实机输出:

查询: "打印机离线怎么处理" 命中 1 条 来源=handbook/printer.md 内容=打印机无法联网时,先检查网线是否插紧,再确认网段是否允许办公设备接入。 --- 最终发送给模型的用户消息 --- 参考资料: [1] 来源:handbook/printer.md 打印机无法联网时,先检查网线是否插紧,再确认网段是否允许办公设备接入。 问题:打印机离线怎么处理 无相关资料时命中数: 0 正确做法:明确说明没有依据,并转人工或补充信息,而不是让模型猜测。

这里能直接看出一个典型问题:用户说的是“离线”,资料里写的是“无法联网”。在简单相似度算法下,这种同义不同词的差距会显著拉低召回分数。真实向量检索对这类问题更健壮,但依然会受影响——这是查询理解要解决的问题。


二、① 查询理解:用户的说法和资料的写法不一样

用户不会用资料的措辞提问。常见的差距:

用户说法资料写法
打印机离线打印机无法联网
网断了网络连接异常
怎么催申请催办流程

改善手段:

多查询检索(MultiQuery):用模型把一个问题改写成多个表达,分别检索后合并结果。这样能覆盖更多措辞。

// 概念示意:把原始问题扩展成多个查询变体。// 实现方式可能是:// - 调用模型生成改写(推荐,可指定数量的变体)// - 或用同义词规则扩展queries:=[]string{"打印机离线怎么处理","打印机无法联网 排查步骤","办公设备 网络连接失败 处理",}for_,q:=rangequeries{docs,err:=r.Retrieve(ctx,q,retriever.WithTopK(3))// 合并、去重}

Eino 的示例仓库里有MultiQueryRetriever的实现(基于模型生成查询变体),可以直接参考其结构。

检索路由:不同类型的问题走不同的检索源。比如“工单进度”查业务数据库,“操作规范”查文档库。路由错误会导致在一个错误的语料里搜索。


三、② 召回质量:该来的没来,不该来的来了

症状可能原因排查
该有的资料没被召回切分不当、Embedding 不匹配、TopK 太小打印召回列表;检查入库与检索用的嵌入模型是否一致
召回了大量无关内容阈值过低、TopK 过大、资料本身有问题提高阈值;减小 TopK;人工检查片段质量
只召回半句话切分切断了上下文检查切分粒度和重叠(第 13 篇)
总是召回同一篇该文档片段重复度高、索引存在重复去重;检查入库逻辑

最容易被忽略的是切分问题。第 13 篇实测出的那个只有句号的碎片就是例子:它可能因“太短所以什么都像”被召回,挤占上下文。

重排序

检索返回的结果,顺序未必可靠。重排序(Rerank)是对候选结果重新打分的独立步骤:先多召回一些(比如 20 条),再用更精细(也更贵)的模型筛出最相关的几条。

这样做的好处是把“召回”和“排序”解耦:召回阶段追求不漏,排序阶段追求精准。

// 概念示意:先宽召回,再精排。candidates,_:=r.Retrieve(ctx,query,retriever.WithTopK(20))top:=rerank(candidates,query,3)// 用更强的相关性判断挑出前 3 条

四、③ 上下文组装:资料对了,但被淹没了

即使检索正确,组装方式也会毁掉效果:

问题后果对策
塞入太多片段关键信息被稀释,Token 浪费控制数量,优先高相关片段
片段无分隔两段内容被连读明确分段,带来源标注
来源缺失无法核验始终带来源
冲突内容未标注模型自行选一个提示词要求列出冲突
无关内容混入干扰判断用阈值过滤

一个实用技巧:在片段前标注它来自哪个文档和章节,给模型更多判断依据:

[1] 来源:handbook/printer.md(第 2 章 网络排查) 打印机无法联网时……

五、④ 生成与引用:资料对,回答还是错

到这一步,资料已经正确进入上下文,但模型仍可能出错:

现象原因对策
张冠李戴把 A 文档的内容说成 B 文档的强调查看来源标注;减小片段数量
过度推断资料只说了一半,模型自行补全提示词要求“只陈述资料明确写出的内容”
引用不支持结论标了来源,但来源其实没这么说需要评测验证,不能只信“有引用”
该拒答却硬答缺乏“不知道”的出口明确允许说明资料不足

“有引用”不等于“引用正确”。这一点必须靠评测来发现,不能靠抽查几个好例子。


六、评测:没有它就只能凭感觉

RAG 优化最大的风险是改一处、坏一处。没有固定测试集,你无法判断改动是进步还是退步。

准备测试集

不需要很大,几十条真实问题就很有用:

问题 期望命中文档 要点 打印机离线怎么办 handbook/printer.md 先查网线再查网段 怎么申请 VPN handbook/vpn.md 需审批 工单多久没处理可以催办 handbook/ticket.md 24 小时 报销流程是什么 (无相关资料) 应明确说明无法回答

关注指标

指标测什么怎么测
召回命中率该命中的是否命中对比期望文档与实际召回
拒答正确率无资料时是否正确说明用无答案的问题测试
引用正确率标注的来源是否支持结论人工或模型辅助核对
答案要点覆盖关键点是否说出对照要点清单
延迟与成本每次问答的开销记录耗时与 Token 用量

最后一行的“无资料”用例尤其重要——它测试的是系统会不会编,而这个问题比“答得好不好”更关键。

改动前后都要跑

每次调整切分、TopK、阈值、提示词,都用同一套测试集对比。凭印象判断优化效果,几乎一定会走弯路。


七、优化的优先级建议

按投入产出比排序,建议的排查与优化顺序是:

  1. 打印实际发送的消息。零成本,能立刻区分是数据问题还是生成问题。
  2. 检查切分质量。人工读几十个片段,往往能发现大量问题。
  3. 准备最小测试集。没有它,后面所有改动都无法评估。
  4. 调 TopK 与阈值。低成本,见效快。
  5. 加入重排序。需要额外模型调用,但通常明显提升排序质量。
  6. 多查询与路由。针对特定类型的查询失效。
  7. 提示词与引用格式优化。最后再做。

不要一上来就换更大的模型。多数 RAG 问题的根因在数据侧。


八、常见问题

召回为空但资料里明明有
检查 Embedding 模型是否一致、切分是否把关键信息切碎、查询与资料是否措辞差异过大。

召回很多但都不相关
提高阈值、减小 TopK,检查片段是否过长混入无关内容。

答案引用了错误来源
先减小上下文中的片段数量,再考虑是否需要更强的排序。评测时把“引用正确率”单独度量。

改了提示词,效果时好时坏
很可能没有固定评测方式,只在看个例。建立测试集后对比。

优化之后某些问题反而变差了
这在 RAG 里很常见(比如提高阈值提高了精度但降低了召回)。用测试集量化取舍,而不是凭感觉。


九、小结

  • 回答不对可能来自四个环节:查询理解、召回质量、上下文组装、生成与引用,先定位再优化。
  • 打印最终消息是最有效的第一步排查手段。
  • 切分质量是常见根因,第 13 篇的碎片例子就是典型。
  • 多查询、检索路由改善查询理解;重排序解耦召回与排序。
  • 必须建立测试集,重点覆盖“无资料时是否正确拒答”。
  • 优先修数据侧,而不是换更大的模型。

第三篇《工具与知识》到此完成。下一篇进入第四篇,开始用 ADK 构建真正会自主决策的 Agent。


参考资料与版本说明

资料核对日期为2026 年 10 月 9 日:

  1. Retriever 使用说明
  2. Eino 官方示例仓库(检索相关示例)
  3. Eino 主仓库

本文示例按Eino v0.9.21编写,检索与相似度计算为自定义实现,已实机运行,正文输出为真实结果。多查询与重排序部分为概念示意与优化方向,未在本篇实机运行。

相关新闻

ReportMachine7.0报表引擎实战:从模板设计到打印导出

ReportMachine7.0报表引擎实战:从模板设计到打印导出

简介&#xff1a;ReportMachine 7.0是一套面向Delphi 7开发环境的专业报表控件资源&#xff0c;适合需要为桌面业务系统添加报表设计、打印与导出能力的开发人员。资源包内含完整的控件源码与设计文件&#xff0c;共811个文件&#xff0c;压缩包大小约9.22MB。其中pas源码与dcu…

2026/10/20 11:57:55 阅读更多 →
BGP选路与属性详解:从权重到MED的完整指南

BGP选路与属性详解:从权重到MED的完整指南

BGP 的选路和属性&#xff0c;这个东西我琢磨了挺久才算是彻底捋顺。一开始看各路文档&#xff0c;什么权重、本地优先级、MED、AS Path&#xff0c;名词堆了一堆&#xff0c;感觉每个都认识&#xff0c;但真到了排障或者调优的时候&#xff0c;根本不知道从哪儿下手。后来在真…

2026/10/20 11:56:54 阅读更多 →
BGP选路机制详解:从属性博弈到网络排障实战

BGP选路机制详解:从属性博弈到网络排障实战

BGP&#xff0c;全称是Border Gateway Protocol&#xff0c;边界网关协议。我最早接触它的时候&#xff0c;是被那句“BGP是互联网的粘合剂”给唬住了&#xff0c;觉得这是只有运营商和顶级网络专家才需要碰的东西。直到后来自己动手搭实验环境、处理真实网络故障&#xff0c;才…

2026/10/20 11:56:54 阅读更多 →

最新新闻

基于Win32 API与GDI+的捕鱼达人游戏开发实战:C++课程设计完整方案

基于Win32 API与GDI+的捕鱼达人游戏开发实战:C++课程设计完整方案

简介&#xff1a;这是一份面向高校C课程设计与Windows游戏开发初学者的实践项目源码&#xff0c;主题为经典小游戏“捕鱼达人”。项目以C语言为基础&#xff0c;结合Windows API完成窗口创建、消息循环与事件响应&#xff0c;并借助GDI绘制游戏背景、鱼群、炮台与子弹等元素&am…

2026/10/20 13:34:41 阅读更多 →
AI辅助教材编写全流程:从大纲到降重的实战指南

AI辅助教材编写全流程:从大纲到降重的实战指南

开头220字&#xff08;我需要认真思考标题和场景&#xff0c;构思一篇结构清晰、完整、专业的博文。先理解用户需求。&#xff09;开头&#xff08;220字&#xff09;写教材在很多人眼里是“专家才配干”的活儿——体例要规范、内容要严谨、查重要过关、还要有可教学性。但这两…

2026/10/20 13:34:41 阅读更多 →
内容创作的“rea”工作流:复盘、重建与行动,打造可持续内容体系

内容创作的“rea”工作流:复盘、重建与行动,打造可持续内容体系

“rea”这个标题&#xff0c;看起来很简短&#xff0c;但如果把它当成一个内容项目或者品牌栏目的起点&#xff0c;它其实是很多想法的容器。我自己的理解是&#xff0c;它更像一套“重新运转”的方法论——react&#xff0c;重新观察、重新拆解、重新行动。这篇博文不写宏大理…

2026/10/20 13:34:41 阅读更多 →
C++ Win32捕鱼达人课设:从零搭建与避坑指南

C++ Win32捕鱼达人课设:从零搭建与避坑指南

简介&#xff1a;这是一份面向高校C课程设计与Windows游戏开发初学者的完整实践项目&#xff0c;主题为“捕鱼达人”&#xff0c;适合正在学习面向对象编程、希望把语法知识落地为可运行图形程序的学生参考。项目围绕Windows API展开&#xff0c;涵盖窗口创建、消息循环与事件响…

2026/10/20 13:34:41 阅读更多 →
跨平台实时协作框架的架构设计与离线同步实践

跨平台实时协作框架的架构设计与离线同步实践

1. rea 这个代号&#xff0c;到底代表什么如果你在一个技术团队里待得够久&#xff0c;就会发现每个项目在正式命名之前&#xff0c;都会有一个特别随意的内部代号&#xff1a;可能是某个人随手敲的文件夹名&#xff0c;可能是会议室名字的缩写&#xff0c;也可能是三个字母的随…

2026/10/20 13:34:40 阅读更多 →
Qt命令行参数解析:从argc/argv到QCommandLineParser实战指南

Qt命令行参数解析:从argc/argv到QCommandLineParser实战指南

写这篇文章的缘由很简单&#xff1a;之前接手一个老项目的维护工作时&#xff0c;发现主程序里塞了满满一屏的手写参数解析代码&#xff0c;各种strcmp加if/else嵌套&#xff0c;看着头大不说&#xff0c;新加一个开关还得顺着调用链一路摸下去&#xff0c;搞得人战战兢兢。后来…

2026/10/20 13:33:32 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:45 阅读更多 →