微信扫码
添加专属顾问
我要投稿
解密RAG多轮会话优化的核心技术,从查询重写到实战策略一网打尽。核心内容: 1. 多轮会话在RAG系统中的关键挑战与必要性 2. 查询重写策略的原理与典型应用场景 3. 主流开源框架(LlamaIndex/LangChain)的实战解决方案
检索增强生成(Retrieval-Augmented Generation, RAG)技术通过结合外部知识库,极大地提升了大型语言模型(LLM)回答问题的准确性和时效性。一个基础的RAG应用在处理单轮、独立的问答时表现优异。然而,真实世界的交互远不止于此。人类的交流充满了上下文依赖,是自然流畅的多轮对话。
用户很自然地会将这种交流习惯带入与AI的互动中。例如,在客户服务场景中,用户可能会先问"我的订单到哪了?",在得到回复后接着问"能加急吗?"或者"怎么修改收货地址?"。如果系统无法理解后续问题中的"它"或"那个"指代的是前一轮对话的订单,那么交互体验将大打折扣。这种上下文的缺失,尤其是在检索环节,是当前许多RAG应用面临的核心挑战。本文将深入探讨如何优化RAG系统中的多轮会话能力。
面对多轮对话中上下文依赖的问题,业界普遍采用且行之有效的核心策略是查询重写(Query Rewriting),也称为查询压缩(Query Condensing)。其基本思想非常直观:在进行检索之前,利用LLM的理解能力,将当前用户的问题与之前的对话历史相结合,生成一个全新的、独立的、包含完整上下文信息的问题。
例如,对于之前的对话:
用户:我想了解一下关于保罗·格雷厄姆(Paul Graham)的信息。
AI:好的,保罗·格雷厄姆是Y Combinator的联合创始人之一,也是一位著名的程序员和作家。
用户:他后来做了什么?
直接用"他后来做了什么?"去知识库检索,效果会很差。而查询重写模块会将其改写为:"在联合创办Y Combinator之后,保罗·格雷厄姆做了什么?"。这个新问题是独立的,包含了所有必要信息,可以被RAG系统的检索模块高效、准确地处理。
目前主流的RAG框架,如LlamaIndex、LangChain,都为多轮对话场景提供了开箱即用的解决方案,其核心逻辑都离不开对会话历史的管理和查询重写。
LlamaIndex 提供了专门用于会话场景的 Chat Engine。其中,CondenseQuestionChatEngine
是实现多轮对话的基础。它的工作流程正如其名:先压缩(Condense)问题,再查询。
下面是一个使用 LlamaIndex 实现多轮对话的完整示例:
# 1. 安装必要的库
# pip install llama-index openai
import os
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, Settings
from llama_index.llms.openai import OpenAI
# 设置你的OpenAI API密钥
# os.environ["OPENAI_API_KEY"] = "YOUR_API_KEY"
# 配置LLM
Settings.llm = OpenAI(model="gpt-3.5-turbo", temperature=0)
# 2. 准备数据和索引
# 创建一个虚拟的知识库文件
os.makedirs("data", exist_ok=True)
with open("data/paul_graham_essay.txt", "w", encoding="utf-8") as f:
f.write("Paul Graham co-founded Y Combinator. After YC, he started painting. He spent most of 2014 painting. In March 2015, he started working on Lisp again.")
documents = SimpleDirectoryReader("./data").load_data()
index = VectorStoreIndex.from_documents(documents)
# 3. 配置并使用 CondenseQuestionChatEngine
chat_engine = index.as_chat_engine(
chat_mode="condense_question",
verbose=True# 设置为True以查看重写后的问题
)
# 第一轮对话
response = chat_engine.chat("What did Paul Graham do after YC?")
print(response)
# 第二轮追问
# chat_engine会记住上一轮的上下文
response_follow_up = chat_engine.chat("What about after that?")
print(response_follow_up)
当执行第二轮追问 chat("What about after that?")
时,verbose=True
会在控制台打印出类似下面的信息,清晰地展示了查询重写的威力:
Querying with: What did Paul Graham do after he started painting after leaving YC?
LlamaIndex 使用的默认Prompt模板如下,它指示LLM根据聊天记录(Chat History)和新的追问(Follow Up Message)生成一个独立的、包含所有相关上下文的问题。
Given a conversation (between Human and Assistant) and a follow up message from Human,
rewrite the message to be a standalone question that captures all relevant context
from the conversation.
<Chat History>
{chat_history}
<Follow Up Message>
{question}
<Standalone question>
LangChain 作为一个灵活的LLM应用开发框架,提供了强大的"记忆"(Memory)组件来管理和操作对话历史。这是其实现多轮对话的核心。
LangChain的记忆组件负责在对话链(Chain)的调用之间存储和传递状态。常见的记忆类型包括:
ConversationalRetrievalChain
是LangChain中专门用于构建多轮对话RAG应用的链。它巧妙地集成了记忆、查询重写和检索,其工作流程如下:
下面是一个使用LangChain实现多轮RAG对话的代码示例:
# 1. 安装必要的库
# pip install langchain langchain-openai langchain-community faiss-cpu
import os
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain.chains import ConversationalRetrievalChain
from langchain.memory import ConversationBufferMemory
from langchain.document_loaders import TextLoader
# 设置你的OpenAI API密钥
# os.environ["OPENAI_API_KEY"] = "YOUR_API_KEY"
# 2. 准备数据和索引
# 使用与LlamaIndex示例相同的数据
os.makedirs("data", exist_ok=True)
with open("data/paul_graham_essay.txt", "w", encoding="utf-8") as f:
f.write("Paul Graham co-founded Y Combinator. After YC, he started painting. He spent most of 2014 painting. In March 2015, he started working on Lisp again.")
loader = TextLoader("./data/paul_graham_essay.txt")
documents = loader.load()
vectorstore = FAISS.from_documents(documents, OpenAIEmbeddings())
# 3. 配置记忆和会话检索链
# 使用ConversationBufferMemory来存储对话历史
memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)
llm = ChatOpenAI(temperature=0)
# 创建ConversationalRetrievalChain
qa_chain = ConversationalRetrievalChain.from_llm(
llm=llm,
retriever=vectorstore.as_retriever(),
memory=memory
)
# 4. 进行多轮对话
# 第一轮
result1 = qa_chain.invoke({"question": "What did Paul Graham do after YC?"})
print(result1['answer'])
# 第二轮
result2 = qa_chain.invoke({"question": "What about after that?"})
print(result2['answer'])
在这个例子中,ConversationalRetrievalChain
内部自动处理了查询重写和上下文管理,为开发者提供了非常便捷的多轮对话RAG实现方案。
从以上主流框架的实现可以看出,基于LLM的查询重写是解决RAG多轮对话问题的通用且简洁的方案。
查询重写解决了多轮对话的核心问题,但要构建一个体验极致的对话系统,我们还可以探索更多高级策略。
随着对话轮次增多,完整的对话历史可能会变得非常长,超出LLM的上下文窗口限制,并增加API调用成本。因此,高效的上下文管理至关重要。常见策略包括:
除了将问题重写为单个独立问题外,还可以让LLM生成多个相关的问题变体。例如,当用户问"这个功能怎么用?"时,可以扩展为"XX功能的使用方法是什么?"、"XX功能的入门教程"、"XX功能的常见问题"等多个查询,然后并行检索,汇总结果。这能有效提高召回率,尤其是在知识库内容组织多样化的情况下。
一个经过精心重写的查询,包含了丰富的语义和关键词信息。此时,单一的向量检索可能不是最优解。混合搜索结合了向量检索(捕捉语义相似性)和传统的关键词检索(如BM25,精确匹配术语),能够为重写后的查询提供更全面、更准确的检索结果。
这是一种更前沿的方法。系统首先尝试识别用户的对话意图(例如:查询信息、比较产品、请求操作),然后根据意图来指导后续的检索和生成策略。例如,如果识别到用户的意图是"比较",系统可以专门去检索包含对比信息或规格参数的文档。
对于极其复杂的任务,可以将对话流程拆解给不同的"智能体"处理。例如,可以有一个"对话管理智能体"负责与用户交互和重写查询,一个"研究智能体"负责执行复杂的检索任务,还有一个"总结智能体"负责整合信息生成答案。
打造具备优秀多轮对话能力的RAG应用,是提升用户体验的关键一步。本文从工业界实践出发,阐述了查询重写作为解决此问题的核心与基础策略,并通过LlamaIndex和LangChain框架展示了其具体实现。
查询重写虽然简单,但效果显著,是所有RAG开发者都应掌握的基础技术。在此之上,通过引入更精细的上下文管理、查询扩展、混合搜索乃至更前沿的意图识别和多智能体架构,我们可以将RAG应用的对话能力推向新的高度,使其不仅能准确回答问题,更能像一个真正的专家助手一样,与用户进行自然、连贯、深入的交流。
欢迎加入我的知识星球
本星球有更多的技术和实战内容,专注RAG、Agents、Dify、Ragflow、知识图谱等大模型应用研发企业落地实践、案例教程、最新技术动态等内容,重在技术前沿、企业应用和实战,欢迎大家交流。
53AI,企业落地大模型首选服务商
产品:场景落地咨询+大模型应用平台+行业解决方案
承诺:免费POC验证,效果达标后再合作。零风险落地应用大模型,已交付160+中大型企业
2025-08-17
基于LLM知识图谱构建高精度RAG
2025-08-17
从图的视角看 RAG:GraphRAG 的工作方式与思考
2025-08-16
使用RAG构建高质量知识库(三)- 数据嵌入
2025-08-14
RAG实践技巧:将向量库降级为“语义路由器”,让答案更合理
2025-08-14
别只顾着卷检索了!真正决定RAG上限的,是这四个“后处理”工程
2025-08-14
RAG 入门指南:LlamaIndex、GraphRAG、 RAGFlow 学习建议与技术选型
2025-08-14
Spring AI 高级 RAG 优化指南:文档预处理提升检索精度与召回率
2025-08-13
一文了解Ragflow知识库优化检索的方法
2025-05-30
2025-06-05
2025-06-06
2025-06-05
2025-05-20
2025-05-27
2025-06-05
2025-06-20
2025-06-24
2025-06-05
2025-08-11
2025-08-05
2025-07-28
2025-07-09
2025-07-04
2025-07-01
2025-07-01
2025-07-01