hutool-ai新增SSE流式响应,增加Hutool-AI平台服务,增加超时时间配置

This commit is contained in:
choweli 2025-06-04 14:11:41 +08:00
parent 115b4b91c4
commit b0b44e2ec6
30 changed files with 2159 additions and 132 deletions

View File

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

View File

@ -23,6 +23,10 @@ package org.dromara.hutool.ai;
* @since 6.0.0 * @since 6.0.0
*/ */
public enum ModelName { public enum ModelName {
/**
* hutool
*/
HUTOOL("hutool"),
/** /**
* deepSeek * deepSeek
*/ */

View File

@ -24,6 +24,21 @@ package org.dromara.hutool.ai;
*/ */
public class Models { public class Models {
// Hutool的模型
public enum Hutool {
HUTOOL("hutool");
private final String model;
Hutool(String model) {
this.model = model;
}
public String getModel() {
return model;
}
}
// DeepSeek的模型 // DeepSeek的模型
public enum DeepSeek { public enum DeepSeek {
DEEPSEEK_CHAT("deepseek-chat"), DEEPSEEK_CHAT("deepseek-chat"),
@ -123,7 +138,12 @@ public class Models {
DOUBAO_VISION_LITE_32K("doubao-vision-lite-32k-241015"), DOUBAO_VISION_LITE_32K("doubao-vision-lite-32k-241015"),
DOUBAO_EMBEDDING_LARGE("doubao-embedding-large-text-240915"), DOUBAO_EMBEDDING_LARGE("doubao-embedding-large-text-240915"),
DOUBAO_EMBEDDING_TEXT_240715("doubao-embedding-text-240715"), DOUBAO_EMBEDDING_TEXT_240715("doubao-embedding-text-240715"),
DOUBAO_EMBEDDING_VISION("doubao-embedding-vision-241215"); DOUBAO_EMBEDDING_VISION("doubao-embedding-vision-241215"),
DOUBAO_SEEDREAM_3_0_T2I("doubao-seedream-3-0-t2i-250415"),
Doubao_Seedance_1_0_lite_t2v("doubao-seedance-1-0-lite-t2v-250428"),
Doubao_Seedance_1_0_lite_i2v("doubao-seedance-1-0-lite-i2v-250428"),
Wan2_1_14B_t2v("wan2-1-14b-t2v-250225"),
Wan2_1_14B_i2v("wan2-1-14b-i2v-250225");
private final String model; private final String model;
@ -138,6 +158,23 @@ public class Models {
// Grok的模型 // Grok的模型
public enum Grok { public enum Grok {
GROK_3_BETA_LATEST("grok-3-beta"),
GROK_3_BETA("grok-3-beta"),
GROK_3("grok-3-beta"),
GROK_3_MINI_FAST_LATEST("grok-3-mini-fast-beta"),
GROK_3_MINI_FAST_BETA("grok-3-mini-fast-beta"),
GROK_3_MINI_FAST("grok-3-mini-fast-beta"),
GROK_3_FAST_LATEST("grok-3-fast-beta"),
GROK_3_FAST_BETA("grok-3-fast-beta"),
GROK_3_FAST("grok-3-fast-beta"),
GROK_3_MINI_LATEST("grok-3-mini-beta"),
GROK_3_MINI_BETA("grok-3-mini-beta"),
GROK_3_MINI("grok-3-mini-beta"),
GROK_2_IMAGE_LATEST("grok-2-image-1212"),
GROK_2_IMAGE("grok-2-image-1212"),
GROK_2_IMAGE_1212("grok-2-image-1212"),
grok_2_latest("grok-2-1212"),
GROK_2("grok-2-1212"),
GROK_2_1212("grok-2-1212"), GROK_2_1212("grok-2-1212"),
GROK_2_VISION_1212("grok-2-vision-1212"), GROK_2_VISION_1212("grok-2-vision-1212"),
GROK_BETA("grok-beta"), GROK_BETA("grok-beta"),

View File

@ -110,4 +110,36 @@ public interface AIConfig {
*/ */
Map<String, Object> getAdditionalConfigMap(); Map<String, Object> getAdditionalConfigMap();
/**
* 设置连接超时时间
*
* @param timeout 连接超时时间
* @since 6.0.0
*/
void setTimeout(int timeout);
/**
* 获取连接超时时间
*
* @return timeout
* @since 6.0.0
*/
int getTimeout();
/**
* 设置读取超时时间
*
* @param readTimeout 连接超时时间
* @since 6.0.0
*/
void setReadTimeout(int readTimeout);
/**
* 获取读取超时时间
*
* @return readTimeout
* @since 6.0.0
*/
int getReadTimeout();
} }

View File

@ -106,6 +106,34 @@ public class AIConfigBuilder {
return this; return this;
} }
/**
* 设置连接超时时间不设置为默认值
*
* @param timeout 超时时间
* @return config
* @since 6.0.0
*/
public synchronized AIConfigBuilder setTimout(final int timeout) {
if (timeout > 0) {
config.setTimeout(timeout);
}
return this;
}
/**
* 设置读取超时时间不设置为默认值
*
* @param readTimout 取超时时间
* @return config
* @since 6.0.0
*/
public synchronized AIConfigBuilder setReadTimout(final int readTimout) {
if (readTimout > 0) {
config.setReadTimeout(readTimout);
}
return this;
}
/** /**
* 返回config实例 * 返回config实例
* *

View File

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

View File

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

View File

@ -36,6 +36,10 @@ public class BaseConfig implements AIConfig {
protected volatile String model; protected volatile String model;
//动态扩展字段 //动态扩展字段
protected Map<String, Object> additionalConfig = new SafeConcurrentHashMap<>(); protected Map<String, Object> additionalConfig = new SafeConcurrentHashMap<>();
//连接超时时间
protected volatile int timeout = 180000;
//读取超时时间
protected volatile int readTimeout = 300000;
@Override @Override
public void setApiKey(final String apiKey) { public void setApiKey(final String apiKey) {
@ -82,4 +86,23 @@ public class BaseConfig implements AIConfig {
return new SafeConcurrentHashMap<>(additionalConfig); return new SafeConcurrentHashMap<>(additionalConfig);
} }
@Override
public int getTimeout() {
return timeout;
}
@Override
public void setTimeout(final int timeout) {
this.timeout = timeout;
}
@Override
public int getReadTimeout() {
return readTimeout;
}
@Override
public void setReadTimeout(final int readTimeout) {
this.readTimeout = readTimeout;
}
} }

View File

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

View File

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

View File

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

View File

@ -20,6 +20,7 @@ import org.dromara.hutool.ai.core.AIConfig;
import org.dromara.hutool.ai.core.BaseAIService; import org.dromara.hutool.ai.core.BaseAIService;
import org.dromara.hutool.ai.core.Message; import org.dromara.hutool.ai.core.Message;
import org.dromara.hutool.core.text.StrUtil; import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.core.thread.ThreadUtil;
import org.dromara.hutool.http.client.Response; import org.dromara.hutool.http.client.Response;
import org.dromara.hutool.json.JSONUtil; import org.dromara.hutool.json.JSONUtil;
@ -27,6 +28,7 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Consumer;
/** /**
* Doubao服务AI具体功能的实现 * Doubao服务AI具体功能的实现
@ -54,21 +56,14 @@ public class DoubaoServiceImpl extends BaseAIService implements DoubaoService {
private final String CHAT_CONTEXT = "/context/chat/completions"; private final String CHAT_CONTEXT = "/context/chat/completions";
//创建视频生成任务 //创建视频生成任务
private final String CREATE_VIDEO = "/contents/generations/tasks"; private final String CREATE_VIDEO = "/contents/generations/tasks";
//文生图
private final String IMAGES_GENERATIONS = "/images/generations";
public DoubaoServiceImpl(final AIConfig config) { public DoubaoServiceImpl(final AIConfig config) {
//初始化doubao客户端 //初始化doubao客户端
super(config); 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 @Override
public String chat(final List<Message> messages) { public String chat(final List<Message> messages) {
String paramJson = buildChatRequestBody(messages); String paramJson = buildChatRequestBody(messages);
@ -76,6 +71,12 @@ public class DoubaoServiceImpl extends BaseAIService implements DoubaoService {
return response.bodyStr(); return response.bodyStr();
} }
@Override
public void chat(List<Message> messages, Consumer<String> callback) {
Map<String, Object> paramMap = buildChatStreamRequestBody(messages);
ThreadUtil.newThread(() -> sendPostStream(CHAT_ENDPOINT, paramMap, callback::accept), "doubao-chat-sse").start();
}
@Override @Override
public String chatVision(String prompt, final List<String> images, String detail) { public String chatVision(String prompt, final List<String> images, String detail) {
String paramJson = buildChatVisionRequestBody(prompt, images, detail); String paramJson = buildChatVisionRequestBody(prompt, images, detail);
@ -83,6 +84,12 @@ public class DoubaoServiceImpl extends BaseAIService implements DoubaoService {
return response.bodyStr(); return response.bodyStr();
} }
@Override
public void chatVision(String prompt, List<String> images, String detail, Consumer<String> callback) {
Map<String, Object> paramMap = buildChatVisionStreamRequestBody(prompt, images, detail);
ThreadUtil.newThread(() -> sendPostStream(CHAT_ENDPOINT, paramMap, callback::accept), "doubao-chatVision-sse").start();
}
@Override @Override
public String videoTasks(String text, String image, final List<DoubaoCommon.DoubaoVideo> videoParams) { public String videoTasks(String text, String image, final List<DoubaoCommon.DoubaoVideo> videoParams) {
String paramJson = buildGenerationsTasksRequestBody(text, image, videoParams); String paramJson = buildGenerationsTasksRequestBody(text, image, videoParams);
@ -118,6 +125,12 @@ public class DoubaoServiceImpl extends BaseAIService implements DoubaoService {
return response.bodyStr(); return response.bodyStr();
} }
@Override
public void botsChat(List<Message> messages, Consumer<String> callback) {
Map<String, Object> paramMap = buildBotsChatStreamRequestBody(messages);
ThreadUtil.newThread(() -> sendPostStream(BOTS_CHAT, paramMap, callback::accept), "doubao-botsChat-sse").start();
}
@Override @Override
public String tokenization(String[] text) { public String tokenization(String[] text) {
String paramJson = buildTokenizationRequestBody(text); String paramJson = buildTokenizationRequestBody(text);
@ -125,15 +138,6 @@ public class DoubaoServiceImpl extends BaseAIService implements DoubaoService {
return response.bodyStr(); return response.bodyStr();
} }
@Override
public String batchChat(String prompt) {
// 定义消息结构
final List<Message> messages = new ArrayList<>();
messages.add(new Message("system", "You are a helpful assistant"));
messages.add(new Message("user", prompt));
return batchChat(messages);
}
@Override @Override
public String batchChat(final List<Message> messages) { public String batchChat(final List<Message> messages) {
String paramJson = buildBatchChatRequestBody(messages); String paramJson = buildBatchChatRequestBody(messages);
@ -148,14 +152,6 @@ public class DoubaoServiceImpl extends BaseAIService implements DoubaoService {
return response.bodyStr(); return response.bodyStr();
} }
@Override
public String chatContext(String prompt, String contextId) {
// 定义消息结构
final List<Message> messages = new ArrayList<>();
messages.add(new Message("user", prompt));
return chatContext(messages, contextId);
}
@Override @Override
public String chatContext(final List<Message> messages, String contextId) { public String chatContext(final List<Message> messages, String contextId) {
String paramJson = buildChatContentRequestBody(messages, contextId); String paramJson = buildChatContentRequestBody(messages, contextId);
@ -163,6 +159,19 @@ public class DoubaoServiceImpl extends BaseAIService implements DoubaoService {
return response.bodyStr(); return response.bodyStr();
} }
@Override
public void chatContext(List<Message> messages, String contextId, Consumer<String> callback) {
Map<String, Object> paramMap = buildChatContentStreamRequestBody(messages, contextId);
ThreadUtil.newThread(() -> sendPostStream(CHAT_CONTEXT, paramMap, callback::accept), "doubao-chatContext-sse").start();
}
@Override
public String imagesGenerations(String prompt) {
String paramJson = buildImagesGenerationsRequestBody(prompt);
Response response = sendPost(IMAGES_GENERATIONS, paramJson);
return response.bodyStr();
}
// 构建chat请求体 // 构建chat请求体
private String buildChatRequestBody(final List<Message> messages) { private String buildChatRequestBody(final List<Message> messages) {
//使用JSON工具 //使用JSON工具
@ -175,6 +184,19 @@ public class DoubaoServiceImpl extends BaseAIService implements DoubaoService {
return JSONUtil.toJsonStr(paramMap); return JSONUtil.toJsonStr(paramMap);
} }
// 构建chatStream请求体
private Map<String, Object> buildChatStreamRequestBody(final List<Message> messages) {
//使用JSON工具
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("stream", true);
paramMap.put("model", config.getModel());
paramMap.put("messages", messages);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return paramMap;
}
//构建chatVision请求体 //构建chatVision请求体
private String buildChatVisionRequestBody(String prompt, final List<String> images, String detail) { private String buildChatVisionRequestBody(String prompt, final List<String> images, String detail) {
// 定义消息结构 // 定义消息结构
@ -206,6 +228,37 @@ public class DoubaoServiceImpl extends BaseAIService implements DoubaoService {
return JSONUtil.toJsonStr(paramMap); return JSONUtil.toJsonStr(paramMap);
} }
private Map<String, Object> buildChatVisionStreamRequestBody(String prompt, final List<String> images, String detail) {
// 定义消息结构
final List<Message> messages = new ArrayList<>();
final List<Object> content = new ArrayList<>();
final Map<String, String> contentMap = new HashMap<>();
contentMap.put("type", "text");
contentMap.put("text", prompt);
content.add(contentMap);
for (String img : images) {
HashMap<String, Object> imgUrlMap = new HashMap<>();
imgUrlMap.put("type", "image_url");
HashMap<String, String> urlMap = new HashMap<>();
urlMap.put("url", img);
urlMap.put("detail", detail);
imgUrlMap.put("image_url", urlMap);
content.add(imgUrlMap);
}
messages.add(new Message("user", content));
//使用JSON工具
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("stream", true);
paramMap.put("model", config.getModel());
paramMap.put("messages", messages);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return paramMap;
}
//构建文本向量化请求体 //构建文本向量化请求体
private String buildEmbeddingTextRequestBody(String[] input) { private String buildEmbeddingTextRequestBody(String[] input) {
//使用JSON工具 //使用JSON工具
@ -253,6 +306,10 @@ public class DoubaoServiceImpl extends BaseAIService implements DoubaoService {
return buildChatRequestBody(messages); return buildChatRequestBody(messages);
} }
private Map<String, Object> buildBotsChatStreamRequestBody(final List<Message> messages) {
return buildChatStreamRequestBody(messages);
}
//构建分词请求体 //构建分词请求体
private String buildTokenizationRequestBody(String[] text) { private String buildTokenizationRequestBody(String[] text) {
final Map<String, Object> paramMap = new HashMap<>(); final Map<String, Object> paramMap = new HashMap<>();
@ -266,6 +323,10 @@ public class DoubaoServiceImpl extends BaseAIService implements DoubaoService {
return buildChatRequestBody(messages); return buildChatRequestBody(messages);
} }
private Map<String, Object> buildBatchChatStreamRequestBody(final List<Message> messages) {
return buildChatStreamRequestBody(messages);
}
//构建创建上下文缓存请求体 //构建创建上下文缓存请求体
private String buildCreateContextRequest(final List<Message> messages, String mode) { private String buildCreateContextRequest(final List<Message> messages, String mode) {
final Map<String, Object> paramMap = new HashMap<>(); final Map<String, Object> paramMap = new HashMap<>();
@ -291,6 +352,19 @@ public class DoubaoServiceImpl extends BaseAIService implements DoubaoService {
return JSONUtil.toJsonStr(paramMap); return JSONUtil.toJsonStr(paramMap);
} }
private Map<String, Object> buildChatContentStreamRequestBody(final List<Message> messages, String contextId) {
//使用JSON工具
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("stream", true);
paramMap.put("model", config.getModel());
paramMap.put("messages", messages);
paramMap.put("context_id", contextId);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return paramMap;
}
//构建创建视频任务请求体 //构建创建视频任务请求体
private String buildGenerationsTasksRequestBody(String text, String image, final List<DoubaoCommon.DoubaoVideo> videoParams) { private String buildGenerationsTasksRequestBody(String text, String image, final List<DoubaoCommon.DoubaoVideo> videoParams) {
//使用JSON工具 //使用JSON工具
@ -306,7 +380,7 @@ public class DoubaoServiceImpl extends BaseAIService implements DoubaoService {
content.add(textMap); content.add(textMap);
} }
//添加图片参数 //添加图片参数
if (!StrUtil.isNotBlank(image)) { if (!StrUtil.isBlank(image)) {
final Map<String, Object> imgUrlMap = new HashMap<>(); final Map<String, Object> imgUrlMap = new HashMap<>();
imgUrlMap.put("type", "image_url"); imgUrlMap.put("type", "image_url");
final Map<String, String> urlMap = new HashMap<>(); final Map<String, String> urlMap = new HashMap<>();
@ -351,4 +425,15 @@ public class DoubaoServiceImpl extends BaseAIService implements DoubaoService {
return JSONUtil.toJsonStr(paramMap); return JSONUtil.toJsonStr(paramMap);
} }
//构建文生图请求体
private String buildImagesGenerationsRequestBody(String prompt) {
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("model", config.getModel());
paramMap.put("prompt", prompt);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return JSONUtil.toJsonStr(paramMap);
}
} }

View File

@ -17,8 +17,11 @@
package org.dromara.hutool.ai.model.grok; package org.dromara.hutool.ai.model.grok;
import org.dromara.hutool.ai.core.AIService; import org.dromara.hutool.ai.core.AIService;
import org.dromara.hutool.ai.core.Message;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.Consumer;
/** /**
* grok支持的扩展接口 * grok支持的扩展接口
@ -36,7 +39,48 @@ public interface GrokService extends AIService {
* @return AI回答 * @return AI回答
* @since 6.0.0 * @since 6.0.0
*/ */
String message(String prompt, int maxToken); default String message(String prompt, int maxToken){
// 定义消息结构
final List<Message> messages = new ArrayList<>();
messages.add(new Message("system", "You are a helpful assistant"));
messages.add(new Message("user", prompt));
return message(messages, maxToken);
}
/**
* 创建消息回复-SSE流式输出
*
* @param prompt 题词
* @param maxToken 最大token
* @param callback 流式数据回调函数
* @since 6.0.0
*/
default void message(String prompt, int maxToken, final Consumer<String> callback){
final List<Message> messages = new ArrayList<>();
messages.add(new Message("system", "You are a helpful assistant"));
messages.add(new Message("user", prompt));
message(messages, maxToken, callback);
}
/**
* 创建消息回复
*
* @param messages messages 由对话组成的消息列表如系统人设背景信息等用户自定义的信息
* @param maxToken 最大token
* @return AI回答
* @since 6.0.0
*/
String message(List<Message> messages, int maxToken);
/**
* 创建消息回复-SSE流式输出
*
* @param messages messages 由对话组成的消息列表如系统人设背景信息等用户自定义的信息
* @param maxToken 最大token
* @param callback 流式数据回调函数
* @since 6.0.0
*/
void message(List<Message> messages, int maxToken, final Consumer<String> callback);
/** /**
* 图像理解模型会依据传入的图片信息以及问题给出回复 * 图像理解模型会依据传入的图片信息以及问题给出回复
@ -49,6 +93,17 @@ public interface GrokService extends AIService {
*/ */
String chatVision(String prompt, final List<String> images, String detail); String chatVision(String prompt, final List<String> images, String detail);
/**
* 图像理解-SSE流式输出
*
* @param prompt 题词
* @param images 图片列表/或者图片Base64编码图片列表(URI形式)
* @param detail 手动设置图片的质量取值范围highlowauto,默认为auto
* @param callback 流式数据回调函数
* @since 6.0.0
*/
void chatVision(String prompt, final List<String> images, String detail,final Consumer<String> callback);
/** /**
* 图像理解模型会依据传入的图片信息以及问题给出回复 * 图像理解模型会依据传入的图片信息以及问题给出回复
* *
@ -61,6 +116,18 @@ public interface GrokService extends AIService {
return chatVision(prompt, images, GrokCommon.GrokVision.AUTO.getDetail()); return chatVision(prompt, images, GrokCommon.GrokVision.AUTO.getDetail());
} }
/**
* 图像理解模型会依据传入的图片信息以及问题给出回复
*
* @param prompt 题词
* @param images 传入的图片列表地址/或者图片Base64编码图片列表(URI形式)
* @param callback 流式数据回调函数
* @since 6.0.0
*/
default void chatVision(String prompt, final List<String> images, final Consumer<String> callback){
chatVision(prompt, images, GrokCommon.GrokVision.AUTO.getDetail(), callback);
}
/** /**
* 列出所有model列表 * 列出所有model列表
* *
@ -112,4 +179,14 @@ public interface GrokService extends AIService {
* @since 6.0.0 * @since 6.0.0
*/ */
String deferredCompletion(String requestId); String deferredCompletion(String requestId);
/**
* 文生图
* 请设置config中model为支持图片功能的模型目前支持GROK_2_IMAGE
*
* @param prompt 题词
* @return 包含生成图片的url
* @since 6.0.0
*/
String imagesGenerations(String prompt);
} }

View File

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

View File

@ -0,0 +1,119 @@
/*
* Copyright (c) 2025 Hutool Team and hutool.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.hutool.ai.model.hutool;
/**
* hutool公共类
*
* @author elichow
* @since 6.0.0
*/
public class HutoolCommon {
//hutool视觉参数
public enum HutoolVision {
AUTO("auto"),
LOW("low"),
HIGH("high");
private final String detail;
HutoolVision(String detail) {
this.detail = detail;
}
public String getDetail() {
return detail;
}
}
//hutool音频参数
public enum HutoolSpeech {
ALLOY("alloy"),
ASH("ash"),
CORAL("coral"),
ECHO("echo"),
FABLE("fable"),
ONYX("onyx"),
NOVA("nova"),
SAGE("sage"),
SHIMMER("shimmer");
private final String voice;
HutoolSpeech(String voice) {
this.voice = voice;
}
public String getVoice() {
return voice;
}
}
//hutool视频生成参数
public enum HutoolVideo {
//宽高比例
RATIO_16_9("--rt", "16:9"),//[1280, 720]
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;
HutoolVideo(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 org.dromara.hutool.ai.model.hutool;
import org.dromara.hutool.ai.Models;
import org.dromara.hutool.ai.core.BaseConfig;
/**
* Hutool配置类初始化API接口地址设置默认的模型
*
* @author elichow
* @since 6.0.0
*/
public class HutoolConfig extends BaseConfig {
private final String API_URL = "https://api.hutool.cn/ai/api";
private final String DEFAULT_MODEL = Models.Hutool.HUTOOL.getModel();
public HutoolConfig() {
setApiUrl(API_URL);
setModel(DEFAULT_MODEL);
}
public HutoolConfig(String apiKey) {
this();
setApiKey(apiKey);
}
@Override
public String getModelName() {
return "hutool";
}
}

View File

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

View File

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

View File

@ -0,0 +1,380 @@
/*
* Copyright (c) 2025 Hutool Team and hutool.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.hutool.ai.model.hutool;
import org.dromara.hutool.ai.AIException;
import org.dromara.hutool.ai.core.AIConfig;
import org.dromara.hutool.ai.core.BaseAIService;
import org.dromara.hutool.ai.core.Message;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.core.thread.ThreadUtil;
import org.dromara.hutool.http.client.Response;
import org.dromara.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;
import java.util.function.Consumer;
/**
* Hutool服务AI具体功能的实现
*
* @author elichow
* @since 6.0.0
*/
public class HutoolServiceImpl extends BaseAIService implements HutoolService {
//对话补全
private final String CHAT_ENDPOINT = "/chat/completions";
//分词
private final String TOKENIZE_TEXT = "/tokenize/text";
//文生图
private final String IMAGES_GENERATIONS = "/images/generations";
//图文向量化
private final String EMBEDDING_VISION = "/embeddings/multimodal";
//文本转语音
private final String TTS = "/audio/tts";
//语音转文本
private final String STT = "/audio/stt";
//创建视频生成任务
private final String CREATE_VIDEO = "/video/generations";
public HutoolServiceImpl(final AIConfig config) {
//初始化hutool客户端
super(config);
}
@Override
public String chat(final List<Message> messages) {
String paramJson = buildChatRequestBody(messages);
Response response = sendPost(CHAT_ENDPOINT, paramJson);
return response.bodyStr();
}
@Override
public void chat(List<Message> messages,Consumer<String> callback) {
Map<String, Object> paramMap = buildChatStreamRequestBody(messages);
ThreadUtil.newThread(() -> sendPostStream(CHAT_ENDPOINT, paramMap, callback::accept), "hutool-chat-sse").start();
}
@Override
public String chatVision(String prompt, final List<String> images, String detail) {
String paramJson = buildChatVisionRequestBody(prompt, images, detail);
Response response = sendPost(CHAT_ENDPOINT, paramJson);
return response.bodyStr();
}
@Override
public void chatVision(String prompt, List<String> images, String detail, Consumer<String> callback) {
Map<String, Object> paramMap = buildChatVisionStreamRequestBody(prompt, images, detail);
System.out.println(JSONUtil.toJsonStr(paramMap));
ThreadUtil.newThread(() -> sendPostStream(CHAT_ENDPOINT, paramMap, callback::accept), "hutool-chatVision-sse").start();
}
@Override
public String tokenizeText(String text) {
String paramJson = buildTokenizeRequestBody(text);
Response response = sendPost(TOKENIZE_TEXT, paramJson);
return response.bodyStr();
}
@Override
public String imagesGenerations(String prompt) {
String paramJson = buildImagesGenerationsRequestBody(prompt);
Response response = sendPost(IMAGES_GENERATIONS, paramJson);
return response.bodyStr();
}
@Override
public String embeddingVision(String text, String image) {
String paramJson = buildEmbeddingVisionRequestBody(text, image);
Response response = sendPost(EMBEDDING_VISION, paramJson);
return response.bodyStr();
}
@Override
public InputStream tts(String input, final HutoolCommon.HutoolSpeech voice) {
try {
String paramJson = buildTTSRequestBody(input, voice.getVoice());
Response response = sendPost(TTS, paramJson);
// 检查响应内容类型
String contentType = response.header("Content-Type");
if (contentType != null && contentType.startsWith("application/json")) {
// 如果是JSON响应说明有错误
String errorBody = response.bodyStr();
throw new AIException("TTS请求失败: " + errorBody);
}
// 默认返回音频流
return response.bodyStream();
} catch (Exception e) {
throw new AIException("TTS处理失败: " + e.getMessage(), e);
}
}
@Override
public String stt(final File file) {
final Map<String, Object> paramMap = buildSTTRequestBody(file);
Response response = sendFormData(STT, paramMap);
return response.bodyStr();
}
@Override
public String videoTasks(String text, String image, final List<HutoolCommon.HutoolVideo> videoParams) {
String paramJson = buildGenerationsTasksRequestBody(text, image, videoParams);
Response response = sendPost(CREATE_VIDEO, paramJson);
return response.bodyStr();
}
@Override
public String getVideoTasksInfo(String taskId) {
Response response = sendGet(CREATE_VIDEO + "/" + taskId);
return response.bodyStr();
}
// 构建chat请求体
private String buildChatRequestBody(final List<Message> messages) {
//使用JSON工具
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("model", config.getModel());
paramMap.put("messages", messages);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return JSONUtil.toJsonStr(paramMap);
}
private Map<String, Object> buildChatStreamRequestBody(final List<Message> messages) {
//使用JSON工具
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("stream", true);
paramMap.put("model", config.getModel());
paramMap.put("messages", messages);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return paramMap;
}
//构建chatVision请求体
private String buildChatVisionRequestBody(String prompt, final List<String> images, String detail) {
// 定义消息结构
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 Map<String, Object> buildChatVisionStreamRequestBody(String prompt, final List<String> images, String detail) {
// 定义消息结构
final List<Message> messages = new ArrayList<>();
final List<Object> content = new ArrayList<>();
final Map<String, String> contentMap = new HashMap<>();
contentMap.put("type", "text");
contentMap.put("text", prompt);
content.add(contentMap);
for (String img : images) {
HashMap<String, Object> imgUrlMap = new HashMap<>();
imgUrlMap.put("type", "image_url");
HashMap<String, String> urlMap = new HashMap<>();
urlMap.put("url", img);
urlMap.put("detail", detail);
imgUrlMap.put("image_url", urlMap);
content.add(imgUrlMap);
}
messages.add(new Message("user", content));
//使用JSON工具
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("stream", true);
paramMap.put("model", config.getModel());
paramMap.put("messages", messages);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return paramMap;
}
//构建分词请求体
private String buildTokenizeRequestBody(String text) {
//使用JSON工具
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("model", config.getModel());
paramMap.put("text", text);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return JSONUtil.toJsonStr(paramMap);
}
//构建文生图请求体
private String buildImagesGenerationsRequestBody(String prompt) {
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("model", config.getModel());
paramMap.put("prompt", prompt);
//合并其他参数
paramMap.putAll(config.getAdditionalConfigMap());
return JSONUtil.toJsonStr(paramMap);
}
//构建图文向量化请求体
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());
System.out.println(JSONUtil.toJsonStr(paramMap));
return JSONUtil.toJsonStr(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 buildGenerationsTasksRequestBody(String text, String image, final List<HutoolCommon.HutoolVideo> videoParams) {
//使用JSON工具
final Map<String, Object> paramMap = new HashMap<>();
paramMap.put("model", config.getModel());
final List<Object> content = new ArrayList<>();
//添加文本参数
final Map<String, String> textMap = new HashMap<>();
if (!StrUtil.isBlank(text)) {
textMap.put("type", "text");
textMap.put("text", text);
content.add(textMap);
}
//添加图片参数
if (!StrUtil.isBlank(image)) {
final Map<String, Object> imgUrlMap = new HashMap<>();
imgUrlMap.put("type", "image_url");
final Map<String, String> urlMap = new HashMap<>();
urlMap.put("url", image);
imgUrlMap.put("image_url", urlMap);
content.add(imgUrlMap);
}
//添加视频参数
if (videoParams != null && !videoParams.isEmpty()) {
//如果有文本参数就加在后面
if (textMap != null && !textMap.isEmpty()) {
int textIndex = content.indexOf(textMap);
StringBuilder textBuilder = new StringBuilder(text);
for (HutoolCommon.HutoolVideo 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 (HutoolCommon.HutoolVideo 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());
System.out.println(JSONUtil.toJsonStr(paramMap));
return JSONUtil.toJsonStr(paramMap);
}
}

View File

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

View File

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

View File

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

View File

@ -1,3 +1,4 @@
org.dromara.hutool.ai.model.hutool.HutoolConfig
org.dromara.hutool.ai.model.deepseek.DeepSeekConfig org.dromara.hutool.ai.model.deepseek.DeepSeekConfig
org.dromara.hutool.ai.model.openai.OpenaiConfig org.dromara.hutool.ai.model.openai.OpenaiConfig
org.dromara.hutool.ai.model.doubao.DoubaoConfig org.dromara.hutool.ai.model.doubao.DoubaoConfig

View File

@ -1,3 +1,4 @@
org.dromara.hutool.ai.model.hutool.HutoolProvider
org.dromara.hutool.ai.model.deepseek.DeepSeekProvider org.dromara.hutool.ai.model.deepseek.DeepSeekProvider
org.dromara.hutool.ai.model.openai.OpenaiProvider org.dromara.hutool.ai.model.openai.OpenaiProvider
org.dromara.hutool.ai.model.doubao.DoubaoProvider org.dromara.hutool.ai.model.doubao.DoubaoProvider

View File

@ -22,6 +22,7 @@ import org.dromara.hutool.ai.core.Message;
import org.dromara.hutool.ai.model.deepseek.DeepSeekService; import org.dromara.hutool.ai.model.deepseek.DeepSeekService;
import org.dromara.hutool.ai.model.doubao.DoubaoService; import org.dromara.hutool.ai.model.doubao.DoubaoService;
import org.dromara.hutool.ai.model.grok.GrokService; import org.dromara.hutool.ai.model.grok.GrokService;
import org.dromara.hutool.ai.model.hutool.HutoolService;
import org.dromara.hutool.ai.model.openai.OpenaiService; import org.dromara.hutool.ai.model.openai.OpenaiService;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -46,6 +47,12 @@ class AIUtilTest {
assertNotNull(aiService); assertNotNull(aiService);
} }
@Test
void getHutoolService() {
final HutoolService hutoolService = AIUtil.getHutoolService(new AIConfigBuilder(ModelName.HUTOOL.getValue()).setApiKey(key).build());
assertNotNull(hutoolService);
}
@Test @Test
void getDeepSeekService() { void getDeepSeekService() {
final DeepSeekService deepSeekService = AIUtil.getDeepSeekService(new AIConfigBuilder(ModelName.DEEPSEEK.getValue()).setApiKey(key).build()); final DeepSeekService deepSeekService = AIUtil.getDeepSeekService(new AIConfigBuilder(ModelName.DEEPSEEK.getValue()).setApiKey(key).build());

View File

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

View File

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

View File

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

View File

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

View File

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