113 Commits

Author SHA1 Message Date
Looly
10d660fb06 fix location bug 2025-08-18 19:17:33 +08:00
Looly
831fed4bb3 add test 2025-08-18 18:58:33 +08:00
Looly
4d0074e6a6 fix code 2025-08-18 10:26:27 +08:00
Looly
707daa94f7 增加Gbase8s驱动支持 2025-08-18 10:23:52 +08:00
Looly
60b80340fc add comment 2025-08-14 16:17:23 +08:00
Looly
0255f8a365 add test 2025-08-14 16:02:08 +08:00
Looly
f20536ea12 getClientIP优先获取传入的请求头信息(pr#1373@Gitee) 2025-08-11 11:19:36 +08:00
Looly
cc0b4b10c7 add test 2025-08-11 10:58:28 +08:00
Golden Looly
0d0c7622b6 Merge pull request #4003 from RealHeart/patch-1
fix: README V7 Maven仓库链接错误
2025-08-01 09:07:46 +08:00
真心
e89cb0bdea fix: README V7 Maven仓库链接错误 2025-07-31 20:36:17 +08:00
Looly
bf3376f464 MapUtil增加flatten方法(pr#1368@Gitee) 2025-07-30 19:14:30 +08:00
Looly
a621066525 add test 2025-07-30 19:00:23 +08:00
Looly
8bb82a9d92 add activeMQ support 2025-07-23 18:38:34 +08:00
Looly
27f77f83a7 fix code 2025-07-23 15:05:10 +08:00
Looly
04bb0183e5 fix code 2025-07-23 15:00:50 +08:00
Looly
98a03945f7 fix code 2025-07-23 14:59:06 +08:00
Looly
be20536ba1 prepare M2 2025-07-22 12:36:57 +08:00
Looly
a4a02bdbd6 Merge branch 'v7-dev' of gitee.com:chinabugotech/hutool into v7-dev 2025-07-22 12:36:20 +08:00
Looly
7391526b74 preapre M2 2025-07-22 12:36:08 +08:00
Looly
0978d2ce1b prepare M2 2025-07-22 12:30:26 +08:00
chinabugotech
55794a1725 🐢prepare7.0.0-M2 2025-07-22 11:08:52 +08:00
chinabugotech
8f94b5ba2e 🐢prepare7.0.0-M2 2025-07-22 10:57:13 +08:00
Looly
d2ed2124b6 fix code 2025-07-21 11:24:00 +08:00
Looly
2d8fe7b468 fix code 2025-07-14 16:46:12 +08:00
Looly
b532ca01c5 添加Ollama客户端支持 2025-07-14 10:51:20 +08:00
Looly
8ad16be6cb 修复ChineseDate 闰年闰月节日获取问题 2025-07-10 11:54:13 +08:00
Looly
e6e32a76b9 SftpreconnectIfTimeout方法改为捕获所有异常(issue#3989@Github) 2025-07-08 15:57:29 +08:00
Looly
db7c7f2e34 fix code 2025-07-02 10:22:23 +08:00
Looly
90e8d3bdac 四则运算方式支持不生成负数结果 2025-07-02 09:36:15 +08:00
Looly
91b8a50b68 fix doc 2025-06-27 16:56:23 +08:00
Looly
50dc76ee76 fix dependency 2025-06-27 16:47:39 +08:00
Looly
fdc7d4fb3c fix java doc 2025-06-27 11:58:05 +08:00
Looly
c17c5bb013 fix comment 2025-06-27 11:45:08 +08:00
Looly
9cc095463e fix comment 2025-06-27 11:16:37 +08:00
Looly
c424d76f88 fix test 2025-06-26 17:21:52 +08:00
Looly
2b7adbef08 fix commenty 2025-06-25 18:08:08 +08:00
Looly
6fda8f0244 fix comment 2025-06-25 17:44:06 +08:00
Looly
bb607e30ae fix comment 2025-06-25 17:03:22 +08:00
Looly
9d83869d85 fix code 2025-06-25 11:23:36 +08:00
Looly
a7c5259d4b fix test 2025-06-24 15:24:43 +08:00
Looly
5034b04a14 fix code 2025-06-23 20:17:42 +08:00
Looly
6d94eb6ae6 add config 2025-06-23 20:13:47 +08:00
Looly
a7663b93f7 fix code 2025-06-23 19:58:03 +08:00
Looly
52afcdd388 remove SecurityManagerCaller 2025-06-23 19:31:42 +08:00
Looly
e7546e6c34 fix test 2025-06-23 19:29:25 +08:00
Looly
ac5abab4ea fix test 2025-06-23 19:26:51 +08:00
Looly
e0275c56ac fix test 2025-06-23 19:22:19 +08:00
Looly
9d9d326aa8 fix comment 2025-06-23 19:15:22 +08:00
Looly
73635fd1b2 remove module-info 2025-06-23 18:46:57 +08:00
Looly
caf0ca658e update spring to 3.x 2025-06-23 17:24:25 +08:00
Looly
fbf1b6cd1f update central-publishing-maven-plugin 2025-06-20 18:40:20 +08:00
Looly
0655b26b73 RetryableTask: 避免最后一次任务执行时的线程睡眠 2025-06-20 18:00:15 +08:00
Looly
3e56130847 增加可召回批处理线程池执行器RecyclableBatchThreadPoolExecutor(pr#1343@Gitee) 2025-06-20 17:54:47 +08:00
Looly
68bcc8f5de 修复AbstractCacheputWithoutLock方法可能导致的外部资源泄露问题(pr#3958@Github) 2025-06-20 17:34:37 +08:00
Looly
698443860f add setFetchSize and FetchDirection 2025-06-20 11:40:49 +08:00
Looly
d296b94a9b add setFetchSize and FetchDirection 2025-06-20 11:37:22 +08:00
Looly
39f51ee899 修复Money类的setAmount方法没有获取当前币种的小数位数而是使用的默认小数位和在遇到非2小数位的币种(如日元使用 0 位)会导致金额设置错误问题(pr#3970@Github) 2025-06-20 11:05:57 +08:00
Looly
aaa6932ae1 修复ZipUtil中zlib和unZlib调用后资源未释放问题(issue#3976@Github) 2025-06-20 10:43:20 +08:00
Looly
5a3acc6b80 fix comment 2025-06-14 19:04:05 +08:00
Looly
580c34bbfa 修复TEL_400_800正则规则太窄问题(issue#3967@Github) 2025-06-14 18:40:35 +08:00
Looly
28d1a8cdff 文本及富文本支持自定义规则脱敏 2025-06-09 17:21:42 +08:00
Looly
8b7d621cd7 修复ThreadUtil中中断异常处理丢失中断信息的问题,解决ConcurrencyTester资源未释放的问题(pr#1358@Gitee) 2025-06-09 16:57:14 +08:00
Looly
7c2746c76a fix connection pool bug 2025-06-09 16:41:45 +08:00
Looly
b68e180ad5 fix comment 2025-06-09 12:24:31 +08:00
Looly
4942367ab4 修复LunarFestival中重复节日问题(issue#ICC8X3@Gitee) 2025-06-09 11:32:30 +08:00
chinabugotech
9f4a7b7d9e update docs/apidocs/index.html.
更新百度统计

Signed-off-by: chinabugotech <bugo@bugotech.cn>
2025-06-05 07:15:46 +00:00
eli_chow
d960f5b353 !1357 hutool-ai新增SSE流式响应,增加Hutool-AI平台服务,增加超时时间配置
Merge pull request !1357 from eli_chow/v7-dev
2025-06-04 06:48:09 +00:00
choweli
faf9897097 hutool-ai新增SSE流式响应,增加Hutool-AI平台服务,增加超时时间配置 2025-06-04 14:47:15 +08:00
Looly
3d3cadd1bc 优化XXXToMapCopier的部分性能(pr#1345@Gitee) 2025-05-23 21:54:13 +08:00
Looly
596cdc2984 优化XXXToMapCopier的部分性能(pr#1345@Gitee) 2025-05-23 21:47:29 +08:00
Looly
a51da7369d 修复Money中金额分配的问题bug(issue#IC9Y35@Gitee) 2025-05-23 21:41:30 +08:00
Looly
5f61312587 fix comment 2025-05-23 16:26:17 +08:00
Looly
0073597ae6 fix comment 2025-05-23 15:51:22 +08:00
Looly
a0ca35101e 修复UUIDequals的问题,改为final类 2025-05-23 15:48:05 +08:00
Looly
c7ba1a5a3c fix name 2025-05-15 16:46:52 +08:00
Looly
11fc243705 解决pwd、cd调用command导致仅SftpSubsystem服务时无法正常使用的问题 2025-05-12 12:08:30 +08:00
Looly
255ff52071 修复某些数据库的getParameterMetaData会返回NULL,导致空指针的问题。(pr#3936@Github) 2025-05-12 09:17:22 +08:00
Looly
50d9182b97 修复HttpClient5Response无响应体导致的空指针问题(issue#3930@Github) 2025-05-07 10:41:58 +08:00
Looly
1a102f9359 分流收集器收集方式从固定Collection改为自定义收集器,可以自定义如何收集元素 2025-04-26 13:38:51 +08:00
Looly
51b9bfd8bb fix resource 2025-04-26 13:15:37 +08:00
Looly
f8bf24aa0c fix resource 2025-04-26 13:08:44 +08:00
Looly
05dfc221a5 add rename 2025-04-26 11:45:33 +08:00
Looly
8b3e6c99c4 TemplateConfig增加setUseCache方法(issue#IC3JRY@Gitee) 2025-04-26 11:37:06 +08:00
Looly
80eccf98bb Merge branch 'v7-dev' of gitcode.com:chinabugotech/hutool into v7-dev 2025-04-24 08:59:06 +08:00
Looly
6d493b2fc9 Merge branch 'v7-dev' of github.com:chinabugotech/hutool into v7-dev 2025-04-24 08:59:01 +08:00
choweli
eb68f61a18 fix test 2025-04-22 10:33:05 +08:00
choweli
9cbeea018d fix test 2025-04-22 10:32:02 +08:00
choweli
74738b67ca fix test 2025-04-22 10:18:28 +08:00
Looly
c8199504fc fix test 2025-04-21 19:20:20 +08:00
Looly
861b961b6f fix test 2025-04-21 18:22:48 +08:00
choweli
49093afcd4 添加 modul-info.java 剩余extra模块 2025-04-21 15:38:18 +08:00
choweli
2a90c4b7b5 添加 modul-info.java 暂未完成 2025-04-18 17:22:55 +08:00
Looly
bedfa07a8a ServiceLoader.load换成SpiUtil.loadList 2025-04-18 12:23:23 +08:00
Looly
07739d72db 去除JDK8兼容代码 2025-04-18 12:12:34 +08:00
Looly
45419bb8dc fix version 2025-04-17 18:13:55 +08:00
Looly
570f846d4d fix version as slash and update version 2025-04-17 17:22:58 +08:00
Looly
1b54272ea8 update tomcat to 11 and remove javax.servlet 2025-04-17 17:15:05 +08:00
Looly
869b82b8d0 update Jetty to 12 and add JettyRequest and JettyResponse 2025-04-17 17:09:44 +08:00
Looly
33aa3ecf3a fix Setting autoload 2025-04-17 16:16:27 +08:00
Looly
d58d5ea032 fix code 2025-04-17 16:05:06 +08:00
choweli
038a6ca0c8 7.0.0.M1 2025-04-17 10:55:02 +08:00
choweli
22d487624d 7.0.0.M1 2025-04-15 17:02:41 +08:00
Looly
b0e37e3ef3 增加分段锁实现SegmentLock(pr#1330@Gitee) 2025-04-15 11:27:53 +08:00
Looly
aa13f98934 fix test 2025-04-11 20:26:18 +08:00
Looly
1cfd6c7a5c fix test 2025-04-11 20:18:08 +08:00
Looly
c7c4457deb CharSequenceUtil增加toLoweCase和toUpperCase方法(issue#IC0H2B@Gitee) 2025-04-11 19:15:42 +08:00
Looly
a0eab6fb11 CharSequenceUtil增加toLoweCase和toUpperCase方法(issue#IC0H2B@Gitee) 2025-04-11 19:13:29 +08:00
Looly
4960c77a34 add plugin 2025-04-11 19:05:48 +08:00
Looly
1011c5ec60 增加Argon2类,实现Argon2算法(issue#3890@Github) 2025-04-11 10:45:14 +08:00
Looly
79485aca23 add hana 2025-04-11 09:55:00 +08:00
Looly
deee1f8d09 add hana 2025-04-11 09:51:47 +08:00
Looly
58d44d6086 add hana 2025-04-11 09:50:20 +08:00
Looly
581a5dd0fd fix comment 2025-04-08 16:22:12 +08:00
2910 changed files with 19268 additions and 13401 deletions

2
.github/FUNDING.yml vendored
View File

@@ -1,4 +1,4 @@
# These are supported funding model platforms
github: [Looly]
custom: ['https://gitee.com/dromara/hutool', 'https://dromara.gitee.io/donate.html']
custom: ['https://gitee.com/chinabugotech/hutool']

View File

@@ -3,14 +3,11 @@
-------------------------------------------------------------------------------------------------------------
# 6.0.0-M17 (2024-10-09)
### 计划实现
* 【db 】 增加DDL封装
* 【db 】 Entity数据量大时占用较多内存考虑共享meta信息
# 7.0.0-M1 (2025-05-07)
### ❌不兼容特性
### 🐣新特性
### 🐞Bug修复
### 🐞Bug修复
* 【http】 修复`HttpClient5Response`无响应体导致的空指针问题issue#3930@Github

38
README-EN.md Executable file → Normal file
View File

@@ -33,11 +33,11 @@
<a target="_blank" href='https://gitee.com/dromara/hutool/stargazers'>
<img src='https://gitee.com/dromara/hutool/badge/star.svg?theme=gvp' alt='star'/>
</a>
<a target="_blank" href='https://github.com/dromara/hutool'>
<img src="https://img.shields.io/github/stars/dromara/hutool.svg?style=social" alt="github star"/>
<a target="_blank" href='https://github.com/chinabugotech/hutool'>
<img src="https://img.shields.io/github/stars/chinabugotech/hutool.svg?style=social" alt="github star"/>
</a>
<a target="_blank" href='https://gitcode.com/dromara/hutool'>
<img src="https://gitcode.com/dromara/hutool/star/badge.svg" alt="gitcode star"/>
<a target="_blank" href='https://gitcode.com/chinabugotech/hutool'>
<img src="https://gitcode.com/chinabugotech/hutool/star/badge.svg" alt="gitcode star"/>
</a>
</p>
@@ -126,32 +126,32 @@ Each module can be introduced individually, or all modules can be introduced by
### 🍊Maven
```xml
<dependency>
<groupId>org.dromara.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>6.0.0-M22</version>
<groupId>cn.hutool.v7</groupId>
<artifactId>hutool-all</artifactId>
<version>7.0.0-M2</version>
</dependency>
```
### 🍐Gradle
```
implementation 'org.dromara.hutool:hutool-all:6.0.0-M22'
implementation 'cn.hutool.v7:hutool-all:7.0.0-M2'
```
## 📥Download
- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/6.0.0-M22/)
- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/v7/hutool-all/7.0.0-M2/)
> 🔔note:
> Hutool 5.x supports JDK8+ and is not tested on Android platforms, and cannot guarantee that all tool classes or tool methods are available.
> Hutool 7.x supports JDK17 and is not tested on Android platforms, and cannot guarantee that all tool classes or tool methods are available.
> If your project uses JDK7, please use Hutool 4.x version.
### 🚽Compile and install
Download the entire project source code
gitee[https://gitee.com/dromara/hutool](https://gitee.com/dromara/hutool)
gitee[https://gitee.com/chinabugotech/hutool](https://gitee.com/chinabugotech/hutool)
github:[https://github.com/dromara/hutool](https://github.com/dromara/hutool)
github:[https://github.com/chinabugotech/hutool](https://github.com/chinabugotech/hutool)
```sh
cd ${hutool}
@@ -168,16 +168,16 @@ Hutool's source code is divided into two branches:
| branch | description |
|-----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| v6-master | The master branch, the branch used by the release version, is the same as the jar committed to the central repository and does not receive any pr or modifications. |
| v6-dev | Development branch, which defaults to the next SNAPSHOT version, accepts modifications or pr |
| v7-master | The master branch, the branch used by the release version, is the same as the jar committed to the central repository and does not receive any pr or modifications. |
| v7-dev | Development branch, which defaults to the next SNAPSHOT version, accepts modifications or pr |
### 🐞Provide feedback or suggestions on bugs
When submitting feedback, please indicate which JDK version, Hutool version, and related dependency library version you are using.
- [Gitee issue](https://gitee.com/dromara/hutool/issues)
- [Github issue](https://github.com/dromara/hutool/issues)
- [Gitcode issue](https://gitcode.com/dromara/hutool/issues)
- [Gitee issue](https://gitee.com/chinabugotech/hutool/issues)
- [Github issue](https://github.com/chinabugotech/hutool/issues)
- [Gitcode issue](https://gitcode.com/chinabugotech/hutool/issues)
### 🧬Principles of PR(pull request)
@@ -186,13 +186,13 @@ Hutool welcomes anyone to contribute code to Hutool, but the author suffers from
1. Improve the comments, especially each new method should follow the Java documentation specification to indicate the method description, parameter description, return value description and other information, if necessary, please add unit tests, if you want, you can also add your name.
2. Code indentation according to Eclipse.
3. Newly added methods do not use third-party library methodsUnless the method tool is add to the '**extra module**'.
4. Please pull request to the `v6-dev` branch. Hutool uses a new branch after 5.x: `v6-master` is the master branch, which indicates the version of the central library that has been released, and this branch does not allow pr or modifications.
4. Please pull request to the `v7-dev` branch. Hutool uses a new branch after 7.x: `v7-master` is the master branch, which indicates the version of the central library that has been released, and this branch does not allow pr or modifications.
-------------------------------------------------------------------------------
## ⭐Star Hutool
[![Stargazers over time](https://starchart.cc/dromara/hutool.svg)](https://starchart.cc/dromara/hutool)
[![Stargazers over time](https://starchart.cc/chinabugotech/hutool.svg)](https://starchart.cc/chinabugotech/hutool)
## 📌WeChat Official Account

61
README.md Executable file → Normal file
View File

@@ -9,8 +9,8 @@
</p>
<p align="center">
<a target="_blank" href="https://search.maven.org/artifact/org.dromara.hutool/hutool-all">
<img alt="" src="https://img.shields.io/maven-central/v/org.dromara.hutool/hutool-all.svg?label=Maven%20Central" />
<a target="_blank" href="https://search.maven.org/artifact/cn.hutool/hutool-all">
<img alt="" src="https://img.shields.io/maven-central/v/cn.hutool/hutool-all.svg?label=Maven%20Central" />
</a>
<a target="_blank" href="https://www.apache.org/licenses/LICENSE-2.0.html">
<img alt="" src="https://img.shields.io/:license-apache2.0-blue.svg?logo=apache" />
@@ -18,26 +18,26 @@
<a target="_blank" href="https://www.oracle.com/java/technologies/javase/javase-jdk8-downloads.html">
<img alt="" src="https://img.shields.io/badge/JDK-8+-green.svg" />
</a>
<a target="_blank" href="https://travis-ci.com/dromara/hutool">
<img alt="" src="https://travis-ci.com/dromara/hutool.svg?branch=v5-master" />
<a target="_blank" href="https://travis-ci.com/chinabugotech/hutool">
<img alt="" src="https://travis-ci.com/chinabugotech/hutool.svg?branch=v5-master" />
</a>
<a href="https://www.codacy.com/gh/dromara/hutool/dashboard?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=dromara/hutool&amp;utm_campaign=Badge_Grade">
<a href="https://www.codacy.com/gh/chinabugotech/hutool/dashboard?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=chinabugotech/hutool&amp;utm_campaign=Badge_Grade">
<img alt="" src="https://app.codacy.com/project/badge/Grade/8a6897d9de7440dd9de8804c28d2871d"/>
</a>
<a href="https://codecov.io/gh/dromara/hutool">
<img alt="" src="https://codecov.io/gh/dromara/hutool/branch/v6-master/graph/badge.svg" />
<a href="https://codecov.io/gh/cn/hutool">
<img alt="" src="https://codecov.io/gh/chinabugotech/hutool/branch/v7-master/graph/badge.svg" />
</a>
<a target="_blank" href="https://gitter.im/hutool/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge">
<img alt="" src="https://badges.gitter.im/hutool/Lobby.svg" />
</a>
<a target="_blank" href='https://gitee.com/dromara/hutool/stargazers'>
<img alt="star" src='https://gitee.com/dromara/hutool/badge/star.svg?theme=gvp'/>
<a target="_blank" href='https://gitee.com/chinabugotech/hutool/stargazers'>
<img alt="star" src='https://gitee.com/chinabugotech/hutool/badge/star.svg'/>
</a>
<a target="_blank" href='https://github.com/dromara/hutool'>
<img alt="github star" src="https://img.shields.io/github/stars/dromara/hutool.svg?style=social"/>
<a target="_blank" href='https://github.com/chinabugotech/hutool'>
<img alt="github star" src="https://img.shields.io/github/stars/chinabugotech/hutool.svg?style=social"/>
</a>
<a target="_blank" href='https://gitcode.com/dromara/hutool'>
<img src="https://gitcode.com/dromara/hutool/star/badge.svg" alt="gitcode star"/>
<a target="_blank" href='https://gitcode.com/chinabugotech/hutool'>
<img src="https://gitcode.com/chinabugotech/hutool/star/badge.svg" alt="gitcode star"/>
</a>
</p>
@@ -77,13 +77,14 @@
### ✨版本选择
Hutool目前主要版本4.x、5.x、6.x选择如下
Hutool目前主要版本4.x、5.x、6.x、7.x,选择如下:
| 版本 | jdk| Maven仓库 | 主要特点 |
|-----|----|--------------------------------------------------------------------------------------------------------|-------------------------------------|
| 版本 | jdk | Maven仓库 | 主要特点 |
|-----|--------|--------------------------------------------------------------------------------------------------------|-------------------------------------|
| 4.x | jdk1.7 | [cn.hutool/hutool-all/4.x](https://mvnrepository.com/artifact/cn.hutool/hutool-all/4.6.17) | jdk1.7编译 |
| 5.x | jdk1.8 | [cn.hutool/hutool-all/5.x ](https://mvnrepository.com/artifact/cn.hutool/hutool-all) | jdk1.8编译,使用JavaEE,适配JDK11、17、21 |
| 6.x | jdk1.8 | [org.dromara.hutool/hutool-all/6.x ](https://mvnrepository.com/artifact/org.dromara.hutool/hutool-all) | jdk1.8编译,使用Jakarta EE,适配JDK11、17、21 |
| 7.x | jdk17 | [cn.hutool.v7/hutool-all/7.x ](https://mvnrepository.com/artifact/cn.hutool.v7/hutool-all) | jdk17编译,使用Jakarta EE,适配17+ |
## 🛠️包含组件
@@ -128,30 +129,30 @@ Hutool目前主要版本4.x、5.x、6.x选择如下
```xml
<dependency>
<groupId>org.dromara.hutool</groupId>
<groupId>cn.hutool.v7</groupId>
<artifactId>hutool-all</artifactId>
<version>6.0.0-M22</version>
<version>7.0.0-M2</version>
</dependency>
```
### 🍐Gradle
```
implementation 'org.dromara.hutool:hutool-all:6.0.0-M22'
implementation 'cn.hutool.v7:hutool-all:7.0.0-M2'
```
### 📥下载jar
点击以下链接,下载`hutool-all-X.X.X.jar`即可:
- [Maven中央库](https://repo1.maven.org/maven2/org/dromara/hutool/hutool-all/6.0.0-M22/)
- [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/v7/hutool-all/7.0.0-M2/)
> 🔔️注意
> Hutool 6.x支持JDK8+对Android平台没有测试不能保证所有工具类或工具方法可用。
> Hutool 7.x支持JDK17对Android平台没有测试不能保证所有工具类或工具方法可用。
### 🚽编译安装
访问Hutool的Gitee主页[https://gitee.com/dromara/hutool](https://gitee.com/dromara/hutool)
访问Hutool的Gitee主页[https://gitee.com/chinabugotech/hutool](https://gitee.com/chinabugotech/hutool)
下载整个项目源码v6-master或v6-dev分支都可然后进入Hutool项目目录执行
```sh
@@ -170,23 +171,23 @@ Hutool的源码分为两个分支功能如下
| 分支 | 作用 |
|-----------|--------------------------------------------|
| v6-master | 主分支release版本使用的分支与中央库提交的jar一致不接收任何pr或修改 |
| v6-dev | 开发分支默认为下个版本的SNAPSHOT版本接受修改或pr |
| v7-master | 主分支release版本使用的分支与中央库提交的jar一致不接收任何pr或修改 |
| v7-dev | 开发分支默认为下个版本的SNAPSHOT版本接受修改或pr |
### 🐞提供bug反馈或建议
提交问题反馈请说明正在使用的JDK版本呢、Hutool版本和相关依赖库版本。
- [Gitee issue](https://gitee.com/dromara/hutool/issues)
- [Github issue](https://github.com/dromara/hutool/issues)
- [Gitcode issue Gitcode问题](https://gitcode.com/dromara/hutool/issues)
- [Gitee issue](https://gitee.com/chinabugotech/hutool/issues)
- [Github issue](https://github.com/chinabugotech/hutool/issues)
- [Gitcode issue Gitcode问题](https://gitcode.com/chinabugotech/hutool/issues)
### 🧬贡献代码的步骤
1. 在Gitee、Github或Gitcode上fork项目到自己的repo
2. 把fork过去的项目也就是你的项目clone到你的本地
3. 修改代码记得一定要修改v6-dev分支
4. commit后push到自己的库v6-dev分支
3. 修改代码记得一定要修改v7-dev分支
4. commit后push到自己的库v7-dev分支
5. 登录Gitee、Github或Gitcode在你首页可以看到一个 pull request 按钮,点击它,填写一些说明信息,然后提交即可。
6. 等待维护者合并
@@ -209,7 +210,7 @@ Hutool欢迎任何人为Hutool添砖加瓦贡献代码不过维护者是
## ⭐Star Hutool
[![Stargazers over time](https://starchart.cc/dromara/hutool.svg)](https://starchart.cc/dromara/hutool)[![随时间变化的观星者](https://starchart.cc/dromara/hutool.svg)]https://starchart.cc/dromara/hutool
[![Stargazers over time](https://starchart.cc/chinabugotech/hutool.svg)](https://starchart.cc/chinabugotech/hutool)
### GitHub Contributor Over Time
[![Contributor Over Time](https://contributor-overtime-api.git-contributor.com/contributors-svg?chart=contributorOverTime&repo=dromara/hutool)](https://git-contributor.com?chart=contributorOverTime&repo=dromara/hutool)

View File

@@ -1,13 +0,0 @@
# Security Policy
## Supported Versions支持的版本
| Version | Supported |
|---------|--------------------|
| 6.x.x | :white_check_mark: |
## Reporting a Vulnerability报告漏洞
如果你发现有安全问题或漏洞,请发送邮件到`loolly@aliyun.com`
To report any found security issues or vulnerabilities, please send a mail to `loolly@aliyun.com`.

View File

@@ -1,3 +1,5 @@
#!/bin/bash
#
# Copyright (c) 2013-2025 Hutool Team and hutool.cn
#
@@ -14,5 +16,5 @@
# limitations under the License.
#
org.dromara.hutool.extra.aop.engine.spring.SpringCglibProxyEngine
org.dromara.hutool.extra.aop.engine.jdk.JdkProxyEngine
export JAVA_HOME="/cygdrive/d/java/jdk-17.0.15"
export PATH="$JAVA_HOME/bin:$PATH"

View File

@@ -16,12 +16,12 @@
# limitations under the License.
#
echo -e "\033[32mCheckout to v6-dev\033[0m"
git checkout v6-dev
echo -e "\033[32mCheckout to v7-dev\033[0m"
git checkout v7-dev
echo -e "\033[32mPush to Github(origin) v6-dev\033[0m"
git push origin v6-dev
echo -e "\033[32mPush to Gitee v6-dev\033[0m"
git push osc v6-dev
echo -e "\033[32mPush to Gitcode v6-dev\033[0m"
git push gitcode v6-dev
echo -e "\033[32mPush to Github(origin) v7-dev\033[0m"
git push origin v7-dev
echo -e "\033[32mPush to Gitee v7-dev\033[0m"
git push osc v7-dev
echo -e "\033[32mPush to Gitcode v7-dev\033[0m"
git push gitcode v7-dev

View File

@@ -19,15 +19,15 @@
# show Hutool logo
"$(dirname ${BASH_SOURCE[0]})"/logo.sh
echo -e "\033[32mCheckout to v6-master\033[0m"
git checkout v6-master
echo -e "\033[32mCheckout to v7-master\033[0m"
git checkout v7-master
echo -e "\033[32mMerge v6-dev branch\033[0m"
git merge v6-dev -m 'Prepare release'
echo -e "\033[32mMerge v7-dev branch\033[0m"
git merge v7-dev -m 'Prepare release'
echo -e "\033[32mPush to Github(origin) v6-master\033[0m"
git push origin v6-master
echo -e "\033[32mPush to Gitee v6-master\033[0m"
git push osc v6-master
echo -e "\033[32mPush to Gitcode v6-master\033[0m"
git push gitcode v6-master
echo -e "\033[32mPush to Github(origin) v7-master\033[0m"
git push origin v7-master
echo -e "\033[32mPush to Gitee v7-master\033[0m"
git push osc v7-master
echo -e "\033[32mPush to Gitcode v7-master\033[0m"
git push gitcode v7-master

View File

@@ -19,10 +19,10 @@
# show Hutool logo
"$(dirname ${BASH_SOURCE[0]})"/logo.sh
# 保证当前在v6-dev分支
git checkout v6-dev
# 保证当前在v7-dev分支
git checkout v7-dev
# 同时同步Gitee、Github和Gitcode的库
git pull osc v6-dev
git pull origin v6-dev
git pull gitcode v6-dev
git pull osc v7-dev
git pull origin v7-dev
git pull gitcode v7-dev

View File

@@ -1 +1 @@
6.0.0-M22
7.0.0-M2

View File

@@ -55,6 +55,16 @@
display: block;
}
</style>
<!-- 百度统计 -->
<script>
var _hmt = _hmt || [];
(function () {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?a76bc7a2d60207f04195af1c51cdb9ba";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script>
</head>
<body>

View File

@@ -1 +1 @@
var version = '6.0.0-M22'
var version = '7.0.0-M2'

View File

@@ -7,9 +7,9 @@
<packaging>jar</packaging>
<parent>
<groupId>org.dromara.hutool</groupId>
<groupId>cn.hutool.v7</groupId>
<artifactId>hutool-parent</artifactId>
<version>6.0.0-M22</version>
<version>7.0.0-M2</version>
</parent>
<artifactId>hutool-ai</artifactId>
@@ -17,34 +17,29 @@
<description>Hutool AI大模型封装</description>
<properties>
<Automatic-Module-Name>org.dromara.hutool.ai</Automatic-Module-Name>
<Automatic-Module-Name>cn.hutool.v7.ai</Automatic-Module-Name>
</properties>
<dependencies>
<dependency>
<groupId>org.dromara.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>org.dromara.hutool</groupId>
<groupId>cn.hutool.v7</groupId>
<artifactId>hutool-http</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>org.dromara.hutool</groupId>
<groupId>cn.hutool.v7</groupId>
<artifactId>hutool-log</artifactId>
<version>${project.parent.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.dromara.hutool</groupId>
<groupId>cn.hutool.v7</groupId>
<artifactId>hutool-json</artifactId>
<version>${project.parent.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.dromara.hutool</groupId>
<groupId>cn.hutool.v7</groupId>
<artifactId>hutool-swing</artifactId>
<version>${project.parent.version}</version>
<scope>test</scope>

View File

@@ -14,9 +14,9 @@
* limitations under the License.
*/
package org.dromara.hutool.ai;
package cn.hutool.v7.ai;
import org.dromara.hutool.core.exception.HutoolException;
import cn.hutool.v7.core.exception.HutoolException;
/**
* 异常处理类

View File

@@ -14,15 +14,16 @@
* limitations under the License.
*/
package org.dromara.hutool.ai;
package cn.hutool.v7.ai;
import org.dromara.hutool.ai.core.AIConfig;
import org.dromara.hutool.ai.core.AIService;
import org.dromara.hutool.ai.core.AIServiceProvider;
import org.dromara.hutool.core.map.concurrent.SafeConcurrentHashMap;
import cn.hutool.v7.ai.core.AIConfig;
import cn.hutool.v7.ai.core.AIService;
import cn.hutool.v7.ai.core.AIServiceProvider;
import cn.hutool.v7.core.spi.ServiceLoader;
import cn.hutool.v7.core.spi.SpiUtil;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.concurrent.ConcurrentHashMap;
/**
* 创建AIModelService的工厂类
@@ -32,11 +33,11 @@ import java.util.ServiceLoader;
*/
public class AIServiceFactory {
private static final Map<String, AIServiceProvider> providers = new SafeConcurrentHashMap<>();
private static final Map<String, AIServiceProvider> providers = new ConcurrentHashMap<>();
// 加载所有 AIModelProvider 实现类
static {
final ServiceLoader<AIServiceProvider> loader = ServiceLoader.load(AIServiceProvider.class);
final ServiceLoader<AIServiceProvider> loader = SpiUtil.loadList(AIServiceProvider.class);
for (final AIServiceProvider provider : loader) {
providers.put(provider.getServiceName().toLowerCase(), provider);
}

View File

@@ -14,15 +14,16 @@
* limitations under the License.
*/
package org.dromara.hutool.ai;
package cn.hutool.v7.ai;
import org.dromara.hutool.ai.core.AIConfig;
import org.dromara.hutool.ai.core.AIService;
import org.dromara.hutool.ai.core.Message;
import org.dromara.hutool.ai.model.deepseek.DeepSeekService;
import org.dromara.hutool.ai.model.doubao.DoubaoService;
import org.dromara.hutool.ai.model.grok.GrokService;
import org.dromara.hutool.ai.model.openai.OpenaiService;
import cn.hutool.v7.ai.core.AIConfig;
import cn.hutool.v7.ai.core.AIService;
import cn.hutool.v7.ai.core.Message;
import cn.hutool.v7.ai.model.deepseek.DeepSeekService;
import cn.hutool.v7.ai.model.doubao.DoubaoService;
import cn.hutool.v7.ai.model.grok.GrokService;
import cn.hutool.v7.ai.model.hutool.HutoolService;
import cn.hutool.v7.ai.model.openai.OpenaiService;
import java.util.List;
@@ -58,6 +59,17 @@ public class AIUtil {
return getAIService(config, AIService.class);
}
/**
* 获取Hutool-AI服务
*
* @param config 创建的AI服务模型的配置
* @return HutoolService
* @since 6.0.0
*/
public static HutoolService getHutoolService(final AIConfig config) {
return getAIService(config, HutoolService.class);
}
/**
* 获取DeepSeek模型服务
*

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.dromara.hutool.ai;
package cn.hutool.v7.ai;
/**
* 模型厂商的名称不指具体的模型
@@ -23,6 +23,10 @@ package org.dromara.hutool.ai;
* @since 6.0.0
*/
public enum ModelName {
/**
* hutool
*/
HUTOOL("hutool"),
/**
* deepSeek
*/
@@ -38,7 +42,11 @@ public enum ModelName {
/**
* grok
*/
GROK("grok");
GROK("grok"),
/**
* ollama
*/
OLLAMA("ollama");
private final String value;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.dromara.hutool.ai;
package cn.hutool.v7.ai;
/**
* 各模型厂商包含的model指具体的模型
@@ -24,6 +24,21 @@ package org.dromara.hutool.ai;
*/
public class Models {
// Hutool的模型
public enum Hutool {
HUTOOL("hutool");
private final String model;
Hutool(final String model) {
this.model = model;
}
public String getModel() {
return model;
}
}
// DeepSeek的模型
public enum DeepSeek {
DEEPSEEK_CHAT("deepseek-chat"),
@@ -31,7 +46,7 @@ public class Models {
private final String model;
DeepSeek(String model) {
DeepSeek(final String model) {
this.model = model;
}
@@ -79,7 +94,7 @@ public class Models {
private final String model;
Openai(String model) {
Openai(final String model) {
this.model = model;
}
@@ -123,11 +138,16 @@ public class Models {
DOUBAO_VISION_LITE_32K("doubao-vision-lite-32k-241015"),
DOUBAO_EMBEDDING_LARGE("doubao-embedding-large-text-240915"),
DOUBAO_EMBEDDING_TEXT_240715("doubao-embedding-text-240715"),
DOUBAO_EMBEDDING_VISION("doubao-embedding-vision-241215");
DOUBAO_EMBEDDING_VISION("doubao-embedding-vision-241215"),
DOUBAO_SEEDREAM_3_0_T2I("doubao-seedream-3-0-t2i-250415"),
Doubao_Seedance_1_0_lite_t2v("doubao-seedance-1-0-lite-t2v-250428"),
Doubao_Seedance_1_0_lite_i2v("doubao-seedance-1-0-lite-i2v-250428"),
Wan2_1_14B_t2v("wan2-1-14b-t2v-250225"),
Wan2_1_14B_i2v("wan2-1-14b-i2v-250225");
private final String model;
Doubao(String model) {
Doubao(final String model) {
this.model = model;
}
@@ -138,6 +158,23 @@ public class Models {
// Grok的模型
public enum Grok {
GROK_3_BETA_LATEST("grok-3-beta"),
GROK_3_BETA("grok-3-beta"),
GROK_3("grok-3-beta"),
GROK_3_MINI_FAST_LATEST("grok-3-mini-fast-beta"),
GROK_3_MINI_FAST_BETA("grok-3-mini-fast-beta"),
GROK_3_MINI_FAST("grok-3-mini-fast-beta"),
GROK_3_FAST_LATEST("grok-3-fast-beta"),
GROK_3_FAST_BETA("grok-3-fast-beta"),
GROK_3_FAST("grok-3-fast-beta"),
GROK_3_MINI_LATEST("grok-3-mini-beta"),
GROK_3_MINI_BETA("grok-3-mini-beta"),
GROK_3_MINI("grok-3-mini-beta"),
GROK_2_IMAGE_LATEST("grok-2-image-1212"),
GROK_2_IMAGE("grok-2-image-1212"),
GROK_2_IMAGE_1212("grok-2-image-1212"),
grok_2_latest("grok-2-1212"),
GROK_2("grok-2-1212"),
GROK_2_1212("grok-2-1212"),
GROK_2_VISION_1212("grok-2-vision-1212"),
GROK_BETA("grok-beta"),
@@ -145,7 +182,7 @@ public class Models {
private final String model;
Grok(String model) {
Grok(final String model) {
this.model = model;
}
@@ -154,4 +191,18 @@ public class Models {
}
}
// Ollama的模型
public enum Ollama {
QWEN3_32B("qwen3:32b");
private final String model;
Ollama(final String model) {
this.model = model;
}
public String getModel() {
return model;
}
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.dromara.hutool.ai.core;
package cn.hutool.v7.ai.core;
import java.util.Map;
@@ -110,4 +110,36 @@ public interface AIConfig {
*/
Map<String, Object> getAdditionalConfigMap();
/**
* 设置连接超时时间
*
* @param timeout 连接超时时间
* @since 6.0.0
*/
void setTimeout(int timeout);
/**
* 获取连接超时时间
*
* @return timeout
* @since 6.0.0
*/
int getTimeout();
/**
* 设置读取超时时间
*
* @param readTimeout 连接超时时间
* @since 6.0.0
*/
void setReadTimeout(int readTimeout);
/**
* 获取读取超时时间
*
* @return readTimeout
* @since 6.0.0
*/
int getReadTimeout();
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.dromara.hutool.ai.core;
package cn.hutool.v7.ai.core;
import java.lang.reflect.Constructor;
@@ -106,6 +106,34 @@ public class AIConfigBuilder {
return this;
}
/**
* 设置连接超时时间不设置为默认值
*
* @param timeout 超时时间
* @return config
* @since 6.0.0
*/
public synchronized AIConfigBuilder setTimout(final int timeout) {
if (timeout > 0) {
config.setTimeout(timeout);
}
return this;
}
/**
* 设置读取超时时间不设置为默认值
*
* @param readTimout 取超时时间
* @return config
* @since 6.0.0
*/
public synchronized AIConfigBuilder setReadTimout(final int readTimout) {
if (readTimout > 0) {
config.setReadTimeout(readTimout);
}
return this;
}
/**
* 返回config实例
*

View File

@@ -14,12 +14,13 @@
* limitations under the License.
*/
package org.dromara.hutool.ai.core;
package cn.hutool.v7.ai.core;
import org.dromara.hutool.core.map.concurrent.SafeConcurrentHashMap;
import cn.hutool.v7.core.spi.ServiceLoader;
import cn.hutool.v7.core.spi.SpiUtil;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.concurrent.ConcurrentHashMap;
/**
* AIConfig实现类的加载器
@@ -29,11 +30,11 @@ import java.util.ServiceLoader;
*/
public class AIConfigRegistry {
private static final Map<String, Class<? extends AIConfig>> configClasses = new SafeConcurrentHashMap<>();
private static final Map<String, Class<? extends AIConfig>> configClasses = new ConcurrentHashMap<>();
// 加载所有 AIConfig 实现类
static {
final ServiceLoader<AIConfig> loader = ServiceLoader.load(AIConfig.class);
final ServiceLoader<AIConfig> loader = SpiUtil.loadList(AIConfig.class);
for (final AIConfig config : loader) {
configClasses.put(config.getModelName().toLowerCase(), config.getClass());
}

View File

@@ -14,9 +14,11 @@
* limitations under the License.
*/
package org.dromara.hutool.ai.core;
package cn.hutool.v7.ai.core;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
/**
* 模型公共的API功能特有的功能在model.xx.XXService下定义
@@ -33,7 +35,25 @@ public interface AIService {
* @return AI回答
* @since 6.0.0
*/
String chat(String prompt);
default String chat(String prompt){
final List<Message> messages = new ArrayList<>();
messages.add(new Message("system", "You are a helpful assistant"));
messages.add(new Message("user", prompt));
return chat(messages);
}
/**
* 对话-SSE流式输出
* @param prompt user题词
* @param callback 流式数据回调函数
* @since 6.0.0
*/
default void chat(String prompt, final Consumer<String> callback){
final List<Message> messages = new ArrayList<>();
messages.add(new Message("system", "You are a helpful assistant"));
messages.add(new Message("user", prompt));
chat(messages, callback);
}
/**
* 对话
@@ -44,4 +64,12 @@ public interface AIService {
*/
String chat(final List<Message> messages);
/**
* 对话-SSE流式输出
* @param messages 由目前为止的对话组成的消息列表可以设置rolecontent详细参考官方文档
* @param callback 流式数据回调函数
* @since 6.0.0
*/
void chat(final List<Message> messages, final Consumer<String> callback);
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.dromara.hutool.ai.core;
package cn.hutool.v7.ai.core;
/**
* 用于加载AI服务,每一个通过SPI创建的AI服务都要实现此接口

View File

@@ -14,16 +14,23 @@
* limitations under the License.
*/
package org.dromara.hutool.ai.core;
package cn.hutool.v7.ai.core;
import org.dromara.hutool.ai.AIException;
import org.dromara.hutool.http.HttpGlobalConfig;
import org.dromara.hutool.http.HttpUtil;
import org.dromara.hutool.http.client.Response;
import org.dromara.hutool.http.meta.HeaderName;
import org.dromara.hutool.http.meta.Method;
import cn.hutool.v7.ai.AIException;
import cn.hutool.v7.http.HttpGlobalConfig;
import cn.hutool.v7.http.HttpUtil;
import cn.hutool.v7.http.client.Response;
import cn.hutool.v7.http.meta.HeaderName;
import cn.hutool.v7.http.meta.Method;
import cn.hutool.v7.json.JSONUtil;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;
import java.util.function.Consumer;
/**
* 基础AIService包含基公共参数和公共方法
@@ -33,6 +40,9 @@ import java.util.Map;
*/
public class BaseAIService {
/**
* AI配置
*/
protected final AIConfig config;
/**
@@ -52,8 +62,8 @@ public class BaseAIService {
protected Response sendGet(final String endpoint) {
//链式构建请求
try {
//设置超时3分钟
HttpGlobalConfig.setTimeout(180000);
//设置超时
HttpGlobalConfig.setTimeout(config.getTimeout());
return HttpUtil.createRequest(config.getApiUrl() + endpoint, Method.GET)
.header(HeaderName.ACCEPT, "application/json")
.header(HeaderName.AUTHORIZATION, "Bearer " + config.getApiKey())
@@ -73,7 +83,7 @@ public class BaseAIService {
//链式构建请求
try {
//设置超时3分钟
HttpGlobalConfig.setTimeout(180000);
HttpGlobalConfig.setTimeout(config.getTimeout());
return HttpUtil.createRequest(config.getApiUrl() + endpoint, Method.POST)
.header(HeaderName.CONTENT_TYPE, "application/json")
.header(HeaderName.ACCEPT, "application/json")
@@ -96,7 +106,7 @@ public class BaseAIService {
//链式构建请求
try {
//设置超时3分钟
HttpGlobalConfig.setTimeout(180000);
HttpGlobalConfig.setTimeout(config.getTimeout());
return HttpUtil.createPost(config.getApiUrl() + endpoint)
//form表单中有file对象会自动将文件编码为 multipart/form-data 格式不需要设置
// .header(HeaderName.CONTENT_TYPE, "multipart/form-data")
@@ -107,4 +117,50 @@ public class BaseAIService {
throw new AIException("Failed to send POST request" + e.getMessage(), e);
}
}
/**
* 支持流式返回的 POST 请求
*
* @param endpoint 请求地址
* @param paramMap 请求参数
* @param callback 流式数据回调函数
*/
protected void sendPostStream(final String endpoint, final Map<String, Object> paramMap, final Consumer<String> callback) {
HttpURLConnection connection = null;
try {
// 创建连接
final URL apiUrl = new URL(config.getApiUrl() + endpoint);
connection = (HttpURLConnection) apiUrl.openConnection();
connection.setRequestMethod(Method.POST.name());
connection.setRequestProperty(HeaderName.CONTENT_TYPE.getValue(), "application/json");
connection.setRequestProperty(HeaderName.AUTHORIZATION.getValue(), "Bearer " + config.getApiKey());
connection.setDoOutput(true);
//设置读取超时
connection.setReadTimeout(config.getReadTimeout());
//设置连接超时
connection.setConnectTimeout(config.getTimeout());
// 发送请求体
try (final OutputStream os = connection.getOutputStream()) {
final String jsonInputString = JSONUtil.toJsonStr(paramMap);
os.write(jsonInputString.getBytes());
os.flush();
}
// 读取流式响应
try (final BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
// 调用回调函数处理每一行数据
callback.accept(line);
}
}
} catch (final Exception e) {
callback.accept("{\"error\": \"" + e.getMessage() + "\"}");
} finally {
// 关闭连接
if (connection != null) {
connection.disconnect();
}
}
}
}

View File

@@ -14,11 +14,10 @@
* limitations under the License.
*/
package org.dromara.hutool.ai.core;
import org.dromara.hutool.core.map.concurrent.SafeConcurrentHashMap;
package cn.hutool.v7.ai.core;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Config基础类定义模型配置的基本属性
@@ -28,14 +27,30 @@ import java.util.Map;
*/
public class BaseConfig implements AIConfig {
//apiKey
/**
* API Key
*/
protected volatile String apiKey;
//API请求地址
/**
* API请求地址
*/
protected volatile String apiUrl;
//具体模型
/**
* 模型名称
*/
protected volatile String model;
//动态扩展字段
protected Map<String, Object> additionalConfig = new SafeConcurrentHashMap<>();
/**
* 额外的配置
*/
protected final Map<String, Object> additionalConfig = new ConcurrentHashMap<>();
/**
* 请求超时
*/
protected volatile int timeout = 180000;
/**
* 读取超时
*/
protected volatile int readTimeout = 300000;
@Override
public void setApiKey(final String apiKey) {
@@ -79,7 +94,26 @@ public class BaseConfig implements AIConfig {
@Override
public Map<String, Object> getAdditionalConfigMap() {
return new SafeConcurrentHashMap<>(additionalConfig);
return new ConcurrentHashMap<>(additionalConfig);
}
@Override
public int getTimeout() {
return timeout;
}
@Override
public void setTimeout(final int timeout) {
this.timeout = timeout;
}
@Override
public int getReadTimeout() {
return readTimeout;
}
@Override
public void setReadTimeout(final int readTimeout) {
this.readTimeout = readTimeout;
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.dromara.hutool.ai.core;
package cn.hutool.v7.ai.core;
/**
* 公共Message类

View File

@@ -21,4 +21,4 @@
* @since 6.0.0
*/
package org.dromara.hutool.ai.core;
package cn.hutool.v7.ai.core;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.dromara.hutool.ai.model.deepseek;
package cn.hutool.v7.ai.model.deepseek;
/**
* deepSeek公共类

View File

@@ -0,0 +1,66 @@
/*
* Copyright (c) 2025 Hutool Team and hutool.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.hutool.v7.ai.model.deepseek;
import cn.hutool.v7.ai.Models;
import cn.hutool.v7.ai.core.BaseConfig;
/**
* DeepSeek配置类初始化API接口地址设置默认的模型
*
* @author elichow
* @since 6.0.0
*/
public class DeepSeekConfig extends BaseConfig {
/**
* 定义API的基础URL用于后续的所有API请求
*/
public final String API_URL = "https://api.deepseek.com";
/**
* 定义默认的模型名称,用于在没有指定模型时使用
*/
public final String DEFAULT_MODEL = Models.DeepSeek.DEEPSEEK_CHAT.getModel();
/**
* 默认构造函数用于初始化DeepSeek配置对象
* 设置API的基础URL和默认的模型名称
*/
public DeepSeekConfig() {
setApiUrl(API_URL);
setModel(DEFAULT_MODEL);
}
/**
* 带API密钥参数的构造函数
* 用于初始化DeepSeek配置对象并设置API密钥
*
* @param apiKey 用户的API密钥用于认证和授权
*/
public DeepSeekConfig(final String apiKey) {
this(); // 调用默认构造函数初始化API_URL和DEFAULT_MODEL
setApiKey(apiKey);
}
@Override
public String getModelName() {
return "deepSeek";
}
}

View File

@@ -14,10 +14,10 @@
* limitations under the License.
*/
package org.dromara.hutool.ai.model.deepseek;
package cn.hutool.v7.ai.model.deepseek;
import org.dromara.hutool.ai.core.AIConfig;
import org.dromara.hutool.ai.core.AIServiceProvider;
import cn.hutool.v7.ai.core.AIConfig;
import cn.hutool.v7.ai.core.AIServiceProvider;
/**
* 创建DeepSeek服务实现类

View File

@@ -14,9 +14,11 @@
* limitations under the License.
*/
package org.dromara.hutool.ai.model.deepseek;
package cn.hutool.v7.ai.model.deepseek;
import org.dromara.hutool.ai.core.AIService;
import cn.hutool.v7.ai.core.AIService;
import java.util.function.Consumer;
/**
* deepSeek支持的扩展接口
@@ -35,6 +37,14 @@ public interface DeepSeekService extends AIService {
*/
String beta(String prompt);
/**
* 模型beta功能-SSE流式输出
* @param prompt 题词
* @param callback 流式数据回调函数
* @since 6.0.0
*/
void beta(String prompt, final Consumer<String> callback);
/**
* 列出所有模型列表
*

View File

@@ -14,18 +14,19 @@
* limitations under the License.
*/
package org.dromara.hutool.ai.model.deepseek;
package cn.hutool.v7.ai.model.deepseek;
import org.dromara.hutool.ai.core.AIConfig;
import org.dromara.hutool.ai.core.BaseAIService;
import org.dromara.hutool.ai.core.Message;
import org.dromara.hutool.http.client.Response;
import org.dromara.hutool.json.JSONUtil;
import cn.hutool.v7.ai.core.AIConfig;
import cn.hutool.v7.ai.core.BaseAIService;
import cn.hutool.v7.ai.core.Message;
import cn.hutool.v7.core.thread.ThreadUtil;
import cn.hutool.v7.http.client.Response;
import cn.hutool.v7.json.JSONUtil;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
/**
* DeepSeek服务AI具体功能的实现
@@ -54,15 +55,6 @@ public class DeepSeekServiceImpl extends BaseAIService implements DeepSeekServic
super(config);
}
@Override
public String chat(final String prompt) {
// 定义消息结构
final List<Message> messages = new ArrayList<>();
messages.add(new Message("system", "You are a helpful assistant"));
messages.add(new Message("user", prompt));
return chat(messages);
}
@Override
public String chat(final List<Message> messages) {
final String paramJson = buildChatRequestBody(messages);
@@ -70,6 +62,12 @@ public class DeepSeekServiceImpl extends BaseAIService implements DeepSeekServic
return response.bodyStr();
}
@Override
public void chat(List<Message> messages, Consumer<String> callback) {
Map<String, Object> paramMap = buildChatStreamRequestBody(messages);
ThreadUtil.newThread(() -> sendPostStream(CHAT_ENDPOINT, paramMap, callback::accept), "deepseek-chat-sse").start();
}
@Override
public String beta(final String prompt) {
final String paramJson = buildBetaRequestBody(prompt);
@@ -77,6 +75,12 @@ public class DeepSeekServiceImpl extends BaseAIService implements DeepSeekServic
return response.bodyStr();
}
@Override
public void beta(String prompt, Consumer<String> callback) {
Map<String, Object> paramMap = buildBetaStreamRequestBody(prompt);
ThreadUtil.newThread(() -> sendPostStream(BETA_ENDPOINT, paramMap, callback::accept), "deepseek-beta-sse").start();
}
@Override
public String models() {
final Response response = sendGet(MODELS_ENDPOINT);
@@ -101,6 +105,19 @@ public class DeepSeekServiceImpl extends BaseAIService implements DeepSeekServic
return JSONUtil.toJsonStr(paramMap);
}
// 构建chatStream请求体
private Map<String, Object> buildChatStreamRequestBody(final List<Message> messages) {
//使用JSON工具
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("stream", true);
paramMap.put("model", config.getModel());
paramMap.put("messages", messages);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return paramMap;
}
// 构建beta请求体
private String buildBetaRequestBody(final String prompt) {
// 定义消息结构
@@ -114,4 +131,17 @@ public class DeepSeekServiceImpl extends BaseAIService implements DeepSeekServic
return JSONUtil.toJsonStr(paramMap);
}
// 构建betaStream请求体
private Map<String, Object> buildBetaStreamRequestBody(final String prompt) {
//使用JSON工具
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("stream", true);
paramMap.put("model", config.getModel());
paramMap.put("prompt", prompt);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return paramMap;
}
}

View File

@@ -21,4 +21,4 @@
* @since 6.0.0
*/
package org.dromara.hutool.ai.model.deepseek;
package cn.hutool.v7.ai.model.deepseek;

View File

@@ -0,0 +1,180 @@
/*
* Copyright (c) 2025 Hutool Team and hutool.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.hutool.v7.ai.model.doubao;
/**
* doubao公共类
*
* @author elichow
* @since 6.0.0
*/
public class DoubaoCommon {
/**
* doubao上下文缓存参数
*/
public enum DoubaoContext {
/**
* session
*/
SESSION("session"),
/**
* common_prefix
*/
COMMON_PREFIX("common_prefix");
private final String mode;
DoubaoContext(final String mode) {
this.mode = mode;
}
/**
* 获取参数
*
* @return 参数
*/
public String getMode() {
return mode;
}
}
/**
* doubao视觉参数
*/
public enum DoubaoVision {
/**
* 自动
*/
AUTO("auto"),
/**
* 低
*/
LOW("low"),
/**
* 高
*/
HIGH("high");
private final String detail;
DoubaoVision(final String detail) {
this.detail = detail;
}
/**
* 获取参数
*
* @return 参数
*/
public String getDetail() {
return detail;
}
}
/**
* doubao视频生成参数
*/
public enum DoubaoVideo {
//宽高比例
/**
* 视频比例16:9适用于横向宽屏显示常用作标准视频比例
*/
RATIO_16_9("--rt", "16:9"),//[1280, 720]
/**
* 视频比例4:3传统电视屏幕比例适用于标准清晰度的视频内容
*/
RATIO_4_3("--rt", "4:3"),//[960, 720]
/**
* 视频比例1:1正方形画面适用于社交媒体平台上的短视频内容
*/
RATIO_1_1("--rt", "1:1"),//[720, 720]
/**
* 视频比例3:4竖向视频比例适用于手机端的视频播放场景
*/
RATIO_3_4("--rt", "3:4"),//[720, 960]
/**
* 视频比例9:16常见的竖屏视频比例广泛用于短视频应用
*/
RATIO_9_16("--rt", "9:16"),//[720, 1280]
/**
* 视频比例21:9超宽屏幕比例提供更广阔的视野适合电影和游戏体验
*/
RATIO_21_9("--rt", "21:9"),//[1280, 544]
//生成视频时长
/**
* 文生视频,图生视频
*/
DURATION_5("--dur", 5),
/**
* 文生视频
*/
DURATION_10("--dur", 10),
/**
* 帧率,即一秒时间内视频画面数量
*/
FPS_5("--fps", 24),
/**
* 视频分辨率
*/
RESOLUTION_5("--rs", "720p"),
/**
* 生成视频包含水印
*/
WATERMARK_TRUE("--wm", true),
/**
* 生成视频不包含水印
*/
WATERMARK_FALSE("--wm", false);
private final String type;
private final Object value;
DoubaoVideo(final String type, final Object value) {
this.type = type;
this.value = value;
}
public String getType() {
return type;
}
public Object getValue() {
if (value instanceof String) {
return (String) value;
} else if (value instanceof Integer) {
return (Integer) value;
} else if (value instanceof Boolean) {
return (Boolean) value;
}
return value;
}
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright (c) 2025 Hutool Team and hutool.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.hutool.v7.ai.model.doubao;
import cn.hutool.v7.ai.Models;
import cn.hutool.v7.ai.core.BaseConfig;
/**
* Doubao配置类初始化API接口地址设置默认的模型
*
* @author elichow
* @since 6.0.0
*/
public class DoubaoConfig extends BaseConfig {
// 定义API的基础URL用于和服务器通信
private static final String API_URL = "https://ark.cn-beijing.volces.com/api/v3";
// 定义默认的模型配置,用于初始化配置对象时设定
private static final String DEFAULT_MODEL = Models.Doubao.DOUBAO_1_5_LITE_32K.getModel();
/**
* 无参构造函数用于创建DoubaoConfig对象
* 初始化时会设置API_URL和DEFAULT_MODEL
*/
public DoubaoConfig() {
setApiUrl(API_URL);
setModel(DEFAULT_MODEL);
}
/**
* 带有apiKey参数的构造函数用于创建DoubaoConfig对象并设置API密钥
* 初始化时会设置API_URL、DEFAULT_MODEL以及传入的apiKey
*
* @param apiKey 用户的API密钥用于验证用户身份
*/
public DoubaoConfig(final String apiKey) {
this(); // 先调用无参构造函数初始化API_URL和DEFAULT_MODEL
setApiKey(apiKey); // 设置用户的API密钥
}
@Override
public String getModelName() {
return "doubao";
}
}

View File

@@ -14,10 +14,10 @@
* limitations under the License.
*/
package org.dromara.hutool.ai.model.doubao;
package cn.hutool.v7.ai.model.doubao;
import org.dromara.hutool.ai.core.AIConfig;
import org.dromara.hutool.ai.core.AIServiceProvider;
import cn.hutool.v7.ai.core.AIConfig;
import cn.hutool.v7.ai.core.AIServiceProvider;
/**
* 创建Doubap服务实现类

View File

@@ -14,12 +14,14 @@
* limitations under the License.
*/
package org.dromara.hutool.ai.model.doubao;
package cn.hutool.v7.ai.model.doubao;
import org.dromara.hutool.ai.core.AIService;
import org.dromara.hutool.ai.core.Message;
import cn.hutool.v7.ai.core.AIService;
import cn.hutool.v7.ai.core.Message;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
/**
* doubao支持的扩展接口
@@ -29,17 +31,6 @@ import java.util.List;
*/
public interface DoubaoService extends AIService {
/**
* 图像理解模型会依据传入的图片信息以及问题给出回复
*
* @param prompt 提问
* @param images 图片列表/或者图片Base64编码图片列表(URI形式)
* @param detail 手动设置图片的质量取值范围highlowauto,默认为auto
* @return AI回答
* @since 6.0.0
*/
String chatVision(String prompt, final List<String> images, String detail);
/**
* 图像理解模型会依据传入的图片信息以及问题给出回复
*
@@ -52,9 +43,43 @@ public interface DoubaoService extends AIService {
return chatVision(prompt, images, DoubaoCommon.DoubaoVision.AUTO.getDetail());
}
/**
* 图像理解-SSE流式输出
*
* @param prompt 提问
* @param images 图片列表/或者图片Base64编码图片列表(URI形式)
* @param callback 流式数据回调函数
* @since 6.0.0
*/
default void chatVision(String prompt, final List<String> images, final Consumer<String> callback) {
chatVision(prompt, images, DoubaoCommon.DoubaoVision.AUTO.getDetail(), callback);
}
/**
* 图像理解模型会依据传入的图片信息以及问题给出回复
*
* @param prompt 提问
* @param images 图片列表/或者图片Base64编码图片列表(URI形式)
* @param detail 手动设置图片的质量取值范围highlowauto,默认为auto
* @return AI回答
* @since 6.0.0
*/
String chatVision(String prompt, final List<String> images, String detail);
/**
* 图像理解-SSE流式输出
*
* @param prompt 提问
* @param images 传入的图片列表地址/或者图片Base64编码图片列表(URI形式)
* @param detail 手动设置图片的质量取值范围highlowauto,默认为auto
* @param callback 流式数据回调函数
* @since 6.0.0
*/
void chatVision(String prompt, final List<String> images, String detail, final Consumer<String> callback);
/**
* 创建视频生成任务
* 注意调用该方法时配置config中的model为您创建的推理接入点EndpointID详细参考官方文档
* 注意调用该方法时配置config中的model为生成视频的模型或者您创建的推理接入点EndpointID详细参考官方文档
*
* @param text 文本提示词
* @param image 图片/或者图片Base64编码图片(URI形式)
@@ -114,6 +139,15 @@ public interface DoubaoService extends AIService {
*/
String botsChat(final List<Message> messages);
/**
* 应用(Bot)-SSE流式输出 config中model设置为您创建的应用ID
*
* @param messages 由对话组成的消息列表如系统人设背景信息等用户自定义的信息
* @param callback 流式数据回调函数
* @since 6.0.0
*/
void botsChat(final List<Message> messages, final Consumer<String> callback);
/**
* 分词可以将文本转换为模型可理解的 token id并返回文本的 tokens 数量token id token 在原始文本中的偏移量等信息
*
@@ -132,7 +166,12 @@ public interface DoubaoService extends AIService {
* @return AI回答
* @since 6.0.0
*/
String batchChat(String prompt);
default String batchChat(String prompt){
final List<Message> messages = new ArrayList<>();
messages.add(new Message("system", "You are a helpful assistant"));
messages.add(new Message("user", prompt));
return batchChat(messages);
}
/**
* 批量推理 Chat
@@ -179,7 +218,26 @@ public interface DoubaoService extends AIService {
* @return AI的回答
* @since 6.0.0
*/
String chatContext(String prompt, String contextId);
default String chatContext(String prompt, String contextId){
final List<Message> messages = new ArrayList<>();
messages.add(new Message("user", prompt));
return chatContext(messages, contextId);
}
/**
* 上下文缓存对话-SSE流式输出
* 注意配置config中的model可以为您创建的推理接入点EndpointID也可以是支持chat的model
*
* @param prompt 对话的内容题词
* @param contextId 创建上下文缓存后获取的缓存id
* @param callback 流式数据回调函数
* @since 6.0.0
*/
default void chatContext(String prompt, String contextId, final Consumer<String> callback){
final List<Message> messages = new ArrayList<>();
messages.add(new Message("user", prompt));
chatContext(messages, contextId, callback);
}
/**
* 上下文缓存对话 向大模型发起带上下文缓存的请求
@@ -192,4 +250,24 @@ public interface DoubaoService extends AIService {
*/
String chatContext(final List<Message> messages, String contextId);
/**
* 上下文缓存对话-SSE流式输出
* 注意配置config中的model可以为您创建的推理接入点EndpointID也可以是支持chat的model
*
* @param messages 对话的信息 不支持最后一个元素的role设置为assistant如使用session 缓存mode设置为session传入最新一轮对话的信息无需传入历史信息
* @param contextId 创建上下文缓存后获取的缓存id
* @param callback 流式数据回调函数
* @since 6.0.0
*/
void chatContext(final List<Message> messages, String contextId, final Consumer<String> callback);
/**
* 文生图
* 请设置config中model为支持图片功能的模型目前支持Doubao-Seedream-3.0-t2i
*
* @param prompt 题词
* @return 包含生成图片的url
* @since 6.0.0
*/
String imagesGenerations(String prompt);
}

View File

@@ -0,0 +1,439 @@
/*
* Copyright (c) 2025 Hutool Team and hutool.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.hutool.v7.ai.model.doubao;
import cn.hutool.v7.ai.core.AIConfig;
import cn.hutool.v7.ai.core.BaseAIService;
import cn.hutool.v7.ai.core.Message;
import cn.hutool.v7.core.text.StrUtil;
import cn.hutool.v7.core.thread.ThreadUtil;
import cn.hutool.v7.http.client.Response;
import cn.hutool.v7.json.JSONUtil;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
/**
* Doubao服务AI具体功能的实现
*
* @author elichow
* @since 6.0.0
*/
public class DoubaoServiceImpl extends BaseAIService implements DoubaoService {
//对话
private static final String CHAT_ENDPOINT = "/chat/completions";
//文本向量化
private static final String EMBEDDING_TEXT = "/embeddings";
//图文向量化
private static final String EMBEDDING_VISION = "/embeddings/multimodal";
//应用bots
private static final String BOTS_CHAT = "/bots/chat/completions";
//分词
private static final String TOKENIZATION = "/tokenization";
//批量推理chat
private static final String BATCH_CHAT = "/batch/chat/completions";
//创建上下文缓存
private static final String CREATE_CONTEXT = "/context/create";
//上下文缓存对话
private static final String CHAT_CONTEXT = "/context/chat/completions";
//创建视频生成任务
private static final String CREATE_VIDEO = "/contents/generations/tasks";
//文生图
private static final String IMAGES_GENERATIONS = "/images/generations";
public DoubaoServiceImpl(final AIConfig config) {
//初始化doubao客户端
super(config);
}
@Override
public String chat(final List<Message> messages) {
final String paramJson = buildChatRequestBody(messages);
final Response response = sendPost(CHAT_ENDPOINT, paramJson);
return response.bodyStr();
}
@Override
public void chat(final List<Message> messages, final Consumer<String> callback) {
final Map<String, Object> paramMap = buildChatStreamRequestBody(messages);
ThreadUtil.newThread(() -> sendPostStream(CHAT_ENDPOINT, paramMap, callback::accept), "doubao-chat-sse").start();
}
@Override
public String chatVision(final String prompt, final List<String> images, final String detail) {
final String paramJson = buildChatVisionRequestBody(prompt, images, detail);
final Response response = sendPost(CHAT_ENDPOINT, paramJson);
return response.bodyStr();
}
@Override
public void chatVision(final String prompt, final List<String> images, final String detail, final Consumer<String> callback) {
final Map<String, Object> paramMap = buildChatVisionStreamRequestBody(prompt, images, detail);
ThreadUtil.newThread(() -> sendPostStream(CHAT_ENDPOINT, paramMap, callback::accept), "doubao-chatVision-sse").start();
}
@Override
public String videoTasks(final String text, final String image, final List<DoubaoCommon.DoubaoVideo> videoParams) {
final String paramJson = buildGenerationsTasksRequestBody(text, image, videoParams);
final Response response = sendPost(CREATE_VIDEO, paramJson);
return response.bodyStr();
}
@Override
public String getVideoTasksInfo(final String taskId) {
final Response response = sendGet(CREATE_VIDEO + "/" + taskId);
return response.bodyStr();
}
@Override
public String embeddingText(final String[] input) {
final String paramJson = buildEmbeddingTextRequestBody(input);
final Response response = sendPost(EMBEDDING_TEXT, paramJson);
return response.bodyStr();
}
@Override
public String embeddingVision(final String text, final String image) {
final String paramJson = buildEmbeddingVisionRequestBody(text, image);
final Response response = sendPost(EMBEDDING_VISION, paramJson);
return response.bodyStr();
}
@Override
public String botsChat(final List<Message> messages) {
final String paramJson = buildBotsChatRequestBody(messages);
final Response response = sendPost(BOTS_CHAT, paramJson);
return response.bodyStr();
}
@Override
public void botsChat(final List<Message> messages, final Consumer<String> callback) {
final Map<String, Object> paramMap = buildBotsChatStreamRequestBody(messages);
ThreadUtil.newThread(() -> sendPostStream(BOTS_CHAT, paramMap, callback::accept), "doubao-botsChat-sse").start();
}
@Override
public String tokenization(final String[] text) {
final String paramJson = buildTokenizationRequestBody(text);
final Response response = sendPost(TOKENIZATION, paramJson);
return response.bodyStr();
}
@Override
public String batchChat(final List<Message> messages) {
final String paramJson = buildBatchChatRequestBody(messages);
final Response response = sendPost(BATCH_CHAT, paramJson);
return response.bodyStr();
}
@Override
public String createContext(final List<Message> messages, final String mode) {
final String paramJson = buildCreateContextRequest(messages, mode);
final Response response = sendPost(CREATE_CONTEXT, paramJson);
return response.bodyStr();
}
@Override
public String chatContext(final List<Message> messages, final String contextId) {
final String paramJson = buildChatContentRequestBody(messages, contextId);
final Response response = sendPost(CHAT_CONTEXT, paramJson);
return response.bodyStr();
}
@Override
public void chatContext(final List<Message> messages, final String contextId, final Consumer<String> callback) {
final Map<String, Object> paramMap = buildChatContentStreamRequestBody(messages, contextId);
ThreadUtil.newThread(() -> sendPostStream(CHAT_CONTEXT, paramMap, callback::accept), "doubao-chatContext-sse").start();
}
@Override
public String imagesGenerations(final String prompt) {
final String paramJson = buildImagesGenerationsRequestBody(prompt);
final Response response = sendPost(IMAGES_GENERATIONS, paramJson);
return response.bodyStr();
}
// 构建chat请求体
private String buildChatRequestBody(final List<Message> messages) {
//使用JSON工具
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("model", config.getModel());
paramMap.put("messages", messages);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return JSONUtil.toJsonStr(paramMap);
}
// 构建chatStream请求体
private Map<String, Object> buildChatStreamRequestBody(final List<Message> messages) {
//使用JSON工具
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("stream", true);
paramMap.put("model", config.getModel());
paramMap.put("messages", messages);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return paramMap;
}
//构建chatVision请求体
private String buildChatVisionRequestBody(final String prompt, final List<String> images, final String detail) {
// 定义消息结构
final List<Message> messages = new ArrayList<>();
final List<Object> content = new ArrayList<>();
final Map<String, String> contentMap = new HashMap<>();
contentMap.put("type", "text");
contentMap.put("text", prompt);
content.add(contentMap);
for (final String img : images) {
final HashMap<String, Object> imgUrlMap = new HashMap<>();
imgUrlMap.put("type", "image_url");
final HashMap<String, String> urlMap = new HashMap<>();
urlMap.put("url", img);
urlMap.put("detail", detail);
imgUrlMap.put("image_url", urlMap);
content.add(imgUrlMap);
}
messages.add(new Message("user", content));
//使用JSON工具
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("model", config.getModel());
paramMap.put("messages", messages);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return JSONUtil.toJsonStr(paramMap);
}
private Map<String, Object> buildChatVisionStreamRequestBody(final String prompt, final List<String> images, final String detail) {
// 定义消息结构
final List<Message> messages = new ArrayList<>();
final List<Object> content = new ArrayList<>();
final Map<String, String> contentMap = new HashMap<>();
contentMap.put("type", "text");
contentMap.put("text", prompt);
content.add(contentMap);
for (final String img : images) {
final HashMap<String, Object> imgUrlMap = new HashMap<>();
imgUrlMap.put("type", "image_url");
final HashMap<String, String> urlMap = new HashMap<>();
urlMap.put("url", img);
urlMap.put("detail", detail);
imgUrlMap.put("image_url", urlMap);
content.add(imgUrlMap);
}
messages.add(new Message("user", content));
//使用JSON工具
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("stream", true);
paramMap.put("model", config.getModel());
paramMap.put("messages", messages);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return paramMap;
}
//构建文本向量化请求体
private String buildEmbeddingTextRequestBody(final String[] input) {
//使用JSON工具
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("model", config.getModel());
paramMap.put("input", input);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return JSONUtil.toJsonStr(paramMap);
}
//构建图文向量化请求体
private String buildEmbeddingVisionRequestBody(final String text, final String image) {
//使用JSON工具
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("model", config.getModel());
final List<Object> input = new ArrayList<>();
//添加文本参数
if (!StrUtil.isBlank(text)) {
final Map<String, String> textMap = new HashMap<>();
textMap.put("type", "text");
textMap.put("text", text);
input.add(textMap);
}
//添加图片参数
if (!StrUtil.isBlank(image)) {
final Map<String, Object> imgUrlMap = new HashMap<>();
imgUrlMap.put("type", "image_url");
final Map<String, String> urlMap = new HashMap<>();
urlMap.put("url", image);
imgUrlMap.put("image_url", urlMap);
input.add(imgUrlMap);
}
paramMap.put("input", input);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return JSONUtil.toJsonStr(paramMap);
}
//构建应用chat请求体
private String buildBotsChatRequestBody(final List<Message> messages) {
return buildChatRequestBody(messages);
}
private Map<String, Object> buildBotsChatStreamRequestBody(final List<Message> messages) {
return buildChatStreamRequestBody(messages);
}
//构建分词请求体
private String buildTokenizationRequestBody(final String[] text) {
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("model", config.getModel());
paramMap.put("text", text);
return JSONUtil.toJsonStr(paramMap);
}
//构建批量推理chat请求体
private String buildBatchChatRequestBody(final List<Message> messages) {
return buildChatRequestBody(messages);
}
private Map<String, Object> buildBatchChatStreamRequestBody(final List<Message> messages) {
return buildChatStreamRequestBody(messages);
}
//构建创建上下文缓存请求体
private String buildCreateContextRequest(final List<Message> messages, final String mode) {
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("messages", messages);
paramMap.put("model", config.getModel());
paramMap.put("mode", mode);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return JSONUtil.toJsonStr(paramMap);
}
//构建上下文缓存对话请求体
private String buildChatContentRequestBody(final List<Message> messages, final String contextId) {
//使用JSON工具
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("model", config.getModel());
paramMap.put("messages", messages);
paramMap.put("context_id", contextId);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return JSONUtil.toJsonStr(paramMap);
}
private Map<String, Object> buildChatContentStreamRequestBody(final List<Message> messages, final String contextId) {
//使用JSON工具
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("stream", true);
paramMap.put("model", config.getModel());
paramMap.put("messages", messages);
paramMap.put("context_id", contextId);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return paramMap;
}
//构建创建视频任务请求体
private String buildGenerationsTasksRequestBody(final String text, final String image, final List<DoubaoCommon.DoubaoVideo> videoParams) {
//使用JSON工具
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("model", config.getModel());
final List<Object> content = new ArrayList<>();
//添加文本参数
final Map<String, String> textMap = new HashMap<>();
if (!StrUtil.isBlank(text)) {
textMap.put("type", "text");
textMap.put("text", text);
content.add(textMap);
}
//添加图片参数
if (!StrUtil.isBlank(image)) {
final Map<String, Object> imgUrlMap = new HashMap<>();
imgUrlMap.put("type", "image_url");
final Map<String, String> urlMap = new HashMap<>();
urlMap.put("url", image);
imgUrlMap.put("image_url", urlMap);
content.add(imgUrlMap);
}
//添加视频参数
if (videoParams != null && !videoParams.isEmpty()) {
//如果有文本参数就加在后面
if (!textMap.isEmpty()) {
final int textIndex = content.indexOf(textMap);
final StringBuilder textBuilder = new StringBuilder(text);
for (final DoubaoCommon.DoubaoVideo videoParam : videoParams) {
textBuilder.append(" ").append(videoParam.getType()).append(" ").append(videoParam.getValue());
}
textMap.put("type", "text");
textMap.put("text", textBuilder.toString());
if (textIndex != -1) {
content.set(textIndex, textMap);
} else {
content.add(textMap);
}
} else {
//如果没有文本参数就重新增加
final StringBuilder textBuilder = new StringBuilder();
for (final DoubaoCommon.DoubaoVideo videoParam : videoParams) {
textBuilder.append(videoParam.getType()).append(videoParam.getValue()).append(" ");
}
textMap.put("type", "text");
textMap.put("text", textBuilder.toString());
content.add(textMap);
}
}
paramMap.put("content", content);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return JSONUtil.toJsonStr(paramMap);
}
//构建文生图请求体
private String buildImagesGenerationsRequestBody(final String prompt) {
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("model", config.getModel());
paramMap.put("prompt", prompt);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return JSONUtil.toJsonStr(paramMap);
}
}

View File

@@ -21,4 +21,4 @@
* @since 6.0.0
*/
package org.dromara.hutool.ai.model.doubao;
package cn.hutool.v7.ai.model.doubao;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.dromara.hutool.ai.model.grok;
package cn.hutool.v7.ai.model.grok;
/**
* grok公共类

View File

@@ -14,10 +14,10 @@
* limitations under the License.
*/
package org.dromara.hutool.ai.model.grok;
package cn.hutool.v7.ai.model.grok;
import org.dromara.hutool.ai.Models;
import org.dromara.hutool.ai.core.BaseConfig;
import cn.hutool.v7.ai.Models;
import cn.hutool.v7.ai.core.BaseConfig;
/**
* Grok配置类初始化API接口地址设置默认的模型

View File

@@ -14,10 +14,10 @@
* limitations under the License.
*/
package org.dromara.hutool.ai.model.grok;
package cn.hutool.v7.ai.model.grok;
import org.dromara.hutool.ai.core.AIConfig;
import org.dromara.hutool.ai.core.AIServiceProvider;
import cn.hutool.v7.ai.core.AIConfig;
import cn.hutool.v7.ai.core.AIServiceProvider;
/**
* 创建Grok服务实现类

View File

@@ -0,0 +1,192 @@
/*
* Copyright (c) 2025 Hutool Team and hutool.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.hutool.v7.ai.model.grok;
import cn.hutool.v7.ai.core.AIService;
import cn.hutool.v7.ai.core.Message;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
/**
* grok支持的扩展接口
*
* @author elichow
* @since 6.0.0
*/
public interface GrokService extends AIService {
/**
* 创建消息回复
*
* @param prompt 题词
* @param maxToken 最大token
* @return AI回答
* @since 6.0.0
*/
default String message(String prompt, int maxToken){
// 定义消息结构
final List<Message> messages = new ArrayList<>();
messages.add(new Message("system", "You are a helpful assistant"));
messages.add(new Message("user", prompt));
return message(messages, maxToken);
}
/**
* 创建消息回复-SSE流式输出
*
* @param prompt 题词
* @param maxToken 最大token
* @param callback 流式数据回调函数
* @since 6.0.0
*/
default void message(String prompt, int maxToken, final Consumer<String> callback){
final List<Message> messages = new ArrayList<>();
messages.add(new Message("system", "You are a helpful assistant"));
messages.add(new Message("user", prompt));
message(messages, maxToken, callback);
}
/**
* 创建消息回复
*
* @param messages messages 由对话组成的消息列表。如系统人设,背景信息等,用户自定义的信息
* @param maxToken 最大token
* @return AI回答
* @since 6.0.0
*/
String message(List<Message> messages, int maxToken);
/**
* 创建消息回复-SSE流式输出
*
* @param messages messages 由对话组成的消息列表。如系统人设,背景信息等,用户自定义的信息
* @param maxToken 最大token
* @param callback 流式数据回调函数
* @since 6.0.0
*/
void message(List<Message> messages, int maxToken, final Consumer<String> callback);
/**
* 图像理解:模型会依据传入的图片信息以及问题,给出回复。
*
* @param prompt 题词
* @param images 图片列表/或者图片Base64编码图片列表(URI形式)
* @param detail 手动设置图片的质量取值范围high、low、auto,默认为auto
* @return AI回答
* @since 6.0.0
*/
String chatVision(String prompt, final List<String> images, String detail);
/**
* 图像理解-SSE流式输出
*
* @param prompt 题词
* @param images 图片列表/或者图片Base64编码图片列表(URI形式)
* @param detail 手动设置图片的质量取值范围high、low、auto,默认为auto
* @param callback 流式数据回调函数
* @since 6.0.0
*/
void chatVision(String prompt, final List<String> images, String detail,final Consumer<String> callback);
/**
* 图像理解:模型会依据传入的图片信息以及问题,给出回复。
*
* @param prompt 题词
* @param images 传入的图片列表地址/或者图片Base64编码图片列表(URI形式)
* @return AI回答
* @since 6.0.0
*/
default String chatVision(String prompt, final List<String> images) {
return chatVision(prompt, images, GrokCommon.GrokVision.AUTO.getDetail());
}
/**
* 图像理解:模型会依据传入的图片信息以及问题,给出回复。
*
* @param prompt 题词
* @param images 传入|的图片列表地址/或者图片Base64编码图片列表(URI形式)
* @param callback 流式数据回调函数
* @since 6.0.0
*/
default void chatVision(String prompt, final List<String> images, final Consumer<String> callback){
chatVision(prompt, images, GrokCommon.GrokVision.AUTO.getDetail(), callback);
}
/**
* 列出所有model列表
*
* @return model列表
* @since 6.0.0
*/
String models();
/**
* 获取模型信息
*
* @param modelId model ID
* @return model信息
* @since 6.0.0
*/
String getModel(String modelId);
/**
* 列出所有语言model
*
* @return languageModel列表
* @since 6.0.0
*/
String languageModels();
/**
* 获取语言模型信息
*
* @param modelId model ID
* @return model信息
* @since 6.0.0
*/
String getLanguageModel(String modelId);
/**
* 分词:可以将文本转换为模型可理解的 token 信息
*
* @param text 需要分词的内容
* @return 分词结果
* @since 6.0.0
*/
String tokenizeText(String text);
/**
* 从延迟对话中获取结果
*
* @param requestId 延迟对话中的延迟请求ID
* @return AI回答
* @since 6.0.0
*/
String deferredCompletion(String requestId);
/**
* 文生图
* 请设置config中model为支持图片功能的模型目前支持GROK_2_IMAGE
*
* @param prompt 题词
* @return 包含生成图片的url
* @since 6.0.0
*/
String imagesGenerations(String prompt);
}

View File

@@ -14,18 +14,20 @@
* limitations under the License.
*/
package org.dromara.hutool.ai.model.grok;
package cn.hutool.v7.ai.model.grok;
import org.dromara.hutool.ai.core.AIConfig;
import org.dromara.hutool.ai.core.BaseAIService;
import org.dromara.hutool.ai.core.Message;
import org.dromara.hutool.http.client.Response;
import org.dromara.hutool.json.JSONUtil;
import cn.hutool.v7.ai.core.AIConfig;
import cn.hutool.v7.ai.core.BaseAIService;
import cn.hutool.v7.ai.core.Message;
import cn.hutool.v7.core.thread.ThreadUtil;
import cn.hutool.v7.http.client.Response;
import cn.hutool.v7.json.JSONUtil;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
/**
* Grok服务AI具体功能的实现
@@ -47,21 +49,14 @@ public class GrokServiceImpl extends BaseAIService implements GrokService {
private final String TOKENIZE_TEXT = "/tokenize-text";
//获取延迟对话
private final String DEFERRED_COMPLETION = "/chat/deferred-completion";
//文生图
private final String IMAGES_GENERATIONS = "/images/generations";
public GrokServiceImpl(final AIConfig config) {
//初始化grok客户端
super(config);
}
@Override
public String chat(String prompt) {
// 定义消息结构
final List<Message> messages = new ArrayList<>();
messages.add(new Message("system", "You are a helpful assistant"));
messages.add(new Message("user", prompt));
return chat(messages);
}
@Override
public String chat(final List<Message> messages) {
String paramJson = buildChatRequestBody(messages);
@@ -70,16 +65,24 @@ public class GrokServiceImpl extends BaseAIService implements GrokService {
}
@Override
public String message(String prompt, int maxToken) {
// 定义消息结构
final List<Message> messages = new ArrayList<>();
messages.add(new Message("system", "You are a helpful assistant"));
messages.add(new Message("user", prompt));
public void chat(List<Message> messages, Consumer<String> callback) {
Map<String, Object> paramMap = buildChatStreamRequestBody(messages);
ThreadUtil.newThread(() -> sendPostStream(CHAT_ENDPOINT, paramMap, callback::accept), "grok-chat-sse").start();
}
@Override
public String message(final List<Message> messages, int maxToken) {
String paramJson = buildMessageRequestBody(messages, maxToken);
Response response = sendPost(MESSAGES, paramJson);
return response.bodyStr();
}
@Override
public void message(List<Message> messages, int maxToken, final Consumer<String> callback) {
Map<String, Object> paramMap = buildMessageStreamRequestBody(messages, maxToken);
ThreadUtil.newThread(() -> sendPostStream(MESSAGES, paramMap, callback::accept), "grok-message-sse").start();
}
@Override
public String chatVision(String prompt, final List<String> images, String detail) {
String paramJson = buildChatVisionRequestBody(prompt, images, detail);
@@ -87,6 +90,12 @@ public class GrokServiceImpl extends BaseAIService implements GrokService {
return response.bodyStr();
}
@Override
public void chatVision(String prompt, List<String> images, String detail, Consumer<String> callback) {
Map<String, Object> paramMap = buildChatVisionStreamRequestBody(prompt, images, detail);
ThreadUtil.newThread(() -> sendPostStream(CHAT_ENDPOINT, paramMap, callback::accept), "grok-chatVision-sse").start();
}
@Override
public String models() {
Response response = sendGet(MODELS_ENDPOINT);
@@ -124,6 +133,13 @@ public class GrokServiceImpl extends BaseAIService implements GrokService {
return response.bodyStr();
}
@Override
public String imagesGenerations(String prompt) {
String paramJson = buildImagesGenerationsRequestBody(prompt);
Response response = sendPost(IMAGES_GENERATIONS, paramJson);
return response.bodyStr();
}
// 构建chat请求体
private String buildChatRequestBody(final List<Message> messages) {
//使用JSON工具
@@ -136,6 +152,18 @@ public class GrokServiceImpl extends BaseAIService implements GrokService {
return JSONUtil.toJsonStr(paramMap);
}
private Map<String, Object> buildChatStreamRequestBody(final List<Message> messages) {
//使用JSON工具
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("stream", true);
paramMap.put("model", config.getModel());
paramMap.put("messages", messages);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return paramMap;
}
//构建chatVision请求体
private String buildChatVisionRequestBody(String prompt, final List<String> images, String detail) {
// 定义消息结构
@@ -167,6 +195,37 @@ public class GrokServiceImpl extends BaseAIService implements GrokService {
return JSONUtil.toJsonStr(paramMap);
}
private Map<String, Object> buildChatVisionStreamRequestBody(String prompt, final List<String> images, String detail) {
// 定义消息结构
final List<Message> messages = new ArrayList<>();
final List<Object> content = new ArrayList<>();
final Map<String, String> contentMap = new HashMap<>();
contentMap.put("type", "text");
contentMap.put("text", prompt);
content.add(contentMap);
for (String img : images) {
HashMap<String, Object> imgUrlMap = new HashMap<>();
imgUrlMap.put("type", "image_url");
HashMap<String, String> urlMap = new HashMap<>();
urlMap.put("url", img);
urlMap.put("detail", detail);
imgUrlMap.put("image_url", urlMap);
content.add(imgUrlMap);
}
messages.add(new Message("user", content));
//使用JSON工具
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("stream", true);
paramMap.put("model", config.getModel());
paramMap.put("messages", messages);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return paramMap;
}
//构建消息回复请求体
private String buildMessageRequestBody(final List<Message> messages, int maxToken) {
final Map<String, Object> paramMap = new HashMap<>();
@@ -179,6 +238,18 @@ public class GrokServiceImpl extends BaseAIService implements GrokService {
return JSONUtil.toJsonStr(paramMap);
}
private Map<String, Object> buildMessageStreamRequestBody(final List<Message> messages, int maxToken) {
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("stream", true);
paramMap.put("model", config.getModel());
paramMap.put("messages", messages);
paramMap.put("max_tokens", maxToken);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return paramMap;
}
//构建分词请求体
private String buildTokenizeRequestBody(String text) {
//使用JSON工具
@@ -190,4 +261,15 @@ public class GrokServiceImpl extends BaseAIService implements GrokService {
return JSONUtil.toJsonStr(paramMap);
}
//构建文生图请求体
private String buildImagesGenerationsRequestBody(String prompt) {
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("model", config.getModel());
paramMap.put("prompt", prompt);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return JSONUtil.toJsonStr(paramMap);
}
}

View File

@@ -21,4 +21,4 @@
* @since 6.0.0
*/
package org.dromara.hutool.ai.model.grok;
package cn.hutool.v7.ai.model.grok;

View File

@@ -14,35 +14,18 @@
* limitations under the License.
*/
package org.dromara.hutool.ai.model.doubao;
package cn.hutool.v7.ai.model.hutool;
/**
* doubao公共类
* hutool公共类
*
* @author elichow
* @since 6.0.0
*/
public class DoubaoCommon {
public class HutoolCommon {
//doubao上下文缓存参数
public enum DoubaoContext {
SESSION("session"),
COMMON_PREFIX("common_prefix");
private final String mode;
DoubaoContext(String mode) {
this.mode = mode;
}
public String getMode() {
return mode;
}
}
//doubao视觉参数
public enum DoubaoVision {
//hutool视觉参数
public enum HutoolVision {
AUTO("auto"),
LOW("low"),
@@ -50,7 +33,7 @@ public class DoubaoCommon {
private final String detail;
DoubaoVision(String detail) {
HutoolVision(String detail) {
this.detail = detail;
}
@@ -59,8 +42,32 @@ public class DoubaoCommon {
}
}
//doubao视频生成参数
public enum DoubaoVideo {
//hutool音频参数
public enum HutoolSpeech {
ALLOY("alloy"),
ASH("ash"),
CORAL("coral"),
ECHO("echo"),
FABLE("fable"),
ONYX("onyx"),
NOVA("nova"),
SAGE("sage"),
SHIMMER("shimmer");
private final String voice;
HutoolSpeech(String voice) {
this.voice = voice;
}
public String getVoice() {
return voice;
}
}
//hutool视频生成参数
public enum HutoolVideo {
//宽高比例
RATIO_16_9("--rt", "16:9"),//[1280, 720]
@@ -87,7 +94,7 @@ public class DoubaoCommon {
private final String type;
private final Object value;
DoubaoVideo(String type, Object value) {
HutoolVideo(String type, Object value) {
this.type = type;
this.value = value;
}
@@ -108,4 +115,5 @@ public class DoubaoCommon {
}
}
}

View File

@@ -14,36 +14,36 @@
* limitations under the License.
*/
package org.dromara.hutool.ai.model.doubao;
package cn.hutool.v7.ai.model.hutool;
import org.dromara.hutool.ai.Models;
import org.dromara.hutool.ai.core.BaseConfig;
import cn.hutool.v7.ai.Models;
import cn.hutool.v7.ai.core.BaseConfig;
/**
* Doubao配置类初始化API接口地址设置默认的模型
* Hutool配置类初始化API接口地址设置默认的模型
*
* @author elichow
* @since 6.0.0
*/
public class DoubaoConfig extends BaseConfig {
public class HutoolConfig extends BaseConfig {
private final String API_URL = "https://ark.cn-beijing.volces.com/api/v3";
private final String API_URL = "https://api.hutool.cn/ai/api";
private final String DEFAULT_MODEL = Models.Doubao.DOUBAO_1_5_LITE_32K.getModel();
private final String DEFAULT_MODEL = Models.Hutool.HUTOOL.getModel();
public DoubaoConfig() {
public HutoolConfig() {
setApiUrl(API_URL);
setModel(DEFAULT_MODEL);
}
public DoubaoConfig(String apiKey) {
public HutoolConfig(String apiKey) {
this();
setApiKey(apiKey);
}
@Override
public String getModelName() {
return "doubao";
return "hutool";
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (c) 2025 Hutool Team and hutool.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.hutool.v7.ai.model.hutool;
import cn.hutool.v7.ai.core.AIConfig;
import cn.hutool.v7.ai.core.AIServiceProvider;
/**r
* 创建Hutool服务实现类
*
* @author elichow
* @since 6.0.0
*/
public class HutoolProvider implements AIServiceProvider {
@Override
public String getServiceName() {
return "hutool";
}
@Override
public HutoolService create(final AIConfig config) {
return new HutoolServiceImpl(config);
}
}

View File

@@ -0,0 +1,170 @@
/*
* Copyright (c) 2025 Hutool Team and hutool.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.hutool.v7.ai.model.hutool;
import cn.hutool.v7.ai.core.AIService;
import java.io.File;
import java.io.InputStream;
import java.util.List;
import java.util.function.Consumer;
/**
* hutool支持的扩展接口
*
* @author elichow
* @since 6.0.0
*/
public interface HutoolService extends AIService {
/**
* 图像理解:模型会依据传入的图片信息以及问题,给出回复。
*
* @param prompt 题词
* @param images 图片列表/或者图片Base64编码图片列表(URI形式)
* @param detail 手动设置图片的质量取值范围high、low、auto,默认为auto
* @return AI回答
* @since 6.0.0
*/
String chatVision(String prompt, final List<String> images, String detail);
/**
* 图像理解-SSE流式输出
*
* @param prompt 题词
* @param images 图片列表/或者图片Base64编码图片列表(URI形式)
* @param detail 手动设置图片的质量取值范围high、low、auto,默认为auto
* @param callback 流式数据回调函数
* @since 6.0.0
*/
void chatVision(String prompt, final List<String> images, String detail,final Consumer<String> callback);
/**
* 图像理解:模型会依据传入的图片信息以及问题,给出回复。
*
* @param prompt 题词
* @param images 传入的图片列表地址/或者图片Base64编码图片列表(URI形式)
* @return AI回答
* @since 6.0.0
*/
default String chatVision(String prompt, final List<String> images) {
return chatVision(prompt, images, HutoolCommon.HutoolVision.AUTO.getDetail());
}
/**
* 图像理解:模型会依据传入的图片信息以及问题,给出回复。
*
* @param prompt 题词
* @param images 传入|的图片列表地址/或者图片Base64编码图片列表(URI形式)
* @param callback 流式数据回调函数
* @since 6.0.0
*/
default void chatVision(String prompt, final List<String> images, final Consumer<String> callback){
chatVision(prompt, images, HutoolCommon.HutoolVision.AUTO.getDetail(), callback);
}
/**
* 分词:可以将文本转换为模型可理解的 token 信息
*
* @param text 需要分词的内容
* @return 分词结果
* @since 6.0.0
*/
String tokenizeText(String text);
/**
* 文生图
*
* @param prompt 题词
* @return 包含生成图片的url
* @since 6.0.0
*/
String imagesGenerations(String prompt);
/**
* 图文向量化:仅支持单一文本、单张图片或文本与图片的组合输入(即一段文本 + 一张图片),暂不支持批量文本 / 图片的同时处理
*
* @param text 需要向量化的内容
* @param image 需要向量化的图片地址/或者图片Base64编码图片(URI形式)
* @return 处理后的向量信息
* @since 6.0.0
*/
String embeddingVision(String text, String image);
/**
* TTS文本转语音
*
* @param input 需要转成语音的文本
* @param voice AI的音色
* @return 返回的音频mp3文件流
* @since 6.0.0
*/
InputStream tts(String input, final HutoolCommon.HutoolSpeech voice);
/**
* TTS文本转语音
*
* @param input 需要转成语音的文本
* @return 返回的音频mp3文件流
* @since 6.0.0
*/
default InputStream tts(String input) {
return tts(input, HutoolCommon.HutoolSpeech.ALLOY);
}
/**
* STT音频转文本
*
* @param file 需要转成文本的音频文件
* @return 返回的文本内容
* @since 6.0.0
*/
String stt(final File file);
/**
* 创建视频生成任务
*
* @param text 文本提示词
* @param image 图片/或者图片Base64编码图片(URI形式)
* @param videoParams 视频参数列表
* @return 生成任务id
* @since 6.0.0
*/
String videoTasks(String text, String image, final List<HutoolCommon.HutoolVideo> videoParams);
/**
* 创建视频生成任务
*
* @param text 文本提示词
* @param image 图片/或者图片Base64编码图片(URI形式)
* @return 生成任务id
* @since 6.0.0
*/
default String videoTasks(String text, String image) {
return videoTasks(text, image, null);
}
/**
* 查询视频生成任务信息
*
* @param taskId 通过创建生成视频任务返回的生成任务id
* @return 生成任务信息
* @since 6.0.0
*/
String getVideoTasksInfo(String taskId);
}

View File

@@ -14,61 +14,53 @@
* limitations under the License.
*/
package org.dromara.hutool.ai.model.doubao;
package cn.hutool.v7.ai.model.hutool;
import org.dromara.hutool.ai.core.AIConfig;
import org.dromara.hutool.ai.core.BaseAIService;
import org.dromara.hutool.ai.core.Message;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.http.client.Response;
import org.dromara.hutool.json.JSONUtil;
import cn.hutool.v7.ai.AIException;
import cn.hutool.v7.ai.core.AIConfig;
import cn.hutool.v7.ai.core.BaseAIService;
import cn.hutool.v7.ai.core.Message;
import cn.hutool.v7.core.text.StrUtil;
import cn.hutool.v7.core.thread.ThreadUtil;
import cn.hutool.v7.http.client.Response;
import cn.hutool.v7.json.JSONUtil;
import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
/**
* Doubao服务AI具体功能的实现
* Hutool服务AI具体功能的实现
*
* @author elichow
* @since 6.0.0
*/
public class DoubaoServiceImpl extends BaseAIService implements DoubaoService {
public class HutoolServiceImpl extends BaseAIService implements HutoolService {
//对话
//对话补全
private final String CHAT_ENDPOINT = "/chat/completions";
//文本向量化
private final String EMBEDDING_TEXT = "/embeddings";
//分词
private final String TOKENIZE_TEXT = "/tokenize/text";
//文生图
private final String IMAGES_GENERATIONS = "/images/generations";
//图文向量化
private final String EMBEDDING_VISION = "/embeddings/multimodal";
//应用bots
private final String BOTS_CHAT = "/bots/chat/completions";
//分词
private final String TOKENIZATION = "/tokenization";
//批量推理chat
private final String BATCH_CHAT = "/batch/chat/completions";
//创建上下文缓存
private final String CREATE_CONTEXT = "/context/create";
//上下文缓存对话
private final String CHAT_CONTEXT = "/context/chat/completions";
//文本转语音
private final String TTS = "/audio/tts";
//语音转文本
private final String STT = "/audio/stt";
//创建视频生成任务
private final String CREATE_VIDEO = "/contents/generations/tasks";
private final String CREATE_VIDEO = "/video/generations";
public DoubaoServiceImpl(final AIConfig config) {
//初始化doubao客户端
public HutoolServiceImpl(final AIConfig config) {
//初始化hutool客户端
super(config);
}
@Override
public String chat(String prompt) {
// 定义消息结构
final List<Message> messages = new ArrayList<>();
messages.add(new Message("system", "You are a helpful assistant"));
messages.add(new Message("user", prompt));
return chat(messages);
}
@Override
public String chat(final List<Message> messages) {
String paramJson = buildChatRequestBody(messages);
@@ -76,6 +68,12 @@ public class DoubaoServiceImpl extends BaseAIService implements DoubaoService {
return response.bodyStr();
}
@Override
public void chat(List<Message> messages,Consumer<String> callback) {
Map<String, Object> paramMap = buildChatStreamRequestBody(messages);
ThreadUtil.newThread(() -> sendPostStream(CHAT_ENDPOINT, paramMap, callback::accept), "hutool-chat-sse").start();
}
@Override
public String chatVision(String prompt, final List<String> images, String detail) {
String paramJson = buildChatVisionRequestBody(prompt, images, detail);
@@ -84,7 +82,64 @@ public class DoubaoServiceImpl extends BaseAIService implements DoubaoService {
}
@Override
public String videoTasks(String text, String image, final List<DoubaoCommon.DoubaoVideo> videoParams) {
public void chatVision(String prompt, List<String> images, String detail, Consumer<String> callback) {
Map<String, Object> paramMap = buildChatVisionStreamRequestBody(prompt, images, detail);
System.out.println(JSONUtil.toJsonStr(paramMap));
ThreadUtil.newThread(() -> sendPostStream(CHAT_ENDPOINT, paramMap, callback::accept), "hutool-chatVision-sse").start();
}
@Override
public String tokenizeText(String text) {
String paramJson = buildTokenizeRequestBody(text);
Response response = sendPost(TOKENIZE_TEXT, paramJson);
return response.bodyStr();
}
@Override
public String imagesGenerations(String prompt) {
String paramJson = buildImagesGenerationsRequestBody(prompt);
Response response = sendPost(IMAGES_GENERATIONS, paramJson);
return response.bodyStr();
}
@Override
public String embeddingVision(String text, String image) {
String paramJson = buildEmbeddingVisionRequestBody(text, image);
Response response = sendPost(EMBEDDING_VISION, paramJson);
return response.bodyStr();
}
@Override
public InputStream tts(String input, final HutoolCommon.HutoolSpeech voice) {
try {
String paramJson = buildTTSRequestBody(input, voice.getVoice());
Response response = sendPost(TTS, paramJson);
// 检查响应内容类型
String contentType = response.header("Content-Type");
if (contentType != null && contentType.startsWith("application/json")) {
// 如果是JSON响应说明有错误
String errorBody = response.bodyStr();
throw new AIException("TTS请求失败: " + errorBody);
}
// 默认返回音频流
return response.bodyStream();
} catch (Exception e) {
throw new AIException("TTS处理失败: " + e.getMessage(), e);
}
}
@Override
public String stt(final File file) {
final Map<String, Object> paramMap = buildSTTRequestBody(file);
Response response = sendFormData(STT, paramMap);
return response.bodyStr();
}
@Override
public String videoTasks(String text, String image, final List<HutoolCommon.HutoolVideo> videoParams) {
String paramJson = buildGenerationsTasksRequestBody(text, image, videoParams);
Response response = sendPost(CREATE_VIDEO, paramJson);
return response.bodyStr();
@@ -97,72 +152,6 @@ public class DoubaoServiceImpl extends BaseAIService implements DoubaoService {
}
@Override
public String embeddingText(String[] input) {
String paramJson = buildEmbeddingTextRequestBody(input);
Response response = sendPost(EMBEDDING_TEXT, paramJson);
return response.bodyStr();
}
@Override
public String embeddingVision(String text, String image) {
String paramJson = buildEmbeddingVisionRequestBody(text, image);
Response response = sendPost(EMBEDDING_VISION, paramJson);
return response.bodyStr();
}
@Override
public String botsChat(final List<Message> messages) {
String paramJson = buildBotsChatRequestBody(messages);
Response response = sendPost(BOTS_CHAT, paramJson);
return response.bodyStr();
}
@Override
public String tokenization(String[] text) {
String paramJson = buildTokenizationRequestBody(text);
Response response = sendPost(TOKENIZATION, paramJson);
return response.bodyStr();
}
@Override
public String batchChat(String prompt) {
// 定义消息结构
final List<Message> messages = new ArrayList<>();
messages.add(new Message("system", "You are a helpful assistant"));
messages.add(new Message("user", prompt));
return batchChat(messages);
}
@Override
public String batchChat(final List<Message> messages) {
String paramJson = buildBatchChatRequestBody(messages);
Response response = sendPost(BATCH_CHAT, paramJson);
return response.bodyStr();
}
@Override
public String createContext(final List<Message> messages, String mode) {
String paramJson = buildCreateContextRequest(messages, mode);
Response response = sendPost(CREATE_CONTEXT, paramJson);
return response.bodyStr();
}
@Override
public String chatContext(String prompt, String contextId) {
// 定义消息结构
final List<Message> messages = new ArrayList<>();
messages.add(new Message("user", prompt));
return chatContext(messages, contextId);
}
@Override
public String chatContext(final List<Message> messages, String contextId) {
String paramJson = buildChatContentRequestBody(messages, contextId);
Response response = sendPost(CHAT_CONTEXT, paramJson);
return response.bodyStr();
}
// 构建chat请求体
private String buildChatRequestBody(final List<Message> messages) {
//使用JSON工具
@@ -175,6 +164,18 @@ public class DoubaoServiceImpl extends BaseAIService implements DoubaoService {
return JSONUtil.toJsonStr(paramMap);
}
private Map<String, Object> buildChatStreamRequestBody(final List<Message> messages) {
//使用JSON工具
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("stream", true);
paramMap.put("model", config.getModel());
paramMap.put("messages", messages);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return paramMap;
}
//构建chatVision请求体
private String buildChatVisionRequestBody(String prompt, final List<String> images, String detail) {
// 定义消息结构
@@ -206,14 +207,58 @@ public class DoubaoServiceImpl extends BaseAIService implements DoubaoService {
return JSONUtil.toJsonStr(paramMap);
}
//构建文本向量化请求体
private String buildEmbeddingTextRequestBody(String[] input) {
private Map<String, Object> buildChatVisionStreamRequestBody(String prompt, final List<String> images, String detail) {
// 定义消息结构
final List<Message> messages = new ArrayList<>();
final List<Object> content = new ArrayList<>();
final Map<String, String> contentMap = new HashMap<>();
contentMap.put("type", "text");
contentMap.put("text", prompt);
content.add(contentMap);
for (String img : images) {
HashMap<String, Object> imgUrlMap = new HashMap<>();
imgUrlMap.put("type", "image_url");
HashMap<String, String> urlMap = new HashMap<>();
urlMap.put("url", img);
urlMap.put("detail", detail);
imgUrlMap.put("image_url", urlMap);
content.add(imgUrlMap);
}
messages.add(new Message("user", content));
//使用JSON工具
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("stream", true);
paramMap.put("model", config.getModel());
paramMap.put("messages", messages);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return paramMap;
}
//构建分词请求体
private String buildTokenizeRequestBody(String text) {
//使用JSON工具
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("model", config.getModel());
paramMap.put("input", input);
paramMap.put("text", text);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return JSONUtil.toJsonStr(paramMap);
}
//构建文生图请求体
private String buildImagesGenerationsRequestBody(String prompt) {
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("model", config.getModel());
paramMap.put("prompt", prompt);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return JSONUtil.toJsonStr(paramMap);
}
@@ -244,55 +289,36 @@ public class DoubaoServiceImpl extends BaseAIService implements DoubaoService {
paramMap.put("input", input);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
System.out.println(JSONUtil.toJsonStr(paramMap));
return JSONUtil.toJsonStr(paramMap);
}
//构建应用chat请求体
private String buildBotsChatRequestBody(final List<Message> messages) {
return buildChatRequestBody(messages);
}
//构建分词请求体
private String buildTokenizationRequestBody(String[] text) {
//构建TTS请求体
private String buildTTSRequestBody(String input, String voice) {
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("model", config.getModel());
paramMap.put("text", text);
return JSONUtil.toJsonStr(paramMap);
}
//构建批量推理chat请求体
private String buildBatchChatRequestBody(final List<Message> messages) {
return buildChatRequestBody(messages);
}
//构建创建上下文缓存请求体
private String buildCreateContextRequest(final List<Message> messages, String mode) {
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("messages", messages);
paramMap.put("model", config.getModel());
paramMap.put("mode", mode);
paramMap.put("input", input);
paramMap.put("voice", voice);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return JSONUtil.toJsonStr(paramMap);
}
//构建上下文缓存对话请求体
private String buildChatContentRequestBody(final List<Message> messages, String contextId) {
//使用JSON工具
//构建STT请求体
private Map<String, Object> buildSTTRequestBody(final File file) {
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("model", config.getModel());
paramMap.put("messages", messages);
paramMap.put("context_id", contextId);
paramMap.put("file", file);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return JSONUtil.toJsonStr(paramMap);
return paramMap;
}
//构建创建视频任务请求体
private String buildGenerationsTasksRequestBody(String text, String image, final List<DoubaoCommon.DoubaoVideo> videoParams) {
private String buildGenerationsTasksRequestBody(String text, String image, final List<HutoolCommon.HutoolVideo> videoParams) {
//使用JSON工具
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("model", config.getModel());
@@ -306,7 +332,7 @@ public class DoubaoServiceImpl extends BaseAIService implements DoubaoService {
content.add(textMap);
}
//添加图片参数
if (!StrUtil.isNotBlank(image)) {
if (!StrUtil.isBlank(image)) {
final Map<String, Object> imgUrlMap = new HashMap<>();
imgUrlMap.put("type", "image_url");
final Map<String, String> urlMap = new HashMap<>();
@@ -321,7 +347,7 @@ public class DoubaoServiceImpl extends BaseAIService implements DoubaoService {
if (textMap != null && !textMap.isEmpty()) {
int textIndex = content.indexOf(textMap);
StringBuilder textBuilder = new StringBuilder(text);
for (DoubaoCommon.DoubaoVideo videoParam : videoParams) {
for (HutoolCommon.HutoolVideo videoParam : videoParams) {
textBuilder.append(" ").append(videoParam.getType()).append(" ").append(videoParam.getValue());
}
textMap.put("type", "text");
@@ -335,7 +361,7 @@ public class DoubaoServiceImpl extends BaseAIService implements DoubaoService {
} else {
//如果没有文本参数就重新增加
StringBuilder textBuilder = new StringBuilder();
for (DoubaoCommon.DoubaoVideo videoParam : videoParams) {
for (HutoolCommon.HutoolVideo videoParam : videoParams) {
textBuilder.append(videoParam.getType()).append(videoParam.getValue()).append(" ");
}
textMap.put("type", "text");
@@ -347,7 +373,7 @@ public class DoubaoServiceImpl extends BaseAIService implements DoubaoService {
paramMap.put("content", content);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
System.out.println(JSONUtil.toJsonStr(paramMap));
return JSONUtil.toJsonStr(paramMap);
}

View File

@@ -0,0 +1,24 @@
/*
* Copyright (c) 2025 Hutool Team and hutool.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* 对hutool的封装实现
*
* @author elichow
* @since 6.0.0
*/
package cn.hutool.v7.ai.model.hutool;

View File

@@ -0,0 +1,76 @@
/*
* Copyright (c) 2025 Hutool Team and hutool.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.hutool.v7.ai.model.ollama;
/**
* Ollama公共类
*
* @author yangruoyu-yumeisoft
* @since 5.8.40
*/
public class OllamaCommon {
/**
* Ollama模型格式枚举
*/
public enum OllamaFormat {
/**
* JSON格式
*/
JSON("json"),
/**
* 无格式
*/
NONE("");
private final String format;
OllamaFormat(String format) {
this.format = format;
}
public String getFormat() {
return format;
}
}
/**
* Ollama选项常量
*/
public static class Options {
/**
* 温度参数
*/
public static final String TEMPERATURE = "temperature";
/**
* top_p参数
*/
public static final String TOP_P = "top_p";
/**
* top_k参数
*/
public static final String TOP_K = "top_k";
/**
* 最大token数
*/
public static final String NUM_PREDICT = "num_predict";
/**
* 随机种子
*/
public static final String SEED = "seed";
}
}

View File

@@ -14,36 +14,42 @@
* limitations under the License.
*/
package org.dromara.hutool.ai.model.deepseek;
package cn.hutool.v7.ai.model.ollama;
import org.dromara.hutool.ai.Models;
import org.dromara.hutool.ai.core.BaseConfig;
import cn.hutool.v7.ai.Models;
import cn.hutool.v7.ai.core.BaseConfig;
/**
* DeepSeek配置类初始化API接口地址设置默认的模型
* Ollama配置类初始化API接口地址设置默认的模型
*
* @author elichow
* @since 6.0.0
* @author yangruoyu-yumeisoft
* @since 5.8.40
*/
public class DeepSeekConfig extends BaseConfig {
public class OllamaConfig extends BaseConfig {
private final String API_URL = "https://api.deepseek.com";
private final String API_URL = "http://localhost:11434";
private final String DEFAULT_MODEL = Models.DeepSeek.DEEPSEEK_CHAT.getModel();
private final String DEFAULT_MODEL = Models.Ollama.QWEN3_32B.getModel();
public DeepSeekConfig() {
public OllamaConfig() {
setApiUrl(API_URL);
setModel(DEFAULT_MODEL);
}
public DeepSeekConfig(String apiKey) {
public OllamaConfig(String apiUrl) {
this();
setApiKey(apiKey);
setApiUrl(apiUrl);
}
public OllamaConfig(String apiUrl, String model) {
this();
setApiUrl(apiUrl);
setModel(model);
}
@Override
public String getModelName() {
return "deepSeek";
return "ollama";
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (c) 2025 Hutool Team and hutool.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.hutool.v7.ai.model.ollama;
import cn.hutool.v7.ai.core.AIConfig;
import cn.hutool.v7.ai.core.AIServiceProvider;
/**
* 创建Ollama服务实现类
*
* @author yangruoyu-yumeisoft
* @since 5.8.40
*/
public class OllamaProvider implements AIServiceProvider {
@Override
public String getServiceName() {
return "ollama";
}
@Override
public OllamaService create(final AIConfig config) {
return new OllamaServiceImpl(config);
}
}

View File

@@ -0,0 +1,151 @@
/*
* Copyright (c) 2025 Hutool Team and hutool.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.hutool.v7.ai.model.ollama;
import cn.hutool.v7.ai.core.AIService;
import cn.hutool.v7.ai.core.Message;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
/**
* Ollama特有的功能
*
* @author yangruoyu-yumeisoft
* @since 5.8.40
*/
public interface OllamaService extends AIService {
/**
* 生成文本补全
*
* @param prompt 输入提示
* @return AI回答
* @since 5.8.40
*/
String generate(String prompt);
/**
* 生成文本补全-SSE流式输出
*
* @param prompt 输入提示
* @param callback 流式数据回调函数
* @since 5.8.40
*/
void generate(String prompt, Consumer<String> callback);
/**
* 生成文本补全(带选项)
*
* @param prompt 输入提示
* @param format 响应格式
* @return AI回答
* @since 5.8.40
*/
String generate(String prompt, String format);
/**
* 生成文本补全(带选项)-SSE流式输出
*
* @param prompt 输入提示
* @param format 响应格式
* @param callback 流式数据回调函数
* @since 5.8.40
*/
void generate(String prompt, String format, Consumer<String> callback);
/**
* 生成文本嵌入向量
*
* @param prompt 输入文本
* @return 嵌入向量结果
* @since 5.8.40
*/
String embeddings(String prompt);
/**
* 列出本地可用的模型
*
* @return 模型列表
* @since 5.8.40
*/
String listModels();
/**
* 显示模型信息
*
* @param modelName 模型名称
* @return 模型信息
* @since 5.8.40
*/
String showModel(String modelName);
/**
* 拉取模型
*
* @param modelName 模型名称
* @return 拉取结果
* @since 5.8.40
*/
String pullModel(String modelName);
/**
* 删除模型
*
* @param modelName 模型名称
* @return 删除结果
* @since 5.8.40
*/
String deleteModel(String modelName);
/**
* 复制模型
*
* @param source 源模型名称
* @param destination 目标模型名称
* @return 复制结果
* @since 5.8.40
*/
String copyModel(String source, String destination);
/**
* 简化的对话方法
*
* @param prompt 对话题词
* @return AI回答
* @since 5.8.40
*/
default String chat(String prompt) {
final List<Message> messages = new ArrayList<>();
messages.add(new Message("user", prompt));
return chat(messages);
}
/**
* 简化的对话方法-SSE流式输出
*
* @param prompt 对话题词
* @param callback 流式数据回调函数
* @since 5.8.40
*/
default void chat(String prompt, Consumer<String> callback) {
final List<Message> messages = new ArrayList<>();
messages.add(new Message("user", prompt));
chat(messages, callback);
}
}

View File

@@ -0,0 +1,273 @@
/*
* Copyright (c) 2025 Hutool Team and hutool.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.hutool.v7.ai.model.ollama;
import cn.hutool.v7.ai.AIException;
import cn.hutool.v7.ai.core.AIConfig;
import cn.hutool.v7.ai.core.BaseAIService;
import cn.hutool.v7.ai.core.Message;
import cn.hutool.v7.core.bean.path.BeanPath;
import cn.hutool.v7.core.text.StrUtil;
import cn.hutool.v7.core.thread.ThreadUtil;
import cn.hutool.v7.http.client.Request;
import cn.hutool.v7.http.client.Response;
import cn.hutool.v7.http.meta.HeaderName;
import cn.hutool.v7.http.meta.Method;
import cn.hutool.v7.json.JSONObject;
import cn.hutool.v7.json.JSONUtil;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
/**
* Ollama服务AI具体功能的实现
*
* @author yangruoyu-yumeisoft
* @since 5.8.40
*/
public class OllamaServiceImpl extends BaseAIService implements OllamaService {
// 对话补全
private static final String CHAT_ENDPOINT = "/api/chat";
// 文本生成
private static final String GENERATE_ENDPOINT = "/api/generate";
// 文本嵌入
private static final String EMBEDDINGS_ENDPOINT = "/api/embeddings";
// 列出模型
private static final String LIST_MODELS_ENDPOINT = "/api/tags";
// 显示模型信息
private static final String SHOW_MODEL_ENDPOINT = "/api/show";
// 拉取模型
private static final String PULL_MODEL_ENDPOINT = "/api/pull";
// 删除模型
private static final String DELETE_MODEL_ENDPOINT = "/api/delete";
// 复制模型
private static final String COPY_MODEL_ENDPOINT = "/api/copy";
/**
* 构造函数
*
* @param config AI配置
*/
public OllamaServiceImpl(final AIConfig config) {
super(config);
}
@Override
public String chat(final List<Message> messages) {
final String paramJson = buildChatRequestBody(messages);
final Response response = sendPost(CHAT_ENDPOINT, paramJson);
final JSONObject responseJson = JSONUtil.parseObj(response.body());
final Object errorMessage = BeanPath.of("error").getValue(responseJson);
if(errorMessage!=null){
throw new RuntimeException(errorMessage.toString());
}
return BeanPath.of("message.content").getValue(responseJson).toString();
}
@Override
public void chat(final List<Message> messages, final Consumer<String> callback) {
final Map<String, Object> paramMap = buildChatStreamRequestBody(messages);
ThreadUtil.newThread(() -> sendPostStream(CHAT_ENDPOINT, paramMap, callback::accept), "ollama-chat-sse").start();
}
@Override
public String generate(final String prompt) {
final String paramJson = buildGenerateRequestBody(prompt, null);
final Response response = sendPost(GENERATE_ENDPOINT, paramJson);
return response.bodyStr();
}
@Override
public void generate(final String prompt, final Consumer<String> callback) {
final Map<String, Object> paramMap = buildGenerateStreamRequestBody(prompt, null);
ThreadUtil.newThread(() -> sendPostStream(GENERATE_ENDPOINT, paramMap, callback::accept), "ollama-generate-sse").start();
}
@Override
public String generate(final String prompt, final String format) {
final String paramJson = buildGenerateRequestBody(prompt, format);
final Response response = sendPost(GENERATE_ENDPOINT, paramJson);
return response.bodyStr();
}
@Override
public void generate(final String prompt, final String format, final Consumer<String> callback) {
final Map<String, Object> paramMap = buildGenerateStreamRequestBody(prompt, format);
ThreadUtil.newThread(() -> sendPostStream(GENERATE_ENDPOINT, paramMap, callback::accept), "ollama-generate-sse").start();
}
@Override
public String embeddings(final String prompt) {
final String paramJson = buildEmbeddingsRequestBody(prompt);
final Response response = sendPost(EMBEDDINGS_ENDPOINT, paramJson);
return response.bodyStr();
}
@Override
public String listModels() {
final Response response = sendGet(LIST_MODELS_ENDPOINT);
return response.bodyStr();
}
@Override
public String showModel(final String modelName) {
final String paramJson = buildShowModelRequestBody(modelName);
final Response response = sendPost(SHOW_MODEL_ENDPOINT, paramJson);
return response.bodyStr();
}
@Override
public String pullModel(final String modelName) {
final String paramJson = buildPullModelRequestBody(modelName);
final Response response = sendPost(PULL_MODEL_ENDPOINT, paramJson);
return response.bodyStr();
}
@Override
public String deleteModel(final String modelName) {
final String paramJson = buildDeleteModelRequestBody(modelName);
final Response response = sendDeleteRequest(DELETE_MODEL_ENDPOINT, paramJson);
return response.bodyStr();
}
@Override
public String copyModel(final String source, final String destination) {
final String paramJson = buildCopyModelRequestBody(source, destination);
final Response response = sendPost(COPY_MODEL_ENDPOINT, paramJson);
return response.bodyStr();
}
// 构建chat请求体
private String buildChatRequestBody(final List<Message> messages) {
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("stream",false);
paramMap.put("model", config.getModel());
paramMap.put("messages", messages);
// 合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return JSONUtil.toJsonStr(paramMap);
}
// 构建chatStream请求体
private Map<String, Object> buildChatStreamRequestBody(final List<Message> messages) {
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("stream", true);
paramMap.put("model", config.getModel());
paramMap.put("messages", messages);
// 合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return paramMap;
}
// 构建generate请求体
private String buildGenerateRequestBody(final String prompt, final String format) {
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("model", config.getModel());
paramMap.put("prompt", prompt);
if (StrUtil.isNotBlank(format)) {
paramMap.put("format", format);
}
// 合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return JSONUtil.toJsonStr(paramMap);
}
// 构建generateStream请求体
private Map<String, Object> buildGenerateStreamRequestBody(final String prompt, final String format) {
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("stream", true);
paramMap.put("model", config.getModel());
paramMap.put("prompt", prompt);
if (StrUtil.isNotBlank(format)) {
paramMap.put("format", format);
}
// 合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return paramMap;
}
// 构建embeddings请求体
private String buildEmbeddingsRequestBody(final String prompt) {
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("model", config.getModel());
paramMap.put("prompt", prompt);
// 合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return JSONUtil.toJsonStr(paramMap);
}
// 构建showModel请求体
private String buildShowModelRequestBody(final String modelName) {
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("name", modelName);
return JSONUtil.toJsonStr(paramMap);
}
// 构建pullModel请求体
private String buildPullModelRequestBody(final String modelName) {
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("name", modelName);
return JSONUtil.toJsonStr(paramMap);
}
// 构建deleteModel请求体
private String buildDeleteModelRequestBody(final String modelName) {
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("name", modelName);
return JSONUtil.toJsonStr(paramMap);
}
/**
* 发送DELETE请求
*
* @param endpoint 请求端点
* @param paramJson 请求参数JSON
* @return 响应结果
*/
private Response sendDeleteRequest(final String endpoint, final String paramJson) {
try {
return Request.of(config.getApiUrl() + endpoint)
.method(Method.DELETE)
.header(HeaderName.CONTENT_TYPE, "application/json")
.header(HeaderName.ACCEPT, "application/json")
.body(paramJson)
.send();
} catch (final Exception e) {
throw new AIException("Failed to send DELETE request: " + e.getMessage(), e);
}
}
// 构建copyModel请求体
private String buildCopyModelRequestBody(final String source, final String destination) {
final Map<String, Object> requestBody = new HashMap<>();
requestBody.put("source", source);
requestBody.put("destination", destination);
return JSONUtil.toJsonStr(requestBody);
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (c) 2025 Hutool Team and hutool.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* 对Ollama的封装实现.
* <p>
* 使用方法:
* // 创建AI服务
* <pre>{@code
* OllamaService aiService = AIServiceFactory.getAIService(
* new AIConfigBuilder(ModelName.OLLAMA.getValue())
* .setApiUrl("http://localhost:11434")
* .setModel("qwen2.5-coder:32b")
* .build(),
* OllamaService.class
* );
*
* // 构造上下文
* List<Message> messageList=new ArrayList<>();
* messageList.add(new Message("system","你是一个疯疯癫癫的机器人"));
* messageList.add(new Message("user","你能帮我做什么"));
*
* // 输出对话结果
* System.out.println(aiService.chat(messageList));
* }</pre>
*
* @author yangruoyu-yumeisoft
* @since 5.8.40
*/
package cn.hutool.v7.ai.model.ollama;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.dromara.hutool.ai.model.openai;
package cn.hutool.v7.ai.model.openai;
/**
* openai公共类

View File

@@ -14,10 +14,10 @@
* limitations under the License.
*/
package org.dromara.hutool.ai.model.openai;
package cn.hutool.v7.ai.model.openai;
import org.dromara.hutool.ai.Models;
import org.dromara.hutool.ai.core.BaseConfig;
import cn.hutool.v7.ai.Models;
import cn.hutool.v7.ai.core.BaseConfig;
/**
* openai配置类初始化API接口地址设置默认的模型

View File

@@ -14,10 +14,10 @@
* limitations under the License.
*/
package org.dromara.hutool.ai.model.openai;
package cn.hutool.v7.ai.model.openai;
import org.dromara.hutool.ai.core.AIConfig;
import org.dromara.hutool.ai.core.AIServiceProvider;
import cn.hutool.v7.ai.core.AIConfig;
import cn.hutool.v7.ai.core.AIServiceProvider;
/**
* 创建Openai服务实现类

View File

@@ -14,14 +14,16 @@
* limitations under the License.
*/
package org.dromara.hutool.ai.model.openai;
package cn.hutool.v7.ai.model.openai;
import org.dromara.hutool.ai.core.AIService;
import org.dromara.hutool.ai.core.Message;
import cn.hutool.v7.ai.core.AIService;
import cn.hutool.v7.ai.core.Message;
import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
/**
* openai支持的扩展接口
@@ -42,6 +44,17 @@ public interface OpenaiService extends AIService {
*/
String chatVision(String prompt, final List<String> images, String detail);
/**
* 图像理解-SSE流式输出
*
* @param prompt 题词
* @param images 图片列表/或者图片Base64编码图片列表(URI形式)
* @param detail 手动设置图片的质量取值范围highlowauto,默认为auto
* @param callback 流式数据回调函数
* @since 6.0.0
*/
void chatVision(String prompt, final List<String> images, String detail,final Consumer<String> callback);
/**
* 图像理解模型会依据传入的图片信息以及问题给出回复
*
@@ -54,6 +67,18 @@ public interface OpenaiService extends AIService {
return chatVision(prompt, images, OpenaiCommon.OpenaiVision.AUTO.getDetail());
}
/**
* 图像理解-SSE流式输出
*
* @param prompt 题词
* @param images 传入的图片列表地址/或者图片Base64编码图片列表(URI形式)
* @param callback 流式数据回调函数
* @since 6.0.0
*/
default void chatVision(String prompt, final List<String> images, final Consumer<String> callback){
chatVision(prompt, images, OpenaiCommon.OpenaiVision.AUTO.getDetail(), callback);
}
/**
* 文生图 请设置config中model为支持图片功能的模型 DALL·E系列
*
@@ -166,7 +191,28 @@ public interface OpenaiService extends AIService {
* @return AI回答
* @since 6.0.0
*/
String chatReasoning(String prompt, String reasoningEffort);
default String chatReasoning(String prompt, String reasoningEffort){
final List<Message> messages = new ArrayList<>();
messages.add(new Message("system", "You are a helpful assistant"));
messages.add(new Message("user", prompt));
return chatReasoning(messages, reasoningEffort);
}
/**
* 推理chat-SSE流式输出
* 支持o3-mini和o1
*
* @param prompt 对话题词
* @param reasoningEffort 推理程度
* @param callback 流式数据回调函数
* @since 6.0.0
*/
default void chatReasoning(String prompt, String reasoningEffort, final Consumer<String> callback){
final List<Message> messages = new ArrayList<>();
messages.add(new Message("system", "You are a helpful assistant"));
messages.add(new Message("user", prompt));
chatReasoning(messages, reasoningEffort, callback);
}
/**
* 推理chat
@@ -180,6 +226,18 @@ public interface OpenaiService extends AIService {
return chatReasoning(prompt, OpenaiCommon.OpenaiReasoning.MEDIUM.getEffort());
}
/**
* 推理chat-SSE流式输出
* 支持o3-mini和o1
*
* @param prompt 对话题词
* @param callback 流式数据回调函数
* @since 6.0.0
*/
default void chatReasoning(String prompt, final Consumer<String> callback) {
chatReasoning(prompt, OpenaiCommon.OpenaiReasoning.MEDIUM.getEffort(), callback);
}
/**
* 推理chat
* 支持o3-mini和o1
@@ -191,6 +249,17 @@ public interface OpenaiService extends AIService {
*/
String chatReasoning(final List<Message> messages, String reasoningEffort);
/**
* 推理chat-SSE流式输出
* 支持o3-mini和o1
*
* @param messages 消息列表
* @param reasoningEffort 推理程度
* @param callback 流式数据回调函数
* @since 6.0.0
*/
void chatReasoning(final List<Message> messages, String reasoningEffort, final Consumer<String> callback);
/**
* 推理chat
* 支持o3-mini和o1
@@ -203,4 +272,16 @@ public interface OpenaiService extends AIService {
return chatReasoning(messages, OpenaiCommon.OpenaiReasoning.MEDIUM.getEffort());
}
/**
* 推理chat-SSE流式输出
* 支持o3-mini和o1
*
* @param messages 消息列表
* @param callback 流式数据回调函数
* @since 6.0.0
*/
default void chatReasoning(final List<Message> messages, final Consumer<String> callback) {
chatReasoning(messages, OpenaiCommon.OpenaiReasoning.MEDIUM.getEffort(), callback);
}
}

View File

@@ -14,14 +14,15 @@
* limitations under the License.
*/
package org.dromara.hutool.ai.model.openai;
package cn.hutool.v7.ai.model.openai;
import org.dromara.hutool.ai.core.AIConfig;
import org.dromara.hutool.ai.core.BaseAIService;
import org.dromara.hutool.ai.core.Message;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.http.client.Response;
import org.dromara.hutool.json.JSONUtil;
import cn.hutool.v7.ai.core.AIConfig;
import cn.hutool.v7.ai.core.BaseAIService;
import cn.hutool.v7.ai.core.Message;
import cn.hutool.v7.core.text.StrUtil;
import cn.hutool.v7.core.thread.ThreadUtil;
import cn.hutool.v7.http.client.Response;
import cn.hutool.v7.json.JSONUtil;
import java.io.File;
import java.io.InputStream;
@@ -29,6 +30,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
/**
* openai服务AI具体功能的实现
@@ -60,15 +62,6 @@ public class OpenaiServiceImpl extends BaseAIService implements OpenaiService {
super(config);
}
@Override
public String chat(String prompt) {
// 定义消息结构
final List<Message> messages = new ArrayList<>();
messages.add(new Message("system", "You are a helpful assistant"));
messages.add(new Message("user", prompt));
return chat(messages);
}
@Override
public String chat(final List<Message> messages) {
String paramJson = buildChatRequestBody(messages);
@@ -76,6 +69,12 @@ public class OpenaiServiceImpl extends BaseAIService implements OpenaiService {
return response.bodyStr();
}
@Override
public void chat(List<Message> messages, Consumer<String> callback) {
Map<String, Object> paramMap = buildChatStreamRequestBody(messages);
ThreadUtil.newThread(() -> sendPostStream(CHAT_ENDPOINT, paramMap, callback::accept), "openai-chat-sse").start();
}
@Override
public String chatVision(String prompt, final List<String> images, String detail) {
String paramJson = buildChatVisionRequestBody(prompt, images, detail);
@@ -83,6 +82,12 @@ public class OpenaiServiceImpl extends BaseAIService implements OpenaiService {
return response.bodyStr();
}
@Override
public void chatVision(String prompt, List<String> images, String detail, Consumer<String> callback) {
Map<String, Object> paramMap = buildChatVisionStreamRequestBody(prompt, images, detail);
ThreadUtil.newThread(() -> sendPostStream(CHAT_ENDPOINT, paramMap, callback::accept), "openai-chatVision-sse").start();
}
@Override
public String imagesGenerations(String prompt) {
String paramJson = buildImagesGenerationsRequestBody(prompt);
@@ -132,15 +137,6 @@ public class OpenaiServiceImpl extends BaseAIService implements OpenaiService {
return response.bodyStr();
}
@Override
public String chatReasoning(String prompt, String reasoningEffort) {
// 定义消息结构
final List<Message> messages = new ArrayList<>();
messages.add(new Message("system", "You are a helpful assistant"));
messages.add(new Message("user", prompt));
return chat(messages);
}
@Override
public String chatReasoning(final List<Message> messages, String reasoningEffort) {
String paramJson = buildChatReasoningRequestBody(messages, reasoningEffort);
@@ -148,6 +144,12 @@ public class OpenaiServiceImpl extends BaseAIService implements OpenaiService {
return response.bodyStr();
}
@Override
public void chatReasoning(List<Message> messages, String reasoningEffort, Consumer<String> callback) {
Map<String, Object> paramMap = buildChatReasoningStreamRequestBody(messages, reasoningEffort);
ThreadUtil.newThread(() -> sendPostStream(CHAT_ENDPOINT, paramMap, callback::accept), "openai-chatReasoning-sse").start();
}
// 构建chat请求体
private String buildChatRequestBody(final List<Message> messages) {
//使用JSON工具
@@ -160,6 +162,18 @@ public class OpenaiServiceImpl extends BaseAIService implements OpenaiService {
return JSONUtil.toJsonStr(paramMap);
}
private Map<String, Object> buildChatStreamRequestBody(final List<Message> messages) {
//使用JSON工具
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("stream", true);
paramMap.put("model", config.getModel());
paramMap.put("messages", messages);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return paramMap;
}
//构建chatVision请求体
private String buildChatVisionRequestBody(String prompt, final List<String> images, String detail) {
// 定义消息结构
@@ -191,6 +205,37 @@ public class OpenaiServiceImpl extends BaseAIService implements OpenaiService {
return JSONUtil.toJsonStr(paramMap);
}
private Map<String, Object> buildChatVisionStreamRequestBody(String prompt, final List<String> images, String detail) {
// 定义消息结构
final List<Message> messages = new ArrayList<>();
final List<Object> content = new ArrayList<>();
final Map<String, String> contentMap = new HashMap<>();
contentMap.put("type", "text");
contentMap.put("text", prompt);
content.add(contentMap);
for (String img : images) {
HashMap<String, Object> imgUrlMap = new HashMap<>();
imgUrlMap.put("type", "image_url");
HashMap<String, String> urlMap = new HashMap<>();
urlMap.put("url", img);
urlMap.put("detail", detail);
imgUrlMap.put("image_url", urlMap);
content.add(imgUrlMap);
}
messages.add(new Message("user", content));
//使用JSON工具
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("stream", true);
paramMap.put("model", config.getModel());
paramMap.put("messages", messages);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return paramMap;
}
//构建文生图请求体
private String buildImagesGenerationsRequestBody(String prompt) {
final Map<String, Object> paramMap = new HashMap<>();
@@ -305,4 +350,16 @@ public class OpenaiServiceImpl extends BaseAIService implements OpenaiService {
return JSONUtil.toJsonStr(paramMap);
}
private Map<String, Object> buildChatReasoningStreamRequestBody(final List<Message> messages, String reasoningEffort) {
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("stream", true);
paramMap.put("model", config.getModel());
paramMap.put("messages", messages);
paramMap.put("reasoning_effort", reasoningEffort);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return paramMap;
}
}

View File

@@ -21,4 +21,4 @@
* @since 6.0.0
*/
package org.dromara.hutool.ai.model.openai;
package cn.hutool.v7.ai.model.openai;

View File

@@ -21,4 +21,4 @@
* @since 6.0.0
*/
package org.dromara.hutool.ai.model;
package cn.hutool.v7.ai.model;

View File

@@ -21,4 +21,4 @@
* @since 6.0.0
*/
package org.dromara.hutool.ai;
package cn.hutool.v7.ai;

View File

@@ -1,115 +0,0 @@
/*
* Copyright (c) 2025 Hutool Team and hutool.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.hutool.ai.model.grok;
import org.dromara.hutool.ai.core.AIService;
import java.util.List;
/**
* grok支持的扩展接口
*
* @author elichow
* @since 6.0.0
*/
public interface GrokService extends AIService {
/**
* 创建消息回复
*
* @param prompt 题词
* @param maxToken 最大token
* @return AI回答
* @since 6.0.0
*/
String message(String prompt, int maxToken);
/**
* 图像理解:模型会依据传入的图片信息以及问题,给出回复。
*
* @param prompt 题词
* @param images 图片列表/或者图片Base64编码图片列表(URI形式)
* @param detail 手动设置图片的质量取值范围high、low、auto,默认为auto
* @return AI回答
* @since 6.0.0
*/
String chatVision(String prompt, final List<String> images, String detail);
/**
* 图像理解:模型会依据传入的图片信息以及问题,给出回复。
*
* @param prompt 题词
* @param images 传入的图片列表地址/或者图片Base64编码图片列表(URI形式)
* @return AI回答
* @since 6.0.0
*/
default String chatVision(String prompt, final List<String> images) {
return chatVision(prompt, images, GrokCommon.GrokVision.AUTO.getDetail());
}
/**
* 列出所有model列表
*
* @return model列表
* @since 6.0.0
*/
String models();
/**
* 获取模型信息
*
* @param modelId model ID
* @return model信息
* @since 6.0.0
*/
String getModel(String modelId);
/**
* 列出所有语言model
*
* @return languageModel列表
* @since 6.0.0
*/
String languageModels();
/**
* 获取语言模型信息
*
* @param modelId model ID
* @return model信息
* @since 6.0.0
*/
String getLanguageModel(String modelId);
/**
* 分词:可以将文本转换为模型可理解的 token 信息
*
* @param text 需要分词的内容
* @return 分词结果
* @since 6.0.0
*/
String tokenizeText(String text);
/**
* 从延迟对话中获取结果
*
* @param requestId 延迟对话中的延迟请求ID
* @return AI回答
* @since 6.0.0
*/
String deferredCompletion(String requestId);
}

View File

@@ -0,0 +1,6 @@
cn.hutool.v7.ai.model.hutool.HutoolConfig
cn.hutool.v7.ai.model.deepseek.DeepSeekConfig
cn.hutool.v7.ai.model.openai.OpenaiConfig
cn.hutool.v7.ai.model.doubao.DoubaoConfig
cn.hutool.v7.ai.model.grok.GrokConfig
cn.hutool.v7.ai.model.ollama.OllamaConfig

View File

@@ -0,0 +1,6 @@
cn.hutool.v7.ai.model.hutool.HutoolProvider
cn.hutool.v7.ai.model.deepseek.DeepSeekProvider
cn.hutool.v7.ai.model.openai.OpenaiProvider
cn.hutool.v7.ai.model.doubao.DoubaoProvider
cn.hutool.v7.ai.model.grok.GrokProvider
cn.hutool.v7.ai.model.ollama.OllamaProvider

View File

@@ -1,4 +0,0 @@
org.dromara.hutool.ai.model.deepseek.DeepSeekConfig
org.dromara.hutool.ai.model.openai.OpenaiConfig
org.dromara.hutool.ai.model.doubao.DoubaoConfig
org.dromara.hutool.ai.model.grok.GrokConfig

View File

@@ -1,4 +0,0 @@
org.dromara.hutool.ai.model.deepseek.DeepSeekProvider
org.dromara.hutool.ai.model.openai.OpenaiProvider
org.dromara.hutool.ai.model.doubao.DoubaoProvider
org.dromara.hutool.ai.model.grok.GrokProvider

View File

@@ -14,11 +14,11 @@
* limitations under the License.
*/
package org.dromara.hutool.ai;
package cn.hutool.v7.ai;
import org.dromara.hutool.ai.core.AIConfigBuilder;
import org.dromara.hutool.ai.core.AIService;
import org.dromara.hutool.ai.model.deepseek.DeepSeekService;
import cn.hutool.v7.ai.core.AIConfigBuilder;
import cn.hutool.v7.ai.core.AIService;
import cn.hutool.v7.ai.model.deepseek.DeepSeekService;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

View File

@@ -14,15 +14,16 @@
* limitations under the License.
*/
package org.dromara.hutool.ai;
package cn.hutool.v7.ai;
import org.dromara.hutool.ai.core.AIConfigBuilder;
import org.dromara.hutool.ai.core.AIService;
import org.dromara.hutool.ai.core.Message;
import org.dromara.hutool.ai.model.deepseek.DeepSeekService;
import org.dromara.hutool.ai.model.doubao.DoubaoService;
import org.dromara.hutool.ai.model.grok.GrokService;
import org.dromara.hutool.ai.model.openai.OpenaiService;
import cn.hutool.v7.ai.core.AIConfigBuilder;
import cn.hutool.v7.ai.core.AIService;
import cn.hutool.v7.ai.core.Message;
import cn.hutool.v7.ai.model.deepseek.DeepSeekService;
import cn.hutool.v7.ai.model.doubao.DoubaoService;
import cn.hutool.v7.ai.model.grok.GrokService;
import cn.hutool.v7.ai.model.hutool.HutoolService;
import cn.hutool.v7.ai.model.openai.OpenaiService;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
@@ -46,6 +47,12 @@ class AIUtilTest {
assertNotNull(aiService);
}
@Test
void getHutoolService() {
final HutoolService hutoolService = AIUtil.getHutoolService(new AIConfigBuilder(ModelName.HUTOOL.getValue()).setApiKey(key).build());
assertNotNull(hutoolService);
}
@Test
void getDeepSeekService() {
final DeepSeekService deepSeekService = AIUtil.getDeepSeekService(new AIConfigBuilder(ModelName.DEEPSEEK.getValue()).setApiKey(key).build());

View File

@@ -14,17 +14,21 @@
* limitations under the License.
*/
package org.dromara.hutool.ai.model.deepseek;
package cn.hutool.v7.ai.model.deepseek;
import org.dromara.hutool.ai.AIServiceFactory;
import org.dromara.hutool.ai.ModelName;
import org.dromara.hutool.ai.core.AIConfigBuilder;
import org.dromara.hutool.ai.core.Message;
import cn.hutool.v7.ai.AIServiceFactory;
import cn.hutool.v7.ai.ModelName;
import cn.hutool.v7.ai.core.AIConfigBuilder;
import cn.hutool.v7.ai.core.Message;
import cn.hutool.v7.core.thread.ThreadUtil;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.junit.jupiter.api.Assertions.assertNotNull;
class DeepSeekServiceTest {
@@ -35,7 +39,30 @@ class DeepSeekServiceTest {
@Disabled
void chat(){
final String chat = deepSeekService.chat("写一个疯狂星期四广告词");
System.out.println(chat);
assertNotNull(chat);
}
@Test
@Disabled
void chatStream() {
String prompt = "写一个疯狂星期四广告词";
// 使用AtomicBoolean作为结束标志
AtomicBoolean isDone = new AtomicBoolean(false);
deepSeekService.chat(prompt, data -> {
assertNotNull(data);
if (data.contains("[DONE]")) {
// 设置结束标志
isDone.set(true);
} else if (data.contains("\"error\"")) {
isDone.set(true);
}
});
// 轮询检查结束标志
while (!isDone.get()) {
ThreadUtil.sleep(100);
}
}
@Test
@@ -45,27 +72,50 @@ class DeepSeekServiceTest {
messages.add(new Message("system","你是个抽象大师,会说很抽象的话,最擅长说抽象的笑话"));
messages.add(new Message("user","给我说一个笑话"));
final String chat = deepSeekService.chat(messages);
System.out.println(chat);
assertNotNull(chat);
}
@Test
@Disabled
void beta() {
final String beta = deepSeekService.beta("写一个疯狂星期四广告词");
System.out.println(beta);
assertNotNull(beta);
}
@Test
@Disabled
void betaStream() {
String beta = "写一个疯狂星期四广告词";
// 使用AtomicBoolean作为结束标志
AtomicBoolean isDone = new AtomicBoolean(false);
deepSeekService.beta(beta, data -> {
assertNotNull(data);
if (data.contains("[DONE]")) {
// 设置结束标志
isDone.set(true);
} else if (data.contains("\"error\"")) {
isDone.set(true);
}
});
// 轮询检查结束标志
while (!isDone.get()) {
ThreadUtil.sleep(100);
}
}
@Test
@Disabled
void models() {
final String models = deepSeekService.models();
System.out.println(models);
assertNotNull(models);
}
@Test
@Disabled
void balance() {
final String balance = deepSeekService.balance();
System.out.println(balance);
assertNotNull(balance);
}
}

View File

@@ -14,14 +14,15 @@
* limitations under the License.
*/
package org.dromara.hutool.ai.model.doubao;
package cn.hutool.v7.ai.model.doubao;
import org.dromara.hutool.ai.AIServiceFactory;
import org.dromara.hutool.ai.ModelName;
import org.dromara.hutool.ai.Models;
import org.dromara.hutool.ai.core.AIConfigBuilder;
import org.dromara.hutool.ai.core.Message;
import org.dromara.hutool.swing.img.ImgUtil;
import cn.hutool.v7.ai.AIServiceFactory;
import cn.hutool.v7.ai.ModelName;
import cn.hutool.v7.ai.Models;
import cn.hutool.v7.ai.core.AIConfigBuilder;
import cn.hutool.v7.ai.core.Message;
import cn.hutool.v7.core.thread.ThreadUtil;
import cn.hutool.v7.swing.img.ImgUtil;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@@ -29,6 +30,9 @@ import java.awt.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.junit.jupiter.api.Assertions.assertNotNull;
class DoubaoServiceTest {
@@ -39,7 +43,30 @@ class DoubaoServiceTest {
@Disabled
void chat(){
final String chat = doubaoService.chat("写一个疯狂星期四广告词");
System.out.println(chat);
assertNotNull(chat);
}
@Test
@Disabled
void chatStream() {
String prompt = "写一个疯狂星期四广告词";
// 使用AtomicBoolean作为结束标志
AtomicBoolean isDone = new AtomicBoolean(false);
doubaoService.chat(prompt, data -> {
assertNotNull(data);
if (data.contains("[DONE]")) {
// 设置结束标志
isDone.set(true);
} else if (data.contains("\"error\"")) {
isDone.set(true);
}
});
// 轮询检查结束标志
while (!isDone.get()) {
ThreadUtil.sleep(100);
}
}
@Test
@@ -49,7 +76,7 @@ class DoubaoServiceTest {
messages.add(new Message("system","你是个抽象大师,会说很抽象的话,最擅长说抽象的笑话"));
messages.add(new Message("user","给我说一个笑话"));
final String chat = doubaoService.chat(messages);
System.out.println(chat);
assertNotNull(chat);
}
@Test
@@ -59,7 +86,34 @@ class DoubaoServiceTest {
.setApiKey(key).setModel(Models.Doubao.DOUBAO_1_5_VISION_PRO_32K.getModel()).build(), DoubaoService.class);
final String base64 = ImgUtil.toBase64DataUri(Toolkit.getDefaultToolkit().createImage("your imageUrl"), "png");
final String chatVision = doubaoService.chatVision("图片上有些什么?", Arrays.asList(base64));
System.out.println(chatVision);
assertNotNull(chatVision);
}
@Test
@Disabled
void testChatVisionStream() {
final DoubaoService doubaoService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DOUBAO.getValue())
.setApiKey(key).setModel(Models.Doubao.DOUBAO_1_5_VISION_PRO_32K.getModel()).build(), DoubaoService.class);
String prompt = "图片上有些什么?";
List<String> images = Arrays.asList("https://img2.baidu.com/it/u=862000265,4064861820&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=1544");
// 使用AtomicBoolean作为结束标志
AtomicBoolean isDone = new AtomicBoolean(false);
doubaoService.chatVision(prompt,images, data -> {
assertNotNull(data);
if (data.contains("[DONE]")) {
// 设置结束标志
isDone.set(true);
} else if (data.contains("\"error\"")) {
isDone.set(true);
}
});
// 轮询检查结束标志
while (!isDone.get()) {
ThreadUtil.sleep(100);
}
}
@Test
@@ -68,17 +122,17 @@ class DoubaoServiceTest {
final DoubaoService doubaoService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DOUBAO.getValue())
.setApiKey(key).setModel(Models.Doubao.DOUBAO_1_5_VISION_PRO_32K.getModel()).build(), DoubaoService.class);
final String chatVision = doubaoService.chatVision("图片上有些什么?", Arrays.asList("https://img2.baidu.com/it/u=862000265,4064861820&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=1544"),DoubaoCommon.DoubaoVision.HIGH.getDetail());
System.out.println(chatVision);
assertNotNull(chatVision);
}
@Test
@Disabled
void videoTasks() {
final DoubaoService doubaoService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DOUBAO.getValue())
.setApiKey(key).setModel("your Endpoint ID").build(), DoubaoService.class);
.setApiKey(key).setModel(Models.Doubao.Doubao_Seedance_1_0_lite_i2v.getModel()).build(), DoubaoService.class);
final String videoTasks = doubaoService.videoTasks("生成一段动画视频,主角是大耳朵图图,一个活泼可爱的小男孩。视频中图图在公园里玩耍," +
"画面采用明亮温暖的卡通风格,色彩鲜艳,动作流畅。背景音乐轻快活泼,带有冒险感,音效包括鸟叫声、欢笑声和山洞回声。", "https://img2.baidu.com/it/u=862000265,4064861820&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=1544");
System.out.println(videoTasks);//cgt-20250306170051-6r9gk
assertNotNull(videoTasks);//cgt-20250306170051-6r9gk
}
@Test
@@ -88,7 +142,7 @@ class DoubaoServiceTest {
final DoubaoService doubaoService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DOUBAO.getValue())
.setApiKey(key).build(), DoubaoService.class);
final String videoTasksInfo = doubaoService.getVideoTasksInfo("cgt-20250306170051-6r9gk");
System.out.println(videoTasksInfo);
assertNotNull(videoTasksInfo);
}
@Test
@@ -97,7 +151,7 @@ class DoubaoServiceTest {
final DoubaoService doubaoService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DOUBAO.getValue())
.setApiKey(key).setModel(Models.Doubao.DOUBAO_EMBEDDING_TEXT_240715.getModel()).build(), DoubaoService.class);
final String embeddingText = doubaoService.embeddingText(new String[]{"阿斯顿", "马丁"});
System.out.println(embeddingText);
assertNotNull(embeddingText);
}
@Test
@@ -106,7 +160,7 @@ class DoubaoServiceTest {
final DoubaoService doubaoService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DOUBAO.getValue())
.setApiKey(key).setModel(Models.Doubao.DOUBAO_EMBEDDING_VISION.getModel()).build(), DoubaoService.class);
final String embeddingVision = doubaoService.embeddingVision("天空好难", "https://img2.baidu.com/it/u=862000265,4064861820&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=1544");
System.out.println(embeddingVision);
assertNotNull(embeddingVision);
}
@Test
@@ -118,14 +172,41 @@ class DoubaoServiceTest {
messages.add(new Message("system","你是什么都可以"));
messages.add(new Message("user","你想做些什么"));
final String botsChat = doubaoService.botsChat(messages);
System.out.println(botsChat);
assertNotNull(botsChat);
}
@Test
@Disabled
void botsChatStream() {
final DoubaoService doubaoService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DOUBAO.getValue())
.setApiKey(key).setModel("your bots id").build(), DoubaoService.class);
final ArrayList<Message> messages = new ArrayList<>();
messages.add(new Message("system","你是什么都可以"));
messages.add(new Message("user","你想做些什么"));
// 使用AtomicBoolean作为结束标志
AtomicBoolean isDone = new AtomicBoolean(false);
doubaoService.botsChat(messages, data -> {
assertNotNull(data);
if (data.contains("[DONE]")) {
// 设置结束标志
isDone.set(true);
} else if (data.contains("\"error\"")) {
isDone.set(true);
}
});
// 轮询检查结束标志
while (!isDone.get()) {
ThreadUtil.sleep(100);
}
}
@Test
@Disabled
void tokenization() {
final String tokenization = doubaoService.tokenization(new String[]{"阿斯顿", "马丁"});
System.out.println(tokenization);
assertNotNull(tokenization);
}
@Test
@@ -134,7 +215,7 @@ class DoubaoServiceTest {
final DoubaoService doubaoService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DOUBAO.getValue())
.setApiKey(key).setModel("your Endpoint ID").build(), DoubaoService.class);
final String batchChat = doubaoService.batchChat("写首歌词");
System.out.println(batchChat);
assertNotNull(batchChat);
}
@Test
@@ -146,7 +227,7 @@ class DoubaoServiceTest {
messages.add(new Message("system","你是个抽象大师"));
messages.add(new Message("user","写一个KFC的抽象广告"));
final String batchChat = doubaoService.batchChat(messages);
System.out.println(batchChat);
assertNotNull(batchChat);
}
@Test
@@ -157,7 +238,7 @@ class DoubaoServiceTest {
final List<Message> messages = new ArrayList<>();
messages.add(new Message("system","你是个抽象大师,你真的很抽象"));
final String context = doubaoService.createContext(messages);//ctx-20250307092153-cvslm
System.out.println(context);
assertNotNull(context);
}
@Test
@@ -168,17 +249,16 @@ class DoubaoServiceTest {
final List<Message> messages = new ArrayList<>();
messages.add(new Message("system","你是个抽象大师,你真的很抽象"));
final String context = doubaoService.createContext(messages,DoubaoCommon.DoubaoContext.COMMON_PREFIX.getMode());
System.out.println(context);//ctx-20250307092153-cvslm
assertNotNull(context);
}
@Test
@Disabled
void chatContext() {
//ctx-20250307092153-cvslm
final DoubaoService doubaoService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DOUBAO.getValue())
.setApiKey(key).setModel("eyour Endpoint ID").build(), DoubaoService.class);
final String chatContext = doubaoService.chatContext("你是谁?", "ctx-20250307092153-cvslm");
System.out.println(chatContext);
final String chatContext = doubaoService.chatContext("你是谁?", "your contextId");
assertNotNull(chatContext);
}
@Test
@@ -188,7 +268,43 @@ class DoubaoServiceTest {
.setApiKey(key).setModel("your Endpoint ID").build(), DoubaoService.class);
final List<Message> messages = new ArrayList<>();
messages.add(new Message("user","你怎么看待意大利面拌水泥?"));
final String chatContext = doubaoService.chatContext(messages, "ctx-20250307092153-cvslm");
System.out.println(chatContext);
final String chatContext = doubaoService.chatContext(messages, "your contextId");
assertNotNull(chatContext);
}
@Test
@Disabled
void testChatContextStream() {
final DoubaoService doubaoService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DOUBAO.getValue())
.setApiKey(key).setModel("your Endpoint ID").build(), DoubaoService.class);
final List<Message> messages = new ArrayList<>();
messages.add(new Message("user","你怎么看待意大利面拌水泥?"));
String contextId = "your contextId";
// 使用AtomicBoolean作为结束标志
AtomicBoolean isDone = new AtomicBoolean(false);
doubaoService.chatContext(messages,contextId, data -> {
assertNotNull(data);
if (data.contains("[DONE]")) {
// 设置结束标志
isDone.set(true);
} else if (data.contains("\"error\"")) {
isDone.set(true);
}
});
// 轮询检查结束标志
while (!isDone.get()) {
ThreadUtil.sleep(100);
}
}
@Test
@Disabled
void imagesGenerations() {
final DoubaoService doubaoService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DOUBAO.getValue())
.setApiKey(key).setModel(Models.Doubao.DOUBAO_SEEDREAM_3_0_T2I.getModel()).build(), DoubaoService.class);
final String imagesGenerations = doubaoService.imagesGenerations("一位年轻的宇航员站在未来感十足的太空站内,透过巨大的弧形落地窗凝望浩瀚宇宙。窗外,璀璨的星河与五彩斑斓的星云交织,远处隐约可见未知星球的轮廓,仿佛在召唤着探索的脚步。宇航服上的呼吸灯与透明显示屏上的星图交相辉映,象征着人类科技与宇宙奥秘的碰撞。画面深邃而神秘,充满对未知的渴望与无限可能的想象。");
assertNotNull(imagesGenerations);
}
}

View File

@@ -14,14 +14,15 @@
* limitations under the License.
*/
package org.dromara.hutool.ai.model.grok;
package cn.hutool.v7.ai.model.grok;
import org.dromara.hutool.ai.AIServiceFactory;
import org.dromara.hutool.ai.ModelName;
import org.dromara.hutool.ai.Models;
import org.dromara.hutool.ai.core.AIConfigBuilder;
import org.dromara.hutool.ai.core.Message;
import org.dromara.hutool.swing.img.ImgUtil;
import cn.hutool.v7.ai.AIServiceFactory;
import cn.hutool.v7.ai.ModelName;
import cn.hutool.v7.ai.Models;
import cn.hutool.v7.ai.core.AIConfigBuilder;
import cn.hutool.v7.ai.core.Message;
import cn.hutool.v7.core.thread.ThreadUtil;
import cn.hutool.v7.swing.img.ImgUtil;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@@ -29,6 +30,7 @@ import java.awt.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.junit.jupiter.api.Assertions.*;
@@ -42,7 +44,30 @@ class GrokServiceTest {
@Disabled
void chat(){
final String chat = grokService.chat("写一个疯狂星期四广告词");
System.out.println(chat);
assertNotNull(chat);
}
@Test
@Disabled
void chatStream() {
String prompt = "写一个疯狂星期四广告词";
// 使用AtomicBoolean作为结束标志
AtomicBoolean isDone = new AtomicBoolean(false);
grokService.chat(prompt, data -> {
assertNotNull(data);
if (data.contains("[DONE]")) {
// 设置结束标志
isDone.set(true);
} else if (data.contains("\"error\"")) {
isDone.set(true);
}
});
// 轮询检查结束标志
while (!isDone.get()) {
ThreadUtil.sleep(100);
}
}
@Test
@@ -52,14 +77,37 @@ class GrokServiceTest {
messages.add(new Message("system","你是个抽象大师,会说很抽象的话,最擅长说抽象的笑话"));
messages.add(new Message("user","给我说一个笑话"));
final String chat = grokService.chat(messages);
System.out.println(chat);
assertNotNull(chat);
}
@Test
@Disabled
void message() {
final String message = grokService.message("给我一个KFC的广告词", 4096);
System.out.println(message);
assertNotNull(message);
}
@Test
@Disabled
void messageStream() {
String prompt = "给我一个KFC的广告词";
// 使用AtomicBoolean作为结束标志
AtomicBoolean isDone = new AtomicBoolean(false);
grokService.message(prompt, 4096, data -> {
assertNotNull(data);
if (data.contains("[DONE]")) {
// 设置结束标志
isDone.set(true);
} else if (data.contains("\"error\"")) {
isDone.set(true);
}
});
// 轮询检查结束标志
while (!isDone.get()) {
ThreadUtil.sleep(100);
}
}
@Test
@@ -68,7 +116,32 @@ class GrokServiceTest {
final GrokService grokService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.GROK.getValue()).setModel(Models.Grok.GROK_2_VISION_1212.getModel()).setApiKey(key).build(), GrokService.class);
final String base64 = ImgUtil.toBase64DataUri(Toolkit.getDefaultToolkit().createImage("your imageUrl"), "png");
final String chatVision = grokService.chatVision("图片上有些什么?", Arrays.asList(base64));
System.out.println(chatVision);
assertNotNull(chatVision);
}
@Test
@Disabled
void testChatVisionStream() {
final GrokService grokService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.GROK.getValue()).setModel(Models.Grok.GROK_2_VISION_1212.getModel()).setApiKey(key).build(), GrokService.class);
String prompt = "图片上有些什么?";
List<String> images = Arrays.asList("https://img2.baidu.com/it/u=862000265,4064861820&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=1544");
// 使用AtomicBoolean作为结束标志
AtomicBoolean isDone = new AtomicBoolean(false);
grokService.chatVision(prompt,images, data -> {
assertNotNull(data);
if (data.contains("[DONE]")) {
// 设置结束标志
isDone.set(true);
} else if (data.contains("\"error\"")) {
isDone.set(true);
}
});
// 轮询检查结束标志
while (!isDone.get()) {
ThreadUtil.sleep(100);
}
}
@Test
@@ -76,7 +149,7 @@ class GrokServiceTest {
void testChatVision() {
final GrokService grokService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.GROK.getValue()).setModel(Models.Grok.GROK_2_VISION_1212.getModel()).setApiKey(key).build(), GrokService.class);
final String chatVision = grokService.chatVision("图片上有些什么?", Arrays.asList("https://img2.baidu.com/it/u=862000265,4064861820&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=1544"));
System.out.println(chatVision);
assertNotNull(chatVision);
}
@Test
@@ -120,4 +193,13 @@ class GrokServiceTest {
final String deferred = grokService.deferredCompletion(key);
assertNotNull(deferred);
}
@Test
@Disabled
void imagesGenerations() {
final GrokService grokService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.GROK.getValue())
.setApiKey(key).setModel(Models.Grok.GROK_2_IMAGE.getModel()).build(), GrokService.class);
final String imagesGenerations = grokService.imagesGenerations("一位年轻的宇航员站在未来感十足的太空站内,透过巨大的弧形落地窗凝望浩瀚宇宙。窗外,璀璨的星河与五彩斑斓的星云交织,远处隐约可见未知星球的轮廓,仿佛在召唤着探索的脚步。宇航服上的呼吸灯与透明显示屏上的星图交相辉映,象征着人类科技与宇宙奥秘的碰撞。画面深邃而神秘,充满对未知的渴望与无限可能的想象。");
assertNotNull(imagesGenerations);
}
}

View File

@@ -0,0 +1,193 @@
/*
* Copyright (c) 2025 Hutool Team and hutool.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.hutool.v7.ai.model.hutool;
import cn.hutool.v7.ai.AIException;
import cn.hutool.v7.ai.AIServiceFactory;
import cn.hutool.v7.ai.ModelName;
import cn.hutool.v7.ai.core.AIConfigBuilder;
import cn.hutool.v7.ai.core.Message;
import cn.hutool.v7.core.io.file.FileUtil;
import cn.hutool.v7.core.thread.ThreadUtil;
import cn.hutool.v7.swing.img.ImgUtil;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import java.awt.*;
import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.junit.jupiter.api.Assertions.*;
class HutoolServiceTest {
String key = "请前往Hutool-AI官网https://ai.hutool.cn 获取";
HutoolService hutoolService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.HUTOOL.getValue()).setApiKey(key).build(), HutoolService.class);
@Test
@Disabled
void chat(){
final String chat = hutoolService.chat("写一个疯狂星期四广告词");
assertNotNull(chat);
}
@Test
@Disabled
void chatStream() {
String prompt = "写一个疯狂星期四广告词";
// 使用AtomicBoolean作为结束标志
AtomicBoolean isDone = new AtomicBoolean(false);
hutoolService.chat(prompt, data -> {
assertNotNull(data);
if (data.contains("[DONE]")) {
// 设置结束标志
isDone.set(true);
} else if (data.contains("\"error\"")) {
isDone.set(true);
}
});
// 轮询检查结束标志
while (!isDone.get()) {
ThreadUtil.sleep(100);
}
}
@Test
@Disabled
void testChat(){
final List<Message> messages = new ArrayList<>();
messages.add(new Message("system","你是个抽象大师,会说很抽象的话,最擅长说抽象的笑话"));
messages.add(new Message("user","给我说一个笑话"));
final String chat = hutoolService.chat(messages);
assertNotNull(chat);
}
@Test
@Disabled
void chatVision() {
final String base64 = ImgUtil.toBase64DataUri(Toolkit.getDefaultToolkit().createImage("your imageUrl"), "png");
final String chatVision = hutoolService.chatVision("图片上有些什么?", Arrays.asList(base64));
assertNotNull(chatVision);
}
@Test
@Disabled
void testChatVisionStream() {
String prompt = "图片上有些什么?";
List<String> images = Arrays.asList("https://img2.baidu.com/it/u=862000265,4064861820&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=1544");
// 使用AtomicBoolean作为结束标志
AtomicBoolean isDone = new AtomicBoolean(false);
hutoolService.chatVision(prompt,images, data -> {
assertNotNull(data);
if (data.contains("[DONE]")) {
// 设置结束标志
isDone.set(true);
} else if (data.contains("\"error\"")) {
isDone.set(true);
}
});
// 轮询检查结束标志
while (!isDone.get()) {
ThreadUtil.sleep(100);
}
}
@Test
@Disabled
void testChatVision() {
final String chatVision = hutoolService.chatVision("图片上有些什么?", Arrays.asList("https://img2.baidu.com/it/u=862000265,4064861820&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=1544"));
assertNotNull(chatVision);
}
@Test
@Disabled
void tokenizeText() {
final String tokenizeText = hutoolService.tokenizeText(key);
assertNotNull(tokenizeText);
}
@Test
@Disabled
void imagesGenerations() {
final String imagesGenerations = hutoolService.imagesGenerations("一位年轻的宇航员站在未来感十足的太空站内,透过巨大的弧形落地窗凝望浩瀚宇宙。窗外,璀璨的星河与五彩斑斓的星云交织,远处隐约可见未知星球的轮廓,仿佛在召唤着探索的脚步。宇航服上的呼吸灯与透明显示屏上的星图交相辉映,象征着人类科技与宇宙奥秘的碰撞。画面深邃而神秘,充满对未知的渴望与无限可能的想象。");
assertNotNull(imagesGenerations);
}
@Test
@Disabled
void embeddingVision() {
final String embeddingVision = hutoolService.embeddingVision("天空好难", "https://img2.baidu.com/it/u=862000265,4064861820&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=1544");
System.out.println(embeddingVision);
assertNotNull(embeddingVision);
}
@Test
@Disabled
void textToSpeech() {
try {
// 测试正常音频流返回
final InputStream inputStream = hutoolService.tts("万里山河一夜白,\n" +
"千峰尽染玉龙哀。\n" +
"长风卷起琼花碎,\n" +
"直上九霄揽月来。", HutoolCommon.HutoolSpeech.NOVA);
assertNotNull(inputStream);
// 保存音频文件
final String filePath = "your filePath";
FileUtil.writeFromStream(inputStream, new File(filePath));
} catch (Exception e) {
throw new AIException("TTS测试失败: " + e.getMessage());
}
}
@Test
@Disabled
void speechToText() {
final File file = FileUtil.file("your filePath");
final String speechToText = hutoolService.stt(file);
assertNotNull(speechToText);
}
@Test
@Disabled
void videoTasks() {
final String videoTasks = hutoolService.videoTasks("生成一段动画视频,主角是大耳朵图图,一个活泼可爱的小男孩。视频中图图在公园里玩耍," +
"画面采用明亮温暖的卡通风格,色彩鲜艳,动作流畅。背景音乐轻快活泼,带有冒险感,音效包括鸟叫声、欢笑声和山洞回声。", "https://img2.baidu.com/it/u=862000265,4064861820&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=1544");
assertNotNull(videoTasks);//cgt-20250529154621-d7dq9
}
@Test
@Disabled
void getVideoTasksInfo() {
final String videoTasksInfo = hutoolService.getVideoTasksInfo("cgt-20250529154621-d7dq9");
System.out.println(videoTasksInfo);
assertNotNull(videoTasksInfo);
}
}

View File

@@ -0,0 +1,256 @@
/*
* Copyright (c) 2025 Hutool Team and hutool.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.hutool.v7.ai.model.ollama;
import cn.hutool.v7.ai.AIServiceFactory;
import cn.hutool.v7.ai.ModelName;
import cn.hutool.v7.ai.core.AIConfigBuilder;
import cn.hutool.v7.ai.core.Message;
import cn.hutool.v7.core.text.split.SplitUtil;
import cn.hutool.v7.core.thread.ThreadUtil;
import cn.hutool.v7.json.JSON;
import cn.hutool.v7.json.JSONArray;
import cn.hutool.v7.json.JSONUtil;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import static org.junit.jupiter.api.Assertions.assertNotNull;
/**
* OllamaService
*
* @author yangruoyu-yumeisoft
* @since 5.8.40
*/
class OllamaServiceTest {
// 创建service
OllamaService ollamaService = AIServiceFactory.getAIService(
new AIConfigBuilder(ModelName.OLLAMA.getValue())
// 这里填写Ollama服务的地址
.setApiUrl("http://127.0.0.1:11434")
// 这里填写使用的模型
.setModel("qwen2.5-coder:32b")
.build(),
OllamaService.class
);
// 假设有一个Java工程师的Agent提示词
String javaEngineerPrompt= """
# 角色
你是一位精通Spring Boot 3.0的资深Java全栈工程师具备以下核心能力
- 精通Spring Boot 3.0新特性与最佳实践
- 熟练整合Hutool工具包、Redis数据访问、Feign远程调用、FreeMarker模板引擎
- 能输出符合工程规范的代码结构和配置文件
- 注重代码可读性与注释规范
# 任务
请完成以下编程任务(按优先级排序):
1. **核心要求**
- 使用Spring Boot 3.0构建项目
- 必须包含以下依赖:
- `cn.hutool:hutool-all`(最新版)
- `org.springframework.boot:spring-boot-starter-data-redis`
- `org.springframework.cloud:spring-cloud-starter-openfeign`
- `org.springframework.boot:spring-boot-starter-freemarker`
2. **约束条件**
- 代码需符合Java 17语法规范
- 每个类必须包含Javadoc风格的类注释
- 关键方法需添加`@Api`/`@ApiOperation`注解(若涉及接口)
- Redis操作需使用`RedisTemplate`实现
3. **实现流程**
```
1. 生成pom.xml依赖配置
2. 创建基础配置类如RedisConfig
3. 编写Feign客户端接口
4. 实现FreeMarker模板渲染服务
5. 提供完整Controller示例
```
# 输出要求
请以严格Markdown格式输出每个模块独立代码块
```markdown
## 1. 项目依赖配置pom.xml片段
```xml
<dependency>...</dependency>
```
## 2. Redis配置类
```java
@Configuration
public class RedisConfig { ... }
```
## 3. Feign客户端示例
```java
@FeignClient(name = "...")
public interface ... { ... }
```
## 4. FreeMarker模板服务
```java
@Service
public class TemplateService { ... }
```
## 5. 控制器示例
```java
@RestController
@RequestMapping("/example")
public class ExampleController { ... }
```
```
# 示例片段(供格式参考)
```java
/**
* 示例Feign客户端
* @since 1.0.0
*/
@FeignClient(name = "demo-service", url = "${demo.service.url}")
public interface DemoClient {
@GetMapping("/data/{id}")
@ApiOperation("获取示例数据")
ResponseEntity<String> getData(@PathVariable("id") Long id);
}
```
请按此规范输出完整代码结构,确保自动化程序可直接解析生成项目文件。""";
/**
* 同步方式调用
*/
@Test
@Disabled
void testSimple() {
final String answer = ollamaService.chat("写一个疯狂星期四广告词");
assertNotNull(answer);
}
/**
* 按流方式输出
*/
@Test
@Disabled
void testStream() {
final AtomicBoolean isDone = new AtomicBoolean(false);
final AtomicReference<String> errorMessage = new AtomicReference<>();
ollamaService.chat("写一个疯狂星期四广告词", data -> {
// 输出到控制台
final JSON streamData = JSONUtil.parse(data);
if (streamData.getByPath("error") != null) {
isDone.set(true);
errorMessage.set(streamData.getByPath("error").toString());
return;
}
if ("true".equals(streamData.getByPath("done").toString())) {
isDone.set(true);
}
});
// 轮询检查结束标志
while (!isDone.get()) {
ThreadUtil.sleep(100);
}
if (errorMessage.get() != null) {
throw new RuntimeException(errorMessage.get());
}
}
/**
* 带历史上下文的同步方式调用
*/
@Test
@Disabled
void testSimpleWithHistory(){
final List<Message> messageList=new ArrayList<>();
messageList.add(new Message("system",javaEngineerPrompt));
messageList.add(new Message("user","帮我写一个Java通过Post方式发送JSON给HTTP接口请求头带有token"));
final String result = ollamaService.chat(messageList);
assertNotNull(result);
}
@Test
@Disabled
void testStreamWithHistory(){
final List<Message> messageList=new ArrayList<>();
messageList.add(new Message("system",javaEngineerPrompt));
messageList.add(new Message("user","帮我写一个Java通过Post方式发送JSON给HTTP接口请求头带有token"));
final AtomicBoolean isDone = new AtomicBoolean(false);
final AtomicReference<String> errorMessage = new AtomicReference<>();
ollamaService.chat(messageList, data -> {
// 输出到控制台
final JSON streamData = JSONUtil.parse(data);
if (streamData.getByPath("error") != null) {
isDone.set(true);
errorMessage.set(streamData.getByPath("error").toString());
return;
}
if ("true".equals(streamData.getByPath("done").toString())) {
isDone.set(true);
}
});
// 轮询检查结束标志
while (!isDone.get()) {
ThreadUtil.sleep(100);
}
if (errorMessage.get() != null) {
throw new RuntimeException(errorMessage.get());
}
}
/**
* 列出所有已经拉取到服务器上的模型
*/
@Test
@Disabled
void testListModels(){
final String models = ollamaService.listModels();
final JSONArray modelList = JSONUtil.parse(models).getByPath("models", JSONArray.class);
}
/**
* 让Ollama拉取模型
*/
@Test
@Disabled
void testPullModel(){
final String result = ollamaService.pullModel("qwen2.5:0.5b");
final List<String> lines = SplitUtil.splitTrim(result, "\n");
for (final String line : lines) {
if(line.contains("error")){
throw new RuntimeException(JSONUtil.parse(line).getByPath("error").toString());
}
}
}
/**
* 让Ollama删除已经存在的模型
*/
@Test
@Disabled
void testDeleteModel(){
// 不会返回任何信息
ollamaService.deleteModel("qwen2.5:0.5b");
}
}

View File

@@ -14,14 +14,15 @@
* limitations under the License.
*/
package org.dromara.hutool.ai.model.openai;
package cn.hutool.v7.ai.model.openai;
import org.dromara.hutool.ai.AIServiceFactory;
import org.dromara.hutool.ai.ModelName;
import org.dromara.hutool.ai.Models;
import org.dromara.hutool.ai.core.AIConfigBuilder;
import org.dromara.hutool.ai.core.Message;
import org.dromara.hutool.core.io.file.FileUtil;
import cn.hutool.v7.ai.AIServiceFactory;
import cn.hutool.v7.ai.ModelName;
import cn.hutool.v7.ai.Models;
import cn.hutool.v7.ai.core.AIConfigBuilder;
import cn.hutool.v7.ai.core.Message;
import cn.hutool.v7.core.io.file.FileUtil;
import cn.hutool.v7.core.thread.ThreadUtil;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@@ -35,6 +36,9 @@ import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.junit.jupiter.api.Assertions.assertNotNull;
class OpenaiServiceTest {
@@ -46,7 +50,30 @@ class OpenaiServiceTest {
@Disabled
void chat(){
final String chat = openaiService.chat("写一个疯狂星期四广告词");
System.out.println(chat);
assertNotNull(chat);
}
@Test
@Disabled
void chatStream() {
String prompt = "写一个疯狂星期四广告词";
// 使用AtomicBoolean作为结束标志
AtomicBoolean isDone = new AtomicBoolean(false);
openaiService.chat(prompt, data -> {
assertNotNull(data);
if (data.contains("[DONE]")) {
// 设置结束标志
isDone.set(true);
} else if (data.contains("\"error\"")) {
isDone.set(true);
}
});
// 轮询检查结束标志
while (!isDone.get()) {
ThreadUtil.sleep(100);
}
}
@Test
@@ -56,7 +83,33 @@ class OpenaiServiceTest {
messages.add(new Message("system","你是个抽象大师,会说很抽象的话,最擅长说抽象的笑话"));
messages.add(new Message("user","给我说一个笑话"));
final String chat = openaiService.chat(messages);
System.out.println(chat);
assertNotNull(chat);
}
@Test
@Disabled
void testChatVisionStream() {
final OpenaiService openaiService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.OPENAI.getValue())
.setApiKey(key).setModel(Models.Openai.GPT_4O_MINI.getModel()).build(), OpenaiService.class);
String prompt = "图片上有些什么?";
List<String> images = Arrays.asList("https://img2.baidu.com/it/u=862000265,4064861820&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=1544\",\"https://img2.baidu.com/it/u=1682510685,1244554634&fm=253&fmt=auto&app=138&f=JPEG?w=803&h=800");
// 使用AtomicBoolean作为结束标志
AtomicBoolean isDone = new AtomicBoolean(false);
openaiService.chatVision(prompt,images, data -> {
assertNotNull(data);
if (data.contains("[DONE]")) {
// 设置结束标志
isDone.set(true);
} else if (data.contains("\"error\"")) {
isDone.set(true);
}
});
// 轮询检查结束标志
while (!isDone.get()) {
ThreadUtil.sleep(100);
}
}
@Test
@@ -65,7 +118,7 @@ class OpenaiServiceTest {
final OpenaiService openaiService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.OPENAI.getValue())
.setApiKey(key).setModel(Models.Openai.GPT_4O_MINI.getModel()).build(), OpenaiService.class);
final String chatVision = openaiService.chatVision("图片上有些什么?", Arrays.asList("https://img2.baidu.com/it/u=862000265,4064861820&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=1544","https://img2.baidu.com/it/u=1682510685,1244554634&fm=253&fmt=auto&app=138&f=JPEG?w=803&h=800"));
System.out.println(chatVision);
assertNotNull(chatVision);
}
@Test
@@ -74,8 +127,7 @@ class OpenaiServiceTest {
final OpenaiService openaiService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.OPENAI.getValue())
.setApiKey(key).setModel(Models.Openai.DALL_E_3.getModel()).build(), OpenaiService.class);
final String imagesGenerations = openaiService.imagesGenerations("一位年轻的宇航员站在未来感十足的太空站内,透过巨大的弧形落地窗凝望浩瀚宇宙。窗外,璀璨的星河与五彩斑斓的星云交织,远处隐约可见未知星球的轮廓,仿佛在召唤着探索的脚步。宇航服上的呼吸灯与透明显示屏上的星图交相辉映,象征着人类科技与宇宙奥秘的碰撞。画面深邃而神秘,充满对未知的渴望与无限可能的想象。");
System.out.println(imagesGenerations);
//https://oaidalleapiprodscus.blob.core.windows.net/private/org-l99H6T0zCZejctB2TqdYrXFB/user-LilDVU1V8cUxJYwVAGRkUwYd/img-yA9kNatHnBiUHU5lZGim1hP2.png?st=2025-03-07T01%3A04%3A18Z&se=2025-03-07T03%3A04%3A18Z&sp=r&sv=2024-08-04&sr=b&rscd=inline&rsct=image/png&skoid=d505667d-d6c1-4a0a-bac7-5c84a87759f8&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skt=2025-03-06T15%3A04%3A42Z&ske=2025-03-07T15%3A04%3A42Z&sks=b&skv=2024-08-04&sig=rjcRzC5U7Y3pEDZ4ME0CiviAPdIpoGO2rRTXw3m8rHw%3D
assertNotNull(imagesGenerations);
}
@Test
@@ -85,7 +137,7 @@ class OpenaiServiceTest {
.setApiKey(key).setModel(Models.Openai.DALL_E_2.getModel()).build(), OpenaiService.class);
final File file = FileUtil.file("your imgUrl");
final String imagesEdits = openaiService.imagesEdits("茂密的森林中,有一只九色鹿若隐若现",file);
System.out.println(imagesEdits);
assertNotNull(imagesEdits);
}
@Test
@@ -95,7 +147,7 @@ class OpenaiServiceTest {
.setApiKey(key).setModel(Models.Openai.DALL_E_2.getModel()).build(), OpenaiService.class);
final File file = FileUtil.file("your imgUrl");
final String imagesVariations = openaiService.imagesVariations(file);
System.out.println(imagesVariations);
assertNotNull(imagesVariations);
}
@Test
@@ -130,7 +182,7 @@ class OpenaiServiceTest {
.setApiKey(key).setModel(Models.Openai.WHISPER_1.getModel()).build(), OpenaiService.class);
final File file = FileUtil.file("your filePath");
final String speechToText = openaiService.speechToText(file);
System.out.println(speechToText);
assertNotNull(speechToText);
}
@Test
@@ -139,7 +191,7 @@ class OpenaiServiceTest {
final OpenaiService openaiService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.OPENAI.getValue())
.setApiKey(key).setModel(Models.Openai.TEXT_EMBEDDING_3_SMALL.getModel()).build(), OpenaiService.class);
final String embeddingText = openaiService.embeddingText("萬里山河一夜白,千峰盡染玉龍哀,長風捲起瓊花碎,直上九霄闌月來");
System.out.println(embeddingText);
assertNotNull(embeddingText);
}
@Test
@@ -148,7 +200,7 @@ class OpenaiServiceTest {
final OpenaiService openaiService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.OPENAI.getValue())
.setApiKey(key).setModel(Models.Openai.OMNI_MODERATION_LATEST.getModel()).build(), OpenaiService.class);
final String moderations = openaiService.moderations("你要杀人", "https://img2.baidu.com/it/u=862000265,4064861820&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=1544");
System.out.println(moderations);
assertNotNull(moderations);
}
@Test
@@ -160,6 +212,33 @@ class OpenaiServiceTest {
messages.add(new Message("system","你是现代抽象家"));
messages.add(new Message("user","给我一个KFC疯狂星期四的文案"));
final String chatReasoning = openaiService.chatReasoning(messages, OpenaiCommon.OpenaiReasoning.HIGH.getEffort());
System.out.println(chatReasoning);
assertNotNull(chatReasoning);
}
@Test
@Disabled
void chatReasoningStream() {
final OpenaiService openaiService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.OPENAI.getValue())
.setApiKey(key).setModel(Models.Openai.O3_MINI.getModel()).build(), OpenaiService.class);
final List<Message> messages = new ArrayList<>();
messages.add(new Message("system","你是现代抽象家"));
messages.add(new Message("user","给我一个KFC疯狂星期四的文案"));
// 使用AtomicBoolean作为结束标志
AtomicBoolean isDone = new AtomicBoolean(false);
openaiService.chatReasoning(messages,OpenaiCommon.OpenaiReasoning.HIGH.getEffort(), data -> {
assertNotNull(data);
if (data.contains("[DONE]")) {
// 设置结束标志
isDone.set(true);
} else if (data.contains("\"error\"")) {
isDone.set(true);
}
});
// 轮询检查结束标志
while (!isDone.get()) {
ThreadUtil.sleep(100);
}
}
}

View File

@@ -23,9 +23,9 @@
<packaging>jar</packaging>
<parent>
<groupId>org.dromara.hutool</groupId>
<groupId>cn.hutool.v7</groupId>
<artifactId>hutool-parent</artifactId>
<version>6.0.0-M22</version>
<version>7.0.0-M2</version>
</parent>
<artifactId>hutool-all</artifactId>
@@ -33,7 +33,7 @@
<description>
Hutool是一个功能丰富且易用的Java工具库通过诸多实用工具类的使用旨在帮助开发者快速、便捷地完成各类开发任务。这些封装的工具涵盖了字符串、数字、集合、编码、日期、文件、IO、加密、数据库JDBC、JSON、HTTP客户端等一系列操作可以满足各种不同的开发需求。
</description>
<url>https://github.com/dromara/hutool</url>
<url>https://github.com/chinabugotech/hutool</url>
<dependencies>
<dependency>

View File

@@ -14,11 +14,11 @@
* limitations under the License.
*/
package org.dromara.hutool;
package cn.hutool.v7;
import org.dromara.hutool.core.lang.ConsoleTable;
import org.dromara.hutool.core.reflect.ClassUtil;
import org.dromara.hutool.core.text.StrUtil;
import cn.hutool.v7.core.lang.ConsoleTable;
import cn.hutool.v7.core.reflect.ClassUtil;
import cn.hutool.v7.core.text.StrUtil;
import java.util.Set;
@@ -60,7 +60,7 @@ public class Hutool {
* @since 5.5.2
*/
public static Set<Class<?>> getAllUtils() {
return ClassUtil.scanPackage("org.dromara.hutool",
return ClassUtil.scanPackage("cn.hutool.v7",
(clazz) -> (!clazz.isInterface()) && StrUtil.endWith(clazz.getSimpleName(), "Util"));
}

View File

@@ -23,4 +23,4 @@
*
* @author Looly
*/
package org.dromara.hutool;
package cn.hutool.v7;

View File

@@ -23,15 +23,15 @@
<packaging>pom</packaging>
<parent>
<groupId>org.dromara.hutool</groupId>
<groupId>cn.hutool.v7</groupId>
<artifactId>hutool-parent</artifactId>
<version>6.0.0-M22</version>
<version>7.0.0-M2</version>
</parent>
<artifactId>hutool-bom</artifactId>
<name>${project.artifactId}</name>
<description>提供丰富的Java工具方法此模块为Hutool所有模块汇总最终形式为拆分开的多个jar包可以通过exclude方式排除不需要的模块</description>
<url>https://github.com/dromara/hutool</url>
<url>https://github.com/chinabugotech/hutool</url>
<dependencyManagement>
<dependencies>

View File

@@ -23,9 +23,9 @@
<packaging>jar</packaging>
<parent>
<groupId>org.dromara.hutool</groupId>
<groupId>cn.hutool.v7</groupId>
<artifactId>hutool-parent</artifactId>
<version>6.0.0-M22</version>
<version>7.0.0-M2</version>
</parent>
<artifactId>hutool-core</artifactId>
@@ -33,7 +33,7 @@
<description>Hutool核心包括集合、字符串、Bean等工具</description>
<properties>
<Automatic-Module-Name>org.dromara.hutool.core</Automatic-Module-Name>
<Automatic-Module-Name>cn.hutool.v7.core</Automatic-Module-Name>
</properties>
</project>

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.dromara.hutool.core.annotation;
package cn.hutool.v7.core.annotation;
import java.lang.annotation.*;

View File

@@ -14,14 +14,14 @@
* limitations under the License.
*/
package org.dromara.hutool.core.annotation;
package cn.hutool.v7.core.annotation;
import org.dromara.hutool.core.annotation.elements.HierarchicalAnnotatedElements;
import org.dromara.hutool.core.annotation.elements.MetaAnnotatedElement;
import org.dromara.hutool.core.annotation.elements.RepeatableMetaAnnotatedElement;
import org.dromara.hutool.core.array.ArrayUtil;
import org.dromara.hutool.core.map.reference.WeakConcurrentMap;
import org.dromara.hutool.core.util.ObjUtil;
import cn.hutool.v7.core.annotation.elements.HierarchicalAnnotatedElements;
import cn.hutool.v7.core.annotation.elements.MetaAnnotatedElement;
import cn.hutool.v7.core.annotation.elements.RepeatableMetaAnnotatedElement;
import cn.hutool.v7.core.array.ArrayUtil;
import cn.hutool.v7.core.map.reference.WeakConcurrentMap;
import cn.hutool.v7.core.util.ObjUtil;
import java.lang.annotation.Annotation;
import java.lang.annotation.Inherited;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.dromara.hutool.core.annotation;
package cn.hutool.v7.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

View File

@@ -14,11 +14,10 @@
* limitations under the License.
*/
package org.dromara.hutool.core.annotation;
package cn.hutool.v7.core.annotation;
import org.dromara.hutool.core.map.concurrent.SafeConcurrentHashMap;
import org.dromara.hutool.core.reflect.method.MethodUtil;
import org.dromara.hutool.core.text.CharSequenceUtil;
import cn.hutool.v7.core.reflect.method.MethodUtil;
import cn.hutool.v7.core.text.CharSequenceUtil;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
@@ -28,6 +27,7 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -96,7 +96,7 @@ public final class AnnotationMappingProxy<T extends Annotation> implements Invoc
private AnnotationMappingProxy(final AnnotationMapping<T> annotation) {
final int methodCount = annotation.getAttributes().length;
this.methods = new HashMap<>(methodCount + 5);
this.valueCache = new SafeConcurrentHashMap<>(methodCount);
this.valueCache = new ConcurrentHashMap<>(methodCount);
this.mapping = annotation;
loadMethods();
}

View File

@@ -14,11 +14,12 @@
* limitations under the License.
*/
package org.dromara.hutool.core.annotation;
package cn.hutool.v7.core.annotation;
import org.dromara.hutool.core.reflect.method.MethodUtil;
import org.dromara.hutool.core.text.StrUtil;
import cn.hutool.v7.core.reflect.method.MethodUtil;
import cn.hutool.v7.core.text.StrUtil;
import java.io.Serial;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
@@ -34,6 +35,7 @@ import java.util.Map;
* @since 5.7.23
*/
public class AnnotationProxy<T extends Annotation> implements Annotation, InvocationHandler, Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**

View File

@@ -14,21 +14,21 @@
* limitations under the License.
*/
package org.dromara.hutool.core.annotation;
package cn.hutool.v7.core.annotation;
import org.dromara.hutool.core.annotation.elements.CombinationAnnotatedElement;
import org.dromara.hutool.core.array.ArrayUtil;
import org.dromara.hutool.core.classloader.ClassLoaderUtil;
import org.dromara.hutool.core.exception.HutoolException;
import org.dromara.hutool.core.func.LambdaInfo;
import org.dromara.hutool.core.func.LambdaUtil;
import org.dromara.hutool.core.func.SerFunction;
import org.dromara.hutool.core.map.reference.WeakConcurrentMap;
import org.dromara.hutool.core.reflect.FieldUtil;
import org.dromara.hutool.core.reflect.method.MethodUtil;
import org.dromara.hutool.core.text.CharSequenceUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.core.util.ObjUtil;
import cn.hutool.v7.core.annotation.elements.CombinationAnnotatedElement;
import cn.hutool.v7.core.array.ArrayUtil;
import cn.hutool.v7.core.classloader.ClassLoaderUtil;
import cn.hutool.v7.core.exception.HutoolException;
import cn.hutool.v7.core.func.LambdaInfo;
import cn.hutool.v7.core.func.LambdaUtil;
import cn.hutool.v7.core.func.SerFunction;
import cn.hutool.v7.core.map.reference.WeakConcurrentMap;
import cn.hutool.v7.core.reflect.FieldUtil;
import cn.hutool.v7.core.reflect.method.MethodUtil;
import cn.hutool.v7.core.text.CharSequenceUtil;
import cn.hutool.v7.core.text.StrUtil;
import cn.hutool.v7.core.util.ObjUtil;
import java.lang.annotation.*;
import java.lang.reflect.*;

View File

@@ -14,11 +14,11 @@
* limitations under the License.
*/
package org.dromara.hutool.core.annotation;
package cn.hutool.v7.core.annotation;
import org.dromara.hutool.core.reflect.ClassUtil;
import org.dromara.hutool.core.reflect.method.MethodUtil;
import org.dromara.hutool.core.text.CharSequenceUtil;
import cn.hutool.v7.core.reflect.ClassUtil;
import cn.hutool.v7.core.reflect.method.MethodUtil;
import cn.hutool.v7.core.text.CharSequenceUtil;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.dromara.hutool.core.annotation;
package cn.hutool.v7.core.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.dromara.hutool.core.annotation;
package cn.hutool.v7.core.annotation;
import java.lang.annotation.*;

View File

@@ -14,13 +14,13 @@
* limitations under the License.
*/
package org.dromara.hutool.core.annotation;
package cn.hutool.v7.core.annotation;
import org.dromara.hutool.core.array.ArrayUtil;
import org.dromara.hutool.core.collection.CollUtil;
import org.dromara.hutool.core.map.reference.WeakConcurrentMap;
import org.dromara.hutool.core.reflect.method.MethodUtil;
import org.dromara.hutool.core.text.CharSequenceUtil;
import cn.hutool.v7.core.array.ArrayUtil;
import cn.hutool.v7.core.collection.CollUtil;
import cn.hutool.v7.core.map.reference.WeakConcurrentMap;
import cn.hutool.v7.core.reflect.method.MethodUtil;
import cn.hutool.v7.core.text.CharSequenceUtil;
import java.lang.annotation.Annotation;
import java.lang.annotation.Repeatable;

View File

@@ -14,16 +14,16 @@
* limitations under the License.
*/
package org.dromara.hutool.core.annotation;
package cn.hutool.v7.core.annotation;
import org.dromara.hutool.core.annotation.elements.MetaAnnotatedElement;
import org.dromara.hutool.core.collection.CollUtil;
import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.map.multi.Graph;
import org.dromara.hutool.core.reflect.ClassUtil;
import org.dromara.hutool.core.reflect.method.MethodUtil;
import org.dromara.hutool.core.text.CharSequenceUtil;
import org.dromara.hutool.core.array.ArrayUtil;
import cn.hutool.v7.core.annotation.elements.MetaAnnotatedElement;
import cn.hutool.v7.core.collection.CollUtil;
import cn.hutool.v7.core.lang.Assert;
import cn.hutool.v7.core.map.multi.Graph;
import cn.hutool.v7.core.reflect.ClassUtil;
import cn.hutool.v7.core.reflect.method.MethodUtil;
import cn.hutool.v7.core.text.CharSequenceUtil;
import cn.hutool.v7.core.array.ArrayUtil;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.dromara.hutool.core.annotation;
package cn.hutool.v7.core.annotation;
import java.lang.annotation.*;

View File

@@ -14,12 +14,12 @@
* limitations under the License.
*/
package org.dromara.hutool.core.annotation.elements;
package cn.hutool.v7.core.annotation.elements;
import org.dromara.hutool.core.annotation.AnnotationUtil;
import org.dromara.hutool.core.array.ArrayUtil;
import org.dromara.hutool.core.collection.set.SetUtil;
import org.dromara.hutool.core.map.TableMap;
import cn.hutool.v7.core.annotation.AnnotationUtil;
import cn.hutool.v7.core.array.ArrayUtil;
import cn.hutool.v7.core.collection.set.SetUtil;
import cn.hutool.v7.core.map.TableMap;
import java.io.Serializable;
import java.lang.annotation.*;

View File

@@ -14,15 +14,15 @@
* limitations under the License.
*/
package org.dromara.hutool.core.annotation.elements;
package cn.hutool.v7.core.annotation.elements;
import org.dromara.hutool.core.annotation.AnnotationUtil;
import org.dromara.hutool.core.collection.CollUtil;
import org.dromara.hutool.core.collection.set.SetUtil;
import org.dromara.hutool.core.reflect.ClassUtil;
import org.dromara.hutool.core.reflect.method.MethodUtil;
import org.dromara.hutool.core.text.CharSequenceUtil;
import org.dromara.hutool.core.array.ArrayUtil;
import cn.hutool.v7.core.annotation.AnnotationUtil;
import cn.hutool.v7.core.collection.CollUtil;
import cn.hutool.v7.core.collection.set.SetUtil;
import cn.hutool.v7.core.reflect.ClassUtil;
import cn.hutool.v7.core.reflect.method.MethodUtil;
import cn.hutool.v7.core.text.CharSequenceUtil;
import cn.hutool.v7.core.array.ArrayUtil;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;

View File

@@ -14,15 +14,15 @@
* limitations under the License.
*/
package org.dromara.hutool.core.annotation.elements;
package cn.hutool.v7.core.annotation.elements;
import org.dromara.hutool.core.annotation.AnnotationMapping;
import org.dromara.hutool.core.annotation.AnnotationUtil;
import org.dromara.hutool.core.annotation.ResolvedAnnotationMapping;
import org.dromara.hutool.core.stream.EasyStream;
import org.dromara.hutool.core.text.CharSequenceUtil;
import org.dromara.hutool.core.array.ArrayUtil;
import org.dromara.hutool.core.util.ObjUtil;
import cn.hutool.v7.core.annotation.AnnotationMapping;
import cn.hutool.v7.core.annotation.AnnotationUtil;
import cn.hutool.v7.core.annotation.ResolvedAnnotationMapping;
import cn.hutool.v7.core.stream.EasyStream;
import cn.hutool.v7.core.text.CharSequenceUtil;
import cn.hutool.v7.core.array.ArrayUtil;
import cn.hutool.v7.core.util.ObjUtil;
import java.lang.annotation.Annotation;
import java.lang.annotation.Inherited;

View File

@@ -14,14 +14,14 @@
* limitations under the License.
*/
package org.dromara.hutool.core.annotation.elements;
package cn.hutool.v7.core.annotation.elements;
import org.dromara.hutool.core.annotation.AnnotationMapping;
import org.dromara.hutool.core.annotation.AnnotationUtil;
import org.dromara.hutool.core.annotation.RepeatableAnnotationCollector;
import org.dromara.hutool.core.collection.CollUtil;
import org.dromara.hutool.core.text.CharSequenceUtil;
import org.dromara.hutool.core.array.ArrayUtil;
import cn.hutool.v7.core.annotation.AnnotationMapping;
import cn.hutool.v7.core.annotation.AnnotationUtil;
import cn.hutool.v7.core.annotation.RepeatableAnnotationCollector;
import cn.hutool.v7.core.collection.CollUtil;
import cn.hutool.v7.core.text.CharSequenceUtil;
import cn.hutool.v7.core.array.ArrayUtil;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;

View File

@@ -19,4 +19,4 @@
*
* @author Looly, huangchengxing
*/
package org.dromara.hutool.core.annotation.elements;
package cn.hutool.v7.core.annotation.elements;

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