ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Spring Boot接入OpenAI大模型:多模型切换与流式输出实战

Spring Boot接入OpenAI大模型:多模型切换与流式输出实战 简介这是一份基于Spring Boot构建的人工智能机器人项目已经接入GPT-3.5、GPT-4.0、Kimi、百度文心一言、Stable Diffusion、Midjourney等主流大模型可实现智能对话、知识问答与AI绘画。项目面向计算机、电子信息工程、数学等专业学生可作为课程设计、期末大作业或毕业设计的完整参考资料。压缩包共包含1157个文件大小26.84MB其中以584个Java源文件为主体辅以104个Vue页面、108个JavaScript脚本、73个XML配置以及SQL、YAML、Dockerfile等工程部署文件前后端结构完整易于运行与二次开发同时附有数据库初始化脚本与环境变量示例。目前已有1233人学习下载。代码模块划分清晰对多模型接入、请求路由、上下文对话、前端渲染等关键环节均有可复用的实现参考提供Docker容器化部署配置便于快速搭建本地运行环境适合需要完成智能问答或AI绘图项目的人群。1. 一个基于Spring Boot的AI机器人项目不只是能聊天还能看到大模型接入全貌做Java后端的人都有一个感觉搞了近半年Spring Boot增删改查已经写腻了想碰点AI又不知道从哪下手。这个基于Spring Boot的人工智能机器人项目就是把OpenAI大模型接进Spring Boot这件事做成了一个能跑的完整工程。它不是那种只调一个API的Demo而是把多模型切换、流式输出、会话管理、用户拦截这些真实需求都做进去了。源码拿到手可以直接启动也能拆开看每个环节怎么写的适合做毕业设计也适合想在Spring Boot项目里接入大模型的后端开发当参考。我花了一晚上把它跑通并且对着源码捋了一遍里面的几个设计点确实值得拿出来说说。2. 项目结构和启动准备先把工程跑起来再看代码怎么排拿到资源先别急着双击运行。这个Spring Boot项目用的是Maven标准目录结构Java源码放在src/main/java下资源文件在src/main/resources里前后端分离前端静态页面单独放。整体结构不复杂但启动之前有几个参数得先确认。2.1 pom.xml里的关键依赖版本和模块决定了运行环境打开pom.xml第一件事就是看依赖版本。我当时看见Spring Boot版本是2.7.xJava要求是1.8心里就有底了——这两个版本组合在本地跑不会有什么坑。如果下载到的源码用了Spring Boot 3.x那Java版本会要求17下面环境准备就对不上了。dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- 用于HTTP调用OpenAI接口 -- dependency groupIdorg.apache.httpcomponents.client5/groupId artifactIdhttpclient5/artifactId version5.2.1/version /dependency !-- Redis用于会话缓存 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency !-- JSON序列化 -- dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId /dependency /dependencies这里比较关键的是Apache HttpClient5项目里所有大模型接口的HTTP请求都是它发的版本不要乱改5.x和4.x的API有差别网上很多示例用的是4.x直接抄过来会有编译错误。Redis是处理会话历史的如果你不需要多轮对话记忆可以把Redis配置关掉但我建议留着因为聊天机器人的会话管理这个大模块本身是毕设答辩的加分点。2.2 application.yml里的配置项密钥、超时、模型名称配置文件在src/main/resources/application.yml这两项是启动前必须改的server: port: 8080 spring: redis: host: localhost port: 6379 database: 0 openai: api-key: sk-xxxxxxx base-url: https://api.openai.com/v1 timeout: 60 model: gpt-4o-miniopenai.api-key是你在OpenAI平台申请的密钥base-url注意不要填错有些项目会默认填https://api.openai.com少了/v1路径接口直接404。timeout这里默认设置60秒大模型接口响应慢是常态超时设短了特别容易断流。model字段填的是模型名称这个项目里预置了好几个主流模型后面可以随时切换这也是它和那种写死单模型的Demo最大的区别。2.3 启动入口与前端页面两个模块决定怎么验证效果主启动类就是个常规的Spring Boot入口标注了SpringBootApplication但项目里有两个模块值得提前注意一个是controller包下的ChatController它处理/api/chat请求并响应流式数据另一个是interceptor包下的AuthInterceptor它是整个项目所有请求的守门员拦不到或者规则配置错了前端页面根本访问不了。我建议第一次启动时先跑一次mvn clean package -DskipTests确认打包没报错再启动避免运行时才发现依赖缺失。提示启动参数里的-Dspring.profiles.activedev可以指定不同环境如果你的机器上Redis端口被改了直接在application.yml里调就行不需要改代码。3. 大模型接入的架构设计多种模型切换和流式响应的实现整个项目能演示的关键在于它没有把OpenAI的接口调用写死在服务层。我用一段代码就能说明这个项目的设计思路它把HTTP请求、模型名、响应处理全部拆开换模型的时候不用改业务代码。3.1 HttpService层用统一的方式调不同大模型的接口public String callModel(String modelName, String prompt) { // 构建请求体模型名从这里传进去 JSONObject requestBody new JSONObject(); requestBody.put(model, modelName); requestBody.put(messages, prompt); requestBody.put(stream, false); // 发送HTTP POST请求到 OpenAI兼容接口 HttpPost post new HttpPost(baseUrl /chat/completions); post.setHeader(Authorization, Bearer apiKey); post.setHeader(Content-Type, application/json); post.setEntity(new StringEntity(requestBody.toJSONString(), UTF-8)); // 解析返回结果取出content字段 JSONObject data httpClient.execute(post, response - parseResponse(response)); return data.getJSONObject(message).getString(content); }这段代码的逻辑很直接把模型名作为参数传进方法具体的HTTP请求细节全封装在HttpService里未来对接其他兼容OpenAI格式的服务商只需要改baseUrl和apiKey。这里的streamfalse表示普通同步模式等接口完整返回后一次性输出。3.2 多模型切换的实现配置文件驱动不用改代码项目里做了一个模型管理类把当前可用的模型名存在一个List里前端下拉框能选选完通过请求参数传给后端后端直接调chatService.chat(modelName, userMessage)。我第一次跑通之后试着切换成gpt-3.5-turbo发现延迟明显比gpt-4o-mini短但回答质量也差一截这种切换带来的体验差别的对比做演示时很有说服力。Service public class ChatService { // 项目预置的模型列表可随时增删 private final ListString supportedModels Arrays.asList( gpt-4o-mini, gpt-4o, gpt-3.5-turbo ); public String chat(String model, String message) { if (!supportedModels.contains(model)) { throw new IllegalArgumentException(不支持的模型: model); } // 这里用 userMessage 拼出完整的messages数组不含历史记录 return httpService.callModel(model, message); } }关键点在于supportedModels这个列表资源的价值在这里就体现出来了——新模型出来之后你只需要在列表里加一个名字不用改任何HTTP层代码。但也要注意这里只传了当前单条消息不带历史上下文意味着它是单轮对话模式。如果毕设需要展示多轮对话要自己改callModel方法把Redis里的历史消息一起拼进messages数组。3.3 SSE流式输出打字机效果的关键演示的时候没有打字机效果评委一看就觉得是假AI。这个项目里搭了一个SSE接口用SseEmitter实现流式推送。看代码你会发现它专门开了一个Controller完全独立于普通的同步接口GetMapping(value /api/chat/stream, produces MediaType.TEXT_EVENT_STREAM_VALUE) public SseEmitter streamChat(RequestParam String message) { // 超时设为5分钟避免长时间无数据导致断连 SseEmitter emitter new SseEmitter(300_000L); // 异步任务里逐步推送结果 executorService.execute(() - { try { String fullResponse httpService.callModel(currentModel, message); // 按长度切块每30个字符推一次 for (int i 0; i fullResponse.length(); i 30) { emitter.send(fullResponse.substring(i, Math.min(i 30, fullResponse.length()))); Thread.sleep(100); // 延迟模拟打字效果 } emitter.complete(); } catch (Exception e) { emitter.completeWithError(e); } }); return emitter; }这个实现和真实的SSE流式响应有一点区别真实场景应该靠模型端逐步返回Token来推送这里因为用的同步模型接口只能拿到完整结果再切块推送效果上很像但首字延迟会略长。想要更真实需要把stream参数改成true然后解析SSE格式的返回流这个项目没有完全做到位但演示已经够用。注意SSE的SseEmitter默认超时是30秒如果不显式设置300000毫秒遇到长的回答推送不完连接就断了。我看到这个坑的时候尤其强调了一遍面试的时候问到这里能答出来对方会觉得你不是只会抄代码。4. 会话管理从单轮聊天到多轮对话的记录方案AI聊天机器人如果连你说过什么都记不住展示起来会很尴尬。这个Spring Boot项目用Redis来缓存会话比用数据库做会话存储轻量很多而且天然带过期时间不用手动清理。4.1 Redis在会话场景的具体用法Service public class SessionService { Autowired private StringRedisTemplate redisTemplate; // 存储一条消息到Redis Listkey是会话ID public void saveMessage(String sessionId, String role, String content) { String key chat:session: sessionId; String message role : content; redisTemplate.opsForList().rightPush(key, message); redisTemplate.expire(key, 30, TimeUnit.MINUTES); } // 取出最近20条消息作为上下文 public ListString getRecentMessages(String sessionId) { String key chat:session: sessionId; // -20 到 -1 表示取List中最后20个元素 return redisTemplate.opsForList().range(key, -20, -1); } }saveMessage方法把用户消息和机器人回复都按顺序推进同一个List里每次请求时getRecentMessages取出最后20条拼到messages数组里。expire(key, 30, TimeUnit.MINUTES)是Redis的过期策略30分钟没有新消息这个会话自动消失不会把内存撑爆。这个设计非常适合毕设展示用redis-cli敲一条keys chat:session:*就能看到会话数据回答答辩时很直观。4.2 会话ID怎么来前端传值还是后端生成项目里采用的是前端请求时带一个sessionId后端直接用它当key。如果前端没传后端用UUID.randomUUID()生成一个新的并在响应头里返回前端拿到后就一直带。这个方案简单但有个隐患sessionId如果被恶意传相同值不同用户之间会串话。好在项目里有登录拦截只有登录用户才能访问聊天接口每个用户自己传一个随机ID串话的问题基本不会出现。4.3 Redis配置失败会怎么样本地如果没有启动Redis项目启动时会报连接异常但Spring Boot默认不会让启动失败而是等真正调用Redis时报错。我在第一次启动时就遇到这个问题日志显示RedisConnectionFailureException但应用还在跑点聊天功能才报错。排查方式是先看spring.redis.port是否和你本地启动的Redis端口一致Windows下默认6379如果你装了别的版本改了端口就配置文件里改一下。5. 部署与排查从pom打包到接口调用失败的常见问题这个阶段是所有Spring Boot项目最容易翻车的地方本地IDE里跑得好好的一到部署环境就各种问题。这个AI机器人典型的问题主要集中在HttpClient版本、跨域配置和第三方接口调用失败这三块。5.1 HttpClient5和JDK版本的兼容问题我看到项目里用了Apache HttpClient5如果你的服务器JDK版本低于1.8或者用了特别老的Tomcat版本会出现java.lang.NoSuchMethodError之类的报错。这不是代码问题是HttpClient5最低支持JDK1.8旧环境跑不了。我一般建议部署前执行java -version确认版本再执行mvn clean package -DskipTests检查依赖能否完整拉取。5.2 Nginx反向代理导致的SSE输出锥形变把前端部署到Nginx之后聊天输出经常会全部攒到最后一起显示完全没有流式效果。原因在于SSE依赖Content-Type: text/event-stream的持续响应而Nginx默认开启缓冲会把数据攒够再发给前端。解决方案是修改Nginx配置proxy_buffering off; proxy_cache off; proxy_set_header X-Accel-Buffering no;这三行配置关闭了代理缓冲区让SSE数据像水管一样直接流到前端。如果是本地调试、透过localhost访问不会遇到这个问题但前后端分离部署几乎是必现的。5.3 跨域问题前后端分离调试时的标配坑项目里肯定配置了CrossOrigin或者CorsFilter但我发现一个问题拦截器先于CORS处理器执行所以当OPTIONS预检请求进来的时候如果拦截器直接拒绝跨域请求根本走不到CORS配置那里。现象就是浏览器控制台报CORS错误但后端日志显示请求根本没进Controller。public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { // 放行跨域预检请求是关键 if (OPTIONS.equalsIgnoreCase(request.getMethod())) { return true; } return checkToken(request); }在AuthInterceptor里对OPTIONS请求直接放行是一个必改点。改完记得清除浏览器缓存再测试浏览器对CORS结果有时候会缓存很久。5.4 密钥额度耗尽导致的报错但不是项目代码的问题OpenAI的密钥是按额度走的项目里如果提示401 unauthorization或quota exceeded基本可以确定是密钥无效或者余额用完了。排查时用命令行直接测是最快的curl -X POST https://api.openai.com/v1/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer sk-这里换成自己的key \ -d {model:gpt-4o-mini,messages:[{role:user,content:hi}]}这个测试能区分是密钥问题还是项目代码问题。如果curl能正常返回那就是代码里取配置的时候没读到api-key检查你启动项目时的环境变量和application.yml是不是同一个路径。5.5 打包后前端页面404的注意点这个项目是前后端分离的前端静态页面在单独目录下但很多人拿到的源码里已经合成了一个Spring Boot应用前端文件放在了src/main/resources/static目录下。打包之后静态资源应该能直接访问如果404先看target/classes/static下有没有页面文件没有的话就是你打包的时候没把它们编译进去Maven的resources配置可能漏了目录。build resources resource directorysrc/main/resources/directory /resource /resources /build这段配置解决的就是静态资源没有被打进jar包的问题。要注意的是过滤规则里别加上**/*.html有次我加上之后首页HTML文件被过滤掉了启动后直接404血泪经验。6. 二次开发技巧如何把机器人接口接进你自己的前端网站如果你不是用项目自带的前端页面而是想把AI对话能力接进自己现有的网站官方页面可以直接不启动专注用两个接口就够了。我梳理一下我给客户改造时最常用的两个shift调整。打开浏览器控制台或者Postman向项目发送一个同步请求curl -X POST http://localhost:8080/api/chat \ -H Content-Type: application/json \ -d {message:请用一句话介绍你自己}返回JSON包在一个content字段里。前端拿到之后直接渲染到页面。如果想要打字机效果把请求地址换成/api/chat/stream使用EventSource接口来接收流式数据const eventSource new EventSource(/api/chat/stream?message encodeURIComponent(你好)); eventSource.onmessage function(event) { // 每次推送返回一段文本content字段就是增量内容 console.log(event.data); }; eventSource.onerror function() { eventSource.close(); // 出错时关闭SSE连接避免无限重连 };SSE接口的message参数是查询参数不是JSON体这个和POST接口不一样别把两个接口传参方式搞混。前端用EventSource来拿数据页面端对增量文本做拼接就能得到打字机流式效果。聊天页面改成多轮之后要注意的一件事是你自己的历史消息记录也要保存下来这样才能在重发时把之前的对话上下文一起拼给模型。我后来习惯把登录用户的会话列表存在数据库里Redis只存最近的20条消息。如果想把项目做得更深可以自己加一个/api/chat/history接口返回当前用户全部历史会话前端左侧做一个历史记录列表。这个在答辩的时候可以拿出Redis的List结构讲评委看到你会自己扩展接口印象分能高点。从那以后我每次接这种Spring Boot大模型项目都会先强制走一遍密钥连通性测试和SSE超时配置确认再往下改功能。这两个点占了这个项目六成以上的报错来源提前验证能省下大把调试时间。希望这个项目的拆解对你自己的毕设或开发有点用处。本文还有配套的精品资源点击获取
返回列表