查询扩展在RAG中的作用
想象一下:用户在你的RAG系统中输入了“Python性能问题”。看起来很简单,对吗?不完全是。
他们可能是在寻找关于内存优化、执行速度、调试甚至版本比较的技巧。
但如果您的检索只查询原始的问题,它可能会因为缺乏足够的上下文来回答原始问题而得到不相关或错误的答案。
这就是查询扩展变得至关重要的地方。
1、什么是查询扩展?
查询扩展是重新表述和丰富用户查询的过程,以提高RAG系统中的检索准确性。
除了使用原始查询进行搜索外,系统会生成多个变体、同义词和相关术语,在检索阶段扩大搜索范围。
它还可以包括使用之前的上下文 (考虑聊天系统) 来理解当前查询。
研究表明,查询扩展通过检索更多相关和精确的上下文来改善RAG结果。
把它想象成与一位知识渊博的图书管理员交谈。当你问“关于狗的书”时,他们不会仅仅查找这个确切的短语。
他们会搜索“犬类”、“小狗”、“狗品种”、“宠物护理”等相关主题,因为他们了解你所寻求的更广泛背景。
2、查询扩展技术
现在我们将介绍一些查询扩展技术,这些技术将帮助我们为回答用户查询检索高质量文档。
我们将主要专注于使用某种“上下文”来扩展查询的方法。
2.1 使用关键词进行扩展
这种方法源于这样一个事实:用户的查询将包含一些指向知识库中主题的关键词。
我们可以构建一个类似命名实体识别器 (NER)的东西,并让它从用户的查询中提取关键词。
大语言模型在这个任务上表现非常出色,因此我们可以要求它提取关键词。使用这些关键词,我们可以扩展我们的查询。下面的例子展示了使用LangChain链式这两个操作:
from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain.chains import LLMChain, SequentialChain
# 初始化OpenAI LLM
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# ---- STEP 1: 关键字提取 ----
keyword_prompt = ChatPromptTemplate.from_template("""
您将获得一个查询。提供最多3个关键字从给定的输入查询中
从以下查询中提取最相关的关键词。
以逗号分隔的列表返回它们,不要有任何额外文本。
重要说明:
- **不要列出关键字**。
- **不要生成重复的关键字**。
示例:
输入查询:“如何设置SSO?”
关键字:
答案:["SSO", "Single Sign-On"]
查询:"{query}"
""")
keyword_chain = LLMChain(
llm=llm,
prompt=keyword_prompt,
output_key="keywords"
)
# ---- STEP 2: 查询扩展 ----
expansion_prompt = ChatPromptTemplate.from_template("""
你是一个生成查询扩展的助手。
给定一个基础查询和一个关键词列表,创建{num_variations}
自然流畅的查询变体。每个变体应有意义地融入至少一个关键词。
基础查询:"{query}"
关键词:{keywords}
只输出变体,每行一个,不要编号。
""")
expansion_chain = LLMChain(
llm=ChatOpenAI(model="gpt-4o-mini", temperature=0.7),
prompt=expansion_prompt,
output_key="expanded_queries"
)
# ---- 合并为SequentialChain ----
query_expansion_chain = SequentialChain(
chains=[keyword_chain, expansion_chain],
input_variables=["query", "num_variations"],
output_variables=["keywords", "expanded_queries"],
verbose=True
)
result = query_expansion_chain({
"query": "如何优化Python代码以实现更快的执行和更低的内存使用?",
"num_variations": 5
})
print("\n提取的关键字:", result["keywords"])
print("\n扩展的查询:")
print(result["expanded_queries"])
如果您有一些文档语料库或问题,也可以使用sparse retrievers like BM25来提取关键词…
from rank_bm25 import BM25Okapi
import nltk
from nltk.corpus import stopwords
import string
def extract_keywords_bm25(query: str, corpus: list, top_k: int = 5) -> list:
stop_words = set(stopwords.words('english'))
# 对语料库进行分词
tokenized_corpus = [
[word.lower() for word in doc.split() if word.lower() not in stop_words and word not in string.punctuation]
for doc in corpus
]
bm25 = BM25Okapi(tokenized_corpus)
# 对查询进行分词
tokenized_query = [word.lower() for word in query.split() if word.lower() not in stop_words]
# 获取BM25分数
scores = bm25.get_scores(tokenized_query)
# 找到得分最高的文档
top_doc_idx = scores.argmax()
top_doc_tokens = tokenized_corpus[top_doc_idx]
# 返回来自最高文档的top_k唯一关键词
keywords = list(dict.fromkeys(top_doc_tokens))[:top_k]
return keywords
2.2 使用之前上下文进行扩展
如果您正在构建一个维护之前上下文的应用程序(如聊天应用程序),您可以使用之前的对话作为上下文来扩展您的查询。
from openai import OpenAI
client = OpenAI(api_key="YOUR_API_KEY")
def expand_query_with_context(previous_messages: list, current_query: str, num_variations: int = 5) -> list:
# 将之前的对话合并成一个上下文字符串
conversation_context = "\n".join(previous_messages)
prompt = f"""
你是一个为信息检索系统生成查询扩展的助手。
使用之前的对话上下文来理解主题并保持扩展的相关性。
生成{num_variations}个自然流畅的当前查询变体,
包含同义词、相关短语或替代措辞。
到目前为止的对话:
{conversation_context}
当前查询:
"{current_query}"
只输出变体,每行一个,不要编号或额外评论。
"""
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": "你是查询扩展的有帮助助手。"},
{"role": "user", "content": prompt}
],
temperature=0.7 # 给LLM更多的自由度来生成变体
)
# 提取和清理输出
variations_text = response.choices[0].message.content.strip()
variations = [line.strip() for line in variations_text.split("\n") if line.strip()]
return variations
previous_chats = [
"用户:我想学习如何加快Python代码。",
"助手:你可以尝试对代码进行分析以找到瓶颈并进行优化。",
"用户:那关于内存使用呢?"
]
latest_query = "如何优化Python代码以提高性能?"
expanded_queries = expand_query_with_context(previous_chats, latest_query, num_variations=5)
print("扩展的查询:")
for q in expanded_queries:
print("-", q)
2.3 生成假设文档以扩展查询
如果你没有任何之前的上下文,那就自己编造一个!
这种技术类似于HyDE(假设文档嵌入),其中我们在使用查询向量数据库查找来源时,使用假设的答案和我们的查询一起。
这在你使用一般可用信息(如产品目录、公开研究等)构建RAG系统时效果非常好,因为LLMs会接受这些知识进行训练。
我们使用LLMs为我们的查询生成一个假设的答案,然后使用这个假设的答案作为上下文进行查询扩展。
from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain.chains import LLMChain, SequentialChain
# 初始化LLMs
hypo_llm = ChatOpenAI(model="gpt-4o-mini", temperature=0) # 用于假设答案的确定性
expand_llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.7) # 用于查询扩展的创造性
# --- 第一步:生成假设答案 ---
hypo_prompt = ChatPromptTemplate.from_template("""
你是一个专家助手。给定以下问题,
生成一个详细且相关的假设答案。
问题:"{query}"
""")
hypo_chain = LLMChain(
llm=hypo_llm,
prompt=hypo_prompt,
output_key="hypothetical_answer"
)
# --- 第二步:使用假设答案作为上下文进行查询扩展 ---
expand_prompt = ChatPromptTemplate.from_template("""
你是一个查询扩展助手。
使用以下假设答案作为上下文,生成{num_variations}
自然流畅的原始查询变体。
包含同义词、相关术语和改写。
假设答案:
{hypothetical_answer}
原始查询:
"{query}"
只输出变体,每行一个,不要编号或额外评论。
""")
expand_chain = LLMChain(
llm=expand_llm,
prompt=expand_prompt,
output_key="expanded_queries"
)
# --- 第三步:组合为SequentialChain ---
hyde_expansion_chain = SequentialChain(
chains=[hypo_chain, expand_chain],
input_variables=["query", "num_variations"],
output_variables=["hypothetical_answer", "expanded_queries"],
verbose=True
)
query = "如何优化Python代码以实现更快的执行和更低的内存使用?"
result = hyde_expansion_chain({
"query": query,
"num_variations": 5
})
print("\n--- 假设答案 ---")
print(result["hypothetical_answer"])
print("\n--- 扩展的查询 ---")
print(result["expanded_queries"])
3、结束语
在本文中,我们探讨了查询扩展的定义及其原因,以及它在检索之前如何证明是一个关键步骤。我们还探讨了一些可以用于查询扩展的方法。
原文链接:Making Queries Smarter: The Role of Queries Expansion in RAG
汇智网翻译整理,转载请标明出处