springboot+langchain4j实战:Day 19------Query 改写 & 意图识别,让模糊查询变精准,让检索策略变智能

Day 19 --- Query 改写 & 意图识别

主题 :让模糊查询变精准,让检索策略变智能

端口 :8091

技术栈:Spring Boot 3.4.3 + LangChain4j 1.13.1 + Java 17


1. Day 19 比 Day 18 多了什么

维度 Day 18 Day 19
Query 处理 无(直接用原始 query) LLM 改写(指代消解 + 省略补全)
意图理解 无(固定三路) LLM 四分类 + 意图感知路由
检索策略 固定三路全开 动态策略(按意图选择路径)
API 端点 3 个 7 个(新增 4 个)
Pipeline 召回 → 融合 改写 → 分类 → 策略 → 召回 → 融合
降级处理 LLM 失败降级(返回原始 query)

2. 核心概念速览

Query 改写

  • 指代消解:"昨天那个产品怎么样" → "码哥AI中台产品功能和评价"
  • 省略补全:"SDK初始化" → "SDK初始化步骤方法教程"
  • 降级:LLM 失败时返回原始 query

意图识别

  • 四分类:knowledge(知识库)/ order(订单)/ chat(闲聊)/ complaint(投诉)
  • 策略路由
    • knowledge → 三路全开
    • order → 关键词加重(权重×2)+ ES
    • chat → 仅向量检索
    • complaint → 仅 ES BM25

全链路 Pipeline

复制代码
原始Query → LLM改写 → 意图识别 → 策略选择 → 并行召回 → RRF融合 → Top-K结果

3. 快速启动

bash 复制代码
# 1. 进入项目目录
cd day19-query-rewrite

# 2. 确保 PostgreSQL + Elasticsearch 已启动

# 3. 编译运行(首次启动自动初始化知识库)
mvn clean spring-boot:run

# 4. 访问教学前端
open http://localhost:8091

前置依赖

  • PostgreSQL 14+(已安装 PGVector 扩展)

  • Elasticsearch 8.x

  • Docker 快速启动 ES:

    bash 复制代码
    docker run -d --name es -p 9200:9200 \
      -e "discovery.type=single-node" \
      -e "xpack.security.enabled=false" \
      elasticsearch:8.15.0

4. 项目结构

复制代码
day19-query-rewrite/
├── pom.xml
├── docs/
│   ├── day19-ai-concepts-teaching.md    # 教学文档(5个核心概念)
│   └── day19-reference.md               # 代码参考速查
├── README.md
└── src/main/
    ├── java/com/day19/demo/
    │   ├── Day19Application.java        # 启动入口
    │   ├── config/
    │   │   ├── ChatModelConfig.java      # LLM + Embedding Bean
    │   │   ├── ElasticsearchConfig.java  # ES 客户端配置
    │   │   └── DataInitializer.java      # 知识库自动初始化
    │   ├── core/
    │   │   ├── HybridSearchResult.java   # 统一检索结果 DTO
    │   │   └── RrfExperimentResult.java  # RRF 实验 DTO
    │   ├── dto/
    │   │   └── ApiResult.java            # 统一 API 响应
    │   ├── rag/
    │   │   ├── VectorSearchService.java  # PGVector 向量检索
    │   │   ├── KeywordSearchService.java # N-gram 关键词检索
    │   │   ├── ElasticsearchService.java # ES BM25 检索
    │   │   ├── QueryRewriteService.java  # ⭐ Query 改写
    │   │   ├── IntentClassifierService.java # ⭐ 意图分类
    │   │   └── MultiRecallService.java   # ⭐ 意图感知多路召回
    │   └── controller/
    │       └── SearchController.java     # REST API(7个端点)
    └── resources/
        ├── application.yml              # 配置(端口 8091)
        ├── schema.sql                   # PGVector 建表
        ├── data.sql                     # 种子数据(空)
        ├── static/
        │   └── index.html               # 教学前端(3个Tab)
        └── docs/
            ├── 码哥科技.txt
            ├── SDK集成指南.txt
            ├── API文档.txt
            └── 部署指南.txt

5. 核心实现

5.1 Query 改写服务

java 复制代码
@Service
public class QueryRewriteService {
    
    public String rewrite(String originalQuery) {
        // 1. 构建 System Prompt(指代消解 + 省略补全规则)
        // 2. 调用 LLM 改写
        // 3. 降级:LLM 失败返回原始 query
    }
    
    public String rewriteWithHistory(String query, List<String> recentMessages) {
        // 带对话历史的上下文感知改写
    }
}

System Prompt 核心

  • 指代消解:替换"那个"、"它"、"这个"
  • 省略补全:补充缺失的动作和场景
  • 关键词扩展:适度添加同义词
  • 只输出改写文本,不要解释

5.2 意图识别服务

java 复制代码
@Service
public class IntentClassifierService {
    
    public IntentResult classify(String query) {
        // 1. 构建 System Prompt(四分类规则)
        // 2. 调用 LLM 返回 JSON
        // 3. 解析 JSON → IntentResult
    }
    
    public RetrievalStrategy getRetrievalStrategy(IntentResult intent) {
        // knowledge → 三路全开
        // order → 关键词×2 + ES
        // chat → 仅向量
        // complaint → 仅 ES
    }
}

5.3 意图感知多路召回

java 复制代码
@Service
public class MultiRecallService {
    
    public Map<String, Object> searchIntentAware(String query, String tableName) {
        // 步骤1:意图分类
        // 步骤2:Query 改写(knowledge 意图)
        // 步骤3:策略选择(按意图选路径)
        // 步骤4:并行召回(只执行选中的路径)
        // 步骤5:RRF 融合
    }
}

6. 配置说明

yaml 复制代码
server:
  port: 8091                          # Day 19 端口

# Query 改写 & 意图识别配置(Day 19 新增)
query:
  rewrite:
    enabled: true                     # 是否启用改写
    use-history: true                 # 是否使用对话历史
  intent:
    enabled: true                     # 是否启用意图识别
    default-strategy: knowledge       # 降级时的默认意图

# 多路召回配置(沿用 Day 18)
multi-recall:
  rrf:
    default-k: 60                     # RRF 默认 k 值
    experiment-ks: 10,30,60,100       # k 值实验列表
  recall-top: 20                      # 每条路径取 Top-N
  final-top: 5                        # 最终返回条数

7. API 端点详解

# 端点 用途 测试命令
1 GET /search 三路召回+RRF(标准) curl "localhost:8091/search?query=产品"
2 GET /search/intent-aware 意图感知检索 curl "localhost:8091/search/intent-aware?query=SDK"
3 GET /query/rewrite Query改写 curl "localhost:8091/query/rewrite?query=那个产品"
4 GET /query/classify 意图分类 curl "localhost:8091/query/classify?query=订单"
5 GET /experiment/k-values k值实验 curl "localhost:8091/experiment/k-values?query=部署"
6 GET /search/raw 三路原始数据 curl "localhost:8091/search/raw?query=API"
7 GET /demo/intent-pipeline 全链路演示 curl "localhost:8091/demo/intent-pipeline?query=产品"

8. 测试指南

测试 Query 改写

bash 复制代码
# 模糊查询 → 改写
curl "http://localhost:8091/query/rewrite?query=昨天那个产品怎么样"
# 预期:rewritten = "码哥AI中台产品功能特点和评价"

curl "http://localhost:8091/query/rewrite?query=怎么部署"
# 预期:rewritten = "生产环境部署步骤和方法"

测试意图识别

bash 复制代码
curl "http://localhost:8091/query/classify?query=SDK初始化失败"
# 预期:intent=knowledge, confidence>0.8

curl "http://localhost:8091/query/classify?query=我的订单到哪了"
# 预期:intent=order, keywords包含"订单"

curl "http://localhost:8091/query/classify?query=为什么这么慢"
# 预期:intent=complaint, keywords包含"投诉"或"反馈"

测试意图感知检索

bash 复制代码
# knowledge 意图 → 三路全开
curl "http://localhost:8091/search/intent-aware?query=SDK集成方法"

# chat 意图 → 仅向量路
curl "http://localhost:8091/search/intent-aware?query=你好"

# 对比两种方式的结果差异

测试全链路 Pipeline

bash 复制代码
curl "http://localhost:8091/demo/intent-pipeline?query=码哥科技的产品"
# 查看 6 个步骤的详细结果和每步耗时

9. 踩坑记录

9.1 LLM 改写输出格式不稳定

现象 :LLM 有时输出 "改写后:XXX" 而不是纯文本

解决:在代码中 strip 前缀/引号,并设置长度上限(超过原始 3 倍 → 降级)

9.2 意图分类 JSON 解析失败

现象 :LLM 返回的 JSON 被 Markdown 代码块包裹

解决:用正则去掉 ```json ... ``` 包装再解析

9.3 改写+分类串行太慢

现象 :两次 LLM 调用总耗时 2-3 秒

原因 :改写和分类都依赖 deepseek-v3,API 延迟约 1-1.5s/次

优化:如果改写结果对分类影响不大,可以并行调用

9.4 chat 意图只开向量路可能不够

现象 :闲聊时向量检索返回的结果可能不相关

原因 :闲聊 query(如"今天怎么样")与知识库内容语义距离远

策略:chat 意图可以完全不检索,直接由 LLM 生成回复

9.5 order 意图模拟数据不足

现象 :学习项目的知识库中没有订单数据

说明 :Day 19 的 order/complaint 意图主要用于展示意图路由机制,

实际效果依赖于业务数据的质量


10. 面试怎么说

"用户问题很模糊怎么办?"

"我们在检索前加了 Query 改写管道。用 LLM 做指代消解和省略补全,

比如 '那个产品怎么样' 会改写为 '码哥AI中台产品功能和评价'。

改写是异步的、带降级的------LLM 挂了不影响检索,直接用原 query。"

"怎么提高检索命中率?"

"我们引入了意图感知路由:先分类用户意图(知识库/订单/闲聊/投诉),

不同意图走不同的检索策略。比如订单意图加重关键词路、闲聊意图只走向量路。

从固定三路升级到意图驱动的动态路径,减少了噪音召回。"

"意图识别怎么做的?"

"基于 Prompt 的分类方法。用 System Prompt 告诉 LLM 四种意图的定义和判断规则,

让 LLM 输出 JSON。优点是零样本,不需要训练数据;缺点是依赖 LLM 调用延迟。

生产环境可以用 BERT 分类器替代,延迟降低到 10ms。"

"Pipeline 的瓶颈在哪?"

"瓶颈在 LLM 调用------改写+分类两次调用占 90% 的耗时。优化方向:

  1. 缓存常见 query 的改写结果;2) 改用更快的本地模型;3) 改写和分类可以并行。"