跳到内容

现在您已经加载了数据,构建了索引,并存储了索引以备后用,您就可以开始 LLM 应用中最关键的部分了:查询。

最简单的查询只是对 LLM 的一次提示调用:它可以是一个问题并获取答案,或者是一个摘要请求,亦或是一个更复杂的指令。

更复杂的查询可能涉及重复/链式提示 + 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:在每次 LLM 调用期间“压缩”提示,将尽可能多的 Node 文本块填充到最大提示大小内。如果在一个提示中无法容纳太多块,则通过多个提示进行“创建并精炼”答案。
  • tree_summarize:给定一组 Node 对象和查询,递归构建一棵树并返回根节点作为响应。适用于摘要目的。
  • no_text:仅运行检索器以获取本应发送给 LLM 的节点,但不实际发送。然后可以通过检查 response.source_nodes 进行检查。响应对象将在第 5 节更详细地介绍。
  • accumulate:给定一组 Node 对象和查询,将查询应用于每个 Node 文本块,同时将响应累积到数组中。返回所有响应的连接字符串。适用于需要针对每个文本块单独运行相同查询的情况。
  • 结构化输出#

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

另外,请务必查看我们的完整结构化输出指南。

创建自己的查询工作流#

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

有关更多详细信息,请参阅我们的工作流指南

返回顶部