跳转到内容

查询

现在您已经加载了数据,构建了索引,并将该索引存储以备后用,接下来准备进入LLM应用最重要的部分:查询。

在最简单的情况下,查询只是向大型语言模型发起提示调用:它可以是一个问题并得到答案,或是一个摘要请求,也可以是一个复杂得多的指令。

更复杂的查询可能涉及重复/链式提示 + 大语言模型调用,甚至跨多个组件的推理循环。

所有查询的基础是 QueryEngine。获取 QueryEngine 的最简单方法是让您的索引为您创建一个,如下所示:

query_engine = index.as_query_engine()
response = query_engine.query(
"Write an email to the user given their background information."
)
print(response)

然而,查询的复杂性远超表面所见。查询包含三个不同的阶段:

  • 检索是指从你的Index中查找并返回与查询最相关的文档。正如之前在索引中讨论过的,最常见的检索类型是“top-k”语义检索,但还有许多其他检索策略。
  • 后处理是指对检索到的Node进行可选的重排序、转换或过滤,例如要求它们具有特定的元数据(如附加的关键字)。
  • 响应合成是指将您的查询、最相关数据以及提示组合起来,发送给您的LLM以返回响应的过程。

LlamaIndex 提供了一个低层级的组合API,让您能够对查询过程进行细粒度控制。

在此示例中,我们自定义检索器以对 top_k 使用不同的数值,并添加一个后处理步骤,要求检索到的节点达到最低相似度分数才能被包含。当存在相关结果时,这将为您提供大量数据,但若没有任何相关内容,则可能不会返回数据。

from llama_index.core import VectorStoreIndex, get_response_synthesizer
from llama_index.core.retrievers import VectorIndexRetriever
from llama_index.core.query_engine import RetrieverQueryEngine
from llama_index.core.postprocessor import SimilarityPostprocessor
# build index
index = VectorStoreIndex.from_documents(documents)
# configure retriever
retriever = VectorIndexRetriever(
index=index,
similarity_top_k=10,
)
# configure response synthesizer
response_synthesizer = get_response_synthesizer()
# assemble query engine
query_engine = RetrieverQueryEngine(
retriever=retriever,
response_synthesizer=response_synthesizer,
node_postprocessors=[SimilarityPostprocessor(similarity_cutoff=0.7)],
)
# query
response = query_engine.query("What did the author do growing up?")
print(response)

您也可以通过实现相应的接口来添加自己的检索、响应合成和整体查询逻辑。

有关已实现组件及支持配置的完整列表,请查阅我们的参考文档

让我们更详细地了解如何自定义每个步骤:

retriever = VectorIndexRetriever(
index=index,
similarity_top_k=10,
)

在我们的检索器模块指南中,您可以了解各种各样的检索器。

我们支持高级的Node过滤和增强功能,可以进一步提高检索到的Node对象的相关性。 这有助于减少LLM调用时间/次数/成本或提升响应质量。

例如:

  • KeywordNodePostprocessor: 通过 required_keywordsexclude_keywords 过滤节点。
  • SimilarityPostprocessor: 通过设置相似度分数的阈值来筛选节点(因此仅支持基于嵌入的检索器)
  • PrevNextNodePostprocessor: 基于Node关系,为检索到的Node对象增强附加相关上下文。

完整的节点后处理器列表记录在节点后处理器参考中。

配置所需的节点后处理器:

node_postprocessors = [
KeywordNodePostprocessor(
required_keywords=["Combinator"], exclude_keywords=["Italy"]
)
]
query_engine = RetrieverQueryEngine.from_args(
retriever, node_postprocessors=node_postprocessors
)
response = query_engine.query("What did the author do growing up?")

在检索器获取相关节点后,BaseSynthesizer 通过整合信息来合成最终响应。

您可以通过以下方式配置它

query_engine = RetrieverQueryEngine.from_args(
retriever, response_mode=response_mode
)

目前,我们支持以下选项:

  • default:通过依次遍历每个检索到的Node来“创建并完善”答案; 每个节点都会进行一次独立的LLM调用。适用于需要更详细答案的场景。
  • compact:在每次大语言模型调用时通过将尽可能多的Node文本块塞入最大提示尺寸内来“压缩”提示。如果文本块过多无法一次性塞入单个提示,则通过多个提示“创建并优化”答案。
  • tree_summarize: 给定一组Node对象和查询,递归构建树结构并返回根节点作为响应。适用于摘要生成场景。
  • no_text: 仅运行检索器获取本应发送给LLM的节点,而不实际发送它们。然后可通过检查response.source_nodes进行查看。响应对象在第5节中有更详细的说明。
  • accumulate:给定一组Node对象和查询,将查询应用于每个Node文本片段,同时将响应累积到数组中。返回所有响应的连接字符串。适用于需要对每个文本片段分别运行相同查询的场景。

您可能希望确保输出具有结构化。请参阅我们的查询引擎 + Pydantic 输出了解如何从查询引擎类中提取 Pydantic 对象。

同时请务必查阅我们完整的结构化输出指南。

如果您想要设计复杂的查询流程,可以跨多个不同模块组合自己的查询工作流,从提示词/大语言模型/输出解析器到检索器,再到响应合成器,直至您自己的自定义组件。

查看我们的工作流指南获取更多详情。