新增hutool-ai模块

This commit is contained in:
choweli
2025-05-13 10:14:36 +08:00
parent 6f346fc9a1
commit 45226d78ad
53 changed files with 4253 additions and 42 deletions

View File

@@ -86,27 +86,28 @@ Hutool exists to reduce code search costs and avoid bugs caused by imperfect cod
## 🛠Module
A Java-based tool class for files, streams, encryption and decryption, transcoding, regular, thread, XML and other JDK methods for encapsulationcomposing various Util tool classes, as well as providing the following modules
| module | description |
| -------------------|-------------------------------------------------------------------------------------------------------------------------|
| hutool-aop | JDK dynamic proxy encapsulation to provide non-IOC faceting support |
| hutool-bloomFilter | Bloom filtering to provide some Hash algorithm Bloom filtering |
| hutool-cache | Simple cache |
| hutool-core | Core, including Bean operations, dates, various Utils, etc. |
| hutool-cron | Task scheduling with Cron expressions |
| hutool-crypto | Provides symmetric, asymmetric and digest algorithm encapsulation |
| hutool-db | Db operations based on ActiveRecord thinking. |
| hutool-dfa | DFA models, such as multi-keyword lookups |
| hutool-extra | Extension modules, third-party wrappers (template engine, mail, servlet, QR code, Emoji, FTP, word splitting, etc.) |
| hutool-http | Http client |
| hutool-log | Log (facade) |
| hutool-script | Script execution encapsulation, e.g. Javascript |
| hutool-setting | Stronger Setting Profile tools and Properties tools |
| hutool-system | System parameter tools (JVM information, etc.) |
| hutool-json | JSON |
| hutool-captcha | Image Captcha |
| hutool-poi | Tools for working with Excel and Word in POI |
| hutool-socket | Java-based tool classes for NIO and AIO sockets |
| hutool-jwt | JSON Web Token (JWT) implement |
| module | description |
|--------------------|---------------------------------------------------------------------------------------------------------------------|
| hutool-aop | JDK dynamic proxy encapsulation to provide non-IOC faceting support |
| hutool-bloomFilter | Bloom filtering to provide some Hash algorithm Bloom filtering |
| hutool-cache | Simple cache |
| hutool-core | Core, including Bean operations, dates, various Utils, etc. |
| hutool-cron | Task scheduling with Cron expressions |
| hutool-crypto | Provides symmetric, asymmetric and digest algorithm encapsulation |
| hutool-db | Db operations based on ActiveRecord thinking. |
| hutool-dfa | DFA models, such as multi-keyword lookups |
| hutool-extra | Extension modules, third-party wrappers (template engine, mail, servlet, QR code, Emoji, FTP, word splitting, etc.) |
| hutool-http | Http client |
| hutool-log | Log (facade) |
| hutool-script | Script execution encapsulation, e.g. Javascript |
| hutool-setting | Stronger Setting Profile tools and Properties tools |
| hutool-system | System parameter tools (JVM information, etc.) |
| hutool-json | JSON |
| hutool-captcha | Image Captcha |
| hutool-poi | Tools for working with Excel and Word in POI |
| hutool-socket | Java-based tool classes for NIO and AIO sockets |
| hutool-jwt | JSON Web Token (JWT) implement |
| hutool-ai | AI implement |
Each module can be introduced individually, or all modules can be introduced by introducing `hutool-all` as required.

View File

@@ -74,27 +74,28 @@ Hutool = Hu + tool是原公司项目底层代码剥离后的开源库“Hu
## 🛠️包含组件
一个Java基础工具类对文件、流、加密解密、转码、正则、线程、XML等JDK方法进行封装组成各种Util工具类同时提供以下组件
| 模块 | 介绍 |
| -------------------|---------------------------------------------------------------------------------- |
| hutool-aop | JDK动态代理封装提供非IOC下的切面支持 |
| hutool-bloomFilter | 布隆过滤提供一些Hash算法的布隆过滤 |
| hutool-cache | 简单缓存实现 |
| hutool-core | 核心包括Bean操作、日期、各种Util等 |
| hutool-cron | 定时任务模块提供类Crontab表达式的定时任务 |
| hutool-crypto | 加密解密模块,提供对称、非对称和摘要算法封装 |
| hutool-db | JDBC封装后的数据操作基于ActiveRecord思想 |
| hutool-dfa | 基于DFA模型的多关键字查找 |
| hutool-extra | 扩展模块对第三方封装模板引擎、邮件、Servlet、二维码、Emoji、FTP、分词等 |
| hutool-http | 基于HttpUrlConnection的Http客户端封装 |
| hutool-log | 自动识别日志实现的日志门面 |
| hutool-script | 脚本执行封装例如Javascript |
| hutool-setting | 功能更强大的Setting配置文件和Properties封装 |
| hutool-system | 系统参数调用封装JVM信息等 |
| hutool-json | JSON实现 |
| hutool-captcha | 图片验证码实现 |
| hutool-poi | 针对POI中Excel和Word的封装 |
| hutool-socket | 基于Java的NIO和AIO的Socket封装 |
| hutool-jwt | JSON Web Token (JWT)封装实现 |
| 模块 | 介绍 |
|--------------------|------------------------------------------------|
| hutool-aop | JDK动态代理封装提供非IOC下的切面支持 |
| hutool-bloomFilter | 布隆过滤提供一些Hash算法的布隆过滤 |
| hutool-cache | 简单缓存实现 |
| hutool-core | 核心包括Bean操作、日期、各种Util等 |
| hutool-cron | 定时任务模块提供类Crontab表达式的定时任务 |
| hutool-crypto | 加密解密模块,提供对称、非对称和摘要算法封装 |
| hutool-db | JDBC封装后的数据操作基于ActiveRecord思想 |
| hutool-dfa | 基于DFA模型的多关键字查找 |
| hutool-extra | 扩展模块对第三方封装模板引擎、邮件、Servlet、二维码、Emoji、FTP、分词等 |
| hutool-http | 基于HttpUrlConnection的Http客户端封装 |
| hutool-log | 自动识别日志实现的日志门面 |
| hutool-script | 脚本执行封装例如Javascript |
| hutool-setting | 功能更强大的Setting配置文件和Properties封装 |
| hutool-system | 系统参数调用封装JVM信息等 |
| hutool-json | JSON实现 |
| hutool-captcha | 图片验证码实现 |
| hutool-poi | 针对POI中Excel和Word的封装 |
| hutool-socket | 基于Java的NIO和AIO的Socket封装 |
| hutool-jwt | JSON Web Token (JWT)封装实现 |
| hutool-ai | AI大模型封装实现 |
可以根据需求对每个模块单独引入,也可以通过引入`hutool-all`方式引入所有模块。

45
hutool-ai/pom.xml Normal file
View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.8.38-SNAPSHOT</version>
</parent>
<artifactId>hutool-ai</artifactId>
<name>${project.artifactId}</name>
<description>Hutool AI大模型封装</description>
<properties>
<Automatic-Module-Name>cn.hutool.ai</Automatic-Module-Name>
</properties>
<dependencies>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-http</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-log</artifactId>
<version>${project.parent.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-json</artifactId>
<version>${project.parent.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,87 @@
/*
* 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.ai;
import cn.hutool.core.util.StrUtil;
/**
* 异常处理类
*/
public class AIException extends RuntimeException {
private static final long serialVersionUID = 1L;
/**
* 构造
*
* @param e 异常
*/
public AIException(final Throwable e) {
super(e);
}
/**
* 构造
*
* @param message 消息
*/
public AIException(final String message) {
super(message);
}
/**
* 构造
*
* @param messageTemplate 消息模板
* @param params 参数
*/
public AIException(String messageTemplate, Object... params) {
super(StrUtil.format(messageTemplate, params));
}
/**
* 构造
*
* @param message 消息
* @param cause 被包装的子异常
*/
public AIException(final String message, final Throwable cause) {
super(message, cause);
}
/**
* 构造
*
* @param message 消息
* @param cause 被包装的子异常
* @param enableSuppression 是否启用抑制
* @param writableStackTrace 堆栈跟踪是否应该是可写的
*/
public AIException(final String message, final Throwable cause, final boolean enableSuppression, final boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
/**
* 构造
*
* @param throwable 被包装的子异常
* @param messageTemplate 消息模板
* @param params 参数
*/
public AIException(Throwable throwable, String messageTemplate, Object... params) {
super(StrUtil.format(messageTemplate, params), throwable);
}
}

View File

@@ -0,0 +1,80 @@
/*
* 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.ai;
import cn.hutool.ai.core.AIConfig;
import cn.hutool.ai.core.AIService;
import cn.hutool.ai.core.AIServiceProvider;
import cn.hutool.core.util.ServiceLoaderUtil;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.concurrent.ConcurrentHashMap;
/**
* 创建AIModelService的工厂类
*
* @author elichow
* @since 5.8.38
*/
public class AIServiceFactory {
private static final Map<String, AIServiceProvider> providers = new ConcurrentHashMap<>();
// 加载所有 AIModelProvider 实现类
static {
final ServiceLoader<AIServiceProvider> loader = ServiceLoaderUtil.load(AIServiceProvider.class);
for (final AIServiceProvider provider : loader) {
providers.put(provider.getServiceName().toLowerCase(), provider);
}
}
/**
* 获取AI服务
*
* @param config AIConfig配置
* @return AI服务实例
* @since 5.8.38
*/
public static AIService getAIService(final AIConfig config) {
return getAIService(config, AIService.class);
}
/**
* 获取AI服务
*
* @param config AIConfig配置
* @param clazz AI服务类
* @return clazz对应的AI服务类实例
* @since 5.8.38
* @param <T> AI服务类
*/
@SuppressWarnings("unchecked")
public static <T extends AIService> T getAIService(final AIConfig config, final Class<T> clazz) {
final AIServiceProvider provider = providers.get(config.getModelName().toLowerCase());
if (provider == null) {
throw new IllegalArgumentException("Unsupported model: " + config.getModelName());
}
final AIService service = provider.create(config);
if (!clazz.isInstance(service)) {
throw new AIException("Model service is not of type: " + clazz.getSimpleName());
}
return (T) service;
}
}

View File

@@ -0,0 +1,129 @@
/*
* 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.ai;
import cn.hutool.ai.core.AIConfig;
import cn.hutool.ai.core.AIService;
import cn.hutool.ai.core.Message;
import cn.hutool.ai.model.deepseek.DeepSeekService;
import cn.hutool.ai.model.doubao.DoubaoService;
import cn.hutool.ai.model.grok.GrokService;
import cn.hutool.ai.model.openai.OpenaiService;
import java.util.List;
/**
* AI工具类
*
* @author elichow
* @since 5.8.38
*/
public class AIUtil {
/**
* 获取AI模型服务每个大模型提供的功能会不一样可以调用此方法指定不同AI服务类调用不同的功能
*
* @param config 创建的AI服务模型的配置
* @param clazz AI模型服务类
* @return AIModelService的实现类实例
* @since 5.8.38
* @param <T> AIService实现类
*/
public static <T extends AIService> T getAIService(final AIConfig config, final Class<T> clazz) {
return AIServiceFactory.getAIService(config, clazz);
}
/**
* 获取AI模型服务
*
* @param config 创建的AI服务模型的配置
* @return AIModelService 其中只有公共方法
* @since 5.8.38
*/
public static AIService getAIService(final AIConfig config) {
return getAIService(config, AIService.class);
}
/**
* 获取DeepSeek模型服务
*
* @param config 创建的AI服务模型的配置
* @return DeepSeekService
* @since 5.8.38
*/
public static DeepSeekService getDeepSeekService(final AIConfig config) {
return getAIService(config, DeepSeekService.class);
}
/**
* 获取Doubao模型服务
*
* @param config 创建的AI服务模型的配置
* @return DoubaoService
* @since 5.8.38
*/
public static DoubaoService getDoubaoService(final AIConfig config) {
return getAIService(config, DoubaoService.class);
}
/**
* 获取Grok模型服务
*
* @param config 创建的AI服务模型的配置
* @return GrokService
* @since 5.8.38
*/
public static GrokService getGrokService(final AIConfig config) {
return getAIService(config, GrokService.class);
}
/**
* 获取Openai模型服务
*
* @param config 创建的AI服务模型的配置
* @return OpenAIService
* @since 5.8.38
*/
public static OpenaiService getOpenAIService(final AIConfig config) {
return getAIService(config, OpenaiService.class);
}
/**
* AI大模型对话功能
*
* @param config 创建的AI服务模型的配置
* @param prompt 需要对话的内容
* @return AI模型返回的Response响应字符串
* @since 5.8.38
*/
public static String chat(final AIConfig config, final String prompt) {
return getAIService(config).chat(prompt);
}
/**
* AI大模型对话功能
*
* @param config 创建的AI服务模型的配置
* @param messages 由目前为止的对话组成的消息列表可以设置rolecontent。详细参考官方文档
* @return AI模型返回的Response响应字符串
* @since 5.8.38
*/
public static String chat(final AIConfig config, final List<Message> messages) {
return getAIService(config).chat(messages);
}
}

View File

@@ -0,0 +1,57 @@
/*
* 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.ai;
/**
* 模型厂商的名称(不指具体的模型)
*
* @author elichow
* @since 5.8.38
*/
public enum ModelName {
/**
* deepSeek
*/
DEEPSEEK("deepSeek"),
/**
* openai
*/
OPENAI("openai"),
/**
* doubao
*/
DOUBAO("doubao"),
/**
* grok
*/
GROK("grok");
private final String value;
ModelName(final String value) {
this.value = value;
}
/**
* 获取值
*
* @return 值
*/
public String getValue() {
return value;
}
}

View File

@@ -0,0 +1,157 @@
/*
* 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.ai;
/**
* 各模型厂商包含的model指具体的模型
*
* @author elichow
* @since 5.8.38
*/
public class Models {
// DeepSeek的模型
public enum DeepSeek {
DEEPSEEK_CHAT("deepseek-chat"),
DEEPSEEK_REASONER("deepseek-reasoner");
private final String model;
DeepSeek(String model) {
this.model = model;
}
public String getModel() {
return model;
}
}
// Openai的模型
public enum Openai {
GPT_4_5_PREVIEW("gpt-4.5-preview"),
GPT_4O("gpt-4o"),
CHATGPT_4O_LATEST("chatgpt-4o-latest"),
GPT_4O_MINI("gpt-4o-mini"),
O1("o1"),
O1_MINI("o1-mini"),
O1_PREVIEW("o1-preview"),
O3_MINI("o3-mini"),
GPT_4O_REALTIME_PREVIEW("gpt-4o-realtime-preview"),
GPT_4O_MINI_REALTIME_PREVIEW("gpt-4o-mini-realtime-preview"),
GPT_4O_AUDIO_PREVIEW("gpt-4o-audio-preview"),
GPT_4O_MINI_AUDIO_PREVIEW("gpt-4o-mini-audio-preview"),
GPT_4_TURBO("gpt-4-turbo"),
GPT_4_TURBO_PREVIEW("gpt-4-turbo-preview"),
GPT_4("gpt-4"),
GPT_3_5_TURBO_0125("gpt-3.5-turbo-0125"),
GPT_3_5_TURBO("gpt-3.5-turbo"),
GPT_3_5_TURBO_1106("gpt-3.5-turbo-1106"),
GPT_3_5_TURBO_INSTRUCT("gpt-3.5-turbo-instruct"),
DALL_E_3("dall-e-3"),
DALL_E_2("dall-e-2"),
TTS_1("tts-1"),
TTS_1_HD("tts-1-hd"),
WHISPER_1("whisper-1"),
TEXT_EMBEDDING_3_LARGE("text-embedding-3-large"),
TEXT_EMBEDDING_3_SMALL("text-embedding-3-small"),
TEXT_EMBEDDING_ADA_002("text-embedding-ada-002"),
OMNI_MODERATION_LATEST("omni-moderation-latest"),
OMNI_MODERATION_2024_09_26("omni-moderation-2024-09-26"),
TEXT_MODERATION_LATEST("text-moderation-latest"),
TEXT_MODERATION_STABLE("text-moderation-stable"),
TEXT_MODERATION_007("text-moderation-007"),
BABBAGE_002("babbage-002"),
DAVINCI_002("davinci-002");
private final String model;
Openai(String model) {
this.model = model;
}
public String getModel() {
return model;
}
}
// Doubao的模型
public enum Doubao {
DOUBAO_1_5_PRO_32K("doubao-1.5-pro-32k-250115"),
DOUBAO_1_5_PRO_256K("doubao-1.5-pro-256k-250115"),
DOUBAO_1_5_LITE_32K("doubao-1.5-lite-32k-250115"),
DEEPSEEK_R1("deepseek-r1-250120"),
DEEPSEEK_R1_DISTILL_QWEN_32B("deepseek-r1-distill-qwen-32b-250120"),
DEEPSEEK_R1_DISTILL_QWEN_7B("deepseek-r1-distill-qwen-7b-250120"),
DEEPSEEK_V3("deepseek-v3-241226"),
DOUBAO_PRO_4K_240515("doubao-pro-4k-240515"),
DOUBAO_PRO_4K_CHARACTER_240728("doubao-pro-4k-character-240728"),
DOUBAO_PRO_4K_FUNCTIONCALL_240615("doubao-pro-4k-functioncall-240615"),
DOUBAO_PRO_4K_BROWSING_240524("doubao-pro-4k-browsing-240524"),
DOUBAO_PRO_32K_241215("doubao-pro-32k-241215"),
DOUBAO_PRO_32K_FUNCTIONCALL_241028("doubao-pro-32k-functioncall-241028"),
DOUBAO_PRO_32K_BROWSING_241115("doubao-pro-32k-browsing-241115"),
DOUBAO_PRO_32K_CHARACTER_241215("doubao-pro-32k-character-241215"),
DOUBAO_PRO_128K_240628("doubao-pro-128k-240628"),
DOUBAO_PRO_256K_240828("doubao-pro-256k-240828"),
DOUBAO_LITE_4K_240328("doubao-lite-4k-240328"),
DOUBAO_LITE_4K_PRETRAIN_CHARACTER_240516("doubao-lite-4k-pretrain-character-240516"),
DOUBAO_LITE_32K_240828("doubao-lite-32k-240828"),
DOUBAO_LITE_32K_CHARACTER_241015("doubao-lite-32k-character-241015"),
DOUBAO_LITE_128K_240828("240828"),
MOONSHOT_V1_8K("moonshot-v1-8k"),
MOONSHOT_V1_32K("moonshot-v1-32k"),
MOONSHOT_V1_128K("moonshot-v1-128k"),
CHATGLM3_130B_FC("chatglm3-130b-fc-v1.0"),
CHATGLM3_130_FIN("chatglm3-130-fin-v1.0-update"),
MISTRAL_7B("mistral-7b-instruct-v0.2"),
DOUBAO_1_5_VISION_PRO_32K("doubao-1.5-vision-pro-32k-250115"),
DOUBAO_VISION_PRO_32K("doubao-vision-pro-32k-241008"),
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");
private final String model;
Doubao(String model) {
this.model = model;
}
public String getModel() {
return model;
}
}
// Grok的模型
public enum Grok {
GROK_2_1212("grok-2-1212"),
GROK_2_VISION_1212("grok-2-vision-1212"),
GROK_BETA("grok-beta"),
GROK_VISION_BETA("grok-vision-beta");
private final String model;
Grok(String model) {
this.model = model;
}
public String getModel() {
return model;
}
}
}

View File

@@ -0,0 +1,113 @@
/*
* 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.ai.core;
import java.util.Map;
/**
* AI配置类
*
* @author elichow
* @since 5.8.38
*/
public interface AIConfig {
/**
* 获取模型(厂商)名称
*
* @return 模型(厂商)名称
* @since 5.8.38
*/
default String getModelName() {
return this.getClass().getSimpleName();
}
/**
* 设置apiKey
*
* @param apiKey apiKey
* @since 5.8.38
*/
void setApiKey(String apiKey);
/**
* 获取apiKey
*
* @return apiKey
* @since 5.8.38
*/
String getApiKey();
/**
* 设置apiUrl
*
* @param apiUrl api请求地址
* @since 5.8.38
*/
void setApiUrl(String apiUrl);
/**
* 获取apiUrl
*
* @return apiUrl
* @since 5.8.38
*/
String getApiUrl();
/**
* 设置model
*
* @param model model
* @since 5.8.38
*/
void setModel(String model);
/**
* 返回model
*
* @return model
* @since 5.8.38
*/
String getModel();
/**
* 设置动态参数
*
* @param key 参数字段
* @param value 参数值
* @since 5.8.38
*/
void putAdditionalConfigByKey(String key, Object value);
/**
* 获取动态参数
*
* @param key 参数字段
* @return 参数值
* @since 5.8.38
*/
Object getAdditionalConfigByKey(String key);
/**
* 获取动态参数列表
*
* @return 参数列表Map
* @since 5.8.38
*/
Map<String, Object> getAdditionalConfigMap();
}

View File

@@ -0,0 +1,118 @@
/*
* 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.ai.core;
import java.lang.reflect.Constructor;
/**
* 用于AIConfig的创建创建同时支持链式设置参数
*
* @author elichow
* @since 5.8.38
*/
public class AIConfigBuilder {
private final AIConfig config;
/**
* 构造
*
* @param modelName 模型厂商的名称(注意不是指具体的模型)
*/
public AIConfigBuilder(final String modelName) {
try {
// 获取配置类
final Class<? extends AIConfig> configClass = AIConfigRegistry.getConfigClass(modelName);
if (configClass == null) {
throw new IllegalArgumentException("Unsupported model: " + modelName);
}
// 使用反射创建实例
final Constructor<? extends AIConfig> constructor = configClass.getDeclaredConstructor();
config = constructor.newInstance();
} catch (final Exception e) {
throw new RuntimeException("Failed to create AIConfig instance", e);
}
}
/**
* 设置apiKey
*
* @param apiKey apiKey
* @return config
* @since 5.8.38
*/
public synchronized AIConfigBuilder setApiKey(final String apiKey) {
if (apiKey != null) {
config.setApiKey(apiKey);
}
return this;
}
/**
* 设置AI模型请求API接口的地址不设置为默认值
*
* @param apiUrl API接口地址
* @return config
* @since 5.8.38
*/
public synchronized AIConfigBuilder setApiUrl(final String apiUrl) {
if (apiUrl != null) {
config.setApiUrl(apiUrl);
}
return this;
}
/**
* 设置具体的model不设置为默认值
*
* @param model 具体model的名称
* @return config
* @since 5.8.38
*/
public synchronized AIConfigBuilder setModel(final String model) {
if (model != null) {
config.setModel(model);
}
return this;
}
/**
* 动态设置Request请求体中的属性字段每个模型功能支持的字段请参照对应的官方文档
*
* @param key Request中的支持的属性名
* @param value 设置的属性值
* @return config
* @since 5.8.38
*/
public AIConfigBuilder putAdditionalConfig(final String key, final Object value) {
if (value != null) {
config.putAdditionalConfigByKey(key, value);
}
return this;
}
/**
* 返回config实例
*
* @return config
* @since 5.8.38
*/
public AIConfig build() {
return config;
}
}

View File

@@ -0,0 +1,52 @@
/*
* 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.ai.core;
import cn.hutool.core.util.ServiceLoaderUtil;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.concurrent.ConcurrentHashMap;
/**
* AIConfig实现类的加载器
*
* @author elichow
* @since 5.8.38
*/
public class AIConfigRegistry {
private static final Map<String, Class<? extends AIConfig>> configClasses = new ConcurrentHashMap<>();
// 加载所有 AIConfig 实现类
static {
final ServiceLoader<AIConfig> loader = ServiceLoaderUtil.load(AIConfig.class);
for (final AIConfig config : loader) {
configClasses.put(config.getModelName().toLowerCase(), config.getClass());
}
}
/**
* 根据模型名称获取AIConfig实现类
*
* @param modelName 模型名称
* @return AIConfig实现类
*/
public static Class<? extends AIConfig> getConfigClass(final String modelName) {
return configClasses.get(modelName.toLowerCase());
}
}

View File

@@ -0,0 +1,47 @@
/*
* 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.ai.core;
import java.util.List;
/**
* 模型公共的API功能特有的功能在model.xx.XXService下定义
*
* @author elichow
* @since 5.8.38
*/
public interface AIService {
/**
* 对话
*
* @param prompt user题词
* @return AI回答
* @since 5.8.38
*/
String chat(String prompt);
/**
* 对话
*
* @param messages 由目前为止的对话组成的消息列表可以设置rolecontent。详细参考官方文档
* @return AI回答
* @since 5.8.38
*/
String chat(final List<Message> messages);
}

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.
*/
package cn.hutool.ai.core;
/**
* 用于加载AI服务,每一个通过SPI创建的AI服务都要实现此接口
*
* @author elichow
* @since 5.8.38
*/
public interface AIServiceProvider {
/**
* 获取AI服务名称
*
* @return AI服务名称
* @since 5.8.38
*/
String getServiceName();
/**
* 创建AI服务实例
*
* @param config AIConfig配置
* @param <T> AIService实现类
* @return AI服务实例
* @since 5.8.38
*/
<T extends AIService> T create(final AIConfig config);
}

View File

@@ -0,0 +1,105 @@
/*
* 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.ai.core;
import cn.hutool.ai.AIException;
import cn.hutool.http.*;
import java.util.Map;
/**
* 基础AIService包含基公共参数和公共方法
*
* @author elichow
* @since 5.8.38
*/
public class BaseAIService {
protected final AIConfig config;
/**
* 构造方法
*
* @param config AI配置
*/
public BaseAIService(final AIConfig config) {
this.config = config;
}
/**
* 发送Get请求
* @param endpoint 请求节点
* @return 请求响应
*/
protected HttpResponse sendGet(final String endpoint) {
//链式构建请求
try {
//设置超时3分钟
return HttpRequest.get(config.getApiUrl() + endpoint)
.header(Header.ACCEPT, "application/json")
.header(Header.AUTHORIZATION, "Bearer " + config.getApiKey())
.timeout(180000)
.execute();
} catch (final AIException e) {
throw new AIException("Failed to send GET request: " + e.getMessage(), e);
}
}
/**
* 发送Post请求
* @param endpoint 请求节点
* @param paramJson 请求参数json
* @return 请求响应
*/
protected HttpResponse sendPost(final String endpoint, final String paramJson) {
//链式构建请求
try {
return HttpRequest.post(config.getApiUrl() + endpoint)
.header(Header.CONTENT_TYPE, "application/json")
.header(Header.ACCEPT, "application/json")
.header(Header.AUTHORIZATION, "Bearer " + config.getApiKey())
.body(paramJson)
.timeout(180000)
.execute();
} catch (final AIException e) {
throw new AIException("Failed to send POST request" + e.getMessage(), e);
}
}
/**
* 发送表单请求
* @param endpoint 请求节点
* @param paramMap 请求参数map
* @return 请求响应
*/
protected HttpResponse sendFormData(final String endpoint, final Map<String, Object> paramMap) {
//链式构建请求
try {
//设置超时3分钟
return HttpRequest.post(config.getApiUrl() + endpoint)
.header(Header.CONTENT_TYPE, "multipart/form-data")
.header(Header.ACCEPT, "application/json")
.header(Header.AUTHORIZATION, "Bearer " + config.getApiKey())
.form(paramMap)
.timeout(180000)
.execute();
} catch (final AIException e) {
throw new AIException("Failed to send POST request" + e.getMessage(), e);
}
}
}

View File

@@ -0,0 +1,84 @@
/*
* 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.ai.core;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Config基础类定义模型配置的基本属性
*
* @author elichow
* @since 5.8.38
*/
public class BaseConfig implements AIConfig {
//apiKey
protected volatile String apiKey;
//API请求地址
protected volatile String apiUrl;
//具体模型
protected volatile String model;
//动态扩展字段
protected Map<String, Object> additionalConfig = new ConcurrentHashMap<>();
@Override
public void setApiKey(final String apiKey) {
this.apiKey = apiKey;
}
@Override
public String getApiKey() {
return apiKey;
}
@Override
public void setApiUrl(final String apiUrl) {
this.apiUrl = apiUrl;
}
@Override
public String getApiUrl() {
return apiUrl;
}
@Override
public void setModel(final String model) {
this.model = model;
}
@Override
public String getModel() {
return model;
}
@Override
public void putAdditionalConfigByKey(final String key, final Object value) {
this.additionalConfig.put(key, value);
}
@Override
public Object getAdditionalConfigByKey(final String key) {
return additionalConfig.get(key);
}
@Override
public Map<String, Object> getAdditionalConfigMap() {
return new ConcurrentHashMap<>(additionalConfig);
}
}

View File

@@ -0,0 +1,59 @@
/*
* 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.ai.core;
/**
* 公共Message类
*
* @author elichow
* @since 5.8.38
*/
public class Message {
//角色 注意如果设置系统消息请放在messages列表的第一位
private final String role;
//内容
private final Object content;
/**
* 构造
*
* @param role 角色
* @param content 内容
*/
public Message(final String role, final Object content) {
this.role = role;
this.content = content;
}
/**
* 获取角色
*
* @return 角色
*/
public String getRole() {
return role;
}
/**
* 获取内容
*
* @return 内容
*/
public Object getContent() {
return content;
}
}

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.
*/
/**
* AI相关基础类
*
* @author elichow
* @since 5.8.38
*/
package cn.hutool.ai.core;

View File

@@ -0,0 +1,27 @@
/*
* 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.ai.model.deepseek;
/**
* deepSeek公共类
*
* @author elichow
* @since 5.8.38
*/
public class DeepSeekCommon {
}

View File

@@ -0,0 +1,49 @@
/*
* 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.ai.model.deepseek;
import cn.hutool.ai.Models;
import cn.hutool.ai.core.BaseConfig;
/**
* DeepSeek配置类初始化API接口地址设置默认的模型
*
* @author elichow
* @since 5.8.38
*/
public class DeepSeekConfig extends BaseConfig {
private final String API_URL = "https://api.deepseek.com";
private final String DEFAULT_MODEL = Models.DeepSeek.DEEPSEEK_CHAT.getModel();
public DeepSeekConfig() {
setApiUrl(API_URL);
setModel(DEFAULT_MODEL);
}
public DeepSeekConfig(String apiKey) {
this();
setApiKey(apiKey);
}
@Override
public String getModelName() {
return "deepSeek";
}
}

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.ai.model.deepseek;
import cn.hutool.ai.core.AIConfig;
import cn.hutool.ai.core.AIServiceProvider;
/**
* 创建DeepSeek服务实现类
*
* @author elichow
* @since 5.8.38
*/
public class DeepSeekProvider implements AIServiceProvider {
@Override
public String getServiceName() {
return "deepSeek";
}
@Override
public DeepSeekService create(final AIConfig config) {
return new DeepSeekServiceImpl(config);
}
}

View File

@@ -0,0 +1,53 @@
/*
* 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.ai.model.deepseek;
import cn.hutool.ai.core.AIService;
/**
* deepSeek支持的扩展接口
*
* @author elichow
* @since 5.8.38
*/
public interface DeepSeekService extends AIService {
/**
* 模型beta功能
*
* @param prompt 题词
* @return AI的回答
* @since 5.8.38
*/
String beta(String prompt);
/**
* 列出所有模型列表
*
* @return model列表
* @since 5.8.38
*/
String models();
/**
* 查询余额
*
* @return 余额
* @since 5.8.38
*/
String balance();
}

View File

@@ -0,0 +1,117 @@
/*
* 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.ai.model.deepseek;
import cn.hutool.ai.core.AIConfig;
import cn.hutool.ai.core.BaseAIService;
import cn.hutool.ai.core.Message;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONUtil;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* DeepSeek服务AI具体功能的实现
*
* @author elichow
* @since 5.8.38
*/
public class DeepSeekServiceImpl extends BaseAIService implements DeepSeekService {
//对话补全
private final String CHAT_ENDPOINT = "/chat/completions";
//FIM补全beta
private final String BETA_ENDPOINT = "/beta/completions";
//列出模型
private final String MODELS_ENDPOINT = "/models";
//余额查询
private final String BALANCE_ENDPOINT = "/user/balance";
/**
* 构造函数
*
* @param config AI配置
*/
public DeepSeekServiceImpl(final AIConfig config) {
//初始化DeepSeek客户端
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);
final HttpResponse response = sendPost(CHAT_ENDPOINT, paramJson);
return response.body();
}
@Override
public String beta(final String prompt) {
final String paramJson = buildBetaRequestBody(prompt);
final HttpResponse response = sendPost(BETA_ENDPOINT, paramJson);
return response.body();
}
@Override
public String models() {
final HttpResponse response = sendGet(MODELS_ENDPOINT);
return response.body();
}
@Override
public String balance() {
final HttpResponse response = sendGet(BALANCE_ENDPOINT);
return response.body();
}
// 构建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);
}
// 构建beta请求体
private String buildBetaRequestBody(final String prompt) {
// 定义消息结构
//使用JSON工具
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

@@ -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.
*/
/**
* 对deepSeek的封装实现
*
* @author elichow
* @since 5.8.38
*/
package cn.hutool.ai.model.deepseek;

View File

@@ -0,0 +1,111 @@
/*
* 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.ai.model.doubao;
/**
* doubao公共类
*
* @author elichow
* @since 5.8.38
*/
public class DoubaoCommon {
//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 {
AUTO("auto"),
LOW("low"),
HIGH("high");
private final String detail;
DoubaoVision(String detail) {
this.detail = detail;
}
public String getDetail() {
return detail;
}
}
//doubao视频生成参数
public enum DoubaoVideo {
//宽高比例
RATIO_16_9("--rt", "16:9"),//[1280, 720]
RATIO_4_3("--rt", "4:3"),//[960, 720]
RATIO_1_1("--rt", "1:1"),//[720, 720]
RATIO_3_4("--rt", "3:4"),//[720, 960]
RATIO_9_16("--rt", "9:16"),//[720, 1280]
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(String type, 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,49 @@
/*
* 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.ai.model.doubao;
import cn.hutool.ai.Models;
import cn.hutool.ai.core.BaseConfig;
/**
* Doubao配置类初始化API接口地址设置默认的模型
*
* @author elichow
* @since 5.8.38
*/
public class DoubaoConfig extends BaseConfig {
private final String API_URL = "https://ark.cn-beijing.volces.com/api/v3";
private final String DEFAULT_MODEL = Models.Doubao.DOUBAO_1_5_LITE_32K.getModel();
public DoubaoConfig() {
setApiUrl(API_URL);
setModel(DEFAULT_MODEL);
}
public DoubaoConfig(String apiKey) {
this();
setApiKey(apiKey);
}
@Override
public String getModelName() {
return "doubao";
}
}

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.ai.model.doubao;
import cn.hutool.ai.core.AIConfig;
import cn.hutool.ai.core.AIServiceProvider;
/**
* 创建Doubap服务实现类
*
* @author elichow
* @since 5.8.38
*/
public class DoubaoProvider implements AIServiceProvider {
@Override
public String getServiceName() {
return "doubao";
}
@Override
public DoubaoService create(final AIConfig config) {
return new DoubaoServiceImpl(config);
}
}

View File

@@ -0,0 +1,195 @@
/*
* 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.ai.model.doubao;
import cn.hutool.ai.core.AIService;
import cn.hutool.ai.core.Message;
import java.util.List;
/**
* doubao支持的扩展接口
*
* @author elichow
* @since 5.8.38
*/
public interface DoubaoService extends AIService {
/**
* 图像理解:模型会依据传入的图片信息以及问题,给出回复。
*
* @param prompt 提问
* @param images 图片列表/或者图片Base64编码图片列表(URI形式)
* @param detail 手动设置图片的质量取值范围high、low、auto,默认为auto
* @return AI回答
* @since 5.8.38
*/
String chatVision(String prompt, final List<String> images, String detail);
/**
* 图像理解:模型会依据传入的图片信息以及问题,给出回复。
*
* @param prompt 提问
* @param images 传入的图片列表地址/或者图片Base64编码图片列表(URI形式)
* @return AI回答
* @since 5.8.38
*/
default String chatVision(String prompt, final List<String> images) {
return chatVision(prompt, images, DoubaoCommon.DoubaoVision.AUTO.getDetail());
}
/**
* 创建视频生成任务
* 注意调用该方法时配置config中的model为您创建的推理接入点EndpointID。详细参考官方文档
*
* @param text 文本提示词
* @param image 图片/或者图片Base64编码图片(URI形式)
* @param videoParams 视频参数列表
* @return 生成任务id
* @since 5.8.38
*/
String videoTasks(String text, String image, final List<DoubaoCommon.DoubaoVideo> videoParams);
/**
* 创建视频生成任务
* 注意调用该方法时配置config中的model为您创建的推理接入点EndpointID。详细参考官方文档
*
* @param text 文本提示词
* @param image 图片/或者图片Base64编码图片(URI形式)
* @return 生成任务id
* @since 5.8.38
*/
default String videoTasks(String text, String image) {
return videoTasks(text, image, null);
}
/**
* 查询视频生成任务信息
*
* @param taskId 通过创建生成视频任务返回的生成任务id
* @return 生成任务信息
* @since 5.8.38
*/
String getVideoTasksInfo(String taskId);
/**
* 文本向量化
*
* @param input 需要向量化的内容列表,支持中文、英文
* @return 处理后的向量信息
* @since 5.8.38
*/
String embeddingText(String[] input);
/**
* 图文向量化:仅支持单一文本、单张图片或文本与图片的组合输入(即一段文本 + 一张图片),暂不支持批量文本 / 图片的同时处理
*
* @param text 需要向量化的内容
* @param image 需要向量化的图片地址/或者图片Base64编码图片(URI形式)
* @return 处理后的向量信息
* @since 5.8.38
*/
String embeddingVision(String text, String image);
/**
* 应用(Bot) config中model设置为您创建的应用ID
*
* @param messages 由对话组成的消息列表。如系统人设,背景信息等,用户自定义的信息
* @return AI回答
* @since 5.8.38
*/
String botsChat(final List<Message> messages);
/**
* 分词:可以将文本转换为模型可理解的 token id并返回文本的 tokens 数量、token id、 token 在原始文本中的偏移量等信息
*
* @param text 需要分词的内容列表
* @return 分词结果
* @since 5.8.38
*/
String tokenization(String[] text);
/**
* 批量推理 Chat
* 注意调用该方法时配置config中的model为您创建的批量推理接入点EndpointID。详细参考官方文档
* 该方法不支持流式
*
* @param prompt chat内容
* @return AI回答
* @since 5.8.38
*/
String batchChat(String prompt);
/**
* 批量推理 Chat
* 注意调用该方法时配置config中的model为您创建的批量推理接入点EndpointID。详细参考官方文档
* 该方法不支持流式
*
* @param messages 由对话组成的消息列表。如系统人设,背景信息等,用户自定义的信息
* @return AI回答
* @since 5.8.38
*/
String batchChat(final List<Message> messages);
/**
* 创建上下文缓存: 创建上下文缓存,获得缓存 id字段后在上下文缓存对话 API中使用。
* 注意调用该方法时配置config中的model为您创建的推理接入点EndpointID,
* 推理接入点中使用的模型需要在模型管理中开启缓存功能。详细参考官方文档
*
* @param messages 由对话组成的消息列表。如系统人设,背景信息等,用户自定义的信息
* @param mode 上下文缓存的类型,详细参考官方文档 默认为session
* @return 返回的缓存id
* @since 5.8.38
*/
String createContext(final List<Message> messages, String mode);
/**
* 创建上下文缓存: 创建上下文缓存,获得缓存 id字段后在上下文缓存对话 API中使用。
* 注意调用该方法时配置config中的model为您创建的推理接入点EndpointID,
* 推理接入点中使用的模型需要在模型管理中开启缓存功能。详细参考官方文档
*
* @param messages 由对话组成的消息列表。如系统人设,背景信息等,用户自定义的信息
* @return 返回的缓存id
* @since 5.8.38
*/
default String createContext(final List<Message> messages) {
return createContext(messages, DoubaoCommon.DoubaoContext.SESSION.getMode());
}
/**
* 上下文缓存对话: 向大模型发起带上下文缓存的请求
* 注意配置config中的model可以为您创建的推理接入点EndpointID也可以是支持chat的model
*
* @param prompt 对话的内容题词
* @param contextId 创建上下文缓存后获取的缓存id
* @return AI的回答
* @since 5.8.38
*/
String chatContext(String prompt, String contextId);
/**
* 上下文缓存对话: 向大模型发起带上下文缓存的请求
* 注意配置config中的model可以为您创建的推理接入点EndpointID也可以是支持chat的model
*
* @param messages 对话的信息 不支持最后一个元素的role设置为assistant。如使用session 缓存mode设置为session传入最新一轮对话的信息无需传入历史信息
* @param contextId 创建上下文缓存后获取的缓存id
* @return AI的回答
* @since 5.8.38
*/
String chatContext(final List<Message> messages, String contextId);
}

View File

@@ -0,0 +1,354 @@
/*
* 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.ai.model.doubao;
import cn.hutool.ai.core.AIConfig;
import cn.hutool.ai.core.BaseAIService;
import cn.hutool.ai.core.Message;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONUtil;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Doubao服务AI具体功能的实现
*
* @author elichow
* @since 5.8.38
*/
public class DoubaoServiceImpl extends BaseAIService implements DoubaoService {
//对话
private final String CHAT_ENDPOINT = "/chat/completions";
//文本向量化
private final String EMBEDDING_TEXT = "/embeddings";
//图文向量化
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 CREATE_VIDEO = "/contents/generations/tasks";
public DoubaoServiceImpl(final AIConfig config) {
//初始化doubao客户端
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);
final HttpResponse response = sendPost(CHAT_ENDPOINT, paramJson);
return response.body();
}
@Override
public String chatVision(String prompt, final List<String> images, String detail) {
String paramJson = buildChatVisionRequestBody(prompt, images, detail);
final HttpResponse response = sendPost(CHAT_ENDPOINT, paramJson);
return response.body();
}
@Override
public String videoTasks(String text, String image, final List<DoubaoCommon.DoubaoVideo> videoParams) {
String paramJson = buildGenerationsTasksRequestBody(text, image, videoParams);
final HttpResponse response = sendPost(CREATE_VIDEO, paramJson);
return response.body();
}
@Override
public String getVideoTasksInfo(String taskId) {
final HttpResponse response = sendGet(CREATE_VIDEO + "/" + taskId);
return response.body();
}
@Override
public String embeddingText(String[] input) {
String paramJson = buildEmbeddingTextRequestBody(input);
final HttpResponse response = sendPost(EMBEDDING_TEXT, paramJson);
return response.body();
}
@Override
public String embeddingVision(String text, String image) {
String paramJson = buildEmbeddingVisionRequestBody(text, image);
final HttpResponse response = sendPost(EMBEDDING_VISION, paramJson);
return response.body();
}
@Override
public String botsChat(final List<Message> messages) {
String paramJson = buildBotsChatRequestBody(messages);
final HttpResponse response = sendPost(BOTS_CHAT, paramJson);
return response.body();
}
@Override
public String tokenization(String[] text) {
String paramJson = buildTokenizationRequestBody(text);
final HttpResponse response = sendPost(TOKENIZATION, paramJson);
return response.body();
}
@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);
final HttpResponse response = sendPost(BATCH_CHAT, paramJson);
return response.body();
}
@Override
public String createContext(final List<Message> messages, String mode) {
String paramJson = buildCreateContextRequest(messages, mode);
final HttpResponse response = sendPost(CREATE_CONTEXT, paramJson);
return response.body();
}
@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);
final HttpResponse response = sendPost(CHAT_CONTEXT, paramJson);
return response.body();
}
// 构建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);
}
//构建chatVision请求体
private String buildChatVisionRequestBody(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("model", config.getModel());
paramMap.put("messages", messages);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return JSONUtil.toJsonStr(paramMap);
}
//构建文本向量化请求体
private String buildEmbeddingTextRequestBody(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(String text, 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 String buildTokenizationRequestBody(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 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.putAll(config.getAdditionalConfigMap());
return JSONUtil.toJsonStr(paramMap);
}
//构建上下文缓存对话请求体
private String buildChatContentRequestBody(final List<Message> messages, 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 String buildGenerationsTasksRequestBody(String text, 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.isNotBlank(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 != null && !textMap.isEmpty()) {
int textIndex = content.indexOf(textMap);
StringBuilder textBuilder = new StringBuilder(text);
for (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 {
//如果没有文本参数就重新增加
StringBuilder textBuilder = new StringBuilder();
for (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);
}
}

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.
*/
/**
* 对doubao的封装实现
*
* @author elichow
* @since 5.8.38
*/
package cn.hutool.ai.model.doubao;

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.
*/
package cn.hutool.ai.model.grok;
/**
* grok公共类
*
* @author elichow
* @since 5.8.38
*/
public class GrokCommon {
//grok视觉参数
public enum GrokVision {
AUTO("auto"),
LOW("low"),
HIGH("high");
private final String detail;
GrokVision(String detail) {
this.detail = detail;
}
public String getDetail() {
return detail;
}
}
}

View File

@@ -0,0 +1,50 @@
/*
* 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.ai.model.grok;
import cn.hutool.ai.Models;
import cn.hutool.ai.core.BaseConfig;
/**
* Grok配置类初始化API接口地址设置默认的模型
*
* @author elichow
* @since 5.8.38
*/
public class GrokConfig extends BaseConfig {
private final String API_URL = "https://api.x.ai/v1";
private final String DEFAULT_MODEL = Models.Grok.GROK_2_1212.getModel();
public GrokConfig() {
setApiUrl(API_URL);
setModel(DEFAULT_MODEL);
}
public GrokConfig(String apiKey) {
this();
setApiKey(apiKey);
}
@Override
public String getModelName() {
return "grok";
}
}

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.ai.model.grok;
import cn.hutool.ai.core.AIConfig;
import cn.hutool.ai.core.AIServiceProvider;
/**r
* 创建Grok服务实现类
*
* @author elichow
* @since 5.8.38
*/
public class GrokProvider implements AIServiceProvider {
@Override
public String getServiceName() {
return "grok";
}
@Override
public GrokService create(final AIConfig config) {
return new GrokServiceImpl(config);
}
}

View File

@@ -0,0 +1,115 @@
/*
* 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.ai.model.grok;
import cn.hutool.ai.core.AIService;
import java.util.List;
/**
* grok支持的扩展接口
*
* @author elichow
* @since 5.8.38
*/
public interface GrokService extends AIService {
/**
* 创建消息回复
*
* @param prompt 题词
* @param maxToken 最大token
* @return AI回答
* @since 5.8.38
*/
String message(String prompt, int maxToken);
/**
* 图像理解:模型会依据传入的图片信息以及问题,给出回复。
*
* @param prompt 题词
* @param images 图片列表/或者图片Base64编码图片列表(URI形式)
* @param detail 手动设置图片的质量取值范围high、low、auto,默认为auto
* @return AI回答
* @since 5.8.38
*/
String chatVision(String prompt, final List<String> images, String detail);
/**
* 图像理解:模型会依据传入的图片信息以及问题,给出回复。
*
* @param prompt 题词
* @param images 传入的图片列表地址/或者图片Base64编码图片列表(URI形式)
* @return AI回答
* @since 5.8.38
*/
default String chatVision(String prompt, final List<String> images) {
return chatVision(prompt, images, GrokCommon.GrokVision.AUTO.getDetail());
}
/**
* 列出所有model列表
*
* @return model列表
* @since 5.8.38
*/
String models();
/**
* 获取模型信息
*
* @param modelId model ID
* @return model信息
* @since 5.8.38
*/
String getModel(String modelId);
/**
* 列出所有语言model
*
* @return languageModel列表
* @since 5.8.38
*/
String languageModels();
/**
* 获取语言模型信息
*
* @param modelId model ID
* @return model信息
* @since 5.8.38
*/
String getLanguageModel(String modelId);
/**
* 分词:可以将文本转换为模型可理解的 token 信息
*
* @param text 需要分词的内容
* @return 分词结果
* @since 5.8.38
*/
String tokenizeText(String text);
/**
* 从延迟对话中获取结果
*
* @param requestId 延迟对话中的延迟请求ID
* @return AI回答
* @since 5.8.38
*/
String deferredCompletion(String requestId);
}

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.ai.model.grok;
import cn.hutool.ai.core.AIConfig;
import cn.hutool.ai.core.BaseAIService;
import cn.hutool.ai.core.Message;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONUtil;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Grok服务AI具体功能的实现
*
* @author elichow
* @since 5.8.38
*/
public class GrokServiceImpl extends BaseAIService implements GrokService {
//对话补全
private final String CHAT_ENDPOINT = "/chat/completions";
//创建消息回复
private final String MESSAGES = "/messages";
//列出模型
private final String MODELS_ENDPOINT = "/models";
//列出语言模型
private final String LANGUAGE_MODELS = "/language-models";
//分词
private final String TOKENIZE_TEXT = "/tokenize-text";
//获取延迟对话
private final String DEFERRED_COMPLETION = "/chat/deferred-completion";
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);
final HttpResponse response = sendPost(CHAT_ENDPOINT, paramJson);
return response.body();
}
@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));
String paramJson = buildMessageRequestBody(messages, maxToken);
final HttpResponse response = sendPost(MESSAGES, paramJson);
return response.body();
}
@Override
public String chatVision(String prompt, final List<String> images, String detail) {
String paramJson = buildChatVisionRequestBody(prompt, images, detail);
final HttpResponse response = sendPost(CHAT_ENDPOINT, paramJson);
return response.body();
}
@Override
public String models() {
final HttpResponse response = sendGet(MODELS_ENDPOINT);
return response.body();
}
@Override
public String getModel(String modelId) {
final HttpResponse response = sendGet(MODELS_ENDPOINT + "/" + modelId);
return response.body();
}
@Override
public String languageModels() {
final HttpResponse response = sendGet(LANGUAGE_MODELS);
return response.body();
}
@Override
public String getLanguageModel(String modelId) {
final HttpResponse response = sendGet(LANGUAGE_MODELS + "/" + modelId);
return response.body();
}
@Override
public String tokenizeText(String text) {
String paramJson = buildTokenizeRequestBody(text);
final HttpResponse response = sendPost(TOKENIZE_TEXT, paramJson);
return response.body();
}
@Override
public String deferredCompletion(String requestId) {
final HttpResponse response = sendGet(DEFERRED_COMPLETION + "/" + requestId);
return response.body();
}
// 构建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);
}
//构建chatVision请求体
private String buildChatVisionRequestBody(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("model", config.getModel());
paramMap.put("messages", messages);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return JSONUtil.toJsonStr(paramMap);
}
//构建消息回复请求体
private String buildMessageRequestBody(final List<Message> messages, int maxToken) {
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("model", config.getModel());
paramMap.put("messages", messages);
paramMap.put("max_tokens", maxToken);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return JSONUtil.toJsonStr(paramMap);
}
//构建分词请求体
private String buildTokenizeRequestBody(String text) {
//使用JSON工具
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("model", config.getModel());
paramMap.put("text", text);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
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.
*/
/**
* 对grok的封装实现
*
* @author elichow
* @since 5.8.38
*/
package cn.hutool.ai.model.grok;

View File

@@ -0,0 +1,86 @@
/*
* 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.ai.model.openai;
/**
* openai公共类
*
* @author elichow
* @since 5.8.38
*/
public class OpenaiCommon {
//openai推理参数
public enum OpenaiReasoning {
LOW("low"),
MEDIUM("medium"),
HIGH("high");
private final String effort;
OpenaiReasoning(String effort) {
this.effort = effort;
}
public String getEffort() {
return effort;
}
}
//openai视觉参数
public enum OpenaiVision {
AUTO("auto"),
LOW("low"),
HIGH("high");
private final String detail;
OpenaiVision(String detail) {
this.detail = detail;
}
public String getDetail() {
return detail;
}
}
//openai音频参数
public enum OpenaiSpeech {
ALLOY("alloy"),
ASH("ash"),
CORAL("coral"),
ECHO("echo"),
FABLE("fable"),
ONYX("onyx"),
NOVA("nova"),
SAGE("sage"),
SHIMMER("shimmer");
private final String voice;
OpenaiSpeech(String voice) {
this.voice = voice;
}
public String getVoice() {
return voice;
}
}
}

View File

@@ -0,0 +1,49 @@
/*
* 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.ai.model.openai;
import cn.hutool.ai.Models;
import cn.hutool.ai.core.BaseConfig;
/**
* openai配置类初始化API接口地址设置默认的模型
*
* @author elichow
* @since 5.8.38
*/
public class OpenaiConfig extends BaseConfig {
private final String API_URL = "https://api.openai.com/v1";
private final String DEFAULT_MODEL = Models.Openai.GPT_4O.getModel();
public OpenaiConfig() {
setApiUrl(API_URL);
setModel(DEFAULT_MODEL);
}
public OpenaiConfig(String apiKey) {
this();
setApiKey(apiKey);
}
@Override
public String getModelName() {
return "openai";
}
}

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.ai.model.openai;
import cn.hutool.ai.core.AIConfig;
import cn.hutool.ai.core.AIServiceProvider;
/**
* 创建Openai服务实现类
*
* @author elichow
* @since 5.8.38
*/
public class OpenaiProvider implements AIServiceProvider {
@Override
public String getServiceName() {
return "openai";
}
@Override
public OpenaiService create(final AIConfig config) {
return new OpenaiServiceImpl(config);
}
}

View File

@@ -0,0 +1,206 @@
/*
* 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.ai.model.openai;
import cn.hutool.ai.core.AIService;
import cn.hutool.ai.core.Message;
import java.io.File;
import java.io.InputStream;
import java.util.List;
/**
* openai支持的扩展接口
*
* @author elichow
* @since 5.8.38
*/
public interface OpenaiService extends AIService {
/**
* 图像理解:模型会依据传入的图片信息以及问题,给出回复。
*
* @param prompt 题词
* @param images 图片列表/或者图片Base64编码图片列表(URI形式)
* @param detail 手动设置图片的质量取值范围high、low、auto,默认为auto
* @return AI回答
* @since 5.8.38
*/
String chatVision(String prompt, final List<String> images, String detail);
/**
* 图像理解:模型会依据传入的图片信息以及问题,给出回复。
*
* @param prompt 题词
* @param images 传入的图片列表地址/或者图片Base64编码图片列表(URI形式)
* @return AI回答
* @since 5.8.38
*/
default String chatVision(String prompt, final List<String> images) {
return chatVision(prompt, images, OpenaiCommon.OpenaiVision.AUTO.getDetail());
}
/**
* 文生图 请设置config中model为支持图片功能的模型 DALL·E系列
*
* @param prompt 题词
* @return 包含生成图片的url
* @since 5.8.38
*/
String imagesGenerations(String prompt);
/**
* 图片编辑 该方法仅支持 DALL·E 2 model
*
* @param prompt 题词
* @param image 需要编辑的图像必须是 PNG 格式
* @param mask 如果提供,则是一个与编辑图像大小相同的遮罩图像应该是灰度图,白色表示需要编辑的区域,黑色表示不需要编辑的区域。
* @return 包含生成图片的url
* @since 5.8.38
*/
String imagesEdits(String prompt, final File image, final File mask);
/**
* 图片编辑 该方法仅支持 DALL·E 2 model
*
* @param prompt 题词
* @param image 需要编辑的图像必须是 PNG 格式
* @return 包含生成图片的url
* @since 5.8.38
*/
default String imagesEdits(String prompt, final File image) {
return imagesEdits(prompt, image, null);
}
/**
* 图片变形 该方法仅支持 DALL·E 2 model
*
* @param image 需要变形的图像必须是 PNG 格式
* @return 包含生成图片的url
* @since 5.8.38
*/
String imagesVariations(final File image);
/**
* TTS文本转语音 请设置config中model为支持TTS功能的模型 TTS系列
*
* @param input 需要转成语音的文本
* @param voice AI的音色
* @return 返回的音频mp3文件流
* @since 5.8.38
*/
InputStream textToSpeech(String input, final OpenaiCommon.OpenaiSpeech voice);
/**
* TTS文本转语音 请设置config中model为支持TTS功能的模型 TTS系列
*
* @param input 需要转成语音的文本
* @return 返回的音频mp3文件流
* @since 5.8.38
*/
default InputStream textToSpeech(String input) {
return textToSpeech(input, OpenaiCommon.OpenaiSpeech.ALLOY);
}
/**
* STT音频转文本 请设置config中model为支持STT功能的模型 whisper
*
* @param file 需要转成文本的音频文件
* @return 返回的文本内容
* @since 5.8.38
*/
String speechToText(final File file);
/**
* 文本向量化 请设置config中model为支持文本向量化功能的模型 text-embedding系列
*
* @param input 需要向量化的内容
* @return 处理后的向量信息
* @since 5.8.38
*/
String embeddingText(String input);
/**
* 检查文本或图像是否具有潜在的危害性
* 仅支持omni-moderation-latest和text-moderation-latest模型
*
* @param text 需要检查的文本
* @param imgUrl 需要检查的图片地址
* @return AI返回结果
* @since 5.8.38
*/
String moderations(String text, String imgUrl);
/**
* 检查文本是否具有潜在的危害性
* 仅支持omni-moderation-latest和text-moderation-latest模型
*
* @param text 需要检查的文本
* @return AI返回结果
* @since 5.8.38
*/
default String moderations(String text) {
return moderations(text, null);
}
/**
* 推理chat
* 支持o3-mini和o1
*
* @param prompt 对话题词
* @param reasoningEffort 推理程度
* @return AI回答
* @since 5.8.38
*/
String chatReasoning(String prompt, String reasoningEffort);
/**
* 推理chat
* 支持o3-mini和o1
*
* @param prompt 对话题词
* @return AI回答
* @since 5.8.38
*/
default String chatReasoning(String prompt) {
return chatReasoning(prompt, OpenaiCommon.OpenaiReasoning.MEDIUM.getEffort());
}
/**
* 推理chat
* 支持o3-mini和o1
*
* @param messages 消息列表
* @param reasoningEffort 推理程度
* @return AI回答
* @since 5.8.38
*/
String chatReasoning(final List<Message> messages, String reasoningEffort);
/**
* 推理chat
* 支持o3-mini和o1
*
* @param messages 消息列表
* @return AI回答
* @since 5.8.38
*/
default String chatReasoning(final List<Message> messages) {
return chatReasoning(messages, OpenaiCommon.OpenaiReasoning.MEDIUM.getEffort());
}
}

View File

@@ -0,0 +1,308 @@
/*
* 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.ai.model.openai;
import cn.hutool.ai.core.AIConfig;
import cn.hutool.ai.core.BaseAIService;
import cn.hutool.ai.core.Message;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpResponse;
import cn.hutool.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;
/**
* openai服务AI具体功能的实现
*
* @author elichow
* @since 5.8.38
*/
public class OpenaiServiceImpl extends BaseAIService implements OpenaiService {
//对话
private final String CHAT_ENDPOINT = "/chat/completions";
//文生图
private final String IMAGES_GENERATIONS = "/images/generations";
//图片编辑
private final String IMAGES_EDITS = "/images/edits";
//图片变形
private final String IMAGES_VARIATIONS = "/images/variations";
//文本转语音
private final String TTS = "/audio/speech";
//语音转文本
private final String STT = "/audio/transcriptions";
//文本向量化
private final String EMBEDDINGS = "/embeddings";
//检查文本或图片
private final String MODERATIONS = "/moderations";
public OpenaiServiceImpl(final AIConfig config) {
//初始化Openai客户端
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);
final HttpResponse response = sendPost(CHAT_ENDPOINT, paramJson);
return response.body();
}
@Override
public String chatVision(String prompt, final List<String> images, String detail) {
String paramJson = buildChatVisionRequestBody(prompt, images, detail);
final HttpResponse response = sendPost(CHAT_ENDPOINT, paramJson);
return response.body();
}
@Override
public String imagesGenerations(String prompt) {
String paramJson = buildImagesGenerationsRequestBody(prompt);
final HttpResponse response = sendPost(IMAGES_GENERATIONS, paramJson);
return response.body();
}
@Override
public String imagesEdits(String prompt, final File image, final File mask) {
final Map<String, Object> paramMap = buildImagesEditsRequestBody(prompt, image, mask);
final HttpResponse response = sendFormData(IMAGES_EDITS, paramMap);
return response.body();
}
@Override
public String imagesVariations(final File image) {
final Map<String, Object> paramMap = buildImagesVariationsRequestBody(image);
final HttpResponse response = sendFormData(IMAGES_VARIATIONS, paramMap);
return response.body();
}
@Override
public InputStream textToSpeech(String input, final OpenaiCommon.OpenaiSpeech voice) {
String paramJson = buildTTSRequestBody(input, voice.getVoice());
final HttpResponse response = sendPost(TTS, paramJson);
return response.bodyStream();
}
@Override
public String speechToText(final File file) {
final Map<String, Object> paramMap = buildSTTRequestBody(file);
final HttpResponse response = sendFormData(STT, paramMap);
return response.body();
}
@Override
public String embeddingText(String input) {
String paramJson = buildEmbeddingTextRequestBody(input);
final HttpResponse response = sendPost(EMBEDDINGS, paramJson);
return response.body();
}
@Override
public String moderations(String text, String imgUrl) {
String paramJson = buileModerationsRequestBody(text, imgUrl);
final HttpResponse response = sendPost(MODERATIONS, paramJson);
return response.body();
}
@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);
final HttpResponse response = sendPost(CHAT_ENDPOINT, paramJson);
return response.body();
}
// 构建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);
}
//构建chatVision请求体
private String buildChatVisionRequestBody(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) {
final Map<String, Object> imgUrlMap = new HashMap<>();
imgUrlMap.put("type", "image_url");
final Map<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 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);
}
//构建图片编辑请求体
private Map<String, Object> buildImagesEditsRequestBody(String prompt, final File image, final File mask) {
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("model", config.getModel());
paramMap.put("prompt", prompt);
paramMap.put("image", image);
if (mask != null) {
paramMap.put("mask", mask);
}
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return paramMap;
}
//构建图片变形请求体
private Map<String, Object> buildImagesVariationsRequestBody(final File image) {
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("model", config.getModel());
paramMap.put("image", image);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return paramMap;
}
//构建TTS请求体
private String buildTTSRequestBody(String input, String voice) {
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("model", config.getModel());
paramMap.put("input", input);
paramMap.put("voice", voice);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return JSONUtil.toJsonStr(paramMap);
}
//构建STT请求体
private Map<String, Object> buildSTTRequestBody(final File file) {
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("model", config.getModel());
paramMap.put("file", file);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return paramMap;
}
//构建文本向量化请求体
private String buildEmbeddingTextRequestBody(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 buileModerationsRequestBody(String text, String imgUrl) {
//使用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(imgUrl)) {
final Map<String, Object> imgUrlMap = new HashMap<>();
imgUrlMap.put("type", "image_url");
final Map<String, String> urlMap = new HashMap<>();
urlMap.put("url", imgUrl);
imgUrlMap.put("image_url", urlMap);
input.add(imgUrlMap);
}
paramMap.put("input", input);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return JSONUtil.toJsonStr(paramMap);
}
//构建推理请求体
private String buildChatReasoningRequestBody(final List<Message> messages, String reasoningEffort) {
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("model", config.getModel());
paramMap.put("messages", messages);
paramMap.put("reasoning_effort", reasoningEffort);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
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.
*/
/**
* 对openai的封装实现
*
* @author elichow
* @since 5.8.38
*/
package cn.hutool.ai.model.openai;

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.
*/
/**
* 对各个AI大模型的相关封装
*
* @author elichow
* @since 5.8.38
*/
package cn.hutool.ai.model;

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-ai主要用于AI大模型的封装只需要对AI模型最基本的设置即可调用AI大模型。
*
* @author elichow
* @since 5.8.38
*/
package cn.hutool.ai;

View File

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

View File

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

View File

@@ -0,0 +1,41 @@
/*
* 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.
*/
import cn.hutool.ai.AIServiceFactory;
import cn.hutool.ai.ModelName;
import cn.hutool.ai.core.AIConfigBuilder;
import cn.hutool.ai.core.AIService;
import cn.hutool.ai.model.deepseek.DeepSeekService;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertNotNull;
class AIServiceFactoryTest {
String key = "your key";
@Test
void getAIService() {
final AIService aiService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DEEPSEEK.getValue()).setApiKey(key).build());
assertNotNull(aiService);
}
@Test
void testGetAIService() {
final DeepSeekService deepSeekService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DEEPSEEK.getValue()).setApiKey(key).build(), DeepSeekService.class);
assertNotNull(deepSeekService);
}
}

View File

@@ -0,0 +1,87 @@
/*
* 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.
*/
import cn.hutool.ai.AIUtil;
import cn.hutool.ai.ModelName;
import cn.hutool.ai.core.AIConfigBuilder;
import cn.hutool.ai.core.AIService;
import cn.hutool.ai.core.Message;
import cn.hutool.ai.model.deepseek.DeepSeekService;
import cn.hutool.ai.model.doubao.DoubaoService;
import cn.hutool.ai.model.grok.GrokService;
import cn.hutool.ai.model.openai.OpenaiService;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertNotNull;
class AIUtilTest {
String key = "your key";
@Test
void getAIService() {
final DeepSeekService deepSeekService = AIUtil.getAIService(new AIConfigBuilder(ModelName.DEEPSEEK.getValue()).setApiKey(key).build(), DeepSeekService.class);
assertNotNull(deepSeekService);
}
@Test
void testGetAIService() {
final AIService aiService = AIUtil.getAIService(new AIConfigBuilder(ModelName.OPENAI.getValue()).setApiKey(key).build());
assertNotNull(aiService);
}
@Test
void getDeepSeekService() {
final DeepSeekService deepSeekService = AIUtil.getDeepSeekService(new AIConfigBuilder(ModelName.DEEPSEEK.getValue()).setApiKey(key).build());
assertNotNull(deepSeekService);
}
@Test
void getDoubaoService() {
final DoubaoService doubaoService = AIUtil.getDoubaoService(new AIConfigBuilder(ModelName.DOUBAO.getValue()).setApiKey(key).build());
assertNotNull(doubaoService);
}
@Test
void getGrokService() {
final GrokService grokService = AIUtil.getGrokService(new AIConfigBuilder(ModelName.GROK.getValue()).setApiKey(key).build());
assertNotNull(grokService);
}
@Test
void getOpenAIService() {
final OpenaiService openAIService = AIUtil.getOpenAIService(new AIConfigBuilder(ModelName.OPENAI.getValue()).setApiKey(key).build());
assertNotNull(openAIService);
}
@Test
void chat() {
final String chat = AIUtil.chat(new AIConfigBuilder(ModelName.DEEPSEEK.getValue()).setApiKey(key).build(), "写一首赞美我的诗");
assertNotNull(chat);
}
@Test
void testChat() {
final List<Message> messages = new ArrayList<>();
messages.add(new Message("system","你是财神爷,只会说“我是财神”"));
messages.add(new Message("user","你是谁啊?"));
final String chat = AIUtil.chat(new AIConfigBuilder(ModelName.DEEPSEEK.getValue()).setApiKey(key).build(), messages);
assertNotNull(chat);
}
}

View File

@@ -0,0 +1,73 @@
/*
* 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.ai.model.deepseek;
import cn.hutool.ai.AIServiceFactory;
import cn.hutool.ai.ModelName;
import cn.hutool.ai.core.AIConfigBuilder;
import cn.hutool.ai.core.Message;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertNotNull;
class DeepSeekServiceTest {
String key = "your key";
DeepSeekService deepSeekService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DEEPSEEK.getValue()).setApiKey(key).build(),DeepSeekService.class);
@Test
@Disabled
void chat(){
final String chat = deepSeekService.chat("写一个疯狂星期四广告词");
assertNotNull(chat);
}
@Test
@Disabled
void testChat(){
final List<Message> messages = new ArrayList<>();
messages.add(new Message("system","你是个抽象大师,会说很抽象的话,最擅长说抽象的笑话"));
messages.add(new Message("user","给我说一个笑话"));
final String chat = deepSeekService.chat(messages);
assertNotNull(chat);
}
@Test
@Disabled
void beta() {
final String beta = deepSeekService.beta("写一个疯狂星期四广告词");
System.out.println(beta);
}
@Test
@Disabled
void models() {
final String models = deepSeekService.models();
assertNotNull(models);
}
@Test
@Disabled
void balance() {
final String balance = deepSeekService.balance();
assertNotNull(balance);
}
}

View File

@@ -0,0 +1,196 @@
/*
* 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.ai.model.doubao;
import cn.hutool.ai.AIServiceFactory;
import cn.hutool.ai.ModelName;
import cn.hutool.ai.Models;
import cn.hutool.ai.core.AIConfigBuilder;
import cn.hutool.ai.core.Message;
import cn.hutool.core.img.ImgUtil;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import java.awt.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertNotNull;
class DoubaoServiceTest {
String key = "your key";
DoubaoService doubaoService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DOUBAO.getValue()).setModel(Models.Doubao.DOUBAO_1_5_LITE_32K.getModel()).setApiKey(key).build(), DoubaoService.class);
@Test
@Disabled
void chat(){
final String chat = doubaoService.chat("写一个疯狂星期四广告词");
assertNotNull(chat);
}
@Test
@Disabled
void testChat(){
final List<Message> messages = new ArrayList<>();
messages.add(new Message("system","你是个抽象大师,会说很抽象的话,最擅长说抽象的笑话"));
messages.add(new Message("user","给我说一个笑话"));
final String chat = doubaoService.chat(messages);
assertNotNull(chat);
}
@Test
@Disabled
void chatVision() {
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 base64 = ImgUtil.toBase64DataUri(Toolkit.getDefaultToolkit().createImage("your imageUrl"), "png");
final String chatVision = doubaoService.chatVision("图片上有些什么?", Arrays.asList(base64));
assertNotNull(chatVision);
}
@Test
@Disabled
void testChatVision() {
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());
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);
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");
assertNotNull(videoTasks);//cgt-20250306170051-6r9gk
}
@Test
@Disabled
void getVideoTasksInfo() {
//cgt-20250306170051-6r9gk
final DoubaoService doubaoService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DOUBAO.getValue())
.setApiKey(key).build(), DoubaoService.class);
final String videoTasksInfo = doubaoService.getVideoTasksInfo("cgt-20250306170051-6r9gk");
assertNotNull(videoTasksInfo);
}
@Test
@Disabled
void embeddingText() {
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[]{"阿斯顿", "马丁"});
assertNotNull(embeddingText);
}
@Test
@Disabled
void embeddingVision() {
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");
assertNotNull(embeddingVision);
}
@Test
@Disabled
void botsChat() {
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","你想做些什么"));
final String botsChat = doubaoService.botsChat(messages);
assertNotNull(botsChat);
}
@Test
@Disabled
void tokenization() {
final String tokenization = doubaoService.tokenization(new String[]{"阿斯顿", "马丁"});
assertNotNull(tokenization);
}
@Test
@Disabled
void batchChat() {
final DoubaoService doubaoService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DOUBAO.getValue())
.setApiKey(key).setModel("your Endpoint ID").build(), DoubaoService.class);
final String batchChat = doubaoService.batchChat("写首歌词");
assertNotNull(batchChat);
}
@Test
@Disabled
void testBatchChat() {
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("system","你是个抽象大师"));
messages.add(new Message("user","写一个KFC的抽象广告"));
final String batchChat = doubaoService.batchChat(messages);
assertNotNull(batchChat);
}
@Test
@Disabled
void createContext() {
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("system","你是个抽象大师,你真的很抽象"));
final String context = doubaoService.createContext(messages);//ctx-20250307092153-cvslm
assertNotNull(context);
}
@Test
@Disabled
void testCreateContext() {
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("system","你是个抽象大师,你真的很抽象"));
final String context = doubaoService.createContext(messages,DoubaoCommon.DoubaoContext.COMMON_PREFIX.getMode());
assertNotNull(context);//ctx-20250307092153-cvslm
}
@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");
assertNotNull(chatContext);
}
@Test
@Disabled
void testChatContext() {
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","你怎么看待意大利面拌水泥?"));
final String chatContext = doubaoService.chatContext(messages, "ctx-20250307092153-cvslm");
assertNotNull(chatContext);
}
}

View File

@@ -0,0 +1,123 @@
/*
* 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.ai.model.grok;
import cn.hutool.ai.AIServiceFactory;
import cn.hutool.ai.ModelName;
import cn.hutool.ai.Models;
import cn.hutool.ai.core.AIConfigBuilder;
import cn.hutool.ai.core.Message;
import cn.hutool.core.img.ImgUtil;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import java.awt.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertNotNull;
class GrokServiceTest {
String key = "your key";
GrokService grokService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.GROK.getValue()).setApiKey(key).build(), GrokService.class);
@Test
@Disabled
void chat(){
final String chat = grokService.chat("写一个疯狂星期四广告词");
assertNotNull(chat);
}
@Test
@Disabled
void testChat(){
final List<Message> messages = new ArrayList<>();
messages.add(new Message("system","你是个抽象大师,会说很抽象的话,最擅长说抽象的笑话"));
messages.add(new Message("user","给我说一个笑话"));
final String chat = grokService.chat(messages);
assertNotNull(chat);
}
@Test
@Disabled
void message() {
final String message = grokService.message("给我一个KFC的广告词", 4096);
assertNotNull(message);
}
@Test
@Disabled
void chatVision() {
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));
assertNotNull(chatVision);
}
@Test
@Disabled
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"));
assertNotNull(chatVision);
}
@Test
@Disabled
void models() {
final String models = grokService.models();
assertNotNull(models);
}
@Test
@Disabled
void getModel() {
final String model = grokService.getModel("");
assertNotNull(model);
}
@Test
@Disabled
void languageModels() {
final String languageModels = grokService.languageModels();
assertNotNull(languageModels);
}
@Test
@Disabled
void getLanguageModel() {
final String language = grokService.getLanguageModel("");
assertNotNull(language);
}
@Test
@Disabled
void tokenizeText() {
final String tokenizeText = grokService.tokenizeText(key);
assertNotNull(tokenizeText);
}
@Test
@Disabled
void deferredCompletion() {
final String deferred = grokService.deferredCompletion(key);
assertNotNull(deferred);
}
}

View File

@@ -0,0 +1,168 @@
/*
* 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.ai.model.openai;
import cn.hutool.ai.AIServiceFactory;
import cn.hutool.ai.ModelName;
import cn.hutool.ai.Models;
import cn.hutool.ai.core.AIConfigBuilder;
import cn.hutool.ai.core.Message;
import cn.hutool.core.io.FileUtil;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertNotNull;
class OpenaiServiceTest {
String key = "your key";
OpenaiService openaiService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.OPENAI.getValue()).setApiKey(key).build(), OpenaiService.class);
@Test
@Disabled
void chat(){
final String chat = openaiService.chat("写一个疯狂星期四广告词");
assertNotNull(chat);
}
@Test
@Disabled
void testChat(){
final List<Message> messages = new ArrayList<>();
messages.add(new Message("system","你是个抽象大师,会说很抽象的话,最擅长说抽象的笑话"));
messages.add(new Message("user","给我说一个笑话"));
final String chat = openaiService.chat(messages);
assertNotNull(chat);
}
@Test
@Disabled
void chatVision() {
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"));
assertNotNull(chatVision);
}
@Test
@Disabled
void imagesGenerations() {
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("一位年轻的宇航员站在未来感十足的太空站内,透过巨大的弧形落地窗凝望浩瀚宇宙。窗外,璀璨的星河与五彩斑斓的星云交织,远处隐约可见未知星球的轮廓,仿佛在召唤着探索的脚步。宇航服上的呼吸灯与透明显示屏上的星图交相辉映,象征着人类科技与宇宙奥秘的碰撞。画面深邃而神秘,充满对未知的渴望与无限可能的想象。");
assertNotNull(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
}
@Test
@Disabled
void imagesEdits() {
final OpenaiService openaiService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.OPENAI.getValue())
.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);
assertNotNull(imagesEdits);
}
@Test
@Disabled
void imagesVariations() {
final OpenaiService openaiService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.OPENAI.getValue())
.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);
assertNotNull(imagesVariations);
}
@Test
@Disabled
void textToSpeech() {
final OpenaiService openaiService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.OPENAI.getValue())
.setApiKey(key).setModel(Models.Openai.TTS_1_HD.getModel()).build(), OpenaiService.class);
final InputStream inputStream = openaiService.textToSpeech("万里山河一夜白,\n" +
"千峰尽染玉龙哀。\n" +
"长风卷起琼花碎,\n" +
"直上九霄揽月来。", OpenaiCommon.OpenaiSpeech.NOVA);
final String filePath = "your filePath";
final Path path = Paths.get(filePath);
try (final FileOutputStream outputStream = new FileOutputStream(filePath)) {
Files.createDirectories(path.getParent());
final byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
} catch (final IOException e) {
throw new RuntimeException(e);
}
}
@Test
@Disabled
void speechToText() {
final OpenaiService openaiService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.OPENAI.getValue())
.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
@Disabled
void embeddingText() {
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("萬里山河一夜白,千峰盡染玉龍哀,長風捲起瓊花碎,直上九霄闌月來");
assertNotNull(embeddingText);
}
@Test
@Disabled
void moderations() {
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");
assertNotNull(moderations);
}
@Test
@Disabled
void chatReasoning() {
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疯狂星期四的文案"));
final String chatReasoning = openaiService.chatReasoning(messages, OpenaiCommon.OpenaiReasoning.HIGH.getEffort());
assertNotNull(chatReasoning);
}
}

View File

@@ -113,6 +113,11 @@
<artifactId>hutool-jwt</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-ai</artifactId>
<version>${project.parent.version}</version>
</dependency>
</dependencies>
<build>

View File

@@ -35,6 +35,7 @@
<module>hutool-captcha</module>
<module>hutool-socket</module>
<module>hutool-jwt</module>
<module>hutool-ai</module>
</modules>
<properties>