后端实战|AI客服核心实现:Redis状态机+AI意图识别双引擎

27 阅读8分钟

后端实战|AI客服核心实现:Redis状态机+AI意图识别双引擎

大家好,今天给大家分享一个近期落地的AI客服模块核心实现——用Redis做状态机持久化,结合AI Flow做意图识别,搞定客服会话的全流程管控和用户需求精准判断。

做后端的朋友应该都知道,AI客服看似简单,实则核心痛点在于「会话状态管控」和「用户意图识别」:比如用户要查订单,怎么引导用户输入订单号?用户想转人工,怎么终止AI自动回复?如果会话中途中断,再次进入怎么恢复之前的状态?

今天就把这套企业级落地的双引擎方案拆解开,从核心设计到代码实现,干货拉满,代码可直接复用,新手也能快速上手,建议收藏备用~

先说明下:本次分享选取了原项目中「状态机引擎(Redis实现)」「意图识别引擎(Jeecg AI Flow实现)」两大核心模块,这也是AI客服的底层支撑,也是后端开发中最具复用价值的部分,其余测试、优化细节就不冗余赘述了。

一、核心架构:双引擎搞定AI客服底层逻辑

先上整体架构,不用搞复杂,核心就两个引擎,分工明确,避免耦合:

  • 状态机引擎:基于Redis实现,负责会话状态的持久化、流转控制,全程追踪对话生命周期(比如初始状态→查单状态→转人工状态);
  • 意图识别引擎:基于AI Flow实现,负责识别用户输入的真实需求(比如“查订单”“转人工”“随便聊聊”),输出结构化结果驱动状态机流转。

这里要避个坑:很多人做AI客服会把「状态管控」和「意图识别」混在一起写,后期维护起来巨麻烦,建议按模块拆分,状态机管流程,意图识别管决策,清晰又好扩展。

二、实战实现:Redis状态机引擎(核心代码可直接复用)

状态机是整个客服会话的“大脑”,核心是用Redis的Hash结构持久化会话状态和变量,以conversationId作为会话唯一标识,支持状态读取、初始化、流转和过期管理。

1. 先定义核心常量(KfConsts)

统一管理Redis Key前缀、字段名、过期时间,避免硬编码,后期修改更方便:

public class KfConsts {
    // Redis状态Key前缀(格式:kf:state:会话ID)
    public static final String KF_STATE_PREFIX = "kf:state:";
    // Hash字段:当前会话状态
    public static final String FIELD_CURRENT_STATE = "current_state";
    // Hash字段:会话变量(存储订单号、意图等,JSON格式)
    public static final String FIELD_VARIABLES = "variables";
    // 会话过期时间(30分钟,闲置自动过期,读写刷新TTL)
    public static final long STATE_TTL_MINUTES = 30L;
    // 意图识别AI Flow ID(Jeecg AI Flow的唯一标识)
    public static final String INTENT_FLOW_ID = "2042592514488737794";
}

2. 定义会话状态枚举(KfStateNode)

覆盖客服全流程的4种核心状态,避免状态值混乱,同时提供状态匹配方法:

@AllArgsConstructor
@Getter
public enum KfStateNode {
    // 初始状态:新会话必须先执行意图识别
    INIT,
    // 查单状态:等待用户输入订单号
    ASK_ORDER,
    // 普通对话状态:直接执行AI回复
    CHAT,
    // 转人工状态:终止AI自动回复
    HUMAN_TRANSFER;

    // 根据状态名称匹配枚举(忽略大小写,避免前端传值大小写问题)
    public static KfStateNode findByType(String type){
        for (KfStateNode node : KfStateNode.values()) {
            if(node.name().equalsIgnoreCase(type)){
                return node;
            }
        }
        return null;
    }
}

3. 封装会话状态实体(KfSessionState)

作为状态机的核心数据载体,封装当前状态和会话变量,方便Redis读写和业务逻辑调用:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class KfSessionState {
    // 当前会话状态(枚举类型,避免非法状态)
    private KfStateNode currentState;
    // 会话变量(存储订单号、用户意图、技能标签等,JSON格式)
    private JSONObject variables;
}

4. 状态机核心服务(IntentDecisionManager)

最核心的实现,负责会话状态的加载/初始化、持久化、TTL刷新,也是业务层调用的入口,重点看两个方法:loadOrInitState(加载/初始化状态)和persistState(持久化状态):

@Service
@Slf4j
public class IntentDecisionManager {
    @Autowired
    private RedisTemplate redisTemplate;

    // 拼接Redis状态Key(前缀+会话ID)
    private String stateKey(String sessionId) {
        return KfConsts.KF_STATE_PREFIX + sessionId;
    }

    // 加载或初始化会话状态(核心方法)
    public KfSessionState loadOrInitState(String sessionId) {
        String key = stateKey(sessionId);
        // 从Redis Hash中获取当前状态和变量
        Object currentState = redisTemplate.opsForHash().get(key, KfConsts.FIELD_CURRENT_STATE);
        Object variables = redisTemplate.opsForHash().get(key, KfConsts.FIELD_VARIABLES);

        // 无状态(新会话),初始化状态为INIT
        if (currentState == null) {
            KfSessionState initState = new KfSessionState(KfStateNode.INIT, new JSONObject());
            // 持久化初始状态
            persistState(sessionId, initState);
            return initState;
        }

        // 解析已有状态,刷新TTL(避免会话过期)
        KfStateNode state = KfStateNode.valueOf(String.valueOf(currentState));
        JSONObject varJson = variables == null ? new JSONObject() : JSONObject.parseObject(String.valueOf(variables));
        redisTemplate.expire(key, KfConsts.STATE_TTL_MINUTES, TimeUnit.MINUTES);

        return new KfSessionState(state, varJson);
    }

    // 持久化会话状态到Redis(核心方法)
    public void persistState(String sessionId, KfSessionState state) {
        String key = stateKey(sessionId);
        // 存储当前状态(枚举名称)和变量(JSON字符串)
        redisTemplate.opsForHash().put(key, KfConsts.FIELD_CURRENT_STATE, state.getCurrentState().name());
        redisTemplate.opsForHash().put(key, KfConsts.FIELD_VARIABLES, state.getVariables().toJSONString());
        // 刷新过期时间
        redisTemplate.expire(key, KfConsts.STATE_TTL_MINUTES, TimeUnit.MINUTES);
    }
}

这里有个实战技巧:每次读写Redis状态时,一定要刷新TTL,避免会话在用户交互过程中过期;另外,新会话默认初始化为INIT状态,确保每次新对话都先执行意图识别,避免流程混乱。

三、实战实现:Jeecg AI Flow意图识别引擎

意图识别是驱动状态机流转的关键,核心是通过Jeecg内置的AI Flow编排意图分类流程,输入用户消息,输出结构化的意图结果(意图、技能标签、置信度)。

1. 封装意图识别结果实体(IntentDecision)

统一封装AI Flow的返回结果,方便业务层使用,避免直接操作JSON:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class IntentDecision {
    // 用户意图(transfer_human=转人工,ask_order=查订单,chat=普通对话)
    private String intent;
    // 业务技能标签(售后/售前/技术支持,用于后续业务分流)
    private String skill;
    // 识别置信度(0-1,可根据实际场景调整阈值)
    private BigDecimal confidence;
}

2. 意图识别核心方法(延续IntentDecisionManager)

调用Jeecg AI Flow接口,执行意图分类,解析返回结果,同时处理异常情况(比如AI Flow返回为空、格式异常):

@Service
@Slf4j
public class IntentDecisionManager {
    // 省略上面的Redis状态机相关方法...

    @Autowired
    private ISysBaseAPI sysBaseApi;

    // 执行AI Flow意图识别(核心方法)
    public IntentDecision runIntentFlow(String userMessage) {
        // 1. 封装AI Flow入参(用户消息作为question传入)
        Map<String, Object> inputs = new HashMap<>();
        inputs.put("question", userMessage);

        // 2. 构建AI Flow调用参数(指定Flow ID,非流式调用)
        AiragFlowDTO dto = AiragFlowDTO.builder()
                .flowId(KfConsts.INTENT_FLOW_ID)
                .inputParams(inputs)
                .isStream(false)
                .build();

        // 3. 调用Jeecg AI Flow接口,获取识别结果
        Object result = sysBaseApi.runAiragFlow(dto);
        JSONObject json = normalizeFlowResult(result);

        // 4. 解析意图结果,校验必填字段
        String intent = json.getString("intent");
        String skill = json.getString("skill");
        if (oConvertUtils.isEmpty(intent)) {
            throw new JeecgBootException("意图识别结果缺少intent字段");
        }

        // 5. 返回结构化结果(置信度可根据AI Flow实际返回调整,这里默认0.92)
        return new IntentDecision(intent, skill, new BigDecimal(0.92));
    }

    // 标准化AI Flow返回结果(处理不同格式的返回,避免空指针)
    private JSONObject normalizeFlowResult(Object result) {
        if (result == null) {
            throw new JeecgBootException("意图识别返回为空");
        }
        // 处理JSON对象、字符串、Map三种常见返回格式
        if (result instanceof JSONObject) {
            return (JSONObject) result;
        }
        if (result instanceof String) {
            return JSONObject.parseObject((String) result);
        }
        if (result instanceof Map) {
            JSONObject json = new JSONObject();
            json.putAll((Map<? extends String, ?>) result);
            Object output = json.get("output");
            if (output instanceof String) {
                return JSONObject.parseObject((String) output);
            }
            if (output instanceof Map) {
                JSONObject out = new JSONObject();
                out.putAll((Map<? extends String, ?>) output);
                return out;
            }
            return json;
        }
        // 兜底处理:转为JSON字符串再解析
        return JSONObject.parseObject(JSONObject.toJSONString(result));
    }

    // 额外补充:查单状态专用——提取用户消息中的订单号(正则匹配)
    public String extractOrderNo(String text) {
        if (oConvertUtils.isEmpty(text)) {
            return null;
        }
        // 匹配8-32位字母、数字、横线组成的订单号(可根据实际订单格式调整正则)
        Matcher matcher = Pattern.compile("([A-Za-z0-9\-]{8,32})").matcher(text);
        return matcher.find() ? matcher.group(1) : null;
    }
}

实战避坑:AI Flow的返回格式可能不统一(比如有时是JSON对象,有时是字符串),一定要做标准化处理,否则容易出现空指针;另外,订单号提取的正则的可以根据自己的业务场景调整,避免漏提或误提。

四、核心流程串联(极简版)

最后给大家串一下核心流程,让大家明白两个引擎是怎么配合工作的:

  1. 用户发送消息,请求进入/airag/chat/send接口;
  2. 接口调用loadOrInitState方法,加载或初始化会话状态(新会话为INIT);
  3. 如果是INIT状态,调用runIntentFlow方法识别用户意图;
  4. 根据意图更新会话状态(比如“ask_order”→ASK_ORDER状态,“transfer_human”→HUMAN_TRANSFER状态);
  5. 如果是ASK_ORDER状态,调用extractOrderNo提取订单号,存储到会话变量;
  6. 如果是HUMAN_TRANSFER状态,终止AI回复,提示用户等待人工客服。

五、实战心得&后续优化建议

这套方案已经在项目中落地,总结两个核心心得,帮大家少走弯路:

  • 状态机一定要持久化:用Redis比本地缓存更可靠,避免服务重启后会话状态丢失;
  • 意图识别只执行一次:仅在INIT状态执行,避免重复调用AI Flow,降低成本。

后续可以优化的点(留给大家拓展):

  • AI Flow超时兜底:超时或返回异常时,默认降级为CHAT状态,不中断会话;
  • 转人工联动:状态转为HUMAN_TRANSFER后,通过WebSocket通知人工坐席;
  • 日志审计:记录会话ID、意图、状态,方便问题排查。

今天的分享就到这里,核心代码都已经贴出来了,大家可以直接复制到项目中修改使用。这套双引擎方案不仅适用于AI客服,还可以迁移到其他需要会话状态管控的场景(比如智能问答、对话机器人)。

如果大家有疑问,或者有更好的实现方式,欢迎在评论区交流~ 关注我,后续持续分享后端AI应用开发干货,一起提升技术,少走弯路!