引言
在构建复杂的 AI 应用时,我们常常需要根据用户查询的主题或意图,将请求分发到不同的处理模块。例如,一个客服机器人可能需要将技术问题转给技术专家、将账单问题转给财务系统、将投诉转给投诉处理部门。传统做法是使用关键词匹配或规则分类,但这些方法维护困难、泛化能力弱。
语义路由(Semantic Routing)提供了一种更智能的方式:它将每个可能的目标(称为“路由”,Route)用一组代表性的参考语句(references)来刻画,当用户查询到来时,系统计算查询与所有参考语句的语义相似度,并选择最匹配的一个或多个路由进行分发。
RedisVL 的SemanticRouter类基于 Redis 的向量搜索能力,实现了高效、可扩展的语义路由。它将每个参考语句转换为向量并存储在 Redis 中,查询时通过 KNN 搜索找到最相似的参考,再聚合到路由层面做出决策。
本指南将带你从零开始,掌握如何定义路由、初始化路由器、执行查询路由、动态管理路由引用,以及序列化配置。
前置条件
- 已安装 RedisVL:
pip install redisvl - 一个运行中的 Redis 实例(推荐 Redis 8+ 或 Redis Cloud)
概要
- 定义包含参考语句和距离阈值的路由
- 初始化和配置
SemanticRouter - 将查询路由到单个或多个匹配路由
- 序列化和恢复路由器配置(JSON / YAML)
- 动态添加、获取和删除路由引用
语义路由的工作原理
语义路由的核心是向量相似度搜索。其流程如下:
- 定义路由:每个路由有一个名称、一组参考语句(例如典型问题或关键词),以及一个距离阈值(决定匹配的宽松程度)。
- 向量化:所有参考语句通过嵌入模型(如
sentence-transformers/all-mpnet-base-v2)转换为高维向量,并存入 Redis 的向量索引中。 - 查询:当用户输入查询时,同样将其向量化,并在 Redis 中执行 KNN 搜索,找出距离最近的若干参考语句。
- 聚合与决策:根据找到的参考语句所属的路由,采用平均距离或最小距离等聚合方法,计算每个候选路由的最终得分(距离)。只有那些距离小于各自路由阈值的路由才会被采纳。
下图直观展示了这一流程:
这种方式的优势在于:
- 语义理解:不依赖关键词,能处理同义表达。
- 灵活阈值:每个路由可单独控制匹配严格程度。
- 多路由支持:一个查询可以同时匹配多个路由(例如“AI 在篮球中的应用”既匹配技术也匹配体育)。
定义路由
每个路由由Route对象定义,关键属性包括:
name:路由的唯一标识。references:字符串列表,代表该路由的语义覆盖范围。可以是问题、关键词或短语。metadata:可选字典,用于存储额外信息(如优先级、分类标签)。distance_threshold:余弦距离阈值(范围 0~2,越小越严格)。只有查询与路由中某个参考的距离小于该阈值时,才视为匹配。
下面定义三个路由:技术、体育、娱乐。
fromredisvl.extensions.routerimportRoute technology=Route(name="technology",references=["人工智能有哪些最新进展?","给我讲讲最新的科技产品","科技领域有什么新趋势?"],metadata={"category":"tech","priority":1},distance_threshold=0.71)sports=Route(name="sports",references=["昨晚谁赢了比赛?","告诉我即将到来的体育赛事","体育界最近有什么新闻?","体育","篮球和足球"],metadata={"category":"sports","priority":2},distance_threshold=0.72)entertainment=Route(name="entertainment",references=["现在最火的电影是什么?","谁获得了最佳男演员奖?","娱乐圈有什么新鲜事?"],metadata={"category":"entertainment","priority":3},distance_threshold=0.7)说明:距离阈值需要根据实际应用调整。较高的阈值(如 0.72)会匹配更多查询,但可能误召;较低的阈值(如 0.7)更严格,召回率降低。
初始化 SemanticRouter
SemanticRouter在初始化时会自动在 Redis 中创建索引(如果不存在)。你可以指定嵌入模型(默认使用HFTextVectorizer,模型为sentence-transformers/all-mpnet-base-v2)。
importosfromredisvl.extensions.routerimportSemanticRouterfromredisvl.utils.vectorizeimportHFTextVectorizer os.environ["TOKENIZERS_PARALLELISM"]="false"router=SemanticRouter(name="topic-router",# Redis 索引名称vectorizer=HFTextVectorizer(),# 嵌入模型routes=[technology,sports,entertainment],# 初始路由列表redis_url="redis://localhost:6379",overwrite=True# 如存在同名索引则覆盖)初始化后,可以查看索引信息:
rvl index info-itopic-router你会看到索引包含reference(文本)、vector(向量)、route_name(标签)等字段。num_docs等于所有参考语句的总数(此处为 11 条)。
简单路由查询
使用router(query)方法,返回最佳匹配的路由(RouteMatch对象,包含名称和距离)。若无匹配,则返回name=None。
# 匹配技术路由route_match=router("你能告诉我人工智能的最新进展吗?")print(route_match)# RouteMatch(name='technology', distance=0.419...)# 不匹配任何路由route_match=router("外星人真的存在吗?")print(route_match)# RouteMatch(name=None, distance=None)如果需要返回多个候选路由(按距离升序),使用route_many方法:
# 返回最多 3 个候选路由matches=router.route_many("AI 如何在篮球中应用?",max_k=3)formatchinmatches:print(match.name,match.distance)# 可能输出: technology 0.556, sports 0.671聚合方法(Aggregation Method)
当查询匹配到一个路由的多个参考时,需要将这些距离聚合为一个最终距离。SemanticRouter支持两种聚合方式:
avg(默认):计算该路由所有匹配参考的平均距离。min:只取最小的距离(即最接近的参考)。
选择min会让路由更容易被匹配(因为只要有一个参考足够接近即可),而avg则更稳健,能避免因个别噪音参考导致的误匹配。
你可以通过更新路由配置来切换聚合方法:
fromredisvl.extensions.routerimportRoutingConfigfromredisvl.extensions.router.schemaimportDistanceAggregationMethod router.update_routing_config(RoutingConfig(aggregation_method=DistanceAggregationMethod.min,max_k=3))matches=router.route_many("勒布朗·詹姆斯")print(matches)# 可能只匹配 sports序列化与恢复
你可以将路由器配置导出为字典或 YAML 文件,便于保存或跨环境迁移。
# 导出为字典config_dict=router.to_dict()print(config_dict.keys())# 包含 name, routes, vectorizer, routing_config# 从字典恢复router2=SemanticRouter.from_dict(config_dict,redis_url="redis://localhost:6379")# 导出为 YAML 文件router.to_yaml("router.yaml",overwrite=True)# 从 YAML 恢复router3=SemanticRouter.from_yaml("router.yaml",redis_url="redis://localhost:6379")恢复时,Redis 索引会自动重建(如果不存在)。
动态管理路由引用
在实际应用中,你可能需要动态添加或删除某个路由的参考语句,而不需要重建整个路由器。SemanticRouter提供了相关方法。
添加参考
added_ids=router.add_route_references(route_name="technology",references=["最新 AI 趋势","新科技产品"])print(added_ids)# 返回新增参考的 Redis key 列表获取参考
你可以按路由名称或参考 ID 获取所有参考语句。
# 按路由名称获取refs=router.get_route_references(route_name="technology")forrefinrefs:print(ref["reference"])# 按参考 ID 获取(示例取第一个)first_id=refs[0]["reference_id"]single_ref=router.get_route_references(reference_ids=[first_id])删除参考
可以按路由名称删除该路由下的所有参考,或按指定 ID 删除特定参考。
# 删除整个 sports 路由的参考deleted_count=router.delete_route_references(route_name="sports")print(f"删除了{deleted_count}条参考")# 删除特定参考deleted_count=router.delete_route_references(reference_ids=[first_id])print(f"删除了{deleted_count}条参考")清理资源
clear():清空索引中的所有参考数据,但保留索引结构。delete():删除整个索引(包括数据结构),彻底移除。
router.clear()# 清空数据router.delete()# 删除索引总结
- 原理:基于向量相似度搜索和聚合,实现灵活的语义路由。
- 定义路由:通过参考语句和距离阈值刻画每个路由的语义范围。
- 查询路由:支持单匹配和多匹配,可调节聚合方法(平均/最小)。
- 动态管理:可以随时增删改路由参考,无需重建索引。
- 可移植性:配置可序列化为字典或 YAML,方便版本控制和部署。
语义路由是构建智能对话系统、RAG 应用、多任务分发系统的核心组件。RedisVL 提供的高性能向量检索能力,让这一切变得简单而高效。