初始提交

This commit is contained in:
xiincs 2025-05-18 23:40:12 +08:00
parent 054883c6d3
commit d698fa8039
1503 changed files with 233138 additions and 1 deletions

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
* text=auto eol=lf
*.{bat,cmd} text eol=crlf

48
.gitignore vendored Normal file
View File

@ -0,0 +1,48 @@
######################################################################
# Build Tools
.gradle
/build/
!gradle/wrapper/gradle-wrapper.jar
target/
!.mvn/wrapper/maven-wrapper.jar
######################################################################
# IDE
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### JRebel ###
rebel.xml
### NetBeans ###
nbproject/private/
build/*
nbbuild/
nbdist/
.nb-gradle/
######################################################################
# Others
*.log
*.xml.versionsBackup
*.swp
!*/build/*.java
!*/build/*.html
!*/build/*.xml
.flattened-pom.xml

20
LICENSE Normal file
View File

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2023 ruoyi-ai
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

240
README.md
View File

@ -1 +1,239 @@
AI标书系统2.0-后端
# RuoYi AI
<!-- PROJECT SHIELDS -->
[![Contributors][contributors-shield]][contributors-url]
[![Forks][forks-shield]][forks-url]
[![Stargazers][stars-shield]][stars-url]
[![Issues][issues-shield]][issues-url]
[![MIT License][license-shield]][license-url]
<!-- PROJECT LOGO -->
<br />
<img style="text-align: center;" src="image/00.png" alt="Logo" width="150" height="150">
<h3 style="text-align: center;">快速搭建属于自己的 AI 助手平台</h3>
<p style="text-align: center;">
全新升级,开箱即用,简单高效
<br />
<a href="https://doc.pandarobot.chat"><strong>探索本项目的文档 »</strong></a>
<br />
<br />
<a href="https://web.pandarobot.chat">项目预览</a>
·
<a href="https://github.com/ageerle/ruoyi-ai/issues">报告Bug</a>
·
<a href="https://github.com/ageerle/ruoyi-ai/issues">提出新特性</a>
</p>
## 目录
- [源码地址](#源码地址)
- [特色功能](#特色功能)
- [项目演示](#项目演示)
- [后台管理](#后台管理)
- [用户端](#用户端)
- [小程序端](#小程序端)
- [开发前的配置要求](#开发前的配置要求)
- [文件目录说明](#文件目录说明)
- [使用到的框架](#使用到的框架)
- [贡献者](#贡献者)
- [如何参与开源项目](#如何参与开源项目)
- [版本控制](#版本控制)
- [作者](#作者)
- [鸣谢](#鸣谢)
### 源码地址
- 项目文档: https://doc.pandarobot.chat
- 前端-后台管理: https://github.com/ageerle/ruoyi-admin
- 前端-用户端: https://github.com/ageerle/ruoyi-web
- 小程序端: https://github.com/ageerle/ruoyi-uniapp
- 演示地址: https://web.pandarobot.chat
- 后台管理: https://admin.pandarobot.chat
- 用户名: admin 密码admin123
-
### gitcode源码地址
- https://gitcode.com/ageerle/ruoyi-ai
- https://gitcode.com/ageerle/ruoyi-web
- https://gitcode.com/ageerle/ruoyi-admin
- https://gitcode.com/ageerle/ruoyi-uniapp
### 特色功能
1. 全套开源系统提供完整的前端应用、后台管理以及小程序应用基于MIT协议开箱即用。
2. 本地RAG方案集成Milvus/Weaviate向量库、本地向量化模型与Ollama实现本地化RAG
3. 丰富插件功能支持联网、SQL查询插件及Text2API插件扩展系统能力与应用场景。
4. 内置SSE、websocket等网络协议支持对接多种大语言模型同时还集成了MidJourney和DALLE AI绘画功能
5. 强大的多媒体功能支持AI翻译、PPT制作、语音克隆和翻唱等
6. 扩展功能:支持将大模型接入个人或企业微信
7. 支付功能:支持易支付、微信支付等多种支付方式
### 项目演示
#### 后台管理
<div style="display: flex; flex-wrap: wrap; gap: 20px; justify-content: center;">
<img src="image/02.png" alt="drawing" style="width: 600px; height: 300px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
<img src="image/03.png" alt="drawing" style="width: 600px; height: 300px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
<img src="image/04.png" alt="drawing" style="width: 600px; height: 300px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
<img src="image/05.png" alt="drawing" style="width: 600px; height: 300px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
</div>
#### 用户端
<div style="display: flex; flex-wrap: wrap; gap: 20px; justify-content: center;">
<img src="image/08.png" alt="drawing" style="width: 600px; height: 300px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
<img src="image/09.png" alt="drawing" style="width: 600px; height: 300px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
<img src="image/10.png" alt="drawing" style="width: 600px; height: 300px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
<img src="image/11.png" alt="drawing" style="width: 600px; height: 300px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
</div>
#### 小程序端
<div style="display: flex; flex-wrap: wrap; gap: 20px; justify-content: flex-start;">
<img src="image/06.png" alt="drawing" style="width: 320px; height: 600px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
<img src="image/07.png" alt="drawing" style="width: 320px; height: 600px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
</div>
### 开发前的配置要求
1. jdk 17
2. mysql 5.7、8.0
3. redis 版本必须 >= 5.X
4. maven 3.8+
5. nodejs 20+ & pnpm
### 文件目录说明
RuoYi-AI
```
├─ ruoyi-admin // 管理模块
│ └─ RuoYiApplication // 启动类
│ └─ RuoYiServletInitializer // 容器部署初始化类
│ └─ resources // 资源文件
│ └─ i18n/messages.properties // 国际化配置文件
│ └─ application.yml // 框架总配置文件
│ └─ application-dev.yml // 开发环境配置文件
│ └─ application-prod.yml // 生产环境配置文件
│ └─ banner.txt // 框架启动图标
│ └─ logback-plus.xml // 日志配置文件
│ └─ ip2region.xdb // IP区域地址库
├─ ruoyi-common // 通用模块
│ └─ ruoyi-common-bom // common依赖包管理
└─ ruoyi-common-chat // 聊天模块
│ └─ ruoyi-common-core // 核心模块
│ └─ ruoyi-common-doc // 系统接口模块
│ └─ ruoyi-common-encrypt // 数据加解密模块
│ └─ ruoyi-common-excel // excel模块
│ └─ ruoyi-common-idempotent // 幂等功能模块
│ └─ ruoyi-common-json // 序列化模块
│ └─ ruoyi-common-log // 日志模块
│ └─ ruoyi-common-mail // 邮件模块
│ └─ ruoyi-common-mybatis // 数据库模块
│ └─ ruoyi-common-oss // oss服务模块
│ └─ ruoyi-common-pay // 支付模块
│ └─ ruoyi-common-ratelimiter // 限流功能模块
│ └─ ruoyi-common-redis // 缓存服务模块
│ └─ ruoyi-common-satoken // satoken模块
│ └─ ruoyi-common-security // 安全模块
│ └─ ruoyi-common-sensitive // 脱敏模块
│ └─ ruoyi-common-sms // 短信模块
│ └─ ruoyi-common-tenant // 租户模块
│ └─ ruoyi-common-translation // 通用翻译模块
│ └─ ruoyi-common-web // web模块
├─ ruoyi-modules // 模块组
│ └─ ruoyi-demo // 演示模块
│ └─ ruoyi-system // 业务模块
├─ .run // 执行脚本文件
├─ .editorconfig // 编辑器编码格式配置
├─ LICENSE // 开源协议
├─ pom.xml // 公共依赖
├─ README.md // 框架说明文件
```
### 版本控制
该项目使用Git进行版本管理。您可以在repository参看当前可用版本。
### 版权说明
该项目使用了MIT授权许可详情请参阅 [LICENSE.txt](https://github.com/ageerle/ruoyi-ai/blob/main/LICENSE)
### 作者寄语
最近我们的项目意外地受到了广泛关注甚至被许多人误以为是一个已经成熟且能够快速落地的项目。然而事实并非如此。这个项目是我个人在业余时间进行的研究主要目的是学习和探索。它是一个以人工智能AI为核心的平台旨在帮助企业通过配置的方式快速构建AI应用。
#### 项目现状
目前项目还处于早期阶段距离成熟还有很长的路要走。由于个人精力有限项目的发展速度受到了一定的限制。为了加快项目的进度我真诚地希望更多人能够参与到项目中来。无论是经验丰富的开发者还是刚刚入门的小白我都热烈欢迎你们提交Pull RequestPR。即使代码修改得很少或者存在一些错误都没有关系。我会认真审核每一位贡献者的代码并和大家一起完善项目。
#### 开发计划
- 智能体管理
通过设置提示词、插件、知识库等用户可以快速构建一个AI应用。这将极大地简化AI应用的开发流程降低开发门槛使更多企业能够轻松地利用AI技术。
<div>
<img src="image/13.png" alt="drawing" width="600px" height="300px"/>
</div>
- 流程编排
通过流程编排功能,用户可以将不同的模型按照业务逻辑进行有序连接。这将解决单一模型能力不足的问题,充分发挥多个模型的协同作用,从而更好地满足企业的复杂业务需求。
- 感谢
最后我要感谢RuoYi-Vue-Plus、chatgpt-java、chatgpt-web-midjourney-proxy等优秀框架。正是因为这些项目的开源和共享我才能够在这个基础上进行开发使我们的项目能够取得今天的成果。再次感谢这些项目及其背后的开发者们
希望更多志同道合的朋友能够加入我们共同推动这个项目的发展。让我们一起努力将这个项目打造成一个真正成熟、实用的AI平台
#### 如何参与开源项目
贡献使开源社区成为一个学习、激励和创造的绝佳场所。你所作的任何贡献都是**非常感谢**的。
1. Fork 这个项目
2. 创建你的功能分支 (`git checkout -b feature/dev`)
3. 提交你的更改 (`git commit -m 'Add some dev'`)
4. 推送到分支 (`git push origin feature/dev`)
5. 打开拉取请求
6. pr请提交到GitHub上会定时同步到gitee
#### 项目文档
1. 项目文档基于vitepress构建
2. 按照[如何参与开源项目](#如何参与开源项目)拉取 https://github.com/ageerle/ruoyi-doc
3. 安装依赖npm install
4. 启动项目npm run docs:dev
5. 主页路径docs/guide/introduction/index.md
### 鸣谢
- [chatgpt-java](https://github.com/Grt1228/chatgpt-java)
- [RuoYi-Vue-Plus](https://gitee.com/dromara/RuoYi-Vue-Plus)
- [chatgpt-web-midjourney-proxy](https://github.com/Dooy/chatgpt-web-midjourney-proxy)
- [Vben Admin](https://github.com/vbenjs/vue-vben-admin)
- [Naive UI](https://www.naiveui.com)
<!-- links -->
[your-project-path]:https://github.com/ageerle/ruoyi-ai
[contributors-shield]: https://img.shields.io/github/contributors/ageerle/ruoyi-ai.svg?style=flat-square
[contributors-url]: https://github.com/ageerle/ruoyi-ai/graphs/contributors
[forks-shield]: https://img.shields.io/github/forks/ageerle/ruoyi-ai.svg?style=flat-square
[forks-url]: https://github.com/ageerle/ruoyi-ai/network/members
[stars-shield]: https://img.shields.io/github/stars/ageerle/ruoyi-ai.svg?style=flat-square
[stars-url]: https://github.com/ageerle/ruoyi-ai/stargazers
[issues-shield]: https://img.shields.io/github/issues/ageerle/ruoyi-ai.svg?style=flat-square
[issues-url]: https://img.shields.io/github/issues/ageerle/ruoyi-ai.svg
[license-shield]: https://img.shields.io/github/license/ageerle/ruoyi-ai.svg?style=flat-square
[license-url]: https://github.com/ageerle/ruoyi-ai/blob/master/LICENSE.txt
[linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=flat-square&logo=linkedin&colorB=555

View File

@ -0,0 +1,35 @@
#基础镜像
FROM findepi/graalvm:java17-native
# 设置环境变量
ENV LANG C.UTF-8
ENV LANGUAGE C.UTF-8
ENV LC_ALL C.UTF-8
ENV SERVER_PORT=6039
MAINTAINER ageerle
RUN mkdir -p /aibidding/server/logs \
/aibidding/server/temp \
/aibidding/skywalking/agent
#工作空间
WORKDIR /ruoyi/server
EXPOSE ${SERVER_PORT}
ADD ./target/ruoyi-admin.jar ./app.jar
ENTRYPOINT ["java", \
"-Djava.security.egd=file:/dev/./urandom", \
"-Dserver.port=${SERVER_PORT}", \
# 应用名称 如果想区分集群节点监控 改成不同的名称即可
# "-Dskywalking.agent.service_name=aibidding-server", \
# "-javaagent:/aibidding/skywalking/agent/skywalking-agent.jar", \
"-jar", "app.jar"]

129
aibidding-admin/pom.xml Normal file
View File

@ -0,0 +1,129 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ruoyi-ai</artifactId>
<groupId>org.ruoyi</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<artifactId>aibidding-admin</artifactId>
<description>
web服务入口
</description>
<dependencies>
<!-- Mysql驱动包 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!-- Oracle -->
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc8</artifactId>
</dependency>
<!-- PostgreSql -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<!-- SqlServer -->
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>aibidding-common-doc</artifactId>
</dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>aibidding-system</artifactId>
</dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>aibidding-fusion</artifactId>
</dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>aibidding-knowledge</artifactId>
</dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>aibidding-generator</artifactId>
</dependency>
<!-- demo模块 -->
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>aibidding-demo</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 添加thumbnailator依赖 -->
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>0.4.11</version>
</dependency>
<dependency>
<groupId>io.github.ollama4j</groupId>
<artifactId>ollama4j</artifactId>
<version>1.0.79</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>${maven-jar-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>${maven-war-plugin.version}</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
<warName>${project.artifactId}</warName>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,21 @@
package org.aibidding;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
/**
* 启动程序
*
* @author Lion Li
*/
@SpringBootApplication
public class RuoYiAIApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(RuoYiAIApplication.class);
application.setApplicationStartup(new BufferingApplicationStartup(2048));
application.run(args);
System.out.println("(♥◠‿◠)ノ゙ AIBidding2.0系统启动成功 ლ(´ڡ`ლ)゙");
}
}

View File

@ -0,0 +1,18 @@
package org.aibidding;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
/**
* web容器中进行部署
*
* @author Lion Li
*/
public class RuoYiAIServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(RuoYiAIApplication.class);
}
}

View File

@ -0,0 +1,174 @@
package org.aibidding.controller;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.json.JSONUtil;
import me.chanjar.weixin.common.error.WxErrorException;
import org.aibidding.common.core.constant.Constants;
import org.aibidding.common.core.domain.R;
import org.aibidding.common.core.domain.model.EmailLoginBody;
import org.aibidding.common.core.domain.model.LoginBody;
import org.aibidding.common.core.domain.model.RegisterBody;
import org.aibidding.common.core.domain.model.SmsLoginBody;
import org.aibidding.common.core.domain.model.VisitorLoginBody;
import org.aibidding.common.core.utils.MapstructUtils;
import org.aibidding.common.core.utils.StreamUtils;
import org.aibidding.common.core.utils.StringUtils;
import org.aibidding.common.satoken.utils.LoginHelper;
import org.aibidding.common.tenant.helper.TenantHelper;
import org.aibidding.system.domain.bo.SysTenantBo;
import org.aibidding.system.domain.vo.LoginTenantVo;
import org.aibidding.system.domain.vo.SysTenantVo;
import org.aibidding.system.domain.vo.TenantListVo;
import org.aibidding.system.service.ISysTenantService;
import org.aibidding.system.service.SysLoginService;
import org.aibidding.system.service.SysRegisterService;
import org.aibidding.system.domain.vo.LoginVo;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.net.URL;
import java.util.List;
/**
* 认证
*
* @author Lion Li
*/
@SaIgnore
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/auth")
public class AuthController {
private final SysLoginService loginService;
private final SysRegisterService registerService;
private final ISysTenantService tenantService;
@PostMapping("/xcxLogin")
public R<LoginVo> login(@Validated @RequestBody String xcxCode) throws WxErrorException {
String openidFromCode = loginService.getOpenidFromCode((String) JSONUtil.parseObj(xcxCode).get("xcxCode"));
LoginVo loginVo = loginService.mpLogin(openidFromCode);
return R.ok(loginVo);
}
/**
* 登录方法
*
* @param body 登录信息
* @return 结果
*/
@PostMapping("/login")
public R<LoginVo> login(@Validated @RequestBody LoginBody body) {
body.setTenantId(Constants.TENANT_ID);
LoginVo loginVo = new LoginVo();
// 生成令牌
String token = loginService.login(
body.getTenantId(),
body.getUsername(), body.getPassword(),
body.getCode(), body.getUuid());
loginVo.setToken(token);
loginVo.setUserInfo(LoginHelper.getLoginUser());
return R.ok(loginVo);
}
/**
* 短信登录
*
* @param body 登录信息
* @return 结果
*/
@PostMapping("/smsLogin")
public R<LoginVo> smsLogin(@Validated @RequestBody SmsLoginBody body) {
LoginVo loginVo = new LoginVo();
// 生成令牌
String token = loginService.smsLogin(body.getTenantId(), body.getPhonenumber(), body.getSmsCode());
loginVo.setToken(token);
return R.ok(loginVo);
}
/**
* 访客登录
*
* @param loginBody 登录信息
* @return token信息
*/
@PostMapping("/visitorLogin")
public R<LoginVo> visitorLogin(@RequestBody VisitorLoginBody loginBody) {
LoginVo loginVo = new LoginVo();
return R.ok(loginVo);
}
/**
* 邮件登录
*
* @param body 登录信息
* @return 结果
*/
@PostMapping("/emailLogin")
public R<LoginVo> emailLogin(@Validated @RequestBody EmailLoginBody body) {
LoginVo loginVo = new LoginVo();
// 生成令牌
String token = loginService.emailLogin(body.getTenantId(), body.getEmail(), body.getEmailCode());
loginVo.setToken(token);
return R.ok(loginVo);
}
/**
* 退出登录
*/
@PostMapping("/logout")
public R<Void> logout() {
loginService.logout();
return R.ok("退出成功");
}
/**
* 用户注册
*/
@PostMapping("/register")
public R<Void> register(@Validated @RequestBody RegisterBody user, HttpServletRequest request) {
String domainName = request.getServerName();
user.setDomainName(domainName);
registerService.register(user);
return R.ok();
}
/**
* 重置密码
*/
@PostMapping("/reset/password")
@SaIgnore
public R<Void> resetPassWord(@Validated @RequestBody RegisterBody user) {
registerService.resetPassWord(user);
return R.ok();
}
/**
* 登录页面租户下拉框
*
* @return 租户列表
*/
@GetMapping("/tenant/list")
public R<LoginTenantVo> tenantList(HttpServletRequest request) throws Exception {
List<SysTenantVo> tenantList = tenantService.queryList(new SysTenantBo());
List<TenantListVo> voList = MapstructUtils.convert(tenantList, TenantListVo.class);
// 获取域名
String host = new URL(request.getRequestURL().toString()).getHost();
// 根据域名进行筛选
List<TenantListVo> list = StreamUtils.filter(voList, vo -> StringUtils.equals(vo.getDomain(), host));
// 返回对象
LoginTenantVo vo = new LoginTenantVo();
vo.setVoList(CollUtil.isNotEmpty(list) ? list : voList);
vo.setTenantEnabled(TenantHelper.isEnable());
return R.ok(vo);
}
}

View File

@ -0,0 +1,152 @@
package org.aibidding.controller;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.hutool.captcha.AbstractCaptcha;
import cn.hutool.captcha.generator.CodeGenerator;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import org.aibidding.common.core.constant.Constants;
import org.aibidding.common.core.constant.GlobalConstants;
import org.aibidding.common.core.domain.R;
import org.aibidding.common.core.exception.ServiceException;
import org.aibidding.common.core.service.ConfigService;
import org.aibidding.common.core.utils.SpringUtils;
import org.aibidding.common.core.utils.StringUtils;
import org.aibidding.common.core.utils.reflect.ReflectUtils;
import org.aibidding.common.mail.utils.MailUtils;
import org.aibidding.common.redis.utils.RedisUtils;
import org.aibidding.common.sms.config.properties.SmsProperties;
import org.aibidding.common.sms.core.SmsTemplate;
import org.aibidding.common.sms.entity.SmsResult;
import org.aibidding.common.web.config.properties.CaptchaProperties;
import org.aibidding.common.web.enums.CaptchaType;
import org.aibidding.system.domain.request.EmailRequest;
import org.aibidding.system.domain.vo.CaptchaVo;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.time.Duration;
import java.util.*;
/**
* 验证码操作处理
*
* @author Lion Li
*/
@SaIgnore
@Slf4j
@Validated
@RequiredArgsConstructor
@RestController
public class CaptchaController {
private final CaptchaProperties captchaProperties;
private final SmsProperties smsProperties;
private final ConfigService configService;
/**
* 短信验证码
*
* @param phonenumber 用户手机号
*/
@GetMapping("/resource/sms/code")
public R<Void> smsCode(@NotBlank(message = "{user.phonenumber.not.blank}") String phonenumber) {
if (!smsProperties.getEnabled()) {
return R.fail("当前系统没有开启短信功能!");
}
String key = GlobalConstants.CAPTCHA_CODE_KEY + phonenumber;
String code = RandomUtil.randomNumbers(4);
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
// 验证码模板id 自行处理 (查数据库或写死均可)
String templateId = "";
Map<String, String> map = new HashMap<>(1);
map.put("code", code);
SmsTemplate smsTemplate = SpringUtils.getBean(SmsTemplate.class);
SmsResult result = smsTemplate.send(phonenumber, templateId, map);
if (!result.isSuccess()) {
log.error("验证码短信发送异常 => {}", result);
return R.fail(result.getMessage());
}
return R.ok();
}
/**
* 邮箱验证码
*
* @param emailRequest 用户邮箱
*/
@PostMapping("/resource/email/code")
public R<Void> emailCode(@RequestBody @Valid EmailRequest emailRequest) {
String key = GlobalConstants.CAPTCHA_CODE_KEY + emailRequest.getUsername();
String code = RandomUtil.randomNumbers(4);
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
// 检验邮箱后缀
String suffix = configService.getConfigValue("mail", "suffix");
String prompt = configService.getConfigValue("mail", "prompt");
if(StringUtils.isNotEmpty(suffix)){
// 动态的域名列表
String[] invalidDomains = suffix.split(",");
for (String domain : invalidDomains) {
if (emailRequest.getUsername().endsWith(domain)) {
throw new ServiceException(prompt);
}
}
}
// 自定义邮箱模板
String model = configService.getConfigValue("mail", "mailModel");
String mailTitle = configService.getConfigValue("mail", "mailTitle");
String replacedModel = model.replace("{code}", code);
try {
MailUtils.sendHtml(emailRequest.getUsername(), mailTitle, replacedModel);
} catch (Exception e) {
log.error("邮箱验证码发送异常 => {}", e.getMessage());
return R.fail(e.getMessage());
}
return R.ok();
}
/**
* 生成验证码
*/
@GetMapping("/auth/code")
public R<CaptchaVo> getCode() {
CaptchaVo captchaVo = new CaptchaVo();
boolean captchaEnabled = captchaProperties.getEnable();
if (!captchaEnabled) {
captchaVo.setCaptchaEnabled(false);
return R.ok(captchaVo);
}
// 保存验证码信息
String uuid = IdUtil.simpleUUID();
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + uuid;
// 生成验证码
CaptchaType captchaType = captchaProperties.getType();
boolean isMath = CaptchaType.MATH == captchaType;
Integer length = isMath ? captchaProperties.getNumberLength() : captchaProperties.getCharLength();
CodeGenerator codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), length);
AbstractCaptcha captcha = SpringUtils.getBean(captchaProperties.getCategory().getClazz());
captcha.setGenerator(codeGenerator);
captcha.createCode();
String code = captcha.getCode();
if (isMath) {
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(StringUtils.remove(code, "="));
code = exp.getValue(String.class);
}
RedisUtils.setCacheObject(verifyKey, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
captchaVo.setUuid(uuid);
captchaVo.setImg(captcha.getImageBase64());
return R.ok(captchaVo);
}
}

View File

@ -0,0 +1,26 @@
package org.aibidding.controller;
import cn.dev33.satoken.annotation.SaIgnore;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 首页
*
* @author Lion Li
*/
@SaIgnore
@RequiredArgsConstructor
@RestController
public class IndexController {
/**
* 访问首页提示语
*/
@GetMapping("/")
public String index() {
return "RuoYi-AI 启动成功";
}
}

View File

@ -0,0 +1,208 @@
package org.aibidding.controller;
import cn.dev33.satoken.stp.StpUtil;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.aibidding.common.chat.config.ChatConfig;
import org.aibidding.common.chat.domain.request.ChatRequest;
import org.aibidding.common.chat.entity.chat.ChatCompletion;
import org.aibidding.common.chat.entity.chat.Message;
import org.aibidding.common.chat.openai.OpenAiStreamClient;
import org.aibidding.common.core.domain.R;
import org.aibidding.common.core.validate.AddGroup;
import org.aibidding.common.excel.utils.ExcelUtil;
import org.aibidding.common.log.annotation.Log;
import org.aibidding.common.log.enums.BusinessType;
import org.aibidding.common.mybatis.core.page.PageQuery;
import org.aibidding.common.mybatis.core.page.TableDataInfo;
import org.aibidding.common.satoken.utils.LoginHelper;
import org.aibidding.common.web.core.BaseController;
import org.aibidding.knowledge.domain.bo.KnowledgeAttachBo;
import org.aibidding.knowledge.domain.bo.KnowledgeFragmentBo;
import org.aibidding.knowledge.domain.bo.KnowledgeInfoBo;
import org.aibidding.knowledge.domain.req.KnowledgeInfoUploadRequest;
import org.aibidding.knowledge.domain.vo.KnowledgeAttachVo;
import org.aibidding.knowledge.domain.vo.KnowledgeFragmentVo;
import org.aibidding.knowledge.domain.vo.KnowledgeInfoVo;
import org.aibidding.knowledge.service.EmbeddingService;
import org.aibidding.knowledge.service.IKnowledgeAttachService;
import org.aibidding.knowledge.service.IKnowledgeFragmentService;
import org.aibidding.knowledge.service.IKnowledgeInfoService;
import org.aibidding.system.listener.SSEEventSourceListener;
import org.aibidding.system.service.ISseService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.aibidding.knowledge.chain.vectorstore.VectorStore;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.util.List;
/**
* 知识库
*
* @author Lion Li
* @date 2024-10-21
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/knowledge")
public class KnowledgeController extends BaseController {
private final IKnowledgeInfoService knowledgeInfoService;
private final VectorStore vectorStore;
private final IKnowledgeAttachService attachService;
private final IKnowledgeFragmentService fragmentService;
private final EmbeddingService embeddingService;
private OpenAiStreamClient openAiStreamClient;
private final ChatConfig chatConfig;
private final ISseService sseService;
/**
* 知识库对话
*/
@PostMapping("/send")
public SseEmitter send(@RequestBody @Valid ChatRequest chatRequest) {
openAiStreamClient = chatConfig.getOpenAiStreamClient();
SseEmitter sseEmitter = new SseEmitter(0L);
SSEEventSourceListener openAIEventSourceListener = new SSEEventSourceListener(sseEmitter);
List<Message> messages = chatRequest.getMessages();
String content = messages.get(messages.size() - 1).getContent().toString();
List<String> nearestList;
List<Double> queryVector = embeddingService.getQueryVector(content, chatRequest.getKid());
nearestList = vectorStore.nearest(queryVector,chatRequest.getKid());
for (String prompt : nearestList) {
Message sysMessage = Message.builder().content(prompt).role(Message.Role.USER).build();
messages.add(sysMessage);
}
Message userMessage = Message.builder().content(content + (nearestList.size() > 0 ? "\n\n注意回答问题时须严格根据我给你的系统上下文内容原文进行回答请不要自己发挥,回答时保持原来文本的段落层级" : "") ).role(Message.Role.USER).build();
messages.add(userMessage);
if (chatRequest.getModel().startsWith("ollama")) {
return sseService.ollamaChat(chatRequest);
}
ChatCompletion completion = ChatCompletion
.builder()
.messages(messages)
.model(chatRequest.getModel())
.temperature(chatRequest.getTemperature())
.topP(chatRequest.getTop_p())
.stream(true)
.build();
openAiStreamClient.streamChatCompletion(completion, openAIEventSourceListener);
return sseEmitter;
}
/**
* 根据用户信息查询本地知识库
*/
@GetMapping("/list")
public TableDataInfo<KnowledgeInfoVo> list(KnowledgeInfoBo bo, PageQuery pageQuery) {
if(!StpUtil.isLogin()){
return null;
}
bo.setUid(LoginHelper.getUserId());
return knowledgeInfoService.queryPageList(bo, pageQuery);
}
/**
* 新增知识库
*/
@Log(title = "知识库", businessType = BusinessType.INSERT)
@PostMapping("/save")
public R<Void> save(@Validated(AddGroup.class) @RequestBody KnowledgeInfoBo bo) {
knowledgeInfoService.saveOne(bo);
return R.ok();
}
/**
* 删除知识库
*/
@PostMapping("/remove/{id}")
public R<String> remove(@PathVariable String id){
knowledgeInfoService.removeKnowledge(id);
return R.ok("删除知识库成功!");
}
/**
* 修改知识库
*/
@Log(title = "知识库", businessType = BusinessType.UPDATE)
@PostMapping("/edit")
public R<Void> edit( @RequestBody KnowledgeInfoBo bo) {
return toAjax(knowledgeInfoService.updateByBo(bo));
}
/**
* 导出知识库列表
*/
@Log(title = "知识库", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(KnowledgeInfoBo bo, HttpServletResponse response) {
List<KnowledgeInfoVo> list = knowledgeInfoService.queryList(bo);
ExcelUtil.exportExcel(list, "知识库", KnowledgeInfoVo.class, response);
}
/**
* 查询知识附件信息
*/
@GetMapping("/detail/{kid}")
public TableDataInfo<KnowledgeAttachVo> attach(KnowledgeAttachBo bo, PageQuery pageQuery,@PathVariable String kid){
bo.setKid(kid);
return attachService.queryPageList(bo, pageQuery);
}
/**
* 上传知识库附件
*/
@PostMapping(value = "/attach/upload")
public R<String> upload(KnowledgeInfoUploadRequest request){
knowledgeInfoService.upload(request);
return R.ok("上传知识库附件成功!");
}
/**
* 获取知识库附件详细信息
*
* @param id 主键
*/
@GetMapping("attach/info/{id}")
public R<KnowledgeAttachVo> getAttachInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(attachService.queryById(id));
}
/**
* 删除知识库附件
*
*/
@PostMapping("attach/remove/{docId}")
public R<Void> removeAttach(@NotEmpty(message = "主键不能为空") @PathVariable String docId) {
attachService.removeKnowledgeAttach(docId);
return R.ok();
}
/**
* 查询知识片段
*/
@GetMapping("/fragment/list/{docId}")
public TableDataInfo<KnowledgeFragmentVo> fragmentList(KnowledgeFragmentBo bo, PageQuery pageQuery, @PathVariable String docId) {
bo.setDocId(docId);
return fragmentService.queryPageList(bo, pageQuery);
}
}

View File

@ -0,0 +1,99 @@
--- # 监控中心配置
spring.boot.admin.client:
# 增加客户端开关
enabled: false
url: http://localhost:9090/admin
instance:
service-host-type: IP
username: aibidding
password: 123456
--- # 数据源配置
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
# 动态数据源文档 https://www.kancloud.cn/tracy5546/dynamic-datasource/content
dynamic:
# 性能分析插件(有性能损耗 不建议生产环境使用)
p6spy: true
# 设置默认的数据源或者数据源组,默认值即为 master
primary: master
# 严格模式 匹配不到数据源则报错
strict: true
datasource:
# 主库数据源
master:
type: ${spring.datasource.type}
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://47.104.134.46:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
username: ry-vue
password: hTMeGJdpfLrLCwWD
hikari:
# 最大连接池数量
maxPoolSize: 20
# 最小空闲线程数量
minIdle: 10
# 配置获取连接等待超时的时间
connectionTimeout: 30000
# 校验超时时间
validationTimeout: 5000
# 空闲连接存活最大时间默认10分钟
idleTimeout: 600000
# 此属性控制池中连接的最长生命周期值0表示无限生命周期默认30分钟
maxLifetime: 1800000
# 连接测试query配置检测连接是否有效
connectionTestQuery: SELECT 1
# 多久检查一次连接的活性
keepaliveTime: 30000
--- # redis 单机配置(单机与集群只能开启一个另一个需要注释掉)
spring.data:
redis:
# 地址
host: 127.0.0.1
# 端口默认为6379
port: 6379
# 数据库索引
database: 0
# 密码(如没有密码请注释掉)
# password: 123456
# 连接超时时间
timeout: 10S
# 是否开启ssl
ssl: false
redisson:
# redis key前缀
keyPrefix:
# 线程池数量
threads: 4
# Netty线程池数量
nettyThreads: 8
# 单节点配置
singleServerConfig:
# 客户端名称
clientName: ${ruoyi.name}
# 最小空闲连接数
connectionMinimumIdleSize: 8
# 连接池大小
connectionPoolSize: 32
# 连接空闲超时,单位:毫秒
idleConnectionTimeout: 10000
# 命令等待超时,单位:毫秒
timeout: 3000
# 发布和订阅连接池大小
subscriptionConnectionPoolSize: 50
--- # sms 短信
sms:
enabled: false
# 阿里云 dysmsapi.aliyuncs.com
# 腾讯云 sms.tencentcloudapi.com
endpoint: "dysmsapi.aliyuncs.com"
accessKeyId: xxxxxxx
accessKeySecret: xxxxxx
signName: 测试
# 腾讯专用
sdkAppId:

View File

@ -0,0 +1,174 @@
--- # 临时文件存储位置 避免临时文件被系统清理报错
spring.servlet.multipart.location: /ruoyi/server/temp
--- # 监控中心配置
spring.boot.admin.client:
# 增加客户端开关
enabled: false
url: http://localhost:9090/admin
instance:
service-host-type: IP
username: aibidding
password: 123456
--- # xxl-job 配置
xxl.job:
# 执行器开关
enabled: false
# 调度中心地址:如调度中心集群部署存在多个地址则用逗号分隔。
admin-addresses: http://localhost:9100/xxl-job-admin
# 执行器通讯TOKEN非空时启用
access-token: xxl-job
executor:
# 执行器AppName执行器心跳注册分组依据为空则关闭自动注册
appname: xxl-job-executor
# 执行器端口号 执行器从9101开始往后写
port: 9101
# 执行器注册默认IP:PORT
address:
# 执行器IP默认自动获取IP
ip:
# 执行器运行日志文件存储磁盘路径
logpath: ./logs/xxl-job
# 执行器日志文件保存天数大于3生效
logretentiondays: 30
--- # 数据源配置
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
# 动态数据源文档 https://www.kancloud.cn/tracy5546/dynamic-datasource/content
dynamic:
# 性能分析插件(有性能损耗 不建议生产环境使用)
p6spy: false
# 设置默认的数据源或者数据源组,默认值即为 master
primary: master
# 严格模式 匹配不到数据源则报错
strict: true
datasource:
# 主库数据源
master:
type: ${spring.datasource.type}
driverClassName: com.mysql.cj.jdbc.Driver
# jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562
# rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
username: root
password: root
# 从库数据源
slave:
lazy: true
type: ${spring.datasource.type}
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
username:
password:
# oracle:
# type: ${spring.datasource.type}
# driverClassName: oracle.jdbc.OracleDriver
# url: jdbc:oracle:thin:@//localhost:1521/XE
# username: ROOT
# password: root
# hikari:
# connectionTestQuery: SELECT 1 FROM DUAL
# postgres:
# type: ${spring.datasource.type}
# driverClassName: org.postgresql.Driver
# url: jdbc:postgresql://localhost:5432/postgres?useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true
# username: root
# password: root
# sqlserver:
# type: ${spring.datasource.type}
# driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
# url: jdbc:sqlserver://localhost:1433;DatabaseName=tempdb;SelectMethod=cursor;encrypt=false;rewriteBatchedStatements=true
# username: SA
# password: root
hikari:
# 最大连接池数量
maxPoolSize: 20
# 最小空闲线程数量
minIdle: 10
# 配置获取连接等待超时的时间
connectionTimeout: 30000
# 校验超时时间
validationTimeout: 5000
# 空闲连接存活最大时间默认10分钟
idleTimeout: 600000
# 此属性控制池中连接的最长生命周期值0表示无限生命周期默认30分钟
maxLifetime: 1800000
# 连接测试query配置检测连接是否有效
connectionTestQuery: SELECT 1
# 多久检查一次连接的活性
keepaliveTime: 30000
--- # redis 单机配置(单机与集群只能开启一个另一个需要注释掉)
spring.data:
redis:
# 地址
host: localhost
# 端口默认为6379
port: 6379
# 数据库索引
database: 0
# 密码(如没有密码请注释掉)
# password:
# 连接超时时间
timeout: 10s
# 是否开启ssl
ssl: false
redisson:
# redis key前缀
keyPrefix:
# 线程池数量
threads: 16
# Netty线程池数量
nettyThreads: 32
# 单节点配置
singleServerConfig:
# 客户端名称
clientName: ${ruoyi.name}
# 最小空闲连接数
connectionMinimumIdleSize: 32
# 连接池大小
connectionPoolSize: 64
# 连接空闲超时,单位:毫秒
idleConnectionTimeout: 10000
# 命令等待超时,单位:毫秒
timeout: 3000
# 发布和订阅连接池大小
subscriptionConnectionPoolSize: 50
--- # mail 邮件发送
mail:
enabled: false
host: smtp.163.com
port: 465
# 是否需要用户名密码验证
auth: true
# 发送方遵循RFC-822标准
from: xxx@163.com
# 用户名注意如果使用foxmail邮箱此处user为qq号
user: xxx@163.com
# 密码注意某些邮箱需要为SMTP服务单独设置密码详情查看相关帮助
pass: xxxxxxxxxx
# 使用 STARTTLS安全连接STARTTLS是对纯文本通信协议的扩展。
starttlsEnable: true
# 使用SSL安全连接
sslEnable: true
# SMTP超时时长单位毫秒缺省值不超时
timeout: 0
# Socket连接超时值单位毫秒缺省值不超时
connectionTimeout: 0
--- # sms 短信
sms:
enabled: false
# 阿里云 dysmsapi.aliyuncs.com
# 腾讯云 sms.tencentcloudapi.com
endpoint: "dysmsapi.aliyuncs.com"
accessKeyId: xxxxxxx
accessKeySecret: xxxxxx
signName: 测试
# 腾讯专用
sdkAppId:

View File

@ -0,0 +1,322 @@
# 项目相关配置
ruoyi:
# 名称
name: "ruoyi"
# 版本
version: ${revision}
# 版权年份
copyrightYear: 2025
# 实例演示开关
demoEnabled: true
# 获取ip地址开关
addressEnabled: false
captcha:
enable: false
# 页面 <参数设置> 可开启关闭 验证码校验
# 验证码类型 math 数组计算 char 字符验证
type: MATH
# line 线段干扰 circle 圆圈干扰 shear 扭曲干扰
category: CIRCLE
# 数字验证码位数
numberLength: 1
# 字符验证码长度
charLength: 4
# 开发环境配置
server:
# 服务器的HTTP端口默认为8080
port: 6039
servlet:
# 应用的访问路径
context-path: /
# undertow 配置
undertow:
# HTTP post内容的最大大小。当值为-1时默认值为大小是无限的
max-http-post-size: -1
# 以下的配置会影响buffer,这些buffer会用于服务器连接的IO操作,有点类似netty的池化内存管理
# 每块buffer的空间大小,越小的空间被利用越充分
buffer-size: 512
# 是否分配的直接内存
direct-buffers: true
threads:
# 设置IO线程数, 它主要执行非阻塞的任务,它们会负责多个连接, 默认设置每个CPU核心一个线程
io: 8
# 阻塞任务线程池, 当执行类似servlet请求阻塞操作, undertow会从这个线程池中取得线程,它的值设置取决于系统的负载
worker: 256
# 日志配置
logging:
level:
org.ruoyi: @logging.level@
org.springframework: warn
config: classpath:logback-plus.xml
# 用户配置
user:
password:
# 密码最大错误次数
maxRetryCount: 5
# 密码锁定时间默认10分钟
lockTime: 10
# Spring配置
spring:
application:
name: ${ruoyi.name}
# 资源信息
messages:
# 国际化资源文件路径
basename: i18n/messages
profiles:
active: @profiles.active@
# 文件上传
servlet:
multipart:
# 单个文件大小
max-file-size: 50MB
# 设置总上传的文件大小
max-request-size: 200MB
mvc:
format:
date-time: yyyy-MM-dd HH:mm:ss
jackson:
# 日期格式化
date-format: yyyy-MM-dd HH:mm:ss
serialization:
# 格式化输出
indent_output: false
# 忽略无法转换的对象
fail_on_empty_beans: false
deserialization:
# 允许对象忽略json中不存在的属性
fail_on_unknown_properties: false
# Sa-Token配置
sa-token:
# token名称 (同时也是cookie名称)
token-name: Authorization
# token有效期 设为7天 (必定过期) 单位: 秒
timeout: 604800
# token临时有效期 (指定时间无操作就过期) 单位: 秒
activity-timeout: 604800
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
is-share: false
# 是否尝试从header里读取token
is-read-header: true
# 是否尝试从cookie里读取token
is-read-cookie: false
# token前缀
token-prefix: "Bearer"
# jwt秘钥
jwt-secret-key: abcdefghijklmnopqrstuvwxyz
# security配置
security:
# 排除路径
excludes:
# 支付回调
- /pay/returnUrl
- /pay/notifyUrl
# 上传文件
- /resource/oss/upload
# 重置密码
- /auth/reset/password
# 聊天接口
- /chat
# 静态资源
- /*.html
- /**/*.html
- /**/*.css
- /**/*.js
# 公共路径
- /favicon.ico
- /error
# swagger 文档配置
- /*/api-docs
- /*/api-docs/**
# actuator 监控配置
- /actuator
- /actuator/**
# 多租户配置
tenant:
# 是否开启
enable: false
# 排除表
excludes:
- sys_menu
- sys_tenant
- sys_tenant_package
- sys_role_dept
- sys_role_menu
- sys_user_post
- sys_user_role
# MyBatisPlus配置
# https://baomidou.com/config/
mybatis-plus:
# 不支持多包, 如有需要可在注解配置 或 提升扫包等级
# 例如 com.**.**.mapper
mapperPackage: org.aibidding.**.mapper
# 对应的 XML 文件位置
mapperLocations: classpath*:mapper/**/*Mapper.xml
# 实体扫描多个package用逗号或者分号分隔
typeAliasesPackage: org.ruoyi.**.domain
# 启动时是否检查 MyBatis XML 文件的存在,默认不检查
checkConfigLocation: false
configuration:
# 自动驼峰命名规则camel case映射
mapUnderscoreToCamelCase: true
# MyBatis 自动映射策略
# NONE不启用 PARTIAL只对非嵌套 resultMap 自动映射 FULL对所有 resultMap 自动映射
autoMappingBehavior: FULL
# MyBatis 自动映射时未知列或未知属性处理策
# NONE不做处理 WARNING打印相关警告 FAILING抛出异常和详细信息
autoMappingUnknownColumnBehavior: NONE
# 更详细的日志输出 会有性能损耗 org.apache.ibatis.logging.stdout.StdOutImpl
# 关闭日志记录 (可单纯使用 p6spy 分析) org.apache.ibatis.logging.nologging.NoLoggingImpl
# 默认日志输出 org.apache.ibatis.logging.slf4j.Slf4jImpl
logImpl: org.apache.ibatis.logging.nologging.NoLoggingImpl
global-config:
# 是否打印 Logo banner
banner: true
dbConfig:
# 主键类型
# AUTO 自增 NONE 空 INPUT 用户输入 ASSIGN_ID 雪花 ASSIGN_UUID 唯一 UUID
idType: ASSIGN_ID
# 逻辑已删除值
logicDeleteValue: 2
# 逻辑未删除值
logicNotDeleteValue: 0
# 字段验证策略之 insert,在 insert 的时候的字段验证策略
# IGNORED 忽略 NOT_NULL 非NULL NOT_EMPTY 非空 DEFAULT 默认 NEVER 不加入 SQL
insertStrategy: NOT_NULL
# 字段验证策略之 update,在 update 的时候的字段验证策略
updateStrategy: NOT_NULL
# 字段验证策略之 select,在 select 的时候的字段验证策略既 wrapper 根据内部 entity 生成的 where 条件
where-strategy: NOT_NULL
# 数据加密
mybatis-encryptor:
# 是否开启加密
enable: false
# 默认加密算法
algorithm: BASE64
# 编码方式 BASE64/HEX。默认BASE64
encode: BASE64
# 安全秘钥 对称算法的秘钥 如AESSM4
password:
# 公私钥 非对称算法的公私钥 如SM2RSA
publicKey:
privateKey:
# Swagger配置
swagger:
info:
# 标题
title: '标题:${ruoyi.name}多租户管理系统_接口文档'
# 描述
description: '描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...'
# 版本
version: '版本号: ${ruoyi.version}'
# 作者信息
contact:
name: ageerle
email: ageerle@163.com
url: https://gitee.com/ageerle/ruoyi-ai
components:
# 鉴权方式配置
security-schemes:
apiKey:
type: APIKEY
in: HEADER
name: ${sa-token.token-name}
springdoc:
api-docs:
# 是否开启接口文档
enabled: true
swagger-ui:
# 持久化认证数据
persistAuthorization: true
#这里定义了两个分组,可定义多个,也可以不定义
group-configs:
- group: 1.演示模块
packages-to-scan: org.ruoyi.demo
- group: 2.通用模块
packages-to-scan: org.ruoyi.web
- group: 3.系统模块
packages-to-scan: org.ruoyi.system
- group: 4.代码生成模块
packages-to-scan: org.ruoyi.generator
# 防止XSS攻击
xss:
# 过滤开关
enabled: true
# 排除链接(多个用逗号分隔)
excludes: /system/notice
# 匹配链接
urlPatterns: /system/*,/monitor/*,/tool/*
# 全局线程池相关配置
thread-pool:
# 是否开启线程池
enabled: false
# 队列最大长度
queueCapacity: 128
# 线程池维护线程所允许的空闲时间
keepAliveSeconds: 300
--- # 分布式锁 lock4j 全局配置
lock4j:
# 获取分布式锁超时时间,默认为 3000 毫秒
acquire-timeout: 3000
# 分布式锁的超时时间,默认为 30 秒
expire: 30000
--- # Actuator 监控端点的配置项
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: ALWAYS
logfile:
external-file: ./logs/sys-console.log
# websocket
websocket:
enabled: true
# 路径
path: '/resource/websocket'
# 设置访问源地址
allowedOrigins: '*'
# 微信小程序配置信息
wx:
miniapp:
configs:
- appid: # 你的appid
secret: # 你的secret
token: #微信小程序消息服务器配置的token
aesKey: #微信小程序消息服务器配置的EncodingAESKey
msgDataFormat: JSON
# 企业微信应用
wechat:
cp:
corpId:
appConfigs:
- agentId:
secret: ''
token: ''
aesKey: ''

View File

@ -0,0 +1,2 @@
Application Version: ${revision}
Spring Boot Version: ${spring-boot.version}

View File

@ -0,0 +1,54 @@
#错误消息
not.null=* 必须填写
user.jcaptcha.error=验证码错误
user.jcaptcha.expire=验证码已失效
user.not.exists=对不起, 您的账号:{0} 不存在.
user.password.not.match=用户不存在/密码错误
user.password.retry.limit.count=密码输入错误{0}次
user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定{1}分钟
user.password.delete=对不起,您的账号:{0} 已被删除
user.blocked=对不起,您的账号:{0} 已禁用,请联系管理员
role.blocked=角色已封禁,请联系管理员
user.logout.success=退出成功
length.not.valid=长度必须在{min}到{max}个字符之间
user.username.not.blank=用户名不能为空
user.username.not.valid=* 2到20个汉字、字母、数字或下划线组成且必须以非数字开头
user.username.length.valid=账户长度必须在{min}到{max}个字符之间
user.password.not.blank=用户密码不能为空
user.password.length.valid=用户密码长度必须在{min}到{max}个字符之间
user.password.not.valid=* 5-50个字符
user.email.not.valid=邮箱格式错误
user.email.not.blank=邮箱不能为空
user.phonenumber.not.blank=用户手机号不能为空
user.mobile.phone.number.not.valid=手机号格式错误
user.login.success=登录成功
user.register.success=注册成功
user.register.save.error=保存用户 {0} 失败,注册账号已存在
user.register.error=注册失败,请联系系统管理人员
user.notfound=请重新登录
user.forcelogout=管理员强制退出,请重新登录
user.unknown.error=未知错误,请重新登录
##文件上传消息
upload.exceed.maxSize=上传的文件大小超出限制的文件大小!<br/>允许的文件最大大小是:{0}MB
upload.filename.exceed.length=上传的文件名最长{0}个字符
##权限
no.permission=您没有数据的权限,请联系管理员添加权限 [{0}]
no.create.permission=您没有创建数据的权限,请联系管理员添加权限 [{0}]
no.update.permission=您没有修改数据的权限,请联系管理员添加权限 [{0}]
no.delete.permission=您没有删除数据的权限,请联系管理员添加权限 [{0}]
no.export.permission=您没有导出数据的权限,请联系管理员添加权限 [{0}]
no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}]
repeat.submit.message=不允许重复提交,请稍候再试
rate.limiter.message=访问过于频繁,请稍候再试
sms.code.not.blank=短信验证码不能为空
sms.code.retry.limit.count=短信验证码输入错误{0}次
sms.code.retry.limit.exceed=短信验证码输入错误{0}次,帐户锁定{1}分钟
email.code.not.blank=邮箱验证码不能为空
email.code.retry.limit.count=邮箱验证码输入错误{0}次
email.code.retry.limit.exceed=邮箱验证码输入错误{0}次,帐户锁定{1}分钟
xcx.code.not.blank=小程序code不能为空
##租户
tenant.number.not.blank=租户编号不能为空
tenant.not.exists=对不起, 您的租户不存在,请联系管理员
tenant.blocked=对不起,您的租户已禁用,请联系管理员
tenant.expired=对不起,您的租户已过期,请联系管理员

View File

@ -0,0 +1,54 @@
#错误消息
not.null=* Required fill in
user.jcaptcha.error=Captcha error
user.jcaptcha.expire=Captcha invalid
user.not.exists=Sorry, your account: {0} does not exist
user.password.not.match=User does not exist/Password error
user.password.retry.limit.count=Password input error {0} times
user.password.retry.limit.exceed=Password input error {0} times, account locked for {1} minutes
user.password.delete=Sorry, your account{0} has been deleted
user.blocked=Sorry, your account: {0} has been disabled. Please contact the administrator
role.blocked=Role disabledplease contact administrators
user.logout.success=Exit successful
length.not.valid=The length must be between {min} and {max} characters
user.username.not.blank=Username cannot be blank
user.username.not.valid=* 2 to 20 chinese characters, letters, numbers or underscores, and must start with a non number
user.username.length.valid=Account length must be between {min} and {max} characters
user.password.not.blank=Password cannot be empty
user.password.length.valid=Password length must be between {min} and {max} characters
user.password.not.valid=* 5-50 characters
user.email.not.valid=Mailbox format error
user.email.not.blank=Mailbox cannot be blank
user.phonenumber.not.blank=Phone number cannot be blank
user.mobile.phone.number.not.valid=Phone number format error
user.login.success=Login successful
user.register.success=Register successful
user.register.save.error=Failed to save user {0}, The registered account already exists
user.register.error=Register failed, please contact system administrator
user.notfound=Please login again
user.forcelogout=The administrator is forced to exitplease login again
user.unknown.error=Unknown error, please login again
##文件上传消息
upload.exceed.maxSize=The uploaded file size exceeds the limit file size<br/>the maximum allowed file size is{0}MB
upload.filename.exceed.length=The maximum length of uploaded file name is {0} characters
##权限
no.permission=You do not have permission to the dataplease contact your administrator to add permissions [{0}]
no.create.permission=You do not have permission to create dataplease contact your administrator to add permissions [{0}]
no.update.permission=You do not have permission to modify dataplease contact your administrator to add permissions [{0}]
no.delete.permission=You do not have permission to delete dataplease contact your administrator to add permissions [{0}]
no.export.permission=You do not have permission to export dataplease contact your administrator to add permissions [{0}]
no.view.permission=You do not have permission to view dataplease contact your administrator to add permissions [{0}]
repeat.submit.message=Repeat submit is not allowed, please try again later
rate.limiter.message=Visit too frequently, please try again later
sms.code.not.blank=Sms code cannot be blank
sms.code.retry.limit.count=Sms code input error {0} times
sms.code.retry.limit.exceed=Sms code input error {0} times, account locked for {1} minutes
email.code.not.blank=Email code cannot be blank
email.code.retry.limit.count=Email code input error {0} times
email.code.retry.limit.exceed=Email code input error {0} times, account locked for {1} minutes
xcx.code.not.blank=Mini program code cannot be blank
##租户
tenant.number.not.blank=Tenant number cannot be blank
tenant.not.exists=Sorry, your tenant does not exist. Please contact the administrator
tenant.blocked=Sorry, your tenant is disabled. Please contact the administrator
tenant.expired=Sorry, your tenant has expired. Please contact the administrator.

View File

@ -0,0 +1,54 @@
#错误消息
not.null=* 必须填写
user.jcaptcha.error=验证码错误
user.jcaptcha.expire=验证码已失效
user.not.exists=对不起, 您的账号:{0} 不存在.
user.password.not.match=用户不存在/密码错误
user.password.retry.limit.count=密码输入错误{0}次
user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定{1}分钟
user.password.delete=对不起,您的账号:{0} 已被删除
user.blocked=对不起,您的账号:{0} 已禁用,请联系管理员
role.blocked=角色已封禁,请联系管理员
user.logout.success=退出成功
length.not.valid=长度必须在{min}到{max}个字符之间
user.username.not.blank=用户名不能为空
user.username.not.valid=* 2到20个汉字、字母、数字或下划线组成且必须以非数字开头
user.username.length.valid=账户长度必须在{min}到{max}个字符之间
user.password.not.blank=用户密码不能为空
user.password.length.valid=用户密码长度必须在{min}到{max}个字符之间
user.password.not.valid=* 5-50个字符
user.email.not.valid=邮箱格式错误
user.email.not.blank=邮箱不能为空
user.phonenumber.not.blank=用户手机号不能为空
user.mobile.phone.number.not.valid=手机号格式错误
user.login.success=登录成功
user.register.success=注册成功
user.register.save.error=保存用户 {0} 失败,注册账号已存在
user.register.error=注册失败,请联系系统管理人员
user.notfound=请重新登录
user.forcelogout=管理员强制退出,请重新登录
user.unknown.error=未知错误,请重新登录
##文件上传消息
upload.exceed.maxSize=上传的文件大小超出限制的文件大小!<br/>允许的文件最大大小是:{0}MB
upload.filename.exceed.length=上传的文件名最长{0}个字符
##权限
no.permission=您没有数据的权限,请联系管理员添加权限 [{0}]
no.create.permission=您没有创建数据的权限,请联系管理员添加权限 [{0}]
no.update.permission=您没有修改数据的权限,请联系管理员添加权限 [{0}]
no.delete.permission=您没有删除数据的权限,请联系管理员添加权限 [{0}]
no.export.permission=您没有导出数据的权限,请联系管理员添加权限 [{0}]
no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}]
repeat.submit.message=不允许重复提交,请稍候再试
rate.limiter.message=访问过于频繁,请稍候再试
sms.code.not.blank=短信验证码不能为空
sms.code.retry.limit.count=短信验证码输入错误{0}次
sms.code.retry.limit.exceed=短信验证码输入错误{0}次,帐户锁定{1}分钟
email.code.not.blank=邮箱验证码不能为空
email.code.retry.limit.count=邮箱验证码输入错误{0}次
email.code.retry.limit.exceed=邮箱验证码输入错误{0}次,帐户锁定{1}分钟
xcx.code.not.blank=小程序code不能为空
##租户
tenant.number.not.blank=租户编号不能为空
tenant.not.exists=对不起, 您的租户不存在,请联系管理员
tenant.blocked=对不起,您的租户已禁用,请联系管理员
tenant.expired=对不起,您的租户已过期,请联系管理员

Binary file not shown.

View File

@ -0,0 +1,129 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="log.path" value="./logs"/>
<property name="console.log.pattern"
value="%red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}%n) - %msg%n"/>
<property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"/>
<!-- 控制台输出 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${console.log.pattern}</pattern>
<charset>utf-8</charset>
</encoder>
</appender>
<!-- 控制台输出 -->
<appender name="file_console" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-console.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>${log.path}/sys-console.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大 1天 -->
<maxHistory>1</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
<charset>utf-8</charset>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<!-- 过滤的级别 -->
<level>INFO</level>
</filter>
</appender>
<!-- 系统日志输出 -->
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-info.log</file>
<!-- 循环政策:基于时间创建日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 过滤的级别 -->
<level>INFO</level>
<!-- 匹配时的操作:接收(记录) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配时的操作:拒绝(不记录) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-error.log</file>
<!-- 循环政策:基于时间创建日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 过滤的级别 -->
<level>ERROR</level>
<!-- 匹配时的操作:接收(记录) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配时的操作:拒绝(不记录) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- info异步输出 -->
<appender name="async_info" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>512</queueSize>
<!-- 添加附加的appender,最多只能添加一个 -->
<appender-ref ref="file_info"/>
</appender>
<!-- error异步输出 -->
<appender name="async_error" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>512</queueSize>
<!-- 添加附加的appender,最多只能添加一个 -->
<appender-ref ref="file_error"/>
</appender>
<!-- 整合 skywalking 控制台输出 tid -->
<!-- <appender name="console" class="ch.qos.logback.core.ConsoleAppender">-->
<!-- <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">-->
<!-- <layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">-->
<!-- <pattern>[%tid] ${console.log.pattern}</pattern>-->
<!-- </layout>-->
<!-- <charset>utf-8</charset>-->
<!-- </encoder>-->
<!-- </appender>-->
<!-- 整合 skywalking 推送采集日志 -->
<!-- <appender name="sky_log" class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.log.GRPCLogClientAppender">-->
<!-- <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">-->
<!-- <layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">-->
<!-- <pattern>[%tid] ${console.log.pattern}</pattern>-->
<!-- </layout>-->
<!-- <charset>utf-8</charset>-->
<!-- </encoder>-->
<!-- </appender>-->
<!--系统操作日志-->
<root level="info">
<appender-ref ref="console" />
<appender-ref ref="async_info" />
<appender-ref ref="async_error" />
<appender-ref ref="file_console" />
<!-- <appender-ref ref="sky_log"/>-->
</root>
</configuration>

View File

@ -0,0 +1 @@
exclude=SELECT 1

View File

@ -0,0 +1,187 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.ruoyi</groupId>
<artifactId>aibidding-common-bom</artifactId>
<version>${revision}</version>
<packaging>pom</packaging>
<description>
ruoyi-common-bom common依赖项
</description>
<properties>
<revision>1.0.0</revision>
</properties>
<dependencyManagement>
<dependencies>
<!-- 核心模块 -->
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>aibidding-common-core</artifactId>
<version>${revision}</version>
</dependency>
<!-- 接口模块 -->
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>aibidding-common-doc</artifactId>
<version>${revision}</version>
</dependency>
<!-- excel -->
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>aibidding-common-excel</artifactId>
<version>${revision}</version>
</dependency>
<!-- 幂等 -->
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>aibidding-common-idempotent</artifactId>
<version>${revision}</version>
</dependency>
<!-- 日志记录 -->
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>aibidding-common-log</artifactId>
<version>${revision}</version>
</dependency>
<!-- 邮件服务 -->
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>aibidding-common-mail</artifactId>
<version>${revision}</version>
</dependency>
<!-- 数据库服务 -->
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>aibidding-common-mybatis</artifactId>
<version>${revision}</version>
</dependency>
<!-- OSS -->
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>aibidding-common-oss</artifactId>
<version>${revision}</version>
</dependency>
<!-- 限流 -->
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>aibidding-common-ratelimiter</artifactId>
<version>${revision}</version>
</dependency>
<!-- 缓存服务 -->
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>aibidding-common-redis</artifactId>
<version>${revision}</version>
</dependency>
<!-- satoken -->
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>aibidding-common-satoken</artifactId>
<version>${revision}</version>
</dependency>
<!-- 安全模块 -->
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>aibidding-common-security</artifactId>
<version>${revision}</version>
</dependency>
<!-- 短信模块 -->
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>aibidding-common-sms</artifactId>
<version>${revision}</version>
</dependency>
<!-- web服务 -->
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>aibidding-common-web</artifactId>
<version>${revision}</version>
</dependency>
<!-- 翻译模块 -->
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>aibidding-common-translation</artifactId>
<version>${revision}</version>
</dependency>
<!-- 脱敏模块 -->
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>aibidding-common-sensitive</artifactId>
<version>${revision}</version>
</dependency>
<!-- 序列化模块 -->
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>aibidding-common-json</artifactId>
<version>${revision}</version>
</dependency>
<!-- 数据库加解密模块 -->
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>aibidding-common-encrypt</artifactId>
<version>${revision}</version>
</dependency>
<!-- 租户模块 -->
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>aibidding-common-tenant</artifactId>
<version>${revision}</version>
</dependency>
<!-- chat模块 -->
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>aibidding-common-chat</artifactId>
<version>${revision}</version>
</dependency>
<!-- 微信模块 -->
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>aibidding-common-wechat</artifactId>
<version>${revision}</version>
</dependency>
<!-- AI绘画 -->
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>aibidding-fusion</artifactId>
<version>${revision}</version>
</dependency>
<!-- 支付模块 -->
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>aibidding-common-pay</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>

View File

@ -0,0 +1,118 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.ruoyi</groupId>
<artifactId>aibidding-common</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>aibidding-common-chat</artifactId>
<description>
ruoyi-common-chat 模块
</description>
<properties>
<retrofit2.version>2.9.0</retrofit2.version>
</properties>
<dependencies>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>aibidding-common-core</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-ai-openai</artifactId>
<version>1.0.0-beta.12</version>
</dependency>
<dependency>
<groupId>io.github.ollama4j</groupId>
<artifactId>ollama4j</artifactId>
<version>1.0.79</version>
</dependency>
<!-- 序列化模块 -->
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>aibidding-common-json</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>aibidding-common-redis</artifactId>
</dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>aibidding-common-satoken</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.retrofit2</groupId>
<artifactId>retrofit</artifactId>
<version>${retrofit2.version}</version>
</dependency>
<dependency>
<groupId>com.squareup.retrofit2</groupId>
<artifactId>converter-jackson</artifactId>
<version>${retrofit2.version}</version>
</dependency>
<dependency>
<groupId>com.squareup.retrofit2</groupId>
<artifactId>adapter-rxjava2</artifactId>
<version>${retrofit2.version}</version>
</dependency>
<dependency>
<groupId>com.knuddels</groupId>
<artifactId>jtokkit</artifactId>
<version>0.5.0</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.12</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>cn.bigmodel.openapi</groupId>
<artifactId>oapi-java-sdk</artifactId>
<version>release-V4-2.3.0</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp</groupId>
<artifactId>okhttp</artifactId>
<version>2.7.5</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,56 @@
package org.aibidding.common.chat.config;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import org.aibidding.common.chat.openai.OpenAiStreamClient;
import org.aibidding.common.chat.openai.function.KeyRandomStrategy;
import org.aibidding.common.chat.openai.interceptor.OpenAILogger;
import org.aibidding.common.core.service.ConfigService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
/**
* Chat配置类
*
* @date: 2023/5/16
*/
@Configuration
@RequiredArgsConstructor
public class ChatConfig {
@Getter
private OpenAiStreamClient openAiStreamClient;
private final ConfigService configService;
// 重启才会生效
@Bean
public OpenAiStreamClient openAiStreamClient() {
String apiHost = configService.getConfigValue("chat", "apiHost");
String apiKey = configService.getConfigValue("chat", "apiKey");
openAiStreamClient = createOpenAiStreamClient(apiHost,apiKey);
return openAiStreamClient;
}
public OpenAiStreamClient createOpenAiStreamClient(String apiHost, String apiKey) {
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(new OpenAILogger());
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(httpLoggingInterceptor)
.connectTimeout(30, TimeUnit.SECONDS)
.writeTimeout(600, TimeUnit.SECONDS)
.readTimeout(600, TimeUnit.SECONDS)
.build();
return OpenAiStreamClient.builder()
.apiHost(apiHost)
.apiKey(Collections.singletonList(apiKey))
.keyStrategy(new KeyRandomStrategy())
.okHttpClient(okHttpClient)
.build();
}
}

View File

@ -0,0 +1,36 @@
package org.aibidding.common.chat.config;
import cn.hutool.cache.CacheUtil;
import cn.hutool.cache.impl.TimedCache;
import cn.hutool.core.date.DateUnit;
import lombok.extern.slf4j.Slf4j;
/**
* 描述
*
* @author https:www.unfbx.com
* @date 2023-03-10
*/
@Slf4j
public class LocalCache {
/**
* 缓存时长
*/
public static final long TIMEOUT = 30 * DateUnit.MINUTE.getMillis();
/**
* 清理间隔
*/
private static final long CLEAN_TIMEOUT = 30 * DateUnit.MINUTE.getMillis();
/**
* 缓存对象
*/
public static final TimedCache<String, Object> CACHE = CacheUtil.newTimedCache(TIMEOUT);
static {
//启动定时任务
CACHE.schedulePrune(CLEAN_TIMEOUT);
}
}

View File

@ -0,0 +1,62 @@
package org.aibidding.common.chat.config;
import cn.hutool.core.util.StrUtil;
import org.aibidding.common.chat.config.properties.WebSocketProperties;
import org.aibidding.common.chat.handler.PlusWebSocketHandler;
import org.aibidding.common.chat.interceptor.PlusWebSocketInterceptor;
import org.aibidding.common.chat.listener.WebSocketTopicListener;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.server.HandshakeInterceptor;
/**
* WebSocket 配置
*
* @author zendwang
*/
@AutoConfiguration
@ConditionalOnProperty(value = "websocket.enabled", havingValue = "true")
@EnableConfigurationProperties(WebSocketProperties.class)
@EnableWebSocket
public class WebSocketConfig {
@Bean
public WebSocketConfigurer webSocketConfigurer(HandshakeInterceptor handshakeInterceptor,
WebSocketHandler webSocketHandler,
WebSocketProperties webSocketProperties) {
// 如果WebSocket的路径为空则设置默认路径为 "/websocket"
if (StrUtil.isBlank(webSocketProperties.getPath())) {
webSocketProperties.setPath("/websocket");
}
// 如果允许跨域访问的地址为空则设置为 "*"表示允许所有来源的跨域请求
if (StrUtil.isBlank(webSocketProperties.getAllowedOrigins())) {
webSocketProperties.setAllowedOrigins("*");
}
// 返回一个WebSocketConfigurer对象用于配置WebSocket
return registry -> registry
// 添加WebSocket处理程序和拦截器到指定路径设置允许的跨域来源
.addHandler(webSocketHandler, webSocketProperties.getPath())
.addInterceptors(handshakeInterceptor)
.setAllowedOrigins(webSocketProperties.getAllowedOrigins());
}
@Bean
public HandshakeInterceptor handshakeInterceptor() {
return new PlusWebSocketInterceptor();
}
@Bean
public WebSocketHandler webSocketHandler() {
return new PlusWebSocketHandler();
}
@Bean
public WebSocketTopicListener topicListener() {
return new WebSocketTopicListener();
}
}

View File

@ -0,0 +1,26 @@
package org.aibidding.common.chat.config.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* WebSocket 配置项
*
* @author zendwang
*/
@ConfigurationProperties("websocket")
@Data
public class WebSocketProperties {
private Boolean enabled;
/**
* 路径
*/
private String path;
/**
* 设置访问源地址
*/
private String allowedOrigins;
}

View File

@ -0,0 +1,15 @@
package org.aibidding.common.chat.constant;
/**
* 描述
*
* @author https:www.unfbx.com
* @since 2023-03-06
*/
public class OpenAIConst {
public final static String OPENAI_HOST = "https://api.openai.com/";
public final static int SUCCEED_CODE = 200;
}

View File

@ -0,0 +1,28 @@
package org.aibidding.common.chat.constant;
/**
* websocket的常量配置
*
* @author zendwang
*/
public interface WebSocketConstants {
/**
* websocketSession中的参数的key
*/
String LOGIN_USER_KEY = "loginUser";
/**
* 订阅的频道
*/
String WEB_SOCKET_TOPIC = "global:websocket";
/**
* 前端心跳检查的命令
*/
String PING = "ping";
/**
* 服务端心跳恢复的字符串
*/
String PONG = "pong";
}

View File

@ -0,0 +1,73 @@
package org.aibidding.common.chat.demo;
import cn.hutool.json.JSONUtil;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.sse.EventSource;
import okhttp3.sse.EventSourceListener;
import org.aibidding.common.chat.entity.chat.ChatCompletionResponse;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
/**
* 描述 sse
*
* @author https:www.unfbx.com
* 2023-06-15
*/
@Slf4j
public class ConsoleEventSourceListenerV2 extends EventSourceListener {
@Getter
String args = "";
final CountDownLatch countDownLatch;
public ConsoleEventSourceListenerV2(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void onOpen(EventSource eventSource, Response response) {
log.info("OpenAI建立sse连接...");
}
@Override
public void onEvent(EventSource eventSource, String id, String type, String data) {
log.info("OpenAI返回数据{}", data);
if (data.equals("[DONE]")) {
log.info("OpenAI返回数据结束了");
countDownLatch.countDown();
return;
}
ChatCompletionResponse chatCompletionResponse = JSONUtil.toBean(data, ChatCompletionResponse.class);
if(Objects.nonNull(chatCompletionResponse.getChoices().get(0).getDelta().getFunctionCall())){
args += chatCompletionResponse.getChoices().get(0).getDelta().getFunctionCall().getArguments();
}
}
@Override
public void onClosed(EventSource eventSource) {
log.info("OpenAI关闭sse连接...");
}
@SneakyThrows
@Override
public void onFailure(EventSource eventSource, Throwable t, Response response) {
if(Objects.isNull(response)){
log.error("OpenAI sse连接异常:{}", t);
eventSource.cancel();
return;
}
ResponseBody body = response.body();
if (Objects.nonNull(body)) {
log.error("OpenAI sse连接异常data{},异常:{}", body.string(), t);
} else {
log.error("OpenAI sse连接异常data{},异常:{}", response, t);
}
eventSource.cancel();
}
}

View File

@ -0,0 +1,92 @@
package org.aibidding.common.chat.demo;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.json.JSONUtil;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.sse.EventSource;
import okhttp3.sse.EventSourceListener;
import org.aibidding.common.chat.entity.chat.ChatCompletionResponse;
import org.aibidding.common.chat.entity.chat.Message;
import org.aibidding.common.chat.entity.chat.tool.ToolCallFunction;
import org.aibidding.common.chat.entity.chat.tool.ToolCalls;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
/**
* 描述 demo测试实现类仅供思路参考
*
* @author https:www.unfbx.com
* 2023-11-12
*/
@Slf4j
public class ConsoleEventSourceListenerV3 extends EventSourceListener {
@Getter
List<ToolCalls> choices = new ArrayList<>();
@Getter
ToolCalls toolCalls = new ToolCalls();
@Getter
ToolCallFunction toolCallFunction = ToolCallFunction.builder().name("").arguments("").build();
final CountDownLatch countDownLatch;
public ConsoleEventSourceListenerV3(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void onOpen(EventSource eventSource, Response response) {
log.info("OpenAI建立sse连接...");
}
@Override
public void onEvent(EventSource eventSource, String id, String type, String data) {
log.info("OpenAI返回数据{}", data);
if (data.equals("[DONE]")) {
log.info("OpenAI返回数据结束了");
return;
}
ChatCompletionResponse chatCompletionResponse = JSONUtil.toBean(data, ChatCompletionResponse.class);
Message delta = chatCompletionResponse.getChoices().get(0).getDelta();
if (CollectionUtil.isNotEmpty(delta.getToolCalls())) {
choices.addAll(delta.getToolCalls());
}
}
@Override
public void onClosed(EventSource eventSource) {
if(CollectionUtil.isNotEmpty(choices)){
toolCalls.setId(choices.get(0).getId());
toolCalls.setType(choices.get(0).getType());
choices.forEach(e -> {
toolCallFunction.setName(e.getFunction().getName());
toolCallFunction.setArguments(toolCallFunction.getArguments() + e.getFunction().getArguments());
toolCalls.setFunction(toolCallFunction);
});
}
log.info("OpenAI关闭sse连接...");
countDownLatch.countDown();
}
@SneakyThrows
@Override
public void onFailure(EventSource eventSource, Throwable t, Response response) {
if(Objects.isNull(response)){
log.error("OpenAI sse连接异常:{}", t);
eventSource.cancel();
return;
}
ResponseBody body = response.body();
if (Objects.nonNull(body)) {
log.error("OpenAI sse连接异常data{},异常:{}", body.string(), t);
} else {
log.error("OpenAI sse连接异常data{},异常:{}", response, t);
}
eventSource.cancel();
}
}

View File

@ -0,0 +1,417 @@
package org.aibidding.common.chat.demo;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.Builder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import org.junit.Before;
import org.junit.Test;
import org.aibidding.common.chat.entity.chat.*;
import org.aibidding.common.chat.entity.chat.tool.ToolCallFunction;
import org.aibidding.common.chat.entity.chat.tool.ToolCalls;
import org.aibidding.common.chat.entity.chat.tool.Tools;
import org.aibidding.common.chat.entity.chat.tool.ToolsFunction;
import org.aibidding.common.chat.openai.OpenAiClient;
import org.aibidding.common.chat.openai.OpenAiStreamClient;
import org.aibidding.common.chat.openai.function.KeyRandomStrategy;
import org.aibidding.common.chat.openai.interceptor.DynamicKeyOpenAiAuthInterceptor;
import org.aibidding.common.chat.openai.interceptor.OpenAILogger;
import org.aibidding.common.chat.openai.interceptor.OpenAiResponseInterceptor;
import org.aibidding.common.chat.openai.plugin.PluginAbstract;
import org.aibidding.common.chat.plugin.CmdPlugin;
import org.aibidding.common.chat.plugin.CmdReq;
import org.aibidding.common.chat.sse.ConsoleEventSourceListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* 描述
*
* @author ageerle@163.com
* date 2025/3/8
*/
@Slf4j
public class PluginTest {
private OpenAiClient openAiClient;
private OpenAiStreamClient openAiStreamClient;
@Before
public void before() {
//可以为null
// Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 7890));
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(new OpenAILogger());
//千万别再生产或者测试环境打开BODY级别日志
//生产或者测试环境建议设置为这三种级别NONE,BASIC,HEADERS,
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
OkHttpClient okHttpClient = new OkHttpClient
.Builder()
// .proxy(proxy)
.addInterceptor(httpLoggingInterceptor)
.addInterceptor(new OpenAiResponseInterceptor())
.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build();
openAiClient = OpenAiClient.builder()
//支持多key传入请求时候随机选择
.apiKey(Arrays.asList("sk-xx"))
//自定义key的获取策略默认KeyRandomStrategy
//.keyStrategy(new KeyRandomStrategy())
.keyStrategy(new KeyRandomStrategy())
.okHttpClient(okHttpClient)
//自己做了代理就传代理地址没有可不不传,(关注公众号回复openai 获取免费的测试代理地址)
.apiHost("https://api.pandarobot.chat/")
.build();
openAiStreamClient = OpenAiStreamClient.builder()
//支持多key传入请求时候随机选择
.apiKey(Arrays.asList("sk-xx"))
//自定义key的获取策略默认KeyRandomStrategy
.keyStrategy(new KeyRandomStrategy())
.authInterceptor(new DynamicKeyOpenAiAuthInterceptor())
.okHttpClient(okHttpClient)
//自己做了代理就传代理地址没有可不不传,(关注公众号回复openai 获取免费的测试代理地址)
.apiHost("https://api.pandarobot.chat/")
.build();
}
@Test
public void chatFunction() {
//模型GPT_3_5_TURBO_16K_0613
Message message = Message.builder().role(Message.Role.USER).content("给我输出一个长度为2的中文词语并解释下词语对应物品的用途").build();
//属性一
JSONObject wordLength = new JSONObject();
wordLength.put("type", "number");
wordLength.put("description", "词语的长度");
//属性二
JSONObject language = new JSONObject();
language.put("type", "string");
language.put("enum", Arrays.asList("zh", "en"));
language.put("description", "语言类型例如zh代表中文、en代表英语");
//参数
JSONObject properties = new JSONObject();
properties.put("wordLength", wordLength);
properties.put("language", language);
Parameters parameters = Parameters.builder()
.type("object")
.properties(properties)
.required(Collections.singletonList("wordLength")).build();
Functions functions = Functions.builder()
.name("getOneWord")
.description("获取一个指定长度和语言类型的词语")
.parameters(parameters)
.build();
ChatCompletion chatCompletion = ChatCompletion
.builder()
.messages(Collections.singletonList(message))
.functions(Collections.singletonList(functions))
.functionCall("auto")
.model(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName())
.build();
ChatCompletionResponse chatCompletionResponse = openAiClient.chatCompletion(chatCompletion);
ChatChoice chatChoice = chatCompletionResponse.getChoices().get(0);
log.info("构造的方法值:{}", chatChoice.getMessage().getFunctionCall());
log.info("构造的方法名称:{}", chatChoice.getMessage().getFunctionCall().getName());
log.info("构造的方法参数:{}", chatChoice.getMessage().getFunctionCall().getArguments());
WordParam wordParam = JSONUtil.toBean(chatChoice.getMessage().getFunctionCall().getArguments(), WordParam.class);
String oneWord = getOneWord(wordParam);
FunctionCall functionCall = FunctionCall.builder()
.arguments(chatChoice.getMessage().getFunctionCall().getArguments())
.name("getOneWord")
.build();
Message message2 = Message.builder().role(Message.Role.ASSISTANT).content("方法参数").functionCall(functionCall).build();
String content
= "{ " +
"\"wordLength\": \"3\", " +
"\"language\": \"zh\", " +
"\"word\": \"" + oneWord + "\"," +
"\"用途\": [\"直接吃\", \"做沙拉\", \"售卖\"]" +
"}";
Message message3 = Message.builder().role(Message.Role.FUNCTION).name("getOneWord").content(content).build();
List<Message> messageList = Arrays.asList(message, message2, message3);
ChatCompletion chatCompletionV2 = ChatCompletion
.builder()
.messages(messageList)
.model(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName())
.build();
ChatCompletionResponse chatCompletionResponseV2 = openAiClient.chatCompletion(chatCompletionV2);
log.info("自定义的方法返回值:{}",chatCompletionResponseV2.getChoices().get(0).getMessage().getContent());
}
@Test
public void plugin() {
CmdPlugin plugin = new CmdPlugin(CmdReq.class);
// 插件名称
plugin.setName("命令行工具");
// 方法名称
plugin.setFunction("openCmd");
// 方法说明
plugin.setDescription("提供一个命令行指令,比如<记事本>,指令使用中文,以function返回结果为准");
PluginAbstract.Arg arg = new PluginAbstract.Arg();
// 参数名称
arg.setName("cmd");
// 参数说明
arg.setDescription("命令行指令");
// 参数类型
arg.setType("string");
arg.setRequired(true);
plugin.setArgs(Collections.singletonList(arg));
Message message2 = Message.builder().role(Message.Role.USER).content("帮我打开计算器,结合上下文判断指令是否执行成功,只用回复成功或者失败").build();
List<Message> messages = new ArrayList<>();
messages.add(message2);
//有四个重载方法都可以使用
ChatCompletionResponse response = openAiClient.chatCompletionWithPlugin(messages,"gpt-4o-mini",plugin);
log.info("自定义的方法返回值:{}", response.getChoices().get(0).getMessage().getContent());
}
/**
* 自定义返回数据格式
*/
@Test
public void diyReturnDataModelChat() {
Message message = Message.builder().role(Message.Role.USER).content("随机输出10个单词使用json输出").build();
ChatCompletion chatCompletion = ChatCompletion
.builder()
.messages(Collections.singletonList(message))
.responseFormat(ResponseFormat.builder().type(ResponseFormat.Type.JSON_OBJECT.getName()).build())
.model(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName())
.build();
ChatCompletionResponse chatCompletionResponse = openAiClient.chatCompletion(chatCompletion);
chatCompletionResponse.getChoices().forEach(e -> System.out.println(e.getMessage()));
}
@Test
public void streamPlugin() {
WeatherPlugin plugin = new WeatherPlugin(WeatherReq.class);
plugin.setName("知心天气");
plugin.setFunction("getLocationWeather");
plugin.setDescription("提供一个地址,方法将会获取该地址的天气的实时温度信息。");
PluginAbstract.Arg arg = new PluginAbstract.Arg();
arg.setName("location");
arg.setDescription("地名");
arg.setType("string");
arg.setRequired(true);
plugin.setArgs(Collections.singletonList(arg));
// Message message1 = Message.builder().role(Message.Role.USER).content("秦始皇统一了哪六国。").build();
Message message2 = Message.builder().role(Message.Role.USER).content("获取上海市的天气现在多少度然后再给出3个推荐的户外运动。").build();
List<Message> messages = new ArrayList<>();
// messages.add(message1);
messages.add(message2);
//默认模型GPT_3_5_TURBO_16K_0613
//有四个重载方法都可以使用
openAiStreamClient.streamChatCompletionWithPlugin(messages, ChatCompletion.Model.GPT_4_1106_PREVIEW.getName(), new ConsoleEventSourceListener(), plugin);
CountDownLatch countDownLatch = new CountDownLatch(1);
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* tools使用示例
*/
@Test
public void toolsChat() {
Message message = Message.builder().role(Message.Role.USER).content("给我输出一个长度为2的中文词语并解释下词语对应物品的用途").build();
//属性一
JSONObject wordLength = new JSONObject();
wordLength.put("type", "number");
wordLength.put("description", "词语的长度");
//属性二
JSONObject language = new JSONObject();
language.put("type", "string");
language.put("enum", Arrays.asList("zh", "en"));
language.put("description", "语言类型例如zh代表中文、en代表英语");
//参数
JSONObject properties = new JSONObject();
properties.put("wordLength", wordLength);
properties.put("language", language);
Parameters parameters = Parameters.builder()
.type("object")
.properties(properties)
.required(Collections.singletonList("wordLength")).build();
Tools tools = Tools.builder()
.type(Tools.Type.FUNCTION.getName())
.function(ToolsFunction.builder().name("getOneWord").description("获取一个指定长度和语言类型的词语").parameters(parameters).build())
.build();
ChatCompletion chatCompletion = ChatCompletion
.builder()
.messages(Collections.singletonList(message))
.tools(Collections.singletonList(tools))
.model(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName())
.build();
ChatCompletionResponse chatCompletionResponse = openAiClient.chatCompletion(chatCompletion);
ChatChoice chatChoice = chatCompletionResponse.getChoices().get(0);
log.info("构造的方法值:{}", chatChoice.getMessage().getToolCalls());
ToolCalls openAiReturnToolCalls = chatChoice.getMessage().getToolCalls().get(0);
WordParam wordParam = JSONUtil.toBean(openAiReturnToolCalls.getFunction().getArguments(), WordParam.class);
String oneWord = getOneWord(wordParam);
ToolCallFunction tcf = ToolCallFunction.builder().name("getOneWord").arguments(openAiReturnToolCalls.getFunction().getArguments()).build();
ToolCalls tc = ToolCalls.builder().id(openAiReturnToolCalls.getId()).type(ToolCalls.Type.FUNCTION.getName()).function(tcf).build();
//构造tool call
Message message2 = Message.builder().role(Message.Role.ASSISTANT).content("方法参数").toolCalls(Collections.singletonList(tc)).build();
String content
= "{ " +
"\"wordLength\": \"3\", " +
"\"language\": \"zh\", " +
"\"word\": \"" + oneWord + "\"," +
"\"用途\": [\"直接吃\", \"做沙拉\", \"售卖\"]" +
"}";
Message message3 = Message.builder().toolCallId(openAiReturnToolCalls.getId()).role(Message.Role.TOOL).name("getOneWord").content(content).build();
List<Message> messageList = Arrays.asList(message, message2, message3);
ChatCompletion chatCompletionV2 = ChatCompletion
.builder()
.messages(messageList)
.model(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName())
.build();
ChatCompletionResponse chatCompletionResponseV2 = openAiClient.chatCompletion(chatCompletionV2);
log.info("自定义的方法返回值:{}", chatCompletionResponseV2.getChoices().get(0).getMessage().getContent());
}
/**
* tools流式输出使用示例
*/
@Test
public void streamToolsChat() {
CountDownLatch countDownLatch = new CountDownLatch(1);
ConsoleEventSourceListenerV3 eventSourceListener = new ConsoleEventSourceListenerV3(countDownLatch);
Message message = Message.builder().role(Message.Role.USER).content("给我输出一个长度为2的中文词语并解释下词语对应物品的用途").build();
//属性一
JSONObject wordLength = new JSONObject();
wordLength.put("type", "number");
wordLength.put("description", "词语的长度");
//属性二
JSONObject language = new JSONObject();
language.put("type", "string");
language.put("enum", Arrays.asList("zh", "en"));
language.put("description", "语言类型例如zh代表中文、en代表英语");
//参数
JSONObject properties = new JSONObject();
properties.put("wordLength", wordLength);
properties.put("language", language);
Parameters parameters = Parameters.builder()
.type("object")
.properties(properties)
.required(Collections.singletonList("wordLength")).build();
Tools tools = Tools.builder()
.type(Tools.Type.FUNCTION.getName())
.function(ToolsFunction.builder().name("getOneWord").description("获取一个指定长度和语言类型的词语").parameters(parameters).build())
.build();
ChatCompletion chatCompletion = ChatCompletion
.builder()
.messages(Collections.singletonList(message))
.tools(Collections.singletonList(tools))
.model(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName())
.build();
openAiStreamClient.streamChatCompletion(chatCompletion, eventSourceListener);
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
ToolCalls openAiReturnToolCalls = eventSourceListener.getToolCalls();
WordParam wordParam = JSONUtil.toBean(openAiReturnToolCalls.getFunction().getArguments(), WordParam.class);
String oneWord = getOneWord(wordParam);
ToolCallFunction tcf = ToolCallFunction.builder().name("getOneWord").arguments(openAiReturnToolCalls.getFunction().getArguments()).build();
ToolCalls tc = ToolCalls.builder().id(openAiReturnToolCalls.getId()).type(ToolCalls.Type.FUNCTION.getName()).function(tcf).build();
//构造tool call
Message message2 = Message.builder().role(Message.Role.ASSISTANT).content("方法参数").toolCalls(Collections.singletonList(tc)).build();
String content
= "{ " +
"\"wordLength\": \"3\", " +
"\"language\": \"zh\", " +
"\"word\": \"" + oneWord + "\"," +
"\"用途\": [\"直接吃\", \"做沙拉\", \"售卖\"]" +
"}";
Message message3 = Message.builder().toolCallId(openAiReturnToolCalls.getId()).role(Message.Role.TOOL).name("getOneWord").content(content).build();
List<Message> messageList = Arrays.asList(message, message2, message3);
ChatCompletion chatCompletionV2 = ChatCompletion
.builder()
.messages(messageList)
.model(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName())
.build();
CountDownLatch countDownLatch1 = new CountDownLatch(1);
openAiStreamClient.streamChatCompletion(chatCompletionV2, new ConsoleEventSourceListenerV3(countDownLatch));
try {
countDownLatch1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
countDownLatch1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Data
@Builder
static class WordParam {
private int wordLength;
@Builder.Default
private String language = "zh";
}
/**
* 获取一个词语(根据语言和字符长度查询)
* @param wordParam
* @return
*/
public String getOneWord(WordParam wordParam) {
List<String> zh = Arrays.asList("大香蕉", "哈密瓜", "苹果");
List<String> en = Arrays.asList("apple", "banana", "cantaloupe");
if (wordParam.getLanguage().equals("zh")) {
for (String e : zh) {
if (e.length() == wordParam.getWordLength()) {
return e;
}
}
}
if (wordParam.getLanguage().equals("en")) {
for (String e : en) {
if (e.length() == wordParam.getWordLength()) {
return e;
}
}
}
return "西瓜";
}
}

View File

@ -0,0 +1,24 @@
package org.aibidding.common.chat.demo;
import org.aibidding.common.chat.openai.plugin.PluginAbstract;
public class WeatherPlugin extends PluginAbstract<WeatherReq, WeatherResp> {
public WeatherPlugin(Class<?> r) {
super(r);
}
@Override
public WeatherResp func(WeatherReq args) {
WeatherResp weatherResp = new WeatherResp();
weatherResp.setTemp("25到28摄氏度");
weatherResp.setLevel(3);
return weatherResp;
}
@Override
public String content(WeatherResp weatherResp) {
return "当前天气温度:" + weatherResp.getTemp() + ",风力等级:" + weatherResp.getLevel();
}
}

View File

@ -0,0 +1,13 @@
package org.aibidding.common.chat.demo;
import lombok.Data;
import org.aibidding.common.chat.openai.plugin.PluginParam;
@Data
public class WeatherReq extends PluginParam {
/**
* 城市
*/
private String location;
}

View File

@ -0,0 +1,15 @@
package org.aibidding.common.chat.demo;
import lombok.Data;
@Data
public class WeatherResp {
/**
* 温度
*/
private String temp;
/**
* 风力等级
*/
private Integer level;
}

View File

@ -0,0 +1,223 @@
package org.aibidding.common.chat.demo;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zhipu.oapi.ClientV4;
import com.zhipu.oapi.Constants;
import com.zhipu.oapi.service.v4.tools.*;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import com.zhipu.oapi.service.v4.model.*;
import io.reactivex.Flowable;
import java.util.HashMap;
import java.util.Map;
public class WebSearchToolsTest {
private final static Logger logger = LoggerFactory.getLogger(WebSearchToolsTest.class);
private static final String API_SECRET_KEY = "xx";
private static final ClientV4 client = new ClientV4.Builder(API_SECRET_KEY)
.networkConfig(300, 100, 100, 100, TimeUnit.SECONDS)
.connectionPool(new okhttp3.ConnectionPool(8, 1, TimeUnit.SECONDS))
.build();
private static final ObjectMapper mapper = new ObjectMapper();
// 请自定义自己的业务id
private static final String requestIdTemplate = "mycompany-%d";
@Test
public void test1() throws JsonProcessingException {
// json 转换 ArrayList<SearchChatMessage>
String jsonString = "[\n" +
" {\n" +
" \"content\": \"今天武汉天气怎么样\",\n" +
" \"role\": \"user\"\n" +
" }\n" +
" ]";
ArrayList<SearchChatMessage> messages = new ObjectMapper().readValue(jsonString, new TypeReference<ArrayList<SearchChatMessage>>() {
});
String requestId = String.format(requestIdTemplate, System.currentTimeMillis());
WebSearchParamsRequest chatCompletionRequest = WebSearchParamsRequest.builder()
.model("web-search-pro")
.stream(Boolean.TRUE)
.messages(messages)
.requestId(requestId)
.build();
WebSearchApiResponse webSearchApiResponse = client.webSearchProStreamingInvoke(chatCompletionRequest);
if (webSearchApiResponse.isSuccess()) {
AtomicBoolean isFirst = new AtomicBoolean(true);
List<ChoiceDelta> choices = new ArrayList<>();
AtomicReference<WebSearchPro> lastAccumulator = new AtomicReference<>();
webSearchApiResponse.getFlowable().map(result -> result)
.doOnNext(accumulator -> {
{
if (isFirst.getAndSet(false)) {
logger.info("Response: ");
}
ChoiceDelta delta = accumulator.getChoices().get(0).getDelta();
if (delta != null && delta.getToolCalls() != null) {
logger.info("tool_calls: {}", mapper.writeValueAsString(delta.getToolCalls()));
}
choices.add(delta);
lastAccumulator.set(accumulator);
}
})
.doOnComplete(() -> System.out.println("Stream completed."))
.doOnError(throwable -> System.err.println("Error: " + throwable)) // Handle errors
.blockingSubscribe();// Use blockingSubscribe instead of blockingGet()
WebSearchPro chatMessageAccumulator = lastAccumulator.get();
webSearchApiResponse.setFlowable(null);// 打印前置空
webSearchApiResponse.setData(chatMessageAccumulator);
}
logger.info("model output: {}", mapper.writeValueAsString(webSearchApiResponse));
client.getConfig().getHttpClient().dispatcher().executorService().shutdown();
client.getConfig().getHttpClient().connectionPool().evictAll();
// List all active threads
for (Thread t : Thread.getAllStackTraces().keySet()) {
logger.info("Thread: " + t.getName() + " State: " + t.getState());
}
}
@Test
public void test2() throws JsonProcessingException {
// json 转换 ArrayList<SearchChatMessage>
String jsonString = "[\n" +
" {\n" +
" \"content\": \"今天天气怎么样\",\n" +
" \"role\": \"user\"\n" +
" }\n" +
" ]";
ArrayList<SearchChatMessage> messages = new ObjectMapper().readValue(jsonString, new TypeReference<ArrayList<SearchChatMessage>>() {
});
String requestId = String.format(requestIdTemplate, System.currentTimeMillis());
WebSearchParamsRequest chatCompletionRequest = WebSearchParamsRequest.builder()
.model("web-search-pro")
.stream(Boolean.FALSE)
.messages(messages)
.requestId(requestId)
.build();
WebSearchApiResponse webSearchApiResponse = client.invokeWebSearchPro(chatCompletionRequest);
logger.info("model output: {}", mapper.writeValueAsString(webSearchApiResponse));
}
@Test
public void testFunctionSSE() throws JsonProcessingException {
List<ChatMessage> messages = new ArrayList<>();
ChatMessage chatMessage = new ChatMessage(ChatMessageRole.USER.value(), "成都到北京要多久,天气如何");
messages.add(chatMessage);
String requestId = String.format(requestIdTemplate, System.currentTimeMillis());
// 函数调用参数构建部分
List<ChatTool> chatToolList = new ArrayList<>();
ChatTool chatTool = new ChatTool();
chatTool.setType(ChatToolType.FUNCTION.value());
ChatFunctionParameters chatFunctionParameters = new ChatFunctionParameters();
chatFunctionParameters.setType("object");
Map<String, Object> properties = new HashMap<>();
properties.put("location", new HashMap<String, Object>() {{
put("type", "string");
put("description", "城市,如:北京");
}});
properties.put("unit", new HashMap<String, Object>() {{
put("type", "string");
put("enum", new ArrayList<String>() {{
add("celsius");
add("fahrenheit");
}});
}});
chatFunctionParameters.setProperties(properties);
ChatFunction chatFunction = ChatFunction.builder()
.name("get_weather")
.description("Get the current weather of a location")
.parameters(chatFunctionParameters)
.build();
chatTool.setFunction(chatFunction);
chatToolList.add(chatTool);
HashMap<String, Object> extraJson = new HashMap<>();
extraJson.put("temperature", 0.5);
extraJson.put("max_tokens", 50);
ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
.model(Constants.ModelChatGLM4)
.stream(Boolean.TRUE)
.messages(messages)
.requestId(requestId)
.tools(chatToolList)
.toolChoice("auto")
.extraJson(extraJson)
.build();
ModelApiResponse sseModelApiResp = client.invokeModelApi(chatCompletionRequest);
if (sseModelApiResp.isSuccess()) {
AtomicBoolean isFirst = new AtomicBoolean(true);
List<Choice> choices = new ArrayList<>();
ChatMessageAccumulator chatMessageAccumulator = mapStreamToAccumulator(sseModelApiResp.getFlowable())
.doOnNext(accumulator -> {
{
if (isFirst.getAndSet(false)) {
logger.info("Response: ");
}
if (accumulator.getDelta() != null && accumulator.getDelta().getTool_calls() != null) {
String jsonString = mapper.writeValueAsString(accumulator.getDelta().getTool_calls());
logger.info("tool_calls: {}", jsonString);
}
if (accumulator.getDelta() != null && accumulator.getDelta().getContent() != null) {
logger.info(accumulator.getDelta().getContent());
}
choices.add(accumulator.getChoice());
}
})
.doOnComplete(System.out::println)
.lastElement()
.blockingGet();
ModelData data = new ModelData();
data.setChoices(choices);
data.setUsage(chatMessageAccumulator.getUsage());
data.setId(chatMessageAccumulator.getId());
data.setCreated(chatMessageAccumulator.getCreated());
data.setRequestId(chatCompletionRequest.getRequestId());
sseModelApiResp.setFlowable(null);// 打印前置空
sseModelApiResp.setData(data);
}
logger.info("model output: {}", mapper.writeValueAsString(sseModelApiResp));
}
public static Flowable<ChatMessageAccumulator> mapStreamToAccumulator(Flowable<ModelData> flowable) {
return flowable.map(chunk -> {
return new ChatMessageAccumulator(chunk.getChoices().get(0).getDelta(), null, chunk.getChoices().get(0), chunk.getUsage(), chunk.getCreated(), chunk.getId());
});
}
}

View File

@ -0,0 +1,65 @@
package org.aibidding.common.chat.domain.request;
import org.aibidding.common.chat.entity.chat.Message;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import java.util.List;
/**
* 描述
*
* @author https:www.unfbx.com
* @sine 2023-04-08
*/
@Data
public class ChatRequest {
private String frequency_penalty;
private String max_tokens;
@NotEmpty(message = "对话消息不能为空")
List<Message> messages;
@NotEmpty(message = "传入的模型不能为空")
private String model;
private String presence_penalty;
private String stream;
private double temperature;
private double top_p = 1;
/**
* 知识库id
*/
private String kid;
private String userId;
//
//
// /**
// * gpt的默认设置
// */
// private String systemMessage = "";
//
//
//
// private double temperature = 0.2;
//
// /**
// * 上下文的条数
// */
// private Integer contentNumber = 10;
//
// /**
// * 是否携带上下文
// */
// private Boolean usingContext = Boolean.TRUE;
}

View File

@ -0,0 +1,33 @@
package org.aibidding.common.chat.domain.request;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
/**
* 描述
*
* @author https:www.unfbx.com
* @sine 2023-04-08
*/
@Data
public class Dall3Request {
@NotEmpty(message = "传入的模型不能为空")
private String model;
@NotEmpty(message = "提示词不能为空")
private String prompt;
/** 图片大小 */
@NotEmpty(message = "图片大小不能为空")
private String size ;
/** 图片质量 */
@NotEmpty(message = "图片质量不能为空")
private String quality;
/** 图片风格 */
@NotEmpty(message = "图片风格不能为空")
private String style;
}

View File

@ -0,0 +1,48 @@
package org.aibidding.common.chat.entity.Tts;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.*;
@Data
@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
@NoArgsConstructor
@AllArgsConstructor
public class TextToSpeech {
@Builder.Default
private String model = Model.TTS_1.getName();
/**
* 音频声音源
*
* @see TtsVoice
*/
private String voice;
/**
* 输入内容
*/
private String input;
/**
* 输出音频文件格式
*
* @see TtsFormat
*/
@JsonProperty("response_format")
private String responseFormat;
/**
* 速度调节默认是1取值范围0.254.0
*/
private Double speed;
@Getter
@AllArgsConstructor
public enum Model {
TTS_1("tts-1"),
TTS_1_HD("tts-1-hd"),
;
private final String name;
}
}

View File

@ -0,0 +1,15 @@
package org.aibidding.common.chat.entity.Tts;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum TtsFormat {
MP3("mp3"),
OPUS("opus"),
AAC("aac"),
FLAC("flac"),
;
private final String name;
}

View File

@ -0,0 +1,23 @@
package org.aibidding.common.chat.entity.Tts;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 生成不同声音的音频
* <p>具体语音效果参考https://platform.openai.com/docs/guides/text-to-speech</p>
*/
@Getter
@AllArgsConstructor
public enum TtsVoice {
ALLOY("alloy"),
ECHO("echo"),
FABLE("fable"),
ONYX("onyx"),
NOVA("nova"),
SHIMMER("shimmer"),
;
private final String name;
}

View File

@ -0,0 +1,33 @@
package org.aibidding.common.chat.entity.billing;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
/**
* 描述金额消耗信息
*
* @author https:www.unfbx.com
* @since 2023-04-08
*/
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class BillingUsage {
@JsonProperty("object")
private String object;
/**
* 账号金额消耗明细
*/
@JsonProperty("daily_costs")
private List<DailyCost> dailyCosts;
/**
* 总使用金额美分
*/
@JsonProperty("total_usage")
private BigDecimal totalUsage;
}

View File

@ -0,0 +1,39 @@
package org.aibidding.common.chat.entity.billing;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
/**
* 描述余额查询接口返回值
*
* @author https:www.unfbx.com
* @since 2023-03-18
*/
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class CreditGrantsResponse implements Serializable {
private String object;
/**
* 总金额美元
*/
@JsonProperty("total_granted")
private BigDecimal totalGranted;
/**
* 总使用金额美元
*/
@JsonProperty("total_used")
private BigDecimal totalUsed;
/**
* 总剩余金额美元
*/
@JsonProperty("total_available")
private BigDecimal totalAvailable;
/**
* 余额明细
*/
private Grants grants;
}

View File

@ -0,0 +1,28 @@
package org.aibidding.common.chat.entity.billing;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;
/**
* 描述金额消耗列表
*
* @author https:www.unfbx.com
* @since 2023-04-08
*/
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class DailyCost {
/**
* 时间戳
*/
@JsonProperty("timestamp")
private long timestamp;
/**
* 模型消耗金额详情
*/
@JsonProperty("line_items")
private List<LineItem> lineItems;
}

View File

@ -0,0 +1,40 @@
package org.aibidding.common.chat.entity.billing;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.math.BigDecimal;
/**
* 描述
*
* @author https:www.unfbx.com
* @since 2023-03-18
*/
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class Datum {
private String object;
private String id;
/**
* 赠送金额美元
*/
@JsonProperty("grant_amount")
private BigDecimal grantAmount;
/**
* 使用金额美元
*/
@JsonProperty("used_amount")
private BigDecimal usedAmount;
/**
* 生效时间戳
*/
@JsonProperty("effective_at")
private Long effectiveAt;
/**
* 过期时间戳
*/
@JsonProperty("expires_at")
private Long expiresAt;
}

View File

@ -0,0 +1,21 @@
package org.aibidding.common.chat.entity.billing;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;
/**
* 描述
*
* @author https:www.unfbx.com
* @since 2023-03-18
*/
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class Grants {
private String object;
@JsonProperty("data")
private List<Datum> data;
}

View File

@ -0,0 +1,56 @@
package org.aibidding.common.chat.entity.billing;
import lombok.*;
import java.time.LocalDate;
/**
* openKey信息
*
* @author admin
* @date 2023/6/15
*/
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class KeyInfo {
/**
* 订阅类型
*/
private String planTitle;
/**
* key值
*/
private String keyValue;
/**
* 剩余额度
*/
private Double remaining;
/**
* 账户总余额
*/
private Double totalAmount;
/**
* 已使用的额度
*/
private Double totalUsage;
/**
* 截至日期
*/
private LocalDate limitDate;
/**
* 是否绑卡
*/
private Boolean isHasPaymentMethod;
/**
* 最高可用模型
*/
private String model;
}

View File

@ -0,0 +1,25 @@
package org.aibidding.common.chat.entity.billing;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import java.math.BigDecimal;
/**
* 描述金额消耗列表
*
* @author https:www.unfbx.com
* @since 2023-04-08
*/
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class LineItem {
/**
* 模型名称
*/
private String name;
/**
* 消耗金额
*/
private BigDecimal cost;
}

View File

@ -0,0 +1,17 @@
package org.aibidding.common.chat.entity.billing;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
/**
* 描述
*
* @author https:www.unfbx.com
* @since 2023-04-08
*/
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class Plan {
private String title;
private String id;
}

View File

@ -0,0 +1,73 @@
package org.aibidding.common.chat.entity.billing;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
* 描述账户信息
*
* @author https:www.unfbx.com
* @since 2023-04-08
*/
@Data
public class Subscription {
@JsonProperty("object")
private String object;
/**
* 付款方式
*/
@JsonProperty("has_payment_method")
private boolean hasPaymentMethod;
@JsonProperty("canceled")
private boolean canceled;
@JsonProperty("canceled_at")
private Object canceledAt;
@JsonProperty("delinquent")
private Object delinquent;
@JsonProperty("access_until")
private long accessUntil;
@JsonProperty("soft_limit")
private long softLimit;
@JsonProperty("hard_limit")
private long hardLimit;
@JsonProperty("system_hard_limit")
private long systemHardLimit;
@JsonProperty("soft_limit_usd")
private double softLimitUsd;
@JsonProperty("hard_limit_usd")
private double hardLimitUsd;
@JsonProperty("system_hard_limit_usd")
private double systemHardLimitUsd;
/**
* 计划
*/
@JsonProperty("plan")
private Plan plan;
/**
* 账户名称
*/
@JsonProperty("account_name")
private String accountName;
@JsonProperty("po_number")
private Object poNumber;
/**
* 账单邮箱
*/
@JsonProperty("billing_email")
private Object billingEmail;
@JsonProperty("tax_ids")
private Object taxIds;
@JsonProperty("billing_address")
private Object billingAddress;
@JsonProperty("business_address")
private Object businessAddress;
@JsonProperty("primary")
private Boolean primary;
}

View File

@ -0,0 +1,255 @@
package org.aibidding.common.chat.entity.chat;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.*;
import lombok.experimental.SuperBuilder;
import org.aibidding.common.chat.entity.chat.tool.Tools;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import static org.aibidding.common.chat.entity.chat.BaseChatCompletion.Model.GPT_3_5_TURBO;
/**
* 描述 chat模型基础类
*
* @author https:www.unfbx.com
* @since 1.1.2
* 2023-11-10
*/
@Data
@SuperBuilder
@JsonInclude(JsonInclude.Include.NON_NULL)
@NoArgsConstructor
@AllArgsConstructor
public class BaseChatCompletion implements Serializable {
@NonNull
@Builder.Default
private String model = GPT_3_5_TURBO.getName();
/**
* 指定模型必须输出的格式的对象
*
* @since 1.1.2
*/
@JsonProperty("response_format")
private ResponseFormat responseFormat;
/**
* 已过时
*
* @see #tools
*/
@Deprecated
private List<Functions> functions;
/**
* 取值null,auto或者自定义
* functions没有值的时候默认为null
* functions存在值得时候默认为auto
* 也可以自定义
* <p>已过时</p>
*
* @see #toolChoice
*/
@Deprecated
@JsonProperty("function_call")
private Object functionCall;
/**
* 模型可能调用的工具列表
* 当前版本仅支持functions
*
* @since 1.1.2
*/
private List<Tools> tools;
/**
* 取值String或者ToolChoiceObj
*
* @since 1.1.2
*/
@JsonProperty("tool_choice")
private Object toolChoice;
/**
* 使用什么取样温度0到2之间较高的值(如0.8)将使输出更加随机而较低的值(如0.2)将使输出更加集中和确定
* <p>
* We generally recommend altering this or but not both.top_p
*/
@Builder.Default
private double temperature = 0.2;
/**
* 使用温度采样的替代方法称为核心采样其中模型考虑具有top_p概率质量的令牌的结果因此0.1 意味着只考虑包含前 10% 概率质量的代币
* <p>
* 我们通常建议更改此设置但不要同时更改两者temperature
*/
@JsonProperty("top_p")
@Builder.Default
private Double topP = 1d;
/**
* 为每个提示生成的完成次数
*/
@Builder.Default
private Integer n = 1;
/**
* 是否流式输出.
* default:false
*/
@Builder.Default
private boolean stream = false;
/**
* 停止输出标识
*/
private List<String> stop;
/**
* 最大支持4096
*/
@JsonProperty("max_tokens")
@Builder.Default
private Integer maxTokens = 2048;
@JsonProperty("presence_penalty")
@Builder.Default
private double presencePenalty = 0;
/**
* -2.0 ~~ 2.0
*/
@JsonProperty("frequency_penalty")
@Builder.Default
private double frequencyPenalty = 0;
@JsonProperty("logit_bias")
private Map logitBias;
/**
* 用户唯一值确保接口不被重复调用
*/
private String user;
/**
* @since 1.1.2
*/
private Integer seed;
/**
* 最新模型参考官方文档
* <a href="https://platform.openai.com/docs/models/model-endpoint-compatibility">官方稳定模型列表</a>
*/
@Getter
@AllArgsConstructor
public enum Model {
/**
* gpt-3.5-turbo
*/
GPT_3_5_TURBO("gpt-3.5-turbo"),
/**
* 临时模型不建议使用2023年9 13 日将被弃用
*/
@Deprecated
GPT_3_5_TURBO_0301("gpt-3.5-turbo-0301"),
/**
* gpt-3.5-turbo-0613 支持函数
*/
GPT_3_5_TURBO_1106("gpt-3.5-turbo-1106"),
GPT_3_5_TURBO_0613("gpt-3.5-turbo-0613"),
/**
* gpt-3.5-turbo-16k 超长上下文
*/
GPT_3_5_TURBO_16K("gpt-3.5-turbo-16k"),
/**
* gpt-3.5-turbo-16k-0613 超长上下文 支持函数
*/
GPT_3_5_TURBO_16K_0613("gpt-3.5-turbo-16k-0613"),
/**
* gpt-3.5-turbo-0125 超长上下文 支持函数
*/
GPT_3_5_TURBO_0125("gpt-3.5-turbo-0125"),
/**
* GPT4.0
*/
GPT_4("gpt-4"),
/**
* 临时模型不建议使用2023年9 13 日将被弃用
*/
@Deprecated
GPT_4_0314("gpt-4-0314"),
/**
* GPT4.0 超长上下文
*/
GPT_4_32K("gpt-4-32k"),
/**
* 临时模型不建议使用2023年9 13 日将被弃用
*/
@Deprecated
GPT_4_32K_0314("gpt-4-32k-0314"),
/**
* gpt-4-0613支持函数
*/
GPT_4_0613("gpt-4-0613"),
/**
* gpt-4-0613支持函数
*/
GPT_4_32K_0613("gpt-4-32k-0613"),
/**
* 支持数组模式支持function call支持可重复输出
*/
GPT_4_1106_PREVIEW("gpt-4-1106-preview"),
/**
* 支持图片
*/
GPT_4_VISION_PREVIEW("gpt-4-vision-preview"),
/**
* gpt-4-0613支持函数
*/
GPT_4_0125_PREVIEW("gpt-4-0125-preview"),
/**
* GPT_4_ALL
*/
GPT_4_ALL("gpt-4-all"),
GPT_4_GIZMO("gpt-4-gizmo"),
NET("net"),
CLAUDE_3_SONNET("claude-3-sonnet-20240229"),
GEMINI_PRO("gemini-pro"),
STABLE_DIFFUSION("stable-diffusion"),
SUNO_V3("suno-v3"),
;
private final String name;
}
@Getter
@AllArgsConstructor
public enum ChatType {
/**
* 对话类型 - 输入
*/
CHAT_IN("in"),
/**
* 对话类型 - 输出
*/
CHAT_OUT("out"),
;
private final String name;
}
}

View File

@ -0,0 +1,84 @@
package org.aibidding.common.chat.entity.chat;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import org.aibidding.common.chat.entity.chat.tool.ToolCalls;
import java.io.Serializable;
import java.util.List;
/**
* 描述
*
* @author https:www.unfbx.com
* @since 1.1.2
* 2023-03-02
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
@AllArgsConstructor
public class BaseMessage implements Serializable {
/**
* 目前支持四个中角色参考官网进行情景输入
* https://platform.openai.com/docs/guides/chat/introduction
*/
private String role;
private String name;
/**
* The tool calls generated by the model, such as function calls.
* @since 1.1.2
*/
@JsonProperty("tool_calls")
private List<ToolCalls> toolCalls;
/**
* @since 1.1.2
*/
@JsonProperty("tool_call_id")
private String toolCallId;
@Deprecated
@JsonProperty("function_call")
private FunctionCall functionCall;
/**
* 构造函数
*
* @param role 角色
* @param name name
* @param functionCall functionCall
*/
public BaseMessage(String role, String name, FunctionCall functionCall) {
this.role = role;
this.name = name;
this.functionCall = functionCall;
}
public BaseMessage() {
}
@Getter
@AllArgsConstructor
public enum Role {
SYSTEM("system"),
USER("user"),
ASSISTANT("assistant"),
FUNCTION("function"),
TOOL("tool"),
;
private final String name;
}
}

View File

@ -0,0 +1,31 @@
package org.aibidding.common.chat.entity.chat;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.io.Serializable;
/**
* 描述
*
* @author https:www.unfbx.com
* @since 2023-03-02
*/
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class ChatChoice implements Serializable {
private long index;
/**
* 请求参数stream为true返回是delta
*/
@JsonProperty("delta")
private Message delta;
/**
* 请求参数stream为false返回是message
*/
@JsonProperty("message")
private Message message;
@JsonProperty("finish_reason")
private String finishReason;
}

View File

@ -0,0 +1,34 @@
package org.aibidding.common.chat.entity.chat;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.experimental.SuperBuilder;
import lombok.extern.slf4j.Slf4j;
import java.io.Serializable;
import java.util.List;
/**
* 描述 chat模型参数
*
* @author https:www.unfbx.com
* 2023-03-02
*/
@Data
@SuperBuilder
@Slf4j
@JsonInclude(JsonInclude.Include.NON_NULL)
@NoArgsConstructor
@AllArgsConstructor
public class ChatCompletion extends BaseChatCompletion implements Serializable {
/**
* 问题描述
*/
@NonNull
private List<Message> messages;
}

View File

@ -0,0 +1,25 @@
package org.aibidding.common.chat.entity.chat;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import org.aibidding.common.chat.entity.common.Usage;
import java.io.Serializable;
import java.util.List;
/**
* 描述 chat答案类
*
* @author https:www.unfbx.com
* 2023-03-02
*/
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class ChatCompletionResponse implements Serializable {
private String id;
private String object;
private long created;
private String model;
private List<ChatChoice> choices;
private Usage usage;
}

View File

@ -0,0 +1,32 @@
package org.aibidding.common.chat.entity.chat;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import lombok.extern.slf4j.Slf4j;
import java.io.Serializable;
import java.util.List;
/**
* 描述 chat模型附带图片的参数
*
* @author https:www.unfbx.com
* @since 1.1.2
* 2023-11-10
*/
@Data
@SuperBuilder
@Slf4j
@JsonInclude(JsonInclude.Include.NON_NULL)
@NoArgsConstructor
@AllArgsConstructor
public class ChatCompletionWithPicture extends BaseChatCompletion implements Serializable {
/**
* 问题描述
*/
private List<MessagePicture> messages;
}

View File

@ -0,0 +1,43 @@
package org.aibidding.common.chat.entity.chat;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.*;
import lombok.extern.slf4j.Slf4j;
/**
* 描述
*
* @author https://www.unfbx.com
* @since 1.1.2
* 2023-11-10
*/
@Data
@Builder
@Slf4j
@JsonInclude(JsonInclude.Include.NON_NULL)
@NoArgsConstructor
@AllArgsConstructor
public class Content {
/**
* 输入类型textimage_url
*
* @see Type
*/
private String type;
private String text;
@JsonProperty("image_url")
private ImageUrl imageUrl;
/**
* 生成图片风格
*/
@Getter
@AllArgsConstructor
public enum Type {
TEXT("text"),
IMAGE_URL("image_url"),
;
private final String name;
}
}

View File

@ -0,0 +1,27 @@
package org.aibidding.common.chat.entity.chat;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 描述函数调用返回值
*
* @author https://www.unfbx.com
* @since 2023-06-14
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class FunctionCall {
/**
* 方法名
*/
private String name;
/**
* 方法参数
*/
private String arguments;
}

View File

@ -0,0 +1,46 @@
package org.aibidding.common.chat.entity.chat;
import lombok.Builder;
import lombok.Data;
import java.io.Serializable;
/**
* 描述方法参数实体类实例数据如下
* <pre>
* {
* "name": "get_current_weather",
* "description": "Get the current weather in a given location",
* "parameters": {
* "type": "object",
* "properties": {
* "location": {
* "type": "string",
* "description": "The city and state, e.g. San Francisco, CA"
* },
* "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
* },
* "required": ["location"]
* },
* }
* </pre>
* @author https:www.unfbx.com
* @since 2023-06-14
*/
@Data
@Builder
public class Functions implements Serializable {
/**
* 方法名称
*/
private String name;
/**
* 方法描述
*/
private String description;
/**
* 方法参数
* 扩展参数可以继承Parameters自己实现json格式的数据
*/
private Parameters parameters;
}

View File

@ -0,0 +1,28 @@
package org.aibidding.common.chat.entity.chat;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* 描述
*
* @author https://www.unfbx.com
* 2023-11-10
*/
@Data
@Builder
@Slf4j
@JsonInclude(JsonInclude.Include.NON_NULL)
@NoArgsConstructor
@AllArgsConstructor
public class ImageUrl {
/**
* 图片地址支持base64. eg: data:image/jpeg;base64,{base64_image} <p\>
* https://platform.openai.com/docs/guides/vision
*/
private String url;
}

View File

@ -0,0 +1,107 @@
package org.aibidding.common.chat.entity.chat;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import org.aibidding.common.chat.entity.chat.tool.ToolCalls;
import java.io.Serializable;
import java.util.List;
/**
* 描述
*
* @author https:www.unfbx.com
* @since 2023-03-02
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class Message extends BaseMessage implements Serializable {
private Object content;
public static Builder builder() {
return new Builder();
}
/**
* 构造函数
*
* @param role 角色
* @param name name
* @param content content
* @param functionCall functionCall
*/
public Message(String role, String name, String content, List<ToolCalls> toolCalls, String toolCallId, FunctionCall functionCall) {
this.content = content;
super.setRole(role);
super.setName(name);
super.setToolCalls(toolCalls);
super.setToolCallId(toolCallId);
super.setFunctionCall(functionCall);
}
public Message() {
}
private Message(Builder builder) {
setContent(builder.content);
super.setRole(builder.role);
super.setName(builder.name);
super.setFunctionCall(builder.functionCall);
super.setToolCalls(builder.toolCalls);
super.setToolCallId(builder.toolCallId);
}
public static final class Builder {
private String role;
private String content;
private String name;
private String toolCallId;
private List<ToolCalls> toolCalls;
private FunctionCall functionCall;
public Builder() {
}
public Builder role(Role role) {
this.role = role.getName();
return this;
}
public Builder role(String role) {
this.role = role;
return this;
}
public Builder content(String content) {
this.content = content;
return this;
}
public Builder name(String name) {
this.name = name;
return this;
}
public Builder functionCall(FunctionCall functionCall) {
this.functionCall = functionCall;
return this;
}
public Builder toolCalls(List<ToolCalls> toolCalls) {
this.toolCalls = toolCalls;
return this;
}
public Builder toolCallId(String toolCallId) {
this.toolCallId = toolCallId;
return this;
}
public Message build() {
return new Message(this);
}
}
}

View File

@ -0,0 +1,114 @@
package org.aibidding.common.chat.entity.chat;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.aibidding.common.chat.entity.chat.tool.ToolCalls;
import java.io.Serializable;
import java.util.List;
/**
* 描述
*
* @author https:www.unfbx.com
* @since 2023-03-02
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
@AllArgsConstructor
public class MessagePicture extends BaseMessage implements Serializable {
/**
* Content数组支持多图片输入
* https://platform.openai.com/docs/guides/vision
*/
private List<Content> content;
public static Builder builder() {
return new Builder();
}
/**
* 构造函数
*
* @param role 角色
* @param name name
* @param content content
* @param functionCall functionCall
*/
public MessagePicture(String role, String name, List<Content> content, List<ToolCalls> toolCalls, String toolCallId, FunctionCall functionCall) {
this.content = content;
super.setRole(role);
super.setName(name);
super.setToolCalls(toolCalls);
super.setToolCallId(toolCallId);
super.setFunctionCall(functionCall);
}
public MessagePicture() {
}
private MessagePicture(Builder builder) {
setContent(builder.content);
super.setRole(builder.role);
super.setName(builder.name);
super.setFunctionCall(builder.functionCall);
super.setToolCalls(builder.toolCalls);
super.setToolCallId(builder.toolCallId);
}
public static final class Builder {
private String role;
private List<Content> content;
private String name;
private String toolCallId;
private List<ToolCalls> toolCalls;
private FunctionCall functionCall;
public Builder() {
}
public Builder role(Role role) {
this.role = role.getName();
return this;
}
public Builder role(String role) {
this.role = role;
return this;
}
public Builder content(List<Content> content) {
this.content = content;
return this;
}
public Builder name(String name) {
this.name = name;
return this;
}
public Builder functionCall(FunctionCall functionCall) {
this.functionCall = functionCall;
return this;
}
public Builder toolCalls(List<ToolCalls> toolCalls) {
this.toolCalls = toolCalls;
return this;
}
public Builder toolCallId(String toolCallId) {
this.toolCallId = toolCallId;
return this;
}
public MessagePicture build() {
return new MessagePicture(this);
}
}
}

View File

@ -0,0 +1,42 @@
package org.aibidding.common.chat.entity.chat;
import lombok.Builder;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* 描述方法参数类扩展参数可以继承Parameters自己实现
* 参考
* <pre>
* {
* "type": "object",
* "properties": {
* "location": {
* "type": "string",
* "description": "The city and state, e.g. San Francisco, CA"
* },
* "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
* },
* "required": ["location"]
* }
* </pre>
* @author https:www.unfbx.com
* @since 2023-06-14
*/
@Data
@Builder
public class Parameters implements Serializable {
/**
* 参数类型
*/
private String type;
/**
* 参数属性描述
*/
private Object properties;
/**
* 方法必输字段
*/
private List<String> required;
}

View File

@ -0,0 +1,29 @@
package org.aibidding.common.chat.entity.chat;
import lombok.*;
/**
* 指定模型必须输出的格式的对象
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ResponseFormat {
/**
* 默认text
*
* @see Type
*/
private String type;
@Getter
@AllArgsConstructor
public enum Type {
JSON_OBJECT("json_object"),
TEXT("text"),
;
private final String name;
}
}

View File

@ -0,0 +1,31 @@
package org.aibidding.common.chat.entity.chat.tool;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* ToolCall Function参数
* The function that the model called.
*
* @author https:www.unfbx.com
* @since 1.1.2
* 2023-11-09
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ToolCallFunction implements Serializable {
/**
* 方法名
*/
private String name;
/**
* 方法参数
*/
private String arguments;
}

View File

@ -0,0 +1,37 @@
package org.aibidding.common.chat.entity.chat.tool;
import lombok.*;
import java.io.Serializable;
/**
* The tool calls generated by the model, such as function calls.
*
* @author <a href="https://www.unfbx.com">unfbx</a>
* @since 1.1.2
* 2023-11-09
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ToolCalls implements Serializable {
/**
* The ID of the tool call.
*/
private String id;
/**
* The type of the tool. Currently, only function is supported.
*/
private String type;
private ToolCallFunction function;
@Getter
@AllArgsConstructor
public enum Type {
FUNCTION("function"),
;
private final String name;
}
}

View File

@ -0,0 +1,27 @@
package org.aibidding.common.chat.entity.chat.tool;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import java.io.Serializable;
/**
* choice和object同时存在是以object为准
*
* @author <a href="https://www.unfbx.com">unfbx</a>
* @since 1.1.2
* 2023-11-09
*/
@Data
public class ToolChoice implements Serializable {
@Getter
@AllArgsConstructor
public enum Choice {
NONE("none"),
AUTO("auto"),
;
private final String name;
}
}

View File

@ -0,0 +1,33 @@
package org.aibidding.common.chat.entity.chat.tool;
import lombok.*;
/**
* @author <a href="https://www.unfbx.com">unfbx</a>
* @since 1.1.2
* 2023-11-09
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ToolChoiceObj {
/**
* 需要调用的方法名称
*/
private ToolChoiceObjFunction function;
/**
* 工具的类型目前仅支持函数
*
* @see Type
*/
private String type;
@Getter
@AllArgsConstructor
public enum Type {
FUNCTION("function"),
;
private final String name;
}
}

View File

@ -0,0 +1,21 @@
package org.aibidding.common.chat.entity.chat.tool;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author <a href="https://www.unfbx.com">unfbx</a>
* @since 1.1.2
* 2023-11-09
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ToolChoiceObjFunction {
private String name;
}

View File

@ -0,0 +1,35 @@
package org.aibidding.common.chat.entity.chat.tool;
import lombok.*;
import java.io.Serializable;
/**
* @author <a href="https://www.unfbx.com">unfbx</a>
* @since 1.1.2
* 2023-11-09
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Tools implements Serializable {
/**
* 目前只支持function
*
* @see Type
*/
private String type;
private ToolsFunction function;
@Getter
@AllArgsConstructor
public enum Type {
FUNCTION("function"),
;
private final String name;
}
}

View File

@ -0,0 +1,36 @@
package org.aibidding.common.chat.entity.chat.tool;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.aibidding.common.chat.entity.chat.Parameters;
import java.io.Serializable;
/**
* @author <a href="https://www.unfbx.com">unfbx</a>
* @since 1.1.2
* 2023-11-09
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ToolsFunction implements Serializable {
/**
* 要调用的函数的名称必须是 a-zA-Z0-9或包含下划线和破折号最大长度为 64
*/
private String name;
/**
* 对函数功能的描述模型使用它来选择何时以及如何调用该函数
*/
private String description;
/**
* 函数接受的参数描述为 JSON Schema 对象
* 扩展参数可以继承Parameters自己实现json格式的数据
*/
private Parameters parameters;
}

View File

@ -0,0 +1,23 @@
package org.aibidding.common.chat.entity.common;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.io.Serializable;
/**
* 描述
*
* @author https:www.unfbx.com
* 2023-02-15
*/
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class Choice implements Serializable {
private String text;
private long index;
private Object logprobs;
@JsonProperty("finish_reason")
private String finishReason;
}

View File

@ -0,0 +1,20 @@
package org.aibidding.common.chat.entity.common;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import java.io.Serializable;
/**
* 描述
*
* @author https:www.unfbx.com
* 2023-02-15
*/
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class DeleteResponse implements Serializable {
private String id;
private String object;
private boolean deleted;
}

View File

@ -0,0 +1,30 @@
package org.aibidding.common.chat.entity.common;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* 描述
*
* @author https:www.unfbx.com
* 2023-02-15
*/
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class OpenAiResponse<T> implements Serializable {
private String object;
private List<T> data;
private Error error;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class Error {
private String message;
private String type;
private String param;
private String code;
}
}

View File

@ -0,0 +1,24 @@
package org.aibidding.common.chat.entity.common;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.io.Serializable;
/**
* 描述
*
* @author https:www.unfbx.com
* 2023-02-15
*/
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class Usage implements Serializable {
@JsonProperty("prompt_tokens")
private long promptTokens;
@JsonProperty("completion_tokens")
private long completionTokens;
@JsonProperty("total_tokens")
private long totalTokens;
}

View File

@ -0,0 +1,125 @@
package org.aibidding.common.chat.entity.completions;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.*;
import lombok.extern.slf4j.Slf4j;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
/**
* 描述 问题类
*
* @author https:www.unfbx.com
* 2023-02-11
*/
@Data
@Builder
@Slf4j
@JsonInclude(JsonInclude.Include.NON_NULL)
@NoArgsConstructor
@AllArgsConstructor
public class Completion implements Serializable {
@NonNull
@Builder.Default
private String model = Model.DAVINCI_003.getName();
/**
* 问题描述
*/
@NonNull
private String prompt;
/**
* 完成输出后的后缀用于格式化输出结果
*/
private String suffix;
/**
* 最大支持4096
*/
@JsonProperty("max_tokens")
@Builder.Default
private Integer maxTokens = 2048;
/**
* 使用什么取样温度0到2之间较高的值(如0.8)将使输出更加随机而较低的值(如0.2)将使输出更加集中和确定
* <p>
* We generally recommend altering this or but not both.top_p
*/
@Builder.Default
private double temperature = 0;
/**
* 使用温度采样的替代方法称为核心采样其中模型考虑具有top_p概率质量的令牌的结果因此0.1 意味着只考虑包含前 10% 概率质量的代币
* <p>
* 我们通常建议更改此设置但不要同时更改两者temperature
*/
@JsonProperty("top_p")
@Builder.Default
private Double topP = 1d;
/**
* 为每个提示生成的完成次数
*/
@Builder.Default
private Integer n = 1;
@Builder.Default
private boolean stream = false;
/**
* 最大值5
*/
private Integer logprobs;
@Builder.Default
private boolean echo = false;
private List<String> stop;
@JsonProperty("presence_penalty")
@Builder.Default
private double presencePenalty = 0;
/**
* -2.0 ~~ 2.0
*/
@JsonProperty("frequency_penalty")
@Builder.Default
private double frequencyPenalty = 0;
@JsonProperty("best_of")
@Builder.Default
private Integer bestOf = 1;
@JsonProperty("logit_bias")
private Map logitBias;
/**
* 用户唯一值确保接口不被重复调用
*/
private String user;
/**
* 获取当前参数的tokens数
* @return token数量
*/
// public long tokens() {
// if (StrUtil.isBlank(this.prompt) || StrUtil.isBlank(this.model)) {
// log.warn("参数异常model{}prompt{}", this.model, this.prompt);
// return 0;
// }
// return TikTokensUtil.tokens(this.model, this.prompt);
// }
@Getter
@AllArgsConstructor
public enum Model {
DAVINCI_003("text-davinci-003"),
DAVINCI_002("text-davinci-002"),
DAVINCI("davinci"),
;
private String name;
}
}

View File

@ -0,0 +1,26 @@
package org.aibidding.common.chat.entity.completions;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import org.aibidding.common.chat.entity.common.Choice;
import org.aibidding.common.chat.entity.common.OpenAiResponse;
import org.aibidding.common.chat.entity.common.Usage;
import java.io.Serializable;
/**
* 描述 答案类
*
* @author https:www.unfbx.com
* 2023-02-11
*/
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class CompletionResponse extends OpenAiResponse implements Serializable {
private String id;
private String object;
private long created;
private String model;
private Choice[] choices;
private Usage usage;
}

View File

@ -0,0 +1,29 @@
package org.aibidding.common.chat.entity.dto;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
* 消息的dto
*
* @author zendwang
*/
@Data
public class WebSocketMessageDto implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 需要推送到的session key 列表
*/
private List<Long> sessionKeys;
/**
* 需要发送的消息
*/
private String message;
}

View File

@ -0,0 +1,104 @@
package org.aibidding.common.chat.entity.edits;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.*;
import lombok.extern.slf4j.Slf4j;
import java.io.Serializable;
/**
* 描述
*
* @author https:www.unfbx.com
* 2023-02-15
*/
@Getter
@Builder
@Slf4j
@NoArgsConstructor
@AllArgsConstructor
public class Edit implements Serializable {
/**
* 编辑模型目前支持两种
*/
@NonNull
private String model;
@NonNull
private String input;
/**
* 提示说明告知模型如何修改
*/
@NonNull
private String instruction;
/**
* 使用什么取样温度0到2之间较高的值(如0.8)将使输出更加随机而较低的值(如0.2)将使输出更加集中和确定
*
* We generally recommend altering this or but not both.top_p
*/
@Builder.Default
private double temperature = 0;
/**
* 使用温度采样的替代方法称为核心采样其中模型考虑具有top_p概率质量的令牌的结果因此0.1 意味着只考虑包含前 10% 概率质量的代币
*
* 我们通常建议更改此设置但不要同时更改两者temperature
*/
@JsonProperty("top_p")
@Builder.Default
private Double topP = 1d;
/**
* 为每个提示生成的完成次数
*/
@Builder.Default
private Integer n = 1;
public void setModel(Model model) {
this.model = model.getName();
}
public void setTemperature(double temperature) {
if (temperature > 2 || temperature < 0) {
log.error("temperature参数异常temperature属于[0,2]");
this.temperature = 2;
return;
}
if (temperature < 0) {
log.error("temperature参数异常temperature属于[0,2]");
this.temperature = 0;
return;
}
this.temperature = temperature;
}
public void setTopP(Double topP) {
this.topP = topP;
}
public void setN(Integer n) {
this.n = n;
}
public void setInput(String input) {
this.input = input;
}
public void setInstruction(String instruction) {
this.instruction = instruction;
}
@Getter
@AllArgsConstructor
public enum Model {
TEXT_DAVINCI_EDIT_001("text-davinci-edit-001"),
CODE_DAVINCI_EDIT_001("code-davinci-edit-001"),
;
private String name;
}
}

View File

@ -0,0 +1,26 @@
package org.aibidding.common.chat.entity.edits;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import org.aibidding.common.chat.entity.common.Choice;
import org.aibidding.common.chat.entity.common.Usage;
import java.io.Serializable;
/**
* 描述
*
* @author https:www.unfbx.com
* 2023-02-15
*/
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class EditResponse implements Serializable {
private String id;
private String object;
private long created;
private String model;
private Choice[] choices;
private Usage usage;
}

View File

@ -0,0 +1,54 @@
package org.aibidding.common.chat.entity.embeddings;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.*;
import lombok.extern.slf4j.Slf4j;
import java.io.Serializable;
import java.util.List;
import java.util.Objects;
/**
* 描述
*
* @author https:www.unfbx.com
* 2023-02-15
*/
@Getter
@Slf4j
@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
@NoArgsConstructor
@AllArgsConstructor
public class Embedding implements Serializable {
@NonNull
@Builder.Default
private String model = Model.TEXT_EMBEDDING_ADA_002.getName();
/**
* 必选项长度不能超过8192
*/
@NonNull
private List<String> input;
private String user;
public void setModel(Model model) {
if (Objects.isNull(model)) {
model = Model.TEXT_EMBEDDING_ADA_002;
}
this.model = model.getName();
}
public void setUser(String user) {
this.user = user;
}
@Getter
@AllArgsConstructor
public enum Model {
TEXT_EMBEDDING_ADA_002("text-embedding-ada-002"),
;
private String name;
}
}

View File

@ -0,0 +1,24 @@
package org.aibidding.common.chat.entity.embeddings;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import org.aibidding.common.chat.entity.common.Usage;
import java.io.Serializable;
import java.util.List;
/**
* 描述
*
* @author https:www.unfbx.com
* 2023-02-15
*/
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class EmbeddingResponse implements Serializable {
private String object;
private List<Item> data;
private String model;
private Usage usage;
}

View File

@ -0,0 +1,16 @@
package org.aibidding.common.chat.entity.embeddings;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.List;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class Item implements Serializable {
private String object;
private List<BigDecimal> embedding;
private Integer index;
}

View File

@ -0,0 +1,25 @@
package org.aibidding.common.chat.entity.engines;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import java.io.Serializable;
/**
* 描述
*
* @author https:www.unfbx.com
* 2023-02-15
*/
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class Engine implements Serializable {
private String id;
private String object;
private String owner;
private boolean ready;
private Object permissions;
private long created;
}

View File

@ -0,0 +1,34 @@
package org.aibidding.common.chat.entity.files;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import java.io.Serializable;
/**
* 描述
*
* @author https:www.unfbx.com
* 2023-02-15
*/
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class File implements Serializable {
// private String id;
// private String object;
// private long bytes;
// private long created_at;
// private String filename;
// private String purpose;
// private String status;
// @JsonProperty("status_details")
// private String statusDetails;
private long bytes;
private long created_at;
private String filename;
private String id;
private String object;
private String url;
}

View File

@ -0,0 +1,17 @@
package org.aibidding.common.chat.entity.files;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import java.io.Serializable;
/**
* 描述
*
* @author https:www.unfbx.com
* 2023-02-15
*/
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class UploadFileResponse extends File implements Serializable {
}

View File

@ -0,0 +1,17 @@
package org.aibidding.common.chat.entity.fineTune;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.io.Serializable;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class Event implements Serializable {
private String object;
@JsonProperty("created_at")
private long createdAt;
private String level;
private String message;
}

View File

@ -0,0 +1,122 @@
package org.aibidding.common.chat.entity.fineTune;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.*;
import lombok.extern.slf4j.Slf4j;
import org.aibidding.common.chat.openai.exception.CommonError;
import org.aibidding.common.core.exception.base.BaseException;
import java.io.Serializable;
import java.util.List;
import java.util.Objects;
@Getter
@Slf4j
@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
@NoArgsConstructor
@AllArgsConstructor
public class FineTune implements Serializable {
/**
* 上传的文件ID
*/
@NonNull
@JsonProperty("training_file")
private String trainingFile;
@JsonProperty("validation_file")
private String validationFile;
/**
* 参考
* @see Model
*/
private String model;
@JsonProperty("n_epochs")
@Builder.Default
private Integer n_epochs = 4;
@JsonProperty("batch_size")
private Integer batchSize;
@JsonProperty("learning_rate_multiplier")
private Double learningRateMultiplier;
@JsonProperty("prompt_loss_weight")
@Builder.Default
private Double promptLossWeight = 0.01;
@JsonProperty("compute_classification_metrics")
@Builder.Default
private boolean computeClassificationMetrics = false;
@JsonProperty("classification_n_classes")
private Integer classificationNClasses;
@JsonProperty("classification_betas")
private List classificationBetas;
private String suffix;
public void setTrainingFile(String trainingFile) {
this.trainingFile = trainingFile;
}
public void setValidationFile(String validationFile) {
this.validationFile = validationFile;
}
public void setModel(String model) {
this.model = model;
}
public void setN_epochs(Integer n_epochs) {
this.n_epochs = n_epochs;
}
public void setBatchSize(Integer batchSize) {
this.batchSize = batchSize;
}
public void setLearningRateMultiplier(Double learningRateMultiplier) {
this.learningRateMultiplier = learningRateMultiplier;
}
public void setPromptLossWeight(Double promptLossWeight) {
this.promptLossWeight = promptLossWeight;
}
public void setComputeClassificationMetrics(boolean computeClassificationMetrics) {
this.computeClassificationMetrics = computeClassificationMetrics;
}
public void setClassificationNClasses(Integer classificationNClasses) {
this.classificationNClasses = classificationNClasses;
}
public void setClassificationBetas(List classificationBetas) {
this.classificationBetas = classificationBetas;
}
public void setSuffix(String suffix) {
if(Objects.nonNull(suffix) && !"".equals(suffix) && suffix.length() > 40){
log.error("后缀长度不能大于40");
throw new BaseException(CommonError.PARAM_ERROR.msg());
}
this.suffix = suffix;
}
@Getter
@AllArgsConstructor
public enum Model {
// or a fine-tuned model created after 2022-04-21.
ADA("ada"),
BABBAGE("babbage"),
CURIE("curie"),
DAVINCI("davinci"),
;
private String name;
}
}

View File

@ -0,0 +1,18 @@
package org.aibidding.common.chat.entity.fineTune;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import java.io.Serializable;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class FineTuneDeleteResponse implements Serializable {
private String id;
private String object;
private boolean deleted;
}

View File

@ -0,0 +1,49 @@
package org.aibidding.common.chat.entity.fineTune;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class FineTuneResponse implements Serializable {
private String id;
private String object;
private String model;
@JsonProperty("created_at")
private long createdAt;
private List<Event> events;
@JsonProperty("fine_tuned_model")
private String fineTunedModel;
@JsonProperty("hyperparams")
private HyperParam hyperParams;
@JsonProperty("organization_id")
private String organizationId;
@JsonProperty("result_files")
private List resultFiles;
private String status;
@JsonProperty("validation_files")
private List validationFiles;
@JsonProperty("training_files")
private List<TrainingFile> trainingFiles;
@JsonProperty("updated_at")
private long updatedAt;
}

View File

@ -0,0 +1,21 @@
package org.aibidding.common.chat.entity.fineTune;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.io.Serializable;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class HyperParam implements Serializable {
@JsonProperty("batch_size")
private Integer batchSize;
@JsonProperty("learning_rate_multiplier")
private Double learningRateMultiplier;
@JsonProperty("n_epochs")
private Integer nEpochs;
@JsonProperty("prompt_loss_weight")
private Double promptLossWeight;
}

View File

@ -0,0 +1,23 @@
package org.aibidding.common.chat.entity.fineTune;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.io.Serializable;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class TrainingFile implements Serializable {
private String id;
private String object;
private long bytes;
@JsonProperty("created_at")
private long createdAt;
private String filename;
private String purpose;
private String status;
@JsonProperty("status_details")
private String statusDetails;
}

View File

@ -0,0 +1,111 @@
package org.aibidding.common.chat.entity.images;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.io.Serializable;
/**
* 描述
*
* @author https:www.unfbx.com
* 2023-02-15
*/
@Getter
@Slf4j
@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
@NoArgsConstructor
@AllArgsConstructor
public class Image implements Serializable {
/**
* 提示词dall-e-2支持1000字符dall-e-3支持4000字符
*/
private String prompt;
/**
* 支持dall-e-2dall-e-3
*
* @see Model
*/
@Builder.Default
private String model = Model.DALL_E_3.getName();
/**
* 此参数仅仅dall-e-3,默认值standard
*
* @see Quality
*/
private String quality;
/**
* 为每个提示生成的个数dall-e-3只能为1
*/
private Integer n;
/**
* 图片尺寸默认值1024x1024
* dall-e-2支持256x256, 512x512, or 1024x1024
* dall-e-3支持1024x1024, 1792x1024, or 1024x1792
*
* @see SizeEnum
*/
private String size;
/**
* 此参数仅仅dall-e-3,取值范围vividnatural
* 默认值vivid
*
* @see Style
*/
private String style;
/**
* 生成图片格式urlb64_json
*
* @see ResponseFormat
*/
@JsonProperty("response_format")
private String responseFormat;
private String user;
/**
* 图片生成模型
*/
@Getter
@AllArgsConstructor
public enum Model {
DALL_E_2("dall-e-2"),
DALL_E_3("dall-e-3"),
;
private final String name;
}
/**
* 生成图片质量
*/
@Getter
@AllArgsConstructor
public enum Quality {
STANDARD("standard"),
HD("hd"),
;
private final String name;
}
/**
* 生成图片风格
*/
@Getter
@AllArgsConstructor
public enum Style {
VIVID("vivid"),
NATURAL("natural"),
;
private final String name;
}
}

View File

@ -0,0 +1,98 @@
package org.aibidding.common.chat.entity.images;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.*;
import lombok.extern.slf4j.Slf4j;
import org.aibidding.common.chat.openai.exception.CommonError;
import org.aibidding.common.core.exception.base.BaseException;
import java.io.Serializable;
import java.util.Objects;
/**
* 描述
*
* @author https:www.unfbx.com
* 2023-02-15
*/
@Getter
@Slf4j
@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
@NoArgsConstructor
@AllArgsConstructor
public class ImageEdit implements Serializable {
/**
* 必选项描述文字最多1000字符
*/
@NonNull
private String prompt;
/**
* 为每个提示生成的完成次数
*/
@Builder.Default
private Integer n = 1;
/**
* 256x256
* 512x512
* 1024x1024
*/
@Builder.Default
private String size = SizeEnum.size_512.getName();
@JsonProperty("response_format")
@Builder.Default
private String responseFormat = ResponseFormat.URL.getName();
private String user;
public ImageEdit setN(Integer n) {
if(n < 1){
log.warn("n最小值1");
n = 1;
}
if(n > 10){
log.warn("n最大值10");
n = 10;
}
this.n = n;
return this;
}
public ImageEdit setPrompt(String prompt) {
if(Objects.isNull(prompt) || "".equals(prompt)){
log.error("参数异常");
throw new BaseException(CommonError.PARAM_ERROR.msg());
}
if(prompt.length() > 1000){
log.error("长度超过1000");
throw new BaseException(CommonError.PARAM_ERROR.msg());
}
this.prompt = prompt;
return this;
}
public ImageEdit setSize(SizeEnum size) {
if(Objects.isNull(size)){
size = SizeEnum.size_512;
}
this.size = size.getName();
return this;
}
public ImageEdit setResponseFormat(ResponseFormat responseFormat) {
if(Objects.isNull(responseFormat)){
responseFormat = ResponseFormat.URL;
}
this.responseFormat = responseFormat.getName();
return this;
}
public ImageEdit setUser(String user) {
this.user = user;
return this;
}
}

View File

@ -0,0 +1,20 @@
package org.aibidding.common.chat.entity.images;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* 描述
*
* @author https:www.unfbx.com
* 2023-02-15
*/
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class ImageResponse implements Serializable {
private long created;
private List<Item> data;
}

View File

@ -0,0 +1,81 @@
package org.aibidding.common.chat.entity.images;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.io.Serializable;
import java.util.Objects;
/**
* 描述
*
* @author https:www.unfbx.com
* 2023-02-15
*/
@Getter
@Slf4j
@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
@NoArgsConstructor
@AllArgsConstructor
public class ImageVariations implements Serializable {
/**
* 为每个提示生成的完成次数
*/
@Builder.Default
private Integer n = 1;
/**
* 256x256
* 512x512
* 1024x1024
*/
@Builder.Default
private String size = SizeEnum.size_512.getName();
@JsonProperty("response_format")
@Builder.Default
private String responseFormat = ResponseFormat.URL.getName();
private String user;
public void setN(Integer n) {
if (n < 1) {
log.warn("n最小值1");
this.n = 1;
return;
}
if (n > 10) {
log.warn("n最大值10");
this.n = 10;
return;
}
this.n = n;
}
public void setSize(SizeEnum size) {
if (Objects.isNull(size)) {
size = SizeEnum.size_512;
}
this.size = size.getName();
}
public void setResponseFormat(ResponseFormat responseFormat) {
if (Objects.isNull(responseFormat)) {
responseFormat = ResponseFormat.URL;
}
this.responseFormat = responseFormat.getName();
}
public void setUser(String user) {
this.user = user;
}
}

View File

@ -0,0 +1,21 @@
package org.aibidding.common.chat.entity.images;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.io.Serializable;
/**
* 描述
*
* @author https:www.unfbx.com
* 2023-02-15
*/
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class Item implements Serializable {
private String url;
@JsonProperty("b64_json")
private String b64Json;
}

View File

@ -0,0 +1,22 @@
package org.aibidding.common.chat.entity.images;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.io.Serializable;
/**
* 描述
*
* @author https:www.unfbx.com
* 2023-02-15
*/
@AllArgsConstructor
@Getter
public enum ResponseFormat implements Serializable {
URL("url"),
B64_JSON("b64_json"),
;
private String name;
}

View File

@ -0,0 +1,26 @@
package org.aibidding.common.chat.entity.images;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.io.Serializable;
/**
* 描述
*
* @author https:www.unfbx.com
* 2023-02-15
*/
@Getter
@AllArgsConstructor
public enum SizeEnum implements Serializable {
size_1024_1792("1024x1792"),
size_1792_1024("1792x1024"),
size_1024("1024x1024"),
size_512("512x512"),
size_256("256x256"),
;
private String name;
}

Some files were not shown because too many files have changed in this diff Show More