介绍
Rewrite-Retrieve-Read (重写-检索-阅读) 是一种改进 RAG(Retrieve-and-Read)流程的方法,最早由论文 Query Rewriting for Retrieval-Augmented Large Language Models 提出。
在现实世界中,用户的原始查询往往并不适合直接用于检索,尤其是在 LLM 任务中。为了解决这个问题,该方法首先利用 LLM 重写查询,使其更加适合检索,进而提升最终回答的准确性。
本篇文章将展示如何使用 LangChain Expression Language 来实现 Rewrite-Retrieve-Read。
基准 RAG 方法
基准 RAG (Retrieve-and-Read) 方法的实现如下:
from langchain.chat_models import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain.schema.output_parser import StrOutputParser
from langchain.schema.runnable import RunnablePassthrough
from langchain.utilities import DuckDuckGoSearchAPIWrapper
# 定义 Prompt 模板
template = """Answer the user's question based only on the following context:
<context>
{context}
</context>
Question: {question}
"""
prompt = ChatPromptTemplate.from_template(template)
model = ChatOpenAI(temperature=0)
search = DuckDuckGoSearchAPIWrapper()
def retriever(query):
return search.run(query)
# 构建检索-阅读流水线
chain = (
{"context": retriever, "question": RunnablePassthrough()}
| prompt
| model
| StrOutputParser()
)
# 示例查询
simple_query = "what is langchain?"
chain.invoke(simple_query)
问题分析
上述方法对于格式良好的查询表现良好,但如果查询包含无关内容(如带有情绪或背景信息的长句),检索效果可能会大幅下降。例如:
distracted_query = "man that sam bankman fried trial was crazy! what is langchain?"
chain.invoke(distracted_query)
由于检索器不能有效处理这些"分散注意力的查询",它可能会返回不相关的搜索结果。
Rewrite-Retrieve-Read 实现
为了解决上述问题,我们可以先使用 LLM 重写查询,然后再进行检索。具体实现如下。
查询重写模块
from langchain.chat_models import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain.schema.output_parser import StrOutputParser
# 定义查询重写 Prompt
template = """Provide a better search query for \
web search engine to answer the given question, end \
the queries with ’**’. Question: \
{x} Answer:"""
rewrite_prompt = ChatPromptTemplate.from_template(template)
# 也可以使用 LangChain Hub 中的现成 Prompt
from langchain import hub
rewrite_prompt = hub.pull("langchain-ai/rewrite")
# 解析查询
def _parse(text):
return text.strip("**")
rewriter = rewrite_prompt | ChatOpenAI(temperature=0) | StrOutputParser() | _parse
# 示例测试
rewriter.invoke({"x": distracted_query})
Rewrite-Retrieve-Read 流水线
rewrite_retrieve_read_chain = (
{
"context": {"x": RunnablePassthrough()} | rewriter | retriever,
"question": RunnablePassthrough(),
}
| prompt
| model
| StrOutputParser()
)
# 执行查询
rewrite_retrieve_read_chain.invoke(distracted_query)
结论
Rewrite-Retrieve-Read 方法通过引入 查询重写 (Query Rewrite),使检索更加精准,提升了最终 LLM 生成回答的质量。相比于传统 Retrieve-and-Read 方法,它能够更好地应对用户提供的非标准化查询,适用于 搜索引擎增强生成 (RAG)、问答系统、法律/医学文档检索 等应用场景。
如果你对这个方法感兴趣,欢迎在评论区交流你的想法!
如果你觉得这篇博文对你有帮助,请点赞、收藏、关注我,并且可以打赏支持我!
欢迎关注我的后续博文,我将分享更多关于人工智能、自然语言处理和计算机视觉的精彩内容。
谢谢大家的支持!