多重检索RAG:LangChain与Llama-Index实战对比

2026-06-22阅读 0热度 0
ai 人工智能

在信息检索领域,查询扩展技术正在大幅提升搜索效率。这套机制的具体工作原理是什么?LangChain与Llama-Index在实际项目中又如何应用?本文将围绕多查询检索这一核心技术,详细剖析它在两个主流框架中的实现差异与适用场景。

使用LangChain和Llama-Index实现多重检索RAG

先给出几个关键判断:当用户查询过于宽泛或语义模糊时,传统检索极易出现信息偏离。多查询检索通过从不同角度生成多个问题,能显著提升召回率。其本质是对“语义鸿沟”问题的巧妙弥补——用多个查询覆盖单一查询无法表达的信息需求。

1 查询扩展

查询扩展是一种信息检索技术:在原始查询基础上叠加同义词或语义近似的术语,从而优化搜索结果。这种方法能丰富查询的语义密度,提升检索系统的准确率与相关性。

在多查询检索中,查询扩展是核心策略。它自动生成多个相关联的查询请求,拓宽搜索边界,帮助用户更全面地获取目标信息。特别适用于复杂查询场景——例如搜索“AI在医疗领域的应用”,系统会自动衍生出“机器学习诊断”、“深度学习影像识别”等变体,显著降低遗漏风险。

2 机制

系统接收到查询请求后,不会直接检索数据库,而是先借助高级语言模型“推理”出一个与原查询语义相似的新查询。该新查询随后用于在Llama-Index中检索相关文档,从而获取与原查询高度匹配的信息。整个过程增强了上下文感知能力,确保结果更精准、更贴合用户的真实意图。

2次LLM交互:

为精确生成查询,流程会向大型语言模型(LLM)并行发出两次请求:初次使用gpt-3模型,随后升级至gpt-4或其他高级模型,以获取更丰富的查询变体。这种分层调用本质上遵循“先粗筛、再精调”的策略。

3 实现方法

3.1 LangChain

loader = UnstructuredPDFLoader(FILE_NAME)
docs = loader.load()

text_splitter = SentenceTransformersTokenTextSplitter()
texts = text_splitter.split_documents(docs)

emb = OpenAIEmbeddings(openai_api_key=openai.api_key)
vec_db = Chroma.from_documents(documents=texts, embedding=emb)

lc_model = ChatOpenAI(openai_api_key=openai.api_key, temperature=1.5)
base_retriever = vec_db.as_retriever(k=K)
final_retriever = MultiQueryRetriever.from_llm(base_retriever, lc_model)

tmpl = """
You are an assistant to answer a question from user with a context.

Context:
{context}

Question:
{question}

The response should be presented as a list of key points, after creating the title of the content,
    formatted in HTML with appropriate markup for clarity and organization.
"""
prompt = ChatPromptTemplate.from_template(tmpl)
chain = {"question": RunnablePassthrough(), "context": final_retriever} 
        | prompt 
        | lc_model 
        | StrOutputParser() 

result = chain.invoke("Waht is the doc talking about?")

LangChain的实现非常简洁,这主要得益于MultiQueryRetriever类对底层逻辑的高度封装。其核心机制:配备一个基础检索器,自动生成最多三个定制化的查询变体。整个过程安全、开箱即用,开发者只需调用即可。

3.2 Llama-Index

Llama-Index的实现则相对“硬核”——要求开发者手动创建生成的查询,并自行实现多查询的检索流程。面对多个查询的并发需求,这里采用协程机制来保证异步执行效率。


vector_index: BaseIndex = VectorStoreIndex.from_documents(
    docs,
    service_context=service_context, 
    show_progress=True,
)

base_retriever = vector_index.as_retriever(similarity_top_k=K)

class MultiQueriesRetriever(BaseRetriever):
    def __init__(self, base_retriever: BaseRetriever, model:OpenAI):
        self.template = PromptTemplate("""You are an AI language model assistant. Your task is to generate Five
    different versions of the given user question to retrieve relevant documents from a vector
    database. By generating multiple perspectives on the user question, your goal is to
    help the user overcome some of the limitations of the distance-based similarity search.
    Provide these alternative questions seperated by newlines.
    Original question: {question}""")
        self._retrievers = [base_retriever]
        self.base_retriever = base_retriever
        self.model = model
    
    def gen_queries(self, query) -> List[str]:
        gen_queries_model = OpenAI(model="gpt-3-turbo", temperature=1.5)
        prompt = self.template.format(question=query)
        res = self.model.complete(prompt)
        return res.text.split("n")

    async def run_gen_queries(self,generated_queries: List[str]) -> List[NodeWithScore]:
        tasks = list(map(lambda q: self.base_retriever.aretrieve(q), generated_queries)) 
        res = await tqdm.gather(*tasks)
        return res[0]

    def _retrieve(self, query_bundle: QueryBundle) -> List[NodeWithScore]:
        return list()

    async def _aretrieve(self, query_bundle: QueryBundle) -> List[NodeWithScore]:
        query = query_bundle.query_str
        generated_queries = self.gen_queries(query)
        query_res = await self.run_gen_queries(generated_queries)
        return query_res

    
mr = MultiQueriesRetriever(base_retriever, li_model)

final_res = await RetrieverQueryEngine(mr).aquery(query_text)

关键步骤是继承BaseRetriever类,将其功能与基础检索器融合,实现根据生成查询检索对应信息。由于这些生成查询通过协程实现,因此需要重写_aretrieve方法。这段代码的核心流程:先生成多个问题,再并行检索,最后合并结果。如果你熟悉Python异步编程,这种模式用起来会非常顺手。

3.3 子问题查询引擎

Llama-Index还提供了SubQuestionQueryEngine类,专门应对复杂查询场景。有趣的是,它不采用“生成相似查询”路线,而是将复杂查询拆解为多个子问题。具体用法如下:

query_engine_tools = [
    QueryEngineTool(
        query_engine=vector_query_engine,
        metadata=ToolMetadata(
            name="pg_essay",
            description="Paul Graham essay on What I Worked On",
        ),
    ),
]

query_engine = SubQuestionQueryEngine.from_defaults(
    query_engine_tools=query_engine_tools,
    use_async=True,
)

response = query_engine.query(
    "How was Paul Grahams life different before, during, and after YC?"
)

SubQuestionQueryEngine的工作原理是将原始复杂查询拆分为多个子问题,每个子问题对应特定的数据源。这些子问题的答案不仅提供必要的上下文信息,还共同构成最终答案的基石。每个子问题专门设计用于从相应数据源中抽取关键信息,综合这些答案即可得出对原始查询的完整回应。

此外,SubQuestionQueryEngine能将复杂查询细化为多个子问题,并为每个子问题指定对应的查询引擎进行处理。当所有子问题得到解答后,答案将被汇总并传递给响应合成器,生成最终输出。在这一过程中,引擎会根据SubQuestion中的tool_name属性,决定使用哪个QueryEngineTool来处理每个子问题。

免责声明

本网站新闻资讯均来自公开渠道,力求准确但不保证绝对无误,内容观点仅代表作者本人,与本站无关。若涉及侵权,请联系我们处理。本站保留对声明的修改权,最终解释权归本站所有。

相关阅读

更多
欢迎回来 登录或注册后,可保存提示词和历史记录
登录后可同步收藏、历史记录和常用模板
注册即表示同意服务条款与隐私政策