资讯动态

Spring AI 学习实践笔记(三): 实现企业级自定义智能客服

发布时间:2026/10/16 3:08:59 来源:尧图企业网站定制
Spring AI 学习实践笔记三 实现企业级自定义智能客服本文记录在既有 Spring AI 项目中新增「企业级自定义智能客服」的完整过程后端接入 MySQL MyBatis-Plus 存储课程/校区/预约数据通过Tool把数据库操作暴露给大模型Function Calling前端做客服对话页并复盘真实遇到的建库、代码生成、Bean 冲突、接口重复、模型选型等经验与坑。项目原型参考【Spring AI实战】FunctionCalling实现企业级自定义智能客服。本系列前两篇docs/SpringAI学习实践笔记.md仿 DeepSeek 对话、docs/SpringAI哄哄模拟器学习实践笔记.md提示词工程。一、背景与目标1.1 要做什么做一个 7×24 在线、能给用户咨询培训课程并预约线下试听的智能客服。关键在于模型不仅要会聊天还要能读写真实业务数据——查课程、查校区、生成预约单。这就是Function Calling工具调用要解决的问题。1.2 核心思路用户提问 → 大模型判断需要调用哪个工具函数 → Spring AI 根据 Tool 描述生成工具定义发给模型 → 模型返回要调用的函数名 参数JSON → Spring AI 执行该 Java 方法真实查库 / 写库 → 把执行结果回传给模型 → 模型基于真实数据生成最终回答1.3 技术目标目标实现要点业务数据持久化MySQL MyBatis-Plusmysql-connector-jmybatis-plus-spring-boot3-starter工具暴露Tool/ToolParam注解 ChatClient.defaultTools(tools)多轮记忆复用MessageChatMemoryAdvisorInMemoryChatMemory流式体验stream().content()→FluxString会话历史复用ChatHistoryRepositorytypeservice前端Vue 3 客服对话页 推荐问句二、实现效果演示2.1 数据准备先执行src/main/resources/db/schema.sql建库ai-dome及course/course_reservation/school三张表含初始数据。2.2 接口演示GET /ai/service?prompt{问题}chatId{会话ID} 返回text/html;charsetutf-8 流式文本示例 1咨询课程模型自动调用queryCoursecurl-Nhttp://localhost:8080/ai/service?prompt有哪些编程方向的课程chatIddemo-001模型返回表格形式当然可以下面是编程方向的课程快看看有没有心动的 | 课程名称 | 课程类型 | 学习时长 | | --- | --- | --- | | JavaEE | 编程 | 108 天 | | 鸿蒙应用开发 | 编程 | 98 天 | | AI人工智能 | 编程 | 100 天 | | Python大数据开发 | 编程 | 102 天 |示例 2查询校区调用querySchoolcurl-Nhttp://localhost:8080/ai/service?prompt都有哪些校区chatIddemo-001示例 3预约试听调用createCourseReservation写库curl-Nhttp://localhost:8080/ai/service?prompt帮我预约广东校区的新媒体运营试听我叫张三电话13800000000chatIddemo-001OK已为你预约成功预约信息如下 - 预约课程新媒体运营 - 预约校区广东校区 - 学生姓名张三 - 联系电话13800000000 - 预约单号2若开启SimpleLoggerAdvisor与org.springframework.ai: debug可在后端日志中看到工具的调用参数与返回验证“模型确实调用了工具”而非编造。2.3 前端演示浏览器打开http://localhost:5173/顶部导航切到「智能客服」左侧是咨询记录主区有欢迎语和 4 个推荐问句含一个预约示例点击即可对话回答流式展示、支持 Markdown 表格渲染。三、学习路径与详细步骤阶段 1加入数据库依赖与数据源pom.xmldependencygroupIdcom.mysql/groupIdartifactIdmysql-connector-j/artifactIdscoperuntime/scope/dependencydependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus-spring-boot3-starter/artifactIdversion3.5.10.1/version/dependencyapplication.yaml用环境变量默认值便于本地开发spring:datasource:driver-class-name:com.mysql.cj.jdbc.Driverurl:${DB_URL:jdbc:mysql://localhost:3306/ai-dome?serverTimezoneAsia/ShanghaiuseSSLfalseuseUnicodetruecharacterEncodingutf-8allowPublicKeyRetrievaltrueallowMultiQueriestrue}username:${DB_USERNAME:root}password:${DB_PASSWORD:1234}注意加了数据源后应用启动即依赖 MySQL库连不上会直接启动失败。阶段 2根据 SQL 生成实体 / Mapper / ServiceMyBatis-Plus CRUD针对三张表生成标准三层代码放在com.study.study_ai_client实体Course、CourseReservation、School用TableNameTableId(type IdType.AUTO)下划线字段靠 MyBatis-Plus 默认驼峰映射。MapperXxxMapper extends BaseMapperXxxMapper。ServiceIXxxService extends IServiceXxxXxxServiceImpl extends ServiceImplXxxMapper, Xxx自带全套增删改查。DataTableName(course)publicclassCourse{TableId(valueid,typeIdType.AUTO)privateIntegerid;privateStringname;privateIntegeredu;// 学历要求privateStringtype;// 课程类型privateLongprice;privateIntegerduration;// 学习时长(天)}必须显式开启 Mapper 扫描自动扫描只覆盖启动类所在包SpringBootApplication(scanBasePackagescom.study)MapperScan(com.study.study_ai_client)publicclassStudyAiApplication{...}阶段 3定义工具入参CourseQuery用ToolParam描述每个参数模型才知道怎么填DatapublicclassCourseQuery{ToolParam(requiredfalse,description课程类型编程、设计、自媒体、其它)privateStringtype;ToolParam(requiredfalse,description学历要求0-无、1-初中、2-高中、3-大专、4-本科及本科以上)privateIntegeredu;ToolParam(requiredfalse,description排序方式)privateListSortsorts;DatapublicstaticclassSort{ToolParam(requiredfalse,description排序字段: price或duration)privateStringfield;ToolParam(requiredfalse,description是否是升序: true/false)privateBooleanasc;}}阶段 4编写工具类CourseTools每个业务能力写成一个Tool方法方法体就是普通的 Service 调用RequiredArgsConstructorComponentpublicclassCourseTools{privatefinalICourseServicecourseService;privatefinalISchoolServiceschoolService;privatefinalICourseReservationServicereservationService;Tool(description根据条件查询课程)publicListCoursequeryCourse(ToolParam(description查询的条件,requiredfalse)CourseQueryquery){if(querynull){returncourseService.list();}QueryChainWrapperCoursewrappercourseService.query().eq(query.getType()!null,type,query.getType()).le(query.getEdu()!null,edu,query.getEdu());if(query.getSorts()!null!query.getSorts().isEmpty()){for(CourseQuery.Sortsort:query.getSorts()){wrapper.orderBy(true,Boolean.TRUE.equals(sort.getAsc()),sort.getField());}}returnwrapper.list();}Tool(description查询所有校区)publicListSchoolquerySchool(){returnschoolService.list();}Tool(description生成预约单返回预约单号)publicIntegercreateCourseReservation(ToolParam(description预约课程)Stringcourse,ToolParam(description预约校区)Stringschool,ToolParam(description学生姓名)StringstudentName,ToolParam(description联系电话)StringcontactInfo,ToolParam(description备注,requiredfalse)Stringremark){CourseReservationrnewCourseReservation();r.setCourse(course);r.setSchool(school);r.setStudentName(studentName);r.setContactInfo(contactInfo);r.setRemark(remark);reservationService.save(r);returnr.getId();}}阶段 5把工具注册到 ChatClientConfigurationpublicclassCommonConfiguration{BeanpublicChatClientserviceChatClient(OpenAiChatModelmodel,Qualifier(chatMemory)ChatMemorychatMemory,CourseToolscourseTools){returnChatClient.builder(model).defaultSystem(SystemConstants.SERVICE_SYSTEM_PROMPT).defaultAdvisors(newSimpleLoggerAdvisor(),newMessageChatMemoryAdvisor(chatMemory)).defaultTools(courseTools)// 关键注册工具启用 Function Calling.build();}}阶段 6编写客服 ControllerRequiredArgsConstructorRestControllerRequestMapping(/ai)publicclassCustomerServiceController{privatefinalChatClientserviceChatClient;privatefinalChatHistoryRepositorychatHistoryRepository;RequestMapping(value/service,producestext/html;charsetutf-8)publicFluxStringservice(RequestParam(prompt)Stringprompt,RequestParam(chatId)StringchatId){chatHistoryRepository.save(service,chatId);returnserviceChatClient.prompt().user(prompt).advisors(a-a.param(CHAT_MEMORY_CONVERSATION_ID_KEY,chatId)).stream().content();}}阶段 7系统提示词角色 流程 防注入SystemConstants.SERVICE_SYSTEM_PROMPT明确角色身份、课程咨询规则、预约流程必须收集姓名/电话/校区、安全防护拒绝 prompt 注入、展示要求用表格且不含 id/价格。阶段 8前端客服页services/api.js增加sendServiceMessage(prompt, chatId)→/ai/service。views/ServiceChat.vue侧边「咨询记录」复用/ai/history/service 欢迎语 推荐问句 流式对话。App.vue导航新增「智能客服」。四、学习经验与踩坑记录重点1. 加数据源后应用“变脆”引入 MyBatis-Plus 后Spring Boot 会自动配置 DataSource没有 MySQL 就启动失败。学习期建议先起库再启动或用环境变量切换不同环境。2. MyBatis-Plus 的 Mapper 自动扫描范围Mapper注解的接口只有在启动类所在的自动配置包下才会被自动扫描。本项目 Mapper 在com.study.study_ai_client不属于启动类的包必须加MapperScan(com.study.study_ai_client)否则报“找不到 Mapper Bean”。3. 工具方法相关的常见问题ToolParam的description要写清楚模型靠它决定参数requiredfalse要标对否则模型可能硬造参数。CourseQuery.Sort.getAsc()是BooleanorderBy需要boolean直接传可能 NPE用Boolean.TRUE.equals(...)兜底。查询条件的.eq(条件?, ...)用法MyBatis-Plus 的条件构造能让“参数为空则不参与 where”。4. 接口/Bean 冲突本项目真实踩到我一度新增了第二个RequestMapping(/ai/service)的 Controller和已有CustomerServiceController映射重复会导致启动失败Ambiguous mapping。同一个路径只能有一个处理方法。之前也遇到过Configuration简单类名相同、Bean方法名相同导致的 Bean 冲突——新增功能前先检查命名是否撞车。5. 验证要跑起来看日志工具到底有没有被调用靠日志。开启logging:level:org.springframework.ai:debug配合SimpleLoggerAdvisor能看到请求轮次、工具调用参数与结果确认不是模型“编造”的数据。五、核心知识点小结知识点一句话结论Function Calling用Tool/ToolParam把 Java 方法暴露给模型模型产出调用参数框架执行并回传defaultToolsChatClient.builder(model).defaultTools(tools)注册工具ToolParam参数描述越清晰模型填参越准required要标对MyBatis-PlusBaseMapperIService/ServiceImpl提供通用 CRUD条件构造用QueryChainWrapperMapperScan自动扫描只覆盖启动类包跨包必须显式声明提示词工程角色 流程约束 防注入 展示要求决定客服的“人设”与稳定性数据源影响引入后应用启动依赖数据库需先建库建表六、项目结构本次新增部分src/main/java/com/study/study_ai_client/ ├─ Course.java / CourseReservation.java / School.java # 实体 ├─ CourseMapper.java / CourseReservationMapper.java / SchoolMapper.java ├─ ICourseService.java / ICourseReservationService.java / ISchoolService.java ├─ CourseServiceImpl.java / CourseReservationServiceImpl.java / SchoolServiceImpl.java ├─ CourseQuery.java # 工具入参 ├─ CourseTools.java # Tool 工具类 ├─ SystemConstants.java # 客服系统提示词 ├─ CommonConfiguration.java # serviceChatClient含 defaultTools └─ CustomerServiceController.java # GET /ai/service src/main/resources/db/schema.sql # 建库建表脚本 frontend/src/ ├─ services/api.js # sendServiceMessage ├─ views/ServiceChat.vue # 客服对话页 └─ App.vue # 导航新增「智能客服」七、总结与可扩展方向7.1 总结Function Calling 是“AI 接入业务系统”的关键模型负责理解与决策Java 负责真实数据读写两者用工具协议连接。先有数据再有智能生成实体/Mapper/Service → 写工具 → 注册到 ChatClient是一条清晰的落地路径。提示词决定上限角色、流程先问学历再查课、先收集信息再下单、防注入全靠 System Prompt。工程细节同样重要MapperScan、接口唯一映射、Bean 命名这些“非 AI”问题反而最容易卡住启动。7.2 可扩展方向工具调用可观测性Micrometer 统计工具被调次数、耗时、token 消耗。结构化输出预约确认可用.entity(ReservationResult.class)直接映射。RAG 增强接入课程大纲/常见问题向量库回答更专业。权限与安全敏感写操作下单加校验、限流、审计SafeGuardAdvisor过滤不当内容。多租户/多客服人设不同ChatClientBean 用Qualifier区分。八、参考文档链接本系列文章【Spring AI实战】FunctionCalling实现企业级自定义智能客服本篇原型【SpringAI 实战】提示词工程实现哄哄模拟器【Spring AI实战】实现仿DeepSeek页面对话机器人Spring AI 完整学习路径与项目实践指南姿势纠正Spring AI 官方项目主页https://spring.io/projects/spring-ai参考文档https://docs.spring.io/spring-ai/reference官方示例https://github.com/spring-projects/spring-ai-examples核心 APIChatClienthttps://docs.spring.io/spring-ai/reference/api/chatclient.htmlTools函数调用https://docs.spring.io/spring-ai/reference/api/tools.htmlAdvisorshttps://docs.spring.io/spring-ai/reference/api/advisors.htmlChat Memoryhttps://docs.spring.io/spring-ai/reference/api/chat-memory.html结构化输出https://docs.spring.io/spring-ai/reference/api/structured-output-converter.htmlRAGhttps://docs.spring.io/spring-ai/reference/api/retrieval-augmented-generation.html数据库 / 前端MyBatis-Plus 官网https://baomidou.comMySQL 下载https://dev.mysql.com/downloads/Vue 3https://cn.vuejs.orgVitehttps://cn.vitejs.devDeepSeek 开放平台https://platform.deepseek.com

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑