这道题问的是,当意图数量大到几百个、上下文又长的时候,怎么做到既准确又高效。本质上是在问,怎么把传统大模型那种一次塞进全部意图的笨办法,拆成一套能伸缩的工程架构。
我的核心思路是分层。先做意图聚类,把300到500个意图聚成十几个簇,每个簇里再分20到30个细意图。这样第一级路由只需要判断属于哪个簇,用个轻量模型比如TinyBERT就够了,候选空间一下子压到几十个。第二级再在簇内做细分类。你可以把一级路由理解成前台客服先问'您是要退货还是咨询政策',二级才是具体处理。
但光有分层不够,真正的召回不能靠全量Prompt。我会走多路召回。一路是向量召回,用领域微调过的 [[Embedding]] 模型,比如在客服退款数据上 [[SFT]] 过的 [[Bi-Encoder]],把用户Query和意图描述都转成向量,做 [[ANN]] 检索。另一路是关键词召回,给每个意图维护一个关键词倒排索引,像'退款''少发货'这种词直接命中。还有一路规则兜底,高频意图用正则硬匹配。三路结果合并后,用 [[Cross-Encoder]] 做精排,选Top-3到Top-5。这样实际送到大模型里的上下文,从原本几千token的意图列表,压缩到几百token的候选加几个 [[Few-shot]] 示例。
举个例子,在电商客服场景,用户说'我买的手机没收到,但物流显示签收'。向量召回会把'物流异常''签收未收到'之类的意图排前面,关键词召回靠'没收到''签收'命中,规则可能直接匹配'物流投诉'。精排后选最相关的两三个意图,再带上每个意图的历史相似Query做Few-shot,大模型就能准确判断。
上下文压缩还要做一件事。多轮对话里历史很长,我会用一个小模型先把用户当前Query做指代消解和补全,比如'它'指代上一轮的手机,再把历史轮次压缩成关键状态,比如'用户已确认收货',而不是把整段对话原文塞进去。说白了,就是只保留决策需要的信息。
这里有个坑。分层和压缩虽然省资源,但会引入误差。如果一级路由分错了簇,后面的召回全白费。所以上线我会特别关注一级路由的准确率,低于95%就说明聚类粒度不对,需要调簇内相似度阈值或者补一些难分样本。另外,向量召回依赖Embedding质量,如果意图描述写得太宽泛,比如'售后'这种,召回就会把退货和投诉混一起。我的做法是给每个意图写结构化描述,包含意图名、核心槽位、典型说法,甚至一两个反例,这样Embedding能学到边界。
还有一个延伸点,就是当意图数量继续涨到上千时,单纯分层可能不够,我会考虑引入 [[Knowledge Graph]] 来组织意图之间的层次和关联关系,这样路由和召回都能更准确。
所以整体上,我更倾向把这道题看成一个系统工程问题,不是选一个模型就能搞定。核心是分治和压缩,同时给每层留好容错和监控。如果让我落地,我会先搭一个最小闭环:聚类加多路召回加精排,验证准确率损失在2%以内,再逐步加压缩和缓存。