在 Agent 开发中,意图识别是绕不开的第一关。用户说了一句话,系统要先搞清楚他到底想干什么,才能把请求路由到正确的处理流程。
很多人一提到意图识别,第一反应是调大模型。但对于 50 种左右的意图规模,纯 LLM 方案延迟高、成本高、输出不稳定,其实并不是最优解。
本文用 Java 从零实现一套意图识别方案,从最简单的规则匹配,到 DeepLearning4J 训练的 BiLSTM 分类器,再到接入 LangGraph4j 工作流,帮你快速落地一个生产可用的意图识别模块。
一、为什么 Java 也能做好意图识别
Java 生态在 NLP 和深度学习领域并不弱。Stanford CoreNLP、OpenNLP 提供了经典的文本处理能力,DeepLearning4J 是 Java 原生的深度学习框架,可以直接训练和推理 LSTM、BiLSTM 等序列模型,不需要依赖 Python 服务。
对于意图识别这种典型的文本分类任务,Java 完全可以从训练到部署一条龙搞定。
二、整体方案设计
针对 50 种意图的规模,推荐采用三级流水线架构:
用户输入 → 第一层:关键词规则匹配(毫秒级)
→ 未命中 → 第二层:BiLSTM 分类器(~30ms)
→ 置信度低 → 第三层:大模型兜底
→ 输出:意图 + 置信度
第一层用规则覆盖高频固定意图,延迟不到 1ms,准确率接近 100%。第二层用 BiLSTM 处理语义相似但表述不同的情况,延迟约 30ms。第三层只在置信度很低时调用大模型兜底,大幅降低整体成本。
三、第一层:关键词规则匹配
规则匹配是最简单也最高效的方式,适合表述固定的高频意图。
java
public class RuleBasedIntentRecognizer {
public String classify(String text) {
if (text.matches(".*(查|看).*(订单|物流).*")) {
return "query_order";
} else if (text.matches(".*(退|取消).*(订单|服务).*")) {
return "cancel_order";
} else if (text.matches(".*(天气|温度|冷|热).*")) {
return "weather";
}
return "unknown";
}
}
规则匹配的优势是零成本、零延迟、完全可控。缺点是覆盖有限,用户换个说法就可能识别不了,所以需要第二层来兜底。
四、第二层:BiLSTM 分类器
这是本文的核心部分。我们用 DeepLearning4J 训练一个 BiLSTM 模型,对规则匹配未命中的文本做语义分类。
4.1 引入依赖
xml
<dependency>
<groupId>org.deeplearning4j</groupId>
<artifactId>deeplearning4j-core</artifactId>
<version>1.0.0-M2.1</version>
</dependency>
<dependency>
<groupId>org.nd4j</groupId>
<artifactId>nd4j-native-platform</artifactId>
<version>1.0.0-M2.1</version>
</dependency>
4.2 准备训练数据
准备一个 CSV 文件,格式为 text,intent,每个意图建议 50~100 条样本:
csv
帮我查一下订单,query_order
我的快递到哪了,query_order
物流信息查一下,query_order
我要取消订单,cancel_order
帮我退掉这个服务,cancel_order
今天天气怎么样,weather
明天冷不冷,weather
4.3 构建 BiLSTM 模型
BiLSTM 的核心思路是:正向 LSTM 从左到右读文本,反向 LSTM 从右到左读文本,两者拼接后能同时捕捉上下文信息,比单向 LSTM 效果更好。
java
MultiLayerConfiguration conf = new NeuralNetConfiguration.Builder()
.seed(42)
.updater(new Adam(0.001))
.weightInit(WeightInit.XAVIER)
.gradientNormalization(GradientNormalization.ClipElementWiseAbsoluteValue)
.gradientNormalizationThreshold(1.0)
.list()
.layer(0, new LSTM.Builder()
.nIn(WORD_VECTOR_SIZE).nOut(HIDDEN_SIZE)
.activation(Activation.TANH).build())
.layer(1, new LSTM.Builder()
.nIn(HIDDEN_SIZE).nOut(HIDDEN_SIZE)
.activation(Activation.TANH).build())
.layer(2, new RnnOutputLayer.Builder()
.nIn(HIDDEN_SIZE).nOut(numClasses)
.activation(Activation.SOFTMAX)
.lossFunction(LossFunctions.LossFunction.MCXENT)
.build())
.build();
MultiLayerNetwork model = new MultiLayerNetwork(conf);
model.init();
4.4 文本预处理
将文本转为模型能接受的 INDArray 格式,形状为 [batchSize, wordVectorSize, timeSteps]:
java
private INDArray textToFeatures(String text) {
String[] tokens = text.split("");
INDArray features = Nd4j.zeros(1, WORD_VECTOR_SIZE, MAX_SEQ_LEN);
for (int t = 0; t < Math.min(tokens.length, MAX_SEQ_LEN); t++) {
int wordIdx = wordToIndex.getOrDefault(tokens[t], 0);
for (int d = 0; d < WORD_VECTOR_SIZE; d++) {
features.putScalar(0, d, t, (wordIdx * (d + 1)) % 100 / 100.0);
}
}
return features;
}
实际项目中建议将这里的伪词向量替换为预训练词向量(如腾讯 AI Lab 中文词向量),效果能提升 10%~15%。
4.5 训练与推理
训练完成后将模型保存为 .zip 文件,推理时加载即可:
java
// 训练
model.fit(dataSet);
ModelSerializer.writeModel(model, "intent_model.zip", true);
// 推理
MultiLayerNetwork model = ModelSerializer.restoreMultiLayerNetwork("intent_model.zip", true);
INDArray output = model.output(textToFeatures("帮我看看快递到哪了"));
int predictedIdx = Nd4j.argMax(output, 1).getInt(0);
double confidence = output.getDouble(0, predictedIdx);
五、接入 LangGraph4j 工作流
意图识别的最终目的是路由。在 LangGraph4j 中,意图识别作为工作流的第一个节点,识别完成后通过条件边路由到不同的处理节点:
START → 意图识别节点 → 条件路由 → 订单处理 / 天气查询 / 兜底 → END
java
var stateGraph = new StateGraph<>(IntentState.SCHEMA, IntentState::new)
.addNode("classify", node_async(new IntentClassifyNode()))
.addNode("order_handler", node_async(new OrderHandlerNode()))
.addNode("weather_handler", node_async(new WeatherHandlerNode()))
.addNode("fallback", node_async(new FallbackNode()))
.addEdge(START, "classify")
.addConditionalEdges("classify", state -> {
String intent = state.intent();
return switch (intent) {
case "query_order", "cancel_order" -> "order_handler";
case "weather" -> "weather_handler";
default -> "fallback";
};
})
.addEdge("order_handler", END)
.addEdge("weather_handler", END)
.addEdge("fallback", END);
这样,用户输入进入工作流后,先经过意图识别节点判断意图,再自动路由到对应的处理节点,整个流程清晰可控。
六、关键超参数参考
| 超参数 | 建议值 | 说明 |
|---|---|---|
| 词向量维度 | 100 | 实际项目建议用预训练词向量 |
| LSTM 隐藏层大小 | 128 | 50 个意图建议 128~256 |
| 最大序列长度 | 30 | 根据平均句长调整 |
| 学习率 | 0.001 | 配合 Adam 优化器 |
| 训练轮数 | 20 | 观察 loss 不再下降即可停止 |
| 批大小 | 32 | 数据量小可用 16 |
七、预期效果
- 训练数据:50 个意图 × 50~100 条样本
- 训练时间:CPU 上约 1~5 分钟
- 推理延迟:<30ms(单次推理)
- 整体准确率:90%+(数据质量好的情况下)
八、总结
意图识别本质上是一个分类问题,不需要每次都动用大模型。Java 生态下,规则匹配 + DeepLearning4J 的 BiLSTM 分类器就能覆盖绝大多数场景,延迟低、成本低、完全可控。
关键思路是分层处理:规则解决确定性高的问题,模型解决语义相似的问题,大模型只在兜底时使用。这样既保证了速度,又保证了覆盖率。
希望这篇文章能帮你快速上手 Java 意图识别,如果有 50 种左右的意图需要处理,不妨试试这套方案。