LlamaIndex QueryEngineTool 深度指南:将查询引擎封装为 Agent 工具的完整实战
【免费下载链接】llama_indexLlamaIndex is the document processing platform for AI项目地址: https://gitcode.com/GitHub_Trending/ll/llama_index
导读
本指南以 LlamaIndex 核心库中llama_index.core.tools.query_engine模块为骨架,系统讲解如何将任意 Query Engine(查询引擎)封装为可被 Agent 直接调用的工具QueryEngineTool,并延伸到带质量评估的EvalQueryEngineTool、工具元数据ToolMetadata、输出结构ToolOutput,以及它们在SubQuestionQueryEngine等复合查询引擎中的典型应用。读完本文,你将掌握工具化封装的全部关键 API、参数语义、输入解析规则与底层调用链,能够把知识库问答能力无缝接入 ReAct 等 Agent 工作流。
说明:
docs/api_reference/api_reference/tools/query_engine.md是使用 mkdocstrings 语法::: llama_index.core.tools.query_engine生成的 API 参考页,其完整内容由源码模块 query_engine.py 提供。本文即以此模块及其测试、应用场景为事实依据展开。
一、QueryEngineTool 是什么:查询引擎与 Agent 之间的适配层
在 LlamaIndex 中,Query Engine 负责“接收自然语言查询 → 检索索引 → 合成回答”的完整链路;而 Agent 需要的是“可被 LLM 理解、按函数签名调用”的工具抽象。QueryEngineTool(位于 query_engine.py)正是这两者之间的桥梁:
- 它继承自
AsyncBaseTool(定义于 types.py),既满足 LlamaIndex 自身 Agent 的工具接口,也兼容工具选择(ToolSelection)等调用机制; - 它持有
BaseQueryEngine实例和ToolMetadata元数据,LLM 通过元数据中的名称与描述来决定“何时调用、怎样调用”; - 每次调用等价于执行一次
query_engine.query(query_str),并把返回的Response包装成统一的ToolOutput。
模块级默认值清晰表达了其定位(query_engine.py):
DEFAULT_NAME = "query_engine_tool" DEFAULT_DESCRIPTION = """Useful for running a natural language query against a knowledge base and get back a natural language response. """也就是说:不显式指定name和description时,工具默认名为query_engine_tool,默认描述为“对知识库执行自然语言查询并返回自然语言回复”。实际生产环境中强烈建议自定义更精确的描述——因为 LLM 正是依靠描述来判断何时选用该工具的。
二、快速上手:三行代码把索引变成 Agent 工具
以from_defaults工厂方法为入口,封装过程最简形式如下:
from llama_index.core.tools import QueryEngineTool from llama_index.core import VectorStoreIndex index = VectorStoreIndex.from_documents(documents) query_engine = index.as_query_engine() tool = QueryEngineTool.from_defaults( query_engine=query_engine, name="docs_query", description="查询公司内部产品文档知识库,支持自然语言提问。", )之后即可把tool交给 Agent(如ReActAgent、FunctionCallingAgent),或直接手动调用:
# 同步调用 output = tool("什么是 LlamaIndex?") print(output.content) # 异步调用 output = await tool.acall(input="什么是 LlamaIndex?")工厂方法的完整签名(query_engine.py):
@classmethod def from_defaults( cls, query_engine: BaseQueryEngine, name: Optional[str] = None, description: Optional[str] = None, return_direct: bool = False, resolve_input_errors: bool = True, ) -> "QueryEngineTool"各参数语义如下:
| 参数 | 类型 | 默认值 | 作用 |
|---|---|---|---|
query_engine | BaseQueryEngine | 必填 | 被包装的查询引擎,任意实现了query/aquery的引擎均可(索引查询引擎、CustomQueryEngine、复合引擎等) |
name | str | "query_engine_tool" | 工具名称,Agent 与 LLM 据此引用该工具 |
description | str | 模块默认描述 | 工具的用途说明,是 LLM 决定“是否调用、何时调用”的关键信号 |
return_direct | bool | False | 若为True,工具返回结果将直接作为 Agent 最终回复,不再进入后续推理循环 |
resolve_input_errors | bool | True | 输入参数不匹配时是否容错解析(详见第五节) |
三、核心调用链:call / acall 如何执行一次查询
QueryEngineTool实现了AsyncBaseTool的两个核心方法(query_engine.py):
def call(self, *args: Any, **kwargs: Any) -> ToolOutput: query_str = self._get_query_str(*args, **kwargs) response = self._query_engine.query(query_str) return ToolOutput( content=str(response), tool_name=self.metadata.get_name(), raw_input={"input": query_str}, raw_output=response, ) async def acall(self, *args: Any, **kwargs: Any) -> ToolOutput: query_str = self._get_query_str(*args, **kwargs) response = await self._query_engine.aquery(query_str) return ToolOutput( content=str(response), tool_name=self.metadata.get_name(), raw_input={"input": query_str}, raw_output=response, )从中可以看到完整的底层调用链:
_get_query_str(*args, **kwargs)从调用参数中提取查询字符串;- 同步路径调用
query_engine.query(query_str),异步路径调用query_engine.aquery(query_str); - 结果被封装为
ToolOutput,其中:content为str(response)(响应文本,供 LLM 直接阅读);tool_name为工具元数据中的名称;raw_input记录原始查询输入;raw_output保留完整Response对象(含source_nodes等结构化信息,可供后处理或评估器使用)。
query_engine和metadata均以只读 property 暴露(query_engine.py),便于外部获取底层引擎与元数据。
四、输入解析规则:_get_query_str的三种匹配路径
工具被 LLM 或手动调用时,参数形态并不总是标准化的。_get_query_str(query_engine.py)按优先级处理三种情况:
def _get_query_str(self, *args: Any, **kwargs: Any) -> str: if args is not None and len(args) > 0: query_str = str(args[0]) # 1) 位置参数 elif kwargs is not None and "input" in kwargs: query_str = kwargs["input"] # 2) input 关键字参数 elif kwargs is not None and self._resolve_input_errors: query_str = str(kwargs) # 3) 容错:整体转字符串 else: raise ValueError( "Cannot call query engine without specifying `input` parameter." ) return query_str三种路径逐一说明:
- 位置参数:
tool("hello world")直接把第一个位置参数作为查询串; input关键字:tool(input="foo")—— 这是默认函数签名DefaultToolFnSchema的标准字段(见第六节),也是 Agent 函数调用场景下最常见的形态;- 容错解析:当传入
tool(tmp="hello")这类未匹配参数时,若resolve_input_errors=True,会整体转成字符串"{'tmp': 'hello'}"交给查询引擎,避免工具调用崩溃;若resolve_input_errors=False,则抛出ValueError。
这一容错行为在单元测试 test_query_engine_tool.py 中有完整覆盖:测试同时验证了位置参数、input关键字、按fn_schema生成的参数字典、以及resolve_input_errors开启/关闭两种场景下的行为。
五、ToolMetadata:LLM 眼中的工具说明书
每个工具都携带一个ToolMetadata实例(定义于 types.py):
@dataclass class ToolMetadata: description: str name: Optional[str] = None fn_schema: Optional[Type[BaseModel]] = DefaultToolFnSchema return_direct: bool = False默认函数签名DefaultToolFnSchema只有一个字段:
class DefaultToolFnSchema(BaseModel): """Default tool function Schema.""" input: str即:默认情况下,LLM 眼中的该工具就是一个“只接受一个input字符串参数”的函数。这解释了为什么_get_query_str专门兼容input关键字。
ToolMetadata还负责把工具描述转换成 LLM 供应商可识别的 JSON Schema:
get_parameters_dict()(types.py)基于fn_schema.model_json_schema()生成参数声明,fn_schema为None时退化为{"input": "input query string", "type": "string"}的兜底结构;to_openai_tool()(types.py)生成 OpenAI 风格的{"type": "function", "function": {...}}工具描述,并做两项校验:描述超过 1024 字符会抛出ValueError;名称通过_sanitize_name清洗为只含[a-zA-Z0-9_-]的合法函数名;- 旧版
to_openai_function()已被标记@deprecated,应改用to_openai_tool。
六、ToolOutput:统一的工具输出结构
ToolOutput(types.py)是所有工具的标准化返回类型:
class ToolOutput(BaseModel): blocks: List[ContentBlock] # 内容块列表(含文本块) tool_name: str raw_input: Dict[str, Any] raw_output: Any is_error: bool = False构造时content字符串会被自动包装为TextBlock,且content与blocks不能同时传入(否则抛ValueError)。content属性则将多个文本块拼接为纯文本,方便 Agent 直接消费。
七、进阶:EvalQueryEngineTool——带质量评估闸门的工具
EvalQueryEngineTool继承自QueryEngineTool(实现于 eval_query_engine.py),它在查询后追加一步回答相关性评估:只有评估通过,工具结果才会原样返回;评估失败则把内容替换为失败模板:
FAILED_TOOL_OUTPUT_TEMPLATE = ( "Could not use tool {tool_name} because it failed evaluation.\nReason: {reason}" )关键点:
from_defaults中未显式传入evaluator时,默认使用AnswerRelevancyEvaluator(eval_query_engine.py);call/acall在父类执行后调用evaluator.evaluate_response(query, response),依据EvaluationResult.passing决定放行还是替换输出(eval_query_engine.py);- 评估者本身可以是任意
BaseEvaluator实现,例如自定义的基于 LLM 的评估器。
使用示例:
from llama_index.core.tools.eval_query_engine import EvalQueryEngineTool eval_tool = EvalQueryEngineTool.from_defaults( query_engine=query_engine, name="verified_docs_query", description="查询知识库,且回答必须通过相关性评估。", )其行为在 test_eval_query_engine_tool.py 中通过 mock 评估器分别验证了“评估通过返回原始输出”与“评估失败返回失败模板”两条路径。这类工具适合对回答质量敏感、需要过滤幻觉式回复的场景。
八、典型应用:作为子问题路由与复合查询的基础单元
QueryEngineTool是多个高级查询引擎的构建单元,最典型的当属SubQuestionQueryEngine(sub_question_query_engine.py)。它的工作机制是:
- LLM 依据各工具元数据,把复杂查询拆解为多个子问题(question_gen 阶段);
- 每个子问题交给对应的
QueryEngineTool执行(self._query_engines以tool.metadata.name为键保存引擎映射); - 汇总所有子问题答案与来源节点,交给
response_synthesizer合成最终回答。
典型用法(sub_question_query_engine.py):
from llama_index.core.query_engine import SubQuestionQueryEngine from llama_index.core.tools import QueryEngineTool tool1 = QueryEngineTool.from_defaults( query_engine=engine_a, name="engine_a", description="回答关于文档A的问题" ) tool2 = QueryEngineTool.from_defaults( query_engine=engine_b, name="engine_b", description="回答关于文档B的问题" ) engine = SubQuestionQueryEngine.from_defaults( query_engine_tools=[tool1, tool2], use_async=True, ) response = engine.query("比较文档A与文档B中的方案差异")类似的组合还出现在RouterQueryEngine(router_query_engine.py)、SQL 向量联合查询等场景中,QueryEngineTool因而成为跨引擎编排的通用“接口单元”。上述所有工具类统一从llama_index.core.tools包导出(见 tools/init.py),使用时直接from llama_index.core.tools import QueryEngineTool即可。
九、与 LangChain 互操作:as_langchain_tool
QueryEngineTool提供as_langchain_tool()方法(query_engine.py),可将其转换为 LangChain 的LlamaIndexTool:
def as_langchain_tool(self) -> "LlamaIndexTool": from llama_index.core.langchain_helpers.agents.tools import ( IndexToolConfig, LlamaIndexTool, ) tool_config = IndexToolConfig( query_engine=self.query_engine, name=self.metadata.get_name(), description=self.metadata.description, ) return LlamaIndexTool.from_tool_config(tool_config=tool_config)转换过程中保留查询引擎、工具名称与描述,使你可以在 LangChain 的 Agent 体系中复用同一个 LlamaIndex 查询引擎。
十、设计要点回顾与最佳实践
结合源码与测试,总结QueryEngineTool的使用要点:
- 名称与描述是工具的灵魂:LLM 依赖
description判断工具适用场景,务必写清“查询什么数据、何时使用”,名称保持唯一且仅含字母、数字、下划线、连字符; - 默认签名为单一
input字符串:无需自定义fn_schema即可被函数调用型 Agent 正确调用;若要扩展参数,可自定义fn_schema(BaseModel子类)传入ToolMetadata; return_direct控制是否结束推理:对“查询即答案”的简单检索,置为True可减少一次多余的 LLM 生成;resolve_input_errors决定容错策略:默认开启可避免参数不匹配导致工具崩溃,代价是查询串可能包含额外噪音;追求严格性时关闭并依赖ValueError快速失败;- 同步/异步一致调用:Agent 异步执行时走
acall→aquery,同步时走call→query,两条路径输出结构完全一致; - 质量敏感场景叠加评估:
EvalQueryEngineTool可在回答进入 Agent 上下文前完成相关性过滤,配合failed_tool_output_template自定义失败反馈。
十一、进一步阅读
- 工具基类与元数据定义:tools/types.py
- 查询引擎工具核心实现:tools/query_engine.py
- 带评估的查询引擎工具:tools/eval_query_engine.py
- 单元测试(输入解析与容错):tests/tools/test_query_engine_tool.py
- 单元测试(评估闸门):tests/tools/test_eval_query_engine_tool.py
- 子问题查询引擎应用示例:query_engine/sub_question_query_engine.py
- 工具包统一导出入口:tools/init.py
【免费下载链接】llama_indexLlamaIndex is the document processing platform for AI项目地址: https://gitcode.com/GitHub_Trending/ll/llama_index
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考