为什么 RAG 应用需要路由器
根据用户查询的意图在 RAG 应用程序内路由控制流可以帮助我们创建更有用、更强大的基于检索增强生成 (RAG) 的应用程序。
我们希望用户能够与之交互的数据很可能来自各种来源,例如来自报告、文档、图像、数据库和第三方系统。对于基于业务的 RAG 应用程序,我们可能希望使用户能够与来自业务中一系列领域的信息进行交互,例如来自销售、订购和会计系统的信息。
由于数据源的多样性,信息的存储方式以及我们希望与之交互的方式也可能是多种多样的。有些数据可能存储在向量存储中,有些存储在 SQL 数据库中,有些我们可能需要通过 API 调用访问,因为它位于第三方系统中。
基于查询意图的 RAG 系统路由到不同的数据源
对于相同的数据,也可以设置不同的向量存储,并针对不同的查询类型进行优化。例如,可以设置一个向量存储来回答摘要类型问题,另一个用于回答特定的定向类型问题。
根据问题的不同,我们可能还想将查询路由到不同的组件类型。例如,我们可能想把查询传递给代理、矢量存储,或者直接传递给 LLM 进行处理,所有这些都是根据问题的性质来决定的。
根据用户的查询路由到不同的组件类型
我们甚至可以根据所提问题定制提示模板。
根据用户查询通过不同的提示模板进行路由
总之,我们有很多理由想要改变和引导用户在应用程序中的查询流。我们的应用程序要满足的用例越多,我们就越有可能在整个应用程序中提出路由要求。
路由器本质上只是我们可以用来指导查询控制流的 If/Else 语句。
但有趣的是,它们需要根据自然语言输入做出决定。因此,我们正在寻找一种基于自然语言描述的离散输出。
由于很多路由逻辑都是基于使用 LLM 或机器学习算法(本质上是非确定性的),因此我们无法保证路由器总是 100% 做出正确的选择。此外,我们也不可能预测进入路由器的所有不同查询变化。不过,利用最佳实践和一些测试,我们应该能够使用路由器来帮助创建更强大的 RAG 应用程序。
Natural Language Routers
下面我们将探讨我发现的一些自然语言路由器,这些路由器是由一些不同的 RAG 和LLM框架和库实现的。
LLM Completion Routers(LLM 补全路由器)
LLM Function Calling Routers(LLM 函数调用路由器)
Semantic Routers
Zero Shot Classification Routers(零样本分类路由器)
Language Classification Routers(语言分类路由器)
下图给出了这些路由器的描述,该图还包括逻辑路由器,我将其定义为基于离散逻辑工作的路由器,例如针对字符串长度、文件名、整数值等的条件。
不同种类的自然语言路由器LLM Completion Router
它们使用 LLM 完成调用,要求 LLM 从你传递到其提示的单词选项列表中返回最能描述查询的单个单词。然后可以将该字用作 If/Else 条件的一部分来控制应用程序流程。
这就是 LlamaIndex 中 LLM Selector Router[1] 和 LangChain 文档中介绍的动态路由[2] 的工作原理。
让我们看一个基于 LangChain 文档中提供的代码示例,以便更清楚地理解这一点。
from langchain_anthropic import ChatAnthropicfrom langchain_core.output_parsers import StrOutputParserfrom langchain_core.prompts import PromptTemplate# Set up the LLM Chain to return a single word based on the query,# and based on a list of words we provide to it in the prompt templatellm_completion_select_route_chain = ( PromptTemplate.from_template("""Given the user question below, classify it as eitherbeing about `LangChain`, `Anthropic`, or `Other`.Do not respond with more than one word.<question>{question}</question>Classification:""" ) | ChatAnthropic(model_name="claude-3-haiku") | StrOutputParser())# We setup an IF/Else condition to route the query to the correct chain # based on the LLM completion call abovedef route_to_chain(route_name): if "anthropic" == route_name.lower(): return anthropic_chain elif "langchain" == route_name.lower(): return langchain_chain else: return general_chain...# Later on in the application, we can use the response from the LLM# completion chain to control (i.e route) the flow of the application # to the correct chain via the route_to_chain method we createdroute_name = llm_completion_select_route_chain.invoke(user_query)chain = route_to_chain(route_name)chain.invoke(user_query)
LLM Function Calling Router
它利用了 LLM 的函数调用能力来选择遍历路径。不同的路由被设置为在 LLM 函数调用中具有适当描述的函数。然后,根据传递给 LLM 的查询,它能够返回正确的函数(即路线),供我们使用。
这就是 [Pydantic Router]((https://docs.llamaindex.ai/en/stable/module_guides/querying/router/ "Pydantic Router") 在 LlamaIndex 中的工作方式。这也是大多数代理选择正确工具的工作方式。它们利用 LLM 的函数调用能力,根据用户的查询为工作选择正确的工具。
Semantic Router
此路由器类型利用嵌入和相似性搜索来选择要遍历的最佳路由。
每个路由都有一组与之关联的示例查询,这些查询被嵌入并存储为向量。传入的查询也会被嵌入,并针对来自路由器的其他示例查询进行相似性搜索。将选择属于具有最接近匹配项的查询的路由。
事实上,有一个名为 semantic-router[3] 的 python 包可以做到这一点。让我们看一些实现细节,以更好地了解整个事情是如何工作的。这些示例直接来自该库的 GitHub 页面。
让我们设置两条路线,对于每条路线,我们都会分配一系列通常可能会被问到的问题,以触发该路线。这些示例查询被称为话语。这些话语将被嵌入,以便我们可以使用它们对用户的查询进行相似性搜索。
from semantic_router import Route# we could use this as a guide for our chatbot to avoid political# conversationspolitics = 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!", ],)# this could be used as an indicator to our chatbot to switch to a more# conversational promptchitchat = 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", ],)# we place both of our decisions together into single listroutes = [politics, chitchat]
我们指定 OpenAI 作为编码器,不过任何嵌入库都可以使用。接下来,我们使用路由器和编码器创建路由层。
encoder = OpenAIEncoder()from semantic_router.layer import RouteLayerroute_layer = RouteLayer(encoder=encoder, routes=routes)
然后,在路由器层应用我们的查询时,它会返回查询应使用的路由。
route_layer("don't you love politics?").name# -> 'politics'
因此,再次总结一下,这个语义路由器利用嵌入和相似性搜索,使用用户的查询来选择要遍历的最佳路由。这种路由器类型也应该比其他LLM基于 的路由器更快,因为它只需要处理一个索引查询,而其他类型则需要调用 LLM。
Zero Shot Classification Router
零样本文本分类是自然语言处理中的一项任务,在这项任务中,一个模型是在一组有标签的示例上训练出来的,但随后又能从以前未见过的类别中对新的示例进行分类。这些路由器利用 “零样本分类” 模型,从您传入路由器的一组预定义标签中为一段文本分配一个标签。
示例:Haystack 中的 ZeroShotTextRouter[4],它利用了 Hugging Face 中的 Zero Shot 分类模型。查看相关的源代码,可以访问这个链接[5]。
Language Classification Router
这种路由器能够识别查询所使用的语言,并据此路由查询。如果您的应用程序需要某种多语言解析能力,这种路由器将非常有用。
示例:Haystack 中的 TextClassificationRouter[6]。它利用 langdetect[7] python 库来检测文本的语言,而文本本身使用朴素贝叶斯算法来检测语言。
Keyword Router
这篇[8]来自 LlamaIndex 联合创始人 Jerry Liu 的文章介绍了 RAG 应用程序内部的路由,他提出了一个关键字路由器,该路由器将尝试通过匹配查询和路由列表之间的关键字来选择路由。
这个关键字路由器可以由一个LLM也可以识别关键字,或者由其他一些关键字匹配库提供支持
Logical Routers
它们使用对变量(如字符串长度、文件名和值比较)的逻辑检查来处理路由查询。它们与编程中使用的典型 If/Else 条件非常相似。
换句话说,它们不是基于必须理解自然语言查询的意图,而是可以基于现有和离散变量做出选择。
示例:HayStack 中的 ConditionalRouter[9] 和 FileTypeRouter[10]。
Agents vs Routers
路由器和代理之间确实有很多相似之处,可能很难区分它们的不同之处。
之所以存在相似之处,是因为代理在其工作流程中确实执行了路由选择。它们使用路由机制来为工作选择正确的工具。它们通常利用函数调用来选择正确的工具,就像上文描述的 LLM 函数调用路由器一样。
不过,路由器是比代理简单得多的组件,通常具有将任务路由到正确位置的“简单”工作,而不是执行与该任务相关的任何逻辑或处理。
另一方面,代理通常负责处理逻辑,包括管理他们可以使用的工具所完成的工作。
总结
我们在此介绍了目前在不同的 RAG 和 LLM 框架和软件包中发现的几种不同的自然语言路由器。
随着时间的推移,围绕路由的概念、软件包和库肯定会越来越多。在构建 RAG 应用程序时,你会发现在某些时候,为了构建一个对用户有用的应用程序,路由功能确实是必要的。
路由器是这些基本构建块,允许您将对应用程序的自然语言请求路由到正确的位置,以便尽可能最好地满足用户的查询。
往期文章
当 AI 遇上爬虫:让数据提取变得前所未有地简单!
告别传统客服:支持十几个平台的 AI “懒人客服” 来了!
卖货主播大模型开源了,让销冠触手可及!
Kimi+麦肯锡,5 秒摸透一个行业!
Kimi 10 秒生成流程图,别再手动绘图了!
万字长文秒变精华!Kimi 的超强提示词秘籍
阿里开源的自动化视频剪辑工具,太好用了!
开源,11K Star!一个主题或关键词就能自动生成一条高清的短视频,无侵权风险!
参考资料
[1]
LLM Selector Router: https://docs.llamaindex.ai/en/stable/module_guides/querying/router/[2]
动态路由: https://python.langchain.com/v0.1/docs/expression_language/how_to/routing/[3]
semantic-router: https://github.com/aurelio-labs/semantic-router[4]
ZeroShotTextRouter: https://docs.haystack.deepset.ai/reference/routers-api#module-zero_shot_text_router[5]
链接: https://github.com/deepset-ai/haystack/blob/main/haystack/components/routers/zero_shot_text_router.py#L130[6]
TextClassificationRouter: https://docs.haystack.deepset.ai/reference/routers-api#module-text_language_router[7]
langdetect: https://github.com/deepset-ai/haystack/blob/main/haystack/components/routers/text_language_router.py#L90[8]
这篇: https://betterprogramming.pub/unifying-llm-powered-qa-techniques-with-routing-abstractions-438e2499a0d0[9]
ConditionalRouter: https://docs.haystack.deepset.ai/docs/conditionalrouter[10]
FileTypeRouter: https://docs.haystack.deepset.ai/docs/filetyperouter