在构建智能问答系统时,我们常常需要将大模型、检索器、提示词模板等多个模块高效串联。LlamaIndex 的查询管道(QueryPipeline)正是为此而生的强大工具,它以声明式 API 让模块编排变得简洁灵活,无论是简单的顺序执行链还是复杂的有向无环图(DAG),都能轻松驾驭。今天我们就来深入解析这个核心功能,看看如何通过它构建高效的多模态处理流程。
查询管道的设计理念是 **“组件即节点,连接即逻辑”**。我们可以将大模型、提示词模板、检索器、合成器等功能模块视为图中的节点,通过定义节点间的连接关系(边)来编排工作流。这种方式既保留了代码的可读性,又支持复杂逻辑的可视化构建。
适合处理单路径的线性任务,例如 “提示词生成→大模型推理→结果输出”:
python
from llama_index import PromptTemplate, OpenAI, QueryPipeline
prompt_str = "请为产品设计一句简单的宣传语,我的产品是{product_name}"
prompt_tmpl = PromptTemplate(prompt_str)
llm = OpenAI()
p = QueryPipeline(chain=[prompt_tmpl, llm], verbose=True)
这里通过chain
参数直接定义顺序,前一个组件的输出会作为后一个组件的输入,适合快速验证基础功能。
当需要并行处理或多分支逻辑时,DAG 模式更为灵活。以向量检索 RAG 为例,我们需要将用户输入先经过提示词优化,再同时驱动检索器和合成器:
python
from llama_index import InputComponent, Ollama, get_response_synthesizer
# 准备组件
input = InputComponent()
llm = Ollama(model="qwen:14b")
prompt_tmpl = PromptTemplate("对问题进行完善,输出新的问题:{query_str}")
retriever = index.as_retriever(similarity_top_k=3)
summarizer = get_response_synthesizer(response_mode="tree_summarize")
# 构建管道并连接节点
p = QueryPipeline(verbose=True)
p.add_modules({
"input": input, "prompt": prompt_tmpl,
"llm": llm, "retriever": retriever, "summarizer": summarizer
})
p.add_link("input", "prompt") # 输入传递给提示词模板
p.add_link("prompt", "llm") # 提示词输出给大模型
p.add_link("llm", "retriever") # 大模型输出驱动检索
p.add_link("llm", "summarizer", dest_key="query_str") # 大模型输出作为合成器问题
p.add_link("retriever", "summarizer", dest_key="nodes") # 检索结果作为合成器输入
这里llm
的输出同时流向retriever
和summarizer
,形成分支逻辑,体现了 DAG 的灵活性。
当不同组件的输入输出参数名不匹配时,dest_key
用于指定目标组件接收数据的参数名。例如:
nodes
参数:p.add_link("retriever", "summarizer", dest_key="nodes")
query_str
:p.add_link("llm", "summarizer", dest_key="query_str")
这种设计避免了硬编码,让组件可以像 “积木” 一样自由组合,即使更换底层实现(如换用不同合成器),只需调整dest_key
即可保持连接逻辑不变。
查询管道在底层维护一个 DAG,每个节点对应一个QueryComponent
,边对应数据流向。运行时会自动确定入口节点(无输入依赖的节点),然后按图顺序调用每个节点的run_component
接口,确保数据按定义的路径流动。
所有插入管道的组件(如PromptTemplate
、Ollama
)都会通过as_query_component
方法转换为QueryComponent
类型,实现统一的接口:
_input_keys
:定义组件运行所需的输入参数名(如response
、query_str
)_output_keys
:定义组件运行后的输出参数名(如output
、nodes
)_run_component
:具体的逻辑实现,接收_input_keys
参数,返回_output_keys
数据这种抽象让不同类型的组件(提示词模板、大模型、检索器)可以在同一套机制下运行。
InputComponent
)作为流程起点。run_component
生成输出,根据边的定义将输出传递给下游节点,直至所有无下游依赖的节点执行完毕。当需要复杂逻辑时,我们可以继承CustomQueryComponent
,实现三个核心接口:
python
from llama_index.core.query_pipeline import CustomQueryComponent
from pydantic import BaseModel
class MyOutputParser(CustomQueryComponent):
@property
def _input_keys(self) -> set:
return {'response'} # 定义需要的输入参数名
@property
def _output_keys(self) -> set:
return {'output'} # 定义生成的输出参数名
def _run_component(self, **kwargs):
# 自定义逻辑:将响应解析为结构化对象
class Phone(BaseModel): name: str; cpu: str
output = Phone(name="iPhone 15", cpu="A16")
return {'output': output.dict()} # 必须包含_output_keys定义的键
对于简单函数逻辑,可直接使用FnComponent
包装,无需显式定义输入输出键:
python
from llama_index.core.query_pipeline import FnComponent
# 定义处理函数,输入参数名需与上游组件输出键匹配
def add_extra_info(phone: dict):
return f"{phone['name']} - CPU: {phone['cpu']} (额外信息)"
# 封装为组件,指定输出键
post_processor = FnComponent(fn=add_extra_info, output_key="final_output")
p.add_modules({"post_processor": post_processor})
p.add_link("MyOutputParser", "post_processor", dest_key="phone")
为组件取有意义的名称(如retriever
、summarizer
),方便后续调试和维护。当管道复杂时,可通过p.get_modules()
查看所有节点状态。
在自定义组件的_run_component
中添加异常捕获,避免单个节点故障导致整个管道崩溃:
python
def _run_component(self, **kwargs):
try:
# 核心逻辑
except Exception as e:
raise ValueError(f"组件运行失败: {str(e)}") from e
verbose=True
打印各节点耗时,定位瓶颈。QueryComponent
实现)。LlamaIndex 的查询管道为多模态数据处理提供了优雅的解决方案,通过模块化编排和灵活的图结构,我们可以轻松组合不同功能模块,实现从简单检索到复杂 RAG 系统的构建。无论是直接使用内置组件,还是通过CustomQueryComponent
/FnComponent
扩展自定义逻辑,查询管道都展现了强大的适应性和可扩展性。
如果你在开发智能问答系统、文档检索工具时遇到模块整合难题,不妨试试查询管道的声明式编排方式。觉得本文有帮助的话,欢迎点赞收藏,关注我获取更多 LlamaIndex 进阶技巧!