From e118864530a7e3193f44f3cc6e7cac2df94189b7 Mon Sep 17 00:00:00 2001 From: choweli <1030848819@qq.com> Date: Wed, 19 Mar 2025 15:31:36 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E7=89=B9=E6=80=A7=EF=BC=9A=E6=96=B0?= =?UTF-8?q?=E5=A2=9Ehutool-ai=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hutool-ai/README.md | 17 + hutool-ai/pom.xml | 54 +++ .../org/dromara/hutool/ai/AIException.java | 35 ++ .../dromara/hutool/ai/AIServiceFactory.java | 61 ++++ .../java/org/dromara/hutool/ai/AIUtil.java | 112 ++++++ .../java/org/dromara/hutool/ai/ModelName.java | 24 ++ .../java/org/dromara/hutool/ai/Models.java | 141 ++++++++ .../org/dromara/hutool/ai/core/AIConfig.java | 97 +++++ .../hutool/ai/core/AIConfigBuilder.java | 102 ++++++ .../hutool/ai/core/AIConfigRegistry.java | 29 ++ .../org/dromara/hutool/ai/core/AIService.java | 31 ++ .../hutool/ai/core/AIServiceProvider.java | 28 ++ .../dromara/hutool/ai/core/BaseAIService.java | 72 ++++ .../dromara/hutool/ai/core/BaseConfig.java | 70 ++++ .../org/dromara/hutool/ai/core/Message.java | 27 ++ .../ai/model/deepseek/DeepSeekCommon.java | 11 + .../ai/model/deepseek/DeepSeekConfig.java | 33 ++ .../ai/model/deepseek/DeepSeekProvider.java | 24 ++ .../ai/model/deepseek/DeepSeekService.java | 37 ++ .../model/deepseek/DeepSeekServiceImpl.java | 96 +++++ .../hutool/ai/model/doubao/DoubaoCommon.java | 95 +++++ .../hutool/ai/model/doubao/DoubaoConfig.java | 33 ++ .../ai/model/doubao/DoubaoProvider.java | 23 ++ .../hutool/ai/model/doubao/DoubaoService.java | 179 +++++++++ .../ai/model/doubao/DoubaoServiceImpl.java | 341 ++++++++++++++++++ .../hutool/ai/model/grok/GrokCommon.java | 28 ++ .../hutool/ai/model/grok/GrokConfig.java | 34 ++ .../hutool/ai/model/grok/GrokProvider.java | 23 ++ .../hutool/ai/model/grok/GrokService.java | 99 +++++ .../hutool/ai/model/grok/GrokServiceImpl.java | 177 +++++++++ .../hutool/ai/model/openai/OpenaiCommon.java | 70 ++++ .../hutool/ai/model/openai/OpenaiConfig.java | 34 ++ .../ai/model/openai/OpenaiProvider.java | 24 ++ .../hutool/ai/model/openai/OpenaiService.java | 190 ++++++++++ .../ai/model/openai/OpenaiServiceImpl.java | 292 +++++++++++++++ .../org.dromara.hutool.ai.core.AIConfig | 4 + ...g.dromara.hutool.ai.core.AIServiceProvider | 4 + .../hutool/ai/AIServiceFactoryTest.java | 25 ++ .../org/dromara/hutool/ai/AIUtilTest.java | 71 ++++ .../model/deepseek/DeepSeekServiceTest.java | 49 +++ .../ai/model/doubao/DoubaoServiceTest.java | 163 +++++++++ .../hutool/ai/model/grok/GrokServiceTest.java | 95 +++++ .../ai/model/openai/OpenaiServiceTest.java | 137 +++++++ hutool-all/pom.xml | 5 + pom.xml | 1 + 45 files changed, 3297 insertions(+) create mode 100644 hutool-ai/README.md create mode 100644 hutool-ai/pom.xml create mode 100644 hutool-ai/src/main/java/org/dromara/hutool/ai/AIException.java create mode 100644 hutool-ai/src/main/java/org/dromara/hutool/ai/AIServiceFactory.java create mode 100644 hutool-ai/src/main/java/org/dromara/hutool/ai/AIUtil.java create mode 100644 hutool-ai/src/main/java/org/dromara/hutool/ai/ModelName.java create mode 100644 hutool-ai/src/main/java/org/dromara/hutool/ai/Models.java create mode 100644 hutool-ai/src/main/java/org/dromara/hutool/ai/core/AIConfig.java create mode 100644 hutool-ai/src/main/java/org/dromara/hutool/ai/core/AIConfigBuilder.java create mode 100644 hutool-ai/src/main/java/org/dromara/hutool/ai/core/AIConfigRegistry.java create mode 100644 hutool-ai/src/main/java/org/dromara/hutool/ai/core/AIService.java create mode 100644 hutool-ai/src/main/java/org/dromara/hutool/ai/core/AIServiceProvider.java create mode 100644 hutool-ai/src/main/java/org/dromara/hutool/ai/core/BaseAIService.java create mode 100644 hutool-ai/src/main/java/org/dromara/hutool/ai/core/BaseConfig.java create mode 100644 hutool-ai/src/main/java/org/dromara/hutool/ai/core/Message.java create mode 100644 hutool-ai/src/main/java/org/dromara/hutool/ai/model/deepseek/DeepSeekCommon.java create mode 100644 hutool-ai/src/main/java/org/dromara/hutool/ai/model/deepseek/DeepSeekConfig.java create mode 100644 hutool-ai/src/main/java/org/dromara/hutool/ai/model/deepseek/DeepSeekProvider.java create mode 100644 hutool-ai/src/main/java/org/dromara/hutool/ai/model/deepseek/DeepSeekService.java create mode 100644 hutool-ai/src/main/java/org/dromara/hutool/ai/model/deepseek/DeepSeekServiceImpl.java create mode 100644 hutool-ai/src/main/java/org/dromara/hutool/ai/model/doubao/DoubaoCommon.java create mode 100644 hutool-ai/src/main/java/org/dromara/hutool/ai/model/doubao/DoubaoConfig.java create mode 100644 hutool-ai/src/main/java/org/dromara/hutool/ai/model/doubao/DoubaoProvider.java create mode 100644 hutool-ai/src/main/java/org/dromara/hutool/ai/model/doubao/DoubaoService.java create mode 100644 hutool-ai/src/main/java/org/dromara/hutool/ai/model/doubao/DoubaoServiceImpl.java create mode 100644 hutool-ai/src/main/java/org/dromara/hutool/ai/model/grok/GrokCommon.java create mode 100644 hutool-ai/src/main/java/org/dromara/hutool/ai/model/grok/GrokConfig.java create mode 100644 hutool-ai/src/main/java/org/dromara/hutool/ai/model/grok/GrokProvider.java create mode 100644 hutool-ai/src/main/java/org/dromara/hutool/ai/model/grok/GrokService.java create mode 100644 hutool-ai/src/main/java/org/dromara/hutool/ai/model/grok/GrokServiceImpl.java create mode 100644 hutool-ai/src/main/java/org/dromara/hutool/ai/model/openai/OpenaiCommon.java create mode 100644 hutool-ai/src/main/java/org/dromara/hutool/ai/model/openai/OpenaiConfig.java create mode 100644 hutool-ai/src/main/java/org/dromara/hutool/ai/model/openai/OpenaiProvider.java create mode 100644 hutool-ai/src/main/java/org/dromara/hutool/ai/model/openai/OpenaiService.java create mode 100644 hutool-ai/src/main/java/org/dromara/hutool/ai/model/openai/OpenaiServiceImpl.java create mode 100644 hutool-ai/src/main/resources/META-INF/services/org.dromara.hutool.ai.core.AIConfig create mode 100644 hutool-ai/src/main/resources/META-INF/services/org.dromara.hutool.ai.core.AIServiceProvider create mode 100644 hutool-ai/src/test/java/org/dromara/hutool/ai/AIServiceFactoryTest.java create mode 100644 hutool-ai/src/test/java/org/dromara/hutool/ai/AIUtilTest.java create mode 100644 hutool-ai/src/test/java/org/dromara/hutool/ai/model/deepseek/DeepSeekServiceTest.java create mode 100644 hutool-ai/src/test/java/org/dromara/hutool/ai/model/doubao/DoubaoServiceTest.java create mode 100644 hutool-ai/src/test/java/org/dromara/hutool/ai/model/grok/GrokServiceTest.java create mode 100644 hutool-ai/src/test/java/org/dromara/hutool/ai/model/openai/OpenaiServiceTest.java diff --git a/hutool-ai/README.md b/hutool-ai/README.md new file mode 100644 index 000000000..e78d12d28 --- /dev/null +++ b/hutool-ai/README.md @@ -0,0 +1,17 @@ +

+ +

+

+ 🍬Make Java Sweet Again. +

+

+ 👉 https://hutool.cn/ 👈 +

+ +## 📚Hutool-ai 模块介绍 + +`Hutool-ai`提供了AI大模型的封装。 + +------------------------------------------------------------------------------- + +## 🛠️包含内容 \ No newline at end of file diff --git a/hutool-ai/pom.xml b/hutool-ai/pom.xml new file mode 100644 index 000000000..1561e6201 --- /dev/null +++ b/hutool-ai/pom.xml @@ -0,0 +1,54 @@ + + + 4.0.0 + + jar + + + org.dromara.hutool + hutool-parent + 6.0.0-M21 + + + hutool-ai + ${project.artifactId} + Hutool AI大模型封装 + + + org.dromara.hutool.ai + + + + + org.dromara.hutool + hutool-core + ${project.parent.version} + + + org.dromara.hutool + hutool-http + ${project.parent.version} + + + org.dromara.hutool + hutool-log + ${project.parent.version} + compile + + + org.dromara.hutool + hutool-json + ${project.parent.version} + compile + + + org.dromara.hutool + hutool-swing + ${project.parent.version} + test + + + + diff --git a/hutool-ai/src/main/java/org/dromara/hutool/ai/AIException.java b/hutool-ai/src/main/java/org/dromara/hutool/ai/AIException.java new file mode 100644 index 000000000..5dc46091c --- /dev/null +++ b/hutool-ai/src/main/java/org/dromara/hutool/ai/AIException.java @@ -0,0 +1,35 @@ +package org.dromara.hutool.ai; + +import org.dromara.hutool.core.exception.ExceptionUtil; +import org.dromara.hutool.core.text.StrUtil; + +/** + * 异常处理类 + */ +public class AIException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public AIException(Throwable e) { + super(ExceptionUtil.getMessage(e), e); + } + + public AIException(String message) { + super(message); + } + + public AIException(String messageTemplate, Object... params) { + super(StrUtil.format(messageTemplate, params)); + } + + public AIException(String message, Throwable throwable) { + super(message, throwable); + } + + public AIException(String message, Throwable throwable, boolean enableSuppression, boolean writableStackTrace) { + super(message, throwable, enableSuppression, writableStackTrace); + } + + public AIException(Throwable throwable, String messageTemplate, Object... params) { + super(StrUtil.format(messageTemplate, params), throwable); + } +} diff --git a/hutool-ai/src/main/java/org/dromara/hutool/ai/AIServiceFactory.java b/hutool-ai/src/main/java/org/dromara/hutool/ai/AIServiceFactory.java new file mode 100644 index 000000000..833f9337e --- /dev/null +++ b/hutool-ai/src/main/java/org/dromara/hutool/ai/AIServiceFactory.java @@ -0,0 +1,61 @@ +package org.dromara.hutool.ai; + +import org.dromara.hutool.ai.core.AIConfig; +import org.dromara.hutool.ai.core.AIService; +import org.dromara.hutool.ai.core.AIServiceProvider; +import org.dromara.hutool.core.map.concurrent.SafeConcurrentHashMap; + +import java.util.Map; +import java.util.ServiceLoader; + +/** + * 创建AIModelService的工厂类 + * + * @author elichow + * @since 6.0.0 + */ +public class AIServiceFactory { + + private static final Map providers = new SafeConcurrentHashMap<>(); + + // 加载所有 AIModelProvider 实现类 + static { + ServiceLoader loader = ServiceLoader.load(AIServiceProvider.class); + for (AIServiceProvider provider : loader) { + providers.put(provider.getServiceName().toLowerCase(), provider); + } + } + + /** + * 获取AI服务 + * + * @param config AIConfig配置 + * @return AI服务实例 + * @since 6.0.0 + */ + public static AIService getAIService(AIConfig config) { + return getAIService(config, AIService.class); + } + + /** + * 获取AI服务 + * + * @param config AIConfig配置 + * @param clazz AI服务类 + * @return clazz对应的AI服务类实例 + * @since 6.0.0 + */ + public static T getAIService(AIConfig config, Class clazz) { + AIServiceProvider provider = providers.get(config.getModelName().toLowerCase()); + if (provider == null) { + throw new IllegalArgumentException("Unsupported model: " + config.getModelName()); + } + + 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/org/dromara/hutool/ai/AIUtil.java b/hutool-ai/src/main/java/org/dromara/hutool/ai/AIUtil.java new file mode 100644 index 000000000..73c334ca6 --- /dev/null +++ b/hutool-ai/src/main/java/org/dromara/hutool/ai/AIUtil.java @@ -0,0 +1,112 @@ +package org.dromara.hutool.ai; + +import org.dromara.hutool.ai.core.AIConfig; +import org.dromara.hutool.ai.core.AIService; +import org.dromara.hutool.ai.core.Message; +import org.dromara.hutool.ai.model.deepseek.DeepSeekService; +import org.dromara.hutool.ai.model.doubao.DoubaoService; +import org.dromara.hutool.ai.model.grok.GrokService; +import org.dromara.hutool.ai.model.openai.OpenaiService; + +import java.util.List; + +/** + * AI工具类 + * + * @author elichow + * @since 6.0.0 + */ +public class AIUtil { + + /** + * 获取AI模型服务,每个大模型提供的功能会不一样,可以调用此方法指定不同AI服务类,调用不同的功能 + * + * @param config 创建的AI服务模型的配置 + * @param clazz AI模型服务类 + * @return AIModelService的实现类实例 + * @since 6.0.0 + */ + public static T getAIService(AIConfig config, Class clazz) { + return AIServiceFactory.getAIService(config, clazz); + } + + /** + * 获取AI模型服务 + * + * @param config 创建的AI服务模型的配置 + * @return AIModelService 其中只有公共方法 + * @since 6.0.0 + */ + public static AIService getAIService(AIConfig config) { + return getAIService(config, AIService.class); + } + + /** + * 获取DeepSeek模型服务 + * + * @param config 创建的AI服务模型的配置 + * @return DeepSeekService + * @since 6.0.0 + */ + public static DeepSeekService getDeepSeekService(AIConfig config) { + return getAIService(config, DeepSeekService.class); + } + + /** + * 获取Doubao模型服务 + * + * @param config 创建的AI服务模型的配置 + * @return DoubaoService + * @since 6.0.0 + */ + public static DoubaoService getDoubaoService(AIConfig config) { + return getAIService(config, DoubaoService.class); + } + + /** + * 获取Grok模型服务 + * + * @param config 创建的AI服务模型的配置 + * @return GrokService + * @since 6.0.0 + */ + public static GrokService getGrokService(AIConfig config) { + return getAIService(config, GrokService.class); + } + + /** + * 获取Openai模型服务 + * + * @param config 创建的AI服务模型的配置 + * @return OpenAIService + * @since 6.0.0 + */ + public static OpenaiService getOpenAIService(AIConfig config) { + return getAIService(config, OpenaiService.class); + } + + /** + * AI大模型对话功能 + * + * @param config 创建的AI服务模型的配置 + * @param prompt 需要对话的内容 + * @return AI模型返回的Response响应字符串 + * @since 6.0.0 + */ + public static String chat(AIConfig config, String prompt) { + return getAIService(config).chat(prompt); + } + + /** + * AI大模型对话功能 + * + * @param config 创建的AI服务模型的配置 + * @param messages 由目前为止的对话组成的消息列表,可以设置role,content。详细参考官方文档 + * @return AI模型返回的Response响应字符串 + * @since 6.0.0 + */ + public static String chat(AIConfig config, List messages) { + return getAIService(config).chat(messages); + } + +} diff --git a/hutool-ai/src/main/java/org/dromara/hutool/ai/ModelName.java b/hutool-ai/src/main/java/org/dromara/hutool/ai/ModelName.java new file mode 100644 index 000000000..4ee9ae5b1 --- /dev/null +++ b/hutool-ai/src/main/java/org/dromara/hutool/ai/ModelName.java @@ -0,0 +1,24 @@ +package org.dromara.hutool.ai; + +/** + * 模型厂商的名称(不指具体的模型) + * + * @author elichow + * @since 6.0.0 + */ +public enum ModelName { + DEEPSEEK("deepSeek"), + OPENAI("openai"), + DOUBAO("doubao"), + GROK("grok"); + + private final String value; + + ModelName(String value) { + this.value = value; + } + + public String getValue() { + return value; + } +} diff --git a/hutool-ai/src/main/java/org/dromara/hutool/ai/Models.java b/hutool-ai/src/main/java/org/dromara/hutool/ai/Models.java new file mode 100644 index 000000000..02db63771 --- /dev/null +++ b/hutool-ai/src/main/java/org/dromara/hutool/ai/Models.java @@ -0,0 +1,141 @@ +package org.dromara.hutool.ai; + +/** + * 各模型厂商包含的model(指具体的模型) + * + * @author elichow + * @since 6.0.0 + */ +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/org/dromara/hutool/ai/core/AIConfig.java b/hutool-ai/src/main/java/org/dromara/hutool/ai/core/AIConfig.java new file mode 100644 index 000000000..0a4b2822f --- /dev/null +++ b/hutool-ai/src/main/java/org/dromara/hutool/ai/core/AIConfig.java @@ -0,0 +1,97 @@ +package org.dromara.hutool.ai.core; + +import java.util.Map; + +/** + * AI配置类 + * + * @author elichow + * @since 6.0.0 + */ +public interface AIConfig { + + /** + * 获取模型(厂商)名称 + * + * @return 模型(厂商)名称 + * @since 6.0.0 + */ + default String getModelName() { + return this.getClass().getSimpleName(); + } + + /** + * 设置apiKey + * + * @param apiKey apiKey + * @since 6.0.0 + */ + void setApiKey(String apiKey); + + /** + * 获取apiKey + * + * @return apiKey + * @since 6.0.0 + */ + String getApiKey(); + + /** + * 设置apiUrl + * + * @param apiUrl api请求地址 + * @since 6.0.0 + */ + void setApiUrl(String apiUrl); + + /** + * 获取apiUrl + * + * @return apiUrl + * @since 6.0.0 + */ + String getApiUrl(); + + /** + * 设置model + * + * @param model model + * @since 6.0.0 + */ + void setModel(String model); + + /** + * 返回model + * + * @return model + * @since 6.0.0 + */ + String getModel(); + + /** + * 设置动态参数 + * + * @param key 参数字段 + * @param value 参数值 + * @since 6.0.0 + */ + void putAdditionalConfigByKey(String key, Object value); + + /** + * 获取动态参数 + * + * @param key 参数字段 + * @return 参数值 + * @since 6.0.0 + */ + Object getAdditionalConfigByKey(String key); + + /** + * 获取动态参数列表 + * + * @return 参数列表Map + * @since 6.0.0 + */ + Map getAdditionalConfigMap(); + +} diff --git a/hutool-ai/src/main/java/org/dromara/hutool/ai/core/AIConfigBuilder.java b/hutool-ai/src/main/java/org/dromara/hutool/ai/core/AIConfigBuilder.java new file mode 100644 index 000000000..a4f19d878 --- /dev/null +++ b/hutool-ai/src/main/java/org/dromara/hutool/ai/core/AIConfigBuilder.java @@ -0,0 +1,102 @@ +package org.dromara.hutool.ai.core; + +import java.lang.reflect.Constructor; + +/** + * 用于AIConfig的创建,创建同时支持链式设置参数 + * + * @author elichow + * @since 6.0.0 + */ +public class AIConfigBuilder { + + private final AIConfig config; + + /** + * 构造 + * + * @param modelName 模型厂商的名称(注意不是指具体的模型) + */ + public AIConfigBuilder(String modelName) { + try { + // 获取配置类 + Class configClass = AIConfigRegistry.getConfigClass(modelName); + if (configClass == null) { + throw new IllegalArgumentException("Unsupported model: " + modelName); + } + + // 使用反射创建实例 + Constructor constructor = configClass.getDeclaredConstructor(); + config = constructor.newInstance(); + } catch (Exception e) { + throw new RuntimeException("Failed to create AIConfig instance", e); + } + } + + /** + * 设置apiKey + * + * @param apiKey apiKey + * @return config + * @since 6.0.0 + */ + public synchronized AIConfigBuilder setApiKey(String apiKey) { + if (apiKey != null) { + config.setApiKey(apiKey); + } + return this; + } + + /** + * 设置AI模型请求API接口的地址,不设置为默认值 + * + * @param apiUrl API接口地址 + * @return config + * @since 6.0.0 + */ + public synchronized AIConfigBuilder setApiUrl(String apiUrl) { + if (apiUrl != null) { + config.setApiUrl(apiUrl); + } + return this; + } + + /** + * 设置具体的model,不设置为默认值 + * + * @param model 具体model的名称 + * @return config + * @since 6.0.0 + */ + public synchronized AIConfigBuilder setModel(String model) { + if (model != null) { + config.setModel(model); + } + return this; + } + + /** + * 动态设置Request请求体中的属性字段,每个模型功能支持的字段请参照对应的官方文档 + * + * @param key Request中的支持的属性名 + * @param value 设置的属性值 + * @return config + * @since 6.0.0 + */ + public AIConfigBuilder putAdditionalConfig(String key, Object value) { + if (value != null) { + config.putAdditionalConfigByKey(key, value); + } + return this; + } + + /** + * 返回config实例 + * + * @return config + * @since 6.0.0 + */ + public AIConfig build() { + return config; + } +} diff --git a/hutool-ai/src/main/java/org/dromara/hutool/ai/core/AIConfigRegistry.java b/hutool-ai/src/main/java/org/dromara/hutool/ai/core/AIConfigRegistry.java new file mode 100644 index 000000000..81c1d377a --- /dev/null +++ b/hutool-ai/src/main/java/org/dromara/hutool/ai/core/AIConfigRegistry.java @@ -0,0 +1,29 @@ +package org.dromara.hutool.ai.core; + +import org.dromara.hutool.core.map.concurrent.SafeConcurrentHashMap; + +import java.util.Map; +import java.util.ServiceLoader; + +/** + * AIConfig实现类的加载器 + * + * @author elichow + * @since 6.0.0 + */ +public class AIConfigRegistry { + + private static final Map> configClasses = new SafeConcurrentHashMap<>(); + + // 加载所有 AIConfig 实现类 + static { + ServiceLoader loader = ServiceLoader.load(AIConfig.class); + for (AIConfig config : loader) { + configClasses.put(config.getModelName().toLowerCase(), config.getClass()); + } + } + + public static Class getConfigClass(String modelName) { + return configClasses.get(modelName.toLowerCase()); + } +} diff --git a/hutool-ai/src/main/java/org/dromara/hutool/ai/core/AIService.java b/hutool-ai/src/main/java/org/dromara/hutool/ai/core/AIService.java new file mode 100644 index 000000000..256cefdbf --- /dev/null +++ b/hutool-ai/src/main/java/org/dromara/hutool/ai/core/AIService.java @@ -0,0 +1,31 @@ +package org.dromara.hutool.ai.core; + +import java.util.List; + +/** + * 模型公共的API功能,特有的功能在model.xx.XXService下定义 + * + * @author elichow + * @since 6.0.0 + */ +public interface AIService { + + /** + * 对话 + * + * @param prompt user题词 + * @return AI回答 + * @since 6.0.0 + */ + String chat(String prompt); + + /** + * 对话 + * + * @param messages 由目前为止的对话组成的消息列表,可以设置role,content。详细参考官方文档 + * @return AI回答 + * @since 6.0.0 + */ + String chat(List messages); + +} diff --git a/hutool-ai/src/main/java/org/dromara/hutool/ai/core/AIServiceProvider.java b/hutool-ai/src/main/java/org/dromara/hutool/ai/core/AIServiceProvider.java new file mode 100644 index 000000000..5ff6d2f2e --- /dev/null +++ b/hutool-ai/src/main/java/org/dromara/hutool/ai/core/AIServiceProvider.java @@ -0,0 +1,28 @@ +package org.dromara.hutool.ai.core; + +/** + * 用于加载AI服务,每一个通过SPI创建的AI服务都要实现此接口 + * + * @author elichow + * @since 6.0.0 + */ +public interface AIServiceProvider { + + /** + * 获取AI服务名称 + * + * @return AI服务名称 + * @since 6.0.0 + */ + String getServiceName(); + + /** + * 创建AI服务实例 + * + * @param config AIConfig配置 + * @param AIService类型 + * @return AI服务实例 + * @since 6.0.0 + */ + T create(AIConfig config); +} diff --git a/hutool-ai/src/main/java/org/dromara/hutool/ai/core/BaseAIService.java b/hutool-ai/src/main/java/org/dromara/hutool/ai/core/BaseAIService.java new file mode 100644 index 000000000..18d457295 --- /dev/null +++ b/hutool-ai/src/main/java/org/dromara/hutool/ai/core/BaseAIService.java @@ -0,0 +1,72 @@ +package org.dromara.hutool.ai.core; + +import org.dromara.hutool.ai.AIException; +import org.dromara.hutool.http.HttpGlobalConfig; +import org.dromara.hutool.http.HttpUtil; +import org.dromara.hutool.http.client.Response; +import org.dromara.hutool.http.meta.HeaderName; +import org.dromara.hutool.http.meta.Method; + +import java.util.Map; + +/** + * 基础AIService,包含基公共参数和公共方法 + * + * @author elichow + * @since 6.0.0 + */ +public class BaseAIService { + + protected final AIConfig config; + + public BaseAIService(AIConfig config) { + this.config = config; + } + + protected Response sendGet(String endpoint) { + //链式构建请求 + try { + //设置超时 + HttpGlobalConfig.setTimeout(3000); + return HttpUtil.createRequest(config.getApiUrl() + endpoint, Method.GET) + .header(HeaderName.ACCEPT, "application/json") + .header(HeaderName.AUTHORIZATION, "Bearer " + config.getApiKey()) + .send(); + } catch (AIException e) { + throw new AIException("Failed to send GET request: " + e.getMessage(), e); + } + } + + protected Response sendPost(String endpoint, String paramJson) { + //链式构建请求 + try { + //设置超时 + HttpGlobalConfig.setTimeout(3000); + return HttpUtil.createRequest(config.getApiUrl() + endpoint, Method.POST) + .header(HeaderName.CONTENT_TYPE, "application/json") + .header(HeaderName.ACCEPT, "application/json") + .header(HeaderName.AUTHORIZATION, "Bearer " + config.getApiKey()) + .body(paramJson) + .send(); + } catch (AIException e) { + throw new AIException("Failed to send POST request:" + e.getMessage(), e); + } + + } + + protected Response sendFormData(String endpoint, Map paramMap) { + //链式构建请求 + try { + //设置超时 + HttpGlobalConfig.setTimeout(3000); + return HttpUtil.createPost(config.getApiUrl() + endpoint) + //form表单中有file对象会自动将文件编码为 multipart/form-data 格式。不需要设置 +// .header(HeaderName.CONTENT_TYPE, "multipart/form-data") + .header(HeaderName.AUTHORIZATION, "Bearer " + config.getApiKey()) + .form(paramMap) + .send(); + } catch (AIException e) { + throw new AIException("Failed to send POST request:" + e.getMessage(), e); + } + } +} diff --git a/hutool-ai/src/main/java/org/dromara/hutool/ai/core/BaseConfig.java b/hutool-ai/src/main/java/org/dromara/hutool/ai/core/BaseConfig.java new file mode 100644 index 000000000..a5ef36deb --- /dev/null +++ b/hutool-ai/src/main/java/org/dromara/hutool/ai/core/BaseConfig.java @@ -0,0 +1,70 @@ +package org.dromara.hutool.ai.core; + + +import org.dromara.hutool.core.map.concurrent.SafeConcurrentHashMap; + +import java.util.Map; + +/** + * Config基础类,定义模型配置的基本属性 + * + * @author elichow + * @since 6.0.0 + */ +public class BaseConfig implements AIConfig { + + //apiKey + protected volatile String apiKey; + //API请求地址 + protected volatile String apiUrl; + //具体模型 + protected volatile String model; + //动态扩展字段 + protected Map additionalConfig = new SafeConcurrentHashMap<>(); + + @Override + public void setApiKey(String apiKey) { + this.apiKey = apiKey; + } + + @Override + public String getApiKey() { + return apiKey; + } + + @Override + public void setApiUrl(String apiUrl) { + this.apiUrl = apiUrl; + } + + @Override + public String getApiUrl() { + return apiUrl; + } + + @Override + public void setModel(String model) { + this.model = model; + } + + @Override + public String getModel() { + return model; + } + + @Override + public void putAdditionalConfigByKey(String key, Object value) { + this.additionalConfig.put(key, value); + } + + @Override + public Object getAdditionalConfigByKey(String key) { + return additionalConfig.get(key); + } + + @Override + public Map getAdditionalConfigMap() { + return new SafeConcurrentHashMap<>(additionalConfig); + } + +} diff --git a/hutool-ai/src/main/java/org/dromara/hutool/ai/core/Message.java b/hutool-ai/src/main/java/org/dromara/hutool/ai/core/Message.java new file mode 100644 index 000000000..b5dce74a1 --- /dev/null +++ b/hutool-ai/src/main/java/org/dromara/hutool/ai/core/Message.java @@ -0,0 +1,27 @@ +package org.dromara.hutool.ai.core; + +/** + * 公共Message类 + * + * @author elichow + * @since 6.0.0 + */ +public class Message { + //角色 注意:如果设置系统消息,请放在messages列表的第一位 + private final String role; + //内容 + private final Object content; + + public Message(String role, Object content) { + this.role = role; + this.content = content; + } + + public String getRole() { + return role; + } + + public Object getContent() { + return content; + } +} diff --git a/hutool-ai/src/main/java/org/dromara/hutool/ai/model/deepseek/DeepSeekCommon.java b/hutool-ai/src/main/java/org/dromara/hutool/ai/model/deepseek/DeepSeekCommon.java new file mode 100644 index 000000000..a3aaee27e --- /dev/null +++ b/hutool-ai/src/main/java/org/dromara/hutool/ai/model/deepseek/DeepSeekCommon.java @@ -0,0 +1,11 @@ +package org.dromara.hutool.ai.model.deepseek; + +/** + * deepSeek公共类 + * + * @author elichow + * @since 6.0.0 + */ +public class DeepSeekCommon { + +} diff --git a/hutool-ai/src/main/java/org/dromara/hutool/ai/model/deepseek/DeepSeekConfig.java b/hutool-ai/src/main/java/org/dromara/hutool/ai/model/deepseek/DeepSeekConfig.java new file mode 100644 index 000000000..66a15aed1 --- /dev/null +++ b/hutool-ai/src/main/java/org/dromara/hutool/ai/model/deepseek/DeepSeekConfig.java @@ -0,0 +1,33 @@ +package org.dromara.hutool.ai.model.deepseek; + +import org.dromara.hutool.ai.Models; +import org.dromara.hutool.ai.core.BaseConfig; + +/** + * DeepSeek配置类,初始化API接口地址,设置默认的模型 + * + * @author elichow + * @since 6.0.0 + */ +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/org/dromara/hutool/ai/model/deepseek/DeepSeekProvider.java b/hutool-ai/src/main/java/org/dromara/hutool/ai/model/deepseek/DeepSeekProvider.java new file mode 100644 index 000000000..126790f4e --- /dev/null +++ b/hutool-ai/src/main/java/org/dromara/hutool/ai/model/deepseek/DeepSeekProvider.java @@ -0,0 +1,24 @@ +package org.dromara.hutool.ai.model.deepseek; + + +import org.dromara.hutool.ai.core.AIConfig; +import org.dromara.hutool.ai.core.AIServiceProvider; + +/** + * 创建DeepSeek服务实现类 + * + * @author elichow + * @since 6.0.0 + */ +public class DeepSeekProvider implements AIServiceProvider { + + @Override + public String getServiceName() { + return "deepSeek"; + } + + @Override + public DeepSeekService create(AIConfig config) { + return new DeepSeekServiceImpl(config); + } +} diff --git a/hutool-ai/src/main/java/org/dromara/hutool/ai/model/deepseek/DeepSeekService.java b/hutool-ai/src/main/java/org/dromara/hutool/ai/model/deepseek/DeepSeekService.java new file mode 100644 index 000000000..da893e266 --- /dev/null +++ b/hutool-ai/src/main/java/org/dromara/hutool/ai/model/deepseek/DeepSeekService.java @@ -0,0 +1,37 @@ +package org.dromara.hutool.ai.model.deepseek; + +import org.dromara.hutool.ai.core.AIService; + +/** + * deepSeek支持的扩展接口 + * + * @author elichow + * @since 6.0.0 + */ +public interface DeepSeekService extends AIService { + + /** + * 模型beta功能 + * + * @param prompt 题词 + * @return AI的回答 + * @since 6.0.0 + */ + String beta(String prompt); + + /** + * 列出所有模型列表 + * + * @return model列表 + * @since 6.0.0 + */ + String models(); + + /** + * 查询余额 + * + * @return 余额 + * @since 6.0.0 + */ + String balance(); +} diff --git a/hutool-ai/src/main/java/org/dromara/hutool/ai/model/deepseek/DeepSeekServiceImpl.java b/hutool-ai/src/main/java/org/dromara/hutool/ai/model/deepseek/DeepSeekServiceImpl.java new file mode 100644 index 000000000..d01ee33e3 --- /dev/null +++ b/hutool-ai/src/main/java/org/dromara/hutool/ai/model/deepseek/DeepSeekServiceImpl.java @@ -0,0 +1,96 @@ +package org.dromara.hutool.ai.model.deepseek; + +import org.dromara.hutool.ai.core.AIConfig; +import org.dromara.hutool.ai.core.BaseAIService; +import org.dromara.hutool.ai.core.Message; +import org.dromara.hutool.http.client.Response; +import org.dromara.hutool.json.JSONUtil; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * DeepSeek服务,AI具体功能的实现 + * + * @author elichow + * @since 6.0.0 + */ +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"; + + public DeepSeekServiceImpl(AIConfig config) { + //初始化DeepSeek客户端 + super(config); + } + + @Override + public String chat(String prompt) { + // 定义消息结构 + 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(List messages) { + String paramJson = buildChatRequestBody(messages); + Response response = sendPost(CHAT_ENDPOINT, paramJson); + return response.bodyStr(); + } + + @Override + public String beta(String prompt) { + String paramJson = buildBetaRequestBody(prompt); + Response response = sendPost(BETA_ENDPOINT, paramJson); + return response.bodyStr(); + } + + @Override + public String models() { + Response response = sendGet(MODELS_ENDPOINT); + return response.bodyStr(); + } + + @Override + public String balance() { + Response response = sendGet(BALANCE_ENDPOINT); + return response.bodyStr(); + } + + // 构建chat请求体 + private String buildChatRequestBody(List messages) { + //使用JSON工具 + 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(String prompt) { + // 定义消息结构 + //使用JSON工具 + 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/org/dromara/hutool/ai/model/doubao/DoubaoCommon.java b/hutool-ai/src/main/java/org/dromara/hutool/ai/model/doubao/DoubaoCommon.java new file mode 100644 index 000000000..fe471a44f --- /dev/null +++ b/hutool-ai/src/main/java/org/dromara/hutool/ai/model/doubao/DoubaoCommon.java @@ -0,0 +1,95 @@ +package org.dromara.hutool.ai.model.doubao; + +/** + * doubao公共类 + * + * @author elichow + * @since 6.0.0 + */ +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; + } + } + + //豆包视觉参数 + 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/org/dromara/hutool/ai/model/doubao/DoubaoConfig.java b/hutool-ai/src/main/java/org/dromara/hutool/ai/model/doubao/DoubaoConfig.java new file mode 100644 index 000000000..ee3786696 --- /dev/null +++ b/hutool-ai/src/main/java/org/dromara/hutool/ai/model/doubao/DoubaoConfig.java @@ -0,0 +1,33 @@ +package org.dromara.hutool.ai.model.doubao; + +import org.dromara.hutool.ai.Models; +import org.dromara.hutool.ai.core.BaseConfig; + +/** + * Doubao配置类,初始化API接口地址,设置默认的模型 + * + * @author elichow + * @since 6.0.0 + */ +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/org/dromara/hutool/ai/model/doubao/DoubaoProvider.java b/hutool-ai/src/main/java/org/dromara/hutool/ai/model/doubao/DoubaoProvider.java new file mode 100644 index 000000000..050fde6ca --- /dev/null +++ b/hutool-ai/src/main/java/org/dromara/hutool/ai/model/doubao/DoubaoProvider.java @@ -0,0 +1,23 @@ +package org.dromara.hutool.ai.model.doubao; + +import org.dromara.hutool.ai.core.AIConfig; +import org.dromara.hutool.ai.core.AIServiceProvider; + +/** + * 创建Doubap服务实现类 + * + * @author elichow + * @since 6.0.0 + */ +public class DoubaoProvider implements AIServiceProvider { + + @Override + public String getServiceName() { + return "doubao"; + } + + @Override + public DoubaoService create(AIConfig config) { + return new DoubaoServiceImpl(config); + } +} diff --git a/hutool-ai/src/main/java/org/dromara/hutool/ai/model/doubao/DoubaoService.java b/hutool-ai/src/main/java/org/dromara/hutool/ai/model/doubao/DoubaoService.java new file mode 100644 index 000000000..8f5b36959 --- /dev/null +++ b/hutool-ai/src/main/java/org/dromara/hutool/ai/model/doubao/DoubaoService.java @@ -0,0 +1,179 @@ +package org.dromara.hutool.ai.model.doubao; + +import org.dromara.hutool.ai.core.AIService; +import org.dromara.hutool.ai.core.Message; + +import java.util.List; + +/** + * doubao支持的扩展接口 + * + * @author elichow + * @since 6.0.0 + */ +public interface DoubaoService extends AIService { + + /** + * 图像理解:模型会依据传入的图片信息以及问题,给出回复。 + * + * @param prompt 提问 + * @param images 图片列表/或者图片Base64编码图片列表(URI形式) + * @param detail 手动设置图片的质量,取值范围high、low、auto,默认为auto + * @return AI回答 + * @since 6.0.0 + */ + String chatVision(String prompt, List images, String detail); + + /** + * 图像理解:模型会依据传入的图片信息以及问题,给出回复。 + * + * @param prompt 提问 + * @param images 传入的图片列表地址/或者图片Base64编码图片列表(URI形式) + * @return AI回答 + * @since 6.0.0 + */ + default String chatVision(String prompt, 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 6.0.0 + */ + String videoTasks(String text, String image, List videoParams); + + /** + * 创建视频生成任务 + * 注意:调用该方法时,配置config中的model为您创建的推理接入点(Endpoint)ID。详细参考官方文档 + * + * @param text 文本提示词 + * @param image 图片/或者图片Base64编码图片(URI形式) + * @return 生成任务id + * @since 6.0.0 + */ + default String videoTasks(String text, String image) { + return videoTasks(text, image, null); + } + + /** + * 查询视频生成任务信息 + * + * @param taskId 通过创建生成视频任务返回的生成任务id + * @return 生成任务信息 + * @since 6.0.0 + */ + String getVideoTasksInfo(String taskId); + + /** + * 文本向量化 + * + * @param input 需要向量化的内容列表,支持中文、英文 + * @return 处理后的向量信息 + * @since 6.0.0 + */ + String embeddingText(String[] input); + + /** + * 图文向量化:仅支持单一文本、单张图片或文本与图片的组合输入(即一段文本 + 一张图片),暂不支持批量文本 / 图片的同时处理 + * + * @param text 需要向量化的内容 + * @param image 需要向量化的图片地址/或者图片Base64编码图片(URI形式) + * @return 处理后的向量信息 + * @since 6.0.0 + */ + String embeddingVision(String text, String image); + + /** + * 应用(Bot) config中model设置为您创建的应用ID + * + * @param messages 由对话组成的消息列表。如系统人设,背景信息等,用户自定义的信息 + * @return AI回答 + * @since 6.0.0 + */ + String botsChat(List messages); + + /** + * 分词:可以将文本转换为模型可理解的 token id,并返回文本的 tokens 数量、token id、 token 在原始文本中的偏移量等信息 + * + * @param text 需要分词的内容列表 + * @return 分词结果 + * @since 6.0.0 + */ + String tokenization(String[] text); + + /** + * 批量推理 Chat + * 注意:调用该方法时,配置config中的model为您创建的批量推理接入点(Endpoint)ID。详细参考官方文档 + * 该方法不支持流式 + * + * @param prompt chat内容 + * @return AI回答 + * @since 6.0.0 + */ + String batchChat(String prompt); + + /** + * 批量推理 Chat + * 注意:调用该方法时,配置config中的model为您创建的批量推理接入点(Endpoint)ID。详细参考官方文档 + * 该方法不支持流式 + * + * @param messages 由对话组成的消息列表。如系统人设,背景信息等,用户自定义的信息 + * @return AI回答 + * @since 6.0.0 + */ + String batchChat(List messages); + + /** + * 创建上下文缓存: 创建上下文缓存,获得缓存 id字段后,在上下文缓存对话 API中使用。 + * 注意:调用该方法时,配置config中的model为您创建的推理接入点(Endpoint)ID, + * 推理接入点中使用的模型需要在模型管理中开启缓存功能。详细参考官方文档 + * + * @param messages 由对话组成的消息列表。如系统人设,背景信息等,用户自定义的信息 + * @param mode 上下文缓存的类型,详细参考官方文档 默认为session + * @return 返回的缓存id + * @since 6.0.0 + */ + String createContext(List messages, String mode); + + /** + * 创建上下文缓存: 创建上下文缓存,获得缓存 id字段后,在上下文缓存对话 API中使用。 + * 注意:调用该方法时,配置config中的model为您创建的推理接入点(Endpoint)ID, + * 推理接入点中使用的模型需要在模型管理中开启缓存功能。详细参考官方文档 + * + * @param messages 由对话组成的消息列表。如系统人设,背景信息等,用户自定义的信息 + * @return 返回的缓存id + * @since 6.0.0 + */ + default String createContext(List messages) { + return createContext(messages, DoubaoCommon.DoubaoContext.SESSION.getMode()); + } + + /** + * 上下文缓存对话: 向大模型发起带上下文缓存的请求 + * 注意:配置config中的model可以为您创建的推理接入点(Endpoint)ID,也可以是支持chat的model + * + * @param prompt 对话的内容题词 + * @param contextId 创建上下文缓存后获取的缓存id + * @return AI的回答 + * @since 6.0.0 + */ + 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 6.0.0 + */ + String chatContext(List messages, String contextId); + +} diff --git a/hutool-ai/src/main/java/org/dromara/hutool/ai/model/doubao/DoubaoServiceImpl.java b/hutool-ai/src/main/java/org/dromara/hutool/ai/model/doubao/DoubaoServiceImpl.java new file mode 100644 index 000000000..5a897becb --- /dev/null +++ b/hutool-ai/src/main/java/org/dromara/hutool/ai/model/doubao/DoubaoServiceImpl.java @@ -0,0 +1,341 @@ +package org.dromara.hutool.ai.model.doubao; + +import org.dromara.hutool.ai.core.AIConfig; +import org.dromara.hutool.ai.core.BaseAIService; +import org.dromara.hutool.ai.core.Message; +import org.dromara.hutool.core.text.StrUtil; +import org.dromara.hutool.http.client.Response; +import org.dromara.hutool.json.JSONUtil; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Doubao服务,AI具体功能的实现 + * + * @author elichow + * @since 6.0.0 + */ +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(AIConfig config) { + //初始化doubao客户端 + super(config); + } + + @Override + public String chat(String prompt) { + // 定义消息结构 + 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(List messages) { + String paramJson = buildChatRequestBody(messages); + Response response = sendPost(CHAT_ENDPOINT, paramJson); + return response.bodyStr(); + } + + @Override + public String chatVision(String prompt, List images, String detail) { + String paramJson = buildChatVisionRequestBody(prompt, images, detail); + Response response = sendPost(CHAT_ENDPOINT, paramJson); + return response.bodyStr(); + } + + @Override + public String videoTasks(String text, String image, List videoParams) { + String paramJson = buildGenerationsTasksRequestBody(text, image, videoParams); + Response response = sendPost(CREATE_VIDEO, paramJson); + return response.bodyStr(); + } + + @Override + public String getVideoTasksInfo(String taskId) { + Response response = sendGet(CREATE_VIDEO + "/" + taskId); + return response.bodyStr(); + } + + + @Override + public String embeddingText(String[] input) { + String paramJson = buildEmbeddingTextRequestBody(input); + Response response = sendPost(EMBEDDING_TEXT, paramJson); + return response.bodyStr(); + } + + @Override + public String embeddingVision(String text, String image) { + String paramJson = buildEmbeddingVisionRequestBody(text, image); + Response response = sendPost(EMBEDDING_VISION, paramJson); + return response.bodyStr(); + } + + @Override + public String botsChat(List messages) { + String paramJson = buildBotsChatRequestBody(messages); + System.out.println(paramJson); + Response response = sendPost(BOTS_CHAT, paramJson); + return response.bodyStr(); + } + + @Override + public String tokenization(String[] text) { + String paramJson = buildTokenizationRequestBody(text); + Response response = sendPost(TOKENIZATION, paramJson); + return response.bodyStr(); + } + + @Override + public String batchChat(String prompt) { + // 定义消息结构 + 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(List messages) { + String paramJson = buildBatchChatRequestBody(messages); + System.out.println(paramJson); + Response response = sendPost(BATCH_CHAT, paramJson); + return response.bodyStr(); + } + + @Override + public String createContext(List messages, String mode) { + String paramJson = buildCreateContextRequest(messages, mode); + System.out.println(paramJson); + Response response = sendPost(CREATE_CONTEXT, paramJson); + return response.bodyStr(); + } + + @Override + public String chatContext(String prompt, String contextId) { + // 定义消息结构 + List messages = new ArrayList<>(); + messages.add(new Message("user", prompt)); + return chatContext(messages, contextId); + } + + @Override + public String chatContext(List messages, String contextId) { + String paramJson = buildChatContentRequestBody(messages, contextId); + Response response = sendPost(CHAT_CONTEXT, paramJson); + return response.bodyStr(); + } + + // 构建chat请求体 + private String buildChatRequestBody(List messages) { + //使用JSON工具 + 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, List images, String detail) { + // 定义消息结构 + List messages = new ArrayList<>(); + List content = new ArrayList<>(); + + 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工具 + 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工具 + 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工具 + Map paramMap = new HashMap<>(); + paramMap.put("model", config.getModel()); + + List input = new ArrayList<>(); + //添加文本参数 + if (!StrUtil.isBlank(text)) { + Map textMap = new HashMap<>(); + textMap.put("type", "text"); + textMap.put("text", text); + input.add(textMap); + } + //添加图片参数 + if (!StrUtil.isBlank(image)) { + HashMap imgUrlMap = new HashMap<>(); + imgUrlMap.put("type", "image_url"); + HashMap 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(List messages) { + return buildChatRequestBody(messages); + } + + //构建分词请求体 + private String buildTokenizationRequestBody(String[] text) { + Map paramMap = new HashMap<>(); + paramMap.put("model", config.getModel()); + paramMap.put("text", text); + return JSONUtil.toJsonStr(paramMap); + } + + //构建批量推理chat请求体 + private String buildBatchChatRequestBody(List messages) { + return buildChatRequestBody(messages); + } + + //构建创建上下文缓存请求体 + private String buildCreateContextRequest(List messages, String mode) { + 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(List messages, String contextId) { + //使用JSON工具 + 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, List videoParams) { + //使用JSON工具 + Map paramMap = new HashMap<>(); + paramMap.put("model", config.getModel()); + + List content = new ArrayList<>(); + //添加文本参数 + Map textMap = new HashMap<>(); + if (!StrUtil.isBlank(text)) { + textMap.put("type", "text"); + textMap.put("text", text); + content.add(textMap); + } + //添加图片参数 + if (!StrUtil.isNotBlank(image)) { + Map imgUrlMap = new HashMap<>(); + imgUrlMap.put("type", "image_url"); + HashMap 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/org/dromara/hutool/ai/model/grok/GrokCommon.java b/hutool-ai/src/main/java/org/dromara/hutool/ai/model/grok/GrokCommon.java new file mode 100644 index 000000000..c7eb2d569 --- /dev/null +++ b/hutool-ai/src/main/java/org/dromara/hutool/ai/model/grok/GrokCommon.java @@ -0,0 +1,28 @@ +package org.dromara.hutool.ai.model.grok; + +/** + * grok公共类 + * + * @author elichow + * @since 6.0.0 + */ +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/org/dromara/hutool/ai/model/grok/GrokConfig.java b/hutool-ai/src/main/java/org/dromara/hutool/ai/model/grok/GrokConfig.java new file mode 100644 index 000000000..e0347b111 --- /dev/null +++ b/hutool-ai/src/main/java/org/dromara/hutool/ai/model/grok/GrokConfig.java @@ -0,0 +1,34 @@ +package org.dromara.hutool.ai.model.grok; + +import org.dromara.hutool.ai.Models; +import org.dromara.hutool.ai.core.BaseConfig; + +/** + * Grok配置类,初始化API接口地址,设置默认的模型 + * + * @author elichow + * @since 6.0.0 + */ +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/org/dromara/hutool/ai/model/grok/GrokProvider.java b/hutool-ai/src/main/java/org/dromara/hutool/ai/model/grok/GrokProvider.java new file mode 100644 index 000000000..fb2162360 --- /dev/null +++ b/hutool-ai/src/main/java/org/dromara/hutool/ai/model/grok/GrokProvider.java @@ -0,0 +1,23 @@ +package org.dromara.hutool.ai.model.grok; + +import org.dromara.hutool.ai.core.AIConfig; +import org.dromara.hutool.ai.core.AIServiceProvider; + +/** + * 创建Grok服务实现类 + * + * @author elichow + * @since 6.0.0 + */ +public class GrokProvider implements AIServiceProvider { + + @Override + public String getServiceName() { + return "grok"; + } + + @Override + public GrokService create(AIConfig config) { + return new GrokServiceImpl(config); + } +} diff --git a/hutool-ai/src/main/java/org/dromara/hutool/ai/model/grok/GrokService.java b/hutool-ai/src/main/java/org/dromara/hutool/ai/model/grok/GrokService.java new file mode 100644 index 000000000..275ae71f0 --- /dev/null +++ b/hutool-ai/src/main/java/org/dromara/hutool/ai/model/grok/GrokService.java @@ -0,0 +1,99 @@ +package org.dromara.hutool.ai.model.grok; + +import org.dromara.hutool.ai.core.AIService; + +import java.util.List; + +/** + * grok支持的扩展接口 + * + * @author elichow + * @since 6.0.0 + */ +public interface GrokService extends AIService { + + /** + * 创建消息回复 + * + * @param prompt 题词 + * @param maxToken 最大token + * @return AI回答 + * @since 6.0.0 + */ + String message(String prompt, int maxToken); + + /** + * 图像理解:模型会依据传入的图片信息以及问题,给出回复。 + * + * @param prompt 题词 + * @param images 图片列表/或者图片Base64编码图片列表(URI形式) + * @param detail 手动设置图片的质量,取值范围high、low、auto,默认为auto + * @return AI回答 + * @since 6.0.0 + */ + String chatVision(String prompt, List images, String detail); + + /** + * 图像理解:模型会依据传入的图片信息以及问题,给出回复。 + * + * @param prompt 题词 + * @param images 传入的图片列表地址/或者图片Base64编码图片列表(URI形式) + * @return AI回答 + * @since 6.0.0 + */ + default String chatVision(String prompt, List images) { + return chatVision(prompt, images, GrokCommon.GrokVision.AUTO.getDetail()); + } + + /** + * 列出所有model列表 + * + * @return model列表 + * @since 6.0.0 + */ + String models(); + + /** + * 获取模型信息 + * + * @param modelId model ID + * @return model信息 + * @since 6.0.0 + */ + String getModel(String modelId); + + /** + * 列出所有语言model + * + * @return languageModel列表 + * @since 6.0.0 + */ + String languageModels(); + + /** + * 获取语言模型信息 + * + * @param modelId model ID + * @return model信息 + * @since 6.0.0 + */ + String getLanguageModel(String modelId); + + /** + * 分词:可以将文本转换为模型可理解的 token 信息 + * + * @param text 需要分词的内容 + * @return 分词结果 + * @since 6.0.0 + */ + String tokenizeText(String text); + + /** + * 从延迟对话中获取结果 + * + * @param requestId 延迟对话中的延迟请求ID + * @return AI回答 + * @since 6.0.0 + */ + String deferredCompletion(String requestId); +} diff --git a/hutool-ai/src/main/java/org/dromara/hutool/ai/model/grok/GrokServiceImpl.java b/hutool-ai/src/main/java/org/dromara/hutool/ai/model/grok/GrokServiceImpl.java new file mode 100644 index 000000000..ae86f2005 --- /dev/null +++ b/hutool-ai/src/main/java/org/dromara/hutool/ai/model/grok/GrokServiceImpl.java @@ -0,0 +1,177 @@ +package org.dromara.hutool.ai.model.grok; + +import org.dromara.hutool.ai.core.AIConfig; +import org.dromara.hutool.ai.core.BaseAIService; +import org.dromara.hutool.ai.core.Message; +import org.dromara.hutool.http.client.Response; +import org.dromara.hutool.json.JSONUtil; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Grok服务,AI具体功能的实现 + * + * @author elichow + * @since 6.0.0 + */ +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(AIConfig config) { + //初始化grok客户端 + super(config); + } + + @Override + public String chat(String prompt) { + // 定义消息结构 + 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(List messages) { + String paramJson = buildChatRequestBody(messages); + Response response = sendPost(CHAT_ENDPOINT, paramJson); + return response.bodyStr(); + } + + @Override + public String message(String prompt, int maxToken) { + // 定义消息结构 + 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); + Response response = sendPost(MESSAGES, paramJson); + return response.bodyStr(); + } + + @Override + public String chatVision(String prompt, List images, String detail) { + String paramJson = buildChatVisionRequestBody(prompt, images, detail); + Response response = sendPost(CHAT_ENDPOINT, paramJson); + return response.bodyStr(); + } + + @Override + public String models() { + Response response = sendGet(MODELS_ENDPOINT); + return response.bodyStr(); + } + + @Override + public String getModel(String modelId) { + Response response = sendGet(MODELS_ENDPOINT + "/" + modelId); + return response.bodyStr(); + } + + @Override + public String languageModels() { + Response response = sendGet(LANGUAGE_MODELS); + return response.bodyStr(); + } + + @Override + public String getLanguageModel(String modelId) { + Response response = sendGet(LANGUAGE_MODELS + "/" + modelId); + return response.bodyStr(); + } + + @Override + public String tokenizeText(String text) { + String paramJson = buildTokenizeRequestBody(text); + Response response = sendPost(TOKENIZE_TEXT, paramJson); + return response.bodyStr(); + } + + @Override + public String deferredCompletion(String requestId) { + Response response = sendGet(DEFERRED_COMPLETION + "/" + requestId); + return response.bodyStr(); + } + + // 构建chat请求体 + private String buildChatRequestBody(List messages) { + //使用JSON工具 + 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, List images, String detail) { + // 定义消息结构 + List messages = new ArrayList<>(); + List content = new ArrayList<>(); + + 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工具 + Map paramMap = new HashMap<>(); + paramMap.put("model", config.getModel()); + paramMap.put("messages", messages); + //合并其他参数 + paramMap.putAll(config.getAdditionalConfigMap()); + return JSONUtil.toJsonStr(paramMap); + } + + //构建消息回复请求体 + private String buildMessageRequestBody(List messages, int maxToken) { + 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工具 + 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/org/dromara/hutool/ai/model/openai/OpenaiCommon.java b/hutool-ai/src/main/java/org/dromara/hutool/ai/model/openai/OpenaiCommon.java new file mode 100644 index 000000000..104316b34 --- /dev/null +++ b/hutool-ai/src/main/java/org/dromara/hutool/ai/model/openai/OpenaiCommon.java @@ -0,0 +1,70 @@ +package org.dromara.hutool.ai.model.openai; + +/** + * openai公共类 + * + * @author elichow + * @since 6.0.0 + */ +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/org/dromara/hutool/ai/model/openai/OpenaiConfig.java b/hutool-ai/src/main/java/org/dromara/hutool/ai/model/openai/OpenaiConfig.java new file mode 100644 index 000000000..245e36e2e --- /dev/null +++ b/hutool-ai/src/main/java/org/dromara/hutool/ai/model/openai/OpenaiConfig.java @@ -0,0 +1,34 @@ +package org.dromara.hutool.ai.model.openai; + + +import org.dromara.hutool.ai.Models; +import org.dromara.hutool.ai.core.BaseConfig; + +/** + * openai配置类,初始化API接口地址,设置默认的模型 + * + * @author elichow + * @since 6.0.0 + */ +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/org/dromara/hutool/ai/model/openai/OpenaiProvider.java b/hutool-ai/src/main/java/org/dromara/hutool/ai/model/openai/OpenaiProvider.java new file mode 100644 index 000000000..4b59a0e0a --- /dev/null +++ b/hutool-ai/src/main/java/org/dromara/hutool/ai/model/openai/OpenaiProvider.java @@ -0,0 +1,24 @@ +package org.dromara.hutool.ai.model.openai; + + +import org.dromara.hutool.ai.core.AIConfig; +import org.dromara.hutool.ai.core.AIServiceProvider; + +/** + * 创建Openai服务实现类 + * + * @author elichow + * @since 6.0.0 + */ +public class OpenaiProvider implements AIServiceProvider { + + @Override + public String getServiceName() { + return "openai"; + } + + @Override + public OpenaiService create(AIConfig config) { + return new OpenaiServiceImpl(config); + } +} diff --git a/hutool-ai/src/main/java/org/dromara/hutool/ai/model/openai/OpenaiService.java b/hutool-ai/src/main/java/org/dromara/hutool/ai/model/openai/OpenaiService.java new file mode 100644 index 000000000..6ff590a68 --- /dev/null +++ b/hutool-ai/src/main/java/org/dromara/hutool/ai/model/openai/OpenaiService.java @@ -0,0 +1,190 @@ +package org.dromara.hutool.ai.model.openai; + +import org.dromara.hutool.ai.core.AIService; +import org.dromara.hutool.ai.core.Message; + +import java.io.File; +import java.io.InputStream; +import java.util.List; + +/** + * openai支持的扩展接口 + * + * @author elichow + * @since 6.0.0 + */ +public interface OpenaiService extends AIService { + + /** + * 图像理解:模型会依据传入的图片信息以及问题,给出回复。 + * + * @param prompt 题词 + * @param images 图片列表/或者图片Base64编码图片列表(URI形式) + * @param detail 手动设置图片的质量,取值范围high、low、auto,默认为auto + * @return AI回答 + * @since 6.0.0 + */ + String chatVision(String prompt, List images, String detail); + + /** + * 图像理解:模型会依据传入的图片信息以及问题,给出回复。 + * + * @param prompt 题词 + * @param images 传入的图片列表地址/或者图片Base64编码图片列表(URI形式) + * @return AI回答 + * @since 6.0.0 + */ + default String chatVision(String prompt, List images) { + return chatVision(prompt, images, OpenaiCommon.OpenaiVision.AUTO.getDetail()); + } + + /** + * 文生图 请设置config中model为支持图片功能的模型 DALL·E系列 + * + * @param prompt 题词 + * @return 包含生成图片的url + * @since 6.0.0 + */ + String imagesGenerations(String prompt); + + /** + * 图片编辑 该方法仅支持 DALL·E 2 model + * + * @param prompt 题词 + * @param image 需要编辑的图像必须是 PNG 格式 + * @param mask 如果提供,则是一个与编辑图像大小相同的遮罩图像应该是灰度图,白色表示需要编辑的区域,黑色表示不需要编辑的区域。 + * @return 包含生成图片的url + * @since 6.0.0 + */ + String imagesEdits(String prompt, File image, File mask); + + /** + * 图片编辑 该方法仅支持 DALL·E 2 model + * + * @param prompt 题词 + * @param image 需要编辑的图像必须是 PNG 格式 + * @return 包含生成图片的url + * @since 6.0.0 + */ + default String imagesEdits(String prompt, File image) { + return imagesEdits(prompt, image, null); + } + + /** + * 图片变形 该方法仅支持 DALL·E 2 model + * + * @param image 需要变形的图像必须是 PNG 格式 + * @return 包含生成图片的url + * @since 6.0.0 + */ + String imagesVariations(File image); + + /** + * TTS文本转语音 请设置config中model为支持TTS功能的模型 TTS系列 + * + * @param input 需要转成语音的文本 + * @param voice AI的音色 + * @return 返回的音频mp3文件流 + * @since 6.0.0 + */ + InputStream textToSpeech(String input, OpenaiCommon.OpenaiSpeech voice); + + /** + * TTS文本转语音 请设置config中model为支持TTS功能的模型 TTS系列 + * + * @param input 需要转成语音的文本 + * @return 返回的音频mp3文件流 + * @since 6.0.0 + */ + default InputStream textToSpeech(String input) { + return textToSpeech(input, OpenaiCommon.OpenaiSpeech.ALLOY); + } + + /** + * STT音频转文本 请设置config中model为支持STT功能的模型 whisper + * + * @param file 需要转成文本的音频文件 + * @return 返回的文本内容 + * @since 6.0.0 + */ + String speechToText(File file); + + /** + * 文本向量化 请设置config中model为支持文本向量化功能的模型 text-embedding系列 + * + * @param input 需要向量化的内容 + * @return 处理后的向量信息 + * @since 6.0.0 + */ + String embeddingText(String input); + + /** + * 检查文本或图像是否具有潜在的危害性 + * 仅支持omni-moderation-latest和text-moderation-latest模型 + * + * @param text 需要检查的文本 + * @param imgUrl 需要检查的图片地址 + * @return AI返回结果 + * @since 6.0.0 + */ + String moderations(String text, String imgUrl); + + /** + * 检查文本是否具有潜在的危害性 + * 仅支持omni-moderation-latest和text-moderation-latest模型 + * + * @param text 需要检查的文本 + * @return AI返回结果 + * @since 6.0.0 + */ + default String moderations(String text) { + return moderations(text, null); + } + + /** + * 推理chat + * 支持o3-mini和o1 + * + * @param prompt 对话题词 + * @param reasoningEffort 推理程度 + * @return AI回答 + * @since 6.0.0 + */ + String chatReasoning(String prompt, String reasoningEffort); + + /** + * 推理chat + * 支持o3-mini和o1 + * + * @param prompt 对话题词 + * @return AI回答 + * @since 6.0.0 + */ + default String chatReasoning(String prompt) { + return chatReasoning(prompt, OpenaiCommon.OpenaiReasoning.MEDIUM.getEffort()); + } + + /** + * 推理chat + * 支持o3-mini和o1 + * + * @param messages 消息列表 + * @param reasoningEffort 推理程度 + * @return AI回答 + * @since 6.0.0 + */ + String chatReasoning(List messages, String reasoningEffort); + + /** + * 推理chat + * 支持o3-mini和o1 + * + * @param messages 消息列表 + * @return AI回答 + * @since 6.0.0 + */ + default String chatReasoning(List messages) { + return chatReasoning(messages, OpenaiCommon.OpenaiReasoning.MEDIUM.getEffort()); + } + +} diff --git a/hutool-ai/src/main/java/org/dromara/hutool/ai/model/openai/OpenaiServiceImpl.java b/hutool-ai/src/main/java/org/dromara/hutool/ai/model/openai/OpenaiServiceImpl.java new file mode 100644 index 000000000..7d41be8c7 --- /dev/null +++ b/hutool-ai/src/main/java/org/dromara/hutool/ai/model/openai/OpenaiServiceImpl.java @@ -0,0 +1,292 @@ +package org.dromara.hutool.ai.model.openai; + +import org.dromara.hutool.ai.core.AIConfig; +import org.dromara.hutool.ai.core.BaseAIService; +import org.dromara.hutool.ai.core.Message; +import org.dromara.hutool.core.text.StrUtil; +import org.dromara.hutool.http.client.Response; +import org.dromara.hutool.json.JSONUtil; + +import 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 6.0.0 + */ +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(AIConfig config) { + //初始化Openai客户端 + super(config); + } + + @Override + public String chat(String prompt) { + // 定义消息结构 + 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(List messages) { + String paramJson = buildChatRequestBody(messages); + Response response = sendPost(CHAT_ENDPOINT, paramJson); + return response.bodyStr(); + } + + @Override + public String chatVision(String prompt, List images, String detail) { + String paramJson = buildChatVisionRequestBody(prompt, images, detail); + Response response = sendPost(CHAT_ENDPOINT, paramJson); + return response.bodyStr(); + } + + @Override + public String imagesGenerations(String prompt) { + String paramJson = buildImagesGenerationsRequestBody(prompt); + Response response = sendPost(IMAGES_GENERATIONS, paramJson); + return response.bodyStr(); + } + + @Override + public String imagesEdits(String prompt, File image, File mask) { + Map paramMap = buildImagesEditsRequestBody(prompt, image, mask); + Response response = sendFormData(IMAGES_EDITS, paramMap); + return response.bodyStr(); + } + + @Override + public String imagesVariations(File image) { + Map paramMap = buildImagesVariationsRequestBody(image); + Response response = sendFormData(IMAGES_VARIATIONS, paramMap); + return response.bodyStr(); + } + + @Override + public InputStream textToSpeech(String input, OpenaiCommon.OpenaiSpeech voice) { + String paramJson = buildTTSRequestBody(input, voice.getVoice()); + Response response = sendPost(TTS, paramJson); + return response.bodyStream(); + } + + @Override + public String speechToText(File file) { + Map paramMap = buildSTTRequestBody(file); + Response response = sendFormData(STT, paramMap); + return response.bodyStr(); + } + + @Override + public String embeddingText(String input) { + String paramJson = buildEmbeddingTextRequestBody(input); + Response response = sendPost(EMBEDDINGS, paramJson); + return response.bodyStr(); + } + + @Override + public String moderations(String text, String imgUrl) { + String paramJson = buileModerationsRequestBody(text, imgUrl); + Response response = sendPost(MODERATIONS, paramJson); + return response.bodyStr(); + } + + @Override + public String chatReasoning(String prompt, String reasoningEffort) { + // 定义消息结构 + 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(List messages, String reasoningEffort) { + String paramJson = buildChatReasoningRequestBody(messages, reasoningEffort); + Response response = sendPost(CHAT_ENDPOINT, paramJson); + return response.bodyStr(); + } + + // 构建chat请求体 + private String buildChatRequestBody(List messages) { + //使用JSON工具 + 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, List images, String detail) { + // 定义消息结构 + List messages = new ArrayList<>(); + List content = new ArrayList<>(); + + 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工具 + 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) { + 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, File image, File mask) { + 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(File image) { + 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) { + 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(File file) { + 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工具 + 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工具 + Map paramMap = new HashMap<>(); + paramMap.put("model", config.getModel()); + + List input = new ArrayList<>(); + //添加文本参数 + if (!StrUtil.isBlank(text)) { + Map textMap = new HashMap<>(); + textMap.put("type", "text"); + textMap.put("text", text); + input.add(textMap); + } + //添加图片参数 + if (!StrUtil.isBlank(imgUrl)) { + HashMap imgUrlMap = new HashMap<>(); + imgUrlMap.put("type", "image_url"); + HashMap 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(List messages, String reasoningEffort) { + 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/resources/META-INF/services/org.dromara.hutool.ai.core.AIConfig b/hutool-ai/src/main/resources/META-INF/services/org.dromara.hutool.ai.core.AIConfig new file mode 100644 index 000000000..211e9ad56 --- /dev/null +++ b/hutool-ai/src/main/resources/META-INF/services/org.dromara.hutool.ai.core.AIConfig @@ -0,0 +1,4 @@ +org.dromara.hutool.ai.model.deepseek.DeepSeekConfig +org.dromara.hutool.ai.model.openai.OpenaiConfig +org.dromara.hutool.ai.model.doubao.DoubaoConfig +org.dromara.hutool.ai.model.grok.GrokConfig diff --git a/hutool-ai/src/main/resources/META-INF/services/org.dromara.hutool.ai.core.AIServiceProvider b/hutool-ai/src/main/resources/META-INF/services/org.dromara.hutool.ai.core.AIServiceProvider new file mode 100644 index 000000000..97a1bb272 --- /dev/null +++ b/hutool-ai/src/main/resources/META-INF/services/org.dromara.hutool.ai.core.AIServiceProvider @@ -0,0 +1,4 @@ +org.dromara.hutool.ai.model.deepseek.DeepSeekProvider +org.dromara.hutool.ai.model.openai.OpenaiProvider +org.dromara.hutool.ai.model.doubao.DoubaoProvider +org.dromara.hutool.ai.model.grok.GrokProvider diff --git a/hutool-ai/src/test/java/org/dromara/hutool/ai/AIServiceFactoryTest.java b/hutool-ai/src/test/java/org/dromara/hutool/ai/AIServiceFactoryTest.java new file mode 100644 index 000000000..84ff276ac --- /dev/null +++ b/hutool-ai/src/test/java/org/dromara/hutool/ai/AIServiceFactoryTest.java @@ -0,0 +1,25 @@ +package org.dromara.hutool.ai; + +import org.dromara.hutool.ai.core.AIConfigBuilder; +import org.dromara.hutool.ai.core.AIService; +import org.dromara.hutool.ai.model.deepseek.DeepSeekService; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class AIServiceFactoryTest { + + String key = "your key"; + + @Test + void getAIService() { + AIService aiService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DEEPSEEK.getValue()).setApiKey(key).build()); + assertNotNull(aiService); + } + + @Test + void testGetAIService() { + DeepSeekService deepSeekService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DEEPSEEK.getValue()).setApiKey(key).build(), DeepSeekService.class); + assertNotNull(deepSeekService); + } +} diff --git a/hutool-ai/src/test/java/org/dromara/hutool/ai/AIUtilTest.java b/hutool-ai/src/test/java/org/dromara/hutool/ai/AIUtilTest.java new file mode 100644 index 000000000..f283a1856 --- /dev/null +++ b/hutool-ai/src/test/java/org/dromara/hutool/ai/AIUtilTest.java @@ -0,0 +1,71 @@ +package org.dromara.hutool.ai; + +import org.dromara.hutool.ai.core.AIConfigBuilder; +import org.dromara.hutool.ai.core.AIService; +import org.dromara.hutool.ai.core.Message; +import org.dromara.hutool.ai.model.deepseek.DeepSeekService; +import org.dromara.hutool.ai.model.doubao.DoubaoService; +import org.dromara.hutool.ai.model.grok.GrokService; +import org.dromara.hutool.ai.model.openai.OpenaiService; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class AIUtilTest { + + String key = "your key"; + + @Test + void getAIService() { + DeepSeekService deepSeekService = AIUtil.getAIService(new AIConfigBuilder(ModelName.DEEPSEEK.getValue()).setApiKey(key).build(), DeepSeekService.class); + assertNotNull(deepSeekService); + } + + @Test + void testGetAIService() { + AIService aiService = AIUtil.getAIService(new AIConfigBuilder(ModelName.OPENAI.getValue()).setApiKey(key).build()); + assertNotNull(aiService); + } + + @Test + void getDeepSeekService() { + DeepSeekService deepSeekService = AIUtil.getDeepSeekService(new AIConfigBuilder(ModelName.DEEPSEEK.getValue()).setApiKey(key).build()); + assertNotNull(deepSeekService); + } + + @Test + void getDoubaoService() { + DoubaoService doubaoService = AIUtil.getDoubaoService(new AIConfigBuilder(ModelName.DOUBAO.getValue()).setApiKey(key).build()); + assertNotNull(doubaoService); + } + + @Test + void getGrokService() { + GrokService grokService = AIUtil.getGrokService(new AIConfigBuilder(ModelName.GROK.getValue()).setApiKey(key).build()); + assertNotNull(grokService); + } + + @Test + void getOpenAIService() { + OpenaiService openAIService = AIUtil.getOpenAIService(new AIConfigBuilder(ModelName.OPENAI.getValue()).setApiKey(key).build()); + assertNotNull(openAIService); + } + + @Test + void chat() { + String chat = AIUtil.chat(new AIConfigBuilder(ModelName.DEEPSEEK.getValue()).setApiKey(key).build(), "写一首赞美我的诗"); + assertNotNull(chat); + } + + @Test + void testChat() { + List messages = new ArrayList<>(); + messages.add(new Message("system","你是财神爷,只会说“我是财神”")); + messages.add(new Message("user","你是谁啊?")); + String chat = AIUtil.chat(new AIConfigBuilder(ModelName.DEEPSEEK.getValue()).setApiKey(key).build(), messages); + System.out.println(chat); + } +} diff --git a/hutool-ai/src/test/java/org/dromara/hutool/ai/model/deepseek/DeepSeekServiceTest.java b/hutool-ai/src/test/java/org/dromara/hutool/ai/model/deepseek/DeepSeekServiceTest.java new file mode 100644 index 000000000..c5fbe0257 --- /dev/null +++ b/hutool-ai/src/test/java/org/dromara/hutool/ai/model/deepseek/DeepSeekServiceTest.java @@ -0,0 +1,49 @@ +package org.dromara.hutool.ai.model.deepseek; + +import org.dromara.hutool.ai.AIServiceFactory; +import org.dromara.hutool.ai.ModelName; +import org.dromara.hutool.ai.core.AIConfigBuilder; +import org.dromara.hutool.ai.core.Message; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +class DeepSeekServiceTest { + + String key = "your key"; + DeepSeekService deepSeekService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DEEPSEEK.getValue()).setApiKey(key).build(),DeepSeekService.class); + + @Test + void chat(){ + String chat = deepSeekService.chat("写一个疯狂星期四广告词"); + System.out.println(chat); + } + + @Test + void testChat(){ + List messages = new ArrayList<>(); + messages.add(new Message("system","你是个抽象大师,会说很抽象的话,最擅长说抽象的笑话")); + messages.add(new Message("user","给我说一个笑话")); + String chat = deepSeekService.chat(messages); + System.out.println(chat); + } + + @Test + void beta() { + String beta = deepSeekService.beta("写一个疯狂星期四广告词"); + System.out.println(beta); + } + + @Test + void models() { + String models = deepSeekService.models(); + System.out.println(models); + } + + @Test + void balance() { + String balance = deepSeekService.balance(); + System.out.println(balance); + } +} diff --git a/hutool-ai/src/test/java/org/dromara/hutool/ai/model/doubao/DoubaoServiceTest.java b/hutool-ai/src/test/java/org/dromara/hutool/ai/model/doubao/DoubaoServiceTest.java new file mode 100644 index 000000000..9f69a2e37 --- /dev/null +++ b/hutool-ai/src/test/java/org/dromara/hutool/ai/model/doubao/DoubaoServiceTest.java @@ -0,0 +1,163 @@ +package org.dromara.hutool.ai.model.doubao; + +import org.dromara.hutool.ai.AIServiceFactory; +import org.dromara.hutool.ai.ModelName; +import org.dromara.hutool.ai.Models; +import org.dromara.hutool.ai.core.AIConfigBuilder; +import org.dromara.hutool.ai.core.Message; +import org.dromara.hutool.swing.img.ImgUtil; +import 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.*; + +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 + void chat(){ + String chat = doubaoService.chat("写一个疯狂星期四广告词"); + System.out.println(chat); + } + + @Test + void testChat(){ + List messages = new ArrayList<>(); + messages.add(new Message("system","你是个抽象大师,会说很抽象的话,最擅长说抽象的笑话")); + messages.add(new Message("user","给我说一个笑话")); + String chat = doubaoService.chat(messages); + System.out.println(chat); + } + + @Test + void chatVision() { + DoubaoService doubaoService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DOUBAO.getValue()) + .setApiKey(key).setModel(Models.Doubao.DOUBAO_1_5_VISION_PRO_32K.getModel()).build(), DoubaoService.class); + String base64 = ImgUtil.toBase64DataUri(Toolkit.getDefaultToolkit().createImage("your imageUrl"), "png"); + String chatVision = doubaoService.chatVision("图片上有些什么?", Arrays.asList(base64)); + System.out.println(chatVision); + } + + @Test + void testChatVision() { + DoubaoService doubaoService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DOUBAO.getValue()) + .setApiKey(key).setModel(Models.Doubao.DOUBAO_1_5_VISION_PRO_32K.getModel()).build(), DoubaoService.class); + String chatVision = doubaoService.chatVision("图片上有些什么?", Arrays.asList("https://img2.baidu.com/it/u=862000265,4064861820&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=1544"),DoubaoCommon.DoubaoVision.HIGH.getDetail()); + System.out.println(chatVision); + } + + @Test + void videoTasks() { + DoubaoService doubaoService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DOUBAO.getValue()) + .setApiKey(key).setModel("your Endpoint ID").build(), DoubaoService.class); + String videoTasks = doubaoService.videoTasks("生成一段动画视频,主角是大耳朵图图,一个活泼可爱的小男孩。视频中图图在公园里玩耍," + + "画面采用明亮温暖的卡通风格,色彩鲜艳,动作流畅。背景音乐轻快活泼,带有冒险感,音效包括鸟叫声、欢笑声和山洞回声。", "https://img2.baidu.com/it/u=862000265,4064861820&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=1544"); + System.out.println(videoTasks);//cgt-20250306170051-6r9gk + } + + @Test + void getVideoTasksInfo() { + //cgt-20250306170051-6r9gk + DoubaoService doubaoService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DOUBAO.getValue()) + .setApiKey(key).build(), DoubaoService.class); + String videoTasksInfo = doubaoService.getVideoTasksInfo("cgt-20250306170051-6r9gk"); + System.out.println(videoTasksInfo); + } + + @Test + void embeddingText() { + DoubaoService doubaoService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DOUBAO.getValue()) + .setApiKey(key).setModel(Models.Doubao.DOUBAO_EMBEDDING_TEXT_240715.getModel()).build(), DoubaoService.class); + String embeddingText = doubaoService.embeddingText(new String[]{"阿斯顿", "马丁"}); + System.out.println(embeddingText); + } + + @Test + void embeddingVision() { + DoubaoService doubaoService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DOUBAO.getValue()) + .setApiKey(key).setModel(Models.Doubao.DOUBAO_EMBEDDING_VISION.getModel()).build(), DoubaoService.class); + String embeddingVision = doubaoService.embeddingVision("天空好难", "https://img2.baidu.com/it/u=862000265,4064861820&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=1544"); + System.out.println(embeddingVision); + } + + @Test + void botsChat() { + DoubaoService doubaoService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DOUBAO.getValue()) + .setApiKey(key).setModel("your bots id").build(), DoubaoService.class); + ArrayList messages = new ArrayList<>(); + messages.add(new Message("system","你是什么都可以")); + messages.add(new Message("user","你想做些什么")); + String botsChat = doubaoService.botsChat(messages); + System.out.println(botsChat); + } + + @Test + void tokenization() { + String tokenization = doubaoService.tokenization(new String[]{"阿斯顿", "马丁"}); + System.out.println(tokenization); + } + + @Test + void batchChat() { + DoubaoService doubaoService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DOUBAO.getValue()) + .setApiKey(key).setModel("your Endpoint ID").build(), DoubaoService.class); + String batchChat = doubaoService.batchChat("写首歌词"); + System.out.println(batchChat); + } + + @Test + void testBatchChat() { + DoubaoService doubaoService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DOUBAO.getValue()) + .setApiKey(key).setModel("your Endpoint ID").build(), DoubaoService.class); + List messages = new ArrayList<>(); + messages.add(new Message("system","你是个抽象大师")); + messages.add(new Message("user","写一个KFC的抽象广告")); + String batchChat = doubaoService.batchChat(messages); + System.out.println(batchChat); + } + + @Test + void createContext() { + DoubaoService doubaoService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DOUBAO.getValue()) + .setApiKey(key).setModel("your Endpoint ID").build(), DoubaoService.class); + List messages = new ArrayList<>(); + messages.add(new Message("system","你是个抽象大师,你真的很抽象")); + String context = doubaoService.createContext(messages);//ctx-20250307092153-cvslm + System.out.println(context); + } + + @Test + void testCreateContext() { + DoubaoService doubaoService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DOUBAO.getValue()) + .setApiKey(key).setModel("your Endpoint ID").build(), DoubaoService.class); + List messages = new ArrayList<>(); + messages.add(new Message("system","你是个抽象大师,你真的很抽象")); + String context = doubaoService.createContext(messages,DoubaoCommon.DoubaoContext.COMMON_PREFIX.getMode()); + System.out.println(context);//ctx-20250307092153-cvslm + } + + @Test + void chatContext() { + //ctx-20250307092153-cvslm + DoubaoService doubaoService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DOUBAO.getValue()) + .setApiKey(key).setModel("eyour Endpoint ID").build(), DoubaoService.class); + String chatContext = doubaoService.chatContext("你是谁?", "ctx-20250307092153-cvslm"); + System.out.println(chatContext); + } + + @Test + void testChatContext() { + DoubaoService doubaoService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.DOUBAO.getValue()) + .setApiKey(key).setModel("your Endpoint ID").build(), DoubaoService.class); + List messages = new ArrayList<>(); + messages.add(new Message("user","你怎么看待意大利面拌水泥?")); + String chatContext = doubaoService.chatContext(messages, "ctx-20250307092153-cvslm"); + System.out.println(chatContext); + } +} diff --git a/hutool-ai/src/test/java/org/dromara/hutool/ai/model/grok/GrokServiceTest.java b/hutool-ai/src/test/java/org/dromara/hutool/ai/model/grok/GrokServiceTest.java new file mode 100644 index 000000000..1f8ff2f88 --- /dev/null +++ b/hutool-ai/src/test/java/org/dromara/hutool/ai/model/grok/GrokServiceTest.java @@ -0,0 +1,95 @@ +package org.dromara.hutool.ai.model.grok; + +import org.dromara.hutool.ai.AIServiceFactory; +import org.dromara.hutool.ai.ModelName; +import org.dromara.hutool.ai.Models; +import org.dromara.hutool.ai.core.AIConfigBuilder; +import org.dromara.hutool.ai.core.Message; +import org.dromara.hutool.swing.img.ImgUtil; +import 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.*; + +class GrokServiceTest { + + String key = "your key"; + GrokService grokService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.GROK.getValue()).setApiKey(key).build(), GrokService.class); + + + @Test + void chat(){ + String chat = grokService.chat("写一个疯狂星期四广告词"); + System.out.println(chat); + } + + @Test + void testChat(){ + List messages = new ArrayList<>(); + messages.add(new Message("system","你是个抽象大师,会说很抽象的话,最擅长说抽象的笑话")); + messages.add(new Message("user","给我说一个笑话")); + String chat = grokService.chat(messages); + System.out.println(chat); + } + + @Test + void message() { + String message = grokService.message("给我一个KFC的广告词", 4096); + System.out.println(message); + } + + @Test + void chatVision() { + GrokService grokService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.GROK.getValue()).setModel(Models.Grok.GROK_2_VISION_1212.getModel()).setApiKey(key).build(), GrokService.class); + String base64 = ImgUtil.toBase64DataUri(Toolkit.getDefaultToolkit().createImage("your imageUrl"), "png"); + String chatVision = grokService.chatVision("图片上有些什么?", Arrays.asList(base64)); + System.out.println(chatVision); + } + + @Test + void testChatVision() { + GrokService grokService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.GROK.getValue()).setModel(Models.Grok.GROK_2_VISION_1212.getModel()).setApiKey(key).build(), GrokService.class); + String chatVision = grokService.chatVision("图片上有些什么?", Arrays.asList("https://img2.baidu.com/it/u=862000265,4064861820&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=1544")); + System.out.println(chatVision); + } + + @Test + void models() { + String models = grokService.models(); + assertNotNull(models); + } + + @Test + void getModel() { + String model = grokService.getModel(""); + assertNotNull(model); + } + + @Test + void languageModels() { + String languageModels = grokService.languageModels(); + assertNotNull(languageModels); + } + + @Test + void getLanguageModel() { + String language = grokService.getLanguageModel(""); + assertNotNull(language); + } + + @Test + void tokenizeText() { + String tokenizeText = grokService.tokenizeText(key); + assertNotNull(tokenizeText); + } + + @Test + void deferredCompletion() { + String deferred = grokService.deferredCompletion(key); + assertNotNull(deferred); + } +} diff --git a/hutool-ai/src/test/java/org/dromara/hutool/ai/model/openai/OpenaiServiceTest.java b/hutool-ai/src/test/java/org/dromara/hutool/ai/model/openai/OpenaiServiceTest.java new file mode 100644 index 000000000..a9210003e --- /dev/null +++ b/hutool-ai/src/test/java/org/dromara/hutool/ai/model/openai/OpenaiServiceTest.java @@ -0,0 +1,137 @@ +package org.dromara.hutool.ai.model.openai; + +import org.dromara.hutool.ai.AIServiceFactory; +import org.dromara.hutool.ai.ModelName; +import org.dromara.hutool.ai.Models; +import org.dromara.hutool.ai.core.AIConfigBuilder; +import org.dromara.hutool.ai.core.Message; +import org.dromara.hutool.core.io.file.FileUtil; +import 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; + +class OpenaiServiceTest { + + String key = "your key"; + OpenaiService openaiService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.OPENAI.getValue()).setApiKey(key).build(), OpenaiService.class); + + + @Test + void chat(){ + String chat = openaiService.chat("写一个疯狂星期四广告词"); + System.out.println(chat); + } + + @Test + void testChat(){ + List messages = new ArrayList<>(); + messages.add(new Message("system","你是个抽象大师,会说很抽象的话,最擅长说抽象的笑话")); + messages.add(new Message("user","给我说一个笑话")); + String chat = openaiService.chat(messages); + System.out.println(chat); + } + + @Test + void chatVision() { + OpenaiService openaiService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.OPENAI.getValue()) + .setApiKey(key).setModel(Models.Openai.GPT_4O_MINI.getModel()).build(), OpenaiService.class); + String chatVision = openaiService.chatVision("图片上有些什么?", Arrays.asList("https://img2.baidu.com/it/u=862000265,4064861820&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=1544","https://img2.baidu.com/it/u=1682510685,1244554634&fm=253&fmt=auto&app=138&f=JPEG?w=803&h=800")); + System.out.println(chatVision); + } + + @Test + void imagesGenerations() { + OpenaiService openaiService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.OPENAI.getValue()) + .setApiKey(key).setModel(Models.Openai.DALL_E_3.getModel()).build(), OpenaiService.class); + String imagesGenerations = openaiService.imagesGenerations("一位年轻的宇航员站在未来感十足的太空站内,透过巨大的弧形落地窗凝望浩瀚宇宙。窗外,璀璨的星河与五彩斑斓的星云交织,远处隐约可见未知星球的轮廓,仿佛在召唤着探索的脚步。宇航服上的呼吸灯与透明显示屏上的星图交相辉映,象征着人类科技与宇宙奥秘的碰撞。画面深邃而神秘,充满对未知的渴望与无限可能的想象。"); + System.out.println(imagesGenerations); + //https://oaidalleapiprodscus.blob.core.windows.net/private/org-l99H6T0zCZejctB2TqdYrXFB/user-LilDVU1V8cUxJYwVAGRkUwYd/img-yA9kNatHnBiUHU5lZGim1hP2.png?st=2025-03-07T01%3A04%3A18Z&se=2025-03-07T03%3A04%3A18Z&sp=r&sv=2024-08-04&sr=b&rscd=inline&rsct=image/png&skoid=d505667d-d6c1-4a0a-bac7-5c84a87759f8&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skt=2025-03-06T15%3A04%3A42Z&ske=2025-03-07T15%3A04%3A42Z&sks=b&skv=2024-08-04&sig=rjcRzC5U7Y3pEDZ4ME0CiviAPdIpoGO2rRTXw3m8rHw%3D + } + + @Test + void imagesEdits() { + OpenaiService openaiService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.OPENAI.getValue()) + .setApiKey(key).setModel(Models.Openai.DALL_E_2.getModel()).build(), OpenaiService.class); + File file = FileUtil.file("your imgUrl"); + String imagesEdits = openaiService.imagesEdits("茂密的森林中,有一只九色鹿若隐若现",file); + System.out.println(imagesEdits); + } + + @Test + void imagesVariations() { + OpenaiService openaiService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.OPENAI.getValue()) + .setApiKey(key).setModel(Models.Openai.DALL_E_2.getModel()).build(), OpenaiService.class); + File file = FileUtil.file("your imgUrl"); + String imagesVariations = openaiService.imagesVariations(file); + System.out.println(imagesVariations); + } + + @Test + void textToSpeech() { + OpenaiService openaiService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.OPENAI.getValue()) + .setApiKey(key).setModel(Models.Openai.TTS_1_HD.getModel()).build(), OpenaiService.class); + InputStream inputStream = openaiService.textToSpeech("万里山河一夜白,\n" + + "千峰尽染玉龙哀。\n" + + "长风卷起琼花碎,\n" + + "直上九霄揽月来。", OpenaiCommon.OpenaiSpeech.NOVA); + + String filePath = "your filePath"; + Path path = Paths.get(filePath); + try (FileOutputStream outputStream = new FileOutputStream(filePath)) { + Files.createDirectories(path.getParent()); + byte[] buffer = new byte[1024]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + + } + + @Test + void speechToText() { + OpenaiService openaiService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.OPENAI.getValue()) + .setApiKey(key).setModel(Models.Openai.WHISPER_1.getModel()).build(), OpenaiService.class); + File file = FileUtil.file("your filePath"); + String speechToText = openaiService.speechToText(file); + System.out.println(speechToText); + } + + @Test + void embeddingText() { + OpenaiService openaiService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.OPENAI.getValue()) + .setApiKey(key).setModel(Models.Openai.TEXT_EMBEDDING_3_SMALL.getModel()).build(), OpenaiService.class); + String embeddingText = openaiService.embeddingText("萬里山河一夜白,千峰盡染玉龍哀,長風捲起瓊花碎,直上九霄闌月來"); + System.out.println(embeddingText); + } + + @Test + void moderations() { + OpenaiService openaiService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.OPENAI.getValue()) + .setApiKey(key).setModel(Models.Openai.OMNI_MODERATION_LATEST.getModel()).build(), OpenaiService.class); + String moderations = openaiService.moderations("你要杀人", "https://img2.baidu.com/it/u=862000265,4064861820&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=1544"); + System.out.println(moderations); + } + + @Test + void chatReasoning() { + OpenaiService openaiService = AIServiceFactory.getAIService(new AIConfigBuilder(ModelName.OPENAI.getValue()) + .setApiKey(key).setModel(Models.Openai.O3_MINI.getModel()).build(), OpenaiService.class); + List messages = new ArrayList<>(); + messages.add(new Message("system","你是现代抽象家")); + messages.add(new Message("user","给我一个KFC疯狂星期四的文案")); + String chatReasoning = openaiService.chatReasoning(messages, OpenaiCommon.OpenaiReasoning.HIGH.getEffort()); + System.out.println(chatReasoning); + } +} diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml index 2d66aa336..8b2cbad11 100755 --- a/hutool-all/pom.xml +++ b/hutool-all/pom.xml @@ -96,6 +96,11 @@ hutool-swing ${project.parent.version} + + ${project.parent.groupId} + hutool-ai + ${project.parent.version} + diff --git a/pom.xml b/pom.xml index 1b842d7df..749b79909 100755 --- a/pom.xml +++ b/pom.xml @@ -46,6 +46,7 @@ hutool-poi hutool-socket hutool-swing + hutool-ai