当前位置: 首页 > news >正文

AI项目落地实战:SpringBoot3+SpringAI+Uniapp

前言

AI不仅仅是风口,也是今后的时代潮流。本人花心血开发了一套AI实战项目,可商用。支持h5,小程序,app三端。可拿来二开,也可直接上架。不用担心版权问题,但是如果是倒卖源码,本人会追究其责任

如果你是大学生,也可以抓住这个机会学习AI,源码并不难,都是java那一套。本人也会提供免费学习指导

一. 详细功能

  • 支持文本对话(流失)+ 图片生成。
  • 支持用户创建自己的智能体。
  • 支持动态添加应用,只需简单配置就能生成一个新的AI应用,无需任何代码编写。
  • 动态调优应用的prompt提示词,历史对话轮数,System Role等大模型属性。
  • 内置应用的提示词都是大厂优化的提示词,有相当高的参考价值。
  • 支持用户自己切换大模型,可以任意切换成qwen,gpt4,coze等大模型厂商。
  • 支持插件模式。动态对接新的大模型API(无需重启应用就可以新增一个大模型api的对接)
  • 动态修改大模型属性:base-url和token
  • 支持用户会员模式(包月)+ 非会员模式
  • 实现了邮箱验证码登录

应用部分截图

视频演示功能

AI项目落地实战:SpringBoot3+SpringAI+

二. 技术架构

前台

  • uniapp
  • websocket

后台

  • SpringBoot3.3.0
  • 大模型底座: SpringAI + 通义千问 + Coze
  • JDK17
  • Sa Token
  • Websocket
  • MyBatisPlus+MySQL

三. 数据库表

四. 代码搭建步骤

用mysql新建一个数据库ai-waiter,然后执行语句:ai-waiter.sql。

修改application.yml配置

主要是修改邮箱服务器信息和数据库地址信息。

然后启动main函数:AiWaiterAppApplication

项目会监听两个协议,http协议+ws协议,端口都是 8999。

前端启动直接用Hbuilder导入项目,然后编译运行即可。

注意: 初始化的大模型只有3.5,需要增加4的token的可以联系小编。

五. 主要技术点讲解(开发人员)

插件开发

背景: 市面上有很多种大模型api。比如:openai,千问api,扣子api,讯飞星火等, 插件开发就是为了更好的分离对接代码,以及做到 不重启服务 就可以实现对接各大厂商api。

系统内置插件 OpenAIModelPlugin

系统源代码内置了一个SpringAI实现的插件: com. aiwaiter. openai. plugins. sys.OpenAIModelPlugin

public class OpenAIModelPlugin extends AbstractAIModelPlugin {@Overridepublic void streamcall(Configure configure) {System.out.println("请求 openai-AI>> " + configure);OpenAiApi api = new OpenAiApi(configure.getBaseUrl(), configure.getToken());OpenAiChatOptions options = OpenAiChatOptions.builder().withModel(configure.getModel()).withTemperature(0.7f) //温度系数.build();ChatModel myChatModel = new OpenAiChatModel(api, options);ChatClient.Builder builder = ChatClient.builder(myChatModel);builder.defaultSystem(configure.getRole());ChatClient chatClient = builder.build();// 构建用户消息String curmsg = getUserText(configure);List<Message> messages = new ArrayList<>();if(!CollectionUtils.isEmpty(configure.getHistory())){// 添加历史消息for(DTO.History his : configure.getHistory()){if(!StringUtils.isEmpty(his.getBotContent())){messages.add(new AssistantMessage(his.getBotContent()));continue;}messages.add(new UserMessage(his.getUserContent()));}}messages.add(new UserMessage(curmsg)) ;Flux<ChatResponse> stream = chatClient.prompt().messages(messages).stream().chatResponse();stream.toStream().forEach(response -> {response.getResults().forEach(item -> {AssistantMessage output = item.getOutput();boolean finish = false;if (output.getMetadata().containsKey("finishReason") && output.getMetadata().get("finishReason").equals("STOP")) {finish = true;configure.getListerner().onCall("", finish);return;}configure.getListerner().onCall(output.getContent(), finish);});});}@Overridepublic void textToImge(Configure configure) {System.out.println("请求AI>> " + configure);OpenAiImageApi api = new OpenAiImageApi(configure.getBaseUrl(), configure.getToken(), RestClient.builder());OpenAiImageModel model = new OpenAiImageModel(api);ImageOptions options = ImageOptionsBuilder.builder().withModel(OpenAiImageApi.ImageModel.DALL_E_3.getValue()).build() ;ImagePrompt imagePrompt = new ImagePrompt(getUserText(configure) , options);final String url = model.call(imagePrompt).getResult().getOutput().getUrl();configure.getListerner().onCall(url, true);}}

自定定义插件

插件类必须继承 AbstractAIModelPlugin , 并且必须要实现下面两个方法:

    /**** 流式文字返回* @param configure*/void streamcall(Configure configure);/**** 文字生成图片* @param configure* @return*/void textToImge(Configure configure);

如果要设置流失回调监听器,只需要设置Configure类里面的StreamCallBackListerner即可监听流失回调:

Configure.StreamCallBackListerner

我们用编辑器写好插件代码,比如我写了一个对接千问api的插件

package com.aiwaiter.openai.plugins.sys;import com.aiwaiter.openai.plugins.AbstractAIModelPlugin;
import com.aiwaiter.openai.Configure;
import com.aiwaiter.websocket.dto.DTO;
import com.alibaba.dashscope.aigc.generation.Generation;
import com.alibaba.dashscope.aigc.generation.GenerationParam;
import com.alibaba.dashscope.aigc.generation.GenerationResult;
import com.alibaba.dashscope.common.Message;
import com.alibaba.dashscope.common.Role;
import com.alibaba.dashscope.utils.Constants;
import io.reactivex.Flowable;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;/*** @className: com.aiwaiter.openai.plugins.sys.QwenAIModelPlugin* @author: WX:hadluo QQ:657455400* @date: 2024/7/24 15:43* @Version: 1.0* @description:*/
public class QwenAIModelPlugin extends AbstractAIModelPlugin {@Overridepublic void streamcall(Configure configure) {System.out.println("请求 qwen-AI>> " + configure);Constants.apiKey = configure.getToken();// 系统角色Message sysMsg = Message.builder().role(Role.SYSTEM.getValue()).content(configure.getRole()).build();// 用户消息Message userMsg = Message.builder().role(Role.USER.getValue()).content(getUserText(configure)).build();List<Message> messages = new ArrayList<>();// 构建历史消息if(configure.getHistory() != null && !configure.getHistory().isEmpty()){// 添加历史消息for(DTO.History his : configure.getHistory()){if(!StringUtils.isEmpty(his.getBotContent())){messages.add( Message.builder().role(Role.ASSISTANT.getValue()).content(his.getBotContent()).build());continue;}messages.add( Message.builder().role(Role.USER.getValue()).content(his.getUserContent()).build());}}messages.add( Message.builder().role(Role.USER.getValue()).content(getUserText(configure)).build());GenerationParam param = GenerationParam.builder().model("qwen-turbo").messages(Arrays.asList(sysMsg, userMsg)).resultFormat(GenerationParam.ResultFormat.MESSAGE).topP(0.8).incrementalOutput(true).build();Generation gen = new Generation();try {Flowable<GenerationResult> result = gen.streamCall(param);result.blockingForEach(message -> {String finishReason = message.getOutput().getChoices().get(0).getFinishReason() ;boolean finish =false ;if(!StringUtils.isEmpty(finishReason) && "stop".equals(finishReason)){finish = true ;}String centent = message.getOutput().getChoices().get(0).getMessage().getContent();configure.getListerner().onCall(centent , finish);});} catch (Exception e) {throw new RuntimeException(e);}}@Overridepublic void textToImge(Configure configure) {//千问无法生成图片}
}

写好之后,我们测试没有问题,就将代码插入数据库 t_ai_robot 表中plugin

CREATE TABLE `t_ai_robot` (`robot_id` int(10) unsigned NOT NULL auto_increment COMMENT '自增主键',`token` varchar(400) default NULL COMMENT 'token',`bot_id` varchar(100) NOT NULL COMMENT 'bot_id',`model` varchar(255) default '' COMMENT '大模型底座',`plugin` mediumtext COMMENT '插件实现代码',`plugin_class` varchar(255) default NULL COMMENT '插件的全类名',`plugin_version` int(10) default NULL COMMENT '版本号,自定义插件必须从1开始递增,修改了plugin,必须新增版本号',`base_url` varchar(255) default '' COMMENT '请求地址',`acct` varchar(255) default NULL,`pwd` varchar(255) default NULL,PRIMARY KEY  USING BTREE (`robot_id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8 COMMENT='机器人表';

这样就实现了新增千问对接。如果您后续修改了插件代码,只需要把表中的 plugin_version 版本号字段升级即可。

动态修改token和base-url

也是直接对 t_ai_robot 表进行操作,token字段就是api的key,base_url就是请求的地址。

动态添加,修改应用

是对t_ai_app应用表操作。里面关键字段是大模型的属性:

  • role: 传给大模型的 System 角色。
  • prompt: 传给大模型的UserMessage。此变量在传给大模型之前会将 ${usertext} 替换为用户输入的内容。
  • history_count : 传给大模型的历史对话轮数,为了节省token词,有最大值,在application.yml中配置。

动态调优提示词

只需要修改t_ai_app表的role和prompt字段即可。

提示词

比如内置的诗歌提示词

Role: 诗人
Profile
Author: YZFly
Version: 0.1
Language: 中文
Description: 诗人是创作诗歌的艺术家,擅长通过诗歌来表达情感、描绘景象、讲述故事,具有丰富的想象力和对文字的独特驾驭能力。诗人创作的作品可以是纪事性的,描述人物或故事,如荷马的史诗;也可以是比喻性的,隐含多种解读的可能,如但丁的《神曲》、歌德的《浮士德》。
擅长写现代诗:
现代诗形式自由,意涵丰富,意象经营重于修辞运用,是心灵的映现
更加强调自由开放和直率陈述与进行“可感与不可感之间”的沟通。
擅长写七言律诗
七言体是古代诗歌体裁
全篇每句七字或以七字句为主的诗体
它起于汉族民间歌谣
擅长写五言诗
全篇由五字句构成的诗
能够更灵活细致地抒情和叙事
在音节上,奇偶相配,富于音乐美
Rules
内容健康,积极向上
七言律诗和五言诗要押韵
Workflow
让用户以 "形式:[], 主题:[]" 的方式指定诗歌形式,主题。
针对用户给定的主题,创作诗歌,包括题目和诗句。
Initialization
作为角色 , 严格遵守 , 使用默认 与用户对话,友好的欢迎用户。然后介绍自己,并告诉用户 。

用户自创智能体

用户自己创建的智能体也是在 t_ai_app表里面,但是类目id必须是21 ,此值不能更改。与用户的关联表为:t_user_robot, 比如我创建的智能体:

整个代码截图

前端代码

前端代码就是通过uniapp实现的,没什么好讲的。截图吧

结尾语

如果您对项目感兴趣,请联系小编,如果您有AI的需求也可以找小编,欢迎咨询。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 6.key的层级结构
  • 架构师软考-每日两道单选题6
  • 50etf期权怎么可以买跌做空吗?
  • 理解 Objective-C 中 +load 方法的执行顺序
  • 基于SpringCloud alibaba的流媒体视频点播平台
  • 拦截器和过滤器
  • 抽象代数精解【9】
  • C# 设计模式六大原则之依赖倒置原则
  • MySQL的简单介绍
  • nuclei-快速漏洞扫描器【安装使用详解】
  • 鸿蒙AI功能开发【人脸活体验证控件】 机器学习-场景化视觉服务
  • 前端发版(发包)缓存,需要强制刷新问题处理
  • git版本控制的底层实现
  • Flink 实时数仓(四)【DWD 层搭建(二)流量域事实表】
  • enq: HW - contention事件来啦
  • 【翻译】Mashape是如何管理15000个API和微服务的(三)
  • css系列之关于字体的事
  • express.js的介绍及使用
  • Golang-长连接-状态推送
  • IP路由与转发
  • JWT究竟是什么呢?
  • mysql常用命令汇总
  • PHP的Ev教程三(Periodic watcher)
  • ReactNativeweexDeviceOne对比
  • SSH 免密登录
  • vue-cli在webpack的配置文件探究
  • vue中实现单选
  • Vultr 教程目录
  • 世界编程语言排行榜2008年06月(ActionScript 挺进20强)
  • [Shell 脚本] 备份网站文件至OSS服务(纯shell脚本无sdk) ...
  • ​​​​​​​Installing ROS on the Raspberry Pi
  • #if等命令的学习
  • #Linux(帮助手册)
  • %3cli%3e连接html页面,html+canvas实现屏幕截取
  • (BFS)hdoj2377-Bus Pass
  • (Python第六天)文件处理
  • (简单有案例)前端实现主题切换、动态换肤的两种简单方式
  • (切换多语言)vantUI+vue-i18n进行国际化配置及新增没有的语言包
  • (四)事件系统
  • (五)关系数据库标准语言SQL
  • (一)pytest自动化测试框架之生成测试报告(mac系统)
  • (原創) 人會胖會瘦,都是自我要求的結果 (日記)
  • (原創) 如何使用ISO C++讀寫BMP圖檔? (C/C++) (Image Processing)
  • (转)大型网站的系统架构
  • .NET “底层”异步编程模式——异步编程模型(Asynchronous Programming Model,APM)...
  • .net 后台导出excel ,word
  • .NetCore+vue3上传图片 Multipart body length limit 16384 exceeded.
  • .NET开源、简单、实用的数据库文档生成工具
  • .NET中使用Redis (二)
  • .net最好用的JSON类Newtonsoft.Json获取多级数据SelectToken
  • []T 还是 []*T, 这是一个问题
  • [2019红帽杯]Snake
  • [AS3]URLLoader+URLRequest+JPGEncoder实现BitmapData图片数据保存
  • [C#]使用OpenCvSharp图像滤波中值滤波均值滤波高通滤波双边滤波锐化滤波自定义滤波
  • [CodeForces-759D]Bacterial Melee