这道题我觉得本质在问一件事:你手头有一堆碎片化的检索结果,怎么把它拼成一句人话,而且拼的时候不能瞎编,也不能漏掉关键信息。说白了就是检索和生成中间的衔接,这是[[RAG]]落地最容易翻车的地方。
先说检索结果怎么整理。直接拿Top-K文档拼起来肯定不行,我一般会做三件事。先说按相关性得分[[重排]],把最靠谱的放前面,因为模型对开头的文档注意力更集中。然后看动态截断,模型上下文窗口就那么长,我会预留20%给[[系统提示]]和用户问题,剩下的才分给文档,超长的直接切掉。另外还要看去重和冲突检测,比如两段文档都在讲同一个政策但说法不一样,我会把相似度高的合并,矛盾的内容保留并在Prompt里让模型自己判断。
然后就是Prompt模板设计。我习惯用三层结构:最上面是系统指令,定义角色和任务,比如“你是电商客服助手,只能根据提供的文档回答,不确定就说不知道”;中间是上下文区块,用清晰的标记把每段文档包起来,标注来源ID;最下面是用户查询,因为模型对最近看到的Token更敏感。这里有个小技巧,我会加两三个[[Few-shot]]例子,覆盖“有答案、无答案、多文档综合”三种情况,模型学得很快。还有一个反[[幻觉]]指令特别重要:如果文档里没提,必须回答“根据现有资料无法确定”,这个指令能显著降低幻觉率。
推理策略上,我会根据场景调参数。事实性问答比如退款政策,Temperature设到0.1到0.3,让模型尽量确定,不瞎发挥。如果是创意生成比如写商品描述,可以调到0.7以上。多文档综合时我还会用Beam Search生成多个候选,选一个最合理的。生成完之后还有后处理,我会正则提取模型输出的来源ID,跟原文对一下,确保每句话都能溯源。如果模型生成某个Token的概率特别低,我可能会触发二次检索或者直接标记出来让人工复核。
举个例子,电商客服场景下用户问“满减和优惠券能一起用吗”。如果文档里只说了满减规则,没提优惠券,模型很容易自己推断说“可以”,这就幻觉了。所以我会在Prompt里强调“如果文档没提,就说不知道”,同时后处理时检查引用,如果答案引用了文档1但文档1根本没提优惠券,就拦截掉。
这里有个延伸点:当文档特别多的时候,单纯靠截断可能会丢掉关键信息。我最近在研究用 [[Agentic RAG]] 的思路,让模型先判断还需要什么信息,主动去检索第二轮,而不是一次把所有文档塞进去。这样能更精准,但延迟会高一些,需要做取舍。
所以整体上,我更愿意把生成模块看作一个受约束的翻译器,它的核心任务是把检索结果忠实地转成自然语言,不是自由创作。上线前我会特别关注反幻觉指令的效果和引用溯源的覆盖率,这两个点是最容易暴雷的。