diff --git a/yudao-module-ai/pom.xml b/yudao-module-ai/pom.xml index 3270b44d1d..b81be3aa42 100644 --- a/yudao-module-ai/pom.xml +++ b/yudao-module-ai/pom.xml @@ -84,6 +84,11 @@ spring-ai-starter-model-azure-openai ${spring-ai.version} + + org.springframework.ai + spring-ai-starter-model-anthropic + ${spring-ai.version} + org.springframework.ai spring-ai-starter-model-deepseek diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/model/AiPlatformEnum.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/model/AiPlatformEnum.java index cebe0b9568..aa0ef5d6e2 100644 --- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/model/AiPlatformEnum.java +++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/model/AiPlatformEnum.java @@ -33,6 +33,7 @@ public enum AiPlatformEnum implements ArrayValuable { OPENAI("OpenAI", "OpenAI"), // OpenAI 官方 AZURE_OPENAI("AzureOpenAI", "AzureOpenAI"), // OpenAI 微软 + ANTHROPIC("Anthropic", "Anthropic"), // Anthropic Claude OLLAMA("Ollama", "Ollama"), STABLE_DIFFUSION("StableDiffusion", "StableDiffusion"), // Stability AI diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/AiModelFactoryImpl.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/AiModelFactoryImpl.java index 3387c9b6b1..d6a26ee0e3 100644 --- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/AiModelFactoryImpl.java +++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/AiModelFactoryImpl.java @@ -67,6 +67,7 @@ import org.springframework.ai.minimax.MiniMaxChatOptions; import org.springframework.ai.minimax.MiniMaxEmbeddingModel; import org.springframework.ai.minimax.MiniMaxEmbeddingOptions; import org.springframework.ai.minimax.api.MiniMaxApi; +import org.springframework.ai.model.anthropic.autoconfigure.AnthropicChatAutoConfiguration; import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiChatAutoConfiguration; import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiEmbeddingAutoConfiguration; import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiEmbeddingProperties; @@ -93,6 +94,8 @@ import org.springframework.ai.openai.OpenAiImageModel; import org.springframework.ai.openai.api.OpenAiApi; import org.springframework.ai.openai.api.OpenAiImageApi; import org.springframework.ai.openai.api.common.OpenAiApiConstants; +import org.springframework.ai.anthropic.AnthropicChatModel; +import org.springframework.ai.anthropic.api.AnthropicApi; import org.springframework.ai.stabilityai.StabilityAiImageModel; import org.springframework.ai.stabilityai.api.StabilityAiApi; import org.springframework.ai.vectorstore.SimpleVectorStore; @@ -168,6 +171,8 @@ public class AiModelFactoryImpl implements AiModelFactory { return buildOpenAiChatModel(apiKey, url); case AZURE_OPENAI: return buildAzureOpenAiChatModel(apiKey, url); + case ANTHROPIC: + return buildAnthropicChatModel(apiKey, url); case OLLAMA: return buildOllamaChatModel(url); default: @@ -206,6 +211,8 @@ public class AiModelFactoryImpl implements AiModelFactory { return SpringUtil.getBean(OpenAiChatModel.class); case AZURE_OPENAI: return SpringUtil.getBean(AzureOpenAiChatModel.class); + case ANTHROPIC: + return SpringUtil.getBean(AnthropicChatModel.class); case OLLAMA: return SpringUtil.getBean(OllamaChatModel.class); default: @@ -512,6 +519,21 @@ public class AiModelFactoryImpl implements AiModelFactory { .build(); } + /** + * 可参考 {@link AnthropicChatAutoConfiguration} 的 anthropicApi 方法 + */ + private static AnthropicChatModel buildAnthropicChatModel(String apiKey, String url) { + AnthropicApi.Builder builder = AnthropicApi.builder().apiKey(apiKey); + if (StrUtil.isNotEmpty(url)) { + builder.baseUrl(url); + } + AnthropicApi anthropicApi = builder.build(); + return AnthropicChatModel.builder() + .anthropicApi(anthropicApi) + .toolCallingManager(getToolCallingManager()) + .build(); + } + /** * 可参考 {@link OpenAiImageAutoConfiguration} 的 openAiImageModel 方法 */ diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/util/AiUtils.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/util/AiUtils.java index 0744ff6307..624d07340b 100644 --- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/util/AiUtils.java +++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/util/AiUtils.java @@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum; import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; import org.springaicommunity.moonshot.MoonshotChatOptions; import org.springaicommunity.qianfan.QianFanChatOptions; +import org.springframework.ai.anthropic.AnthropicChatOptions; import org.springframework.ai.azure.openai.AzureOpenAiChatOptions; import org.springframework.ai.chat.messages.*; import org.springframework.ai.chat.prompt.ChatOptions; @@ -67,6 +68,9 @@ public class AiUtils { case AZURE_OPENAI: return AzureOpenAiChatOptions.builder().deploymentName(model).temperature(temperature).maxTokens(maxTokens) .toolNames(toolNames).toolContext(toolContext).build(); + case ANTHROPIC: + return AnthropicChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens) + .toolNames(toolNames).toolContext(toolContext).build(); case OLLAMA: return OllamaOptions.builder().model(model).temperature(temperature).numPredict(maxTokens) .toolNames(toolNames).toolContext(toolContext).build(); diff --git a/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/AnthropicChatModelTest.java b/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/AnthropicChatModelTest.java new file mode 100644 index 0000000000..9bb98cf6ef --- /dev/null +++ b/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/AnthropicChatModelTest.java @@ -0,0 +1,59 @@ +package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.ai.anthropic.AnthropicChatModel; +import org.springframework.ai.anthropic.api.AnthropicApi; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.SystemMessage; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.prompt.Prompt; +import reactor.core.publisher.Flux; + +import java.util.ArrayList; +import java.util.List; + +/** + * {@link AnthropicChatModel} 集成测试类 + * + * @author 芋道源码 + */ +public class AnthropicChatModelTest { + + private final AnthropicChatModel chatModel = AnthropicChatModel.builder() + .anthropicApi(AnthropicApi.builder() + .apiKey("sk-muubv7cXeLw0Etgs743f365cD5Ea44429946Fa7e672d8942") + .baseUrl("https://aihubmix.com") + .build()) + .build(); + + @Test + @Disabled + public void testCall() { + // 准备参数 + List messages = new ArrayList<>(); + messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); + messages.add(new UserMessage("1 + 1 = ?")); + + // 调用 + ChatResponse response = chatModel.call(new Prompt(messages)); + // 打印结果 + System.out.println(response); + } + + @Test + @Disabled + public void testStream() { + // 准备参数 + List messages = new ArrayList<>(); + messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); + messages.add(new UserMessage("1 + 1 = ?")); + + // 调用 + Flux flux = chatModel.stream(new Prompt(messages)); + // 打印结果 + flux.doOnNext(System.out::println).then().block(); + } + +} diff --git a/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/image/TongYiImagesModelTest.java b/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/image/TongYiImagesModelTest.java index 1bfd9c8c05..b31c07696d 100644 --- a/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/image/TongYiImagesModelTest.java +++ b/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/image/TongYiImagesModelTest.java @@ -16,8 +16,11 @@ import org.springframework.ai.image.ImageResponse; */ public class TongYiImagesModelTest { - private final DashScopeImageModel imageModel = new DashScopeImageModel( - new DashScopeImageApi("sk-7d903764249848cfa912733146da12d1")); + private final DashScopeImageModel imageModel = DashScopeImageModel.builder() + .dashScopeApi(DashScopeImageApi.builder() + .apiKey("sk-47aa124781be4bfb95244cc62f63f7d0") + .build()) + .build(); @Test @Disabled diff --git a/yudao-server/src/main/resources/application.yaml b/yudao-server/src/main/resources/application.yaml index c0d79ac881..b9cfd2ae67 100644 --- a/yudao-server/src/main/resources/application.yaml +++ b/yudao-server/src/main/resources/application.yaml @@ -175,7 +175,8 @@ spring: azure: # OpenAI 微软 openai: endpoint: https://eastusprejade.openai.azure.com - api-key: xxx + anthropic: # Anthropic Claude + api-key: sk-muubv7cXeLw0Etgs743f365cD5Ea44429946Fa7e672d8942 ollama: base-url: http://127.0.0.1:11434 chat: