[[RAG]]这道题,我觉得核心是在问怎么通过检索给大模型喂对的信息,来降低[[Hallucination]]。说白了就是,光靠模型参数里的知识不够,得从外部拉文档,但拉什么、怎么拉、拉完怎么喂,每个环节都藏着坑。我先捋一下整体流程,再挑几个关键优化展开。
整个RAG分三段:索引、检索、生成。索引就是把文档切碎、转成向量存进[[Vector Database]];检索是拿用户问题去库里捞最像的几段;生成是把捞回来的片段和问题拼成提示词,让LLM回答。听起来简单,但落地时每个环节都有trade-off。
先说索引里的[[分块]]。分块太粗,一段话包含多个主题,检索时容易召回一堆不相关的东西;分块太细,又可能把关键上下文切断。我一般会先看文档类型:如果是技术手册这种结构清晰的,就按章节边界切,再用[[Sliding Window]]让相邻块重叠个一二十个token,保住连续性;如果是客服对话这种主题跳跃的,就得用语义分块,比如按句子向量相似度聚一下,避免把退款和投诉硬切到一起。前提是得有个靠谱的[[Embedding]]模型,不然语义边界也分不准。
说到Embedding,通用模型在垂直领域经常翻车。比如电商场景里「满减」和「优惠券」语义相近,但通用模型可能觉得它们差很远。这时候我会考虑领域微调,或者用[[ColBERT]]这种多向量表示,每个token单独算相似度,匹配更细。不过代价是存储和检索都更重,所以小规模业务用通用模型加[[Hybrid Search]]其实更划算。
检索阶段,纯向量检索有个老毛病,对专有名词和数字不敏感。比如用户搜「订单号2024AB123」,向量可能把它当成噪音忽略掉,但关键词检索能精准命中。所以我会把向量检索和关键词检索结合起来,也就是Hybrid Search,用[[BM25]]走稀疏路,用[[Dense Retrieval]]走语义路,最后加权合并。权重怎么调?我一般离线跑一批测试query,看召回率决定。
捞回来的Top-K片段不一定都相关,这时候就需要[[重排]]。粗排用[[Bi-Encoder]]快但糙,精排用[[Cross-Encoder]]准但慢。我的做法是:先向量粗排取前100,再用Cross-Encoder精排取前5,这样质量有保证,延迟也能接受。不过如果业务对实时性要求极高,比如在线客服,我就跳过重排,直接把粗排Top-3喂给LLM,靠提示词让模型自己过滤不相关的。
生成阶段有个常见失败场景:上下文太长,LLM会忽略中间片段。比如用户问「退款政策」,你塞了十段文档,模型可能只看到第一段和最后一段。这时候我会用上下文压缩,比如LongLLMLingua,把不重要的token剪掉,只保留最核心的句子。另外,我会强制模型引用原文,在回答里标出「根据第X段」,这样出了问题能回溯,用户也信服。
最后说落地权衡。召回率和精度永远在打架:想多召回就得放宽阈值,但精度会掉;想精度高就得收紧,但可能漏掉正确答案。我的原则是:宁可多召回再精排,也不要漏召回,因为漏了就是幻觉。延迟方面,如果用户等不了三秒,我就砍掉重排,用更小的Embedding模型;成本方面,HyDE虽然效果好,但每次检索都要调一次LLM生成假设文档,性价比不高,只对长尾查询用。
其实还有一个方向我最近在关注,就是[[Agentic RAG]],让模型自己决定什么时候检索、检索什么、要不要多轮检索。这个和传统RAG的边界在哪,我觉得挺值得聊的。
所以整体上,我会把RAG看成一套系统工程,没有银弹。每个优化手段都有前提和代价,真正落地时,我更倾向于先搭一个简单但可靠的基线,再根据业务瓶颈逐步加优化,而不是一上来就堆满。