宏观上看RAG的过程分为以下6个部分:
上传、分块、向量化、索引、检索、生成
他们又分为两个阶段。
一、准备阶段(离线做一次)
上传
这一步需要把各种格式的文档(txt、docx、pdf)解析成干净的纯文本。
分块
根据文档不同的类型选择不同的分块策略把长文档切成小块。
因为:
- 大模型上下文窗口有限
- 一次检索只需要文档中的一部分,塞入大量无关文本反而影响模型生成内容的质量
向量化
调用embedding模型把chunk文本向量化。
通过向量去匹配语义,语义越相近的两段文本在向量空间中距离越近。
索引
把向量存储到向量数据库中并建立索引,供后续检索使用。
二、运行阶段(每次都执行)
检索
把用户提的问题向量化后去数据库匹配相似的n条chunk
(检索阶段还有很多细节,后文继续讨论)
生成
把检索到的文本和systempormpt、用户问题拼接起来一并发送给LLM生成回答。
检索在后端经过的几个阶段
深入项目后,检索阶段内部也分为几个阶段:
1. 加载对话历史
如果系统需要支持多轮对话,就需要引入对话历史,否则 LLM 无法获得之前对话中的上下文信息。关键是上下文窗口是有限的,不能把所有历史都无脑塞进去,常见的策略有:
-
滑动窗口
只保留最近的n轮对话,缺点:可能在开局给LLM交代了一些重要内容,经过n轮的对话后它丢失了最开始的重要信息
-
滑动窗口 + 摘要压缩
这是对方案1的优化,同样保留最近的n轮对话,并且对于滑出窗口的对话进行压缩,保留核心的内容
缺点:调用LLM压缩产生额外的成本,且增加接口响应时间
2. 问题重写和拆分
问题重写主要是去掉问题中的代词,例如:
第一轮,用户:iphone15的保修策略是什么?
第一轮,LLM:xxxxxxxxxx
第二轮,用户:那它的退货政策是什么?
问题就出在这个“它”上,大模型可以理解语义,对照上下文知道这里的它指的是iphone15。
但如果直接用“它”去检索,可能检索到的是“macbook的退货政策”。
再来看问题拆分:
用户:airport的退货流程和保修策略什么?
退货流程和保修策略在不同位置,不同chunk,一起检索的话会导致得分不高,可能影响检索效果。
对于包含多个独立意图的问题,将问题拆分后分别检索,通常可以提高召回结果与各个子问题的相关性,且意图识别需要对每个子问题分析。
3. 意图识别
用户的问题不一定都是需要到知识库检索的,假如随手问个hi,直接让LLM回答就好了,检索反而多此一举;
再比如问“我的年假还有几天?”,系统内部的数据需要调用工具(或MCP)获取数据,到知识库检索反而导致内容错误。
意图的识别也是通过LLM打分来做的。
4. 歧义引导【短路点】
用户问的问题可能太模糊了,有歧义,与其随便检索一个相关性不高的答案,不如引导用户来澄清,在下一次对话中通过问题重写得到精确的问题。
用户:保修政策是什么?
系统引导:您说的是iphone的还是airport或是其他?
5. 系统直答【短路点】
当意图识别到为系统直达后就不需要走后续的流程了,提前结束,直接让LLM回答。
6. 多通道检索
根据意图分别到知识库检索和mcp工具调用(并行),最终将结果合并到一起。
7. 空结果处理
如果没有检索到结果,不要让LLM随意发挥,直接告诉用户“未检索到与问题相关的内容”,不然大模型基于自己训练的数据回答和实际的情况大相径庭,对用户来说完全是误导,不可接受。
8. 拼接prompt流式生成
可能根据不同场景调整参数,例如kb检索时需要稳定,可以把temperature调成0严格按照检索到的上下文回答,调用工具时允许一定程度的发挥temperature=0.3

2081

被折叠的 条评论
为什么被折叠?



