Commit 2fefcf8834ede9c73fbd64a16d04fe2ffd25a425

Authored by YunaiV
1 parent cad1ce48

【新增】AI:通过 AiClientFactory 提供 chatclient

yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/config/AiChatClientFactory.java deleted
1   -package cn.iocoder.yudao.module.ai.config;
2   -
3   -import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
4   -import org.springframework.ai.chat.ChatClient;
5   -import org.springframework.ai.chat.StreamingChatClient;
6   -import cn.iocoder.yudao.framework.ai.core.model.tongyi.QianWenChatClient;
7   -import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatClient;
8   -import cn.iocoder.yudao.framework.ai.core.model.yiyan.YiYanChatClient;
9   -import org.springframework.ai.ollama.OllamaChatClient;
10   -import org.springframework.ai.openai.OpenAiChatClient;
11   -import org.springframework.beans.factory.annotation.Autowired;
12   -import org.springframework.context.ApplicationContext;
13   -import org.springframework.stereotype.Component;
14   -
15   -/**
16   - * factory
17   - *
18   - * @author fansili
19   - * @time 2024/4/25 17:36
20   - * @since 1.0
21   - */
22   -@Component
23   -public class AiChatClientFactory {
24   -
25   - @Autowired
26   - private ApplicationContext applicationContext;
27   -
28   - public ChatClient getChatClient(AiPlatformEnum platformEnum) {
29   - if (AiPlatformEnum.QIAN_WEN == platformEnum) {
30   - return applicationContext.getBean(QianWenChatClient.class);
31   - } else if (AiPlatformEnum.YI_YAN == platformEnum) {
32   - return applicationContext.getBean(YiYanChatClient.class);
33   - } else if (AiPlatformEnum.XING_HUO == platformEnum) {
34   - return applicationContext.getBean(XingHuoChatClient.class);
35   - }
36   - throw new IllegalArgumentException("不支持的 chat client!");
37   - }
38   -
39   - // TODO yunai 要不再加一个接口,让他们拥有 ChatClient、StreamingChatClient 功能
40   - public StreamingChatClient getStreamingChatClient(AiPlatformEnum platformEnum) {
41   -// if (true) {
42   -// return applicationContext.getBean(OllamaChatClient.class);
43   -// }
44   - if (AiPlatformEnum.QIAN_WEN == platformEnum) {
45   - return applicationContext.getBean(QianWenChatClient.class);
46   - } else if (AiPlatformEnum.YI_YAN == platformEnum) {
47   - return applicationContext.getBean(YiYanChatClient.class);
48   - } else if (AiPlatformEnum.XING_HUO == platformEnum) {
49   - return applicationContext.getBean(XingHuoChatClient.class);
50   - } else if (AiPlatformEnum.OLLAMA == platformEnum) {
51   - return applicationContext.getBean(OllamaChatClient.class);
52   - } else if (AiPlatformEnum.OPENAI == platformEnum) {
53   - return applicationContext.getBean(OpenAiChatClient.class);
54   - }
55   - throw new IllegalArgumentException("不支持的 chat client!");
56   - }
57   -}
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/impl/AiChatServiceImpl.java
... ... @@ -4,13 +4,20 @@ import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.util.ObjUtil;
5 5 import cn.hutool.core.util.StrUtil;
6 6 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
  7 +import cn.iocoder.yudao.framework.ai.core.factory.AiClientFactory;
7 8 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
8   -import cn.iocoder.yudao.module.ai.config.AiChatClientFactory;
  9 +import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendRespVO;
  10 +import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO;
  11 +import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService;
  12 +import jakarta.annotation.Resource;
  13 +import org.springframework.ai.chat.ChatResponse;
  14 +import org.springframework.ai.chat.StreamingChatClient;
  15 +import org.springframework.ai.chat.messages.*;
  16 +import org.springframework.ai.chat.prompt.ChatOptions;
  17 +import org.springframework.ai.chat.prompt.Prompt;
9 18 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageRespVO;
10 19 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO;
11   -import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendRespVO;
12 20 import cn.iocoder.yudao.module.ai.convert.AiChatMessageConvert;
13   -import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO;
14 21 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO;
15 22 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
16 23 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
... ... @@ -19,12 +26,7 @@ import cn.iocoder.yudao.module.ai.service.AiChatService;
19 26 import cn.iocoder.yudao.module.ai.service.chat.AiChatConversationService;
20 27 import cn.iocoder.yudao.module.ai.service.model.AiChatModelService;
21 28 import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService;
22   -import lombok.AllArgsConstructor;
23 29 import lombok.extern.slf4j.Slf4j;
24   -import org.springframework.ai.chat.ChatResponse;
25   -import org.springframework.ai.chat.StreamingChatClient;
26   -import org.springframework.ai.chat.messages.*;
27   -import org.springframework.ai.chat.prompt.Prompt;
28 30 import org.springframework.stereotype.Service;
29 31 import org.springframework.transaction.annotation.Transactional;
30 32 import reactor.core.publisher.Flux;
... ... @@ -46,16 +48,22 @@ import static cn.iocoder.yudao.module.ai.ErrorCodeConstants.CHAT_CONVERSATION_NO
46 48 */
47 49 @Slf4j
48 50 @Service
49   -@AllArgsConstructor
50 51 public class AiChatServiceImpl implements AiChatService {
51 52  
52   - private final AiChatClientFactory chatClientFactory;
  53 + @Resource
  54 + private AiChatMessageMapper chatMessageMapper;
53 55  
54   - private final AiChatMessageMapper chatMessageMapper;
  56 + @Resource
  57 + private AiClientFactory clientFactory;
55 58  
56   - private final AiChatConversationService chatConversationService;
57   - private final AiChatModelService chatModalService;
58   - private final AiChatRoleService chatRoleService;
  59 + @Resource
  60 + private AiChatConversationService chatConversationService;
  61 + @Resource
  62 + private AiChatModelService chatModalService;
  63 + @Resource
  64 + private AiChatRoleService chatRoleService;
  65 + @Resource
  66 + private AiApiKeyService apiKeyService;
59 67  
60 68 @Transactional(rollbackFor = Exception.class)
61 69 public AiChatMessageRespVO chat(AiChatMessageSendReqVO req) {
... ... @@ -106,8 +114,7 @@ public class AiChatServiceImpl implements AiChatService {
106 114 List<AiChatMessageDO> historyMessages = chatMessageMapper.selectByConversationId(conversation.getId());
107 115 // 1.2 校验模型
108 116 AiChatModelDO model = chatModalService.validateChatModel(conversation.getModelId());
109   - AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform());
110   - StreamingChatClient chatClient = chatClientFactory.getStreamingChatClient(platform);
  117 + StreamingChatClient chatClient = apiKeyService.getStreamingChatClient(model.getKeyId());
111 118  
112 119 // 2. 插入 user 发送消息
113 120 AiChatMessageDO userMessage = createChatMessage(conversation.getId(), null, model,
... ... @@ -118,13 +125,13 @@ public class AiChatServiceImpl implements AiChatService {
118 125 userId, conversation.getRoleId(), MessageType.ASSISTANT, "", sendReqVO.getUseContext());
119 126  
120 127 // 3.2 创建 chat 需要的 Prompt
121   - Prompt prompt = buildPrompt(conversation, historyMessages, sendReqVO);
  128 + Prompt prompt = buildPrompt(conversation, historyMessages, model, sendReqVO);
122 129 Flux<ChatResponse> streamResponse = chatClient.stream(prompt);
123 130  
124 131 // 3.3 流式返回
125 132 // 注意:Schedulers.immediate() 目的是,避免默认 Schedulers.parallel() 并发消费 chunk 导致 SSE 响应前端会乱序问题
126 133 StringBuffer contentBuffer = new StringBuffer();
127   - return streamResponse.publishOn(Schedulers.immediate()).map(chunk -> {
  134 + return streamResponse.publishOn(Schedulers.single()).map(chunk -> {
128 135 String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getContent() : null;
129 136 newContent = StrUtil.nullToDefault(newContent, ""); // 避免 null 的 情况
130 137 contentBuffer.append(newContent);
... ... @@ -144,7 +151,8 @@ public class AiChatServiceImpl implements AiChatService {
144 151 return chatMessageMapper.deleteByConversationId(conversationId) > 0;
145 152 }
146 153  
147   - private Prompt buildPrompt(AiChatConversationDO conversation, List<AiChatMessageDO> messages, AiChatMessageSendReqVO sendReqVO) {
  154 + private Prompt buildPrompt(AiChatConversationDO conversation, List<AiChatMessageDO> messages,
  155 + AiChatModelDO model, AiChatMessageSendReqVO sendReqVO) {
148 156 // 1. 构建 Prompt Message 列表
149 157 List<Message> chatMessages = new ArrayList<>();
150 158 // 1.1 system context 角色设定
... ... @@ -156,10 +164,11 @@ public class AiChatServiceImpl implements AiChatService {
156 164 chatMessages.add(new UserMessage(sendReqVO.getContent()));
157 165  
158 166 // 2. 构建 ChatOptions 对象 TODO 芋艿:临时注释掉;等文心一言兼容了;
159   - // TODO 每一轮 token 数量
160   -// ChatOptions chatOptions = ChatOptionsBuilder.builder().withTemperature(conversation.getTemperature().floatValue()).build();
161   -// return new Prompt(chatMessages, null);
162   - return new Prompt(chatMessages);
  167 + AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform());
  168 + ChatOptions chatOptions = clientFactory.buildChatOptions(platform, model.getModel(),
  169 + conversation.getTemperature(), conversation.getMaxTokens());
  170 + return new Prompt(chatMessages, chatOptions);
  171 +// return new Prompt(chatMessages);
163 172 }
164 173  
165 174 /**
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiApiKeyService.java
... ... @@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeyPageR
5 5 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeySaveReqVO;
6 6 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO;
7 7 import jakarta.validation.Valid;
  8 +import org.springframework.ai.chat.StreamingChatClient;
8 9  
9 10 import java.util.List;
10 11  
... ... @@ -68,4 +69,14 @@ public interface AiApiKeyService {
68 69 */
69 70 List<AiApiKeyDO> getApiKeyList();
70 71  
  72 + // ========== 与 spring-ai 集成 ==========
  73 +
  74 + /**
  75 + * 获得 StreamingChatClient 对象
  76 + *
  77 + * @param id 编号
  78 + * @return StreamingChatClient 对象
  79 + */
  80 + StreamingChatClient getStreamingChatClient(Long id);
  81 +
71 82 }
72 83 \ No newline at end of file
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiApiKeyServiceImpl.java
1 1 package cn.iocoder.yudao.module.ai.service.model;
2 2  
  3 +import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
  4 +import cn.iocoder.yudao.framework.ai.core.factory.AiClientFactory;
3 5 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 7 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
... ... @@ -8,6 +10,7 @@ import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeySaveR
8 10 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO;
9 11 import cn.iocoder.yudao.module.ai.dal.mysql.model.AiApiKeyMapper;
10 12 import jakarta.annotation.Resource;
  13 +import org.springframework.ai.chat.StreamingChatClient;
11 14 import org.springframework.stereotype.Service;
12 15 import org.springframework.validation.annotation.Validated;
13 16  
... ... @@ -28,6 +31,9 @@ public class AiApiKeyServiceImpl implements AiApiKeyService {
28 31 @Resource
29 32 private AiApiKeyMapper apiKeyMapper;
30 33  
  34 + @Resource
  35 + private AiClientFactory clientFactory;
  36 +
31 37 @Override
32 38 public Long createApiKey(AiApiKeySaveReqVO createReqVO) {
33 39 // 插入
... ... @@ -86,4 +92,13 @@ public class AiApiKeyServiceImpl implements AiApiKeyService {
86 92 return apiKeyMapper.selectList();
87 93 }
88 94  
  95 + // ========== 与 spring-ai 集成 ==========
  96 +
  97 + @Override
  98 + public StreamingChatClient getStreamingChatClient(Long id) {
  99 + AiApiKeyDO apiKey = validateApiKey(id);
  100 + AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform());
  101 + return clientFactory.getOrCreateStreamingChatClient(platform, apiKey.getApiKey(), apiKey.getUrl());
  102 + }
  103 +
89 104 }
90 105 \ No newline at end of file
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java
1 1 package cn.iocoder.yudao.framework.ai.config;
2 2  
3 3 import cn.hutool.core.io.IoUtil;
  4 +import cn.iocoder.yudao.framework.ai.core.factory.AiClientFactory;
  5 +import cn.iocoder.yudao.framework.ai.core.factory.AiClientFactoryImpl;
4 6 import cn.iocoder.yudao.framework.ai.core.model.tongyi.QianWenChatClient;
5 7 import cn.iocoder.yudao.framework.ai.core.model.tongyi.QianWenChatModal;
6 8 import cn.iocoder.yudao.framework.ai.core.model.tongyi.QianWenOptions;
... ... @@ -36,18 +38,23 @@ import java.util.HashMap;
36 38 import java.util.Map;
37 39  
38 40 /**
39   - * ai 自动配置
  41 + * 芋道 AI 自动配置
40 42 *
41 43 * @author fansili
42   - * @time 2024/4/12 16:29
43   - * @since 1.0
44 44 */
45   -@Slf4j
46 45 @AutoConfiguration
47 46 @EnableConfigurationProperties(YudaoAiProperties.class)
  47 +@Slf4j
48 48 public class YudaoAiAutoConfiguration {
49 49  
50 50 @Bean
  51 + public AiClientFactory aiClientFactory() {
  52 + return new AiClientFactoryImpl();
  53 + }
  54 +
  55 + // ========== 各种 AI Client 创建 ==========
  56 +
  57 + @Bean
51 58 @ConditionalOnProperty(value = "yudao.ai.xinghuo.enable", havingValue = "true")
52 59 public XingHuoChatClient xingHuoChatClient(YudaoAiProperties yudaoAiProperties) {
53 60 YudaoAiProperties.XingHuoProperties xingHuoProperties = yudaoAiProperties.getXinghuo();
... ... @@ -108,21 +115,6 @@ public class YudaoAiAutoConfiguration {
108 115 }
109 116  
110 117 @Bean
111   - @ConditionalOnProperty(value = "yudao.ai.openAiImage.enable", havingValue = "true")
112   - public OpenAiImageClient openAiImageClient(YudaoAiProperties yudaoAiProperties) {
113   - YudaoAiProperties.OpenAiImageProperties openAiImageProperties = yudaoAiProperties.getOpenAiImage();
114   - OpenAiImageOptions openAiImageOptions = new OpenAiImageOptions();
115   - openAiImageOptions.setModel(openAiImageProperties.getModel().getModel());
116   - openAiImageOptions.setStyle(openAiImageProperties.getStyle().getStyle());
117   - openAiImageOptions.setResponseFormat("url"); // TODO 芋艿:OpenAiImageOptions.ResponseFormatEnum.URL.getValue()
118   - // 创建 client
119   - return new OpenAiImageClient(
120   - new OpenAiImageApi(openAiImageProperties.getApiKey()),
121   - openAiImageOptions,
122   - RetryUtils.DEFAULT_RETRY_TEMPLATE);
123   - }
124   -
125   - @Bean
126 118 @ConditionalOnMissingBean(value = MidjourneyMessageHandler.class)
127 119 public MidjourneyMessageHandler defaultMidjourneyMessageHandler() {
128 120 // 如果没有实现 MidjourneyMessageHandler 默认注入一个
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiClientFactory.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.ai.core.factory;
  2 +
  3 +import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
  4 +import org.springframework.ai.chat.StreamingChatClient;
  5 +import org.springframework.ai.chat.prompt.ChatOptions;
  6 +
  7 +/**
  8 + * AI 客户端工厂的接口类
  9 + *
  10 + * @author fansili
  11 + */
  12 +public interface AiClientFactory {
  13 +
  14 + /**
  15 + * 基于指定配置,获得 StreamingChatClient 对象
  16 + *
  17 + * 如果不存在,则进行创建
  18 + *
  19 + * @param platform 平台
  20 + * @param apiKey API KEY
  21 + * @param url API URL
  22 + * @return StreamingChatClient 对象
  23 + */
  24 + StreamingChatClient getOrCreateStreamingChatClient(AiPlatformEnum platform, String apiKey, String url);
  25 +
  26 + /**
  27 + * 基于默认配置,获得 StreamingChatClient 对象
  28 + *
  29 + * 默认配置,指的是在 application.yaml 配置文件中的 spring.ai 相关的配置
  30 + *
  31 + * @param platform 平台
  32 + * @return StreamingChatClient 对象
  33 + */
  34 + StreamingChatClient getDefaultStreamingChatClient(AiPlatformEnum platform);
  35 +
  36 + /**
  37 + * 创建 Chat 参数
  38 + *
  39 + * @param platform 平台
  40 + * @param model 模型
  41 + * @param temperature 温度
  42 + * @param maxTokens 生成的最大 Token
  43 + * @return Chat 参数
  44 + */
  45 + ChatOptions buildChatOptions(AiPlatformEnum platform, String model, Double temperature, Integer maxTokens);
  46 +
  47 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiClientFactoryImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.ai.core.factory;
  2 +
  3 +import cn.hutool.core.lang.Assert;
  4 +import cn.hutool.core.lang.Singleton;
  5 +import cn.hutool.core.lang.func.Func0;
  6 +import cn.hutool.core.util.ArrayUtil;
  7 +import cn.hutool.core.util.StrUtil;
  8 +import cn.hutool.extra.spring.SpringUtil;
  9 +import cn.iocoder.yudao.framework.ai.config.YudaoAiAutoConfiguration;
  10 +import cn.iocoder.yudao.framework.ai.config.YudaoAiProperties;
  11 +import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
  12 +import cn.iocoder.yudao.framework.ai.core.model.tongyi.QianWenChatClient;
  13 +import cn.iocoder.yudao.framework.ai.core.model.tongyi.QianWenChatModal;
  14 +import cn.iocoder.yudao.framework.ai.core.model.tongyi.QianWenOptions;
  15 +import cn.iocoder.yudao.framework.ai.core.model.tongyi.api.QianWenApi;
  16 +import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatClient;
  17 +import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel;
  18 +import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoOptions;
  19 +import cn.iocoder.yudao.framework.ai.core.model.xinghuo.api.XingHuoApi;
  20 +import cn.iocoder.yudao.framework.ai.core.model.yiyan.YiYanChatClient;
  21 +import cn.iocoder.yudao.framework.ai.core.model.yiyan.YiYanChatOptions;
  22 +import cn.iocoder.yudao.framework.ai.core.model.yiyan.api.YiYanApi;
  23 +import org.springframework.ai.autoconfigure.ollama.OllamaAutoConfiguration;
  24 +import org.springframework.ai.autoconfigure.openai.OpenAiAutoConfiguration;
  25 +import org.springframework.ai.chat.StreamingChatClient;
  26 +import org.springframework.ai.chat.prompt.ChatOptions;
  27 +import org.springframework.ai.ollama.OllamaChatClient;
  28 +import org.springframework.ai.ollama.api.OllamaApi;
  29 +import org.springframework.ai.ollama.api.OllamaOptions;
  30 +import org.springframework.ai.openai.OpenAiChatClient;
  31 +import org.springframework.ai.openai.OpenAiChatOptions;
  32 +import org.springframework.ai.openai.api.ApiUtils;
  33 +import org.springframework.ai.openai.api.OpenAiApi;
  34 +
  35 +import java.util.List;
  36 +
  37 +/**
  38 + * AI 客户端工厂的实现类
  39 + *
  40 + * @author 芋道源码
  41 + */
  42 +public class AiClientFactoryImpl implements AiClientFactory {
  43 +
  44 + @Override
  45 + public StreamingChatClient getOrCreateStreamingChatClient(AiPlatformEnum platform, String apiKey, String url) {
  46 + String cacheKey = buildClientCacheKey(StreamingChatClient.class, platform, apiKey, url);
  47 + return Singleton.get(cacheKey, (Func0<StreamingChatClient>) () -> {
  48 + //noinspection EnhancedSwitchMigration
  49 + switch (platform) {
  50 + case OPENAI:
  51 + return buildOpenAiChatClient(apiKey, url);
  52 + case OLLAMA:
  53 + return buildOllamaChatClient(url);
  54 + case YI_YAN:
  55 + return buildYiYanChatClient(apiKey);
  56 + case XING_HUO:
  57 + return buildXingHuoChatClient(apiKey);
  58 + case QIAN_WEN:
  59 + return buildQianWenChatClient(apiKey);
  60 + default:
  61 + throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform));
  62 + }
  63 + });
  64 + }
  65 +
  66 + @Override
  67 + public StreamingChatClient getDefaultStreamingChatClient(AiPlatformEnum platform) {
  68 + //noinspection EnhancedSwitchMigration
  69 + switch (platform) {
  70 + case OPENAI:
  71 + return SpringUtil.getBean(OpenAiChatClient.class);
  72 + case OLLAMA:
  73 + return SpringUtil.getBean(OllamaChatClient.class);
  74 + case YI_YAN:
  75 + return SpringUtil.getBean(YiYanChatClient.class);
  76 + case XING_HUO:
  77 + return SpringUtil.getBean(XingHuoChatClient.class);
  78 + case QIAN_WEN:
  79 + return SpringUtil.getBean(QianWenChatClient.class);
  80 + default:
  81 + throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform));
  82 + }
  83 + }
  84 +
  85 + private static String buildClientCacheKey(Class<?> clazz, Object... params) {
  86 + if (ArrayUtil.isEmpty(params)) {
  87 + return clazz.getName();
  88 + }
  89 + return StrUtil.format("{}#{}", clazz.getName(), ArrayUtil.join(params, "_"));
  90 + }
  91 +
  92 + @Override
  93 + public ChatOptions buildChatOptions(AiPlatformEnum platform, String model, Double temperature, Integer maxTokens) {
  94 + Float temperatureF = temperature != null ? temperature.floatValue() : null;
  95 + //noinspection EnhancedSwitchMigration
  96 + switch (platform) {
  97 + case OPENAI:
  98 + return OpenAiChatOptions.builder().withModel(model).withTemperature(temperatureF).withMaxTokens(maxTokens).build();
  99 + case OLLAMA:
  100 + return OllamaOptions.create().withModel(model).withTemperature(temperatureF).withNumPredict(maxTokens);
  101 + case YI_YAN:
  102 + // TODO @fan:增加一个 model
  103 + return new YiYanChatOptions().setTemperature(temperatureF).setMaxOutputTokens(maxTokens);
  104 + case XING_HUO:
  105 + return new XingHuoOptions().setChatModel(XingHuoChatModel.valueOfModel(model)).setTemperature(temperatureF)
  106 + .setMaxTokens(maxTokens);
  107 + case QIAN_WEN:
  108 + // TODO @fan:增加 model、temperature 参数
  109 + return new QianWenOptions().setMaxTokens(maxTokens);
  110 + default:
  111 + throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform));
  112 + }
  113 + }
  114 +
  115 + // ========== 各种创建 spring-ai 客户端的方法 ==========
  116 +
  117 + /**
  118 + * 可参考 {@link OpenAiAutoConfiguration}
  119 + */
  120 + private static OpenAiChatClient buildOpenAiChatClient(String openAiToken, String url) {
  121 + url = StrUtil.blankToDefault(url, ApiUtils.DEFAULT_BASE_URL);
  122 + OpenAiApi openAiApi = new OpenAiApi(url, openAiToken);
  123 + return new OpenAiChatClient(openAiApi);
  124 + }
  125 +
  126 + /**
  127 + * 可参考 {@link OllamaAutoConfiguration}
  128 + */
  129 + private static OllamaChatClient buildOllamaChatClient(String url) {
  130 + OllamaApi ollamaApi = new OllamaApi(url);
  131 + return new OllamaChatClient(ollamaApi);
  132 + }
  133 +
  134 + /**
  135 + * 可参考 {@link YudaoAiAutoConfiguration#yiYanChatClient(YudaoAiProperties)}
  136 + */
  137 + private static YiYanChatClient buildYiYanChatClient(String key) {
  138 + List<String> keys = StrUtil.split(key, '|');
  139 + Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式");
  140 + String appKey = keys.get(0);
  141 + String secretKey = keys.get(1);
  142 + YiYanApi yiYanApi = new YiYanApi(appKey, secretKey, YiYanApi.DEFAULT_CHAT_MODEL, 0);
  143 + return new YiYanChatClient(yiYanApi);
  144 + }
  145 +
  146 + /**
  147 + * 可参考 {@link YudaoAiAutoConfiguration#xingHuoChatClient(YudaoAiProperties)}
  148 + */
  149 + private static XingHuoChatClient buildXingHuoChatClient(String key) {
  150 + List<String> keys = StrUtil.split(key, '|');
  151 + Assert.equals(keys.size(), 2, "XingHuoChatClient 的密钥需要 (appKey|secretKey) 格式");
  152 + String appId = keys.get(0);
  153 + String appKey = keys.get(1);
  154 + String secretKey = keys.get(2);
  155 + XingHuoApi xingHuoApi = new XingHuoApi(appId, appKey, secretKey);
  156 + return new XingHuoChatClient(xingHuoApi);
  157 + }
  158 +
  159 + /**
  160 + * 可参考 {@link YudaoAiAutoConfiguration#qianWenChatClient(YudaoAiProperties)}
  161 + */
  162 + private static QianWenChatClient buildQianWenChatClient(String key) {
  163 + QianWenApi qianWenApi = new QianWenApi(key, QianWenChatModal.QWEN_72B_CHAT);
  164 + return new QianWenChatClient(qianWenApi);
  165 + }
  166 +
  167 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/tongyi/QianWenOptions.java
... ... @@ -6,6 +6,8 @@ import lombok.experimental.Accessors;
6 6  
7 7 import java.util.List;
8 8  
  9 +// TODO @fan:增加一个 model 参数
  10 +// TODO @fan:增加一个 Temperature 参数
9 11 /**
10 12 * 阿里云 千问 属性
11 13 *
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/XingHuoOptions.java
... ... @@ -14,6 +14,7 @@ import lombok.experimental.Accessors;
14 14 @Accessors(chain = true)
15 15 public class XingHuoOptions implements ChatOptions {
16 16  
  17 + // TODO @fan:这里 model 参数,然后使用 string
17 18 /**
18 19 * https://www.xfyun.cn/doc/spark/Web.html#_1-%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E
19 20 * <p>
... ... @@ -43,7 +44,6 @@ public class XingHuoOptions implements ChatOptions {
43 44 */
44 45 private String chatId;
45 46  
46   -
47 47 @Override
48 48 public Float getTemperature() {
49 49 return this.temperature;
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/yiyan/YiYanChatOptions.java
... ... @@ -6,6 +6,7 @@ import org.springframework.ai.chat.prompt.ChatOptions;
6 6  
7 7 import java.util.List;
8 8  
  9 +// TODO @fan:增加一个 model
9 10 // TODO @fan:字段命名,penalty_score 类似的,建议改成驼峰原则
10 11 // TODO @fan:字段的注释,可以都删除掉,让用户 https://cloud.baidu.com/doc/WENXINWORKSHOP/s/clntwmv7t 即可
11 12 /**
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/yiyan/api/YiYanApi.java
... ... @@ -18,7 +18,7 @@ public class YiYanApi {
18 18  
19 19 private static final String AUTH_2_TOKEN_URI = "/oauth/2.0/token";
20 20  
21   - public static final String DEFAULT_CHAT_MODEL = YiYanChatModel.ERNIE4_0.getModel();
  21 + public static final YiYanChatModel DEFAULT_CHAT_MODEL = YiYanChatModel.ERNIE4_0;
22 22  
23 23 private final String appKey;
24 24 private final String secretKey;
... ... @@ -39,6 +39,7 @@ public class YiYanApi {
39 39 */
40 40 private final YiYanChatModel useChatModel;
41 41  
  42 + // TODO fan:看看是不是去掉 refreshTokenSecondTime 字段
42 43 public YiYanApi(String appKey, String secretKey, YiYanChatModel useChatModel, int refreshTokenSecondTime) {
43 44 this.appKey = appKey;
44 45 this.secretKey = secretKey;
... ...
yudao-server/src/main/resources/application.yaml
... ... @@ -150,15 +150,8 @@ spring.ai:
150 150 chat:
151 151 model: llama3
152 152 openai:
153   -# api-key: sk-QmgIIPc5xiYd8lPb076b1b7774Ea49Af9eD2Ef172c8f7e43
154   -# base-url: https://openkey.cloud
155   -# api-key: sk-gkgfYxhX9FxyZJznwxRZSJwKeGQYNPDVWjhby2PRRf17GHeT
156   -# base-url: https://api.chatanywhere.tech
157 153 api-key: sk-yzKea6d8e8212c3bdd99f9f44ced1cae37c097e5aa3BTS7z
158 154 base-url: https://api.gptsapi.net
159   -# chat:
160   -# options:
161   -# model: gpt-4-0125-preview
162 155  
163 156 yudao.ai:
164 157 yiyan:
... ...