这道题问的是[[RAG]]系统里两个关键环节:一是怎么把用户的问题改得更适合检索,二是拿到文档后怎么组织提示词让模型好好回答。说白了,query改写的本质是弥合用户表达和文档表达之间的语义鸿沟,而提示词构建的核心是教会模型怎么用文档、怎么处理不确定性。
先说query改写。常见策略其实分几类,但真正落地你会发现很少只用一种,往往是组合着来。比如查询扩展,最简单的是用LLM生成同义词或近义词,把“退款流程”扩展成“退货退款步骤”“取消订单退钱”之类的,这样能提升召回。更高级一点的是HyDE,就是让模型先基于问题写一段“理想答案”,然后用这段答案去检索,而不是用原问题。这个在文档表述和用户问法差异大的时候特别有效。再一个就是查询分解,比如用户问“比较A和B的优缺点”,我会拆成“A的优点”“A的缺点”“B的优点”“B的缺点”四个子查询,分别检索再合并。这个适合多跳推理场景。另外还有查询澄清,当用户的query特别模糊,比如只说了“这个功能怎么用”,我会先让模型反问一下“您指的是哪个功能?”,等用户明确后再检索,这在对话式RAG里很实用。
这里有个边界要注意:HyDE适合语义差异大的场景,但代价是多一次LLM调用,如果系统对延迟敏感,可能就不合适。查询分解虽然能提升多跳问题的准确率,但子查询之间可能有信息重叠,需要去重,而且延迟会乘以N倍,所以必须并行化。真正落地时,我通常会根据业务场景选主策略,再辅以其他策略。举个例子,在客服退款场景里,用户可能说“我前天买的那个东西坏了”,这个query很模糊,我会先用查询澄清确定订单,然后用查询扩展把“坏了”扩展成“破损”“不工作”“有瑕疵”等,再检索。
说完改写,再讲提示词构建。拿到相关文档后,怎么组织成有效的提示词?核心是三点:上下文组织、指令设计、还有不确定性处理。上下文组织上,我会按相关性排序,高分在前,并且去重,相同内容只保留一份。如果文档太长,我会优先保留开头和结尾,中间用省略号,因为很多模型对中间内容的注意力较弱。元信息比如来源、时间、可信度分数也要标上,帮助模型判断。
指令设计上,我一般会强制要求模型标注引用,比如“根据[1]”,这样能减少[[Hallucination]]。同时明确告诉模型,如果文档不足以回答,就说不知道,不要编。如果文档信息矛盾,要列出不同观点并说明依据。这里有个坑:很多工程上线时只想着让模型多答,结果模型硬答导致错误,所以不确定性处理一定要在指令里写死。
进阶一点,我还会做[[重排序]]提示,就是把Rerank的分数作为文本提示注入,比如“文档A可信度0.95,文档B可信度0.4”,帮助模型判断哪些信息更可靠。另外,根据query类型动态选择提示模板,事实型问题用严格引用模板,观点型问题可以宽松一些。还有一个技巧是负样本提示,主动告知“以下文档与问题无关,请忽略”,这能过滤掉检索带来的噪声。
当然,实际工程中要权衡。比如HyDE能提升召回15%-20%,但多一次LLM调用,延迟和成本都上去了。查询分解也是,准确率提升但延迟翻倍。所以上线前我会先做离线评测,用[[RAGAS]]之类的框架评估不同策略的收益,再逐步上线。提示词模板也要用A/B测试验证效果。
所以整体上,我更倾向于把query改写和提示词构建看作一个系统来设计,先根据业务场景选主策略,再通过离线评测和A/B测试逐步调优,而不是一上来就堆所有策略。