这道题其实问的是,怎么让[[RAG]]在真实场景下真正好用,不光是跑通流程。核心就一句话:把所有优化手段看成在检索质量和生成质量之间找平衡,而不是单纯堆技术。我分几个层面讲一下。
先说检索层。这里最容易出问题的是[[分块]]策略。很多人上来就固定切512个token,但实际效果很差。比如做客服问答,用户问“退款流程”,如果你把整个退款条款切成一个块,那没问题;但如果问“七天无理由退货的运费谁出”,答案可能散落在不同段落里。所以我会按语义结构来分,比如标题-内容层级,或者用[[滑动窗口]]加重叠,确保上下文不丢失。更激进的做法是同一份文档用多个向量表示,比如摘要、关键词、模拟问题各存一份[[embedding]],检索时多角度召回。这有个前提:你的文档结构要清晰,否则语义分块也救不了。
再一个就是查询侧的优化。用户query经常很短或者表述模糊,比如“怎么退钱”。这时候我会用HyDE,先生成一个假设文档,再拿它去检索,效果比直接搜query好很多。但HyDE成本高,多一次LLM调用,所以适合query质量差、但延迟要求不高的场景。反过来,如果用户问的是“订单号12345”,那就完全不需要改写,直接精确匹配就行。说白了,查询改写不是无脑上,得看用户意图。
检索策略上,我倾向[[混合检索]],把向量相似度和[[BM25]]关键词结合起来。向量擅长语义相似,BM25擅长精确匹配,比如搜“iPhone14”这种产品名,向量可能把“iPhone15”也拉进来,但BM25能精确命中。然后用RRF融合排序,简单有效。[[重排]]是性价比很高的优化,用Cross-encoder把Top-100精排到Top-20,延迟增加几十毫秒,但召回质量提升明显。如果你做企业SOP问答,文档噪声大,重排几乎是必选项,否则生成结果很容易被无关段落带偏。
再讲生成层。上下文压缩很重要,因为大模型的输入长度有限,而且噪声会直接导致[[幻觉]]。我会让LLM先对检索回来的文档做摘要,过滤掉冗余信息,只保留最相关的段落。或者用专门的压缩模型,比如LlamaIndex里的那种。提示工程上,角色设定要明确,比如“基于提供的参考资料回答,不知道就说不知道”,再加几个few-shot例子,展示正确的引用格式和拒答方式。这样能有效降低幻觉。
流程层,我比较关注自适应RAG。不是所有问题都需要检索,比如问“今天天气怎么样”,你直接让LLM回答就好,不需要去知识库里翻。所以我会用[[Self-RAG]]让模型先判断是否需要检索,如果需要,再根据生成过程中的信息缺口主动发起二次检索。这特别适合多跳推理问题,比如“苹果公司2023年的营收是多少?和2022年比增长了多少?”这种问题需要先搜2023年营收,再搜2022年营收,然后对比。如果一次检索就生成,大概率会编造数据。
说到自适应,有个点值得注意:多轮检索虽然能提升复杂问题的准确性,但延迟会翻倍甚至更多。如果业务对延迟敏感,比如实时客服,你可能得权衡,或者用缓存机制来缓解。
最后说落地取舍。我的经验是,先做好分块和重排这两个高性价比优化,它们能解决大部分质量问题。HyDE和多轮检索是锦上添花,成本高,只在特定场景下用。微调embedding成本最高,除非领域术语特别特殊,否则不优先考虑。整体上,我会把RAG优化看成一个渐进迭代的过程,上线后收集用户反馈,形成闭环,不断调优。