Skip to content

查询#

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

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

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

快速开始#

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

query_engine = index.as_query_engine()
response = query_engine.query(
    "根据用户的背景信息写一封邮件。"
)
print(response)

查询阶段#

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

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

Tip

你可以了解如何为文档附加元数据节点

自定义查询阶段#

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

# 构建索引
index = VectorStoreIndex.from_documents(documents)

# 配置检索器
retriever = VectorIndexRetriever(
    index=index,
    similarity_top_k=10,
)

# 配置响应合成器
response_synthesizer = get_response_synthesizer()

# 组装查询引擎
query_engine = RetrieverQueryEngine(
    retriever=retriever,
    response_synthesizer=response_synthesizer,
    node_postprocessors=[SimilarityPostprocessor(similarity_cutoff=0.7)],
)

# 查询
response = query_engine.query("作者成长过程中做过什么?")
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("作者成长过程中做过什么?")

配置响应合成#

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

你可以通过以下方式配置它:

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

目前我们支持以下选项:

  • default:通过依次处理每个检索到的Node"创建并优化"答案; 这会对每个Node进行单独的LLM调用。适合需要更详细答案的情况。
  • compact:在每次LLM调用时"压缩"提示,尽可能多地塞入符合最大提示大小的Node文本块。如果 有太多块无法塞入一个提示中,则通过多个提示"创建并优化"答案。
  • tree_summarize:给定一组Node对象和查询,递归构建树 并返回根节点作为响应。适合摘要目的。
  • no_text:仅运行检索器获取本应发送给LLM的节点, 而不实际发送它们。然后可以通过检查response.source_nodes来查看。 响应对象在第5节中有更详细的介绍。
  • accumulate:给定一组Node对象和查询,将查询应用于每个Node文本 块,同时将响应累积到数组中。返回所有响应连接而成的字符串。 适合需要对每个文本块单独运行相同查询的情况。

结构化输出#

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

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

创建自定义查询工作流#

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

更多详情请参阅我们的工作流指南