☰
彻底搞懂分类器与路由:分类只管识别,路由只管分发
2026/10/14 3:10:21 网站建设 项目流程
很多意图路由代码之所以混乱,是因为把“分类”和“路由”写在了同一个方法里。本文只聚焦两件事:分类器如何只做意图识别,路由如何只做服务分发。并用 Spring AI 给出最小可理解的解耦示例。核心结论:分类器负责“用户想干什么”,路由负责“该送到哪个服务”。

一、先看一段常见的耦合代码

很多系统一开始都会写成这样:

if (query.contains("退款")) {
return refundService.call(query);
} else if (query.contains("物流")) {
return logisticsService.call(query);
} else if (query.contains("投诉")) {
return complaintService.call(query);
}

这段代码能跑,但问题很明显:

  1. 分类逻辑和转发逻辑揉在一起;
  2. 新增一个服务,要改分类代码;
  3. 替换分类模型,要动路由逻辑;
  4. 如果让 LLM 同时输出“意图”和“目标服务”,一旦幻觉,路由直接错乱。

所以第一件事,是把两个概念拆开:

  • 分类器 Classifier:只识别意图,输出intent、confidence、参数;
  • 路由 Router:只根据分类结果,决定目标服务。

一句话:

分类 = 识别是什么
路由 = 决定送到哪

分类器不感知下游有哪些服务,路由不负责理解用户文本。


二、分类器:只回答“用户想干什么”

分类器的职责非常单一:输入用户 query,输出意图标签和置信度。

先定义结果对象和接口。这里不用record,用传统 Java 类:

import java.util.Collections;
import java.util.Map;

public class ClassifierResult {

private final String intent;
private final double confidence;
private final Map<String, Object> slots;

public ClassifierResult(String intent, double confidence, Map<String, Object> slots) {
this.intent = intent;
this.confidence = confidence;
this.slots = slots == null ? Collections.emptyMap() : slots;
}

public String getIntent() {
return intent;
}

public double getConfidence() {
return confidence;
}

public Map<String, Object> getSlots() {
return slots;
}
}
public interface IntentClassifier {
ClassifierResult classify(String query);
}

注意:ClassifierResult里没有targetService。
分类器不应该知道下游有哪些服务,更不应该决定调用谁。

分类器通常有三种形态:

  1. 规则分类器;
  2. 轻量模型分类器;
  3. LLM 显式分类器。

它们输出格式一致,都是intent + confidence + slots。


1. 规则分类器

适合强规则、关键词明确的场景,比如“转人工”“退款”“物流”。

import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;

@Component
public class RuleIntentClassifier implements IntentClassifier {

private static class Rule {
private final Pattern pattern;
private final String intent;

public Rule(Pattern pattern, String intent) {
this.pattern = pattern;
this.intent = intent;
}

public Pattern getPattern() {
return pattern;
}

public String getIntent() {
return intent;
}
}

private static final List<Rule> RULES = Arrays.asList(
new Rule(Pattern.compile(".*(转人工|人工客服).*"), "manual_service"),
new Rule(Pattern.compile(".*(物流|快递|到哪).*"), "logistics_query"),
new Rule(Pattern.compile(".*(退款|退货|退钱).*"), "refund_apply"),
new Rule(Pattern.compile(".*(投诉|差评).*"), "complaint")
);

@Override
public ClassifierResult classify(String query) {
for (Rule rule : RULES) {
if (rule.getPattern().matcher(query).matches()) {
return new ClassifierResult(rule.getIntent(), 1.0, Collections.emptyMap());
}
}
return new ClassifierResult(null, 0.0, Collections.emptyMap());
}
}

规则分类器只输出意图,不输出服务。


2. 轻量模型分类器

轻量模型分类器介于规则和 LLM 之间。
它通常使用 TextCNN、FastText、蒸馏 BERT、ONNX 模型等做本地推理,成本低、延迟低,适合高频且意图相对稳定的场景。

它和 LLM 分类器的边界一样:只输出意图,不输出服务。

先定义模型输出对象和模型客户端:

import java.util.Collections;
import java.util.Map;

public class ModelOutput {

private final String intent;
private final double confidence;
private final Map<String, Object> slots;

public ModelOutput(String intent, double confidence, Map<String, Object> slots) {
this.intent = intent;
this.confidence = confidence;
this.slots = slots == null ? Collections.emptyMap() : slots;
}

public String getIntent() {
return intent;
}

public double getConfidence() {
return confidence;
}

public Map<String, Object> getSlots() {
return slots;
}
}
public interface IntentModelClient {
ModelOutput predict(String query);
}

然后实现IntentClassifier:

import org.springframework.stereotype.Component;

@Component
public class LightweightIntentClassifier implements IntentClassifier {

private final IntentModelClient modelClient;

public LightweightIntentClassifier(IntentModelClient modelClient) {
this.modelClient = modelClient;
}

@Override
public ClassifierResult classify(String query) {
ModelOutput output = modelClient.predict(query);

return new ClassifierResult(
output.getIntent(),
output.getConfidence(),
output.getSlots()
);
}
}

这里的IntentModelClient可以是:

  • ONNX Runtime 加载 BERT 蒸馏模型;
  • DJL 加载本地文本分类模型;
  • TensorFlow Serving / Triton 远程推理;
  • 自研小模型 HTTP 服务。

但无论底层是什么,它对外只做一件事:

query -> intent / confidence / slots

它不知道下游有refundService还是logisticsService。
目标服务仍然由ServiceRouter决定。


3. LLM 显式分类器

用 Spring AI 调 LLM,也只让它输出意图,不让它选服务。

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Component;

@Component
public class LlmIntentClassifier implements IntentClassifier {

private final ChatClient chatClient;
private final ObjectMapper objectMapper;

public LlmIntentClassifier(ChatClient.Builder builder, ObjectMapper objectMapper) {
this.chatClient = builder.build();
this.objectMapper = objectMapper;
}

@Override
public ClassifierResult classify(String query) {
String prompt = "你是意图分类器,只输出 JSON,不要输出目标服务。\n"
+ "可选意图:manual_service, logistics_query, refund_apply, complaint, order_query, unknown。\n"
+ "输出格式:{\"intent\":\"xxx\",\"confidence\":0.0,\"slots\":{}}\n"
+ "用户输入:%s";

prompt = String.format(prompt, query);

try {
String content = chatClient.prompt().user(prompt).call().content();
JsonNode node = objectMapper.readTree(content);

return new ClassifierResult(
node.path("intent").asText("unknown"),
node.path("confidence").asDouble(0.0),
objectMapper.convertValue(node.path("slots"), Map.class)
);
} catch (Exception e) {
return new ClassifierResult("unknown", 0.0, null);
}
}
}

关键点:LLM 分类器的 Prompt 里不要写服务列表。
它只需要知道有哪些意图,不需要知道下游有refundService还是logisticsService。


4. 三种分类器对比

分类器原理成本适合场景
规则分类器正则、关键词、字典极低强规则、明确关键词
轻量模型分类器本地小模型推理低高频、意图稳定、有标注数据
LLM 显式分类器Prompt + 大模型高语义复杂、长尾表达

它们的共同点是:只输出 intent,不输出 targetService。


三、路由:只回答“送到哪个服务”

路由的职责也很单一:拿到分类结果,决定目标服务。

定义路由结果和接口:

import java.util.Collections;
import java.util.Map;

public class RouteDecision {

private final String targetService;
private final Map<String, Object> params;
private final boolean fallback;
private final String reason;

public RouteDecision(String targetService, Map<String, Object> params,
boolean fallback, String reason) {
this.targetService = targetService;
this.params = params == null ? Collections.emptyMap() : params;
this.fallback = fallback;
this.reason = reason;
}

public String getTargetService() {
return targetService;
}

public Map<String, Object> getParams() {
return params;
}

public boolean isFallback() {
return fallback;
}

public String getReason() {
return reason;
}
}
public interface ServiceRouter {
RouteDecision route(String query, ClassifierResult classifierResult);
}

这里query传进来,是为了兼容“模型路由”这种特殊实现。
对于规则路由来说,它根本不需要读query。


规则路由

规则路由只做一件事:查表。

import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

@Component
public class RuleServiceRouter implements ServiceRouter {

private static final Map<String, String> INTENT_TO_SERVICE = new HashMap<>();

static {
INTENT_TO_SERVICE.put("manual_service", "humanCustomerService");
INTENT_TO_SERVICE.put("logistics_query", "logisticsService");
INTENT_TO_SERVICE.put("refund_apply", "refundService");
INTENT_TO_SERVICE.put("complaint", "complaintService");
INTENT_TO_SERVICE.put("order_query", "orderService");
}

@Override
public RouteDecision route(String query, ClassifierResult result) {
if (result == null || result.getIntent() == null || result.getConfidence() < 0.5) {
return new RouteDecision("fallbackService", Collections.emptyMap(), true, "low_confidence");
}

String target = INTENT_TO_SERVICE.get(result.getIntent());
if (target == null) {
return new RouteDecision("fallbackService", Collections.emptyMap(), true, "no_mapping");
}

return new RouteDecision(target, result.getSlots(), false, "rule_router");
}
}

注意:规则路由里没有query.contains("退款")。
它不读文本,不懂语义,只根据分类结果查表。

这是分类和路由解耦的关键:

  • 分类器负责理解文本;
  • 路由负责映射意图到服务。

四、模型路由:它是路由,不是分类器

有一种路由实现叫“模型路由”。

它把“理解 query + 选择服务”都交给 LLM,直接输出targetService,不要求外部先有intent。

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Component;

import java.util.Map;

@Component
public class LlmModelRouter implements ServiceRouter {

private final ChatClient chatClient;
private final ObjectMapper objectMapper;

public LlmModelRouter(ChatClient.Builder builder, ObjectMapper objectMapper) {
this.chatClient = builder.build();
this.objectMapper = objectMapper;
}

@Override
public RouteDecision route(String query, ClassifierResult result) {
String prompt = "你是路由决策器。根据用户问题,从下面服务中选择一个:\n"
+ "humanCustomerService, logisticsService, refundService,\n"
+ "complaintService, orderService, fallbackService。\n\n"
+ "严格输出 JSON:\n"
+ "{\"targetService\":\"xxx\",\"params\":{},\"fallback\":false}\n\n"
+ "用户问题:%s";

prompt = String.format(prompt, query);

try {
String content = chatClient.prompt().user(prompt).call().content();
JsonNode node = objectMapper.readTree(content);

return new RouteDecision(
node.path("targetService").asText("fallbackService"),
objectMapper.convertValue(node.path("params"), Map.class),
node.path("fallback").asBoolean(false),
"llm_model_router"
);
} catch (Exception e) {
return new RouteDecision("fallbackService", null, true, "llm_error");
}
}
}

模型路由内部其实也发生了语义理解,但它不对外暴露intent,而是直接输出路由指令。

所以一定要区分:

实现是否输出 intent输出谁决定目标服务
LLM 显式分类 + 规则路由是ClassifierResult路由
模型路由否RouteDecisionLLM 自己

结论:

模型路由是路由的一种实现,不是分类器。
它在内部隐式分类,但外部拿不到意图标签。

五、最小组合:先分类,再路由

把分类器和路由串起来,代码可以非常简单:

public class IntentDispatchService {

private final IntentClassifier classifier;
private final ServiceRouter router;

public IntentDispatchService(IntentClassifier classifier, ServiceRouter router) {
this.classifier = classifier;
this.router = router;
}

public RouteDecision dispatch(String query) {
ClassifierResult result = classifier.classify(query);
return router.route(query, result);
}
}

这样拆开之后:

  • 想换分类器:规则分类器换成 LLM 分类器,路由不用动;
  • 想换路由:规则路由换成模型路由,分类器不用动;
  • 想新增服务:只改路由映射,不改分类逻辑。

这就是解耦的价值。


六、什么时候用哪种?

不需要复杂化,只需要按场景选。

分类器怎么选

场景推荐
关键词明确,比如“转人工”“退款”规则分类器
高频、意图稳定、有标注数据轻量模型分类器
表达多样、语义复杂LLM 显式分类器
需要可观测、需要 intent 标签轻量模型分类器或 LLM 显式分类器

路由怎么选

场景推荐
意图到服务映射稳定规则路由
想让 LLM 端到端直接选服务模型路由
需要可观测、可测试规则路由
服务多、映射复杂、实验性质模型路由

但无论怎么选,边界不要乱:

  • 分类器不要输出targetService;
  • 规则路由不要读query做关键词判断;
  • LLM 分类器的 Prompt 里不要写服务列表;
  • 模型路由不是分类器,它只是路由的另一种实现。

七、总结

分类器和路由的职责其实非常清楚:

  • 规则分类器:根据关键词、正则、字典识别意图;
  • 轻量模型分类器:本地小模型推理,输出标准intent;
  • LLM 显式分类器:用大模型理解语义,仍然只输出intent;
  • 规则路由:根据分类结果查表转发,不读文本,不懂语义;
  • 模型路由:LLM 内部隐式分类,直接输出路由决策,它属于路由,不是分类器。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询