From 3f93d015f894ca066db7691ed5627f8194ee566a Mon Sep 17 00:00:00 2001 From: wei-py Date: Mon, 14 Sep 2026 14:13:29 +0800 Subject: [PATCH] =?UTF-8?q?refactor(chat):=20=E7=A7=BB=E9=99=A4=20LLM=20?= =?UTF-8?q?=E6=84=8F=E5=9B=BE=E5=88=86=E7=B1=BB=E5=B9=B6=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E5=8E=9F=E6=96=87=E6=A3=80=E7=B4=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/wok/supportbot/app/ChatContext.java | 7 +- .../com/wok/supportbot/app/ChatPipeline.java | 87 +---- .../com/wok/supportbot/rag/RagPipeline.java | 18 +- .../wok/supportbot/service/IntentRouter.java | 134 -------- .../com/wok/supportbot/ChatPipelineTests.java | 317 +++++++++++------- .../com/wok/supportbot/IntentRouterTests.java | 126 ------- 6 files changed, 222 insertions(+), 467 deletions(-) delete mode 100644 src/main/java/com/wok/supportbot/service/IntentRouter.java delete mode 100644 src/test/java/com/wok/supportbot/IntentRouterTests.java diff --git a/src/main/java/com/wok/supportbot/app/ChatContext.java b/src/main/java/com/wok/supportbot/app/ChatContext.java index a0b9c32..c584ff4 100644 --- a/src/main/java/com/wok/supportbot/app/ChatContext.java +++ b/src/main/java/com/wok/supportbot/app/ChatContext.java @@ -17,7 +17,7 @@ import java.util.List; * @param systemPrompt 角色人设/系统提示词,可为 null * @param allowedMcpTools 允许的 MCP 工具名列表;null=无角色允许全部;空=有角色但无授权(禁止);["*"]=全部 * @param categoryIds 知识库分类隔离范围,可为空(不限制) - * @param rewriteStrategy RAG 查询重写策略(REWRITE / TRANSLATION / COMPRESSION / MULTI_QUERY),可为 null + * @param rewriteStrategy RAG 查询重写策略,默认 NONE 原文检索;可显式选择 REWRITE / TRANSLATION / COMPRESSION / MULTI_QUERY * @param enableRag 是否启用 RAG 检索;false=普通对话 * @param streaming 是否流式输出 * @param roleId 客服角色 ID(可空,供调用追踪等使用) @@ -47,12 +47,15 @@ public record ChatContext( private static final String DEFAULT_APP_TYPE = "CHAT"; /** - * 紧凑构造器:规范化默认值,保证 appType 非空。 + * 紧凑构造器:规范化应用类型与检索默认值。 */ public ChatContext { if (appType == null || appType.isBlank()) { appType = DEFAULT_APP_TYPE; } + if (rewriteStrategy == null || rewriteStrategy.isBlank()) { + rewriteStrategy = "NONE"; + } } /** diff --git a/src/main/java/com/wok/supportbot/app/ChatPipeline.java b/src/main/java/com/wok/supportbot/app/ChatPipeline.java index 0f5b44c..0adf30f 100644 --- a/src/main/java/com/wok/supportbot/app/ChatPipeline.java +++ b/src/main/java/com/wok/supportbot/app/ChatPipeline.java @@ -3,9 +3,7 @@ package com.wok.supportbot.app; import com.wok.supportbot.rag.RagContext; import com.wok.supportbot.rag.RagPipeline; import com.wok.supportbot.service.FaqMatchEngine.FaqMatchResult; -import com.wok.supportbot.service.IntentRouter; import com.wok.supportbot.service.SystemConfigService; -import com.wok.supportbot.service.RagHitLogService; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.ai.document.Document; @@ -20,19 +18,17 @@ import java.util.Optional; /** * 统一对话管道(编排层)。 *

- * 编排一次完整对话的决策流程:FAQ 优先 → 意图路由 → RAG 检索 → 组装系统提示词与用户消息, + * 编排一次完整对话的决策流程:FAQ 优先 → 本地寒暄判断 → RAG 检索 → 组装系统提示词与用户消息, * 产出 {@link ChatRequest} 交由 {@code AssistantApp} 执行实际的 {@code call()} / {@code stream()}。 *

* 设计说明:本类为纯编排层,不持有 ChatClient(ChatClient 构建与 Advisor 链装配仍在 * {@code AssistantApp}),因此 {@code call} / {@code stream} 由 {@code AssistantApp} 承担, * 避免 {@code ChatPipeline} ↔ {@code AssistantApp} 循环依赖。 *

- * 接入 {@link IntentRouter} 替代原 {@code AiController.shouldBypassKnowledgeRetrieval} 的硬编码寒暄词判断: - * 寒暄词列表保留为快速路径与兜底,IntentRouter 负责细粒度意图分类,二者命中其一即跳过 KB 检索。 + * 完整 FAQ 匹配后仅使用本地寒暄判断,其他请求直接检索,不调用 LLM 做意图分类。 *

* {@code @pipeline} orchestration-layer order=0
- * {@code @pipeline-step} buildRequest: FAQ优先 → 意图路由 → RAG检索 → 提示词组装
- * {@code @pipeline-step} routeIntent: 寒暄词快速路径 → IntentRouter LLM分类 → 降级RAG
+ * {@code @pipeline-step} buildRequest: FAQ优先 → 本地寒暄判断 → 默认原文检索 → 提示词组装
* {@code @pipeline-step} effectiveSystem: DB全局提示词 + 角色人设 动态组合
* 同步至: frontend/src/views/PipelineFlow.vue, CLAUDE.md ASCII管道图 */ @@ -40,29 +36,20 @@ import java.util.Optional; @Slf4j public class ChatPipeline { - /** IntentRouter 判为 CHITCHAT 的置信度阈值,低于此值视为不确定,继续走 RAG */ - private static final double CHITCHAT_CONFIDENCE_THRESHOLD = 0.6; - - @Resource - private IntentRouter intentRouter; - @Resource private RagPipeline ragPipeline; @Resource private SystemConfigService systemConfigService; - @Resource - private RagHitLogService ragHitLogService; - /** * 编排一次对话请求,产出执行决策。 *

* 决策分支: *

* @@ -79,22 +66,18 @@ public class ChatPipeline { globalPrompt, null, null, "CHAT", null, null, null); } - // 完整 FAQ 三级匹配前置:标准答案命中时省去 LLM 意图分类,并保留角色分类隔离。 + // 完整 FAQ 三级匹配前置,保留角色分类隔离。 RagPipeline.FaqMatchOutcome faqOutcome = ragPipeline.tryFaqMatchClean(ctx.message(), ctx.categoryIds()); if (faqOutcome.result().isPresent()) { FaqMatchResult faqMatch = faqOutcome.result().get(); - log.info("FAQ 命中标准答案,跳过意图分类: chatId={}, matchType={}", ctx.chatId(), faqMatch.getMatchType()); + log.info("FAQ 命中标准答案: chatId={}, matchType={}", ctx.chatId(), faqMatch.getMatchType()); return new ChatRequest(ctx, ctx.message(), baseSystem, Optional.ofNullable(faqMatch.getFaq().getAnswer()), globalPrompt, null, null, "FAQ", null, null, faqMatch); } - // FAQ 未命中后再路由:寒暄词快速路径 → IntentRouter LLM 分类。 - IntentRouter.IntentResult intent = routeIntent(ctx.message()); - - // 寒暄/闲聊:IntentRouter 判定 CHITCHAT 高置信,跳过 KB 检索 - if (intent != null && "CHITCHAT".equals(intent.getIntent()) - && intent.getConfidence() >= CHITCHAT_CONFIDENCE_THRESHOLD) { + // 仅本地寒暄词绕过检索;业务问题默认不做任何 LLM 预处理。 + if (isChitchat(ctx.message())) { return new ChatRequest(ctx, ctx.message(), baseSystem, Optional.empty(), globalPrompt, null, null, "CHITCHAT", null, null, null); } @@ -102,21 +85,6 @@ public class ChatPipeline { // 仅干净完成的 FAQ 匹配可跳过;异常降级的 miss 仍由 RAG 重试,避免误跳。 RagContext rag = ragPipeline.retrieve(ctx, faqOutcome.completedCleanly()); - // 记录 RAG 检索日志到 rag_hit_log 表(供知识库分析看板使用) - if (!rag.faqHit() && rag.documents() != null && !rag.documents().isEmpty()) { - String searchMode = ctx.rewriteStrategy() != null ? ctx.rewriteStrategy() : "VECTOR"; - for (Document doc : rag.documents()) { - String docIdStr = String.valueOf(doc.getMetadata().getOrDefault("documentId", "")); - Long documentId = null; - try { if (!docIdStr.isEmpty()) documentId = Long.parseLong(docIdStr); } catch (NumberFormatException ignored) { } - String title = String.valueOf(doc.getMetadata().getOrDefault("title", "")); - String score = String.valueOf(doc.getMetadata().getOrDefault("score", "")); - ragHitLogService.recordHit(ctx.chatId(), ctx.message(), documentId, title, score, searchMode); - } - } else if (!rag.faqHit()) { - String searchMode = ctx.rewriteStrategy() != null ? ctx.rewriteStrategy() : "VECTOR"; - ragHitLogService.recordMiss(ctx.chatId(), ctx.message(), searchMode); - } if (rag.faqHit()) { return new ChatRequest(ctx, ctx.message(), baseSystem, rag.faqAnswer(), globalPrompt, null, null, "FAQ", null, null, rag.faqMatchResult()); @@ -129,44 +97,9 @@ public class ChatPipeline { rag.searchMode(), rag.documents(), null); } - /** - * 意图路由:先用寒暄词列表做快速路径,未命中再调 IntentRouter 做 LLM 分类。 - * 异常时返回 null(调用方默认走 RAG 检索)。 - */ - private IntentRouter.IntentResult routeIntent(String message) { - // 寒暄词快速路径(零 LLM 开销) - if (isChitchat(message)) { - return new IntentRouter.IntentResult("CHITCHAT", 1.0); - } - try { - return intentRouter.route(message); - } catch (Exception e) { - log.debug("意图路由异常,沿用 RAG 检索: {}", e.getMessage()); - return null; - } - } - - /** - * 判断是否跳过 KB 检索(保留旧方法签名供 retrieveSources 等使用)。 - */ - private boolean shouldBypassRag(String message) { - if (isChitchat(message)) { - return true; - } - try { - IntentRouter.IntentResult intent = intentRouter.route(message); - return "CHITCHAT".equals(intent.getIntent()) && intent.getConfidence() >= CHITCHAT_CONFIDENCE_THRESHOLD; - } catch (Exception e) { - log.debug("意图路由异常,沿用 RAG: {}", e.getMessage()); - } - return false; - } - /** * 寒暄词快速判断:问候/感谢/告别等短消息无知识库检索意图。 - * 与原 {@code AiController.shouldBypassKnowledgeRetrieval} 逻辑一致,作为 IntentRouter 的快速路径与兜底。 - *

- * 供 {@code buildRequest} 与"引用来源"等不需 LLM 意图分类的场景共用。 + * 供 {@code buildRequest} 与独立引用检索接口共用,不调用 LLM。 */ public boolean isChitchat(String message) { if (!StringUtils.hasText(message)) { diff --git a/src/main/java/com/wok/supportbot/rag/RagPipeline.java b/src/main/java/com/wok/supportbot/rag/RagPipeline.java index bbb61ab..d62d081 100644 --- a/src/main/java/com/wok/supportbot/rag/RagPipeline.java +++ b/src/main/java/com/wok/supportbot/rag/RagPipeline.java @@ -37,7 +37,7 @@ import java.util.stream.Collectors; * 收敛原本分散在 {@code AssistantApp} 中按策略分支的检索逻辑: *