From 45226d78ad5d481b37a78c65bc3c3906dc638f2d Mon Sep 17 00:00:00 2001 From: choweli <1030848819@qq.com> Date: Tue, 13 May 2025 10:14:36 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E6=96=B0=E5=A2=9Ehutool-ai=E6=A8=A1?= =?UTF-8?q?=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README-EN.md | 43 +-- README.md | 43 +-- hutool-ai/pom.xml | 45 +++ .../main/java/cn/hutool/ai/AIException.java | 87 +++++ .../java/cn/hutool/ai/AIServiceFactory.java | 80 ++++ .../src/main/java/cn/hutool/ai/AIUtil.java | 129 +++++++ .../src/main/java/cn/hutool/ai/ModelName.java | 57 +++ .../src/main/java/cn/hutool/ai/Models.java | 157 ++++++++ .../main/java/cn/hutool/ai/core/AIConfig.java | 113 ++++++ .../cn/hutool/ai/core/AIConfigBuilder.java | 118 ++++++ .../cn/hutool/ai/core/AIConfigRegistry.java | 52 +++ .../java/cn/hutool/ai/core/AIService.java | 47 +++ .../cn/hutool/ai/core/AIServiceProvider.java | 44 +++ .../java/cn/hutool/ai/core/BaseAIService.java | 105 ++++++ .../java/cn/hutool/ai/core/BaseConfig.java | 84 +++++ .../main/java/cn/hutool/ai/core/Message.java | 59 +++ .../java/cn/hutool/ai/core/package-info.java | 24 ++ .../ai/model/deepseek/DeepSeekCommon.java | 27 ++ .../ai/model/deepseek/DeepSeekConfig.java | 49 +++ .../ai/model/deepseek/DeepSeekProvider.java | 39 ++ .../ai/model/deepseek/DeepSeekService.java | 53 +++ .../model/deepseek/DeepSeekServiceImpl.java | 117 ++++++ .../ai/model/deepseek/package-info.java | 24 ++ .../hutool/ai/model/doubao/DoubaoCommon.java | 111 ++++++ .../hutool/ai/model/doubao/DoubaoConfig.java | 49 +++ .../ai/model/doubao/DoubaoProvider.java | 39 ++ .../hutool/ai/model/doubao/DoubaoService.java | 195 ++++++++++ .../ai/model/doubao/DoubaoServiceImpl.java | 354 ++++++++++++++++++ .../hutool/ai/model/doubao/package-info.java | 24 ++ .../cn/hutool/ai/model/grok/GrokCommon.java | 44 +++ .../cn/hutool/ai/model/grok/GrokConfig.java | 50 +++ .../cn/hutool/ai/model/grok/GrokProvider.java | 39 ++ .../cn/hutool/ai/model/grok/GrokService.java | 115 ++++++ .../hutool/ai/model/grok/GrokServiceImpl.java | 193 ++++++++++ .../cn/hutool/ai/model/grok/package-info.java | 24 ++ .../hutool/ai/model/openai/OpenaiCommon.java | 86 +++++ .../hutool/ai/model/openai/OpenaiConfig.java | 49 +++ .../ai/model/openai/OpenaiProvider.java | 39 ++ .../hutool/ai/model/openai/OpenaiService.java | 206 ++++++++++ .../ai/model/openai/OpenaiServiceImpl.java | 308 +++++++++++++++ .../hutool/ai/model/openai/package-info.java | 24 ++ .../java/cn/hutool/ai/model/package-info.java | 24 ++ .../main/java/cn/hutool/ai/package-info.java | 24 ++ .../services/cn.hutool.ai.core.AIConfig | 4 + .../cn.hutool.ai.core.AIServiceProvider | 4 + .../src/test/java/AIServiceFactoryTest.java | 41 ++ hutool-ai/src/test/java/AIUtilTest.java | 87 +++++ .../model/deepseek/DeepSeekServiceTest.java | 73 ++++ .../ai/model/doubao/DoubaoServiceTest.java | 196 ++++++++++ .../hutool/ai/model/grok/GrokServiceTest.java | 123 ++++++ .../ai/model/openai/OpenaiServiceTest.java | 168 +++++++++ hutool-all/pom.xml | 5 + pom.xml | 1 + 53 files changed, 4253 insertions(+), 42 deletions(-) create mode 100644 hutool-ai/pom.xml create mode 100644 hutool-ai/src/main/java/cn/hutool/ai/AIException.java create mode 100644 hutool-ai/src/main/java/cn/hutool/ai/AIServiceFactory.java create mode 100644 hutool-ai/src/main/java/cn/hutool/ai/AIUtil.java create mode 100644 hutool-ai/src/main/java/cn/hutool/ai/ModelName.java create mode 100644 hutool-ai/src/main/java/cn/hutool/ai/Models.java create mode 100644 hutool-ai/src/main/java/cn/hutool/ai/core/AIConfig.java create mode 100644 hutool-ai/src/main/java/cn/hutool/ai/core/AIConfigBuilder.java create mode 100644 hutool-ai/src/main/java/cn/hutool/ai/core/AIConfigRegistry.java create mode 100644 hutool-ai/src/main/java/cn/hutool/ai/core/AIService.java create mode 100644 hutool-ai/src/main/java/cn/hutool/ai/core/AIServiceProvider.java create mode 100644 hutool-ai/src/main/java/cn/hutool/ai/core/BaseAIService.java create mode 100644 hutool-ai/src/main/java/cn/hutool/ai/core/BaseConfig.java create mode 100644 hutool-ai/src/main/java/cn/hutool/ai/core/Message.java create mode 100644 hutool-ai/src/main/java/cn/hutool/ai/core/package-info.java create mode 100644 hutool-ai/src/main/java/cn/hutool/ai/model/deepseek/DeepSeekCommon.java create mode 100644 hutool-ai/src/main/java/cn/hutool/ai/model/deepseek/DeepSeekConfig.java create mode 100644 hutool-ai/src/main/java/cn/hutool/ai/model/deepseek/DeepSeekProvider.java create mode 100644 hutool-ai/src/main/java/cn/hutool/ai/model/deepseek/DeepSeekService.java create mode 100644 hutool-ai/src/main/java/cn/hutool/ai/model/deepseek/DeepSeekServiceImpl.java create mode 100644 hutool-ai/src/main/java/cn/hutool/ai/model/deepseek/package-info.java create mode 100644 hutool-ai/src/main/java/cn/hutool/ai/model/doubao/DoubaoCommon.java create mode 100644 hutool-ai/src/main/java/cn/hutool/ai/model/doubao/DoubaoConfig.java create mode 100644 hutool-ai/src/main/java/cn/hutool/ai/model/doubao/DoubaoProvider.java create mode 100644 hutool-ai/src/main/java/cn/hutool/ai/model/doubao/DoubaoService.java create mode 100644 hutool-ai/src/main/java/cn/hutool/ai/model/doubao/DoubaoServiceImpl.java create mode 100644 hutool-ai/src/main/java/cn/hutool/ai/model/doubao/package-info.java create mode 100644 hutool-ai/src/main/java/cn/hutool/ai/model/grok/GrokCommon.java create mode 100644 hutool-ai/src/main/java/cn/hutool/ai/model/grok/GrokConfig.java create mode 100644 hutool-ai/src/main/java/cn/hutool/ai/model/grok/GrokProvider.java create mode 100644 hutool-ai/src/main/java/cn/hutool/ai/model/grok/GrokService.java create mode 100644 hutool-ai/src/main/java/cn/hutool/ai/model/grok/GrokServiceImpl.java create mode 100644 hutool-ai/src/main/java/cn/hutool/ai/model/grok/package-info.java create mode 100644 hutool-ai/src/main/java/cn/hutool/ai/model/openai/OpenaiCommon.java create mode 100644 hutool-ai/src/main/java/cn/hutool/ai/model/openai/OpenaiConfig.java create mode 100644 hutool-ai/src/main/java/cn/hutool/ai/model/openai/OpenaiProvider.java create mode 100644 hutool-ai/src/main/java/cn/hutool/ai/model/openai/OpenaiService.java create mode 100644 hutool-ai/src/main/java/cn/hutool/ai/model/openai/OpenaiServiceImpl.java create mode 100644 hutool-ai/src/main/java/cn/hutool/ai/model/openai/package-info.java create mode 100644 hutool-ai/src/main/java/cn/hutool/ai/model/package-info.java create mode 100644 hutool-ai/src/main/java/cn/hutool/ai/package-info.java create mode 100644 hutool-ai/src/main/resources/META-INF/services/cn.hutool.ai.core.AIConfig create mode 100644 hutool-ai/src/main/resources/META-INF/services/cn.hutool.ai.core.AIServiceProvider create mode 100644 hutool-ai/src/test/java/AIServiceFactoryTest.java create mode 100644 hutool-ai/src/test/java/AIUtilTest.java create mode 100644 hutool-ai/src/test/java/cn/hutool/ai/model/deepseek/DeepSeekServiceTest.java create mode 100644 hutool-ai/src/test/java/cn/hutool/ai/model/doubao/DoubaoServiceTest.java create mode 100644 hutool-ai/src/test/java/cn/hutool/ai/model/grok/GrokServiceTest.java create mode 100644 hutool-ai/src/test/java/cn/hutool/ai/model/openai/OpenaiServiceTest.java diff --git a/README-EN.md b/README-EN.md index aa70c0820..7e118bf2f 100755 --- a/README-EN.md +++ b/README-EN.md @@ -86,27 +86,28 @@ Hutool exists to reduce code search costs and avoid bugs caused by imperfect cod ## 🛠️Module A Java-based tool class for files, streams, encryption and decryption, transcoding, regular, thread, XML and other JDK methods for encapsulation,composing various Util tool classes, as well as providing the following modules: -| module | description | -| -------------------|-------------------------------------------------------------------------------------------------------------------------| -| hutool-aop | JDK dynamic proxy encapsulation to provide non-IOC faceting support | -| hutool-bloomFilter | Bloom filtering to provide some Hash algorithm Bloom filtering | -| hutool-cache | Simple cache | -| hutool-core | Core, including Bean operations, dates, various Utils, etc. | -| hutool-cron | Task scheduling with Cron expressions | -| hutool-crypto | Provides symmetric, asymmetric and digest algorithm encapsulation | -| hutool-db | Db operations based on ActiveRecord thinking. | -| hutool-dfa | DFA models, such as multi-keyword lookups | -| hutool-extra | Extension modules, third-party wrappers (template engine, mail, servlet, QR code, Emoji, FTP, word splitting, etc.) | -| hutool-http | Http client | -| hutool-log | Log (facade) | -| hutool-script | Script execution encapsulation, e.g. Javascript | -| hutool-setting | Stronger Setting Profile tools and Properties tools | -| hutool-system | System parameter tools (JVM information, etc.) | -| hutool-json | JSON | -| hutool-captcha | Image Captcha | -| hutool-poi | Tools for working with Excel and Word in POI | -| hutool-socket | Java-based tool classes for NIO and AIO sockets | -| hutool-jwt | JSON Web Token (JWT) implement | +| module | description | +|--------------------|---------------------------------------------------------------------------------------------------------------------| +| hutool-aop | JDK dynamic proxy encapsulation to provide non-IOC faceting support | +| hutool-bloomFilter | Bloom filtering to provide some Hash algorithm Bloom filtering | +| hutool-cache | Simple cache | +| hutool-core | Core, including Bean operations, dates, various Utils, etc. | +| hutool-cron | Task scheduling with Cron expressions | +| hutool-crypto | Provides symmetric, asymmetric and digest algorithm encapsulation | +| hutool-db | Db operations based on ActiveRecord thinking. | +| hutool-dfa | DFA models, such as multi-keyword lookups | +| hutool-extra | Extension modules, third-party wrappers (template engine, mail, servlet, QR code, Emoji, FTP, word splitting, etc.) | +| hutool-http | Http client | +| hutool-log | Log (facade) | +| hutool-script | Script execution encapsulation, e.g. Javascript | +| hutool-setting | Stronger Setting Profile tools and Properties tools | +| hutool-system | System parameter tools (JVM information, etc.) | +| hutool-json | JSON | +| hutool-captcha | Image Captcha | +| hutool-poi | Tools for working with Excel and Word in POI | +| hutool-socket | Java-based tool classes for NIO and AIO sockets | +| hutool-jwt | JSON Web Token (JWT) implement | +| hutool-ai | AI implement | Each module can be introduced individually, or all modules can be introduced by introducing `hutool-all` as required. diff --git a/README.md b/README.md index cd1ef3981..31c9b26f6 100755 --- a/README.md +++ b/README.md @@ -74,27 +74,28 @@ Hutool = Hu + tool,是原公司项目底层代码剥离后的开源库,“Hu ## 🛠️包含组件 一个Java基础工具类,对文件、流、加密解密、转码、正则、线程、XML等JDK方法进行封装,组成各种Util工具类,同时提供以下组件: -| 模块 | 介绍 | -| -------------------|---------------------------------------------------------------------------------- | -| hutool-aop | JDK动态代理封装,提供非IOC下的切面支持 | -| hutool-bloomFilter | 布隆过滤,提供一些Hash算法的布隆过滤 | -| hutool-cache | 简单缓存实现 | -| hutool-core | 核心,包括Bean操作、日期、各种Util等 | -| hutool-cron | 定时任务模块,提供类Crontab表达式的定时任务 | -| hutool-crypto | 加密解密模块,提供对称、非对称和摘要算法封装 | -| hutool-db | JDBC封装后的数据操作,基于ActiveRecord思想 | -| hutool-dfa | 基于DFA模型的多关键字查找 | -| hutool-extra | 扩展模块,对第三方封装(模板引擎、邮件、Servlet、二维码、Emoji、FTP、分词等) | -| hutool-http | 基于HttpUrlConnection的Http客户端封装 | -| hutool-log | 自动识别日志实现的日志门面 | -| hutool-script | 脚本执行封装,例如Javascript | -| hutool-setting | 功能更强大的Setting配置文件和Properties封装 | -| hutool-system | 系统参数调用封装(JVM信息等) | -| hutool-json | JSON实现 | -| hutool-captcha | 图片验证码实现 | -| hutool-poi | 针对POI中Excel和Word的封装 | -| hutool-socket | 基于Java的NIO和AIO的Socket封装 | -| hutool-jwt | JSON Web Token (JWT)封装实现 | +| 模块 | 介绍 | +|--------------------|------------------------------------------------| +| hutool-aop | JDK动态代理封装,提供非IOC下的切面支持 | +| hutool-bloomFilter | 布隆过滤,提供一些Hash算法的布隆过滤 | +| hutool-cache | 简单缓存实现 | +| hutool-core | 核心,包括Bean操作、日期、各种Util等 | +| hutool-cron | 定时任务模块,提供类Crontab表达式的定时任务 | +| hutool-crypto | 加密解密模块,提供对称、非对称和摘要算法封装 | +| hutool-db | JDBC封装后的数据操作,基于ActiveRecord思想 | +| hutool-dfa | 基于DFA模型的多关键字查找 | +| hutool-extra | 扩展模块,对第三方封装(模板引擎、邮件、Servlet、二维码、Emoji、FTP、分词等) | +| hutool-http | 基于HttpUrlConnection的Http客户端封装 | +| hutool-log | 自动识别日志实现的日志门面 | +| hutool-script | 脚本执行封装,例如Javascript | +| hutool-setting | 功能更强大的Setting配置文件和Properties封装 | +| hutool-system | 系统参数调用封装(JVM信息等) | +| hutool-json | JSON实现 | +| hutool-captcha | 图片验证码实现 | +| hutool-poi | 针对POI中Excel和Word的封装 | +| hutool-socket | 基于Java的NIO和AIO的Socket封装 | +| hutool-jwt | JSON Web Token (JWT)封装实现 | +| hutool-ai | AI大模型封装实现 | 可以根据需求对每个模块单独引入,也可以通过引入`hutool-all`方式引入所有模块。 diff --git a/hutool-ai/pom.xml b/hutool-ai/pom.xml new file mode 100644 index 000000000..43816e5aa --- /dev/null +++ b/hutool-ai/pom.xml @@ -0,0 +1,45 @@ + + + 4.0.0 + + cn.hutool + hutool-parent + 5.8.38-SNAPSHOT + + + hutool-ai + ${project.artifactId} + Hutool AI大模型封装 + + + cn.hutool.ai + + + + + cn.hutool + hutool-core + ${project.parent.version} + + + cn.hutool + hutool-http + ${project.parent.version} + + + cn.hutool + hutool-log + ${project.parent.version} + compile + + + cn.hutool + hutool-json + ${project.parent.version} + compile + + + + diff --git a/hutool-ai/src/main/java/cn/hutool/ai/AIException.java b/hutool-ai/src/main/java/cn/hutool/ai/AIException.java new file mode 100644 index 000000000..57bad86ad --- /dev/null +++ b/hutool-ai/src/main/java/cn/hutool/ai/AIException.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.hutool.ai; + +import cn.hutool.core.util.StrUtil; + +/** + * 异常处理类 + */ +public class AIException extends RuntimeException { + private static final long serialVersionUID = 1L; + + /** + * 构造 + * + * @param e 异常 + */ + public AIException(final Throwable e) { + super(e); + } + + /** + * 构造 + * + * @param message 消息 + */ + public AIException(final String message) { + super(message); + } + + /** + * 构造 + * + * @param messageTemplate 消息模板 + * @param params 参数 + */ + public AIException(String messageTemplate, Object... params) { + super(StrUtil.format(messageTemplate, params)); + } + + /** + * 构造 + * + * @param message 消息 + * @param cause 被包装的子异常 + */ + public AIException(final String message, final Throwable cause) { + super(message, cause); + } + + /** + * 构造 + * + * @param message 消息 + * @param cause 被包装的子异常 + * @param enableSuppression 是否启用抑制 + * @param writableStackTrace 堆栈跟踪是否应该是可写的 + */ + public AIException(final String message, final Throwable cause, final boolean enableSuppression, final boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + /** + * 构造 + * + * @param throwable 被包装的子异常 + * @param messageTemplate 消息模板 + * @param params 参数 + */ + public AIException(Throwable throwable, String messageTemplate, Object... params) { + super(StrUtil.format(messageTemplate, params), throwable); + } +} diff --git a/hutool-ai/src/main/java/cn/hutool/ai/AIServiceFactory.java b/hutool-ai/src/main/java/cn/hutool/ai/AIServiceFactory.java new file mode 100644 index 000000000..23cb87403 --- /dev/null +++ b/hutool-ai/src/main/java/cn/hutool/ai/AIServiceFactory.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.hutool.ai; + +import cn.hutool.ai.core.AIConfig; +import cn.hutool.ai.core.AIService; +import cn.hutool.ai.core.AIServiceProvider; +import cn.hutool.core.util.ServiceLoaderUtil; + +import java.util.Map; +import java.util.ServiceLoader; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 创建AIModelService的工厂类 + * + * @author elichow + * @since 5.8.38 + */ +public class AIServiceFactory { + + private static final Map providers = new ConcurrentHashMap<>(); + + // 加载所有 AIModelProvider 实现类 + static { + final ServiceLoader loader = ServiceLoaderUtil.load(AIServiceProvider.class); + for (final AIServiceProvider provider : loader) { + providers.put(provider.getServiceName().toLowerCase(), provider); + } + } + + /** + * 获取AI服务 + * + * @param config AIConfig配置 + * @return AI服务实例 + * @since 5.8.38 + */ + public static AIService getAIService(final AIConfig config) { + return getAIService(config, AIService.class); + } + + /** + * 获取AI服务 + * + * @param config AIConfig配置 + * @param clazz AI服务类 + * @return clazz对应的AI服务类实例 + * @since 5.8.38 + * @param AI服务类 + */ + @SuppressWarnings("unchecked") + public static T getAIService(final AIConfig config, final Class clazz) { + final AIServiceProvider provider = providers.get(config.getModelName().toLowerCase()); + if (provider == null) { + throw new IllegalArgumentException("Unsupported model: " + config.getModelName()); + } + + final AIService service = provider.create(config); + if (!clazz.isInstance(service)) { + throw new AIException("Model service is not of type: " + clazz.getSimpleName()); + } + + return (T) service; + } +} diff --git a/hutool-ai/src/main/java/cn/hutool/ai/AIUtil.java b/hutool-ai/src/main/java/cn/hutool/ai/AIUtil.java new file mode 100644 index 000000000..5da188bff --- /dev/null +++ b/hutool-ai/src/main/java/cn/hutool/ai/AIUtil.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.hutool.ai; + +import cn.hutool.ai.core.AIConfig; +import cn.hutool.ai.core.AIService; +import cn.hutool.ai.core.Message; +import cn.hutool.ai.model.deepseek.DeepSeekService; +import cn.hutool.ai.model.doubao.DoubaoService; +import cn.hutool.ai.model.grok.GrokService; +import cn.hutool.ai.model.openai.OpenaiService; + +import java.util.List; + +/** + * AI工具类 + * + * @author elichow + * @since 5.8.38 + */ +public class AIUtil { + + /** + * 获取AI模型服务,每个大模型提供的功能会不一样,可以调用此方法指定不同AI服务类,调用不同的功能 + * + * @param config 创建的AI服务模型的配置 + * @param clazz AI模型服务类 + * @return AIModelService的实现类实例 + * @since 5.8.38 + * @param AIService实现类 + */ + public static T getAIService(final AIConfig config, final Class clazz) { + return AIServiceFactory.getAIService(config, clazz); + } + + /** + * 获取AI模型服务 + * + * @param config 创建的AI服务模型的配置 + * @return AIModelService 其中只有公共方法 + * @since 5.8.38 + */ + public static AIService getAIService(final AIConfig config) { + return getAIService(config, AIService.class); + } + + /** + * 获取DeepSeek模型服务 + * + * @param config 创建的AI服务模型的配置 + * @return DeepSeekService + * @since 5.8.38 + */ + public static DeepSeekService getDeepSeekService(final AIConfig config) { + return getAIService(config, DeepSeekService.class); + } + + /** + * 获取Doubao模型服务 + * + * @param config 创建的AI服务模型的配置 + * @return DoubaoService + * @since 5.8.38 + */ + public static DoubaoService getDoubaoService(final AIConfig config) { + return getAIService(config, DoubaoService.class); + } + + /** + * 获取Grok模型服务 + * + * @param config 创建的AI服务模型的配置 + * @return GrokService + * @since 5.8.38 + */ + public static GrokService getGrokService(final AIConfig config) { + return getAIService(config, GrokService.class); + } + + /** + * 获取Openai模型服务 + * + * @param config 创建的AI服务模型的配置 + * @return OpenAIService + * @since 5.8.38 + */ + public static OpenaiService getOpenAIService(final AIConfig config) { + return getAIService(config, OpenaiService.class); + } + + /** + * AI大模型对话功能 + * + * @param config 创建的AI服务模型的配置 + * @param prompt 需要对话的内容 + * @return AI模型返回的Response响应字符串 + * @since 5.8.38 + */ + public static String chat(final AIConfig config, final String prompt) { + return getAIService(config).chat(prompt); + } + + /** + * AI大模型对话功能 + * + * @param config 创建的AI服务模型的配置 + * @param messages 由目前为止的对话组成的消息列表,可以设置role,content。详细参考官方文档 + * @return AI模型返回的Response响应字符串 + * @since 5.8.38 + */ + public static String chat(final AIConfig config, final List messages) { + return getAIService(config).chat(messages); + } + +} diff --git a/hutool-ai/src/main/java/cn/hutool/ai/ModelName.java b/hutool-ai/src/main/java/cn/hutool/ai/ModelName.java new file mode 100644 index 000000000..aa828a609 --- /dev/null +++ b/hutool-ai/src/main/java/cn/hutool/ai/ModelName.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.hutool.ai; + +/** + * 模型厂商的名称(不指具体的模型) + * + * @author elichow + * @since 5.8.38 + */ +public enum ModelName { + /** + * deepSeek + */ + DEEPSEEK("deepSeek"), + /** + * openai + */ + OPENAI("openai"), + /** + * doubao + */ + DOUBAO("doubao"), + /** + * grok + */ + GROK("grok"); + + private final String value; + + ModelName(final String value) { + this.value = value; + } + + /** + * 获取值 + * + * @return 值 + */ + public String getValue() { + return value; + } +} diff --git a/hutool-ai/src/main/java/cn/hutool/ai/Models.java b/hutool-ai/src/main/java/cn/hutool/ai/Models.java new file mode 100644 index 000000000..dd3ac7364 --- /dev/null +++ b/hutool-ai/src/main/java/cn/hutool/ai/Models.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.hutool.ai; + +/** + * 各模型厂商包含的model(指具体的模型) + * + * @author elichow + * @since 5.8.38 + */ +public class Models { + + // DeepSeek的模型 + public enum DeepSeek { + DEEPSEEK_CHAT("deepseek-chat"), + DEEPSEEK_REASONER("deepseek-reasoner"); + + private final String model; + + DeepSeek(String model) { + this.model = model; + } + + public String getModel() { + return model; + } + } + + // Openai的模型 + public enum Openai { + GPT_4_5_PREVIEW("gpt-4.5-preview"), + GPT_4O("gpt-4o"), + CHATGPT_4O_LATEST("chatgpt-4o-latest"), + GPT_4O_MINI("gpt-4o-mini"), + O1("o1"), + O1_MINI("o1-mini"), + O1_PREVIEW("o1-preview"), + O3_MINI("o3-mini"), + GPT_4O_REALTIME_PREVIEW("gpt-4o-realtime-preview"), + GPT_4O_MINI_REALTIME_PREVIEW("gpt-4o-mini-realtime-preview"), + GPT_4O_AUDIO_PREVIEW("gpt-4o-audio-preview"), + GPT_4O_MINI_AUDIO_PREVIEW("gpt-4o-mini-audio-preview"), + GPT_4_TURBO("gpt-4-turbo"), + GPT_4_TURBO_PREVIEW("gpt-4-turbo-preview"), + GPT_4("gpt-4"), + GPT_3_5_TURBO_0125("gpt-3.5-turbo-0125"), + GPT_3_5_TURBO("gpt-3.5-turbo"), + GPT_3_5_TURBO_1106("gpt-3.5-turbo-1106"), + GPT_3_5_TURBO_INSTRUCT("gpt-3.5-turbo-instruct"), + DALL_E_3("dall-e-3"), + DALL_E_2("dall-e-2"), + TTS_1("tts-1"), + TTS_1_HD("tts-1-hd"), + WHISPER_1("whisper-1"), + TEXT_EMBEDDING_3_LARGE("text-embedding-3-large"), + TEXT_EMBEDDING_3_SMALL("text-embedding-3-small"), + TEXT_EMBEDDING_ADA_002("text-embedding-ada-002"), + OMNI_MODERATION_LATEST("omni-moderation-latest"), + OMNI_MODERATION_2024_09_26("omni-moderation-2024-09-26"), + TEXT_MODERATION_LATEST("text-moderation-latest"), + TEXT_MODERATION_STABLE("text-moderation-stable"), + TEXT_MODERATION_007("text-moderation-007"), + BABBAGE_002("babbage-002"), + DAVINCI_002("davinci-002"); + + private final String model; + + Openai(String model) { + this.model = model; + } + + public String getModel() { + return model; + } + } + + // Doubao的模型 + public enum Doubao { + DOUBAO_1_5_PRO_32K("doubao-1.5-pro-32k-250115"), + DOUBAO_1_5_PRO_256K("doubao-1.5-pro-256k-250115"), + DOUBAO_1_5_LITE_32K("doubao-1.5-lite-32k-250115"), + DEEPSEEK_R1("deepseek-r1-250120"), + DEEPSEEK_R1_DISTILL_QWEN_32B("deepseek-r1-distill-qwen-32b-250120"), + DEEPSEEK_R1_DISTILL_QWEN_7B("deepseek-r1-distill-qwen-7b-250120"), + DEEPSEEK_V3("deepseek-v3-241226"), + DOUBAO_PRO_4K_240515("doubao-pro-4k-240515"), + DOUBAO_PRO_4K_CHARACTER_240728("doubao-pro-4k-character-240728"), + DOUBAO_PRO_4K_FUNCTIONCALL_240615("doubao-pro-4k-functioncall-240615"), + DOUBAO_PRO_4K_BROWSING_240524("doubao-pro-4k-browsing-240524"), + DOUBAO_PRO_32K_241215("doubao-pro-32k-241215"), + DOUBAO_PRO_32K_FUNCTIONCALL_241028("doubao-pro-32k-functioncall-241028"), + DOUBAO_PRO_32K_BROWSING_241115("doubao-pro-32k-browsing-241115"), + DOUBAO_PRO_32K_CHARACTER_241215("doubao-pro-32k-character-241215"), + DOUBAO_PRO_128K_240628("doubao-pro-128k-240628"), + DOUBAO_PRO_256K_240828("doubao-pro-256k-240828"), + DOUBAO_LITE_4K_240328("doubao-lite-4k-240328"), + DOUBAO_LITE_4K_PRETRAIN_CHARACTER_240516("doubao-lite-4k-pretrain-character-240516"), + DOUBAO_LITE_32K_240828("doubao-lite-32k-240828"), + DOUBAO_LITE_32K_CHARACTER_241015("doubao-lite-32k-character-241015"), + DOUBAO_LITE_128K_240828("240828"), + MOONSHOT_V1_8K("moonshot-v1-8k"), + MOONSHOT_V1_32K("moonshot-v1-32k"), + MOONSHOT_V1_128K("moonshot-v1-128k"), + CHATGLM3_130B_FC("chatglm3-130b-fc-v1.0"), + CHATGLM3_130_FIN("chatglm3-130-fin-v1.0-update"), + MISTRAL_7B("mistral-7b-instruct-v0.2"), + DOUBAO_1_5_VISION_PRO_32K("doubao-1.5-vision-pro-32k-250115"), + DOUBAO_VISION_PRO_32K("doubao-vision-pro-32k-241008"), + DOUBAO_VISION_LITE_32K("doubao-vision-lite-32k-241015"), + DOUBAO_EMBEDDING_LARGE("doubao-embedding-large-text-240915"), + DOUBAO_EMBEDDING_TEXT_240715("doubao-embedding-text-240715"), + DOUBAO_EMBEDDING_VISION("doubao-embedding-vision-241215"); + + private final String model; + + Doubao(String model) { + this.model = model; + } + + public String getModel() { + return model; + } + } + + // Grok的模型 + public enum Grok { + GROK_2_1212("grok-2-1212"), + GROK_2_VISION_1212("grok-2-vision-1212"), + GROK_BETA("grok-beta"), + GROK_VISION_BETA("grok-vision-beta"); + + private final String model; + + Grok(String model) { + this.model = model; + } + + public String getModel() { + return model; + } + } + +} diff --git a/hutool-ai/src/main/java/cn/hutool/ai/core/AIConfig.java b/hutool-ai/src/main/java/cn/hutool/ai/core/AIConfig.java new file mode 100644 index 000000000..dffcad977 --- /dev/null +++ b/hutool-ai/src/main/java/cn/hutool/ai/core/AIConfig.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.hutool.ai.core; + +import java.util.Map; + +/** + * AI配置类 + * + * @author elichow + * @since 5.8.38 + */ +public interface AIConfig { + + /** + * 获取模型(厂商)名称 + * + * @return 模型(厂商)名称 + * @since 5.8.38 + */ + default String getModelName() { + return this.getClass().getSimpleName(); + } + + /** + * 设置apiKey + * + * @param apiKey apiKey + * @since 5.8.38 + */ + void setApiKey(String apiKey); + + /** + * 获取apiKey + * + * @return apiKey + * @since 5.8.38 + */ + String getApiKey(); + + /** + * 设置apiUrl + * + * @param apiUrl api请求地址 + * @since 5.8.38 + */ + void setApiUrl(String apiUrl); + + /** + * 获取apiUrl + * + * @return apiUrl + * @since 5.8.38 + */ + String getApiUrl(); + + /** + * 设置model + * + * @param model model + * @since 5.8.38 + */ + void setModel(String model); + + /** + * 返回model + * + * @return model + * @since 5.8.38 + */ + String getModel(); + + /** + * 设置动态参数 + * + * @param key 参数字段 + * @param value 参数值 + * @since 5.8.38 + */ + void putAdditionalConfigByKey(String key, Object value); + + /** + * 获取动态参数 + * + * @param key 参数字段 + * @return 参数值 + * @since 5.8.38 + */ + Object getAdditionalConfigByKey(String key); + + /** + * 获取动态参数列表 + * + * @return 参数列表Map + * @since 5.8.38 + */ + Map getAdditionalConfigMap(); + +} diff --git a/hutool-ai/src/main/java/cn/hutool/ai/core/AIConfigBuilder.java b/hutool-ai/src/main/java/cn/hutool/ai/core/AIConfigBuilder.java new file mode 100644 index 000000000..9eb1698ab --- /dev/null +++ b/hutool-ai/src/main/java/cn/hutool/ai/core/AIConfigBuilder.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.hutool.ai.core; + +import java.lang.reflect.Constructor; + +/** + * 用于AIConfig的创建,创建同时支持链式设置参数 + * + * @author elichow + * @since 5.8.38 + */ +public class AIConfigBuilder { + + private final AIConfig config; + + /** + * 构造 + * + * @param modelName 模型厂商的名称(注意不是指具体的模型) + */ + public AIConfigBuilder(final String modelName) { + try { + // 获取配置类 + final Class configClass = AIConfigRegistry.getConfigClass(modelName); + if (configClass == null) { + throw new IllegalArgumentException("Unsupported model: " + modelName); + } + + // 使用反射创建实例 + final Constructor constructor = configClass.getDeclaredConstructor(); + config = constructor.newInstance(); + } catch (final Exception e) { + throw new RuntimeException("Failed to create AIConfig instance", e); + } + } + + /** + * 设置apiKey + * + * @param apiKey apiKey + * @return config + * @since 5.8.38 + */ + public synchronized AIConfigBuilder setApiKey(final String apiKey) { + if (apiKey != null) { + config.setApiKey(apiKey); + } + return this; + } + + /** + * 设置AI模型请求API接口的地址,不设置为默认值 + * + * @param apiUrl API接口地址 + * @return config + * @since 5.8.38 + */ + public synchronized AIConfigBuilder setApiUrl(final String apiUrl) { + if (apiUrl != null) { + config.setApiUrl(apiUrl); + } + return this; + } + + /** + * 设置具体的model,不设置为默认值 + * + * @param model 具体model的名称 + * @return config + * @since 5.8.38 + */ + public synchronized AIConfigBuilder setModel(final String model) { + if (model != null) { + config.setModel(model); + } + return this; + } + + /** + * 动态设置Request请求体中的属性字段,每个模型功能支持的字段请参照对应的官方文档 + * + * @param key Request中的支持的属性名 + * @param value 设置的属性值 + * @return config + * @since 5.8.38 + */ + public AIConfigBuilder putAdditionalConfig(final String key, final Object value) { + if (value != null) { + config.putAdditionalConfigByKey(key, value); + } + return this; + } + + /** + * 返回config实例 + * + * @return config + * @since 5.8.38 + */ + public AIConfig build() { + return config; + } +} diff --git a/hutool-ai/src/main/java/cn/hutool/ai/core/AIConfigRegistry.java b/hutool-ai/src/main/java/cn/hutool/ai/core/AIConfigRegistry.java new file mode 100644 index 000000000..616644049 --- /dev/null +++ b/hutool-ai/src/main/java/cn/hutool/ai/core/AIConfigRegistry.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.hutool.ai.core; + +import cn.hutool.core.util.ServiceLoaderUtil; + +import java.util.Map; +import java.util.ServiceLoader; +import java.util.concurrent.ConcurrentHashMap; + +/** + * AIConfig实现类的加载器 + * + * @author elichow + * @since 5.8.38 + */ +public class AIConfigRegistry { + + private static final Map> configClasses = new ConcurrentHashMap<>(); + + // 加载所有 AIConfig 实现类 + static { + final ServiceLoader loader = ServiceLoaderUtil.load(AIConfig.class); + for (final AIConfig config : loader) { + configClasses.put(config.getModelName().toLowerCase(), config.getClass()); + } + } + + /** + * 根据模型名称获取AIConfig实现类 + * + * @param modelName 模型名称 + * @return AIConfig实现类 + */ + public static Class getConfigClass(final String modelName) { + return configClasses.get(modelName.toLowerCase()); + } +} diff --git a/hutool-ai/src/main/java/cn/hutool/ai/core/AIService.java b/hutool-ai/src/main/java/cn/hutool/ai/core/AIService.java new file mode 100644 index 000000000..8bff076f8 --- /dev/null +++ b/hutool-ai/src/main/java/cn/hutool/ai/core/AIService.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.hutool.ai.core; + +import java.util.List; + +/** + * 模型公共的API功能,特有的功能在model.xx.XXService下定义 + * + * @author elichow + * @since 5.8.38 + */ +public interface AIService { + + /** + * 对话 + * + * @param prompt user题词 + * @return AI回答 + * @since 5.8.38 + */ + String chat(String prompt); + + /** + * 对话 + * + * @param messages 由目前为止的对话组成的消息列表,可以设置role,content。详细参考官方文档 + * @return AI回答 + * @since 5.8.38 + */ + String chat(final List messages); + +} diff --git a/hutool-ai/src/main/java/cn/hutool/ai/core/AIServiceProvider.java b/hutool-ai/src/main/java/cn/hutool/ai/core/AIServiceProvider.java new file mode 100644 index 000000000..341f43a7f --- /dev/null +++ b/hutool-ai/src/main/java/cn/hutool/ai/core/AIServiceProvider.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.hutool.ai.core; + +/** + * 用于加载AI服务,每一个通过SPI创建的AI服务都要实现此接口 + * + * @author elichow + * @since 5.8.38 + */ +public interface AIServiceProvider { + + /** + * 获取AI服务名称 + * + * @return AI服务名称 + * @since 5.8.38 + */ + String getServiceName(); + + /** + * 创建AI服务实例 + * + * @param config AIConfig配置 + * @param AIService实现类 + * @return AI服务实例 + * @since 5.8.38 + */ + T create(final AIConfig config); +} diff --git a/hutool-ai/src/main/java/cn/hutool/ai/core/BaseAIService.java b/hutool-ai/src/main/java/cn/hutool/ai/core/BaseAIService.java new file mode 100644 index 000000000..218db8012 --- /dev/null +++ b/hutool-ai/src/main/java/cn/hutool/ai/core/BaseAIService.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.hutool.ai.core; + +import cn.hutool.ai.AIException; +import cn.hutool.http.*; + +import java.util.Map; + +/** + * 基础AIService,包含基公共参数和公共方法 + * + * @author elichow + * @since 5.8.38 + */ +public class BaseAIService { + + protected final AIConfig config; + + /** + * 构造方法 + * + * @param config AI配置 + */ + public BaseAIService(final AIConfig config) { + this.config = config; + } + + /** + * 发送Get请求 + * @param endpoint 请求节点 + * @return 请求响应 + */ + protected HttpResponse sendGet(final String endpoint) { + //链式构建请求 + try { + //设置超时3分钟 + return HttpRequest.get(config.getApiUrl() + endpoint) + .header(Header.ACCEPT, "application/json") + .header(Header.AUTHORIZATION, "Bearer " + config.getApiKey()) + .timeout(180000) + .execute(); + } catch (final AIException e) { + throw new AIException("Failed to send GET request: " + e.getMessage(), e); + } + } + + /** + * 发送Post请求 + * @param endpoint 请求节点 + * @param paramJson 请求参数json + * @return 请求响应 + */ + protected HttpResponse sendPost(final String endpoint, final String paramJson) { + //链式构建请求 + try { + return HttpRequest.post(config.getApiUrl() + endpoint) + .header(Header.CONTENT_TYPE, "application/json") + .header(Header.ACCEPT, "application/json") + .header(Header.AUTHORIZATION, "Bearer " + config.getApiKey()) + .body(paramJson) + .timeout(180000) + .execute(); + } catch (final AIException e) { + throw new AIException("Failed to send POST request:" + e.getMessage(), e); + } + + } + + /** + * 发送表单请求 + * @param endpoint 请求节点 + * @param paramMap 请求参数map + * @return 请求响应 + */ + protected HttpResponse sendFormData(final String endpoint, final Map paramMap) { + //链式构建请求 + try { + //设置超时3分钟 + return HttpRequest.post(config.getApiUrl() + endpoint) + .header(Header.CONTENT_TYPE, "multipart/form-data") + .header(Header.ACCEPT, "application/json") + .header(Header.AUTHORIZATION, "Bearer " + config.getApiKey()) + .form(paramMap) + .timeout(180000) + .execute(); + } catch (final AIException e) { + throw new AIException("Failed to send POST request:" + e.getMessage(), e); + } + } +} diff --git a/hutool-ai/src/main/java/cn/hutool/ai/core/BaseConfig.java b/hutool-ai/src/main/java/cn/hutool/ai/core/BaseConfig.java new file mode 100644 index 000000000..0d5cd0747 --- /dev/null +++ b/hutool-ai/src/main/java/cn/hutool/ai/core/BaseConfig.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.hutool.ai.core; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Config基础类,定义模型配置的基本属性 + * + * @author elichow + * @since 5.8.38 + */ +public class BaseConfig implements AIConfig { + + //apiKey + protected volatile String apiKey; + //API请求地址 + protected volatile String apiUrl; + //具体模型 + protected volatile String model; + //动态扩展字段 + protected Map additionalConfig = new ConcurrentHashMap<>(); + + @Override + public void setApiKey(final String apiKey) { + this.apiKey = apiKey; + } + + @Override + public String getApiKey() { + return apiKey; + } + + @Override + public void setApiUrl(final String apiUrl) { + this.apiUrl = apiUrl; + } + + @Override + public String getApiUrl() { + return apiUrl; + } + + @Override + public void setModel(final String model) { + this.model = model; + } + + @Override + public String getModel() { + return model; + } + + @Override + public void putAdditionalConfigByKey(final String key, final Object value) { + this.additionalConfig.put(key, value); + } + + @Override + public Object getAdditionalConfigByKey(final String key) { + return additionalConfig.get(key); + } + + @Override + public Map getAdditionalConfigMap() { + return new ConcurrentHashMap<>(additionalConfig); + } + +} diff --git a/hutool-ai/src/main/java/cn/hutool/ai/core/Message.java b/hutool-ai/src/main/java/cn/hutool/ai/core/Message.java new file mode 100644 index 000000000..cf2540021 --- /dev/null +++ b/hutool-ai/src/main/java/cn/hutool/ai/core/Message.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.hutool.ai.core; + +/** + * 公共Message类 + * + * @author elichow + * @since 5.8.38 + */ +public class Message { + //角色 注意:如果设置系统消息,请放在messages列表的第一位 + private final String role; + //内容 + private final Object content; + + /** + * 构造 + * + * @param role 角色 + * @param content 内容 + */ + public Message(final String role, final Object content) { + this.role = role; + this.content = content; + } + + /** + * 获取角色 + * + * @return 角色 + */ + public String getRole() { + return role; + } + + /** + * 获取内容 + * + * @return 内容 + */ + public Object getContent() { + return content; + } +} diff --git a/hutool-ai/src/main/java/cn/hutool/ai/core/package-info.java b/hutool-ai/src/main/java/cn/hutool/ai/core/package-info.java new file mode 100644 index 000000000..855a61034 --- /dev/null +++ b/hutool-ai/src/main/java/cn/hutool/ai/core/package-info.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * AI相关基础类 + * + * @author elichow + * @since 5.8.38 + */ + +package cn.hutool.ai.core; diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/deepseek/DeepSeekCommon.java b/hutool-ai/src/main/java/cn/hutool/ai/model/deepseek/DeepSeekCommon.java new file mode 100644 index 000000000..c712b488a --- /dev/null +++ b/hutool-ai/src/main/java/cn/hutool/ai/model/deepseek/DeepSeekCommon.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.hutool.ai.model.deepseek; + +/** + * deepSeek公共类 + * + * @author elichow + * @since 5.8.38 + */ +public class DeepSeekCommon { + +} diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/deepseek/DeepSeekConfig.java b/hutool-ai/src/main/java/cn/hutool/ai/model/deepseek/DeepSeekConfig.java new file mode 100644 index 000000000..8497d6fd6 --- /dev/null +++ b/hutool-ai/src/main/java/cn/hutool/ai/model/deepseek/DeepSeekConfig.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.hutool.ai.model.deepseek; + +import cn.hutool.ai.Models; +import cn.hutool.ai.core.BaseConfig; + +/** + * DeepSeek配置类,初始化API接口地址,设置默认的模型 + * + * @author elichow + * @since 5.8.38 + */ +public class DeepSeekConfig extends BaseConfig { + + private final String API_URL = "https://api.deepseek.com"; + + private final String DEFAULT_MODEL = Models.DeepSeek.DEEPSEEK_CHAT.getModel(); + + public DeepSeekConfig() { + setApiUrl(API_URL); + setModel(DEFAULT_MODEL); + } + + public DeepSeekConfig(String apiKey) { + this(); + setApiKey(apiKey); + } + + @Override + public String getModelName() { + return "deepSeek"; + } + +} diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/deepseek/DeepSeekProvider.java b/hutool-ai/src/main/java/cn/hutool/ai/model/deepseek/DeepSeekProvider.java new file mode 100644 index 000000000..6fe344d92 --- /dev/null +++ b/hutool-ai/src/main/java/cn/hutool/ai/model/deepseek/DeepSeekProvider.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.hutool.ai.model.deepseek; + +import cn.hutool.ai.core.AIConfig; +import cn.hutool.ai.core.AIServiceProvider; + +/** + * 创建DeepSeek服务实现类 + * + * @author elichow + * @since 5.8.38 + */ +public class DeepSeekProvider implements AIServiceProvider { + + @Override + public String getServiceName() { + return "deepSeek"; + } + + @Override + public DeepSeekService create(final AIConfig config) { + return new DeepSeekServiceImpl(config); + } +} diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/deepseek/DeepSeekService.java b/hutool-ai/src/main/java/cn/hutool/ai/model/deepseek/DeepSeekService.java new file mode 100644 index 000000000..b601d5934 --- /dev/null +++ b/hutool-ai/src/main/java/cn/hutool/ai/model/deepseek/DeepSeekService.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.hutool.ai.model.deepseek; + +import cn.hutool.ai.core.AIService; + +/** + * deepSeek支持的扩展接口 + * + * @author elichow + * @since 5.8.38 + */ +public interface DeepSeekService extends AIService { + + /** + * 模型beta功能 + * + * @param prompt 题词 + * @return AI的回答 + * @since 5.8.38 + */ + String beta(String prompt); + + /** + * 列出所有模型列表 + * + * @return model列表 + * @since 5.8.38 + */ + String models(); + + /** + * 查询余额 + * + * @return 余额 + * @since 5.8.38 + */ + String balance(); +} diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/deepseek/DeepSeekServiceImpl.java b/hutool-ai/src/main/java/cn/hutool/ai/model/deepseek/DeepSeekServiceImpl.java new file mode 100644 index 000000000..54c38f9dc --- /dev/null +++ b/hutool-ai/src/main/java/cn/hutool/ai/model/deepseek/DeepSeekServiceImpl.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.hutool.ai.model.deepseek; + +import cn.hutool.ai.core.AIConfig; +import cn.hutool.ai.core.BaseAIService; +import cn.hutool.ai.core.Message; +import cn.hutool.http.HttpResponse; +import cn.hutool.json.JSONUtil; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * DeepSeek服务,AI具体功能的实现 + * + * @author elichow + * @since 5.8.38 + */ +public class DeepSeekServiceImpl extends BaseAIService implements DeepSeekService { + + //对话补全 + private final String CHAT_ENDPOINT = "/chat/completions"; + //FIM补全(beta) + private final String BETA_ENDPOINT = "/beta/completions"; + //列出模型 + private final String MODELS_ENDPOINT = "/models"; + //余额查询 + private final String BALANCE_ENDPOINT = "/user/balance"; + + /** + * 构造函数 + * + * @param config AI配置 + */ + public DeepSeekServiceImpl(final AIConfig config) { + //初始化DeepSeek客户端 + super(config); + } + + @Override + public String chat(final String prompt) { + // 定义消息结构 + final List messages = new ArrayList<>(); + messages.add(new Message("system", "You are a helpful assistant")); + messages.add(new Message("user", prompt)); + return chat(messages); + } + + @Override + public String chat(final List messages) { + final String paramJson = buildChatRequestBody(messages); + final HttpResponse response = sendPost(CHAT_ENDPOINT, paramJson); + return response.body(); + } + + @Override + public String beta(final String prompt) { + final String paramJson = buildBetaRequestBody(prompt); + final HttpResponse response = sendPost(BETA_ENDPOINT, paramJson); + return response.body(); + } + + @Override + public String models() { + final HttpResponse response = sendGet(MODELS_ENDPOINT); + return response.body(); + } + + @Override + public String balance() { + final HttpResponse response = sendGet(BALANCE_ENDPOINT); + return response.body(); + } + + // 构建chat请求体 + private String buildChatRequestBody(final List messages) { + //使用JSON工具 + final Map paramMap = new HashMap<>(); + paramMap.put("model", config.getModel()); + paramMap.put("messages", messages); + //合并其他参数 + paramMap.putAll(config.getAdditionalConfigMap()); + + return JSONUtil.toJsonStr(paramMap); + } + + // 构建beta请求体 + private String buildBetaRequestBody(final String prompt) { + // 定义消息结构 + //使用JSON工具 + final Map paramMap = new HashMap<>(); + paramMap.put("model", config.getModel()); + paramMap.put("prompt", prompt); +// //合并其他参数 + paramMap.putAll(config.getAdditionalConfigMap()); + + return JSONUtil.toJsonStr(paramMap); + } + +} diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/deepseek/package-info.java b/hutool-ai/src/main/java/cn/hutool/ai/model/deepseek/package-info.java new file mode 100644 index 000000000..99b1ca414 --- /dev/null +++ b/hutool-ai/src/main/java/cn/hutool/ai/model/deepseek/package-info.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * 对deepSeek的封装实现 + * + * @author elichow + * @since 5.8.38 + */ + +package cn.hutool.ai.model.deepseek; diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/doubao/DoubaoCommon.java b/hutool-ai/src/main/java/cn/hutool/ai/model/doubao/DoubaoCommon.java new file mode 100644 index 000000000..494f2c668 --- /dev/null +++ b/hutool-ai/src/main/java/cn/hutool/ai/model/doubao/DoubaoCommon.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.hutool.ai.model.doubao; + +/** + * doubao公共类 + * + * @author elichow + * @since 5.8.38 + */ +public class DoubaoCommon { + + //doubao上下文缓存参数 + public enum DoubaoContext { + + SESSION("session"), + COMMON_PREFIX("common_prefix"); + + private final String mode; + + DoubaoContext(String mode) { + this.mode = mode; + } + + public String getMode() { + return mode; + } + } + + //doubao视觉参数 + public enum DoubaoVision { + + AUTO("auto"), + LOW("low"), + HIGH("high"); + + private final String detail; + + DoubaoVision(String detail) { + this.detail = detail; + } + + public String getDetail() { + return detail; + } + } + + //doubao视频生成参数 + public enum DoubaoVideo { + + //宽高比例 + RATIO_16_9("--rt", "16:9"),//[1280, 720] + RATIO_4_3("--rt", "4:3"),//[960, 720] + RATIO_1_1("--rt", "1:1"),//[720, 720] + RATIO_3_4("--rt", "3:4"),//[720, 960] + RATIO_9_16("--rt", "9:16"),//[720, 1280] + RATIO_21_9("--rt", "21:9"),//[1280, 544] + + //生成视频时长 + DURATION_5("--dur", 5),//文生视频,图生视频 + DURATION_10("--dur", 10),//文生视频 + + //帧率,即一秒时间内视频画面数量 + FPS_5("--fps", 24), + + //视频分辨率 + RESOLUTION_5("--rs", "720p"), + + //生成视频是否包含水印 + WATERMARK_TRUE("--wm", true), + WATERMARK_FALSE("--wm", false); + + private final String type; + private final Object value; + + DoubaoVideo(String type, Object value) { + this.type = type; + this.value = value; + } + + public String getType() { + return type; + } + + public Object getValue() { + if (value instanceof String) { + return (String) value; + } else if (value instanceof Integer) { + return (Integer) value; + } else if (value instanceof Boolean) { + return (Boolean) value; + } + return value; + } + + } +} diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/doubao/DoubaoConfig.java b/hutool-ai/src/main/java/cn/hutool/ai/model/doubao/DoubaoConfig.java new file mode 100644 index 000000000..fa7b1c8a5 --- /dev/null +++ b/hutool-ai/src/main/java/cn/hutool/ai/model/doubao/DoubaoConfig.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.hutool.ai.model.doubao; + +import cn.hutool.ai.Models; +import cn.hutool.ai.core.BaseConfig; + +/** + * Doubao配置类,初始化API接口地址,设置默认的模型 + * + * @author elichow + * @since 5.8.38 + */ +public class DoubaoConfig extends BaseConfig { + + private final String API_URL = "https://ark.cn-beijing.volces.com/api/v3"; + + private final String DEFAULT_MODEL = Models.Doubao.DOUBAO_1_5_LITE_32K.getModel(); + + public DoubaoConfig() { + setApiUrl(API_URL); + setModel(DEFAULT_MODEL); + } + + public DoubaoConfig(String apiKey) { + this(); + setApiKey(apiKey); + } + + @Override + public String getModelName() { + return "doubao"; + } + +} diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/doubao/DoubaoProvider.java b/hutool-ai/src/main/java/cn/hutool/ai/model/doubao/DoubaoProvider.java new file mode 100644 index 000000000..46d5be8d2 --- /dev/null +++ b/hutool-ai/src/main/java/cn/hutool/ai/model/doubao/DoubaoProvider.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.hutool.ai.model.doubao; + +import cn.hutool.ai.core.AIConfig; +import cn.hutool.ai.core.AIServiceProvider; + +/** + * 创建Doubap服务实现类 + * + * @author elichow + * @since 5.8.38 + */ +public class DoubaoProvider implements AIServiceProvider { + + @Override + public String getServiceName() { + return "doubao"; + } + + @Override + public DoubaoService create(final AIConfig config) { + return new DoubaoServiceImpl(config); + } +} diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/doubao/DoubaoService.java b/hutool-ai/src/main/java/cn/hutool/ai/model/doubao/DoubaoService.java new file mode 100644 index 000000000..14d087115 --- /dev/null +++ b/hutool-ai/src/main/java/cn/hutool/ai/model/doubao/DoubaoService.java @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.hutool.ai.model.doubao; + +import cn.hutool.ai.core.AIService; +import cn.hutool.ai.core.Message; + +import java.util.List; + +/** + * doubao支持的扩展接口 + * + * @author elichow + * @since 5.8.38 + */ +public interface DoubaoService extends AIService { + + /** + * 图像理解:模型会依据传入的图片信息以及问题,给出回复。 + * + * @param prompt 提问 + * @param images 图片列表/或者图片Base64编码图片列表(URI形式) + * @param detail 手动设置图片的质量,取值范围high、low、auto,默认为auto + * @return AI回答 + * @since 5.8.38 + */ + String chatVision(String prompt, final List images, String detail); + + /** + * 图像理解:模型会依据传入的图片信息以及问题,给出回复。 + * + * @param prompt 提问 + * @param images 传入的图片列表地址/或者图片Base64编码图片列表(URI形式) + * @return AI回答 + * @since 5.8.38 + */ + default String chatVision(String prompt, final List images) { + return chatVision(prompt, images, DoubaoCommon.DoubaoVision.AUTO.getDetail()); + } + + /** + * 创建视频生成任务 + * 注意:调用该方法时,配置config中的model为您创建的推理接入点(Endpoint)ID。详细参考官方文档 + * + * @param text 文本提示词 + * @param image 图片/或者图片Base64编码图片(URI形式) + * @param videoParams 视频参数列表 + * @return 生成任务id + * @since 5.8.38 + */ + String videoTasks(String text, String image, final List videoParams); + + /** + * 创建视频生成任务 + * 注意:调用该方法时,配置config中的model为您创建的推理接入点(Endpoint)ID。详细参考官方文档 + * + * @param text 文本提示词 + * @param image 图片/或者图片Base64编码图片(URI形式) + * @return 生成任务id + * @since 5.8.38 + */ + default String videoTasks(String text, String image) { + return videoTasks(text, image, null); + } + + /** + * 查询视频生成任务信息 + * + * @param taskId 通过创建生成视频任务返回的生成任务id + * @return 生成任务信息 + * @since 5.8.38 + */ + String getVideoTasksInfo(String taskId); + + /** + * 文本向量化 + * + * @param input 需要向量化的内容列表,支持中文、英文 + * @return 处理后的向量信息 + * @since 5.8.38 + */ + String embeddingText(String[] input); + + /** + * 图文向量化:仅支持单一文本、单张图片或文本与图片的组合输入(即一段文本 + 一张图片),暂不支持批量文本 / 图片的同时处理 + * + * @param text 需要向量化的内容 + * @param image 需要向量化的图片地址/或者图片Base64编码图片(URI形式) + * @return 处理后的向量信息 + * @since 5.8.38 + */ + String embeddingVision(String text, String image); + + /** + * 应用(Bot) config中model设置为您创建的应用ID + * + * @param messages 由对话组成的消息列表。如系统人设,背景信息等,用户自定义的信息 + * @return AI回答 + * @since 5.8.38 + */ + String botsChat(final List messages); + + /** + * 分词:可以将文本转换为模型可理解的 token id,并返回文本的 tokens 数量、token id、 token 在原始文本中的偏移量等信息 + * + * @param text 需要分词的内容列表 + * @return 分词结果 + * @since 5.8.38 + */ + String tokenization(String[] text); + + /** + * 批量推理 Chat + * 注意:调用该方法时,配置config中的model为您创建的批量推理接入点(Endpoint)ID。详细参考官方文档 + * 该方法不支持流式 + * + * @param prompt chat内容 + * @return AI回答 + * @since 5.8.38 + */ + String batchChat(String prompt); + + /** + * 批量推理 Chat + * 注意:调用该方法时,配置config中的model为您创建的批量推理接入点(Endpoint)ID。详细参考官方文档 + * 该方法不支持流式 + * + * @param messages 由对话组成的消息列表。如系统人设,背景信息等,用户自定义的信息 + * @return AI回答 + * @since 5.8.38 + */ + String batchChat(final List messages); + + /** + * 创建上下文缓存: 创建上下文缓存,获得缓存 id字段后,在上下文缓存对话 API中使用。 + * 注意:调用该方法时,配置config中的model为您创建的推理接入点(Endpoint)ID, + * 推理接入点中使用的模型需要在模型管理中开启缓存功能。详细参考官方文档 + * + * @param messages 由对话组成的消息列表。如系统人设,背景信息等,用户自定义的信息 + * @param mode 上下文缓存的类型,详细参考官方文档 默认为session + * @return 返回的缓存id + * @since 5.8.38 + */ + String createContext(final List messages, String mode); + + /** + * 创建上下文缓存: 创建上下文缓存,获得缓存 id字段后,在上下文缓存对话 API中使用。 + * 注意:调用该方法时,配置config中的model为您创建的推理接入点(Endpoint)ID, + * 推理接入点中使用的模型需要在模型管理中开启缓存功能。详细参考官方文档 + * + * @param messages 由对话组成的消息列表。如系统人设,背景信息等,用户自定义的信息 + * @return 返回的缓存id + * @since 5.8.38 + */ + default String createContext(final List messages) { + return createContext(messages, DoubaoCommon.DoubaoContext.SESSION.getMode()); + } + + /** + * 上下文缓存对话: 向大模型发起带上下文缓存的请求 + * 注意:配置config中的model可以为您创建的推理接入点(Endpoint)ID,也可以是支持chat的model + * + * @param prompt 对话的内容题词 + * @param contextId 创建上下文缓存后获取的缓存id + * @return AI的回答 + * @since 5.8.38 + */ + String chatContext(String prompt, String contextId); + + /** + * 上下文缓存对话: 向大模型发起带上下文缓存的请求 + * 注意:配置config中的model可以为您创建的推理接入点(Endpoint)ID,也可以是支持chat的model + * + * @param messages 对话的信息 不支持最后一个元素的role设置为assistant。如使用session 缓存(mode设置为session)传入最新一轮对话的信息,无需传入历史信息 + * @param contextId 创建上下文缓存后获取的缓存id + * @return AI的回答 + * @since 5.8.38 + */ + String chatContext(final List messages, String contextId); + +} diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/doubao/DoubaoServiceImpl.java b/hutool-ai/src/main/java/cn/hutool/ai/model/doubao/DoubaoServiceImpl.java new file mode 100644 index 000000000..88a24d218 --- /dev/null +++ b/hutool-ai/src/main/java/cn/hutool/ai/model/doubao/DoubaoServiceImpl.java @@ -0,0 +1,354 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.hutool.ai.model.doubao; + +import cn.hutool.ai.core.AIConfig; +import cn.hutool.ai.core.BaseAIService; +import cn.hutool.ai.core.Message; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpResponse; +import cn.hutool.json.JSONUtil; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Doubao服务,AI具体功能的实现 + * + * @author elichow + * @since 5.8.38 + */ +public class DoubaoServiceImpl extends BaseAIService implements DoubaoService { + + //对话 + private final String CHAT_ENDPOINT = "/chat/completions"; + //文本向量化 + private final String EMBEDDING_TEXT = "/embeddings"; + //图文向量化 + private final String EMBEDDING_VISION = "/embeddings/multimodal"; + //应用bots + private final String BOTS_CHAT = "/bots/chat/completions"; + //分词 + private final String TOKENIZATION = "/tokenization"; + //批量推理chat + private final String BATCH_CHAT = "/batch/chat/completions"; + //创建上下文缓存 + private final String CREATE_CONTEXT = "/context/create"; + //上下文缓存对话 + private final String CHAT_CONTEXT = "/context/chat/completions"; + //创建视频生成任务 + private final String CREATE_VIDEO = "/contents/generations/tasks"; + + public DoubaoServiceImpl(final AIConfig config) { + //初始化doubao客户端 + super(config); + } + + @Override + public String chat(String prompt) { + // 定义消息结构 + final List messages = new ArrayList<>(); + messages.add(new Message("system", "You are a helpful assistant")); + messages.add(new Message("user", prompt)); + return chat(messages); + } + + @Override + public String chat(final List messages) { + String paramJson = buildChatRequestBody(messages); + final HttpResponse response = sendPost(CHAT_ENDPOINT, paramJson); + return response.body(); + } + + @Override + public String chatVision(String prompt, final List images, String detail) { + String paramJson = buildChatVisionRequestBody(prompt, images, detail); + final HttpResponse response = sendPost(CHAT_ENDPOINT, paramJson); + return response.body(); + } + + @Override + public String videoTasks(String text, String image, final List videoParams) { + String paramJson = buildGenerationsTasksRequestBody(text, image, videoParams); + final HttpResponse response = sendPost(CREATE_VIDEO, paramJson); + return response.body(); + } + + @Override + public String getVideoTasksInfo(String taskId) { + final HttpResponse response = sendGet(CREATE_VIDEO + "/" + taskId); + return response.body(); + } + + + @Override + public String embeddingText(String[] input) { + String paramJson = buildEmbeddingTextRequestBody(input); + final HttpResponse response = sendPost(EMBEDDING_TEXT, paramJson); + return response.body(); + } + + @Override + public String embeddingVision(String text, String image) { + String paramJson = buildEmbeddingVisionRequestBody(text, image); + final HttpResponse response = sendPost(EMBEDDING_VISION, paramJson); + return response.body(); + } + + @Override + public String botsChat(final List messages) { + String paramJson = buildBotsChatRequestBody(messages); + final HttpResponse response = sendPost(BOTS_CHAT, paramJson); + return response.body(); + } + + @Override + public String tokenization(String[] text) { + String paramJson = buildTokenizationRequestBody(text); + final HttpResponse response = sendPost(TOKENIZATION, paramJson); + return response.body(); + } + + @Override + public String batchChat(String prompt) { + // 定义消息结构 + final List messages = new ArrayList<>(); + messages.add(new Message("system", "You are a helpful assistant")); + messages.add(new Message("user", prompt)); + return batchChat(messages); + } + + @Override + public String batchChat(final List messages) { + String paramJson = buildBatchChatRequestBody(messages); + final HttpResponse response = sendPost(BATCH_CHAT, paramJson); + return response.body(); + } + + @Override + public String createContext(final List messages, String mode) { + String paramJson = buildCreateContextRequest(messages, mode); + final HttpResponse response = sendPost(CREATE_CONTEXT, paramJson); + return response.body(); + } + + @Override + public String chatContext(String prompt, String contextId) { + // 定义消息结构 + final List messages = new ArrayList<>(); + messages.add(new Message("user", prompt)); + return chatContext(messages, contextId); + } + + @Override + public String chatContext(final List messages, String contextId) { + String paramJson = buildChatContentRequestBody(messages, contextId); + final HttpResponse response = sendPost(CHAT_CONTEXT, paramJson); + return response.body(); + } + + // 构建chat请求体 + private String buildChatRequestBody(final List messages) { + //使用JSON工具 + final Map paramMap = new HashMap<>(); + paramMap.put("model", config.getModel()); + paramMap.put("messages", messages); + //合并其他参数 + paramMap.putAll(config.getAdditionalConfigMap()); + + return JSONUtil.toJsonStr(paramMap); + } + + //构建chatVision请求体 + private String buildChatVisionRequestBody(String prompt, final List images, String detail) { + // 定义消息结构 + final List messages = new ArrayList<>(); + final List content = new ArrayList<>(); + + final Map contentMap = new HashMap<>(); + contentMap.put("type", "text"); + contentMap.put("text", prompt); + content.add(contentMap); + for (String img : images) { + HashMap imgUrlMap = new HashMap<>(); + imgUrlMap.put("type", "image_url"); + HashMap 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 paramMap = new HashMap<>(); + paramMap.put("model", config.getModel()); + paramMap.put("messages", messages); + //合并其他参数 + paramMap.putAll(config.getAdditionalConfigMap()); + return JSONUtil.toJsonStr(paramMap); + } + + //构建文本向量化请求体 + private String buildEmbeddingTextRequestBody(String[] input) { + //使用JSON工具 + final Map paramMap = new HashMap<>(); + paramMap.put("model", config.getModel()); + paramMap.put("input", input); + //合并其他参数 + paramMap.putAll(config.getAdditionalConfigMap()); + return JSONUtil.toJsonStr(paramMap); + } + + //构建图文向量化请求体 + private String buildEmbeddingVisionRequestBody(String text, String image) { + //使用JSON工具 + final Map paramMap = new HashMap<>(); + paramMap.put("model", config.getModel()); + + final List input = new ArrayList<>(); + //添加文本参数 + if (!StrUtil.isBlank(text)) { + final Map textMap = new HashMap<>(); + textMap.put("type", "text"); + textMap.put("text", text); + input.add(textMap); + } + //添加图片参数 + if (!StrUtil.isBlank(image)) { + final Map imgUrlMap = new HashMap<>(); + imgUrlMap.put("type", "image_url"); + final Map urlMap = new HashMap<>(); + urlMap.put("url", image); + imgUrlMap.put("image_url", urlMap); + input.add(imgUrlMap); + } + + paramMap.put("input", input); + //合并其他参数 + paramMap.putAll(config.getAdditionalConfigMap()); + + return JSONUtil.toJsonStr(paramMap); + } + + //构建应用chat请求体 + private String buildBotsChatRequestBody(final List messages) { + return buildChatRequestBody(messages); + } + + //构建分词请求体 + private String buildTokenizationRequestBody(String[] text) { + final Map paramMap = new HashMap<>(); + paramMap.put("model", config.getModel()); + paramMap.put("text", text); + return JSONUtil.toJsonStr(paramMap); + } + + //构建批量推理chat请求体 + private String buildBatchChatRequestBody(final List messages) { + return buildChatRequestBody(messages); + } + + //构建创建上下文缓存请求体 + private String buildCreateContextRequest(final List messages, String mode) { + final Map paramMap = new HashMap<>(); + paramMap.put("messages", messages); + paramMap.put("model", config.getModel()); + paramMap.put("mode", mode); + //合并其他参数 + paramMap.putAll(config.getAdditionalConfigMap()); + + return JSONUtil.toJsonStr(paramMap); + } + + //构建上下文缓存对话请求体 + private String buildChatContentRequestBody(final List messages, String contextId) { + //使用JSON工具 + final Map paramMap = new HashMap<>(); + paramMap.put("model", config.getModel()); + paramMap.put("messages", messages); + paramMap.put("context_id", contextId); + //合并其他参数 + paramMap.putAll(config.getAdditionalConfigMap()); + + return JSONUtil.toJsonStr(paramMap); + } + + //构建创建视频任务请求体 + private String buildGenerationsTasksRequestBody(String text, String image, final List videoParams) { + //使用JSON工具 + final Map paramMap = new HashMap<>(); + paramMap.put("model", config.getModel()); + + final List content = new ArrayList<>(); + //添加文本参数 + final Map textMap = new HashMap<>(); + if (!StrUtil.isBlank(text)) { + textMap.put("type", "text"); + textMap.put("text", text); + content.add(textMap); + } + //添加图片参数 + if (!StrUtil.isNotBlank(image)) { + final Map imgUrlMap = new HashMap<>(); + imgUrlMap.put("type", "image_url"); + final Map urlMap = new HashMap<>(); + urlMap.put("url", image); + imgUrlMap.put("image_url", urlMap); + content.add(imgUrlMap); + } + + //添加视频参数 + if (videoParams != null && !videoParams.isEmpty()) { + //如果有文本参数就加在后面 + if (textMap != null && !textMap.isEmpty()) { + int textIndex = content.indexOf(textMap); + StringBuilder textBuilder = new StringBuilder(text); + for (DoubaoCommon.DoubaoVideo videoParam : videoParams) { + textBuilder.append(" ").append(videoParam.getType()).append(" ").append(videoParam.getValue()); + } + textMap.put("type", "text"); + textMap.put("text", textBuilder.toString()); + + if (textIndex != -1) { + content.set(textIndex, textMap); + } else { + content.add(textMap); + } + } else { + //如果没有文本参数就重新增加 + StringBuilder textBuilder = new StringBuilder(); + for (DoubaoCommon.DoubaoVideo videoParam : videoParams) { + textBuilder.append(videoParam.getType()).append(videoParam.getValue()).append(" "); + } + textMap.put("type", "text"); + textMap.put("text", textBuilder.toString()); + content.add(textMap); + } + } + + paramMap.put("content", content); + //合并其他参数 + paramMap.putAll(config.getAdditionalConfigMap()); + + return JSONUtil.toJsonStr(paramMap); + } + +} diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/doubao/package-info.java b/hutool-ai/src/main/java/cn/hutool/ai/model/doubao/package-info.java new file mode 100644 index 000000000..dd1e84371 --- /dev/null +++ b/hutool-ai/src/main/java/cn/hutool/ai/model/doubao/package-info.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * 对doubao的封装实现 + * + * @author elichow + * @since 5.8.38 + */ + +package cn.hutool.ai.model.doubao; diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/grok/GrokCommon.java b/hutool-ai/src/main/java/cn/hutool/ai/model/grok/GrokCommon.java new file mode 100644 index 000000000..2044e58e9 --- /dev/null +++ b/hutool-ai/src/main/java/cn/hutool/ai/model/grok/GrokCommon.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.hutool.ai.model.grok; + +/** + * grok公共类 + * + * @author elichow + * @since 5.8.38 + */ +public class GrokCommon { + + //grok视觉参数 + public enum GrokVision { + + AUTO("auto"), + LOW("low"), + HIGH("high"); + + private final String detail; + + GrokVision(String detail) { + this.detail = detail; + } + + public String getDetail() { + return detail; + } + } +} diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/grok/GrokConfig.java b/hutool-ai/src/main/java/cn/hutool/ai/model/grok/GrokConfig.java new file mode 100644 index 000000000..7a4727b95 --- /dev/null +++ b/hutool-ai/src/main/java/cn/hutool/ai/model/grok/GrokConfig.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.hutool.ai.model.grok; + +import cn.hutool.ai.Models; +import cn.hutool.ai.core.BaseConfig; + +/** + * Grok配置类,初始化API接口地址,设置默认的模型 + * + * @author elichow + * @since 5.8.38 + */ +public class GrokConfig extends BaseConfig { + + private final String API_URL = "https://api.x.ai/v1"; + + private final String DEFAULT_MODEL = Models.Grok.GROK_2_1212.getModel(); + + + public GrokConfig() { + setApiUrl(API_URL); + setModel(DEFAULT_MODEL); + } + + public GrokConfig(String apiKey) { + this(); + setApiKey(apiKey); + } + + @Override + public String getModelName() { + return "grok"; + } + +} diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/grok/GrokProvider.java b/hutool-ai/src/main/java/cn/hutool/ai/model/grok/GrokProvider.java new file mode 100644 index 000000000..af62fd219 --- /dev/null +++ b/hutool-ai/src/main/java/cn/hutool/ai/model/grok/GrokProvider.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.hutool.ai.model.grok; + +import cn.hutool.ai.core.AIConfig; +import cn.hutool.ai.core.AIServiceProvider; + +/**r + * 创建Grok服务实现类 + * + * @author elichow + * @since 5.8.38 + */ +public class GrokProvider implements AIServiceProvider { + + @Override + public String getServiceName() { + return "grok"; + } + + @Override + public GrokService create(final AIConfig config) { + return new GrokServiceImpl(config); + } +} diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/grok/GrokService.java b/hutool-ai/src/main/java/cn/hutool/ai/model/grok/GrokService.java new file mode 100644 index 000000000..391c07a41 --- /dev/null +++ b/hutool-ai/src/main/java/cn/hutool/ai/model/grok/GrokService.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.hutool.ai.model.grok; + +import cn.hutool.ai.core.AIService; + +import java.util.List; + +/** + * grok支持的扩展接口 + * + * @author elichow + * @since 5.8.38 + */ +public interface GrokService extends AIService { + + /** + * 创建消息回复 + * + * @param prompt 题词 + * @param maxToken 最大token + * @return AI回答 + * @since 5.8.38 + */ + String message(String prompt, int maxToken); + + /** + * 图像理解:模型会依据传入的图片信息以及问题,给出回复。 + * + * @param prompt 题词 + * @param images 图片列表/或者图片Base64编码图片列表(URI形式) + * @param detail 手动设置图片的质量,取值范围high、low、auto,默认为auto + * @return AI回答 + * @since 5.8.38 + */ + String chatVision(String prompt, final List images, String detail); + + /** + * 图像理解:模型会依据传入的图片信息以及问题,给出回复。 + * + * @param prompt 题词 + * @param images 传入的图片列表地址/或者图片Base64编码图片列表(URI形式) + * @return AI回答 + * @since 5.8.38 + */ + default String chatVision(String prompt, final List images) { + return chatVision(prompt, images, GrokCommon.GrokVision.AUTO.getDetail()); + } + + /** + * 列出所有model列表 + * + * @return model列表 + * @since 5.8.38 + */ + String models(); + + /** + * 获取模型信息 + * + * @param modelId model ID + * @return model信息 + * @since 5.8.38 + */ + String getModel(String modelId); + + /** + * 列出所有语言model + * + * @return languageModel列表 + * @since 5.8.38 + */ + String languageModels(); + + /** + * 获取语言模型信息 + * + * @param modelId model ID + * @return model信息 + * @since 5.8.38 + */ + String getLanguageModel(String modelId); + + /** + * 分词:可以将文本转换为模型可理解的 token 信息 + * + * @param text 需要分词的内容 + * @return 分词结果 + * @since 5.8.38 + */ + String tokenizeText(String text); + + /** + * 从延迟对话中获取结果 + * + * @param requestId 延迟对话中的延迟请求ID + * @return AI回答 + * @since 5.8.38 + */ + String deferredCompletion(String requestId); +} diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/grok/GrokServiceImpl.java b/hutool-ai/src/main/java/cn/hutool/ai/model/grok/GrokServiceImpl.java new file mode 100644 index 000000000..a87debce7 --- /dev/null +++ b/hutool-ai/src/main/java/cn/hutool/ai/model/grok/GrokServiceImpl.java @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.hutool.ai.model.grok; + +import cn.hutool.ai.core.AIConfig; +import cn.hutool.ai.core.BaseAIService; +import cn.hutool.ai.core.Message; +import cn.hutool.http.HttpResponse; +import cn.hutool.json.JSONUtil; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Grok服务,AI具体功能的实现 + * + * @author elichow + * @since 5.8.38 + */ +public class GrokServiceImpl extends BaseAIService implements GrokService { + + //对话补全 + private final String CHAT_ENDPOINT = "/chat/completions"; + //创建消息回复 + private final String MESSAGES = "/messages"; + //列出模型 + private final String MODELS_ENDPOINT = "/models"; + //列出语言模型 + private final String LANGUAGE_MODELS = "/language-models"; + //分词 + private final String TOKENIZE_TEXT = "/tokenize-text"; + //获取延迟对话 + private final String DEFERRED_COMPLETION = "/chat/deferred-completion"; + + public GrokServiceImpl(final AIConfig config) { + //初始化grok客户端 + super(config); + } + + @Override + public String chat(String prompt) { + // 定义消息结构 + final List messages = new ArrayList<>(); + messages.add(new Message("system", "You are a helpful assistant")); + messages.add(new Message("user", prompt)); + return chat(messages); + } + + @Override + public String chat(final List messages) { + String paramJson = buildChatRequestBody(messages); + final HttpResponse response = sendPost(CHAT_ENDPOINT, paramJson); + return response.body(); + } + + @Override + public String message(String prompt, int maxToken) { + // 定义消息结构 + final List messages = new ArrayList<>(); + messages.add(new Message("system", "You are a helpful assistant")); + messages.add(new Message("user", prompt)); + String paramJson = buildMessageRequestBody(messages, maxToken); + final HttpResponse response = sendPost(MESSAGES, paramJson); + return response.body(); + } + + @Override + public String chatVision(String prompt, final List images, String detail) { + String paramJson = buildChatVisionRequestBody(prompt, images, detail); + final HttpResponse response = sendPost(CHAT_ENDPOINT, paramJson); + return response.body(); + } + + @Override + public String models() { + final HttpResponse response = sendGet(MODELS_ENDPOINT); + return response.body(); + } + + @Override + public String getModel(String modelId) { + final HttpResponse response = sendGet(MODELS_ENDPOINT + "/" + modelId); + return response.body(); + } + + @Override + public String languageModels() { + final HttpResponse response = sendGet(LANGUAGE_MODELS); + return response.body(); + } + + @Override + public String getLanguageModel(String modelId) { + final HttpResponse response = sendGet(LANGUAGE_MODELS + "/" + modelId); + return response.body(); + } + + @Override + public String tokenizeText(String text) { + String paramJson = buildTokenizeRequestBody(text); + final HttpResponse response = sendPost(TOKENIZE_TEXT, paramJson); + return response.body(); + } + + @Override + public String deferredCompletion(String requestId) { + final HttpResponse response = sendGet(DEFERRED_COMPLETION + "/" + requestId); + return response.body(); + } + + // 构建chat请求体 + private String buildChatRequestBody(final List messages) { + //使用JSON工具 + final Map paramMap = new HashMap<>(); + paramMap.put("model", config.getModel()); + paramMap.put("messages", messages); + //合并其他参数 + paramMap.putAll(config.getAdditionalConfigMap()); + + return JSONUtil.toJsonStr(paramMap); + } + + //构建chatVision请求体 + private String buildChatVisionRequestBody(String prompt, final List images, String detail) { + // 定义消息结构 + final List messages = new ArrayList<>(); + final List content = new ArrayList<>(); + + final Map contentMap = new HashMap<>(); + contentMap.put("type", "text"); + contentMap.put("text", prompt); + content.add(contentMap); + for (String img : images) { + HashMap imgUrlMap = new HashMap<>(); + imgUrlMap.put("type", "image_url"); + HashMap 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 paramMap = new HashMap<>(); + paramMap.put("model", config.getModel()); + paramMap.put("messages", messages); + //合并其他参数 + paramMap.putAll(config.getAdditionalConfigMap()); + return JSONUtil.toJsonStr(paramMap); + } + + //构建消息回复请求体 + private String buildMessageRequestBody(final List messages, int maxToken) { + final Map paramMap = new HashMap<>(); + paramMap.put("model", config.getModel()); + paramMap.put("messages", messages); + paramMap.put("max_tokens", maxToken); + //合并其他参数 + paramMap.putAll(config.getAdditionalConfigMap()); + + return JSONUtil.toJsonStr(paramMap); + } + + //构建分词请求体 + private String buildTokenizeRequestBody(String text) { + //使用JSON工具 + final Map paramMap = new HashMap<>(); + paramMap.put("model", config.getModel()); + paramMap.put("text", text); + //合并其他参数 + paramMap.putAll(config.getAdditionalConfigMap()); + + return JSONUtil.toJsonStr(paramMap); + } +} diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/grok/package-info.java b/hutool-ai/src/main/java/cn/hutool/ai/model/grok/package-info.java new file mode 100644 index 000000000..6541d40fd --- /dev/null +++ b/hutool-ai/src/main/java/cn/hutool/ai/model/grok/package-info.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * 对grok的封装实现 + * + * @author elichow + * @since 5.8.38 + */ + +package cn.hutool.ai.model.grok; diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/openai/OpenaiCommon.java b/hutool-ai/src/main/java/cn/hutool/ai/model/openai/OpenaiCommon.java new file mode 100644 index 000000000..5cca73f28 --- /dev/null +++ b/hutool-ai/src/main/java/cn/hutool/ai/model/openai/OpenaiCommon.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.hutool.ai.model.openai; + +/** + * openai公共类 + * + * @author elichow + * @since 5.8.38 + */ +public class OpenaiCommon { + + //openai推理参数 + public enum OpenaiReasoning { + + LOW("low"), + MEDIUM("medium"), + HIGH("high"); + + private final String effort; + + OpenaiReasoning(String effort) { + this.effort = effort; + } + + public String getEffort() { + return effort; + } + } + + //openai视觉参数 + public enum OpenaiVision { + + AUTO("auto"), + LOW("low"), + HIGH("high"); + + private final String detail; + + OpenaiVision(String detail) { + this.detail = detail; + } + + public String getDetail() { + return detail; + } + } + + //openai音频参数 + public enum OpenaiSpeech { + + ALLOY("alloy"), + ASH("ash"), + CORAL("coral"), + ECHO("echo"), + FABLE("fable"), + ONYX("onyx"), + NOVA("nova"), + SAGE("sage"), + SHIMMER("shimmer"); + + private final String voice; + + OpenaiSpeech(String voice) { + this.voice = voice; + } + + public String getVoice() { + return voice; + } + } +} diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/openai/OpenaiConfig.java b/hutool-ai/src/main/java/cn/hutool/ai/model/openai/OpenaiConfig.java new file mode 100644 index 000000000..0d3eaeb6b --- /dev/null +++ b/hutool-ai/src/main/java/cn/hutool/ai/model/openai/OpenaiConfig.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.hutool.ai.model.openai; + +import cn.hutool.ai.Models; +import cn.hutool.ai.core.BaseConfig; + +/** + * openai配置类,初始化API接口地址,设置默认的模型 + * + * @author elichow + * @since 5.8.38 + */ +public class OpenaiConfig extends BaseConfig { + + private final String API_URL = "https://api.openai.com/v1"; + + private final String DEFAULT_MODEL = Models.Openai.GPT_4O.getModel(); + + public OpenaiConfig() { + setApiUrl(API_URL); + setModel(DEFAULT_MODEL); + } + + public OpenaiConfig(String apiKey) { + this(); + setApiKey(apiKey); + } + + @Override + public String getModelName() { + return "openai"; + } + +} diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/openai/OpenaiProvider.java b/hutool-ai/src/main/java/cn/hutool/ai/model/openai/OpenaiProvider.java new file mode 100644 index 000000000..2e11350b5 --- /dev/null +++ b/hutool-ai/src/main/java/cn/hutool/ai/model/openai/OpenaiProvider.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.hutool.ai.model.openai; + +import cn.hutool.ai.core.AIConfig; +import cn.hutool.ai.core.AIServiceProvider; + +/** + * 创建Openai服务实现类 + * + * @author elichow + * @since 5.8.38 + */ +public class OpenaiProvider implements AIServiceProvider { + + @Override + public String getServiceName() { + return "openai"; + } + + @Override + public OpenaiService create(final AIConfig config) { + return new OpenaiServiceImpl(config); + } +} diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/openai/OpenaiService.java b/hutool-ai/src/main/java/cn/hutool/ai/model/openai/OpenaiService.java new file mode 100644 index 000000000..de8ff5869 --- /dev/null +++ b/hutool-ai/src/main/java/cn/hutool/ai/model/openai/OpenaiService.java @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.hutool.ai.model.openai; + +import cn.hutool.ai.core.AIService; +import cn.hutool.ai.core.Message; + +import java.io.File; +import java.io.InputStream; +import java.util.List; + +/** + * openai支持的扩展接口 + * + * @author elichow + * @since 5.8.38 + */ +public interface OpenaiService extends AIService { + + /** + * 图像理解:模型会依据传入的图片信息以及问题,给出回复。 + * + * @param prompt 题词 + * @param images 图片列表/或者图片Base64编码图片列表(URI形式) + * @param detail 手动设置图片的质量,取值范围high、low、auto,默认为auto + * @return AI回答 + * @since 5.8.38 + */ + String chatVision(String prompt, final List images, String detail); + + /** + * 图像理解:模型会依据传入的图片信息以及问题,给出回复。 + * + * @param prompt 题词 + * @param images 传入的图片列表地址/或者图片Base64编码图片列表(URI形式) + * @return AI回答 + * @since 5.8.38 + */ + default String chatVision(String prompt, final List images) { + return chatVision(prompt, images, OpenaiCommon.OpenaiVision.AUTO.getDetail()); + } + + /** + * 文生图 请设置config中model为支持图片功能的模型 DALL·E系列 + * + * @param prompt 题词 + * @return 包含生成图片的url + * @since 5.8.38 + */ + String imagesGenerations(String prompt); + + /** + * 图片编辑 该方法仅支持 DALL·E 2 model + * + * @param prompt 题词 + * @param image 需要编辑的图像必须是 PNG 格式 + * @param mask 如果提供,则是一个与编辑图像大小相同的遮罩图像应该是灰度图,白色表示需要编辑的区域,黑色表示不需要编辑的区域。 + * @return 包含生成图片的url + * @since 5.8.38 + */ + String imagesEdits(String prompt, final File image, final File mask); + + /** + * 图片编辑 该方法仅支持 DALL·E 2 model + * + * @param prompt 题词 + * @param image 需要编辑的图像必须是 PNG 格式 + * @return 包含生成图片的url + * @since 5.8.38 + */ + default String imagesEdits(String prompt, final File image) { + return imagesEdits(prompt, image, null); + } + + /** + * 图片变形 该方法仅支持 DALL·E 2 model + * + * @param image 需要变形的图像必须是 PNG 格式 + * @return 包含生成图片的url + * @since 5.8.38 + */ + String imagesVariations(final File image); + + /** + * TTS文本转语音 请设置config中model为支持TTS功能的模型 TTS系列 + * + * @param input 需要转成语音的文本 + * @param voice AI的音色 + * @return 返回的音频mp3文件流 + * @since 5.8.38 + */ + InputStream textToSpeech(String input, final OpenaiCommon.OpenaiSpeech voice); + + /** + * TTS文本转语音 请设置config中model为支持TTS功能的模型 TTS系列 + * + * @param input 需要转成语音的文本 + * @return 返回的音频mp3文件流 + * @since 5.8.38 + */ + default InputStream textToSpeech(String input) { + return textToSpeech(input, OpenaiCommon.OpenaiSpeech.ALLOY); + } + + /** + * STT音频转文本 请设置config中model为支持STT功能的模型 whisper + * + * @param file 需要转成文本的音频文件 + * @return 返回的文本内容 + * @since 5.8.38 + */ + String speechToText(final File file); + + /** + * 文本向量化 请设置config中model为支持文本向量化功能的模型 text-embedding系列 + * + * @param input 需要向量化的内容 + * @return 处理后的向量信息 + * @since 5.8.38 + */ + String embeddingText(String input); + + /** + * 检查文本或图像是否具有潜在的危害性 + * 仅支持omni-moderation-latest和text-moderation-latest模型 + * + * @param text 需要检查的文本 + * @param imgUrl 需要检查的图片地址 + * @return AI返回结果 + * @since 5.8.38 + */ + String moderations(String text, String imgUrl); + + /** + * 检查文本是否具有潜在的危害性 + * 仅支持omni-moderation-latest和text-moderation-latest模型 + * + * @param text 需要检查的文本 + * @return AI返回结果 + * @since 5.8.38 + */ + default String moderations(String text) { + return moderations(text, null); + } + + /** + * 推理chat + * 支持o3-mini和o1 + * + * @param prompt 对话题词 + * @param reasoningEffort 推理程度 + * @return AI回答 + * @since 5.8.38 + */ + String chatReasoning(String prompt, String reasoningEffort); + + /** + * 推理chat + * 支持o3-mini和o1 + * + * @param prompt 对话题词 + * @return AI回答 + * @since 5.8.38 + */ + default String chatReasoning(String prompt) { + return chatReasoning(prompt, OpenaiCommon.OpenaiReasoning.MEDIUM.getEffort()); + } + + /** + * 推理chat + * 支持o3-mini和o1 + * + * @param messages 消息列表 + * @param reasoningEffort 推理程度 + * @return AI回答 + * @since 5.8.38 + */ + String chatReasoning(final List messages, String reasoningEffort); + + /** + * 推理chat + * 支持o3-mini和o1 + * + * @param messages 消息列表 + * @return AI回答 + * @since 5.8.38 + */ + default String chatReasoning(final List messages) { + return chatReasoning(messages, OpenaiCommon.OpenaiReasoning.MEDIUM.getEffort()); + } + +} diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/openai/OpenaiServiceImpl.java b/hutool-ai/src/main/java/cn/hutool/ai/model/openai/OpenaiServiceImpl.java new file mode 100644 index 000000000..02c835b57 --- /dev/null +++ b/hutool-ai/src/main/java/cn/hutool/ai/model/openai/OpenaiServiceImpl.java @@ -0,0 +1,308 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.hutool.ai.model.openai; + +import cn.hutool.ai.core.AIConfig; +import cn.hutool.ai.core.BaseAIService; +import cn.hutool.ai.core.Message; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpResponse; +import cn.hutool.json.JSONUtil; + +import java.io.File; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * openai服务,AI具体功能的实现 + * + * @author elichow + * @since 5.8.38 + */ +public class OpenaiServiceImpl extends BaseAIService implements OpenaiService { + + //对话 + private final String CHAT_ENDPOINT = "/chat/completions"; + //文生图 + private final String IMAGES_GENERATIONS = "/images/generations"; + //图片编辑 + private final String IMAGES_EDITS = "/images/edits"; + //图片变形 + private final String IMAGES_VARIATIONS = "/images/variations"; + //文本转语音 + private final String TTS = "/audio/speech"; + //语音转文本 + private final String STT = "/audio/transcriptions"; + //文本向量化 + private final String EMBEDDINGS = "/embeddings"; + //检查文本或图片 + private final String MODERATIONS = "/moderations"; + + public OpenaiServiceImpl(final AIConfig config) { + //初始化Openai客户端 + super(config); + } + + @Override + public String chat(String prompt) { + // 定义消息结构 + final List messages = new ArrayList<>(); + messages.add(new Message("system", "You are a helpful assistant")); + messages.add(new Message("user", prompt)); + return chat(messages); + } + + @Override + public String chat(final List messages) { + String paramJson = buildChatRequestBody(messages); + final HttpResponse response = sendPost(CHAT_ENDPOINT, paramJson); + return response.body(); + } + + @Override + public String chatVision(String prompt, final List images, String detail) { + String paramJson = buildChatVisionRequestBody(prompt, images, detail); + final HttpResponse response = sendPost(CHAT_ENDPOINT, paramJson); + return response.body(); + } + + @Override + public String imagesGenerations(String prompt) { + String paramJson = buildImagesGenerationsRequestBody(prompt); + final HttpResponse response = sendPost(IMAGES_GENERATIONS, paramJson); + return response.body(); + } + + @Override + public String imagesEdits(String prompt, final File image, final File mask) { + final Map paramMap = buildImagesEditsRequestBody(prompt, image, mask); + final HttpResponse response = sendFormData(IMAGES_EDITS, paramMap); + return response.body(); + } + + @Override + public String imagesVariations(final File image) { + final Map paramMap = buildImagesVariationsRequestBody(image); + final HttpResponse response = sendFormData(IMAGES_VARIATIONS, paramMap); + return response.body(); + } + + @Override + public InputStream textToSpeech(String input, final OpenaiCommon.OpenaiSpeech voice) { + String paramJson = buildTTSRequestBody(input, voice.getVoice()); + final HttpResponse response = sendPost(TTS, paramJson); + return response.bodyStream(); + } + + @Override + public String speechToText(final File file) { + final Map paramMap = buildSTTRequestBody(file); + final HttpResponse response = sendFormData(STT, paramMap); + return response.body(); + } + + @Override + public String embeddingText(String input) { + String paramJson = buildEmbeddingTextRequestBody(input); + final HttpResponse response = sendPost(EMBEDDINGS, paramJson); + return response.body(); + } + + @Override + public String moderations(String text, String imgUrl) { + String paramJson = buileModerationsRequestBody(text, imgUrl); + final HttpResponse response = sendPost(MODERATIONS, paramJson); + return response.body(); + } + + @Override + public String chatReasoning(String prompt, String reasoningEffort) { + // 定义消息结构 + final List messages = new ArrayList<>(); + messages.add(new Message("system", "You are a helpful assistant")); + messages.add(new Message("user", prompt)); + return chat(messages); + } + + @Override + public String chatReasoning(final List messages, String reasoningEffort) { + String paramJson = buildChatReasoningRequestBody(messages, reasoningEffort); + final HttpResponse response = sendPost(CHAT_ENDPOINT, paramJson); + return response.body(); + } + + // 构建chat请求体 + private String buildChatRequestBody(final List messages) { + //使用JSON工具 + final Map paramMap = new HashMap<>(); + paramMap.put("model", config.getModel()); + paramMap.put("messages", messages); + //合并其他参数 + paramMap.putAll(config.getAdditionalConfigMap()); + + return JSONUtil.toJsonStr(paramMap); + } + + //构建chatVision请求体 + private String buildChatVisionRequestBody(String prompt, final List images, String detail) { + // 定义消息结构 + final List messages = new ArrayList<>(); + final List content = new ArrayList<>(); + + final Map contentMap = new HashMap<>(); + contentMap.put("type", "text"); + contentMap.put("text", prompt); + content.add(contentMap); + for (String img : images) { + final Map imgUrlMap = new HashMap<>(); + imgUrlMap.put("type", "image_url"); + final Map 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 paramMap = new HashMap<>(); + paramMap.put("model", config.getModel()); + paramMap.put("messages", messages); + //合并其他参数 + paramMap.putAll(config.getAdditionalConfigMap()); + return JSONUtil.toJsonStr(paramMap); + } + + //构建文生图请求体 + private String buildImagesGenerationsRequestBody(String prompt) { + final Map paramMap = new HashMap<>(); + paramMap.put("model", config.getModel()); + paramMap.put("prompt", prompt); + //合并其他参数 + paramMap.putAll(config.getAdditionalConfigMap()); + + return JSONUtil.toJsonStr(paramMap); + } + + //构建图片编辑请求体 + private Map buildImagesEditsRequestBody(String prompt, final File image, final File mask) { + final Map paramMap = new HashMap<>(); + paramMap.put("model", config.getModel()); + paramMap.put("prompt", prompt); + paramMap.put("image", image); + if (mask != null) { + paramMap.put("mask", mask); + } + //合并其他参数 + paramMap.putAll(config.getAdditionalConfigMap()); + + return paramMap; + } + + //构建图片变形请求体 + private Map buildImagesVariationsRequestBody(final File image) { + final Map paramMap = new HashMap<>(); + paramMap.put("model", config.getModel()); + paramMap.put("image", image); + //合并其他参数 + paramMap.putAll(config.getAdditionalConfigMap()); + + return paramMap; + } + + //构建TTS请求体 + private String buildTTSRequestBody(String input, String voice) { + final Map 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 buildSTTRequestBody(final File file) { + final Map paramMap = new HashMap<>(); + paramMap.put("model", config.getModel()); + paramMap.put("file", file); + //合并其他参数 + paramMap.putAll(config.getAdditionalConfigMap()); + + return paramMap; + } + + //构建文本向量化请求体 + private String buildEmbeddingTextRequestBody(String input) { + //使用JSON工具 + final Map paramMap = new HashMap<>(); + paramMap.put("model", config.getModel()); + paramMap.put("input", input); + //合并其他参数 + paramMap.putAll(config.getAdditionalConfigMap()); + return JSONUtil.toJsonStr(paramMap); + } + + //构建检查图片或文字请求体 + private String buileModerationsRequestBody(String text, String imgUrl) { + //使用JSON工具 + final Map paramMap = new HashMap<>(); + paramMap.put("model", config.getModel()); + + final List input = new ArrayList<>(); + //添加文本参数 + if (!StrUtil.isBlank(text)) { + final Map textMap = new HashMap<>(); + textMap.put("type", "text"); + textMap.put("text", text); + input.add(textMap); + } + //添加图片参数 + if (!StrUtil.isBlank(imgUrl)) { + final Map imgUrlMap = new HashMap<>(); + imgUrlMap.put("type", "image_url"); + final Map urlMap = new HashMap<>(); + urlMap.put("url", imgUrl); + imgUrlMap.put("image_url", urlMap); + input.add(imgUrlMap); + } + + paramMap.put("input", input); + //合并其他参数 + paramMap.putAll(config.getAdditionalConfigMap()); + + return JSONUtil.toJsonStr(paramMap); + } + + //构建推理请求体 + private String buildChatReasoningRequestBody(final List messages, String reasoningEffort) { + final Map paramMap = new HashMap<>(); + paramMap.put("model", config.getModel()); + paramMap.put("messages", messages); + paramMap.put("reasoning_effort", reasoningEffort); + //合并其他参数 + paramMap.putAll(config.getAdditionalConfigMap()); + + return JSONUtil.toJsonStr(paramMap); + } + +} diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/openai/package-info.java b/hutool-ai/src/main/java/cn/hutool/ai/model/openai/package-info.java new file mode 100644 index 000000000..8622b9e90 --- /dev/null +++ b/hutool-ai/src/main/java/cn/hutool/ai/model/openai/package-info.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * 对openai的封装实现 + * + * @author elichow + * @since 5.8.38 + */ + +package cn.hutool.ai.model.openai; diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/package-info.java b/hutool-ai/src/main/java/cn/hutool/ai/model/package-info.java new file mode 100644 index 000000000..bdac4d5f1 --- /dev/null +++ b/hutool-ai/src/main/java/cn/hutool/ai/model/package-info.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * 对各个AI大模型的相关封装 + * + * @author elichow + * @since 5.8.38 + */ + +package cn.hutool.ai.model; diff --git a/hutool-ai/src/main/java/cn/hutool/ai/package-info.java b/hutool-ai/src/main/java/cn/hutool/ai/package-info.java new file mode 100644 index 000000000..19f36c3ca --- /dev/null +++ b/hutool-ai/src/main/java/cn/hutool/ai/package-info.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Hutool-ai主要用于AI大模型的封装,只需要对AI模型最基本的设置,即可调用AI大模型。 + * + * @author elichow + * @since 5.8.38 + */ + +package cn.hutool.ai; diff --git a/hutool-ai/src/main/resources/META-INF/services/cn.hutool.ai.core.AIConfig b/hutool-ai/src/main/resources/META-INF/services/cn.hutool.ai.core.AIConfig new file mode 100644 index 000000000..c31ffb46d --- /dev/null +++ b/hutool-ai/src/main/resources/META-INF/services/cn.hutool.ai.core.AIConfig @@ -0,0 +1,4 @@ +cn.hutool.ai.model.deepseek.DeepSeekConfig +cn.hutool.ai.model.openai.OpenaiConfig +cn.hutool.ai.model.doubao.DoubaoConfig +cn.hutool.ai.model.grok.GrokConfig diff --git a/hutool-ai/src/main/resources/META-INF/services/cn.hutool.ai.core.AIServiceProvider b/hutool-ai/src/main/resources/META-INF/services/cn.hutool.ai.core.AIServiceProvider new file mode 100644 index 000000000..40e506c9d --- /dev/null +++ b/hutool-ai/src/main/resources/META-INF/services/cn.hutool.ai.core.AIServiceProvider @@ -0,0 +1,4 @@ +cn.hutool.ai.model.deepseek.DeepSeekProvider +cn.hutool.ai.model.openai.OpenaiProvider +cn.hutool.ai.model.doubao.DoubaoProvider +cn.hutool.ai.model.grok.GrokProvider diff --git a/hutool-ai/src/test/java/AIServiceFactoryTest.java b/hutool-ai/src/test/java/AIServiceFactoryTest.java new file mode 100644 index 000000000..8a959bd1b --- /dev/null +++ b/hutool-ai/src/test/java/AIServiceFactoryTest.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import cn.hutool.ai.AIServiceFactory; +import cn.hutool.ai.ModelName; +import cn.hutool.ai.core.AIConfigBuilder; +import cn.hutool.ai.core.AIService; +import cn.hutool.ai.model.deepseek.DeepSeekService; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class AIServiceFactoryTest { + + String key = "your key"; + + @Test + void getAIService() { + final AIService aiService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DEEPSEEK.getValue()).setApiKey(key).build()); + assertNotNull(aiService); + } + + @Test + void testGetAIService() { + final DeepSeekService deepSeekService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DEEPSEEK.getValue()).setApiKey(key).build(), DeepSeekService.class); + assertNotNull(deepSeekService); + } +} diff --git a/hutool-ai/src/test/java/AIUtilTest.java b/hutool-ai/src/test/java/AIUtilTest.java new file mode 100644 index 000000000..5a0333f15 --- /dev/null +++ b/hutool-ai/src/test/java/AIUtilTest.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import cn.hutool.ai.AIUtil; +import cn.hutool.ai.ModelName; +import cn.hutool.ai.core.AIConfigBuilder; +import cn.hutool.ai.core.AIService; +import cn.hutool.ai.core.Message; +import cn.hutool.ai.model.deepseek.DeepSeekService; +import cn.hutool.ai.model.doubao.DoubaoService; +import cn.hutool.ai.model.grok.GrokService; +import cn.hutool.ai.model.openai.OpenaiService; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class AIUtilTest { + + String key = "your key"; + + @Test + void getAIService() { + final DeepSeekService deepSeekService = AIUtil.getAIService(new AIConfigBuilder(ModelName.DEEPSEEK.getValue()).setApiKey(key).build(), DeepSeekService.class); + assertNotNull(deepSeekService); + } + + @Test + void testGetAIService() { + final AIService aiService = AIUtil.getAIService(new AIConfigBuilder(ModelName.OPENAI.getValue()).setApiKey(key).build()); + assertNotNull(aiService); + } + + @Test + void getDeepSeekService() { + final DeepSeekService deepSeekService = AIUtil.getDeepSeekService(new AIConfigBuilder(ModelName.DEEPSEEK.getValue()).setApiKey(key).build()); + assertNotNull(deepSeekService); + } + + @Test + void getDoubaoService() { + final DoubaoService doubaoService = AIUtil.getDoubaoService(new AIConfigBuilder(ModelName.DOUBAO.getValue()).setApiKey(key).build()); + assertNotNull(doubaoService); + } + + @Test + void getGrokService() { + final GrokService grokService = AIUtil.getGrokService(new AIConfigBuilder(ModelName.GROK.getValue()).setApiKey(key).build()); + assertNotNull(grokService); + } + + @Test + void getOpenAIService() { + final OpenaiService openAIService = AIUtil.getOpenAIService(new AIConfigBuilder(ModelName.OPENAI.getValue()).setApiKey(key).build()); + assertNotNull(openAIService); + } + + @Test + void chat() { + final String chat = AIUtil.chat(new AIConfigBuilder(ModelName.DEEPSEEK.getValue()).setApiKey(key).build(), "写一首赞美我的诗"); + assertNotNull(chat); + } + + @Test + void testChat() { + final List messages = new ArrayList<>(); + messages.add(new Message("system","你是财神爷,只会说“我是财神”")); + messages.add(new Message("user","你是谁啊?")); + final String chat = AIUtil.chat(new AIConfigBuilder(ModelName.DEEPSEEK.getValue()).setApiKey(key).build(), messages); + assertNotNull(chat); + } +} diff --git a/hutool-ai/src/test/java/cn/hutool/ai/model/deepseek/DeepSeekServiceTest.java b/hutool-ai/src/test/java/cn/hutool/ai/model/deepseek/DeepSeekServiceTest.java new file mode 100644 index 000000000..7dd486c08 --- /dev/null +++ b/hutool-ai/src/test/java/cn/hutool/ai/model/deepseek/DeepSeekServiceTest.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.hutool.ai.model.deepseek; + +import cn.hutool.ai.AIServiceFactory; +import cn.hutool.ai.ModelName; +import cn.hutool.ai.core.AIConfigBuilder; +import cn.hutool.ai.core.Message; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class DeepSeekServiceTest { + + String key = "your key"; + DeepSeekService deepSeekService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DEEPSEEK.getValue()).setApiKey(key).build(),DeepSeekService.class); + + @Test + @Disabled + void chat(){ + final String chat = deepSeekService.chat("写一个疯狂星期四广告词"); + assertNotNull(chat); + } + + @Test + @Disabled + void testChat(){ + final List messages = new ArrayList<>(); + messages.add(new Message("system","你是个抽象大师,会说很抽象的话,最擅长说抽象的笑话")); + messages.add(new Message("user","给我说一个笑话")); + final String chat = deepSeekService.chat(messages); + assertNotNull(chat); + } + + @Test + @Disabled + void beta() { + final String beta = deepSeekService.beta("写一个疯狂星期四广告词"); + System.out.println(beta); + } + + @Test + @Disabled + void models() { + final String models = deepSeekService.models(); + assertNotNull(models); + } + + @Test + @Disabled + void balance() { + final String balance = deepSeekService.balance(); + assertNotNull(balance); + } +} diff --git a/hutool-ai/src/test/java/cn/hutool/ai/model/doubao/DoubaoServiceTest.java b/hutool-ai/src/test/java/cn/hutool/ai/model/doubao/DoubaoServiceTest.java new file mode 100644 index 000000000..0aeb9b9b1 --- /dev/null +++ b/hutool-ai/src/test/java/cn/hutool/ai/model/doubao/DoubaoServiceTest.java @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.hutool.ai.model.doubao; + +import cn.hutool.ai.AIServiceFactory; +import cn.hutool.ai.ModelName; +import cn.hutool.ai.Models; +import cn.hutool.ai.core.AIConfigBuilder; +import cn.hutool.ai.core.Message; +import cn.hutool.core.img.ImgUtil; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.awt.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class DoubaoServiceTest { + + String key = "your key"; + DoubaoService doubaoService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DOUBAO.getValue()).setModel(Models.Doubao.DOUBAO_1_5_LITE_32K.getModel()).setApiKey(key).build(), DoubaoService.class); + + @Test + @Disabled + void chat(){ + final String chat = doubaoService.chat("写一个疯狂星期四广告词"); + assertNotNull(chat); + } + + @Test + @Disabled + void testChat(){ + final List messages = new ArrayList<>(); + messages.add(new Message("system","你是个抽象大师,会说很抽象的话,最擅长说抽象的笑话")); + messages.add(new Message("user","给我说一个笑话")); + final String chat = doubaoService.chat(messages); + assertNotNull(chat); + } + + @Test + @Disabled + void chatVision() { + final DoubaoService doubaoService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DOUBAO.getValue()) + .setApiKey(key).setModel(Models.Doubao.DOUBAO_1_5_VISION_PRO_32K.getModel()).build(), DoubaoService.class); + final String base64 = ImgUtil.toBase64DataUri(Toolkit.getDefaultToolkit().createImage("your imageUrl"), "png"); + final String chatVision = doubaoService.chatVision("图片上有些什么?", Arrays.asList(base64)); + assertNotNull(chatVision); + } + + @Test + @Disabled + void testChatVision() { + final DoubaoService doubaoService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DOUBAO.getValue()) + .setApiKey(key).setModel(Models.Doubao.DOUBAO_1_5_VISION_PRO_32K.getModel()).build(), DoubaoService.class); + final String chatVision = doubaoService.chatVision("图片上有些什么?", Arrays.asList("https://img2.baidu.com/it/u=862000265,4064861820&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=1544"),DoubaoCommon.DoubaoVision.HIGH.getDetail()); + assertNotNull(chatVision); + } + + @Test + @Disabled + void videoTasks() { + final DoubaoService doubaoService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DOUBAO.getValue()) + .setApiKey(key).setModel("your Endpoint ID").build(), DoubaoService.class); + final String videoTasks = doubaoService.videoTasks("生成一段动画视频,主角是大耳朵图图,一个活泼可爱的小男孩。视频中图图在公园里玩耍," + + "画面采用明亮温暖的卡通风格,色彩鲜艳,动作流畅。背景音乐轻快活泼,带有冒险感,音效包括鸟叫声、欢笑声和山洞回声。", "https://img2.baidu.com/it/u=862000265,4064861820&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=1544"); + assertNotNull(videoTasks);//cgt-20250306170051-6r9gk + } + + @Test + @Disabled + void getVideoTasksInfo() { + //cgt-20250306170051-6r9gk + final DoubaoService doubaoService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DOUBAO.getValue()) + .setApiKey(key).build(), DoubaoService.class); + final String videoTasksInfo = doubaoService.getVideoTasksInfo("cgt-20250306170051-6r9gk"); + assertNotNull(videoTasksInfo); + } + + @Test + @Disabled + void embeddingText() { + final DoubaoService doubaoService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DOUBAO.getValue()) + .setApiKey(key).setModel(Models.Doubao.DOUBAO_EMBEDDING_TEXT_240715.getModel()).build(), DoubaoService.class); + final String embeddingText = doubaoService.embeddingText(new String[]{"阿斯顿", "马丁"}); + assertNotNull(embeddingText); + } + + @Test + @Disabled + void embeddingVision() { + final DoubaoService doubaoService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DOUBAO.getValue()) + .setApiKey(key).setModel(Models.Doubao.DOUBAO_EMBEDDING_VISION.getModel()).build(), DoubaoService.class); + final String embeddingVision = doubaoService.embeddingVision("天空好难", "https://img2.baidu.com/it/u=862000265,4064861820&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=1544"); + assertNotNull(embeddingVision); + } + + @Test + @Disabled + void botsChat() { + final DoubaoService doubaoService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DOUBAO.getValue()) + .setApiKey(key).setModel("your bots id").build(), DoubaoService.class); + final ArrayList messages = new ArrayList<>(); + messages.add(new Message("system","你是什么都可以")); + messages.add(new Message("user","你想做些什么")); + final String botsChat = doubaoService.botsChat(messages); + assertNotNull(botsChat); + } + + @Test + @Disabled + void tokenization() { + final String tokenization = doubaoService.tokenization(new String[]{"阿斯顿", "马丁"}); + assertNotNull(tokenization); + } + + @Test + @Disabled + void batchChat() { + final DoubaoService doubaoService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DOUBAO.getValue()) + .setApiKey(key).setModel("your Endpoint ID").build(), DoubaoService.class); + final String batchChat = doubaoService.batchChat("写首歌词"); + assertNotNull(batchChat); + } + + @Test + @Disabled + void testBatchChat() { + final DoubaoService doubaoService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DOUBAO.getValue()) + .setApiKey(key).setModel("your Endpoint ID").build(), DoubaoService.class); + final List messages = new ArrayList<>(); + messages.add(new Message("system","你是个抽象大师")); + messages.add(new Message("user","写一个KFC的抽象广告")); + final String batchChat = doubaoService.batchChat(messages); + assertNotNull(batchChat); + } + + @Test + @Disabled + void createContext() { + final DoubaoService doubaoService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DOUBAO.getValue()) + .setApiKey(key).setModel("your Endpoint ID").build(), DoubaoService.class); + final List messages = new ArrayList<>(); + messages.add(new Message("system","你是个抽象大师,你真的很抽象")); + final String context = doubaoService.createContext(messages);//ctx-20250307092153-cvslm + assertNotNull(context); + } + + @Test + @Disabled + void testCreateContext() { + final DoubaoService doubaoService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DOUBAO.getValue()) + .setApiKey(key).setModel("your Endpoint ID").build(), DoubaoService.class); + final List messages = new ArrayList<>(); + messages.add(new Message("system","你是个抽象大师,你真的很抽象")); + final String context = doubaoService.createContext(messages,DoubaoCommon.DoubaoContext.COMMON_PREFIX.getMode()); + assertNotNull(context);//ctx-20250307092153-cvslm + } + + @Test + @Disabled + void chatContext() { + //ctx-20250307092153-cvslm + final DoubaoService doubaoService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DOUBAO.getValue()) + .setApiKey(key).setModel("eyour Endpoint ID").build(), DoubaoService.class); + final String chatContext = doubaoService.chatContext("你是谁?", "ctx-20250307092153-cvslm"); + assertNotNull(chatContext); + } + + @Test + @Disabled + void testChatContext() { + final DoubaoService doubaoService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DOUBAO.getValue()) + .setApiKey(key).setModel("your Endpoint ID").build(), DoubaoService.class); + final List messages = new ArrayList<>(); + messages.add(new Message("user","你怎么看待意大利面拌水泥?")); + final String chatContext = doubaoService.chatContext(messages, "ctx-20250307092153-cvslm"); + assertNotNull(chatContext); + } +} diff --git a/hutool-ai/src/test/java/cn/hutool/ai/model/grok/GrokServiceTest.java b/hutool-ai/src/test/java/cn/hutool/ai/model/grok/GrokServiceTest.java new file mode 100644 index 000000000..bd2aac911 --- /dev/null +++ b/hutool-ai/src/test/java/cn/hutool/ai/model/grok/GrokServiceTest.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.hutool.ai.model.grok; + +import cn.hutool.ai.AIServiceFactory; +import cn.hutool.ai.ModelName; +import cn.hutool.ai.Models; +import cn.hutool.ai.core.AIConfigBuilder; +import cn.hutool.ai.core.Message; +import cn.hutool.core.img.ImgUtil; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.awt.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class GrokServiceTest { + + String key = "your key"; + GrokService grokService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.GROK.getValue()).setApiKey(key).build(), GrokService.class); + + + @Test + @Disabled + void chat(){ + final String chat = grokService.chat("写一个疯狂星期四广告词"); + assertNotNull(chat); + } + + @Test + @Disabled + void testChat(){ + final List messages = new ArrayList<>(); + messages.add(new Message("system","你是个抽象大师,会说很抽象的话,最擅长说抽象的笑话")); + messages.add(new Message("user","给我说一个笑话")); + final String chat = grokService.chat(messages); + assertNotNull(chat); + } + + @Test + @Disabled + void message() { + final String message = grokService.message("给我一个KFC的广告词", 4096); + assertNotNull(message); + } + + @Test + @Disabled + void chatVision() { + final GrokService grokService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.GROK.getValue()).setModel(Models.Grok.GROK_2_VISION_1212.getModel()).setApiKey(key).build(), GrokService.class); + final String base64 = ImgUtil.toBase64DataUri(Toolkit.getDefaultToolkit().createImage("your imageUrl"), "png"); + final String chatVision = grokService.chatVision("图片上有些什么?", Arrays.asList(base64)); + assertNotNull(chatVision); + } + + @Test + @Disabled + void testChatVision() { + final GrokService grokService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.GROK.getValue()).setModel(Models.Grok.GROK_2_VISION_1212.getModel()).setApiKey(key).build(), GrokService.class); + final String chatVision = grokService.chatVision("图片上有些什么?", Arrays.asList("https://img2.baidu.com/it/u=862000265,4064861820&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=1544")); + assertNotNull(chatVision); + } + + @Test + @Disabled + void models() { + final String models = grokService.models(); + assertNotNull(models); + } + + @Test + @Disabled + void getModel() { + final String model = grokService.getModel(""); + assertNotNull(model); + } + + @Test + @Disabled + void languageModels() { + final String languageModels = grokService.languageModels(); + assertNotNull(languageModels); + } + + @Test + @Disabled + void getLanguageModel() { + final String language = grokService.getLanguageModel(""); + assertNotNull(language); + } + + @Test + @Disabled + void tokenizeText() { + final String tokenizeText = grokService.tokenizeText(key); + assertNotNull(tokenizeText); + } + + @Test + @Disabled + void deferredCompletion() { + final String deferred = grokService.deferredCompletion(key); + assertNotNull(deferred); + } +} diff --git a/hutool-ai/src/test/java/cn/hutool/ai/model/openai/OpenaiServiceTest.java b/hutool-ai/src/test/java/cn/hutool/ai/model/openai/OpenaiServiceTest.java new file mode 100644 index 000000000..9e5e8f98f --- /dev/null +++ b/hutool-ai/src/test/java/cn/hutool/ai/model/openai/OpenaiServiceTest.java @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.hutool.ai.model.openai; + +import cn.hutool.ai.AIServiceFactory; +import cn.hutool.ai.ModelName; +import cn.hutool.ai.Models; +import cn.hutool.ai.core.AIConfigBuilder; +import cn.hutool.ai.core.Message; +import cn.hutool.core.io.FileUtil; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class OpenaiServiceTest { + + String key = "your key"; + OpenaiService openaiService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.OPENAI.getValue()).setApiKey(key).build(), OpenaiService.class); + + + @Test + @Disabled + void chat(){ + final String chat = openaiService.chat("写一个疯狂星期四广告词"); + assertNotNull(chat); + } + + @Test + @Disabled + void testChat(){ + final List messages = new ArrayList<>(); + messages.add(new Message("system","你是个抽象大师,会说很抽象的话,最擅长说抽象的笑话")); + messages.add(new Message("user","给我说一个笑话")); + final String chat = openaiService.chat(messages); + assertNotNull(chat); + } + + @Test + @Disabled + void chatVision() { + final OpenaiService openaiService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.OPENAI.getValue()) + .setApiKey(key).setModel(Models.Openai.GPT_4O_MINI.getModel()).build(), OpenaiService.class); + final String chatVision = openaiService.chatVision("图片上有些什么?", Arrays.asList("https://img2.baidu.com/it/u=862000265,4064861820&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=1544","https://img2.baidu.com/it/u=1682510685,1244554634&fm=253&fmt=auto&app=138&f=JPEG?w=803&h=800")); + assertNotNull(chatVision); + } + + @Test + @Disabled + void imagesGenerations() { + final OpenaiService openaiService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.OPENAI.getValue()) + .setApiKey(key).setModel(Models.Openai.DALL_E_3.getModel()).build(), OpenaiService.class); + final String imagesGenerations = openaiService.imagesGenerations("一位年轻的宇航员站在未来感十足的太空站内,透过巨大的弧形落地窗凝望浩瀚宇宙。窗外,璀璨的星河与五彩斑斓的星云交织,远处隐约可见未知星球的轮廓,仿佛在召唤着探索的脚步。宇航服上的呼吸灯与透明显示屏上的星图交相辉映,象征着人类科技与宇宙奥秘的碰撞。画面深邃而神秘,充满对未知的渴望与无限可能的想象。"); + assertNotNull(imagesGenerations); + //https://oaidalleapiprodscus.blob.core.windows.net/private/org-l99H6T0zCZejctB2TqdYrXFB/user-LilDVU1V8cUxJYwVAGRkUwYd/img-yA9kNatHnBiUHU5lZGim1hP2.png?st=2025-03-07T01%3A04%3A18Z&se=2025-03-07T03%3A04%3A18Z&sp=r&sv=2024-08-04&sr=b&rscd=inline&rsct=image/png&skoid=d505667d-d6c1-4a0a-bac7-5c84a87759f8&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skt=2025-03-06T15%3A04%3A42Z&ske=2025-03-07T15%3A04%3A42Z&sks=b&skv=2024-08-04&sig=rjcRzC5U7Y3pEDZ4ME0CiviAPdIpoGO2rRTXw3m8rHw%3D + } + + @Test + @Disabled + void imagesEdits() { + final OpenaiService openaiService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.OPENAI.getValue()) + .setApiKey(key).setModel(Models.Openai.DALL_E_2.getModel()).build(), OpenaiService.class); + final File file = FileUtil.file("your imgUrl"); + final String imagesEdits = openaiService.imagesEdits("茂密的森林中,有一只九色鹿若隐若现",file); + assertNotNull(imagesEdits); + } + + @Test + @Disabled + void imagesVariations() { + final OpenaiService openaiService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.OPENAI.getValue()) + .setApiKey(key).setModel(Models.Openai.DALL_E_2.getModel()).build(), OpenaiService.class); + final File file = FileUtil.file("your imgUrl"); + final String imagesVariations = openaiService.imagesVariations(file); + assertNotNull(imagesVariations); + } + + @Test + @Disabled + void textToSpeech() { + final OpenaiService openaiService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.OPENAI.getValue()) + .setApiKey(key).setModel(Models.Openai.TTS_1_HD.getModel()).build(), OpenaiService.class); + final InputStream inputStream = openaiService.textToSpeech("万里山河一夜白,\n" + + "千峰尽染玉龙哀。\n" + + "长风卷起琼花碎,\n" + + "直上九霄揽月来。", OpenaiCommon.OpenaiSpeech.NOVA); + + final String filePath = "your filePath"; + final Path path = Paths.get(filePath); + try (final FileOutputStream outputStream = new FileOutputStream(filePath)) { + Files.createDirectories(path.getParent()); + final byte[] buffer = new byte[1024]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + } + } catch (final IOException e) { + throw new RuntimeException(e); + } + + } + + @Test + @Disabled + void speechToText() { + final OpenaiService openaiService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.OPENAI.getValue()) + .setApiKey(key).setModel(Models.Openai.WHISPER_1.getModel()).build(), OpenaiService.class); + final File file = FileUtil.file("your filePath"); + final String speechToText = openaiService.speechToText(file); + System.out.println(speechToText); + assertNotNull(speechToText); + } + + @Test + @Disabled + void embeddingText() { + final OpenaiService openaiService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.OPENAI.getValue()) + .setApiKey(key).setModel(Models.Openai.TEXT_EMBEDDING_3_SMALL.getModel()).build(), OpenaiService.class); + final String embeddingText = openaiService.embeddingText("萬里山河一夜白,千峰盡染玉龍哀,長風捲起瓊花碎,直上九霄闌月來"); + assertNotNull(embeddingText); + } + + @Test + @Disabled + void moderations() { + final OpenaiService openaiService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.OPENAI.getValue()) + .setApiKey(key).setModel(Models.Openai.OMNI_MODERATION_LATEST.getModel()).build(), OpenaiService.class); + final String moderations = openaiService.moderations("你要杀人", "https://img2.baidu.com/it/u=862000265,4064861820&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=1544"); + assertNotNull(moderations); + } + + @Test + @Disabled + void chatReasoning() { + final OpenaiService openaiService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.OPENAI.getValue()) + .setApiKey(key).setModel(Models.Openai.O3_MINI.getModel()).build(), OpenaiService.class); + final List messages = new ArrayList<>(); + messages.add(new Message("system","你是现代抽象家")); + messages.add(new Message("user","给我一个KFC疯狂星期四的文案")); + final String chatReasoning = openaiService.chatReasoning(messages, OpenaiCommon.OpenaiReasoning.HIGH.getEffort()); + assertNotNull(chatReasoning); + } +} diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml index 5102ccf63..9e1471012 100755 --- a/hutool-all/pom.xml +++ b/hutool-all/pom.xml @@ -113,6 +113,11 @@ hutool-jwt ${project.parent.version} + + cn.hutool + hutool-ai + ${project.parent.version} + diff --git a/pom.xml b/pom.xml index 1375ea854..f4d6470f0 100755 --- a/pom.xml +++ b/pom.xml @@ -35,6 +35,7 @@ hutool-captcha hutool-socket hutool-jwt + hutool-ai From 13d0edc957e7128449902df818a55a24c2714187 Mon Sep 17 00:00:00 2001 From: choweli <1030848819@qq.com> Date: Tue, 13 May 2025 10:38:13 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E5=A2=9E=E5=8A=A0hutool-ai=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=EF=BC=8C=E5=AF=B9AI=E5=A4=A7=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E7=9A=84=E5=B0=81=E8=A3=85=E5=AE=9E=E7=8E=B0=EF=BC=88pr#3937@G?= =?UTF-8?q?ithub=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a55096d06..5fd1ce129 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ # 🚀Changelog ------------------------------------------------------------------------------------------------------------- -# 5.8.38(2025-05-12) +# 5.8.38(2025-05-13) ### 🐣新特性 * 【core 】 `PathUtil#del`增加null检查(pr#1331@Gitee) @@ -16,6 +16,7 @@ * 【core 】 优化`PropDesc`缓存注解判断,提升性能(pr#1335@Gitee) * 【core 】 添加`RecordUtil`支持record类(issue#3931@Github) * 【core 】 `Dict`的customKey方法访问权限修改为protected(pr#1340@Gitee) +* 【ai 】 增加hutool-ai模块,对AI大模型的封装实现(pr#3937@Github) ### 🐞Bug修复 * 【setting】 修复`Setting`autoLoad可能的加载为空的问题(issue#3919@Github)