186 Commits

Author SHA1 Message Date
ageerle
bd94a3eae8 Merge remote-tracking branch 'origin/main' 2025-05-27 17:55:44 +08:00
ageerle
fcdcf534f1 fix: 修复脱敏后的apikey也被更新了 2025-05-27 17:55:34 +08:00
lindaxia
deefb6cc0b Merge branch 'main' of https://github.com/ageerle/ruoyi-ai 2025-05-26 22:45:18 +08:00
lindaxia
fa96c3d12f feat: 更新deploy脚本 2025-05-26 22:43:54 +08:00
ageerle
77069fdecd feat: 移除聊天、会话角色校验 2025-05-26 22:41:55 +08:00
ageerle
db4a264a52 Merge remote-tracking branch 'origin/main' 2025-05-26 22:40:17 +08:00
ageerle
70ae7ea8f1 feat: 向量库查询删除逻辑调整 2025-05-26 22:40:01 +08:00
lindaxia
6a93856d90 feat: 更新deploy脚本 2025-05-26 22:24:17 +08:00
lindaxia
71cae94815 Revert "lindaxia"
This reverts commit a2905d08f9.
2025-05-26 22:20:51 +08:00
lindaxia
a2905d08f9 lindaxia
feat: 更新deploy
2025-05-26 22:16:23 +08:00
ageerle
463ad6c583 Merge remote-tracking branch 'origin/main' 2025-05-26 12:56:45 +08:00
ageerle
abcde9e36e refactor: 1. 移除图片附件模块 2. 优化import 2025-05-26 12:56:28 +08:00
ageerle
8e723f5b78 Update README.md 2025-05-26 10:21:09 +08:00
ageerle
2021fb5071 feat: 修改项目介绍 2025-05-26 10:15:19 +08:00
ageerle
ecbab7df7b Update README.md 2025-05-26 10:10:00 +08:00
ageerle
d6fcaa0a11 Update README.md 2025-05-26 10:09:28 +08:00
ageerle
58faded4ff Update README.md 2025-05-26 10:09:02 +08:00
ageerle
7cc7af70f0 Update README.md 2025-05-26 10:07:47 +08:00
ageerle
4312a729a2 feat: 会话管理返回创建时间 2025-05-26 09:32:53 +08:00
ageer
5d972d66f4 fix: 代码优化 2025-05-25 14:42:22 +08:00
ageerle
1bff2791a6 Merge pull request #108 from liudalian/20250520
集成国产大模型 ds、zhipu、qianwen
2025-05-25 14:36:23 +08:00
菩提取一叶
a76769e540 add:集成国产大模型 ds、zhipu、qianwen 2025-05-25 14:30:05 +08:00
ageer
bedffffd86 fix: 修复对话时无法上传文件 2025-05-24 23:59:40 +08:00
ageer
2884c37599 feat: 移除微信支付 2025-05-24 23:07:59 +08:00
ageer
c25749c9ca feat: 更新sql脚本 2025-05-24 23:02:59 +08:00
ageer
147b3fd8f5 feat: 查询ppt apikey 2025-05-24 20:06:19 +08:00
ageerle
373424bd01 feat(更新日志):
更新日志

1. 移除个人微信模块
2. 移除直播模块
3. 移除gpts模块
4. 移除应用商店模块
5. 移除套餐管理模块
6. 移除兑换管理模块

## 微信相关
小程序相关功能迁移至企业版
微信公众号/微信机器人迁移至企业版
微信支付迁移至企业版

## 功能模块
智能体模块迁移至企业版
插件管理改为MCP应用并迁移至企业版

知识库:
excel解析迁移至企业版
pdf图片解析迁移至企业版
milvus qdrant扩展 迁移至企业版
2025-05-24 16:18:18 +08:00
ageerle
287a0b3d70 Update README.md 2025-05-23 10:28:42 +08:00
ageerle
cd7e07bea6 Update README.md 2025-05-23 10:26:48 +08:00
ageerle
7edfae09bc Update README.md 2025-05-23 10:24:32 +08:00
ageer
a1759ddf5a perf(wechat,live): 移除个人/企业微信,直播模块
1. 个人微信移除原因:功能不稳定,且有封号风险 2. 直播模块移除原因:使用频率低
2025-05-22 18:36:35 +08:00
ageerle
5517a5d19b feat: 新增聊天时返回消息id 2025-05-22 17:24:56 +08:00
ageerle
8fb72ba4fe feat: 修改配置 2025-05-22 17:19:00 +08:00
ageerle
24511fac8f refactor: 移除个人微信模块,移除直播模 2025-05-22 17:13:49 +08:00
ageerle
ce9f2a82da feat: 新增会话返回会话id 2025-05-22 17:11:39 +08:00
ageerle
b81fb7cb3f feat: 修改项目介绍 2025-05-22 11:37:45 +08:00
ageerle
676a63c0d6 feat: 修改项目介绍 2025-05-22 11:30:34 +08:00
ageerle
427736271c feat: 修改项目介绍 2025-05-22 11:28:32 +08:00
ageerle
c39dd7a917 feat: 修改项目介绍 2025-05-22 11:24:17 +08:00
ageerle
c491a98dc7 Merge pull request #95 from MuSan-Li/feature_20250520_fix_email_config
修复用户端注册发送邮件报错
2025-05-21 09:40:11 +08:00
MuSan-Li
2a4978c668 修复用户端注册发送邮件报错 2025-05-20 23:10:27 +08:00
lh
5b4eeee756 更新readme.md 2025-05-20 11:00:21 +08:00
ageerle
93ddcd53b1 feat: 对话时记录对话角色 2025-05-19 16:41:55 +08:00
ageerle
0ee47a5c00 feat: 对话时记录对话角色 2025-05-19 16:35:21 +08:00
ageerle
3ceef41ab0 feat: 知识库上传逻辑调整 2025-05-19 16:11:37 +08:00
ageerle
57693059a1 Merge pull request #91 from xingjisen/main
fix: 自动上传向量时间改为三分钟;修复根据条件删除向量数据报错问题;
2025-05-19 09:21:35 +08:00
邢继森
d18e307e10 fix: 自动上传向量时间改为三分钟;修复根据条件删除向量数据报错问题; 2025-05-19 00:45:32 +08:00
ageerle
eed708c6d3 Merge pull request #90 from alanpeng/main
Add one-step auto-deployment script
2025-05-18 22:11:01 +08:00
root
1093888889 fix the deploy-en.sh by dos2unix command 2025-05-18 15:58:34 +08:00
Alan Peng
e58a420f9f Update README.md 2025-05-18 21:48:36 +08:00
root
4a0c3d132a Update README.md 2025-05-18 15:48:47 +08:00
root
0d81aa640e Add one-step auto-deployment script 2025-05-18 15:48:20 +08:00
ageer
951524bff7 Merge remote-tracking branch 'origin/main' 2025-05-17 12:51:54 +08:00
ageer
c7d0e9337a fix: 修复聊天记录保存异常 2025-05-17 12:51:29 +08:00
ageerle
6955f3c7dc Merge pull request #87 from alanpeng/main
Add weaviate database service
2025-05-17 11:20:10 +08:00
root
a52b017e1a Add weaviate database service 2025-05-17 10:11:16 +08:00
ageer
158a0190b5 feat: 优化会话管理查询逻辑 2025-05-16 23:52:43 +08:00
ageerle
096fc11313 Merge pull request #86 from alanpeng/main
Containerized service package/imagese build and deploy
2025-05-16 22:36:21 +08:00
root
e9ac795d23 Add manual 2025-05-16 22:31:14 +08:00
root
32f1a6bab1 Containerized service package/imagese build and deploy 2025-05-16 22:29:36 +08:00
ageerle
052069a2df Merge pull request #85 from MuSan-Li/feature_20250516_fix_auth
修复未登录也可以访问接口
2025-05-16 22:11:41 +08:00
MuSan-Li
e1c997883f 修复未登录也可以访问接口 2025-05-16 21:01:37 +08:00
ageerle
031b7da198 Merge pull request #84 from janzhou123/main
feat:pdf文件解析图片和分析图片,上传向量数据库都修改成 成异步处理
2025-05-16 16:29:21 +08:00
zhouweiyi
931af963b1 feat:pdf文件解析图片和分析图片,上传向量数据库都修改成 成异步处理 2025-05-16 13:50:12 +08:00
Albert
f5fa7d5da8 Merge branch 'ageerle:main' into main 2025-05-15 17:49:09 +08:00
zhouweiyi
c6ffbcb3cf feat:pdf文件解析图片和分析图片,上传向量数据库都修改成 成异步处理 2025-05-15 17:46:38 +08:00
ageerle
05ae200ff5 Merge pull request #82 from chenwei1015/fix-20250514-knowledge_info
fix- 20250514.sql, alter单词错误
2025-05-14 17:42:21 +08:00
weizhangfz
e745f772ef fix- 20250514.sql, alter单词错误 2025-05-14 17:23:34 +08:00
zhouweiyi
584212c569 Merge remote-tracking branch 'origin/main'
# Conflicts:
#	ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/KnowledgeInfoServiceImpl.java
2025-05-14 16:13:07 +08:00
zhouweiyi
dc9bf3e25d pdf文件解析成异步处理 2025-05-14 15:41:57 +08:00
ageerle
da84a26c47 feat: 更新sql脚本 2025-05-14 13:56:09 +08:00
ageerle
7d3282c347 feat: 修复知识库上传失败 2025-05-14 13:53:49 +08:00
ageerle
4454be44c3 Update README.md 2025-05-13 14:40:27 +08:00
zhouweiyi
52e0feda01 修改方法描述 2025-05-13 13:44:31 +08:00
ageerle
c89f5d07fb Merge pull request #80 from janzhou123/main
Extract the image from the PDF and call the large model to identify the image content and return it
2025-05-13 11:08:20 +08:00
zhouweiyi
778a7bc21b 提取PDF中的图片并调用大模型,识别图片内容并返回 2025-05-13 11:01:33 +08:00
zhouweiyi
50f5f38996 提取PDF中的图片并调用大模型,识别图片内容并返回 2025-05-13 10:56:05 +08:00
zhouweiyi
32da85daab 提取PDF中的图片并调用大模型,识别图片内容并返回 2025-05-13 10:55:39 +08:00
Albert
3666157d14 Merge pull request #1 from ageerle/main
new
2025-05-13 09:59:36 +08:00
ageer
1a178d0ef8 Merge remote-tracking branch 'origin/main' 2025-05-11 21:38:01 +08:00
ageer
b64b758263 feat: 小助手wx 2025-05-11 21:37:55 +08:00
ageerle
a68c6481ba Update README.md 2025-05-11 21:37:09 +08:00
ageerle
3cbacb5ed9 Update README.md 2025-05-11 21:35:53 +08:00
ageerle
38667797d3 Update README.md 2025-05-11 21:34:42 +08:00
ageerle
9a6acb84a4 Update README.md 2025-05-11 21:33:59 +08:00
ageerle
e3c8a84849 Update README.md 2025-05-11 21:32:39 +08:00
ageerle
cc349f5954 Update README.md 2025-05-11 21:31:01 +08:00
ageerle
e4b78e0510 Update README.md 2025-05-11 21:30:08 +08:00
ageerle
15c6cd272e Update README.md 2025-05-11 21:28:59 +08:00
ageerle
1614eff008 Update README.md 2025-05-11 21:27:49 +08:00
ageerle
c86e88ebd1 Update README.md 2025-05-11 21:26:57 +08:00
ageerle
39ebd85f58 Update README.md 2025-05-11 21:25:50 +08:00
ageer
bde82fe516 feat: 忽略README.md 2025-05-11 19:45:57 +08:00
ageer
2ca650db76 feat: 代码优化 2025-05-11 19:37:52 +08:00
ageer
e3da640737 feat: 代码优化 2025-05-11 18:37:51 +08:00
ageer
e1dea1d9e2 Merge remote-tracking branch 'origin/main' 2025-05-11 17:25:08 +08:00
ageer
84b8d6f675 feat: 支持coze,dify,派欧云等三方模型调用 2025-05-11 17:25:02 +08:00
ageerle
8563428bf9 Merge pull request #77 from zhangpengxiang/ExcelFileLader/dev
feat:补充Excel文档加载类
2025-05-11 14:55:44 +08:00
zhangpengxiang
7b3b727c0e 补充Excel文档加载类 2025-05-11 14:50:11 +08:00
ageerle
08098ff3f3 Merge pull request #76 from zhangpengxiang/ExcelFileLader/dev
增加Excel文档加载功能,处理向量库管理createSchema方法报错问题
2025-05-11 14:31:55 +08:00
zhangpengxiang
fb492d41f3 增加Excel文档加载功能,处理向量库管理createSchema方法WeaviateEmbeddingStore.builder().build();直接构建报错问题。 2025-05-11 10:55:33 +08:00
ageerle
9c2586ab43 Update README.md 2025-05-09 17:24:55 +08:00
ageerle
1bddf5df3b feat: 兼容多平台模型 2025-05-09 16:45:41 +08:00
ageer
57b2f833f8 feat: 修复向量库初始化失败导致程序启动失败 2025-05-08 23:56:40 +08:00
ageer
7907cbeb7f feat: 修复向量库初始化失败导致程序启动失败 2025-05-08 19:31:46 +08:00
ageer
9cfcdd2b9b feat: 代码优化 2025-05-08 19:00:48 +08:00
ageerle
980df20752 feat: 支持milvus、qdrant向量库 2025-05-08 16:09:02 +08:00
ageerle
aa92d232bb feat: Weaviate操作向量库功能优化 2025-05-08 10:41:01 +08:00
ageer
81c0bb5738 feat: Weaviate改为langchain4j方式调用 2025-05-07 22:53:21 +08:00
ageerle
1a645c6e10 feat: 接入langchain4j操作向量库 2025-05-07 17:33:22 +08:00
ageer
731f6ceb6e feat: sql脚本 2025-05-05 15:10:53 +08:00
ageer
8e21245348 feat: sql脚本 2025-05-05 15:04:02 +08:00
ageer
936e157e4a feat: sql脚本 2025-05-05 15:02:57 +08:00
ageer
256f72c487 feat: 支付模块引用 2025-05-03 15:37:56 +08:00
ageer
009aa5f1a5 feat: 添加接口说明 2025-05-03 15:27:46 +08:00
ageer
d22d2cb708 fix(接口文档): 修复接口文档/v3/api-docs无法正常访问 2025-05-03 14:31:20 +08:00
ageer
59104028b6 feat: 会话管理 2025-05-03 10:45:29 +08:00
ageer
c2f6a8321a feat: 会话管理 2025-05-03 10:41:50 +08:00
ageer
5476a4b0b7 fix: 公众号登录功能同步 2025-04-30 19:34:09 +08:00
ageerle
cd490aa0e5 feat: 新增wechat模块 2025-04-30 11:29:23 +08:00
ageerle
b1ff44df4b feat: sse对话样式优化 2025-04-29 11:44:47 +08:00
ageerle
dc6d00f0fc feat: 查询gpts无需登录 2025-04-29 11:00:34 +08:00
ageerle
990da8da6f feat: 增加工具调用超时时间配置 2025-04-24 17:09:52 +08:00
ageerle
8fc7ad0359 feat: mcp开启后才执行工具调用 2025-04-24 10:31:58 +08:00
ageer
8a89d9eb9c fix: 1.合并知识库对话接口 2.修复web无法查询套餐信息 2025-04-23 23:10:31 +08:00
ageer
abfa9ae97b fix: 1.合并知识库对话接口 2.修复web无法查询套餐信息 2025-04-23 23:09:56 +08:00
ageer
bde2e8ff8e fix: 1.合并知识库对话接口 2.修复web无法查询套餐信息 2025-04-23 23:09:22 +08:00
ageer
4d8eb1850f feat: 附件上传逻辑调整 2025-04-22 21:43:40 +08:00
ageer
5aaa3a5662 fix: 1. 修复No static resource tool/gen/list. 2. 修复新增数据库校验异常 2025-04-22 21:24:00 +08:00
ageerle
8fec96f5b2 feat: 默认关闭mcp功能 2025-04-22 18:01:40 +08:00
ageerle
15c306eca2 fix: 根据不同的模型构建对话客户端 2025-04-22 11:28:10 +08:00
ageerle
620ea1fc76 fix: 系统提示词非必填 2025-04-22 11:06:38 +08:00
ageerle
c5c375dc6d fix: 扣费时无法获取用户id 2025-04-22 10:43:54 +08:00
ageerle
1b793e822a fix: 扣费时无法获取用户id 2025-04-22 10:40:36 +08:00
ageerle
6281840f36 fix: 修复后台管理系统登录异常 2025-04-22 09:12:56 +08:00
ageer
f5daa7eb78 feat: 设置server-filesystem默认路径为D盘 防止启动报错 2025-04-21 20:34:05 +08:00
ageer
52cb563383 fix(1.修复/store/appList404 2.修复无法查询数据库 3.修复请求地址'/chat/config/configKey/logoImage',发生系统异常): 2025-04-21 20:16:53 +08:00
ageerle
69efc3261e Merge branch 'dev' 2025-04-21 09:44:09 +08:00
ageerle
fb19fb29cc feat: 移除文件 2025-04-21 09:43:09 +08:00
ageer
9f257cf712 feat: mcp升级依赖 2025-04-20 22:52:09 +08:00
ageer
4af26fe4b4 feat: mcp升级依赖 2025-04-20 22:52:00 +08:00
ageerle
ab2a118ee9 feat: 更新项目说明 2025-04-18 11:11:48 +08:00
ageerle
9cd97a4dc5 feat: mcp支持远程调用 2025-04-18 10:55:42 +08:00
ageerle
788b372e32 feat: mcp支持远程调用 2025-04-17 16:23:32 +08:00
lh
744f9b6c7f 更新readme.md 2025-04-17 11:09:00 +08:00
ageer
761d954ef1 feat: mcp 1.0.0 2025-04-16 21:56:05 +08:00
ageerle
d1006f50ad feat: mcp-1.0.0 2025-04-16 10:25:10 +08:00
ageerle
f24ff5bbdd feat: mcp 测试版本 2025-04-15 17:34:21 +08:00
ageer
a4314dbbde feat: mcp测试版 2025-04-14 23:10:17 +08:00
ageerle
188dc1e55e feat: mcp 测试版本 2025-04-14 17:23:33 +08:00
ageer
731352fd04 feat: mcp测试版 2025-04-14 00:22:21 +08:00
ageer
4b53939002 feat: mcp支持,模块重构 2025-04-12 15:18:16 +08:00
lh
b50c15755d 更新readme.md 2025-04-12 14:19:51 +08:00
ageer
0dc6262b39 feat: mcp支持 2025-04-12 10:09:01 +08:00
ageer
9b32e3b3b2 feat: mcp支持 2025-04-12 10:08:51 +08:00
ageerle
37a8b7dad3 feat: 对话模块重构 2025-04-11 15:47:05 +08:00
ageerle
efeb0bd6fb fix: ollama兼容联网查询 知识库检索 2025-04-11 12:05:13 +08:00
ageerle
af33040117 fix: 语音功能无法使用 2025-04-11 10:45:02 +08:00
ageerle
ac4c037634 feat: 重构模块 2025-04-11 09:51:23 +08:00
ageerle
9734ec53f7 feat: 重构模块 2025-04-11 09:34:37 +08:00
ageer
2b8a92c7d6 fix: 重构模块 2025-04-10 22:18:44 +08:00
ageer
adb4538317 fix: 修复使用本地向量模型只能检索到一条知识内容 2025-04-10 21:39:31 +08:00
ageerle
2509099146 feat: 重构模块 2025-04-10 17:25:23 +08:00
ageerle
3be9005f95 feat: 调整知识库模块 2025-04-09 17:41:29 +08:00
ageerle
be6d027cad feat: 删除代码生成脚本 2025-04-09 09:42:14 +08:00
ageerle
3d679f8749 Merge remote-tracking branch 'origin/dev' into dev 2025-04-09 09:40:28 +08:00
ageer
5a5a48e153 feat: ruoyi-chat-api模块调整 2025-04-09 09:40:17 +08:00
ageer
e5da648941 feat: 代码生成脚本 2025-04-08 21:30:01 +08:00
ageerle
d2755f00bc feat: 测试版本提交 2025-04-08 16:48:06 +08:00
lh
09abc0b5af 新增讨论群信息 2025-04-08 14:05:05 +08:00
ageerle
00f362acf1 Merge remote-tracking branch 'origin/main' 2025-04-08 11:11:10 +08:00
ageerle
65d479458e fix: 修复部门树无法加载 2025-04-08 11:11:00 +08:00
ageerle
57e17e0dda Merge pull request #58 from winkeylucky/winkey-0407-02
修改应用使用,通过配置的应用的appid modelname字段关联 0407 02
2025-04-08 09:12:50 +08:00
winkey
691d1735fc 数据库更新 2025-04-07 22:42:06 +08:00
winkey
360984bc4b 应用增加系统角色提示词 2025-04-07 22:41:56 +08:00
winkey
0153f004f4 修改应用使用,通过配置的应用的appid modelname字段关联 2025-04-07 22:41:33 +08:00
ageerle
cc23508527 Merge pull request #57 from winkeylucky/winkey_0407
向量模型通过模型管理获取配置
2025-04-07 16:00:33 +08:00
winkey
c884f4f2d3 向量模型插入模型库sql 2025-04-07 15:27:20 +08:00
winkey
fab6de1f5c 知识库对话修改 2025-04-07 15:16:50 +08:00
winkey
c02f66636d 向量模型通过模型管理获取配置 2025-04-07 15:16:31 +08:00
ageerle
c1162148b1 Merge pull request #50 from winkeylucky/winkey-0502
解决登录异常_和公共资源例外 0502
2025-04-07 14:02:35 +08:00
ageerle
f76fdbf3ad Merge pull request #46 from winkeylucky/winkey-main
增加deepseek深度思考返回
2025-04-07 14:02:18 +08:00
winkey
feca08b3ec 获取公告和版权信息权限例外 2025-04-02 15:13:53 +08:00
winkey
72675b17c4 解决登录异常问题 2025-04-02 15:13:39 +08:00
ageerle
9ea5186f49 feat: 微信机器人应用脚本 2025-04-02 10:51:53 +08:00
winkey
d0a2eadc38 增加deepseek深度思考返回 2025-04-01 13:34:31 +08:00
1343 changed files with 16376 additions and 184587 deletions

2
.gitignore vendored
View File

@@ -13,6 +13,8 @@ target/
ruoyi-modules/ruoyi-generator/src/main/resources/vm/vben5
README.md
######################################################################
# IDE

204
README.md
View File

@@ -34,55 +34,127 @@
<a href="https://github.com/ageerle/ruoyi-ai/issues">提出新特性</a>
</p>
## 快速启动
### 拉取镜像(最低配置2H2G):
```bash
script/deploy/deploy目录下执行: docker-compose up -d
```
### 通过脚本启动(最低配置4H4G):
1. 确认系统内已经安装好以下软件
- docker
- docker-compose
- git
- unzip
2. **克隆项目**
```bash
git clone https://github.com/ageerle/ruoyi-ai
cd ruoyi-ai/script/deploy/one-step-script
```
3. **启动部署脚本**
中文界面部署脚本(拉取gitee仓库)
```bash
./deploy-cn.sh
```
按照脚本提示一步步操作,如果是一台新服务器,选择默认配置,直接回车即可。
<img src="image/deploy-01.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);"/>
耐心等待安装完成...
英文界面部署脚本(拉取github仓库)
```bash
./deploy-en.sh
```
4. 如果在执行部署脚本过程中不需要在本地重新构建编译服务软件包以及重新封装容器镜像则需要在脚本交互提出以下问题时选择D按键进行直接部署否则就会执行全新的编译构建及容器封装之后再执行部署
```
已将模板文件复制到部署目录。
正在使用您的配置更新 .env 文件...
已使用您的配置更新 .env 文件。
正在使用您的配置更新 docker-compose.yaml 文件...
已使用您的配置更新 docker-compose.yaml 文件。
=== 构建或部署选项 ===
您想构建新镜像 (B) 还是直接使用现有镜像部署 (D)[B/d]:
```
5. **访问应用界面**
- 用户界面:`http://your-server-ip:8081`
- 管理员界面:`http://your-server-ip:8082`
## 目录
- [系统体验](#系统体验)
- [源码地址](#源码地址)
- [特色功能](#特色功能)
- [配套文档](#项目文档)
- [核心功能](#核心功能)
- [项目演示](#项目演示)
- [后台管理](#后台管理)
- [管理](#管理)
- [用户端](#用户端)
- [小程序端](#小程序端)
- [开发前的配置要求](#开发前的配置要求)
- [文件目录说明](#文件目录说明)
- [使用到的框架](#使用到的框架)
- [开发环境](#开发环境)
- [项目结构](#项目结构)
- [ruoyi-ai](#ruoyi-ai)
- [注意事项](#注意事项)
- [vben模板](#vben模板)
- [贡献者](#贡献者)
- [如何参与开源项目](#如何参与开源项目)
- [版本控制](#版本控制)
- [作者](#作者)
- [鸣谢](#鸣谢)
- [技术讨论群](#技术讨论群)
### 系统体验
- 用户端https://web.pandarobot.chat
- 演示账号: demo 密码demo123
- 管理端https://admin.pandarobot.chat
- 演示账号: admin 密码admin123
- 商业版体验商业版请联系下方小助手获取演示地址预计6月份上线
### 源码地址
- 项目文档: 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
[1]github
- 前端服务-用户端: https://github.com/ageerle/ruoyi-web
- 前端服务-管理端: https://github.com/ageerle/ruoyi-admin
- 后端服务:https://github.com/ageerle/ruoyi-ai
### 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
[2]gitcode
- 前端服务-用户端:https://gitcode.com/ageerle/ruoyi-web
- 前端服务-管理端: https://gitcode.com/ageerle/ruoyi-admin
- 后端服务:https://gitcode.com/ageerle/ruoyi-ai
### 特色功能
1. 全套开源系统提供完整的前端应用、后台管理以及小程序应用基于MIT协议开箱即用。
2. 本地RAG方案集成Milvus/Weaviate向量库、本地向量化模型与Ollama实现本地化RAG
3. 丰富插件功能支持联网、SQL查询插件及Text2API插件扩展系统能力与应用场景。
4. 内置SSE、websocket等网络协议支持对接多种大语言模型同时还集成了MidJourney和DALLE AI绘画功能
5. 强大的多媒体功能支持AI翻译、PPT制作、语音克隆和翻唱等
6. 扩展功能:支持将大模型接入个人或企业微信
7. 支付功能:支持易支付、微信支付等多种支付方式
### 配套文档
- 配套文档: https://doc.pandarobot.chat
- 项目部署文档https://doc.pandarobot.chat/guide/introduction/
### 核心功能与技术亮点
#### 1. 全栈式开源系统
- 全套开源系统:提供完整的前端应用、后台管理,基于MIT协议开箱即用。
#### 2. 本地化 RAG 方案
- 基于 **Langchain4j** 框架,支持 Milvus/Weaviate/Qdrant 向量库,结合 BGE-large-zh-v1.5 本地向量化模型 实现高效文档检索与知识库构建。
- 支持 本地 LLM 接入,结合私有知识库实现安全可控的问答系统,避免依赖云端服务的隐私风险。
#### 3. 多模态 AI 引擎与工具集成
- 智能对话:支持 OpenAI GPT-4、Azure、ChatGLM 等主流模型,内置 SSE/WebSocket 协议实现低延迟交互,兼容 **扣子**、**DIFY** 等平台 API 调用。
- **Spring AI MCP** 支持:通过注解快速定义本地工具,支持调用 MCP 广场 的海量 MCP Server 服务,扩展模型能力边界。
#### 4. 企业级扩展与商业化支持
- 即时通讯集成:支持对接个人微信、企业微信及微信公众号,实现消息自动回复、用户管理与智能客服。
- 支付系统集成易支付、微信支付、Stripe 国际信用卡支付,满足商业化场景需求。
#### 5. 多媒体处理与创新功能
- AI 绘画:集成 DALL·E-3、MidJourney、Stable Diffusion支持文生图、图生图及风格化创作适用于营销素材生成与创意设计。
- PPT 制作:根据文本输入自动生成结构化幻灯片,支持自定义模板(需要使用三方平台 如:文多多)。
### 项目演示
#### 后台管理
#### mcp支持
<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);"/>
<img src="image/mcp-01.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/mcp-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/mcp-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/mcp-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);"/>
</div>
@@ -94,13 +166,16 @@
<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 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>
### 开发前的配置要求
### 开发环境
1. jdk 17
2. mysql 5.7、8.0
@@ -108,8 +183,14 @@
4. maven 3.8+
5. nodejs 20+ & pnpm
### 文件目录说明
RuoYi-AI
- 附-部署配套视频https://www.bilibili.com/video/BV1jDXkYWEba
<div>
<img src="image/教程搭建.png" alt="drawing" width="600px" height="300px"/>
</div>
### 项目结构
- RuoYi-AI
```
├─ ruoyi-admin // 管理模块
@@ -158,32 +239,34 @@ RuoYi-AI
```
### 注意事项
- vben模板
Qvben5 的模板默认是没有的吗?
Avben模板是收费的 请联系vben-vue-plus作者获取。
### 版本控制
该项目使用Git进行版本管理。您可以在repository参看当前可用版本。
### 版权说明
该项目使用了MIT授权许可详情请参阅 [LICENSE.txt](https://github.com/ageerle/ruoyi-ai/blob/main/LICENSE)
### 项目现状
目前项目还处于早期阶段距离成熟还有很长的路要走。由于个人精力有限项目的发展速度受到了一定的限制。为了加快项目的进度我真诚地希望更多人能够参与到项目中来。无论是经验丰富的开发者还是刚刚入门的小白我都热烈欢迎你们提交Pull RequestPR。即使代码修改得很少或者存在一些错误都没有关系。我会认真审核每一位贡献者的代码并和大家一起完善项目。
目前项目还处于早期阶段距离成熟还有很长的路要走。由于个人精力有限项目的发展速度受到了一定的限制。为了加快项目的进度我真诚地希望更多人能够参与到项目中来。无论是经验丰富的开发者还是刚刚入门的小白我都热烈欢迎你们提交Pull RequestPR👏👏👏。即使代码修改得很少,或者存在一些错误,都没有关系。我会认真审核每一位贡献者的代码,并和大家一起完善项目⛽️⛽️⛽️
### 开发计划
- 智能体管理
| 主题 | 方向 | 时间节点 |
| --- |-----------------------------------|--------|
| 前端简化版 | 与element-plus-x框架合作推出基于该框架的前端简化版 | 2025.5 |
| agent2agent | Agent2Agent协议支持 | 2025.6 |
| 流程编排 | 通过可视化界面和灵活的配置方式快速构建AI应用 | 2025.7 |
通过设置提示词、插件、知识库等用户可以快速构建一个AI应用。这将极大地简化AI应用的开发流程降低开发门槛使更多企业能够轻松地利用AI技术。
<div>
<img src="image/13.png" alt="drawing" width="600px" height="300px"/>
</div>
- 流程编排
通过流程编排功能,用户可以将不同的模型按照业务逻辑进行有序连接。这将解决单一模型能力不足的问题,充分发挥多个模型的协同作用,从而更好地满足企业的复杂业务需求。
- 感谢
@@ -193,7 +276,7 @@ RuoYi-AI
#### 如何参与开源项目
贡献使开源社区成为一个学习、激励和创造的绝佳场所。你所作的任何贡献都是**非常感谢**的。
贡献使开源社区成为一个学习、激励和创造的绝佳场所。你所作的任何贡献,我们都非常感谢!🙏
1. Fork 这个项目
2. 创建你的功能分支 (`git checkout -b feature/dev`)
@@ -204,7 +287,7 @@ RuoYi-AI
#### 项目文档
1. 项目文档基于vitepress构建
2. 按照[如何参与开源项目](#如何参与开源项目)拉取 https://github.com/ageerle/ruoyi-doc
2. 按照[如何参与开源项目](#如何参与开源项目)拉取https://github.com/ageerle/ruoyi-doc
3. 安装依赖npm install
4. 启动项目npm run docs:dev
5. 主页路径docs/guide/introduction/index.md
@@ -230,5 +313,24 @@ RuoYi-AI
[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
## 🌿 第三方生态
- [PPIO 派欧云:一键调用高性价比的开源模型 API 和 GPU 容器](https://ppinfra.com/user/register?invited_by=P8QTUY&utm_source=github_ruoyi-ai)
### 附:技术讨论群
#### 技术交流(如需进群请添加小助手)
<div style="display: flex; flex-wrap: wrap; gap: 20px; justify-content: center;">
<img src="image/wx.png" alt="drawing" style="width: 400px; height: 400px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
</div>
#### 进群学习
🏠 小助手wxruoyi-ai加人备注ruoyi-ai
🏠 小助手qq1603234088 加人备注ruoyi-ai
👏👏👏 ruoyi-ai官方交流群qq区
<div style="display: flex; flex-wrap: wrap; gap: 20px; justify-content: center;">
<img src="image/qq.png" alt="drawing" style="width: 400px; height: 400px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
</div>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 563 KiB

BIN
image/deploy-01.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

BIN
image/mcp-01.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

BIN
image/mcp-02.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 KiB

BIN
image/mcp-03.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

BIN
image/mcp-04.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

BIN
image/qq-msg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 391 KiB

BIN
image/qq.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 392 KiB

BIN
image/wx.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

BIN
image/教程搭建.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 KiB

97
pom.xml
View File

@@ -14,25 +14,26 @@
<properties>
<revision>1.0.0</revision>
<spring-boot.version>3.0.6</spring-boot.version>
<spring-boot.version>3.4.4</spring-boot.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>17</java.version>
<spring-boot.mybatis>3.0.1</spring-boot.mybatis>
<springdoc.version>2.1.0</springdoc.version>
<mysql.version>8.0.33</mysql.version>
<mybatis.version>3.5.16</mybatis.version>
<springdoc.version>2.8.5</springdoc.version>
<therapi-javadoc.version>0.15.0</therapi-javadoc.version>
<poi.version>5.2.3</poi.version>
<easyexcel.version>3.2.1</easyexcel.version>
<velocity.version>2.3</velocity.version>
<satoken.version>1.34.0</satoken.version>
<mybatis-plus.version>3.5.3.1</mybatis-plus.version>
<mybatis-plus.version>3.5.11</mybatis-plus.version>
<p6spy.version>3.9.1</p6spy.version>
<hutool.version>5.8.18</hutool.version>
<hutool.version>5.8.35</hutool.version>
<okhttp.version>4.10.0</okhttp.version>
<dynamic-ds.version>4.3.1</dynamic-ds.version>
<spring-boot-admin.version>3.0.3</spring-boot-admin.version>
<redisson.version>3.20.1</redisson.version>
<lock4j.version>2.2.4</lock4j.version>
<dynamic-ds.version>3.6.1</dynamic-ds.version>
<alibaba-ttl.version>2.14.2</alibaba-ttl.version>
<xxl-job.version>2.4.0</xxl-job.version>
<mapstruct-plus.version>1.2.1</mapstruct-plus.version>
@@ -41,10 +42,6 @@
<bouncycastle.version>1.72</bouncycastle.version>
<!-- 离线IP地址定位库 -->
<ip2region.version>2.7.0</ip2region.version>
<!-- 临时修复 snakeyaml 漏洞 -->
<snakeyaml.version>1.33</snakeyaml.version>
<!-- OSS 配置 -->
<aws-java-sdk-s3.version>1.12.400</aws-java-sdk-s3.version>
<!-- SMS 配置 -->
@@ -57,9 +54,6 @@
<maven-compiler-plugin.verison>3.11.0</maven-compiler-plugin.verison>
<maven-surefire-plugin.version>3.0.0</maven-surefire-plugin.version>
<flatten-maven-plugin.version>1.3.0</flatten-maven-plugin.version>
<weixin-java-miniapp.version>4.5.0</weixin-java-miniapp.version>
<weixin-java-pay.version>4.6.0</weixin-java-pay.version>
<weixin-java-cp.version>4.6.0</weixin-java-cp.version>
</properties>
<profiles>
@@ -96,6 +90,12 @@
<dependencyManagement>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!-- SpringBoot的依赖配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
@@ -194,22 +194,22 @@
<version>${satoken.version}</version>
</dependency>
<!-- dynamic-datasource 多数据源-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>${dynamic-ds.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${spring-boot.mybatis}</version>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-jsqlparser</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
@@ -219,6 +219,13 @@
<version>${mybatis-plus.version}</version>
</dependency>
<!-- dynamic-datasource 多数据源-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
<version>${dynamic-ds.version}</version>
</dependency>
<!-- sql性能分析插件 -->
<dependency>
<groupId>p6spy</groupId>
@@ -244,18 +251,6 @@
<version>${tencent.sms.version}</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>de.codecentric</groupId>-->
<!-- <artifactId>spring-boot-admin-starter-server</artifactId>-->
<!-- <version>${spring-boot-admin.version}</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>de.codecentric</groupId>-->
<!-- <artifactId>spring-boot-admin-starter-client</artifactId>-->
<!-- <version>${spring-boot-admin.version}</version>-->
<!-- </dependency>-->
<!--redisson-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
@@ -281,13 +276,6 @@
<version>${alibaba-ttl.version}</version>
</dependency>
<!-- 临时修复 snakeyaml 漏洞 -->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>${snakeyaml.version}</version>
</dependency>
<!-- 加密包引入 -->
<dependency>
<groupId>org.bouncycastle</groupId>
@@ -321,10 +309,21 @@
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-knowledge-api</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-knowledge</artifactId>
<artifactId>ruoyi-chat-api</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-system-api</artifactId>
<version>${revision}</version>
</dependency>
@@ -333,21 +332,17 @@
<artifactId>ruoyi-generator</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-demo</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
</dependencyManagement>
<modules>
<module>ruoyi-admin</module>
<module>ruoyi-common</module>
<module>ruoyi-modules</module>
<module>ruoyi-modules-api</module>
<module>ruoyi-admin</module>
<module>ruoyi-extend</module>
</modules>
<packaging>pom</packaging>
<build>

View File

@@ -1,35 +0,0 @@
#基础镜像
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 /ruoyi/server/logs \
/ruoyi/server/temp \
/ruoyi/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=ruoyi-server", \
# "-javaagent:/ruoyi/skywalking/agent/skywalking-agent.jar", \
"-jar", "app.jar"]

View File

@@ -42,11 +42,6 @@
<artifactId>mssql-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-common-doc</artifactId>
</dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-system</artifactId>
@@ -57,41 +52,11 @@
<artifactId>ruoyi-chat</artifactId>
</dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-knowledge</artifactId>
</dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-generator</artifactId>
</dependency>
<!-- demo模块 -->
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-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>

View File

@@ -3,6 +3,8 @@ package org.ruoyi;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* 启动程序
@@ -10,6 +12,8 @@ import org.springframework.boot.context.metrics.buffering.BufferingApplicationSt
* @author Lion Li
*/
@SpringBootApplication
@EnableScheduling
@EnableAsync
public class RuoYiAIApplication {
public static void main(String[] args) {

View File

@@ -2,15 +2,11 @@ package org.ruoyi.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 jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.ruoyi.common.core.constant.Constants;
import org.ruoyi.common.core.domain.R;
import org.ruoyi.common.core.domain.model.EmailLoginBody;
import org.ruoyi.common.core.domain.model.LoginBody;
import org.ruoyi.common.core.domain.model.RegisterBody;
import org.ruoyi.common.core.domain.model.SmsLoginBody;
import org.ruoyi.common.core.domain.model.VisitorLoginBody;
import org.ruoyi.common.core.domain.model.*;
import org.ruoyi.common.core.utils.MapstructUtils;
import org.ruoyi.common.core.utils.StreamUtils;
import org.ruoyi.common.core.utils.StringUtils;
@@ -18,16 +14,12 @@ import org.ruoyi.common.satoken.utils.LoginHelper;
import org.ruoyi.common.tenant.helper.TenantHelper;
import org.ruoyi.system.domain.bo.SysTenantBo;
import org.ruoyi.system.domain.vo.LoginTenantVo;
import org.ruoyi.system.domain.vo.LoginVo;
import org.ruoyi.system.domain.vo.SysTenantVo;
import org.ruoyi.system.domain.vo.TenantListVo;
import org.ruoyi.system.service.ISysTenantService;
import org.ruoyi.system.service.SysLoginService;
import org.ruoyi.system.service.SysRegisterService;
import org.ruoyi.system.domain.vo.LoginVo;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@@ -50,15 +42,6 @@ public class AuthController {
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);
}
/**
* 登录方法
*
@@ -75,6 +58,7 @@ public class AuthController {
body.getUsername(), body.getPassword(),
body.getCode(), body.getUuid());
loginVo.setToken(token);
// 兼容后台管理登录
loginVo.setAccess_token(token);
loginVo.setUserInfo(LoginHelper.getLoginUser());
return R.ok(loginVo);
@@ -97,7 +81,6 @@ public class AuthController {
/**
* 访客登录
*
* @param loginBody 登录信息
* @return token信息
*/
@@ -136,7 +119,7 @@ public class AuthController {
*/
@PostMapping("/register")
public R<Void> register(@Validated @RequestBody RegisterBody user, HttpServletRequest request) {
String domainName = request.getServerName();
String domainName = request.getServerName();
user.setDomainName(domainName);
registerService.register(user);
return R.ok();

View File

@@ -5,10 +5,13 @@ import cn.hutool.captcha.AbstractCaptcha;
import cn.hutool.captcha.generator.CodeGenerator;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.common.core.constant.Constants;
import org.ruoyi.common.core.constant.GlobalConstants;
import org.ruoyi.common.core.domain.R;
import org.ruoyi.common.core.exception.ServiceException;
import org.ruoyi.common.core.service.ConfigService;
import org.ruoyi.common.core.utils.SpringUtils;
import org.ruoyi.common.core.utils.StringUtils;
@@ -22,10 +25,6 @@ import org.ruoyi.common.web.config.properties.CaptchaProperties;
import org.ruoyi.common.web.enums.CaptchaType;
import org.ruoyi.system.domain.request.EmailRequest;
import org.ruoyi.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;
@@ -36,7 +35,8 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.time.Duration;
import java.util.*;
import java.util.HashMap;
import java.util.Map;
/**
* 验证码操作处理
@@ -67,7 +67,7 @@ public class CaptchaController {
String key = GlobalConstants.CAPTCHA_CODE_KEY + phonenumber;
String code = RandomUtil.randomNumbers(4);
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
// 验证码模板id 自行处理 (查数据库或写死均可)
// 验证码模板id
String templateId = "";
Map<String, String> map = new HashMap<>(1);
map.put("code", code);
@@ -90,18 +90,6 @@ public class CaptchaController {
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");

View File

@@ -1,7 +1,6 @@
package org.ruoyi.controller;
import cn.dev33.satoken.annotation.SaIgnore;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -11,7 +10,6 @@ import org.springframework.web.bind.annotation.RestController;
* @author Lion Li
*/
@SaIgnore
@RequiredArgsConstructor
@RestController
public class IndexController {
@@ -20,7 +18,9 @@ public class IndexController {
*/
@GetMapping("/")
public String index() {
return "RuoYi-AI 启动成功";
return "RuoYi AI启动成功";
}
}

View File

@@ -1,208 +0,0 @@
package org.ruoyi.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.ruoyi.common.chat.config.ChatConfig;
import org.ruoyi.common.chat.domain.request.ChatRequest;
import org.ruoyi.common.chat.entity.chat.ChatCompletion;
import org.ruoyi.common.chat.entity.chat.Message;
import org.ruoyi.common.chat.openai.OpenAiStreamClient;
import org.ruoyi.common.core.domain.R;
import org.ruoyi.common.core.validate.AddGroup;
import org.ruoyi.common.excel.utils.ExcelUtil;
import org.ruoyi.common.log.annotation.Log;
import org.ruoyi.common.log.enums.BusinessType;
import org.ruoyi.common.mybatis.core.page.PageQuery;
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
import org.ruoyi.common.satoken.utils.LoginHelper;
import org.ruoyi.common.web.core.BaseController;
import org.ruoyi.knowledge.domain.bo.KnowledgeAttachBo;
import org.ruoyi.knowledge.domain.bo.KnowledgeFragmentBo;
import org.ruoyi.knowledge.domain.bo.KnowledgeInfoBo;
import org.ruoyi.knowledge.domain.req.KnowledgeInfoUploadRequest;
import org.ruoyi.knowledge.domain.vo.KnowledgeAttachVo;
import org.ruoyi.knowledge.domain.vo.KnowledgeFragmentVo;
import org.ruoyi.knowledge.domain.vo.KnowledgeInfoVo;
import org.ruoyi.knowledge.service.EmbeddingService;
import org.ruoyi.knowledge.service.IKnowledgeAttachService;
import org.ruoyi.knowledge.service.IKnowledgeFragmentService;
import org.ruoyi.knowledge.service.IKnowledgeInfoService;
import org.ruoyi.system.listener.SSEEventSourceListener;
import org.ruoyi.system.service.ISseService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.ruoyi.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

@@ -1,12 +1,3 @@
--- # 监控中心配置
spring.boot.admin.client:
# 增加客户端开关
enabled: false
url: http://localhost:9090/admin
instance:
service-host-type: IP
username: ruoyi
password: 123456
--- # 数据源配置
spring:
@@ -25,11 +16,10 @@ spring:
master:
type: ${spring.datasource.type}
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://43.139.70.230:3306/ruoyi-ai?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
url: jdbc:mysql://127.0.0.1:3306/ruoyi-ai?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
username: ruoyi-ai
password: ruoyi-ai
hikari:
# 最大连接池数量
maxPoolSize: 20
@@ -61,9 +51,6 @@ spring.data:
# password: 123456
# 连接超时时间
timeout: 10S
# 是否开启ssl
ssl: false
redisson:
# redis key前缀
keyPrefix:
@@ -97,3 +84,5 @@ sms:
signName: 测试
# 腾讯专用
sdkAppId:

View File

@@ -51,6 +51,8 @@ logging:
level:
org.ruoyi: @logging.level@
org.springframework: warn
org.mybatis.spring.mapper: error
org.apache.fury: warn
config: classpath:logback-plus.xml
# 用户配置
@@ -118,6 +120,8 @@ sa-token:
security:
# 排除路径
excludes:
# 获取模型信息
- /system/model/modelList
# 支付回调
- /pay/returnUrl
- /pay/notifyUrl
@@ -126,7 +130,9 @@ security:
# 重置密码
- /auth/reset/password
# 聊天接口
- /chat
- /chat/send
# 文件上传
- /chat/upload
# 静态资源
- /*.html
- /**/*.html
@@ -213,13 +219,18 @@ mybatis-encryptor:
publicKey:
privateKey:
# Swagger配置
swagger:
springdoc:
api-docs:
# 是否开启接口文档
enabled: true
# swagger-ui:
# # 持久化认证数据
# persistAuthorization: true
info:
# 标题
title: '标题:${ruoyi.name}多租户管理系统_接口文档'
title: '标题:RuoYi-Vue-Plus多租户管理系统_接口文档'
# 描述
description: '描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...'
description: ' 用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...'
# 版本
version: '版本号: ${ruoyi.version}'
# 作者信息
@@ -234,24 +245,10 @@ swagger:
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.系统模块
- group: 1.系统模块
packages-to-scan: org.ruoyi.system
- group: 4.代码生成模块
packages-to-scan: org.ruoyi.generator
# 防止XSS攻击
xss:
@@ -298,25 +295,20 @@ websocket:
# 设置访问源地址
allowedOrigins: '*'
# 微信小程序配置信息
wx:
miniapp:
configs:
- appid: # 你的appid
secret: # 你的secret
token: #微信小程序消息服务器配置的token
aesKey: #微信小程序消息服务器配置的EncodingAESKey
msgDataFormat: JSON
# 企业微信应用
wechat:
cp:
corpId:
appConfigs:
- agentId:
secret: ''
token: ''
aesKey: ''
spring:
ai:
openai:
api-key: sk-xx
base-url: https://api.pandarobot.chat/
mcp:
client:
enabled: false
name: ruoyi-ai-mcp
sse:
connections:
server:
url: http://127.0.0.1:8081
stdio:
servers-configuration: classpath:mcp-server.json
request-timeout: 300s

View File

@@ -0,0 +1,22 @@
{
"mcpServers": {
"fileSystem": {
"command": "C:\\Program Files\\nodejs\\npx.cmd",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"D:\\"
]
},
"search1api": {
"command": "C:\\Program Files\\nodejs\\npx.cmd",
"args": [
"-y",
"search1api-mcp"
],
"env": {
"SEARCH1API_KEY": "xx"
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

View File

@@ -2,13 +2,20 @@
<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>
<parent>
<artifactId>ruoyi-ai</artifactId>
<groupId>org.ruoyi</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ruoyi-common</artifactId>
<packaging>pom</packaging>
<description>
common 通用模块
</description>
<modules>
<module>ruoyi-common-bom</module>
@@ -33,15 +40,6 @@
<module>ruoyi-common-tenant</module>
<module>ruoyi-common-chat</module>
<module>ruoyi-common-pay</module>
<module>ruoyi-common-wechat</module>
<module>ruoyi-common-live</module>
</modules>
<artifactId>ruoyi-common</artifactId>
<packaging>pom</packaging>
<description>
common 通用模块
</description>
</project>

View File

@@ -166,14 +166,6 @@
<version>${revision}</version>
</dependency>
<!-- AI绘画 -->
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-chat</artifactId>
<version>${revision}</version>
</dependency>
<!-- 支付模块 -->
<dependency>
@@ -182,6 +174,7 @@
<version>${revision}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>

View File

@@ -18,6 +18,10 @@
<properties>
<retrofit2.version>2.9.0</retrofit2.version>
<azure.version>1.0.0-beta.12</azure.version>
<chatglm.version>release-V4-2.3.0</chatglm.version>
<okhttp.version>2.7.5</okhttp.version>
<jtokkit.version>0.5.0</jtokkit.version>
</properties>
<dependencies>
@@ -26,38 +30,22 @@
<artifactId>ruoyi-common-core</artifactId>
</dependency>
<!-- 序列化模块 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-common-json</artifactId>
</dependency>
<!-- redis模块 -->
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-common-redis</artifactId>
</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>ruoyi-common-json</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-common-redis</artifactId>
</dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-common-satoken</artifactId>
<version>${azure.version}</version>
</dependency>
<dependency>
@@ -79,13 +67,7 @@
<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>
<version>${jtokkit.version}</version>
</dependency>
<dependency>
@@ -98,21 +80,18 @@
</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>
<version>${chatglm.version}</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp</groupId>
<artifactId>okhttp</artifactId>
<version>2.7.5</version>
<scope>compile</scope>
<version>${okhttp.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -6,7 +6,6 @@ import cn.hutool.core.date.DateUnit;
import lombok.extern.slf4j.Slf4j;
/**
* 描述:
*
* @author https:www.unfbx.com
* @date 2023-03-10

View File

@@ -12,6 +12,9 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
public class WebSocketProperties {
/**
* 是否开启
*/
private Boolean enabled;
/**

View File

@@ -1,7 +1,6 @@
package org.ruoyi.common.chat.constant;
/**
* 描述:
*
* @author https:www.unfbx.com
* @since 2023-03-06
@@ -10,6 +9,8 @@ public class OpenAIConst {
public final static String OPENAI_HOST = "https://api.openai.com/";
public final static String apiUrl = "v1/chat/completions";
public final static int SUCCEED_CODE = 200;
}

View File

@@ -1,73 +0,0 @@
package org.ruoyi.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.ruoyi.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

@@ -1,92 +0,0 @@
package org.ruoyi.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.ruoyi.common.chat.entity.chat.ChatCompletionResponse;
import org.ruoyi.common.chat.entity.chat.Message;
import org.ruoyi.common.chat.entity.chat.tool.ToolCallFunction;
import org.ruoyi.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

@@ -1,417 +0,0 @@
package org.ruoyi.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.ruoyi.common.chat.entity.chat.*;
import org.ruoyi.common.chat.entity.chat.tool.ToolCallFunction;
import org.ruoyi.common.chat.entity.chat.tool.ToolCalls;
import org.ruoyi.common.chat.entity.chat.tool.Tools;
import org.ruoyi.common.chat.entity.chat.tool.ToolsFunction;
import org.ruoyi.common.chat.openai.OpenAiClient;
import org.ruoyi.common.chat.openai.OpenAiStreamClient;
import org.ruoyi.common.chat.openai.function.KeyRandomStrategy;
import org.ruoyi.common.chat.openai.interceptor.DynamicKeyOpenAiAuthInterceptor;
import org.ruoyi.common.chat.openai.interceptor.OpenAILogger;
import org.ruoyi.common.chat.openai.interceptor.OpenAiResponseInterceptor;
import org.ruoyi.common.chat.openai.plugin.PluginAbstract;
import org.ruoyi.common.chat.plugin.CmdPlugin;
import org.ruoyi.common.chat.plugin.CmdReq;
import org.ruoyi.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

@@ -1,24 +0,0 @@
package org.ruoyi.common.chat.demo;
import org.ruoyi.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

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

View File

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

View File

@@ -1,223 +0,0 @@
package org.ruoyi.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

@@ -1,65 +0,0 @@
package org.ruoyi.common.chat.domain.request;
import org.ruoyi.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

@@ -1,33 +0,0 @@
package org.ruoyi.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

@@ -8,7 +8,7 @@ import java.math.BigDecimal;
import java.util.List;
/**
* 描述:金额消耗信息
* 金额消耗信息
*
* @author https:www.unfbx.com
* @since 2023-04-08

View File

@@ -8,7 +8,7 @@ import java.io.Serializable;
import java.math.BigDecimal;
/**
* 描述:余额查询接口返回值
* 余额查询接口返回值
*
* @author https:www.unfbx.com
* @since 2023-03-18

View File

@@ -7,7 +7,7 @@ import lombok.Data;
import java.util.List;
/**
* 描述:金额消耗列表
* 金额消耗列表
*
* @author https:www.unfbx.com
* @since 2023-04-08

View File

@@ -7,7 +7,6 @@ import lombok.Data;
import java.math.BigDecimal;
/**
* 描述:
*
* @author https:www.unfbx.com
* @since 2023-03-18

View File

@@ -7,7 +7,6 @@ import lombok.Data;
import java.util.List;
/**
* 描述:
*
* @author https:www.unfbx.com
* @since 2023-03-18

View File

@@ -6,7 +6,7 @@ import lombok.Data;
import java.math.BigDecimal;
/**
* 描述:金额消耗列表
* 金额消耗列表
*
* @author https:www.unfbx.com
* @since 2023-04-08

View File

@@ -4,7 +4,6 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
/**
* 描述:
*
* @author https:www.unfbx.com
* @since 2023-04-08

View File

@@ -4,7 +4,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
* 描述:账户信息
* 账户信息
*
* @author https:www.unfbx.com
* @since 2023-04-08

View File

@@ -13,7 +13,7 @@ import java.util.Map;
import static org.ruoyi.common.chat.entity.chat.BaseChatCompletion.Model.GPT_3_5_TURBO;
/**
* 描述: chat模型基础类
* chat模型基础类
*
* @author https:www.unfbx.com
* @since 1.1.2

View File

@@ -12,7 +12,6 @@ import java.io.Serializable;
import java.util.List;
/**
* 描述:
*
* @author https:www.unfbx.com
* @since 1.1.2

View File

@@ -7,7 +7,6 @@ import lombok.Data;
import java.io.Serializable;
/**
* 描述:
*
* @author https:www.unfbx.com
* @since 2023-03-02

View File

@@ -12,7 +12,7 @@ import java.io.Serializable;
import java.util.List;
/**
* 描述: chat模型参数
* chat模型参数
*
* @author https:www.unfbx.com
* 2023-03-02

View File

@@ -8,7 +8,7 @@ import java.io.Serializable;
import java.util.List;
/**
* 描述: chat答案类
* chat答案类
*
* @author https:www.unfbx.com
* 2023-03-02

View File

@@ -11,7 +11,7 @@ import java.io.Serializable;
import java.util.List;
/**
* 描述 chat模型附带图片的参数
* chat模型附带图片的参数
*
* @author https:www.unfbx.com
* @since 1.1.2

View File

@@ -6,7 +6,6 @@ import lombok.*;
import lombok.extern.slf4j.Slf4j;
/**
* 描述:
*
* @author https://www.unfbx.com
* @since 1.1.2

View File

@@ -6,7 +6,7 @@ import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 描述:函数调用返回值
* 函数调用返回值
*
* @author https://www.unfbx.com
* @since 2023-06-14

View File

@@ -6,7 +6,7 @@ import lombok.Data;
import java.io.Serializable;
/**
* 描述:方法参数实体类,实例数据如下
* 方法参数实体类,实例数据如下
* <pre>
* {
* "name": "get_current_weather",

View File

@@ -8,7 +8,6 @@ import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* 描述:
*
* @author https://www.unfbx.com
* 2023-11-10

View File

@@ -2,6 +2,7 @@ package org.ruoyi.common.chat.entity.chat;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import org.ruoyi.common.chat.entity.chat.tool.ToolCalls;
@@ -9,7 +10,7 @@ import java.io.Serializable;
import java.util.List;
/**
* 描述:
*
*
* @author https:www.unfbx.com
* @since 2023-03-02
@@ -20,6 +21,8 @@ import java.util.List;
public class Message extends BaseMessage implements Serializable {
private Object content;
@JsonProperty("reasoning_content")
private String reasoningContent;
public static Builder builder() {
return new Builder();

View File

@@ -10,8 +10,6 @@ import java.io.Serializable;
import java.util.List;
/**
* 描述:
*
* @author https:www.unfbx.com
* @since 2023-03-02
*/

View File

@@ -6,7 +6,7 @@ import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* 描述:方法参数类扩展参数可以继承Parameters自己实现
* 方法参数类扩展参数可以继承Parameters自己实现
* 参考:
* <pre>
* {

View File

@@ -7,7 +7,6 @@ import lombok.Data;
import java.io.Serializable;
/**
* 描述:
*
* @author https:www.unfbx.com
* 2023-02-15

View File

@@ -6,7 +6,7 @@ import lombok.Data;
import java.io.Serializable;
/**
* 描述:
*
*
* @author https:www.unfbx.com
* 2023-02-15

View File

@@ -6,7 +6,7 @@ import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* 描述:
*
*
* @author https:www.unfbx.com
* 2023-02-15

View File

@@ -7,7 +7,7 @@ import lombok.Data;
import java.io.Serializable;
/**
* 描述:
*
*
* @author https:www.unfbx.com
* 2023-02-15

View File

@@ -10,7 +10,7 @@ import java.util.List;
import java.util.Map;
/**
* 描述: 问题类
* 问题类
*
* @author https:www.unfbx.com
* 2023-02-11

View File

@@ -9,7 +9,7 @@ import org.ruoyi.common.chat.entity.common.Usage;
import java.io.Serializable;
/**
* 描述: 答案类
* 答案类
*
* @author https:www.unfbx.com
* 2023-02-11

View File

@@ -7,7 +7,7 @@ import lombok.extern.slf4j.Slf4j;
import java.io.Serializable;
/**
* 描述:
*
*
* @author https:www.unfbx.com
* 2023-02-15

View File

@@ -9,7 +9,7 @@ import org.ruoyi.common.chat.entity.common.Usage;
import java.io.Serializable;
/**
* 描述:
*
*
* @author https:www.unfbx.com
* 2023-02-15

View File

@@ -9,7 +9,7 @@ import java.util.List;
import java.util.Objects;
/**
* 描述:
*
*
* @author https:www.unfbx.com
* 2023-02-15

View File

@@ -8,7 +8,7 @@ import java.io.Serializable;
import java.util.List;
/**
* 描述:
*
*
* @author https:www.unfbx.com
* 2023-02-15

View File

@@ -6,7 +6,7 @@ import lombok.Data;
import java.io.Serializable;
/**
* 描述:
*
*
* @author https:www.unfbx.com
* 2023-02-15

View File

@@ -6,7 +6,7 @@ import lombok.Data;
import java.io.Serializable;
/**
* 描述:
*
*
* @author https:www.unfbx.com
* 2023-02-15

View File

@@ -6,7 +6,7 @@ import lombok.Data;
import java.io.Serializable;
/**
* 描述:
*
*
* @author https:www.unfbx.com
* 2023-02-15

View File

@@ -11,7 +11,7 @@ import lombok.extern.slf4j.Slf4j;
import java.io.Serializable;
/**
* 描述:
*
*
* @author https:www.unfbx.com
* 2023-02-15

View File

@@ -11,7 +11,7 @@ import java.io.Serializable;
import java.util.Objects;
/**
* 描述:
*
*
* @author https:www.unfbx.com
* 2023-02-15

View File

@@ -7,7 +7,7 @@ import java.io.Serializable;
import java.util.List;
/**
* 描述:
*
*
* @author https:www.unfbx.com
* 2023-02-15

View File

@@ -12,7 +12,7 @@ import java.io.Serializable;
import java.util.Objects;
/**
* 描述:
*
*
* @author https:www.unfbx.com
* 2023-02-15

View File

@@ -7,7 +7,7 @@ import lombok.Data;
import java.io.Serializable;
/**
* 描述:
*
*
* @author https:www.unfbx.com
* 2023-02-15

View File

@@ -6,7 +6,7 @@ import lombok.Getter;
import java.io.Serializable;
/**
* 描述:
*
*
* @author https:www.unfbx.com
* 2023-02-15

View File

@@ -6,7 +6,7 @@ import lombok.Getter;
import java.io.Serializable;
/**
* 描述:
*
*
* @author https:www.unfbx.com
* 2023-02-15

View File

@@ -8,7 +8,7 @@ import java.io.Serializable;
import java.util.List;
/**
* 描述:
*
*
* @author https:www.unfbx.com
* 2023-02-15

View File

@@ -7,7 +7,7 @@ import java.io.Serializable;
import java.util.List;
/**
* 描述:
*
*
* @author https:www.unfbx.com
* 2023-02-15

View File

@@ -7,7 +7,7 @@ import lombok.Data;
import java.io.Serializable;
/**
* 描述:
*
*
* @author https:www.unfbx.com
* 2023-02-15

View File

@@ -7,7 +7,7 @@ import lombok.Data;
import java.io.Serializable;
/**
* 描述:
*
*
* @author https:www.unfbx.com
* 2023-02-15

View File

@@ -8,7 +8,7 @@ import java.io.Serializable;
import java.math.BigDecimal;
/**
* 描述:
*
*
* @author https:www.unfbx.com
* 2023-02-15

View File

@@ -10,7 +10,7 @@ import java.util.List;
import java.util.Objects;
/**
* 描述:文本审核,敏感词鉴别
* 文本审核,敏感词鉴别
*
* @author https:www.unfbx.com
* 2023-02-15

View File

@@ -7,7 +7,7 @@ import java.io.Serializable;
import java.util.List;
/**
* 描述:
*
*
* @author https:www.unfbx.com
* 2023-02-15

View File

@@ -7,7 +7,7 @@ import lombok.Data;
import java.io.Serializable;
/**
* 描述:
*
*
* @author https:www.unfbx.com
* 2023-02-15

View File

@@ -7,7 +7,7 @@ import lombok.Getter;
import java.io.Serializable;
/**
* 描述:语音转文字
* 语音转文字
*
* @author https:www.unfbx.com
* @since 2023-03-02

View File

@@ -6,7 +6,7 @@ import lombok.Data;
import java.io.Serializable;
/**
* 描述:
*
*
* @author https:www.unfbx.com
* @since 2023-03-02

View File

@@ -2,7 +2,6 @@ package org.ruoyi.common.chat.handler;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.common.chat.config.LocalCache;
import org.ruoyi.common.chat.entity.chat.ChatCompletion;
@@ -12,7 +11,6 @@ import org.ruoyi.common.chat.listener.WebSocketEventListener;
import org.ruoyi.common.chat.openai.OpenAiStreamClient;
import org.ruoyi.common.chat.utils.WebSocketUtils;
import org.ruoyi.common.core.utils.SpringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.socket.*;
import org.springframework.web.socket.handler.AbstractWebSocketHandler;

View File

@@ -7,16 +7,14 @@ import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.sse.EventSource;
import okhttp3.sse.EventSourceListener;
import org.ruoyi.common.chat.constant.OpenAIConst;
import org.ruoyi.common.chat.entity.chat.ChatCompletionResponse;
import org.ruoyi.common.chat.entity.chat.Message;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import java.util.Objects;
/**
* 描述:OpenAI流式输出Socket接收
* OpenAI流式输出Socket接收
*
* @author https:www.unfbx.com
* @date 2023-03-23
@@ -63,7 +61,7 @@ public class WebSocketEventListener extends EventSourceListener {
delta = mapper.writeValueAsString(completionResponse.getChoices().get(0).getDelta());
}catch (Exception e){
log.error("转换失败{}",e.getMessage());
}
}
session.sendMessage(new TextMessage(delta));
}

View File

@@ -1,198 +0,0 @@
package org.ruoyi.common.chat.localModels;
import io.micrometer.common.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import org.ruoyi.common.chat.entity.models.LocalModelsSearchRequest;
import org.ruoyi.common.chat.entity.models.LocalModelsSearchResponse;
import org.springframework.stereotype.Service;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.jackson.JacksonConverterFactory;
import java.util.List;
import java.util.concurrent.CountDownLatch;
@Slf4j
@Service
public class LocalModelsofitClient {
private static final String BASE_URL = "http://127.0.0.1:5000"; // Flask 服务的 URL
private static Retrofit retrofit = null;
// 获取 Retrofit 实例
public static Retrofit getRetrofitInstance() {
if (retrofit == null) {
OkHttpClient client = new OkHttpClient.Builder()
.build();
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(JacksonConverterFactory.create()) // 使用 Jackson 处理 JSON 转换
.build();
}
return retrofit;
}
/**
* 向 Flask 服务发送文本向量化请求
*
* @param queries 查询文本列表
* @param modelName 模型名称
* @param delimiter 文本分隔符
* @param topK 返回的结果数
* @param blockSize 文本块大小
* @param overlapChars 重叠字符数
* @return 返回计算得到的 Top K 嵌入向量列表
*/
public static List<List<Double>> getTopKEmbeddings(
List<String> queries,
String modelName,
String delimiter,
int topK,
int blockSize,
int overlapChars) {
modelName = (!StringUtils.isEmpty(modelName)) ? modelName : "msmarco-distilbert-base-tas-b"; // 默认模型名称
delimiter = (!StringUtils.isEmpty(delimiter) ) ? delimiter : "."; // 默认分隔符
topK = (topK > 0) ? topK : 3; // 默认返回 3 个结果
blockSize = (blockSize > 0) ? blockSize : 500; // 默认文本块大小为 500
overlapChars = (overlapChars > 0) ? overlapChars : 50; // 默认重叠字符数为 50
// 创建 Retrofit 实例
Retrofit retrofit = getRetrofitInstance();
// 创建 SearchService 接口
SearchService service = retrofit.create(SearchService.class);
// 创建请求对象 LocalModelsSearchRequest
LocalModelsSearchRequest request = new LocalModelsSearchRequest(
queries, // 查询文本列表
modelName, // 模型名称
delimiter, // 文本分隔符
topK, // 返回的结果数
blockSize, // 文本块大小
overlapChars // 重叠字符数
);
final CountDownLatch latch = new CountDownLatch(1); // 创建一个 CountDownLatch
final List<List<Double>>[] topKEmbeddings = new List[]{null}; // 使用数组来存储结果(因为 Java 不支持直接修改 List
// 发起异步请求
service.vectorize(request).enqueue(new Callback<LocalModelsSearchResponse>() {
@Override
public void onResponse(Call<LocalModelsSearchResponse> call, Response<LocalModelsSearchResponse> response) {
if (response.isSuccessful()) {
LocalModelsSearchResponse searchResponse = response.body();
if (searchResponse != null) {
topKEmbeddings[0] = searchResponse.getTopKEmbeddings().get(0); // 获取结果
log.info("Successfully retrieved embeddings");
} else {
log.error("Response body is null");
}
} else {
log.error("Request failed. HTTP error code: " + response.code());
}
latch.countDown(); // 请求完成,减少计数
}
@Override
public void onFailure(Call<LocalModelsSearchResponse> call, Throwable t) {
t.printStackTrace();
log.error("Request failed: ", t);
latch.countDown(); // 请求失败,减少计数
}
});
try {
latch.await(); // 等待请求完成
} catch (InterruptedException e) {
e.printStackTrace();
}
return topKEmbeddings[0]; // 返回结果
}
// public static void main(String[] args) {
// // 示例调用
// List<String> queries = Arrays.asList("What is artificial intelligence?", "AI is transforming industries.");
// String modelName = "msmarco-distilbert-base-tas-b";
// String delimiter = ".";
// int topK = 3;
// int blockSize = 500;
// int overlapChars = 50;
//
// List<List<Double>> topKEmbeddings = getTopKEmbeddings(queries, modelName, delimiter, topK, blockSize, overlapChars);
//
// // 打印结果
// if (topKEmbeddings != null) {
// System.out.println("Top K embeddings: ");
// for (List<Double> embedding : topKEmbeddings) {
// System.out.println(embedding);
// }
// } else {
// System.out.println("No embeddings returned.");
// }
// }
// public static void main(String[] args) {
// // 创建 Retrofit 实例
// Retrofit retrofit = LocalModelsofitClient.getRetrofitInstance();
//
// // 创建 SearchService 接口
// SearchService service = retrofit.create(SearchService.class);
//
// // 创建请求对象 LocalModelsSearchRequest
// LocalModelsSearchRequest request = new LocalModelsSearchRequest(
// Arrays.asList("What is artificial intelligence?", "AI is transforming industries."), // 查询文本列表
// "msmarco-distilbert-base-tas-b", // 模型名称
// ".", // 分隔符
// 3, // 返回的结果数
// 500, // 文本块大小
// 50 // 重叠字符数
// );
//
// // 发起请求
// service.vectorize(request).enqueue(new Callback<LocalModelsSearchResponse>() {
// @Override
// public void onResponse(Call<LocalModelsSearchResponse> call, Response<LocalModelsSearchResponse> response) {
// if (response.isSuccessful()) {
// LocalModelsSearchResponse searchResponse = response.body();
// System.out.println("Response Body: " + response.body()); // Print the whole response body for debugging
//
// if (searchResponse != null) {
// // If the response is not null, process it.
// // Example: Extract the embeddings and print them
// List<List<List<Double>>> topKEmbeddings = searchResponse.getTopKEmbeddings();
// if (topKEmbeddings != null) {
// // Print the Top K embeddings
//
// } else {
// System.err.println("Top K embeddings are null");
// }
//
// // If there is more information you want to process, handle it here
//
// } else {
// System.err.println("Response body is null");
// }
// } else {
// System.err.println("Request failed. HTTP error code: " + response.code());
// log.error("Failed to retrieve data. HTTP error code: " + response.code());
// }
// }
//
// @Override
// public void onFailure(Call<LocalModelsSearchResponse> call, Throwable t) {
// // 请求失败,打印错误
// t.printStackTrace();
// log.error("Request failed: ", t);
// }
// });
// }
}

View File

@@ -1,25 +0,0 @@
package org.ruoyi.common.chat.localModels;
import org.ruoyi.common.chat.entity.models.LocalModelsSearchRequest;
import org.ruoyi.common.chat.entity.models.LocalModelsSearchResponse;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.POST;
/**
* @program: RUOYIAI
* @ClassName SearchService
* @description: 请求模型
* @author: hejh
* @create: 2025-03-15 17:27
* @Version 1.0
**/
public interface SearchService {
@POST("/vectorize") // 与 Flask 服务中的路由匹配
Call<LocalModelsSearchResponse> vectorize(@Body LocalModelsSearchRequest request);
}

View File

@@ -40,7 +40,7 @@ import java.time.LocalDate;
import java.util.Map;
/**
* 描述: open ai官方api接口
* open ai官方api接口
*
* @author https:www.unfbx.com
* 2023-02-15

View File

@@ -10,6 +10,7 @@ import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.RequestBody;
import org.jetbrains.annotations.NotNull;
import org.ruoyi.common.chat.constant.OpenAIConst;
import org.ruoyi.common.chat.entity.billing.BillingUsage;
import org.ruoyi.common.chat.entity.billing.Subscription;
@@ -45,7 +46,6 @@ import org.ruoyi.common.chat.openai.interceptor.OpenAiAuthInterceptor;
import org.ruoyi.common.chat.openai.plugin.PluginAbstract;
import org.ruoyi.common.chat.openai.plugin.PluginParam;
import org.ruoyi.common.core.exception.base.BaseException;
import org.jetbrains.annotations.NotNull;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.jackson.JacksonConverterFactory;
@@ -56,7 +56,7 @@ import java.util.concurrent.TimeUnit;
/**
* 描述: open ai 客户端
* open ai 客户端
*
* @author https:www.unfbx.com
* @since 2023-02-11

View File

@@ -13,6 +13,7 @@ import okhttp3.*;
import okhttp3.sse.EventSource;
import okhttp3.sse.EventSourceListener;
import okhttp3.sse.EventSources;
import org.jetbrains.annotations.NotNull;
import org.ruoyi.common.chat.constant.OpenAIConst;
import org.ruoyi.common.chat.entity.Tts.TextToSpeech;
import org.ruoyi.common.chat.entity.billing.BillingUsage;
@@ -27,7 +28,6 @@ import org.ruoyi.common.chat.entity.images.ImageResponse;
import org.ruoyi.common.chat.entity.models.Model;
import org.ruoyi.common.chat.entity.models.ModelResponse;
import org.ruoyi.common.chat.entity.whisper.Transcriptions;
import org.ruoyi.common.chat.entity.whisper.Translations;
import org.ruoyi.common.chat.entity.whisper.WhisperResponse;
import org.ruoyi.common.chat.openai.exception.CommonError;
import org.ruoyi.common.chat.openai.function.KeyRandomStrategy;
@@ -40,7 +40,6 @@ import org.ruoyi.common.chat.openai.plugin.PluginParam;
import org.ruoyi.common.chat.sse.DefaultPluginListener;
import org.ruoyi.common.chat.sse.PluginListener;
import org.ruoyi.common.core.exception.base.BaseException;
import org.jetbrains.annotations.NotNull;
import retrofit2.Call;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
@@ -53,7 +52,7 @@ import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* 描述: open ai 客户端
* open ai 客户端
*
* @author https:www.unfbx.com
* 2023-02-28
@@ -71,6 +70,11 @@ public class OpenAiStreamClient {
*/
private String apiHost;
/**
* 自定义url 兼容多个平台
*/
private String apiUrl;
/**
* 自定义的okHttpClient
* 如果不自定义 就是用sdk默认的OkHttpClient实例
@@ -112,6 +116,11 @@ public class OpenAiStreamClient {
}
apiHost = builder.apiHost;
if (StrUtil.isBlank(builder.apiUrl)) {
builder.apiUrl = OpenAIConst.apiUrl;
}
apiUrl = builder.apiUrl;
if (Objects.isNull(builder.keyStrategy)) {
builder.keyStrategy = new KeyRandomStrategy();
}
@@ -135,13 +144,15 @@ public class OpenAiStreamClient {
.build();
}
okHttpClient = builder.okHttpClient;
if (apiHost.endsWith("/")) {
this.openAiApi = new Retrofit.Builder()
.baseUrl(apiHost)
.client(okHttpClient)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(JacksonConverterFactory.create())
.build().create(OpenAiApi.class);
}
this.openAiApi = new Retrofit.Builder()
.baseUrl(apiHost)
.client(okHttpClient)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(JacksonConverterFactory.create())
.build().create(OpenAiApi.class);
}
/**
@@ -180,7 +191,7 @@ public class OpenAiStreamClient {
ObjectMapper mapper = new ObjectMapper();
String requestBody = mapper.writeValueAsString(chatCompletion);
Request request = new Request.Builder()
.url(this.apiHost + "v1/chat/completions")
.url(this.apiHost)
.post(RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), requestBody))
.build();
factory.newEventSource(request, eventSourceListener);
@@ -334,7 +345,6 @@ public class OpenAiStreamClient {
BillingUsage billingUsage = billingUsage(start.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(), end.toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
double totalUsage = billingUsage.getTotalUsage().doubleValue() / 100;
System.out.println(totalUsage);
Subscription subscription = subscription();
KeyInfo keyInfo = new KeyInfo();
String start_key = key.substring(0, 6);
@@ -466,8 +476,8 @@ public class OpenAiStreamClient {
* @since 1.1.3
*/
public ResponseBody textToSpeech(TextToSpeech textToSpeech){
Call<ResponseBody> responseBody = this.openAiApi.textToSpeech(textToSpeech);
try {
Call<ResponseBody> responseBody = this.openAiApi.textToSpeech(textToSpeech);
return responseBody.execute().body();
} catch (IOException e) {
throw new BaseException("文本转语音(同步)失败: "+e.getMessage());
@@ -593,11 +603,6 @@ public class OpenAiStreamClient {
}
/**
* 构造
*
@@ -616,6 +621,8 @@ public class OpenAiStreamClient {
*/
private String apiHost;
private String apiUrl;
/**
* 自定义OkhttpClient
*/
@@ -650,6 +657,16 @@ public class OpenAiStreamClient {
return this;
}
/**
* @param val 自定义请求后缀
* @return Builder
* @see OpenAIConst
*/
public Builder apiUrl(String val) {
apiUrl = val;
return this;
}
public Builder keyStrategy(KeyStrategyFunction val) {
keyStrategy = val;
return this;

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