掘金 阅读 ( ) • 2024-05-14 09:00

构建RAG驱动的应用程序中的路由

根据用户查询的意图在 RAG 应用程序内路由控制流可以帮助我们创建更有用、更强大的基于检索增强生成 (RAG) 的应用程序。

我们希望用户能够交互的数据很可能来自各种来源,例如报告、文档、图像、数据库和第三方系统。对于基于业务的 RAG 应用程序,我们可能希望使用户能够与来自业务中一系列领域的信息进行交互,例如来自销售、订购和会计系统的信息。

由于数据源的多样性,信息存储的方式以及我们想要与之交互的方式也可能是多种多样的。有些数据可能存储在向量存储中,有些数据存储在 SQL 数据库中,有些数据可能需要通过 API 调用进行访问,因为它位于第三方系统中。

img

RAG系统根据查询意图路由到不同的数据源

对于相同但不同的数据,也可以有不同的向量存储设置,并针对不同的查询类型进行优化。例如,可以设置一个向量存储用于回答摘要类型问题,而另一个向量存储用于回答特定的定向类型问题。

根据问题,我们可能还想路由到不同的组件类型。例如,我们可能希望将查询传递给 AgentVectorStore,或者直接传递给 LLM 进行处理,所有这些都基于问题的性质

img

根据用户的查询路由到不同的组件类型

我们甚至可能想根据所提出的问题自定义提示模板。

img

根据用户查询通过不同的提示模板进行路由

总而言之,我们希望通过应用程序更改和引导用户查询流程的原因有很多。我们的应用程序尝试满足的用例越多,我们就越有可能在整个应用程序中提出路由要求。

路由器本质上只是 If/Else 语句,我们可以用它来指导查询的控制流。

但有趣的是,他们需要根据自然语言输入做出决定。因此,我们正在寻找基于自然语言描述的离散输出。

由于许多路由逻辑都是基于使用 LLM 或机器学习算法,而这些算法本质上是不确定的,因此我们不能保证路由器始终 100% 做出正确的选择。

除此之外,我们不太可能预测进入路由器的所有不同查询变体。然而,通过使用最佳实践和一些测试,我们应该能够使用路由器来帮助创建更强大的 RAG 应用程序。

自然语言路由器

我们将在这里探索一些自然语言路由器,它们是由一些不同的 RAGLLM 框架和库实现的。

  • LLM 完成路由器
  • LLM 函数调用路由器
  • 语义路由器
  • 零样本分类路由器
  • 语言分类路由器

下图给出了这些路由器的描述,以及可以找到它们的框架/包。

该图还包括逻辑路由器,我将其定义为基于离散逻辑工作的路由器,例如针对字符串长度、文件名、整数值等的条件。换句话说,它们不是基于必须理解自然语言的意图询问

img

不同类型的自然语言路由器

让我们更详细地探讨一下这些路由器

LLM 路由器

这些利用LLM 的决策能力来根据用户的查询选择路线。

LLM 完成路由器

这些使用 LLM 完成调用,要求 LLM 从您传递到其提示的单词选项列表中返回最能描述查询的单个单词。然后可以将该字用作 If/Else 条件的一部分来控制应用程序流程。

这就是LlamaIndexLLM 选择器路由器的工作原理。这也是LangChain文档中给出的路由器示例。

让我们看一个基于 LangChain 文档中提供的代码示例,以使这一点更加清晰。正如所看到的,在 LangChain 中自己编写其中一个代码非常简单。

 from langchain_anthropic import ChatAnthropic 
 from langchain_core.output_parsers import StrOutputParser 
 from langchain_core.prompts import PromptTemplate 
 ​
 # 设置 LLM 链根据查询返回单个单词,
 # 并根据我们在提示模板中提供给它的单词列表
 llm_completion_select_route_chain = ( 
         PromptTemplate.from_template( """
 给定下面的用户问题,将其分类为
 有关 `LangChain`、`Anthropic` 或 `Other` 的问题。
 ​
 请勿使用多个单词进行回答。
 ​
 <question> 
 {question} 
 </question> 
 ​
 Classification:"""
                                       ) 
         | ChatAnthropic(model_name= "claude-3-haiku" ) 
         | StrOutputParser() 
 ) 
 ​
 ​
 # 我们设置一个 IF/Else 条件将查询路由到正确的链
 # 基于上面的 LLM 完成调用
 def  Route_to_chain ( Route_name ): 
     if  "anthropic" == Route_name.lower(): 
         return anthropic_chain 
     elif  "langchain" == Route_name.lower(): 
         return langchain_chain 
     else : 
         return generic_chain 
 ​
 ... 
 ​
 # 稍后在应用程序中,我们可以使用LLM完成链的响应
 # 来控制(即路由)应用程序流到
 #正确的链,通过我们创建的route_to_chain方法
 route_name = llm_completion_select_route_chain.invoke(user_query) 
 chain = route_to_chain(route_name) 
 chain.invoke(user_query )

LLM 函数调用路由器

这利用了 LLM 的函数调用能力来选择遍历路径。不同的路由被设置为在 LLM 函数调用中具有适当描述的函数。然后,根据传递给LLM的查询,它能够返回正确的函数(即路线),供我们使用。

这就是Pydantic Router在 LlamaIndex 中的工作原理。这也是大多数特工选择要使用的正确工具的工作方式。他们利用法学硕士的函数调用能力,根据用户的查询为工作选择正确的工具。

语义路由器

这种路由器类型利用嵌入和相似性搜索来选择最佳的遍历路线。

每条路线都有一组与其关联的示例查询,这些查询被嵌入并存储为向量。传入的查询也会被嵌入,并且针对来自路由器的其他示例查询进行相似性搜索。属于具有最接近匹配的查询的路线将被选择。

事实上,有一个名为“semantic-router”的 python 包就可以做到这一点。让我们看一下一些实现细节,以更好地了解整个事情是如何工作的。这些示例直接来自该库的 GitHub 页面。

让我们设置两条路线,一条针对有关政治的问题,另一条针对一般闲聊类型的问题。对于每条路线,我们都会分配一系列通常可能会被问到的问题,以触发该路线。这些示例查询称为话语。这些话语将被嵌入,以便我们可以使用它们对用户的查询进行相似性搜索。

 from Semantic_router import Route 
 ​
 # 我们可以用它作为我们的聊天机器人的指南,以避免政治对话 #
 politics = Route( 
     name= "politics" , 
     utterances=[ 
         "isn't politics the best thing ever",
         "why don't you tell me about your political opinions",
         "don't you just love the president",
         "they're going to destroy this country!",
         "they will save the country!",
     ],
 )
 ​
 # 这可以用作指示我们的聊天机器人切换到更多对话提示
 chichat = Route( 
     name= "chitchat" , 
     utterances=[ 
         "how's the weather today?",
         "how are things going?",
         "lovely weather today",
         "the weather is horrendous",
         "let's go to the chippy",
     ], 
 ) 
 ​
 # 我们将我们的两个决定放在一起到单个列表中
 routes = [politics, chitchat]

我们指定 OpenAI 作为编码器,尽管任何嵌入库都可以工作。接下来我们使用路由器和编码器创建路由层。

 embedding= OpenAIEncoder()
 ​
 from semantic_router.layer import RouteLayerroute_layer 
 ​
 route_layer = RouteLayer(encoder=encoder, routes=routes)

然后,当对路由器层应用我们的查询时,它返回应该用于查询的路由

 route_layer("don't you love politics?").name
 # -> 'politics'

因此,再次总结一下,该语义路由器利用嵌入和相似性搜索,使用用户的查询来选择最佳遍历路线。这种路由器类型也应该比其他基于 LLM 的路由器更快,因为它只需要处理单个索引查询,这与需要调用 LLM 的其他类型相反。

零样本分类路由器

“零样本文本分类是自然语言处理中的一项任务,其中模型在一组标记示例上进行训练,然后能够对以前未见过的类中的新示例进行分类”。这些路由器利用零样本分类模型,从传入路由器的一组预定义标签中为一段文本分配标签。

语言分类路由器

这种类型的路由器能够识别查询所使用的语言,并据此路由查询。如果您的应用程序需要某种多语言解析能力,那么这很有用。

关键字路由器

LlamaIndex 联合创始人 Jerry Liu 撰写的这篇关于 RAG 应用程序内部路由的文章建议使用关键字路由器,该路由器将尝试通过在查询和路由列表之间匹配关键字来选择路由。

该关键字路由器可以由 LLM 提供支持,也可以识别关键字,或者由一些其他关键字匹配库提供支持。

逻辑路由器

它们使用针对变量(例如字符串长度、文件名和值比较)的逻辑检查来处理如何路由查询。它们与编程中使用的典型 If/Else 条件非常相似。

换句话说,它们不是基于必须理解自然语言查询的意图,而是可以根据现有的离散变量做出选择。

示例: HayStack 中的ConditionalRouterFileTypeRouter

代理与路由器

乍一看,路由器和代理之间确实有很多相似之处,可能很难区分它们的不同之处。

存在相似之处是因为代理实际上将路由作为其流程的一部分执行。他们使用路由机制来选择用于工作的正确工具。他们经常利用函数调用来选择正确的工具,就像上面描述的LLM 函数调用路由器一样。

不过,路由器是比代理简单得多的组件,通常具有将任务路由到正确位置的“简单”工作,而不是执行与该任务相关的任何逻辑或处理。

另一方面,代理通常负责处理逻辑,包括管理他们有权访问的工具完成的工作。

结论

我们在这里介绍了目前在不同的 RAGLLM 框架和包中发现的一些不同的自然语言路由器。

随着时间的推移,围绕路由的概念、包和库肯定会增加。在构建 RAG 应用程序时,您会发现在某些时候,为了构建对用户有用的应用程序,路由功能确实变得必要。

路由器是这些基本构建块,允许您将应用程序的自然语言请求路由到正确的位置,以便尽可能最好地满足用户的查询。

相关阅读