Commit b7180d34818c7a7ff988c6c9fc74a25a1e20ebee

Authored by YunaiV
1 parent 6856f5f1

【新增】AI:会话接入 API KEY 逻辑

yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/model/AiApiKeyMapper.java
@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.ai.dal.mysql.model; @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.ai.dal.mysql.model;
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  6 +import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
6 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeyPageReqVO; 7 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeyPageReqVO;
7 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO; 8 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO;
8 import org.apache.ibatis.annotations.Mapper; 9 import org.apache.ibatis.annotations.Mapper;
@@ -23,4 +24,12 @@ public interface AiApiKeyMapper extends BaseMapperX<AiApiKeyDO> { @@ -23,4 +24,12 @@ public interface AiApiKeyMapper extends BaseMapperX<AiApiKeyDO> {
23 .orderByDesc(AiApiKeyDO::getId)); 24 .orderByDesc(AiApiKeyDO::getId));
24 } 25 }
25 26
  27 + default AiApiKeyDO selectFirstByPlatformAndStatus(String platform, Integer status) {
  28 + return selectOne(new QueryWrapperX<AiApiKeyDO>()
  29 + .eq("platform", platform)
  30 + .eq("status", status)
  31 + .limitN(1)
  32 + .orderByAsc("id"));
  33 + }
  34 +
26 } 35 }
27 \ No newline at end of file 36 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java
@@ -4,11 +4,13 @@ import cn.hutool.core.collection.CollUtil; @@ -4,11 +4,13 @@ import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.util.ObjUtil; 4 import cn.hutool.core.util.ObjUtil;
5 import cn.hutool.core.util.StrUtil; 5 import cn.hutool.core.util.StrUtil;
6 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; 6 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
7 -import cn.iocoder.yudao.framework.ai.core.factory.AiClientFactory; 7 +import cn.iocoder.yudao.framework.ai.core.model.tongyi.QianWenOptions;
  8 +import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel;
  9 +import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoOptions;
  10 +import cn.iocoder.yudao.framework.ai.core.model.yiyan.YiYanChatOptions;
8 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 11 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
9 import cn.iocoder.yudao.framework.common.pojo.PageResult; 12 import cn.iocoder.yudao.framework.common.pojo.PageResult;
10 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 13 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
11 -import cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants;  
12 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessagePageReqVO; 14 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessagePageReqVO;
13 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageRespVO; 15 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageRespVO;
14 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO; 16 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO;
@@ -18,6 +20,7 @@ import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO; @@ -18,6 +20,7 @@ import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO;
18 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; 20 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
19 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; 21 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
20 import cn.iocoder.yudao.module.ai.dal.mysql.chat.AiChatMessageMapper; 22 import cn.iocoder.yudao.module.ai.dal.mysql.chat.AiChatMessageMapper;
  23 +import cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants;
21 import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService; 24 import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService;
22 import cn.iocoder.yudao.module.ai.service.model.AiChatModelService; 25 import cn.iocoder.yudao.module.ai.service.model.AiChatModelService;
23 import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService; 26 import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService;
@@ -28,6 +31,8 @@ import org.springframework.ai.chat.StreamingChatClient; @@ -28,6 +31,8 @@ import org.springframework.ai.chat.StreamingChatClient;
28 import org.springframework.ai.chat.messages.*; 31 import org.springframework.ai.chat.messages.*;
29 import org.springframework.ai.chat.prompt.ChatOptions; 32 import org.springframework.ai.chat.prompt.ChatOptions;
30 import org.springframework.ai.chat.prompt.Prompt; 33 import org.springframework.ai.chat.prompt.Prompt;
  34 +import org.springframework.ai.ollama.api.OllamaOptions;
  35 +import org.springframework.ai.openai.OpenAiChatOptions;
31 import org.springframework.stereotype.Service; 36 import org.springframework.stereotype.Service;
32 import org.springframework.transaction.annotation.Transactional; 37 import org.springframework.transaction.annotation.Transactional;
33 import reactor.core.publisher.Flux; 38 import reactor.core.publisher.Flux;
@@ -55,9 +60,6 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { @@ -55,9 +60,6 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
55 private AiChatMessageMapper chatMessageMapper; 60 private AiChatMessageMapper chatMessageMapper;
56 61
57 @Resource 62 @Resource
58 - private AiClientFactory clientFactory;  
59 -  
60 - @Resource  
61 private AiChatConversationService chatConversationService; 63 private AiChatConversationService chatConversationService;
62 @Resource 64 @Resource
63 private AiChatModelService chatModalService; 65 private AiChatModelService chatModalService;
@@ -168,11 +170,33 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { @@ -168,11 +170,33 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
168 170
169 // 2. 构建 ChatOptions 对象 171 // 2. 构建 ChatOptions 对象
170 AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform()); 172 AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform());
171 - ChatOptions chatOptions = clientFactory.buildChatOptions(platform, model.getModel(), 173 + ChatOptions chatOptions = buildChatOptions(platform, model.getModel(),
172 conversation.getTemperature(), conversation.getMaxTokens()); 174 conversation.getTemperature(), conversation.getMaxTokens());
173 return new Prompt(chatMessages, chatOptions); 175 return new Prompt(chatMessages, chatOptions);
174 } 176 }
175 177
  178 + private static ChatOptions buildChatOptions(AiPlatformEnum platform, String model, Double temperature, Integer maxTokens) {
  179 + Float temperatureF = temperature != null ? temperature.floatValue() : null;
  180 + //noinspection EnhancedSwitchMigration
  181 + switch (platform) {
  182 + case OPENAI:
  183 + return OpenAiChatOptions.builder().withModel(model).withTemperature(temperatureF).withMaxTokens(maxTokens).build();
  184 + case OLLAMA:
  185 + return OllamaOptions.create().withModel(model).withTemperature(temperatureF).withNumPredict(maxTokens);
  186 + case YI_YAN:
  187 + // TODO @fan:增加一个 model
  188 + return new YiYanChatOptions().setTemperature(temperatureF).setMaxOutputTokens(maxTokens);
  189 + case XING_HUO:
  190 + return new XingHuoOptions().setChatModel(XingHuoChatModel.valueOfModel(model)).setTemperature(temperatureF)
  191 + .setMaxTokens(maxTokens);
  192 + case QIAN_WEN:
  193 + // TODO @fan:增加 model、temperature 参数
  194 + return new QianWenOptions().setMaxTokens(maxTokens);
  195 + default:
  196 + throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform));
  197 + }
  198 + }
  199 +
176 /** 200 /**
177 * 从历史消息中,获得倒序的 n 组消息作为消息上下文 201 * 从历史消息中,获得倒序的 n 组消息作为消息上下文
178 * 202 *
@@ -183,7 +207,9 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { @@ -183,7 +207,9 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
183 * @param sendReqVO 发送请求 207 * @param sendReqVO 发送请求
184 * @return 消息上下文 208 * @return 消息上下文
185 */ 209 */
186 - private List<AiChatMessageDO> filterContextMessages(List<AiChatMessageDO> messages, AiChatConversationDO conversation, AiChatMessageSendReqVO sendReqVO) { 210 + private List<AiChatMessageDO> filterContextMessages(List<AiChatMessageDO> messages,
  211 + AiChatConversationDO conversation,
  212 + AiChatMessageSendReqVO sendReqVO) {
187 if (conversation.getMaxContexts() == null || ObjUtil.notEqual(sendReqVO.getUseContext(), Boolean.TRUE)) { 213 if (conversation.getMaxContexts() == null || ObjUtil.notEqual(sendReqVO.getUseContext(), Boolean.TRUE)) {
188 return Collections.emptyList(); 214 return Collections.emptyList();
189 } 215 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageService.java
@@ -64,4 +64,5 @@ public interface AiImageService { @@ -64,4 +64,5 @@ public interface AiImageService {
64 * @return 64 * @return
65 */ 65 */
66 Boolean midjourneyNotify(Long loginUserId, MidjourneyNotifyReqVO notifyReqVO); 66 Boolean midjourneyNotify(Long loginUserId, MidjourneyNotifyReqVO notifyReqVO);
  67 +
67 } 68 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java
@@ -7,7 +7,6 @@ import cn.hutool.core.util.StrUtil; @@ -7,7 +7,6 @@ import cn.hutool.core.util.StrUtil;
7 import cn.hutool.extra.spring.SpringUtil; 7 import cn.hutool.extra.spring.SpringUtil;
8 import cn.hutool.http.HttpUtil; 8 import cn.hutool.http.HttpUtil;
9 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; 9 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
10 -import cn.iocoder.yudao.framework.ai.core.factory.AiClientFactory;  
11 import cn.iocoder.yudao.framework.common.pojo.PageParam; 10 import cn.iocoder.yudao.framework.common.pojo.PageParam;
12 import cn.iocoder.yudao.framework.common.pojo.PageResult; 11 import cn.iocoder.yudao.framework.common.pojo.PageResult;
13 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 12 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
@@ -23,6 +22,7 @@ import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageMidjourneyIma @@ -23,6 +22,7 @@ import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageMidjourneyIma
23 import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO; 22 import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO;
24 import cn.iocoder.yudao.module.ai.dal.mysql.image.AiImageMapper; 23 import cn.iocoder.yudao.module.ai.dal.mysql.image.AiImageMapper;
25 import cn.iocoder.yudao.module.ai.enums.image.AiImageStatusEnum; 24 import cn.iocoder.yudao.module.ai.enums.image.AiImageStatusEnum;
  25 +import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService;
26 import cn.iocoder.yudao.module.infra.api.file.FileApi; 26 import cn.iocoder.yudao.module.infra.api.file.FileApi;
27 import jakarta.annotation.Resource; 27 import jakarta.annotation.Resource;
28 import lombok.extern.slf4j.Slf4j; 28 import lombok.extern.slf4j.Slf4j;
@@ -57,7 +57,7 @@ public class AiImageServiceImpl implements AiImageService { @@ -57,7 +57,7 @@ public class AiImageServiceImpl implements AiImageService {
57 private FileApi fileApi; 57 private FileApi fileApi;
58 58
59 @Resource 59 @Resource
60 - private AiClientFactory aiClientFactory; 60 + private AiApiKeyService apiKeyService;
61 61
62 @Autowired 62 @Autowired
63 private MidjourneyProxyClient midjourneyProxyClient; 63 private MidjourneyProxyClient midjourneyProxyClient;
@@ -82,17 +82,17 @@ public class AiImageServiceImpl implements AiImageService { @@ -82,17 +82,17 @@ public class AiImageServiceImpl implements AiImageService {
82 .setWidth(drawReqVO.getWidth()).setHeight(drawReqVO.getHeight()).setStatus(AiImageStatusEnum.IN_PROGRESS.getStatus()); 82 .setWidth(drawReqVO.getWidth()).setHeight(drawReqVO.getHeight()).setStatus(AiImageStatusEnum.IN_PROGRESS.getStatus());
83 imageMapper.insert(image); 83 imageMapper.insert(image);
84 // 2. 异步绘制,后续前端通过返回的 id 进行轮询结果 84 // 2. 异步绘制,后续前端通过返回的 id 进行轮询结果
85 - getSelf().doDall(image, drawReqVO); 85 + getSelf().executeDrawImage(image, drawReqVO);
86 return image.getId(); 86 return image.getId();
87 } 87 }
88 88
89 @Async 89 @Async
90 - public void doDall(AiImageDO image, AiImageDrawReqVO req) { 90 + public void executeDrawImage(AiImageDO image, AiImageDrawReqVO req) {
91 try { 91 try {
92 // 1.1 构建请求 92 // 1.1 构建请求
93 ImageOptions request = buildImageOptions(req); 93 ImageOptions request = buildImageOptions(req);
94 // 1.2 执行请求 94 // 1.2 执行请求
95 - ImageClient imageClient = aiClientFactory.getDefaultImageClient(AiPlatformEnum.validatePlatform(req.getPlatform())); 95 + ImageClient imageClient = apiKeyService.getImageClient(AiPlatformEnum.validatePlatform(req.getPlatform()));
96 ImageResponse response = imageClient.call(new ImagePrompt(req.getPrompt(), request)); 96 ImageResponse response = imageClient.call(new ImagePrompt(req.getPrompt(), request));
97 97
98 // 2. 上传到文件服务 98 // 2. 上传到文件服务
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiApiKeyService.java
1 package cn.iocoder.yudao.module.ai.service.model; 1 package cn.iocoder.yudao.module.ai.service.model;
2 2
  3 +import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeyPageReqVO; 5 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeyPageReqVO;
5 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeySaveReqVO; 6 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeySaveReqVO;
6 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO; 7 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO;
7 import jakarta.validation.Valid; 8 import jakarta.validation.Valid;
8 import org.springframework.ai.chat.StreamingChatClient; 9 import org.springframework.ai.chat.StreamingChatClient;
  10 +import org.springframework.ai.image.ImageClient;
9 11
10 import java.util.List; 12 import java.util.List;
11 13
@@ -79,4 +81,14 @@ public interface AiApiKeyService { @@ -79,4 +81,14 @@ public interface AiApiKeyService {
79 */ 81 */
80 StreamingChatClient getStreamingChatClient(Long id); 82 StreamingChatClient getStreamingChatClient(Long id);
81 83
  84 + /**
  85 + * 获得 ImageClient 对象
  86 + *
  87 + * TODO 可优化点:目前默认获取 platform 对应的第一个开启的配置用于绘画;后续可以支持配置选择
  88 + *
  89 + * @param platform 平台
  90 + * @return ImageClient 对象
  91 + */
  92 + ImageClient getImageClient(AiPlatformEnum platform);
  93 +
82 } 94 }
83 \ No newline at end of file 95 \ 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
@@ -11,6 +11,7 @@ import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO; @@ -11,6 +11,7 @@ import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO;
11 import cn.iocoder.yudao.module.ai.dal.mysql.model.AiApiKeyMapper; 11 import cn.iocoder.yudao.module.ai.dal.mysql.model.AiApiKeyMapper;
12 import jakarta.annotation.Resource; 12 import jakarta.annotation.Resource;
13 import org.springframework.ai.chat.StreamingChatClient; 13 import org.springframework.ai.chat.StreamingChatClient;
  14 +import org.springframework.ai.image.ImageClient;
14 import org.springframework.stereotype.Service; 15 import org.springframework.stereotype.Service;
15 import org.springframework.validation.annotation.Validated; 16 import org.springframework.validation.annotation.Validated;
16 17
@@ -101,4 +102,13 @@ public class AiApiKeyServiceImpl implements AiApiKeyService { @@ -101,4 +102,13 @@ public class AiApiKeyServiceImpl implements AiApiKeyService {
101 return clientFactory.getOrCreateStreamingChatClient(platform, apiKey.getApiKey(), apiKey.getUrl()); 102 return clientFactory.getOrCreateStreamingChatClient(platform, apiKey.getApiKey(), apiKey.getUrl());
102 } 103 }
103 104
  105 + @Override
  106 + public ImageClient getImageClient(AiPlatformEnum platform) {
  107 + AiApiKeyDO apiKey = apiKeyMapper.selectFirstByPlatformAndStatus(platform.getName(), CommonStatusEnum.ENABLE.getStatus());
  108 + if (apiKey == null) {
  109 + return null;
  110 + }
  111 + return clientFactory.getOrCreateImageClient(platform, apiKey.getApiKey(), apiKey.getUrl());
  112 + }
  113 +
104 } 114 }
105 \ No newline at end of file 115 \ No newline at end of file
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiClientFactory.java
@@ -2,7 +2,6 @@ package cn.iocoder.yudao.framework.ai.core.factory; @@ -2,7 +2,6 @@ package cn.iocoder.yudao.framework.ai.core.factory;
2 2
3 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; 3 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
4 import org.springframework.ai.chat.StreamingChatClient; 4 import org.springframework.ai.chat.StreamingChatClient;
5 -import org.springframework.ai.chat.prompt.ChatOptions;  
6 import org.springframework.ai.image.ImageClient; 5 import org.springframework.ai.image.ImageClient;
7 6
8 /** 7 /**
@@ -45,14 +44,15 @@ public interface AiClientFactory { @@ -45,14 +44,15 @@ public interface AiClientFactory {
45 ImageClient getDefaultImageClient(AiPlatformEnum platform); 44 ImageClient getDefaultImageClient(AiPlatformEnum platform);
46 45
47 /** 46 /**
48 - * 创建 Chat 参数 47 + * 基于指定配置,获得 ImageClient 对象
  48 + *
  49 + * 如果不存在,则进行创建
49 * 50 *
50 * @param platform 平台 51 * @param platform 平台
51 - * @param model 模型  
52 - * @param temperature 温度  
53 - * @param maxTokens 生成的最大 Token  
54 - * @return Chat 参数 52 + * @param apiKey API KEY
  53 + * @param url API URL
  54 + * @return ImageClient 对象
55 */ 55 */
56 - ChatOptions buildChatOptions(AiPlatformEnum platform, String model, Double temperature, Integer maxTokens); 56 + ImageClient getOrCreateImageClient(AiPlatformEnum platform, String apiKey, String url);
57 57
58 } 58 }
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiClientFactoryImpl.java
@@ -11,29 +11,25 @@ import cn.iocoder.yudao.framework.ai.config.YudaoAiProperties; @@ -11,29 +11,25 @@ import cn.iocoder.yudao.framework.ai.config.YudaoAiProperties;
11 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; 11 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
12 import cn.iocoder.yudao.framework.ai.core.model.tongyi.QianWenChatClient; 12 import cn.iocoder.yudao.framework.ai.core.model.tongyi.QianWenChatClient;
13 import cn.iocoder.yudao.framework.ai.core.model.tongyi.QianWenChatModal; 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; 14 import cn.iocoder.yudao.framework.ai.core.model.tongyi.api.QianWenApi;
16 import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatClient; 15 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; 16 import cn.iocoder.yudao.framework.ai.core.model.xinghuo.api.XingHuoApi;
20 import cn.iocoder.yudao.framework.ai.core.model.yiyan.YiYanChatClient; 17 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; 18 import cn.iocoder.yudao.framework.ai.core.model.yiyan.api.YiYanApi;
23 import org.springframework.ai.autoconfigure.ollama.OllamaAutoConfiguration; 19 import org.springframework.ai.autoconfigure.ollama.OllamaAutoConfiguration;
24 import org.springframework.ai.autoconfigure.openai.OpenAiAutoConfiguration; 20 import org.springframework.ai.autoconfigure.openai.OpenAiAutoConfiguration;
25 import org.springframework.ai.chat.StreamingChatClient; 21 import org.springframework.ai.chat.StreamingChatClient;
26 -import org.springframework.ai.chat.prompt.ChatOptions;  
27 import org.springframework.ai.image.ImageClient; 22 import org.springframework.ai.image.ImageClient;
28 import org.springframework.ai.ollama.OllamaChatClient; 23 import org.springframework.ai.ollama.OllamaChatClient;
29 import org.springframework.ai.ollama.api.OllamaApi; 24 import org.springframework.ai.ollama.api.OllamaApi;
30 -import org.springframework.ai.ollama.api.OllamaOptions;  
31 import org.springframework.ai.openai.OpenAiChatClient; 25 import org.springframework.ai.openai.OpenAiChatClient;
32 -import org.springframework.ai.openai.OpenAiChatOptions;  
33 import org.springframework.ai.openai.OpenAiImageClient; 26 import org.springframework.ai.openai.OpenAiImageClient;
34 import org.springframework.ai.openai.api.ApiUtils; 27 import org.springframework.ai.openai.api.ApiUtils;
35 import org.springframework.ai.openai.api.OpenAiApi; 28 import org.springframework.ai.openai.api.OpenAiApi;
  29 +import org.springframework.ai.openai.api.OpenAiImageApi;
36 import org.springframework.ai.stabilityai.StabilityAiImageClient; 30 import org.springframework.ai.stabilityai.StabilityAiImageClient;
  31 +import org.springframework.ai.stabilityai.api.StabilityAiApi;
  32 +import org.springframework.web.client.RestClient;
37 33
38 import java.util.List; 34 import java.util.List;
39 35
@@ -100,36 +96,26 @@ public class AiClientFactoryImpl implements AiClientFactory { @@ -100,36 +96,26 @@ public class AiClientFactoryImpl implements AiClientFactory {
100 } 96 }
101 } 97 }
102 98
103 - private static String buildClientCacheKey(Class<?> clazz, Object... params) {  
104 - if (ArrayUtil.isEmpty(params)) {  
105 - return clazz.getName();  
106 - }  
107 - return StrUtil.format("{}#{}", clazz.getName(), ArrayUtil.join(params, "_"));  
108 - }  
109 -  
110 @Override 99 @Override
111 - public ChatOptions buildChatOptions(AiPlatformEnum platform, String model, Double temperature, Integer maxTokens) {  
112 - Float temperatureF = temperature != null ? temperature.floatValue() : null; 100 + public ImageClient getOrCreateImageClient(AiPlatformEnum platform, String apiKey, String url) {
113 //noinspection EnhancedSwitchMigration 101 //noinspection EnhancedSwitchMigration
114 switch (platform) { 102 switch (platform) {
115 case OPENAI: 103 case OPENAI:
116 - return OpenAiChatOptions.builder().withModel(model).withTemperature(temperatureF).withMaxTokens(maxTokens).build();  
117 - case OLLAMA:  
118 - return OllamaOptions.create().withModel(model).withTemperature(temperatureF).withNumPredict(maxTokens);  
119 - case YI_YAN:  
120 - // TODO @fan:增加一个 model  
121 - return new YiYanChatOptions().setTemperature(temperatureF).setMaxOutputTokens(maxTokens);  
122 - case XING_HUO:  
123 - return new XingHuoOptions().setChatModel(XingHuoChatModel.valueOfModel(model)).setTemperature(temperatureF)  
124 - .setMaxTokens(maxTokens);  
125 - case QIAN_WEN:  
126 - // TODO @fan:增加 model、temperature 参数  
127 - return new QianWenOptions().setMaxTokens(maxTokens); 104 + return buildOpenAiImageClient(apiKey, url);
  105 + case STABLE_DIFFUSION:
  106 + return buildStabilityAiImageClient(apiKey, url);
128 default: 107 default:
129 throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform)); 108 throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform));
130 } 109 }
131 } 110 }
132 111
  112 + private static String buildClientCacheKey(Class<?> clazz, Object... params) {
  113 + if (ArrayUtil.isEmpty(params)) {
  114 + return clazz.getName();
  115 + }
  116 + return StrUtil.format("{}#{}", clazz.getName(), ArrayUtil.join(params, "_"));
  117 + }
  118 +
133 // ========== 各种创建 spring-ai 客户端的方法 ========== 119 // ========== 各种创建 spring-ai 客户端的方法 ==========
134 120
135 /** 121 /**
@@ -182,7 +168,6 @@ public class AiClientFactoryImpl implements AiClientFactory { @@ -182,7 +168,6 @@ public class AiClientFactoryImpl implements AiClientFactory {
182 return new QianWenChatClient(qianWenApi); 168 return new QianWenChatClient(qianWenApi);
183 } 169 }
184 170
185 -  
186 // private static VertexAiGeminiChatClient buildGoogleGemir(String key) { 171 // private static VertexAiGeminiChatClient buildGoogleGemir(String key) {
187 // List<String> keys = StrUtil.split(key, '|'); 172 // List<String> keys = StrUtil.split(key, '|');
188 // Assert.equals(keys.size(), 2, "VertexAiGeminiChatClient 的密钥需要 (projectId|location) 格式"); 173 // Assert.equals(keys.size(), 2, "VertexAiGeminiChatClient 的密钥需要 (projectId|location) 格式");
@@ -190,4 +175,16 @@ public class AiClientFactoryImpl implements AiClientFactory { @@ -190,4 +175,16 @@ public class AiClientFactoryImpl implements AiClientFactory {
190 // return new VertexAiGeminiChatClient(vertexApi); 175 // return new VertexAiGeminiChatClient(vertexApi);
191 // } 176 // }
192 177
  178 + private ImageClient buildOpenAiImageClient(String openAiToken, String url) {
  179 + url = StrUtil.blankToDefault(url, ApiUtils.DEFAULT_BASE_URL);
  180 + OpenAiImageApi openAiApi = new OpenAiImageApi(url, openAiToken, RestClient.builder());
  181 + return new OpenAiImageClient(openAiApi);
  182 + }
  183 +
  184 + private ImageClient buildStabilityAiImageClient(String apiKey, String url) {
  185 + url = StrUtil.blankToDefault(url, StabilityAiApi.DEFAULT_BASE_URL);
  186 + StabilityAiApi stabilityAiApi = new StabilityAiApi(apiKey, StabilityAiApi.DEFAULT_IMAGE_MODEL, url);
  187 + return new StabilityAiImageClient(stabilityAiApi);
  188 + }
  189 +
193 } 190 }
yudao-server/src/main/resources/application.yaml
@@ -161,7 +161,6 @@ spring: @@ -161,7 +161,6 @@ spring:
161 project-id: 1 # TODO 芋艿:缺配置 161 project-id: 1 # TODO 芋艿:缺配置
162 location: 2 162 location: 2
163 163
164 -  
165 yudao.ai: 164 yudao.ai:
166 yiyan: 165 yiyan:
167 enable: true 166 enable: true
@@ -193,11 +192,6 @@ yudao.ai: @@ -193,11 +192,6 @@ yudao.ai:
193 topP: 0.8 192 topP: 0.8
194 topK: 0 193 topK: 0
195 api-key: sk-Zsd81gZYg7 194 api-key: sk-Zsd81gZYg7
196 - openAiImage:  
197 - enable: true  
198 - api-key: ${OPEN_AI_KEY}  
199 - model: dall_e_2  
200 - style: vivid  
201 midjourney: 195 midjourney:
202 enable: true 196 enable: true
203 token: MTE4MjE3MjY2MjkxNTY3ODIzOA.GEV1SG.c49F8lZoGCUHwsj8O0UdodmM6nyQHvuD2fXflw 197 token: MTE4MjE3MjY2MjkxNTY3ODIzOA.GEV1SG.c49F8lZoGCUHwsj8O0UdodmM6nyQHvuD2fXflw
@@ -206,6 +200,7 @@ yudao.ai: @@ -206,6 +200,7 @@ yudao.ai:
206 suno: 200 suno:
207 enable: true 201 enable: true
208 token: 16b4356581984d538652354b60d69ff0 202 token: 16b4356581984d538652354b60d69ff0
  203 +
209 --- #################### 芋道相关配置 #################### 204 --- #################### 芋道相关配置 ####################
210 205
211 yudao: 206 yudao: