Spring AI 1.1.x 智能客服FunctionCall实战手册

0 阅读13分钟

一、技术前置基础

1.1 FunctionCall工具调用核心价值

大模型仅具备文本对话能力,无法读取业务数据、调用后端接口、获取实时信息。Spring AI 1.1.x内置标准化Function Calling机制,通过简单注解即可让大模型主动调用Java业务方法,无需手动编写工具配置、无需手动解析参数,快速实现AI联动业务场景。

1.2 Spring AI 1.1.x核心优势

原生支持对话记忆、自动工具注册、智能参数抽取、SSE流式输出,屏蔽通义千问、GPT、文心一言等各大模型厂商接口差异,一套代码可适配多类大模型,降低项目适配与迭代成本。

1.3 项目整体架构

前端流式交互页面 → Spring AI 1.1.x(对话管理、工具调度、流式响应、风控限流、状态管控)→ Java业务服务(订单物流查询、人工客服转接、全场景售后处理)

1.4 核心项目依赖

<!-- Spring AI 1.1.x 核心依赖 -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
    <version>1.1.0</version>
</dependency>
<!-- Redis分布式会话依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis&lt;/artifactId&gt;
&lt;/dependency&gt;

二、多轮连贯对话(生产稳定版)

2.1 功能概述

多轮连贯对话可自动留存用户历史聊天上下文,连续提问时结合过往内容响应,避免语境割裂,还原自然交互体验。Spring AI 1.1.x提供原生ChatMemory对话记忆能力,替代传统手动Map上下文维护方式,生产环境可无缝替换为Redis持久化会话,支持集群部署、服务重启不丢失会话。

2.2 实现原理

1. 注入RedisChatMemory实现分布式会话持久化,适配集群部署场景;

2. 通过conversationId会话ID区分独立用户,精准隔离个人对话数据;

3. 每次请求自动拼接历史上下文与当前提问,统一提交大模型处理;

4. 问答结束自动存储对话记录,无需手动编码维护;

5. 内置Token阈值裁剪机制,避免上下文冗余、内存溢出;

6. 配置会话超时自动销毁,自动清理无效会话、释放Redis资源。

2.3 核心落地代码

2.3.1 分布式对话记忆配置

import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.redis.RedisChatMemory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;

/**
 * Spring AI对话记忆生产配置
 * 解决单机会话丢失问题,支持集群部署、会话自动过期、资源自动释放
 */
@Configuration
public class AiChatConfig {

    /**
     * 注册Redis分布式对话记忆
     * 会话30分钟无操作自动过期,清理无效会话数据
     */
    @Bean
    public ChatMemory chatMemory(RedisTemplate<String,Object> redisTemplate) {
        // 过期时间1800秒=30分钟
        return new RedisChatMemory(redisTemplate, 1800L);
    }
}

2.3.2 多轮对话接口(含参数校验、防重)

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.validation.annotation.Validated;

@RestController
@RequestMapping("/api/chat")
public class AiChatController {

    // Spring AI核心对话客户端,自动装配
    private final ChatClient chatClient;
    // 注入分布式对话记忆组件
    private final ChatMemory chatMemory;

    public AiChatController(ChatClient.Builder chatClientBuilder, ChatMemory chatMemory) {
        this.chatClient = chatClientBuilder.build();
        this.chatMemory = chatMemory;
    }

    /**
     * 多轮连贯对话接口(生产版)
     * 自带参数校验、异常兜底、上下文连贯能力
     */
    @PostMapping("/normal")
    public String normalChat(@Validated @RequestBody ChatParam param) {
        // 绑定会话ID,自动关联历史上下文,实现多轮连贯对话
        return chatClient.prompt()
                .user(param.getMessage())
                .chatMemory(chatMemory, param.getConversationId())
                .call()
                .content();
    }
}

2.3.3 标准化入参实体(含校验规则)

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import java.io.Serializable;

/**
 * 对话入参实体(生产规范版)
 * 强参数校验,拦截脏数据、非法入参
 */
public class ChatParam implements Serializable {
    private static final long serialVersionUID = 1L;

    @NotBlank(message = "会话ID不能为空")
    @Size(min = 16, max = 64, message = "会话ID格式非法")
    private String conversationId;

    @NotBlank(message = "提问内容不能为空")
    @Size(max = 500, message = "提问内容过长,请精简后重试")
    private String message;

    public ChatParam() {}

    public ChatParam(String conversationId, String message) {
        this.conversationId = conversationId;
        this.message = message;
    }

    // getter、setter方法
    public String getConversationId() {
        return conversationId;
    }

    public void setConversationId(String conversationId) {
        this.conversationId = conversationId;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

2.4 运行效果

1. 用户首次提问模糊问题(如“帮我查订单”),AI自动识别缺失参数,主动引导用户补充信息;

2. 同一会话ID下,用户补充信息后,系统自动关联历史语境,连贯完成响应;

3. 全程自动维护上下文,无需手动拼接数据,对话无断层;

4. 服务重启、集群节点切换会话不丢失,无效会话自动清理,无资源堆积;

5. 自动拦截非法参数、超长文本,规避脏数据进入业务逻辑。

2.5 高频面试题解析

题目1:Spring AI 1.1.x如何实现多轮对话?对比手动维护上下文有什么优势?

参考答案:通过框架内置ChatMemory接口实现多轮对话,依靠conversationId隔离不同用户会话,自动完成上下文的存储、拼接和裁剪。相比手动维护Map集合,无需手动处理上下文逻辑,自带Token溢出保护,适配多类大模型,支持分布式集群改造,代码更简洁、稳定性更强。

题目2:InMemoryChatMemory适用场景是什么?生产环境如何优化?

参考答案:仅适用于单机测试、小型单机演示项目,部署简单、开箱即用。生产集群环境必须替换为RedisChatMemory,实现会话持久化、集群共享,解决服务重启、节点切换导致的会话丢失问题,同时支持自动过期清理,减少资源占用。

三、FunctionCall工具调用(全场景修复+多工具组合)

3.1 功能概述

Spring AI 1.1.x支持注解式工具注册,摒弃传统手动编写JSON工具描述的繁琐操作,实现工具轻量化开发。框架可自动扫描工具、解析用户提问、抽取业务参数、回调Java方法,全程自动化完成AI与业务接口的联动,同时支持多工具组合调用,适配用户复合提问场景。

3.2 执行流程

1. 自定义工具入参实体,配置参数强校验规则,规范业务请求;

2. 通过@FunctionCallback注解标记业务工具,标注工具功能说明;

3. 项目启动自动扫描注解,生成工具调用元数据,完成工具注册;

4. 用户发起业务提问,大模型语义解析后匹配对应工具、提取参数;

5. 框架自动校验参数、通过反射执行对应Java业务方法;

6. 将结构化业务结果回传大模型,整理为自然语言回复用户;

7. 配套限流、重试、多工具组合能力,解决原生循环调用、调用失败等生产问题。

3.3 核心落地代码

3.3.1 工具统一入参实体(强校验)

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;

/**
 * 售后业务工具统一入参
 * 正则+长度双重校验,拦截非法订单号参数
 */
public class OrderToolParam {
    @NotBlank(message = "订单号不能为空")
    @Size(min = 9, max = 9, message = "订单号必须为9位数字")
    @Pattern(regexp = "^[0-9]{9}$", message = "订单号格式错误,仅支持纯数字")
    private String orderNo;

    // getter、setter方法
    public String getOrderNo() {
        return orderNo;
    }

    public void setOrderNo(String orderNo) {
        this.orderNo = orderNo;
    }
}

3.3.2 全场景售后业务工具类

import org.springframework.ai.tool.annotation.FunctionCallback;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;

/**
 * 电商全场景售后工具类
 * 覆盖订单、物流、退款、改址、投诉五大核心场景
 * 支持多工具组合调用
 */
@Component
public class OrderLogisticsTool {

    /**
     * 订单详情查询
     * 适配用户查询订单状态、商品信息、价格、下单时间场景
     */
    @FunctionCallback(description = "根据订单号查询用户电商订单详情,可查询订单商品、订单状态、订单价格、下单时间")
    public Map<String,Object> queryOrder(OrderToolParam param) {
        Map<String,Object> result = new HashMap<>();
        String orderNo = param.getOrderNo();
        // 模拟数据库订单查询
        result.put("code", 200);
        result.put("orderNo", orderNo);
        result.put("goodsName", "夏季纯棉短袖T恤");
        result.put("orderStatus", "已发货");
        result.put("orderPrice", "89.9元");
        result.put("createTime", "2026-08-03 14:22:10");
        return result;
    }

    /**
     * 物流轨迹查询
     * 适配用户查询物流状态、运输进度、快递公司场景
     */
    @FunctionCallback(description = "根据订单号查询商品物流轨迹、快递公司、快递单号、运输状态")
    public Map<String,Object> queryLogistics(OrderToolParam param) {
        Map<String,Object> result = new HashMap<>();
        String orderNo = param.getOrderNo();
        // 模拟物流数据查询
        result.put("code", 200);
        result.put("orderNo", orderNo);
        result.put("expressCompany", "中通快递");
        result.put("expressNo", "ZT9876543210");
        result.put("logisticsStatus", "运输中");
        result.put("logisticsDetail", "2026-08-04 09:30 已离开杭州转运中心,发往北京");
        return result;
    }

    /**
     * 退款状态查询
     */
    @FunctionCallback(description = "根据订单号查询退款状态、退款金额、审核进度、退款到账时间")
    public Map<String,Object> queryRefund(OrderToolParam param) {
        Map<String,Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("orderNo", param.getOrderNo());
        result.put("refundStatus", "未申请退款");
        result.put("refundAmount", "0元");
        result.put("auditProgress", "无退款记录");
        return result;
    }

    /**
     * 收货地址修改查询
     */
    @FunctionCallback(description = "根据订单号查询是否可修改收货地址、最新收货地址信息")
    public Map<String,Object> queryAddressModify(OrderToolParam param) {
        Map<String,Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("orderNo", param.getOrderNo());
        result.put("canModify", "是");
        result.put("currentAddress", "北京市朝阳区XX街道XX小区");
        result.put("modifyDeadline", "发货前可免费修改");
        return result;
    }

    /**
     * 订单投诉进度查询
     */
    @FunctionCallback(description = "根据订单号查询订单投诉记录、投诉状态、处理进度")
    public Map<String,Object> queryComplaint(OrderToolParam param) {
        Map<String,Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("orderNo", param.getOrderNo());
        result.put("complaintStatus", "无投诉记录");
        result.put("processProgress", "正常履约中");
        return result;
    }
}

3.3.3 工具调用核心接口(含限流、多工具适配)

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.validation.annotation.Validated;

@RestController
@RequestMapping("/api/chat")
public class AiFunctionChatController {

    private final ChatClient chatClient;
    private final ChatMemory chatMemory;
    private final OrderLogisticsTool orderLogisticsTool;
    private final ToolLimitAdvisor toolLimitAdvisor;

    public AiFunctionChatController(ChatClient.Builder chatClientBuilder,
                                    ChatMemory chatMemory,
                                    OrderLogisticsTool orderLogisticsTool,
                                    ToolLimitAdvisor toolLimitAdvisor) {
        this.chatClient = chatClientBuilder.build();
        this.chatMemory = chatMemory;
        this.orderLogisticsTool = orderLogisticsTool;
        this.toolLimitAdvisor = toolLimitAdvisor;
    }

    /**
     * 工具调用智能对话接口
     * 整合多轮上下文、多工具组合、限流防循环、异常兜底能力
     */
    @PostMapping("/function")
    public String functionChat(@Validated @RequestBody ChatParam param) {
        return chatClient.prompt()
                .user(param.getMessage())
                // 绑定多轮对话上下文
                .chatMemory(chatMemory, param.getConversationId())
                // 一次性注册全场景工具,支持复合提问多工具联动
                .tools(orderLogisticsTool)
                // 接入限流拦截器,杜绝循环调用、超额扣费
                .advisors(toolLimitAdvisor)
                .call()
                .content();
    }
}

3.4 运行效果

1. 单一业务提问可精准匹配对应工具,自动校验参数、查询数据并整理回复;

2. 支持复合提问,可自动串行调用多个工具,汇总订单、物流、退款等多维度数据;

3. 参数格式错误、缺失时,AI自动追问用户补充合规信息;

4. 工具调用次数超限自动终止,杜绝死循环扣费、服务卡顿问题;

5. 网络轻微波动场景自动重试,提升接口可用性。

3.5 高频面试题解析

题目1:Spring AI 1.1.x注解式FunctionCall完整执行流程是什么?

参考答案:项目启动时框架扫描@FunctionCallback注解,自动生成工具调用元数据;用户提问后,系统将工具描述、历史上下文、用户指令同步提交大模型;大模型判断是否需要调用工具,返回结构化参数;框架自动解析、校验参数,反射执行对应Java业务方法;最终将工具执行结果回传大模型,生成自然语言回复。

题目2:注解式FunctionCall对比传统手动JSON配置工具的优势?

参考答案:传统方式需要手动编写工具JSON描述、手动解析参数、手动触发接口调用,代码冗余、维护成本高。注解式实现全程自动化,零手动配置、自动抽参、自动调用、自带异常兜底,代码简洁、适配性强,是企业主流生产方案。

题目3:生产环境如何解决FunctionCall循环调用、超额扣费问题?

参考答案:1. 自定义Advisor拦截器,限制单轮对话最大工具调用次数,从根源杜绝死循环;2. 工具返回结果添加终止提示,引导大模型停止重复调用;3. 增加用户、IP维度限流,拦截恶意高频请求;4. 配置Token限额,管控整体调用成本。

四、智能人工客服转接(BUG彻底修复版)

4.1 功能概述

人工转接是客服系统的核心兜底能力,针对高风险售后、参数异常、查询失败、非业务提问等AI无法处理的场景,自动流转人工坐席接待,同时锁定会话状态,避免AI与人工响应冲突,保障用户问题有效解决。

4.2 核心修复原理

1. 制定标准化转接规则,覆盖主动转接、业务异常、高风险场景、无效提问四类场景;

2. 修复原生漏洞:新增工具执行结果二次校验,解决调用异常、查询失败无法转接人工的问题;

3. 增加会话状态锁定机制,转接人工后禁止AI自动回复,杜绝状态混乱;

4. 保留完整会话上下文,人工坐席可查看历史聊天记录,实现无缝接管;

5. 新增会话复位接口,人工接待结束后可恢复AI自动对话。

4.3 核心落地代码

4.3.1 人工转接规则工具类

import org.springframework.stereotype.Component;

@Component
public class HumanTransferUtil {

    /**
     * 前置校验是否需要转接人工客服
     * 双维度校验:用户提问内容 + 工具执行结果
     */
    public boolean needTransferHuman(String userInput, String toolResult) {
        // 1. 用户主动申请人工服务
        if (userInput.contains("人工") || userInput.contains("客服")
                || userInput.contains("转接人工") || userInput.contains("找客服")) {
            return true;
        }
        // 2. 工具调用异常、数据查询失败、参数错误场景,强制转接人工
        if (toolResult.contains("格式错误") || toolResult.contains("查询失败")
                || toolResult.contains("无对应数据") || toolResult.contains("400")) {
            return true;
        }
        // 3. 高风险售后场景,规避AI处理纠纷风险
        if (userInput.contains("投诉") || userInput.contains("退款")
                || userInput.contains("纠纷") || userInput.contains("赔付")
                || userInput.contains("差评") || userInput.contains("维权")) {
            return true;
        }
        // 4. 非平台业务提问,转接人工处理
        return !isBusinessQuestion(userInput);
    }

    /**
     * 判断是否为平台核心业务提问
     */
    private boolean isBusinessQuestion(String question) {
        return question.contains("订单") || question.contains("物流")
                || question.contains("发货") || question.contains("收货")
                || question.contains("价格") || question.contains("商品")
                || question.contains("退款") || question.contains("地址")
                || question.contains("投诉");
    }
}

4.3.2 对话+人工转接整合接口

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@RestController
@RequestMapping("/api/chat")
public class AiFullChatController {

    // 会话状态缓存:true=人工接待中 false=AI自动接待
    private final Map<String,Boolean> sessionStatus = new ConcurrentHashMap<>();

    private final ChatClient chatClient;
    private final ChatMemory chatMemory;
    private final OrderLogisticsTool orderLogisticsTool;
    private final ToolLimitAdvisor toolLimitAdvisor;

    @Autowired
    private HumanTransferUtil humanTransferUtil;

    public AiFullChatController(ChatClient.Builder chatClientBuilder,
                                ChatMemory chatMemory,
                                OrderLogisticsTool orderLogisticsTool,
                                ToolLimitAdvisor toolLimitAdvisor) {
        this.chatClient = chatClientBuilder.build();
        this.chatMemory = chatMemory;
        this.orderLogisticsTool = orderLogisticsTool;
        this.toolLimitAdvisor = toolLimitAdvisor;
    }

    /**
     * 完整AI对话+人工转接整合接口
     * 修复状态混乱、异常无法转接等原生BUG
     */
    @PostMapping("/full")
    public Map<String,String> fullChat(@RequestBody ChatParam param) {
        String userMsg = param.getMessage();
        String conversationId = param.getConversationId();

        // 人工接待中,直接拦截AI请求,避免冲突
        if (sessionStatus.getOrDefault(conversationId, false)) {
            Map<String,String> res = new HashMap<>();
            res.put("type", "human");
            res.put("reply", "当前正在人工客服接待中,请稍后重试~");
            return res;
        }

        // 执行AI工具调用与对话逻辑
        String aiReply = chatClient.prompt()
                .user(userMsg)
                .chatMemory(chatMemory, conversationId)
                .tools(orderLogisticsTool)
                .advisors(toolLimitAdvisor)
                .call()
                .content();

        // 二次校验,判断是否需要转接人工
        if (humanTransferUtil.needTransferHuman(userMsg, aiReply)) {
            // 锁定会话状态
            sessionStatus.put(conversationId, true);
            Map<String,String> res = new HashMap<>();
            res.put("type", "transfer");
            res.put("reply", "已为您转接人工客服,正在排队等待,请稍候~");
            return res;
        }

        Map<String,String> res = new HashMap<>();
        res.put("type", "ai");
        res.put("reply", aiReply);
        return res;
    }

    /**
     * 人工会话复位接口
     * 人工接待结束后,恢复AI自动对话
     */
    @PostMapping("/human/reset")
    public Map<String,String> resetSession(@RequestParam String conversationId) {
        sessionStatus.remove(conversationId);
        Map<String,String> res = new HashMap<String,String>();
        res.put("type", "success");
        res.put("reply", "已退出人工接待,恢复AI智能咨询");
        return res;
    }
}

4.4 运行效果

1. 高风险、无效、非业务提问前置拦截,减少无用大模型调用,节省成本;

2. 工具调用异常、数据查询失败时自动转接人工,补齐原生逻辑漏洞;

3. 人工接待后锁定会话,彻底解决AI、人工同时响应的混乱问题;

4. 支持会话手动复位,灵活切换对话模式,适配实际客服业务场景;

5. 全程保留完整对话记录,人工坐席可无缝接管用户咨询。

4.5 高频面试题解析

题目:人工转接逻辑为什么要做前置拦截+工具结果二次校验?

参考答案:前置拦截可提前过滤无效、高风险请求,避免不必要的大模型调用,降低服务成本;二次校验工具结果,可覆盖工具调用失败、参数错误、数据为空等业务异常场景,补齐原生逻辑短板。同时搭配会话状态锁定,杜绝AI与人工响应冲突,保障生产环境交互稳定规范。

五、SSE流式打字机输出(生产优化版)

5.1 功能概述

Spring AI 1.1.x原生支持SSE流式输出,无需手动封装连接、分片推送逻辑,可实现前端打字机逐字渲染效果。相比手动实现SSE,框架原生方案更稳定,无线程冗余、内存泄漏问题,同时适配生产环境防抖、重连、资源回收等核心需求。

5.2 实现原理

1. 调用ChatClient.stream()开启流式响应模式;

2. 大模型分段生成回复内容,框架自动封装为标准SSE分片数据;

3. 后端持续向前端推送文本片段,无需等待完整回复生成;

4. 前端通过EventSource监听流数据,逐字渲染实现打字机效果;

5. 回复推送完成后自动关闭长连接,释放服务资源;

6. 叠加防抖、断流重连、手动终止、异常兜底等生产优化。

5.3 核心落地代码

5.3.1 后端流式接口

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

@RestController
@RequestMapping("/api/chat")
public class AiStreamChatController {

    private final ChatClient chatClient;
    private final ChatMemory chatMemory;
    private final OrderLogisticsTool orderLogisticsTool;
    private final ToolLimitAdvisor toolLimitAdvisor;

    public AiStreamChatController(ChatClient.Builder chatClientBuilder,
                                  ChatMemory chatMemory,
                                  OrderLogisticsTool orderLogisticsTool,
                                  ToolLimitAdvisor toolLimitAdvisor) {
        this.chatClient = chatClientBuilder.build();
        this.chatMemory = chatMemory;
        this.orderLogisticsTool = orderLogisticsTool;
        this.toolLimitAdvisor = toolLimitAdvisor;
    }

    /**
     * 生产级流式对话接口
     * 整合限流、多工具调用、分布式会话、异常兜底能力
     */
    @GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> streamChat(@RequestParam String conversationId,
                                   @RequestParam String message) {
        return chatClient.prompt()
                .user(message)
                .chatMemory(chatMemory, conversationId)
                .tools(orderLogisticsTool)
                .advisors(toolLimitAdvisor)
                .stream()
                .content();
    }
}

5.3.2 前端生产级流式渲染代码

// 生产级流式对话优化:防抖、重连、手动终止、异常兜底
let eventSource = null;

// 主动关闭流式连接,释放服务资源,解决连接堆积、内存泄漏
function closeStream() {
    if(eventSource){
        eventSource.close();
        eventSource = null;
    }
}

// 商用打字机流式渲染
function aiStreamChat(conversationId, message) {
    // 防抖:新请求关闭旧连接,杜绝长连接堆积
    closeStream();
    const chatBox = document.getElementById("chat-content");
    chatBox.innerText = "正在调用工具,加载中...";
    let html = "";
    let retryCount = 0;
    const maxRetry = 1;

    eventSource = new EventSource(`/api/chat/stream?conversationId=${conversationId}&message=${message}`);

    // 逐字拼接渲染,实现打字机效果
    eventSource.onmessage = function(res) {
        html += res.data;
        chatBox.innerText = html;
    };

    // 响应正常结束,清空连接
    eventSource.onclose = function() {
        eventSource = null;
    };

    // 弱网异常自动重连兜底
    eventSource.onerror = function() {
        if(retryCount < maxRetry){
            retryCount++;
            closeStream();
            setTimeout(()=>{
                aiStreamChat(conversationId,message);
            },1000);
        }else{
            chatBox.innerText = "网络异常,咨询失败,请稍后重试";
            closeStream();
        }
    };
}

5.4 运行效果

1. 接口无卡顿,文字逐字实时渲染,交互体验流畅;

2. 支持工具调用结果流式输出,动态展示订单、物流等业务数据;

3. 长连接自动回收,无内存泄漏、连接堆积问题,适配高并发场景;

4. 弱网环境支持自动重连,大幅降低咨询失败率;

5. 支持手动终止对话,及时释放无效服务资源。

5.5 高频面试题解析

题目1:Spring AI 1.1.x流式输出的核心原理?

参考答案:基于Reactor的Flux响应式流,结合SSE服务端推送协议实现。大模型分段生成内容后,框架自动封装为标准流数据实时推送前端,无需手动管理线程、数据分片、连接生命周期,底层封装完善,开箱即用。

题目2:框架流式输出对比手动SSE实现的优势?

参考答案:手动SSE需要手动创建线程、处理分片、捕获异常、关闭连接,代码繁琐且易出现稳定性问题。Spring AI原生流式API封装所有底层逻辑,一行代码实现流式对话、工具调用、上下文联动,性能更好、稳定性更高、维护成本更低。

题目3:生产环境SSE流式输出必须优化哪些点?

参考答案:1. 请求防抖,新请求关闭旧连接,杜绝长连接堆积;2. 弱网断流重连,提升容错性;3. 手动终止+自动资源回收,避免资源占用;4. 增加加载状态提示,优化用户交互体验。

六、项目全局配置规范(安全加固版)

6.1 配置作用

通过配置文件统一管理大模型密钥、接口地址、对话参数、超时时间,避免代码硬编码,支持多环境切换、参数动态调整,同时通过参数限额、密钥脱敏,规避安全风险与超额扣费问题,适配生产上线规范。

6.2 配置原理

1. Spring AI自动读取yml配置,完成大模型客户端自动装配;

2. 可自定义模型随机性、最大生成长度、接口超时等核心参数;

3. 配置与代码解耦,便于多环境迭代、参数调优、密钥管控;

4. 通过Token限额、超时配置,实现成本管控与服务防护。

6.3 完整生产配置

# Spring AI 1.1.x 生产级全局配置(安全加固)
spring:
  ai:
    openai:
      # 生产环境通过环境变量/配置中心加密注入,禁止明文硬编码
      api-key: ${OPENAI_API_KEY:sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}
      base-url: https://api.openai.com/v1
      chat:
        options:
          # 客服场景固定低随机值,保证回复严谨、无幻觉
          temperature: 0.2
          # 单轮Token上限,管控扣费成本
          max-tokens: 1024
          model: gpt-3.5-turbo
    # 全局接口超时时间
    timeout: 30000
# 服务端口配置
server:
  port: 8080
# Redis分布式会话配置
spring:
  data:
    redis:
      host: localhost
      port: 6379
      password: ${REDIS_PASSWORD:}
      timeout: 10000

6.4 配置生效效果

项目启动自动加载所有配置,对话、工具调用、流式输出统一遵循配置规则,无需修改代码即可适配多环境部署,密钥脱敏、Token限额有效规避安全漏洞与超额扣费风险。

6.5 高频面试题解析

题目1:客服场景为什么调低temperature参数?

参考答案:temperature控制大模型回复随机性,数值越高创意性越强、随机性越大。智能客服需要回复严谨、标准化、无幻觉误差,因此设置0.1-0.3低区间数值,保证业务回答统一、精准。

题目2:生产环境大模型密钥如何安全管控?

参考答案:禁止配置文件明文硬编码,生产环境通过系统环境变量、Nacos/Apollo加密配置中心、服务器密钥文件挂载三种方式注入,同时区分测试、预发、生产环境密钥隔离,杜绝密钥泄露、账号盗刷风险。

七、项目架构封装(商用生产版)

7.1 封装意义

原生Demo代码存在返回格式不统一、无异常兜底、无日志记录、无风控防护等问题,仅适用于测试。通过统一返回格式、全局异常处理、日志记录、实体序列化封装,完全适配企业生产规范,实现可监控、可排查、可兜底的商用架构。

7.2 架构核心能力

1. 完善实体序列化机制,支持分布式集群传输;

2. 统一接口返回体,标准化前后端交互格式;

3. 精准捕获AI工具异常、参数异常、系统异常,屏蔽原始错误堆栈;

4. 全局日志记录,支持线上问题追溯、故障复盘。

7.3 核心落地代码

7.3.1 全局统一接口返回体

/**
 * 全局统一接口返回封装
 * 标准化所有接口响应,适配前端统一处理逻辑
 */
public class Result<T> {
    private Integer code;
    private String msg;
    private T data;

    public static <T> Result<T> success(T data) {
        Result<T> r = new Result<>();
        r.setCode(200);
        r.setMsg("success");
        r.setData(data);
        return r;
    }

    public static <T> Result<T> fail(String msg) {
        Result<T> r = new Result<>();
        r.setCode(500);
        r.setMsg(msg);
        return r;
    }

    // getter、setter省略
    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}

7.3.2 全局异常处理器(精准捕获AI异常)

import org.springframework.ai.tool.execution.ToolExecutionException;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import lombok.extern.slf4j.Slf4j;

/**
 * 全局异常拦截处理器
 * 统一日志记录、异常兜底、堆栈信息脱敏
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    // 捕获AI工具调用专属异常
    @ExceptionHandler(ToolExecutionException.class)
    public Result<String> toolError(ToolExecutionException e) {
        log.error("AI工具调用异常:{}", e.getMessage());
        return Result.fail("工具调用失败:" + e.getMessage());
    }

    // 捕获接口参数校验异常
    @ExceptionHandler(BindException.class)
    public Result<String> paramError() {
        log.error("接口参数校验异常");
        return Result.fail("参数缺失或格式错误,请补充完整信息");
    }

    // 全局系统异常兜底
    @ExceptionHandler(Exception.class)
    public Result<String> error(Exception e) {
        log.error("系统未知异常:{}", e.getMessage(), e);
        return Result.fail("系统异常,请稍后重试");
    }
}

7.4 架构生效效果

所有接口响应格式统一,各类异常均被优雅兜底,不会对外暴露系统原始堆栈,完整日志记录线上故障信息,完全符合生产安全、运维规范。

7.5 高频面试题解析

题目:为什么需要单独捕获ToolExecutionException?

参考答案:工具调用异常属于可控业务异常,多为参数错误、数据不存在、接口调用失败等场景。单独捕获可精准区分业务异常与系统故障,返回用户友好提示,提升用户体验,同时便于后端精准定位AI工具调用问题,方便线上排查复盘。

八、FunctionCall底层核心机制

8.1 机制价值

掌握Spring AI工具调用底层执行逻辑,可从根源规避循环调用、参数缺失、AI幻觉等常见问题,是项目调优、线上问题排查、面试深挖的核心知识点。

8.2 三段式完整执行链路

模型思考阶段:系统将用户提问、历史上下文、工具功能描述同步提交大模型,大模型自主判断是否需要调用后端工具,无需工具则直接生成文本回复。

工具调用阶段:判定需要调用工具时,大模型返回标准化JSON参数;Spring AI自动完成参数解析、合法性校验、反射调用对应Java业务方法。

结果汇总阶段:工具执行完成返回结构化业务数据,回传大模型后整理为规范、通顺的自然语言回复,反馈给用户。

8.3 核心自动机制

自动Schema生成:项目启动时,框架扫描所有@FunctionCallback注解方法,通过反射解析入参字段、校验规则、工具描述,自动生成OpenAI标准JSON Schema,无需手动配置。

自动参数补全追问:用户提问缺失必填参数时,框架自动将字段校验规则、参数要求同步至大模型Prompt,大模型主动追问用户补充信息,无需后端编写冗余判断逻辑。

8.4 机制运行效果

用户模糊提问“查我的物流”,系统自动识别缺失9位订单号参数,AI主动提示用户补充合规参数,全程自动化交互,无需人工编码判断。

8.5 高频面试题解析

题目1:参数缺失时,AI自动追问补参的核心原理?

参考答案:框架自动将实体类的参数校验注解、字段约束、工具功能描述同步至大模型提示词,大模型自主识别参数缺失、格式异常问题,主动引导用户补充信息,后端无需编写任何if判断、补参逻辑。

题目2:FunctionCall是否存在循环调用问题?生产如何解决?

参考答案:原生框架无调用次数限制,复杂场景下极易出现工具循环调用,导致超额扣费、服务卡顿。生产解决方案:1. 自定义Advisor拦截器,限制单轮最大调用次数;2. 工具结果添加终止提示词;3. 增加多维度限流,杜绝无效重复调用。

九、工具限流与多工具组合调用(生产落地)

9.1 功能痛点与解决方案

Spring AI原生工具调用存在两大生产致命问题:一是无调用次数限制,极易出现死循环调用,造成大模型超额扣费、服务接口雪崩、线程资源耗尽;二是默认不支持多工具联动,无法处理用户复合业务提问。本章落地会话级限流防循环多工具组合调用能力,彻底解决线上稳定性与业务适配问题。

9.2 核心实现原理

9.2.1 工具限流防循环原理

基于Spring AI Advisor拦截机制,自定义全局工具拦截器,会话维度统计调用次数,设置单轮对话最大阈值,超额直接终止工具链路;同时拦截无效、重复、空参数调用,搭配会话级频次管控,杜绝恶意扣费与服务卡死。

9.2.2 多工具组合调用原理

一次性向大模型注册全部售后业务工具,不做调用隔离;大模型自主解析用户复合提问,拆解多维度业务需求,自动串行调用对应工具,汇总所有业务数据后统一生成回复,无需多次问答交互。

9.3 核心落地代码

9.3.1 自定义工具限流拦截器

import org.springframework.ai.chat.client.advisor.api.AdvisedRequest;
import org.springframework.ai.chat.client.advisor.api.AdvisedResponse;
import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisor;
import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisorChain;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 生产级工具限流拦截器
 * 核心能力:防死循环、控调用次数、杜绝高频扣费、会话级资源管控
 */
@Component
public class ToolLimitAdvisor implements CallAroundAdvisor {

    // 会话级工具调用计数器:key=会话ID,value=当前轮次调用次数
    private final ConcurrentHashMap<String, Integer> sessionToolCount = new ConcurrentHashMap<>();

    // 单轮对话最大工具调用次数,生产固定阈值,杜绝死循环
    private static final int MAX_TOOL_CALL_TIMES = 3;

    @Override
    public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {
        String conversationId = advisedRequest.get