从零搭建生产级AI智能客服系统(六):意图识别与任务型对话,打造可扩展的对话架构

作者 :大洪聊AI

本章目标 :基于策略模式+工厂模式重构对话架构,实现LLM意图识别、任务型对话槽位填充,解决循环依赖,统一Prompt与流式工具,打造符合开闭原则的生产级对话系统

前置条件:第五章多轮对话100%完成,上下文持久化、RAG知识库功能全部正常


前言

上一章我们实现了多轮对话与上下文持久化,系统已经具备完整的问答能力。但随着业务功能增加,代码会出现两个致命问题:

  1. if-else 爆炸:每加一个业务场景,就要在主服务里加一堆判断分支,代码越来越臃肿,改一处容易崩全局
  2. 职责混乱:查订单、转人工、产品咨询逻辑混在一起,不符合单一职责原则,维护成本指数级上升

这一章我们用策略模式 + 工厂模式重构整个对话架构,配合大模型做意图识别,把不同业务场景拆成独立的处理器。新增业务只需要加一个类,完全不用修改原有代码,真正符合开闭原则。同时我们会解决Spring循环依赖问题,统一Prompt管理,封装流式工具类,让代码质量提升一个台阶。


一、整体架构设计

1.1 为什么用策略模式+工厂模式

痛点 解决方案 收益
大量if-else,代码臃肿 每个意图对应一个Handler策略类 代码结构清晰,职责分离
新增业务要改原有代码 工厂类自动扫描所有Handler 符合开闭原则,扩展无侵入
流式、存库逻辑重复 抽取ChatStreamHelper通用工具 减少70%重复代码
Prompt散落在各处 统一CustomerServicePrompt管理 修改规则只改一个地方

1.2 完整调用链路

复制代码
用户发消息
    ↓
ChatController → DoubaoAiService
    ↓
1. IntentRecognizer 识别用户意图(LLM零样本分类)
2. 保存用户消息到MySQL
3. IntentHandlerFactory 根据意图路由到对应处理器
    ↓
┌─────────┬──────────┬──────────┬──────────┐
│咨询产品 │ 查询订单 │ 转人工   │ 其他问题 │
│Handler  │ Handler  │ Handler  │ Handler  │
└─────────┴──────────┴──────────┴──────────┘
    ↓
统一调用 ChatStreamHelper(上下文组装+流式输出+消息存库)
    ↓
SSE流式返回给前端

1.3 已实现的意图与处理器

我们先实现4个核心处理器,剩余意图自动兜底,后续可无缝扩展:

处理器 对应意图 是否调用LLM 业务说明
ProductConsultHandler 咨询产品 RAG检索知识库,流式生成回答
QueryOrderHandler 查询订单 槽位填充,多轮追问订单号
TransferHumanHandler 转人工 返回固定话术与联系方式
OtherIntentHandler 其他问题 通用大模型回答,兜底所有未实现意图

⚠️ 注意:「申请退款」「投诉建议」在意图列表里,但暂不做独立处理器,会自动兜底到OtherIntentHandler,后续扩展直接加对应Handler即可,完全不用改原有代码。


二、核心架构代码实现

2.1 第一步:定义意图处理器接口

所有意图处理器都实现同一个接口,保证统一的调用规范。

新建文件:src/main/java/org/example/service/intent/IntentHandler.java

java 复制代码
package org.example.service.intent;

import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

/**
 * 意图处理器统一接口
 */
public interface IntentHandler {

    /**
     * 获取当前处理器支持的意图名称
     */
    String getSupportedIntent();

    /**
     * 处理用户请求
     * @param sessionId 会话ID
     * @param message 用户原始消息
     * @param emitter SSE流式发射器
     */
    void handle(String sessionId, String message, SseEmitter emitter);
}

2.2 第二步:实现意图处理器工厂

工厂类负责自动收集所有处理器,根据意图名称路由,彻底消灭if-else。

新建文件:src/main/java/org/example/service/intent/IntentHandlerFactory.java

java 复制代码
package org.example.service.intent;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 意图处理器工厂:自动收集所有Handler,按意图路由
 */
@Component
public class IntentHandlerFactory {

    private final Map<String, IntentHandler> handlerMap = new HashMap<>();

    /**
     * Spring自动注入所有实现了IntentHandler接口的Bean
     */
    @Autowired
    public IntentHandlerFactory(List<IntentHandler> handlers) {
        for (IntentHandler handler : handlers) {
            handlerMap.put(handler.getSupportedIntent(), handler);
        }
    }

    /**
     * 根据意图获取处理器,未知意图自动兜底到「其他问题」
     */
    public IntentHandler getHandler(String intent) {
        return handlerMap.getOrDefault(intent, handlerMap.get("其他问题"));
    }
}

2.3 第三步:实现LLM意图识别器

基于豆包大模型的零样本能力,不需要任何训练数据,就能精准分类用户意图。

新建文件:src/main/java/org/example/util/IntentRecognizer.java

java 复制代码
package org.example.util;

import dev.langchain4j.model.chat.ChatLanguageModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;

/**
 * 大模型意图识别器
 */
@Component
public class IntentRecognizer {

    @Autowired
    @Qualifier("doubaoChatModel")
    private ChatLanguageModel chatModel;

    /**
     * 预定义6类客服常见意图
     */
    private static final List<String> INTENTS = Arrays.asList(
            "咨询产品",
            "查询订单",
            "申请退款",
            "投诉建议",
            "转人工",
            "其他问题"
    );

    /**
     * 识别用户消息对应的意图
     */
    public String recognize(String message) {
        StringBuilder prompt = new StringBuilder();
        prompt.append("你是专业的客服意图识别助手,请严格从下面的意图列表中选择最匹配的一项。\n");
        prompt.append("只返回意图名称本身,不要加任何解释、标点、序号。\n\n");
        prompt.append("可选意图列表:\n");
        prompt.append(String.join("\n", INTENTS)).append("\n\n");
        prompt.append("用户消息:").append(message);

        String result = chatModel.generate(prompt.toString()).trim();

        // 兜底:不在预定义列表里就归为其他问题
        if (!INTENTS.contains(result)) {
            return "其他问题";
        }
        return result;
    }
}

三、实现4个核心意图处理器

3.1 转人工处理器(固定话术,最简实现)

新建文件:src/main/java/org/example/service/intent/TransferHumanHandler.java

java 复制代码
package org.example.service.intent;

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.IOException;
import org.springframework.http.MediaType;

/**
 * 转人工意图处理器
 */
@Component
public class TransferHumanHandler implements IntentHandler {

    @Override
    public String getSupportedIntent() {
        return "转人工";
    }

    @Override
    public void handle(String sessionId, String message, SseEmitter emitter) {
        try {
            StringBuilder reply = new StringBuilder();
            reply.append("好的,正在为您转接人工客服,请稍候。\n\n");
            reply.append("人工客服工作时间:周一至周五 9:00-18:00\n");
            reply.append("客服热线:400-123-4567\n");
            reply.append("您也可以留下联系方式,我们会尽快与您取得联系。");

            emitter.send(reply.toString(), MediaType.TEXT_PLAIN);
            emitter.send("[DONE]", MediaType.TEXT_PLAIN);
            emitter.complete();
        } catch (IOException e) {
            emitter.completeWithError(e);
        }
    }
}

3.2 查询订单处理器(槽位填充+多轮追问)

⚠️ 踩坑预警:这里会用到IntentHandlerFactory做意图切换,直接注入会产生Spring循环依赖,我们先用构造器注入,后面再解决。

新建文件:src/main/java/org/example/service/intent/QueryOrderHandler.java

java 复制代码
package org.example.service.intent;

import org.example.util.IntentRecognizer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 查询订单意图处理器:槽位填充 + 多轮追问
 */
@Component
public class QueryOrderHandler implements IntentHandler {

    private final IntentRecognizer intentRecognizer;
    private final IntentHandlerFactory handlerFactory;

    // 关键:@Lazy 延迟加载,解决循环依赖
    @Autowired
    public QueryOrderHandler(
            IntentRecognizer intentRecognizer,
            @Lazy IntentHandlerFactory handlerFactory
    ) {
        this.intentRecognizer = intentRecognizer;
        this.handlerFactory = handlerFactory;
    }

    // 订单号正则:10-12位字母数字组合
    private static final Pattern ORDER_NO_PATTERN = Pattern.compile("\\b[A-Za-z0-9]{10,12}\\b");

    // 每个会话的槽位状态(生产环境建议存Redis,这里用本地Map演示)
    private final Map<String, Map<String, String>> sessionSlots = new HashMap<>();

    @Override
    public String getSupportedIntent() {
        return "查询订单";
    }

    @Override
    public void handle(String sessionId, String message, SseEmitter emitter) {
        // 1. 检测用户是否切换了意图(比如查订单中途说要转人工)
        String currentIntent = intentRecognizer.recognize(message);
        if (!"查询订单".equals(currentIntent)) {
            sessionSlots.remove(sessionId);
            IntentHandler newHandler = handlerFactory.getHandler(currentIntent);
            newHandler.handle(sessionId, message, emitter);
            return;
        }

        // 2. 获取当前会话的槽位
        Map<String, String> slots = sessionSlots.getOrDefault(sessionId, new HashMap<>());

        // 3. 从用户消息中提取订单号
        String orderNo = extractOrderNo(message);
        if (orderNo != null) {
            slots.put("orderNo", orderNo.toUpperCase());
            sessionSlots.put(sessionId, slots);
        }

        // 4. 没有订单号就追问
        if (!slots.containsKey("orderNo")) {
            try {
                String reply = "请问您的订单号是多少呢?订单号一般是10-12位的字母数字组合,在订单详情页可以查到。";
                emitter.send(reply, MediaType.TEXT_PLAIN);
                emitter.send("[DONE]", MediaType.TEXT_PLAIN);
                emitter.complete();
            } catch (IOException e) {
                emitter.completeWithError(e);
            }
            return;
        }

        // 5. 有订单号,查询订单状态(实际项目对接订单系统接口)
        String orderNoToQuery = slots.get("orderNo");
        String orderStatus = mockQueryOrder(orderNoToQuery);

        try {
            emitter.send(orderStatus, MediaType.TEXT_PLAIN);
            emitter.send("[DONE]", MediaType.TEXT_PLAIN);
            emitter.complete();
        } catch (IOException e) {
            emitter.completeWithError(e);
        }

        // 6. 查询完成,清空当前槽位
        sessionSlots.remove(sessionId);
    }

    /**
     * 从文本中提取订单号
     */
    private String extractOrderNo(String message) {
        Matcher matcher = ORDER_NO_PATTERN.matcher(message);
        if (matcher.find()) {
            return matcher.group();
        }
        return null;
    }

    /**
     * 模拟查询订单状态
     */
    private String mockQueryOrder(String orderNo) {
        StringBuilder sb = new StringBuilder();
        sb.append("查询到您的订单信息:\n");
        sb.append("订单号:").append(orderNo).append("\n");

        if (orderNo.startsWith("A")) {
            sb.append("状态:已发货,运输中\n");
            sb.append("预计送达:明天18:00前\n");
            sb.append("物流单号:SF1234567890");
        } else if (orderNo.startsWith("B")) {
            sb.append("状态:已签收\n");
            sb.append("签收时间:2026-06-01 14:32");
        } else {
            sb.append("状态:未查询到相关订单\n");
            sb.append("请您核对订单号是否正确,或联系人工客服协助查询。");
        }
        return sb.toString();
    }
}
循环依赖问题说明

产生原因

  • IntentHandlerFactory 构造器注入所有 IntentHandler
  • QueryOrderHandler 又需要注入 IntentHandlerFactory 做意图切换
  • 形成闭环:Factory → QueryOrderHandler → Factory,Spring无法实例化

解决方案

QueryOrderHandler 里的 IntentHandlerFactory 加上 @Lazy 注解,Spring会在真正用到的时候才创建实例,打破循环。这是Spring官方推荐的循环依赖解决方案之一。


四、生产级代码优化

4.1 统一Prompt管理类

之前Prompt散落在各个Handler里,修改规则要改很多地方。现在统一收敛到一个工具类。

新建文件:src/main/java/org/example/utils/CustomerServicePrompt.java

java 复制代码
package org.example.utils;

/**
 * 客服系统统一Prompt管理
 */
public class CustomerServicePrompt {

    /**
     * 全局统一系统提示词,所有LLM调用共用
     */
    public static String systemPrompt() {
        StringBuilder sb = new StringBuilder();
        sb.append("你是【XX公司】的专业AI客服,必须严格遵守以下规则:\n");
        sb.append("1. 语气友好、耐心、礼貌,全程使用「您」称呼用户\n");
        sb.append("2. 回答简洁清晰、专业有条理,复杂内容用数字分点说明\n");
        sb.append("3. 只输出纯文本,不要使用markdown、代码块、表情符号\n");
        sb.append("4. 结合上下文理解用户需求,不要答非所问\n");
        sb.append("5. 绝对禁止编造信息,不知道的内容明确告知用户\n");
        return sb.toString();
    }

    /**
     * 通用问答用户Prompt(OtherIntentHandler使用)
     */
    public static String generalUserPrompt(String question) {
        return "请根据上面的客服规则,回答用户的问题:" + question;
    }

    /**
     * RAG问答用户Prompt(ProductConsultHandler使用)
     */
    public static String ragUserPrompt(String question, String relevantContent) {
        StringBuilder sb = new StringBuilder();
        sb.append("请你严格使用下面的【知识库内容】回答用户问题。\n");
        sb.append("如果知识库中没有相关信息,直接回复:「抱歉,我没有找到相关信息,请您咨询人工客服。」\n");
        sb.append("禁止编造任何知识库中没有的内容。\n\n");
        sb.append("【知识库内容】\n");
        sb.append(relevantContent).append("\n\n");
        sb.append("用户问题:").append(question);
        return sb.toString();
    }
}

4.2 封装流式输出通用工具类

每个Handler里都写一遍流式输出、上下文组装、消息存库的逻辑,重复代码太多。我们抽取成通用工具类。

新建文件:src/main/java/org/example/service/intent/ChatStreamHelper.java

java 复制代码
package org.example.service.intent;

import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.SystemMessage;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.model.StreamingResponseHandler;
import dev.langchain4j.model.chat.StreamingChatLanguageModel;
import dev.langchain4j.model.output.Response;
import org.example.service.MessageService;
import org.example.utils.CustomerServicePrompt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.IOException;
import java.util.List;

/**
 * 流式对话通用工具:统一处理上下文组装、流式输出、消息存库
 */
@Component
public class ChatStreamHelper {

    @Autowired
    private MessageService messageService;

    // 滑动窗口大小
    private static final int WINDOW_SIZE = 10;

    /**
     * 带上下文的流式生成回答
     * @param sessionId 会话ID
     * @param userPrompt 增强后的用户Prompt
     * @param streamingModel 流式大模型
     * @param emitter SSE发射器
     */
    public void streamWithContext(
            String sessionId,
            String userPrompt,
            StreamingChatLanguageModel streamingModel,
            SseEmitter emitter
    ) {
        // 1. 获取滑动窗口历史消息
        List<ChatMessage> contextMessages = messageService.buildSlidingWindowContext(sessionId, WINDOW_SIZE);

        // 2. 移除刚入库的最后一条用户原话(避免和增强Prompt重复)
        if (!contextMessages.isEmpty() && contextMessages.get(contextMessages.size() - 1) instanceof UserMessage) {
            contextMessages.remove(contextMessages.size() - 1);
        }

        // 3. 组装完整消息:系统规则 + 历史上下文 + 当前增强问题
        contextMessages.add(0, SystemMessage.from(CustomerServicePrompt.systemPrompt()));
        contextMessages.add(UserMessage.from(userPrompt));

        // 4. 拼接完整AI回复,用于最后存库
        StringBuilder fullResponse = new StringBuilder();

        // 5. 流式调用大模型
        streamingModel.generate(contextMessages, new StreamingResponseHandler<AiMessage>() {
            @Override
            public void onNext(String token) {
                try {
                    emitter.send(token, MediaType.TEXT_PLAIN);
                    fullResponse.append(token);
                } catch (IOException e) {
                    emitter.completeWithError(e);
                }
            }

            @Override
            public void onComplete(Response<AiMessage> response) {
                try {
                    emitter.send("[DONE]", MediaType.TEXT_PLAIN);
                    emitter.complete();
                    // 6. 流式结束,保存AI回复到数据库
                    messageService.addMessage(sessionId, "assistant", fullResponse.toString());
                } catch (IOException e) {
                    emitter.completeWithError(e);
                }
            }

            @Override
            public void onError(Throwable error) {
                emitter.completeWithError(error);
            }
        });
    }

    /**
     * 发送固定话术回答(自动保存到数据库)
     */
    public void sendFixedResponse(
            String sessionId,
            String response,
            SseEmitter emitter
    ) {
        try {
            emitter.send(response, MediaType.TEXT_PLAIN);
            emitter.send("[DONE]", MediaType.TEXT_PLAIN);
            emitter.complete();
            messageService.addMessage(sessionId, "assistant", response);
        } catch (IOException e) {
            emitter.completeWithError(e);
        }
    }
}

4.3 改造产品咨询处理器

ChatStreamHelper 简化代码,接入统一Prompt。

修改文件:src/main/java/org/example/service/intent/ProductConsultHandler.java

java 复制代码
package org.example.service.intent;

import dev.langchain4j.model.chat.StreamingChatLanguageModel;
import org.example.service.RagService;
import org.example.utils.CustomerServicePrompt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

/**
 * 产品咨询意图处理器:RAG知识库检索 + 流式生成
 */
@Component
public class ProductConsultHandler implements IntentHandler {

    @Autowired
    private RagService ragService;

    @Autowired
    @Qualifier("doubaoStreamingChatModel")
    private StreamingChatLanguageModel streamingModel;

    @Autowired
    private ChatStreamHelper chatStreamHelper;

    @Override
    public String getSupportedIntent() {
        return "咨询产品";
    }

    @Override
    public void handle(String sessionId, String message, SseEmitter emitter) {
        // 1. 检索知识库
        String relevantContent = ragService.retrieveRelevantContent(message);

        // 2. 无相关内容,直接拒答
        if (relevantContent.isEmpty()) {
            chatStreamHelper.sendFixedResponse(
                    sessionId,
                    "抱歉,我没有找到相关产品信息,请您咨询人工客服。",
                    emitter
            );
            return;
        }

        // 3. 有相关内容,构建RAG增强Prompt并流式生成
        String userPrompt = CustomerServicePrompt.ragUserPrompt(message, relevantContent);
        chatStreamHelper.streamWithContext(sessionId, userPrompt, streamingModel, emitter);
    }
}

4.4 改造其他问题处理器

修改文件:src/main/java/org/example/service/intent/OtherIntentHandler.java

java 复制代码
package org.example.service.intent;

import dev.langchain4j.model.chat.StreamingChatLanguageModel;
import org.example.utils.CustomerServicePrompt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

/**
 * 其他问题意图处理器:通用大模型回答,兜底所有未实现意图
 */
@Component
public class OtherIntentHandler implements IntentHandler {

    @Autowired
    @Qualifier("doubaoStreamingChatModel")
    private StreamingChatLanguageModel streamingModel;

    @Autowired
    private ChatStreamHelper chatStreamHelper;

    @Override
    public String getSupportedIntent() {
        return "其他问题";
    }

    @Override
    public void handle(String sessionId, String message, SseEmitter emitter) {
        String userPrompt = CustomerServicePrompt.generalUserPrompt(message);
        chatStreamHelper.streamWithContext(sessionId, userPrompt, streamingModel, emitter);
    }
}

💡 可以看到,接入 ChatStreamHelper 之后,每个Handler只需要关注自己的业务逻辑,代码量减少了70%以上,结构非常清晰。


五、重构主服务DoubaoAiService

现在主服务只做流程编排,不包含任何业务逻辑,职责非常单一。

修改文件:src/main/java/org/example/service/impl/DoubaoAiService.java

java 复制代码
package org.example.service.impl;

import lombok.extern.slf4j.Slf4j;
import org.example.service.AiService;
import org.example.service.MessageService;
import org.example.service.intent.IntentHandler;
import org.example.service.intent.IntentHandlerFactory;
import org.example.util.IntentRecognizer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

@Slf4j
@Service("doubaoAiService")
public class DoubaoAiService implements AiService {

    @Autowired
    private IntentRecognizer intentRecognizer;

    @Autowired
    private IntentHandlerFactory handlerFactory;

    @Autowired
    private MessageService messageService;

    @Override
    public void streamGenerate(String sessionId, String message, SseEmitter emitter) {
        // 1. 大模型识别意图
        String intent = intentRecognizer.recognize(message);
        log.info("识别到用户意图:{}", intent);

        // 2. 保存用户消息到数据库
        messageService.addMessage(sessionId, "user", message);

        // 3. 路由到对应处理器处理
        IntentHandler handler = handlerFactory.getHandler(intent);
        handler.handle(sessionId, message, emitter);
    }
}

六、前端说明

前端代码完全不需要修改。因为我们的接口地址、请求参数、返回格式都和第五章完全一致,只是后端内部做了架构重构,前端无感知。

这就是好的架构的好处:后端内部优化升级,完全不影响前端调用。


七、完整功能验证

按照下面的顺序逐一测试,确保所有功能正常:

测试1:基础聊天

  • 发送「你好」,AI正常回复,走「其他问题」意图

测试2:产品咨询 + RAG

  • 上传产品知识库文档
  • 发送「你们产品多少钱」,AI准确返回价格,走「咨询产品」意图
  • 发送「你们支持视频会议吗」,AI回复找不到相关信息,防编造生效

测试3:查询订单 + 槽位追问

  • 发送「我想查订单」,AI回复「请问您的订单号是多少?」
  • 发送「A1234567890」,AI返回订单详情
  • 验证多轮追问正常

测试4:中途切换意图

  • 发送「我要查订单」→ AI追问订单号
  • 不等回复订单号,直接发送「算了,帮我转人工」
  • AI立即回复转人工话术,说明意图切换正常

测试5:转人工

  • 发送「我要找人工」,AI返回人工客服信息,走「转人工」意图

测试6:持久化验证

  • 刷新页面,历史消息全部保留
  • 继续对话,AI能记住上下文

✅ 全部测试通过,说明意图识别架构重构完成。


八、常见问题排查

问题1:启动报错循环依赖

  • 检查 QueryOrderHandler 的构造器里,IntentHandlerFactory 是否加了 @Lazy 注解
  • 检查包扫描路径是否正确,确保所有Bean都被Spring扫描到

问题2:意图识别不准确

  • 优化 IntentRecognizer 里的Prompt,加重语气要求严格从列表选
  • 把模型 temperature 调到0.1-0.3,降低创造性
  • 增加更明确的示例,做少样本分类

问题3:固定话术没有保存到数据库

  • 检查对应Handler是不是调用了 chatStreamHelper.sendFixedResponse(),而不是自己直接emitter.send
  • 确认 ChatStreamHelper 里的存库逻辑正常

问题4:上下文不生效

  • 检查 ChatStreamHelper 里的滑动窗口大小是否合适
  • 确认请求时传了正确的 sessionId
  • 检查消息是否成功写入了数据库

九、本章总结

本章我们完成了对话架构的生产级重构:

  1. ✅ 用策略模式+工厂模式重构了对话架构,彻底消灭if-else
  2. ✅ 实现了基于LLM的零样本意图识别,支持6类客服意图
  3. ✅ 实现了4个核心意图处理器,支持槽位填充与多轮追问
  4. ✅ 解决了Spring循环依赖的经典问题
  5. ✅ 统一了Prompt管理,所有规则集中维护
  6. ✅ 封装了ChatStreamHelper通用工具,大幅减少重复代码
  7. ✅ 主服务职责单一,只做编排,符合单一职责原则
  8. ✅ 完全符合开闭原则:新增意图只加类,不改原有代码

现在系统的可扩展性和可维护性已经达到了生产级标准,后续不管加多少业务场景,都不会让代码变得混乱。


下章预告

下一章我们将进入基础优化与本地部署阶段,实现双层缓存节省API费用、全局异常统一处理、前端性能优化,最后完成前后端一体化打包,实现一键部署开箱即用。

关注我,第一时间收到更新通知!