Commit 2ae90b9edc327db77e77d9aaa7e15cfbf62853b1

Authored by cherishsince
2 parents d865cc29 68ed8cd6

Merge remote-tracking branch 'origin/master-jdk21-ai' into master-jdk21-ai

Showing 15 changed files with 209 additions and 145 deletions
script/idea/http-client.env.json
1 { 1 {
2 "local": { 2 "local": {
3 "baseUrl": "http://127.0.0.1:48080/admin-api", 3 "baseUrl": "http://127.0.0.1:48080/admin-api",
4 - "token": "Bearer 1c2ce60de96a4fb0bf5bea9604099a3d", 4 + "token": "test1",
5 "adminTenentId": "1", 5 "adminTenentId": "1",
6 6
7 "appApi": "http://127.0.0.1:48080/app-api", 7 "appApi": "http://127.0.0.1:48080/app-api",
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/AiChatRoleEnum.java
@@ -39,6 +39,7 @@ public enum AiChatRoleEnum implements IntArrayValuable { @@ -39,6 +39,7 @@ public enum AiChatRoleEnum implements IntArrayValuable {
39 除此之外不要任何解释性语句。 39 除此之外不要任何解释性语句。
40 """); 40 """);
41 41
  42 + // TODO @xin:这个 role 是不是删除掉好点哈。= = 目前主要是没做角色枚举。这里多了 role 反倒容易误解哈
42 /** 43 /**
43 * 角色 44 * 角色
44 */ 45 */
yudao-module-ai/yudao-module-ai-biz/pom.xml
@@ -60,9 +60,5 @@ @@ -60,9 +60,5 @@
60 <groupId>cn.iocoder.boot</groupId> 60 <groupId>cn.iocoder.boot</groupId>
61 <artifactId>yudao-spring-boot-starter-test</artifactId> 61 <artifactId>yudao-spring-boot-starter-test</artifactId>
62 </dependency> 62 </dependency>
63 - <dependency>  
64 - <groupId>cn.iocoder.boot</groupId>  
65 - <artifactId>yudao-spring-boot-starter-excel</artifactId>  
66 - </dependency>  
67 </dependencies> 63 </dependencies>
68 </project> 64 </project>
69 \ No newline at end of file 65 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/mindmap/AiMindMapDO.java
@@ -12,8 +12,7 @@ import lombok.Data; @@ -12,8 +12,7 @@ import lombok.Data;
12 * 12 *
13 * @author xiaoxin 13 * @author xiaoxin
14 */ 14 */
15 -// TODO @xin:如果没 typehandler 的需求,autoResultMap 可以去掉哈  
16 -@TableName(value = "ai_mind_map", autoResultMap = true) 15 +@TableName(value = "ai_mind_map")
17 @Data 16 @Data
18 public class AiMindMapDO extends BaseDO { 17 public class AiMindMapDO extends BaseDO {
19 18
@@ -25,7 +24,7 @@ public class AiMindMapDO extends BaseDO { @@ -25,7 +24,7 @@ public class AiMindMapDO extends BaseDO {
25 24
26 /** 25 /**
27 * 用户编号 26 * 用户编号
28 - * 27 + * <p>
29 * 关联 AdminUserDO 的 userId 字段 28 * 关联 AdminUserDO 的 userId 字段
30 */ 29 */
31 private Long userId; 30 private Long userId;
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/mindmap/AiMindMapMapper.java
@@ -5,7 +5,7 @@ import cn.iocoder.yudao.module.ai.dal.dataobject.mindmap.AiMindMapDO; @@ -5,7 +5,7 @@ import cn.iocoder.yudao.module.ai.dal.dataobject.mindmap.AiMindMapDO;
5 import org.apache.ibatis.annotations.Mapper; 5 import org.apache.ibatis.annotations.Mapper;
6 6
7 /** 7 /**
8 - * AI 音乐 Mapper 8 + * AI 思维导图 Mapper
9 * 9 *
10 * @author xiaoxin 10 * @author xiaoxin
11 */ 11 */
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java
@@ -111,7 +111,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { @@ -111,7 +111,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
111 AiChatMessageDO assistantMessage = createChatMessage(conversation.getId(), userMessage.getId(), model, 111 AiChatMessageDO assistantMessage = createChatMessage(conversation.getId(), userMessage.getId(), model,
112 userId, conversation.getRoleId(), MessageType.ASSISTANT, "", sendReqVO.getUseContext()); 112 userId, conversation.getRoleId(), MessageType.ASSISTANT, "", sendReqVO.getUseContext());
113 113
114 - // 3.2 创建 chat 需要的 Prompt 114 + // 3.2 构建 Prompt,并进行调用
115 Prompt prompt = buildPrompt(conversation, historyMessages, model, sendReqVO); 115 Prompt prompt = buildPrompt(conversation, historyMessages, model, sendReqVO);
116 Flux<ChatResponse> streamResponse = chatModel.stream(prompt); 116 Flux<ChatResponse> streamResponse = chatModel.stream(prompt);
117 117
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java
@@ -31,6 +31,7 @@ import org.springframework.ai.image.ImageOptions; @@ -31,6 +31,7 @@ import org.springframework.ai.image.ImageOptions;
31 import org.springframework.ai.image.ImagePrompt; 31 import org.springframework.ai.image.ImagePrompt;
32 import org.springframework.ai.image.ImageResponse; 32 import org.springframework.ai.image.ImageResponse;
33 import org.springframework.ai.openai.OpenAiImageOptions; 33 import org.springframework.ai.openai.OpenAiImageOptions;
  34 +import org.springframework.ai.qianfan.QianFanImageOptions;
34 import org.springframework.ai.stabilityai.api.StabilityAiImageOptions; 35 import org.springframework.ai.stabilityai.api.StabilityAiImageOptions;
35 import org.springframework.scheduling.annotation.Async; 36 import org.springframework.scheduling.annotation.Async;
36 import org.springframework.stereotype.Service; 37 import org.springframework.stereotype.Service;
@@ -142,6 +143,11 @@ public class AiImageServiceImpl implements AiImageService { @@ -142,6 +143,11 @@ public class AiImageServiceImpl implements AiImageService {
142 .withModel(draw.getModel()).withN(1) 143 .withModel(draw.getModel()).withN(1)
143 .withHeight(draw.getHeight()).withWidth(draw.getWidth()) 144 .withHeight(draw.getHeight()).withWidth(draw.getWidth())
144 .build(); 145 .build();
  146 + } else if (ObjUtil.equal(draw.getPlatform(), AiPlatformEnum.YI_YAN.getPlatform())) {
  147 + return QianFanImageOptions.builder()
  148 + .withModel(draw.getModel()).withN(1)
  149 + .withHeight(draw.getHeight()).withWidth(draw.getWidth())
  150 + .build();
145 } 151 }
146 throw new IllegalArgumentException("不支持的 AI 平台:" + draw.getPlatform()); 152 throw new IllegalArgumentException("不支持的 AI 平台:" + draw.getPlatform());
147 } 153 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/mindmap/AiMindMapServiceImpl.java
1 package cn.iocoder.yudao.module.ai.service.mindmap; 1 package cn.iocoder.yudao.module.ai.service.mindmap;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.lang.Assert;
4 import cn.hutool.core.util.StrUtil; 5 import cn.hutool.core.util.StrUtil;
5 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; 6 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
6 import cn.iocoder.yudao.framework.ai.core.util.AiUtils; 7 import cn.iocoder.yudao.framework.ai.core.util.AiUtils;
@@ -31,13 +32,12 @@ import reactor.core.publisher.Flux; @@ -31,13 +32,12 @@ import reactor.core.publisher.Flux;
31 32
32 import java.util.ArrayList; 33 import java.util.ArrayList;
33 import java.util.List; 34 import java.util.List;
34 -import java.util.Objects;  
35 35
36 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.error; 36 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.error;
37 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 37 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
38 38
39 /** 39 /**
40 - * AI 写作 Service 实现类 40 + * AI 思维导图 Service 实现类
41 * 41 *
42 * @author xiaoxin 42 * @author xiaoxin
43 */ 43 */
@@ -57,38 +57,28 @@ public class AiMindMapServiceImpl implements AiMindMapService { @@ -57,38 +57,28 @@ public class AiMindMapServiceImpl implements AiMindMapService {
57 57
58 @Override 58 @Override
59 public Flux<CommonResult<String>> generateMindMap(AiMindMapGenerateReqVO generateReqVO, Long userId) { 59 public Flux<CommonResult<String>> generateMindMap(AiMindMapGenerateReqVO generateReqVO, Long userId) {
60 - // 1.1 获取脑图模型 尝试获取思维导图助手角色,如果没有则使用默认模型  
61 - AiChatRoleDO mindMapRole = CollUtil.getFirst(chatRoleService.getChatRoleListByName(AiChatRoleEnum.AI_MIND_MAP_ROLE.getName()));  
62 - AiChatModelDO model;  
63 - String systemMessage;  
64 - if (Objects.nonNull(mindMapRole) && Objects.nonNull(mindMapRole.getModelId())) {  
65 - model = chatModalService.getChatModel(mindMapRole.getModelId());  
66 - systemMessage = mindMapRole.getSystemMessage();  
67 - } else {  
68 - model = chatModalService.getRequiredDefaultChatModel();  
69 - systemMessage = AiChatRoleEnum.AI_MIND_MAP_ROLE.getSystemMessage();  
70 - }  
71 - 60 + // 1. 获取脑图模型。尝试获取思维导图助手角色,如果没有则使用默认模型
  61 + AiChatRoleDO role = CollUtil.getFirst(
  62 + chatRoleService.getChatRoleListByName(AiChatRoleEnum.AI_MIND_MAP_ROLE.getName()));
  63 + // 1.1 获取脑图执行模型
  64 + AiChatModelDO model = getModel(role);
  65 + // 1.2 获取角色设定消息
  66 + String systemMessage = role != null && StrUtil.isNotBlank(role.getSystemMessage())
  67 + ? role.getSystemMessage() : AiChatRoleEnum.AI_MIND_MAP_ROLE.getSystemMessage();
  68 + // 1.3 校验平台
72 AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform()); 69 AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform());
73 ChatModel chatModel = apiKeyService.getChatModel(model.getKeyId()); 70 ChatModel chatModel = apiKeyService.getChatModel(model.getKeyId());
74 71
75 - // 2 插入思维导图信息  
76 - AiMindMapDO mindMapDO = BeanUtils.toBean(generateReqVO, AiMindMapDO.class, e -> e.setUserId(userId).setModel(model.getModel()).setPlatform(platform.getPlatform())); 72 + // 2. 插入思维导图信息
  73 + AiMindMapDO mindMapDO = BeanUtils.toBean(generateReqVO, AiMindMapDO.class,
  74 + mindMap -> mindMap.setUserId(userId).setModel(model.getModel()).setPlatform(platform.getPlatform()));
77 mindMapMapper.insert(mindMapDO); 75 mindMapMapper.insert(mindMapDO);
78 76
79 - ChatOptions chatOptions = AiUtils.buildChatOptions(platform, model.getModel(), model.getTemperature(), model.getMaxTokens());  
80 - // 3.1 角色设定  
81 - List<Message> chatMessages = new ArrayList<>();  
82 - if (StrUtil.isNotBlank(systemMessage)) {  
83 - chatMessages.add(new SystemMessage(systemMessage));  
84 - }  
85 - // 3.2 用户输入  
86 - chatMessages.add(new UserMessage(generateReqVO.getPrompt()));  
87 - // 3.3 构建提示词  
88 - Prompt prompt = new Prompt(chatMessages, chatOptions);  
89 - 77 + // 3.1 构建 Prompt,并进行调用
  78 + Prompt prompt = buildPrompt(generateReqVO, model, systemMessage);
90 Flux<ChatResponse> streamResponse = chatModel.stream(prompt); 79 Flux<ChatResponse> streamResponse = chatModel.stream(prompt);
91 - // 3.4 流式返回 80 +
  81 + // 3.2 流式返回
92 StringBuffer contentBuffer = new StringBuffer(); 82 StringBuffer contentBuffer = new StringBuffer();
93 return streamResponse.map(chunk -> { 83 return streamResponse.map(chunk -> {
94 String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getContent() : null; 84 String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getContent() : null;
@@ -109,4 +99,36 @@ public class AiMindMapServiceImpl implements AiMindMapService { @@ -109,4 +99,36 @@ public class AiMindMapServiceImpl implements AiMindMapService {
109 99
110 } 100 }
111 101
  102 + private Prompt buildPrompt(AiMindMapGenerateReqVO generateReqVO, AiChatModelDO model, String systemMessage) {
  103 + // 1. 构建 message 列表
  104 + List<Message> chatMessages = buildMessages(generateReqVO, systemMessage);
  105 + // 2. 构建 options 对象
  106 + AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform());
  107 + ChatOptions options = AiUtils.buildChatOptions(platform, model.getModel(), model.getTemperature(), model.getMaxTokens());
  108 + return new Prompt(chatMessages, options);
  109 + }
  110 +
  111 + private static List<Message> buildMessages(AiMindMapGenerateReqVO generateReqVO, String systemMessage) {
  112 + List<Message> chatMessages = new ArrayList<>();
  113 + // 1. 角色设定
  114 + if (StrUtil.isNotBlank(systemMessage)) {
  115 + chatMessages.add(new SystemMessage(systemMessage));
  116 + }
  117 + // 2. 用户输入
  118 + chatMessages.add(new UserMessage(generateReqVO.getPrompt()));
  119 + return chatMessages;
  120 + }
  121 +
  122 + private AiChatModelDO getModel(AiChatRoleDO role) {
  123 + AiChatModelDO model = null;
  124 + if (role != null && role.getModelId() != null) {
  125 + model = chatModalService.getChatModel(role.getModelId());
  126 + }
  127 + if (model != null) {
  128 + model = chatModalService.getRequiredDefaultChatModel();
  129 + }
  130 + Assert.notNull(model, "[AI] 获取不到模型");
  131 + return model;
  132 + }
  133 +
112 } 134 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/write/AiWriteServiceImpl.java
1 package cn.iocoder.yudao.module.ai.service.write; 1 package cn.iocoder.yudao.module.ai.service.write;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.lang.Assert;
4 import cn.hutool.core.util.StrUtil; 5 import cn.hutool.core.util.StrUtil;
5 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; 6 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
6 import cn.iocoder.yudao.framework.ai.core.util.AiUtils; 7 import cn.iocoder.yudao.framework.ai.core.util.AiUtils;
@@ -67,19 +68,15 @@ public class AiWriteServiceImpl implements AiWriteService { @@ -67,19 +68,15 @@ public class AiWriteServiceImpl implements AiWriteService {
67 68
68 @Override 69 @Override
69 public Flux<CommonResult<String>> generateWriteContent(AiWriteGenerateReqVO generateReqVO, Long userId) { 70 public Flux<CommonResult<String>> generateWriteContent(AiWriteGenerateReqVO generateReqVO, Long userId) {
70 - // 1.1 获取写作模型 尝试获取写作助手角色,如果没有则使用默认模型  
71 - AiChatRoleDO writeRole = CollUtil.getFirst(chatRoleService.getChatRoleListByName(AiChatRoleEnum.AI_WRITE_ROLE.getName()));  
72 - // TODO @xin:如果有 writeRole,但是没 modeId,是不是也可以用 systemMessage 哈?建议的写法是:先通过 modelId 获取 model。如果 model == null,则 chatModalService.getRequiredDefaultChatModel();如果还是 null,则抛出异常;。。。。。。。。。。。。。。然后,systemMessage = writeRole != null && writeRole.systemPrompt != "" 这样处理。  
73 - AiChatModelDO model;  
74 - String systemMessage;  
75 - if (Objects.nonNull(writeRole) && Objects.nonNull(writeRole.getModelId())) {  
76 - model = chatModalService.getChatModel(writeRole.getModelId());  
77 - systemMessage = writeRole.getSystemMessage();  
78 - } else {  
79 - model = chatModalService.getRequiredDefaultChatModel();  
80 - systemMessage = AiChatRoleEnum.AI_WRITE_ROLE.getSystemMessage();  
81 - }  
82 - // 1.2 校验平台 71 + // 1 获取写作模型。尝试获取写作助手角色,没有则使用默认模型
  72 + AiChatRoleDO writeRole = CollUtil.getFirst(
  73 + chatRoleService.getChatRoleListByName(AiChatRoleEnum.AI_WRITE_ROLE.getName()));
  74 + // 1.1 获取写作执行模型
  75 + AiChatModelDO model = getModel(writeRole);
  76 + // 1.2 获取角色设定消息
  77 + String systemMessage = Objects.nonNull(writeRole) && StrUtil.isNotBlank(writeRole.getSystemMessage())
  78 + ? writeRole.getSystemMessage() : AiChatRoleEnum.AI_WRITE_ROLE.getSystemMessage();
  79 + // 1.3 校验平台
83 AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform()); 80 AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform());
84 StreamingChatModel chatModel = apiKeyService.getChatModel(model.getKeyId()); 81 StreamingChatModel chatModel = apiKeyService.getChatModel(model.getKeyId());
85 82
@@ -88,21 +85,11 @@ public class AiWriteServiceImpl implements AiWriteService { @@ -88,21 +85,11 @@ public class AiWriteServiceImpl implements AiWriteService {
88 write -> write.setUserId(userId).setPlatform(platform.getPlatform()).setModel(model.getModel())); 85 write -> write.setUserId(userId).setPlatform(platform.getPlatform()).setModel(model.getModel()));
89 writeMapper.insert(writeDO); 86 writeMapper.insert(writeDO);
90 87
91 - // 3. 调用大模型,写作生成  
92 - ChatOptions chatOptions = AiUtils.buildChatOptions(platform, model.getModel(), model.getTemperature(), model.getMaxTokens());  
93 - // 3.1 角色设定  
94 - // TODO @xin:要不把 90 到 97 这部分,合并到一个方法里。目的是:让这个方法的主干更明确  
95 - List<Message> chatMessages = new ArrayList<>();  
96 - if (StrUtil.isNotBlank(systemMessage)) {  
97 - chatMessages.add(new SystemMessage(systemMessage));  
98 - }  
99 - // 3.2 用户输入  
100 - chatMessages.add(new UserMessage(buildWritingPrompt(generateReqVO)));  
101 - // 3.3 构建提示词  
102 - Prompt prompt = new Prompt(chatMessages, chatOptions); 88 + // 3.1 构建 Prompt,并进行调用
  89 + Prompt prompt = buildPrompt(generateReqVO, model, systemMessage);
103 Flux<ChatResponse> streamResponse = chatModel.stream(prompt); 90 Flux<ChatResponse> streamResponse = chatModel.stream(prompt);
104 91
105 - // 4. 流式返回 92 + // 3.2 流式返回
106 StringBuffer contentBuffer = new StringBuffer(); 93 StringBuffer contentBuffer = new StringBuffer();
107 return streamResponse.map(chunk -> { 94 return streamResponse.map(chunk -> {
108 String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getContent() : null; 95 String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getContent() : null;
@@ -122,7 +109,39 @@ public class AiWriteServiceImpl implements AiWriteService { @@ -122,7 +109,39 @@ public class AiWriteServiceImpl implements AiWriteService {
122 }).onErrorResume(error -> Flux.just(error(ErrorCodeConstants.WRITE_STREAM_ERROR))); 109 }).onErrorResume(error -> Flux.just(error(ErrorCodeConstants.WRITE_STREAM_ERROR)));
123 } 110 }
124 111
125 - private String buildWritingPrompt(AiWriteGenerateReqVO generateReqVO) { 112 + private AiChatModelDO getModel(AiChatRoleDO writeRole) {
  113 + AiChatModelDO model = null;
  114 + if (Objects.nonNull(writeRole) && Objects.nonNull(writeRole.getModelId())) {
  115 + model = chatModalService.getChatModel(writeRole.getModelId());
  116 + }
  117 + if (Objects.isNull(model)) {
  118 + model = chatModalService.getRequiredDefaultChatModel();
  119 + }
  120 + Assert.notNull(model, "[AI] 获取不到模型");
  121 + return model;
  122 + }
  123 +
  124 + private Prompt buildPrompt(AiWriteGenerateReqVO generateReqVO, AiChatModelDO model, String systemMessage) {
  125 + // 1. 构建 message 列表
  126 + List<Message> chatMessages = buildMessages(generateReqVO, systemMessage);
  127 + // 2. 构建 options 对象
  128 + AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform());
  129 + ChatOptions options = AiUtils.buildChatOptions(platform, model.getModel(), model.getTemperature(), model.getMaxTokens());
  130 + return new Prompt(chatMessages, options);
  131 + }
  132 +
  133 + private List<Message> buildMessages(AiWriteGenerateReqVO generateReqVO, String systemMessage) {
  134 + List<Message> chatMessages = new ArrayList<>();
  135 + if (StrUtil.isNotBlank(systemMessage)) {
  136 + // 1.1 角色设定
  137 + chatMessages.add(new SystemMessage(systemMessage));
  138 + }
  139 + // 1.2 用户输入
  140 + chatMessages.add(new UserMessage(buildUserMessage(generateReqVO)));
  141 + return chatMessages;
  142 + }
  143 +
  144 + private String buildUserMessage(AiWriteGenerateReqVO generateReqVO) {
126 String format = dictDataApi.getDictDataLabel(DictTypeConstants.AI_WRITE_FORMAT, generateReqVO.getFormat()); 145 String format = dictDataApi.getDictDataLabel(DictTypeConstants.AI_WRITE_FORMAT, generateReqVO.getFormat());
127 String tone = dictDataApi.getDictDataLabel(DictTypeConstants.AI_WRITE_TONE, generateReqVO.getTone()); 146 String tone = dictDataApi.getDictDataLabel(DictTypeConstants.AI_WRITE_TONE, generateReqVO.getTone());
128 String language = dictDataApi.getDictDataLabel(DictTypeConstants.AI_WRITE_LANGUAGE, generateReqVO.getLanguage()); 147 String language = dictDataApi.getDictDataLabel(DictTypeConstants.AI_WRITE_LANGUAGE, generateReqVO.getLanguage());
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java
@@ -18,12 +18,15 @@ import com.alibaba.cloud.ai.tongyi.TongYiConnectionProperties; @@ -18,12 +18,15 @@ import com.alibaba.cloud.ai.tongyi.TongYiConnectionProperties;
18 import com.alibaba.cloud.ai.tongyi.chat.TongYiChatModel; 18 import com.alibaba.cloud.ai.tongyi.chat.TongYiChatModel;
19 import com.alibaba.cloud.ai.tongyi.chat.TongYiChatProperties; 19 import com.alibaba.cloud.ai.tongyi.chat.TongYiChatProperties;
20 import com.alibaba.cloud.ai.tongyi.image.TongYiImagesModel; 20 import com.alibaba.cloud.ai.tongyi.image.TongYiImagesModel;
  21 +import com.alibaba.cloud.ai.tongyi.image.TongYiImagesProperties;
21 import com.alibaba.dashscope.aigc.generation.Generation; 22 import com.alibaba.dashscope.aigc.generation.Generation;
  23 +import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesis;
22 import org.springframework.ai.autoconfigure.ollama.OllamaAutoConfiguration; 24 import org.springframework.ai.autoconfigure.ollama.OllamaAutoConfiguration;
23 import org.springframework.ai.autoconfigure.openai.OpenAiAutoConfiguration; 25 import org.springframework.ai.autoconfigure.openai.OpenAiAutoConfiguration;
24 import org.springframework.ai.autoconfigure.qianfan.QianFanAutoConfiguration; 26 import org.springframework.ai.autoconfigure.qianfan.QianFanAutoConfiguration;
25 import org.springframework.ai.autoconfigure.qianfan.QianFanChatProperties; 27 import org.springframework.ai.autoconfigure.qianfan.QianFanChatProperties;
26 import org.springframework.ai.autoconfigure.qianfan.QianFanConnectionProperties; 28 import org.springframework.ai.autoconfigure.qianfan.QianFanConnectionProperties;
  29 +import org.springframework.ai.autoconfigure.qianfan.QianFanImageProperties;
27 import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiAutoConfiguration; 30 import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiAutoConfiguration;
28 import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiChatProperties; 31 import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiChatProperties;
29 import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiConnectionProperties; 32 import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiConnectionProperties;
@@ -111,6 +114,10 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -111,6 +114,10 @@ public class AiModelFactoryImpl implements AiModelFactory {
111 public ImageModel getDefaultImageModel(AiPlatformEnum platform) { 114 public ImageModel getDefaultImageModel(AiPlatformEnum platform) {
112 //noinspection EnhancedSwitchMigration 115 //noinspection EnhancedSwitchMigration
113 switch (platform) { 116 switch (platform) {
  117 + case TONG_YI:
  118 + return SpringUtil.getBean(TongYiImagesModel.class);
  119 + case YI_YAN:
  120 + return SpringUtil.getBean(QianFanImageModel.class);
114 case OPENAI: 121 case OPENAI:
115 return SpringUtil.getBean(OpenAiImageModel.class); 122 return SpringUtil.getBean(OpenAiImageModel.class);
116 case STABLE_DIFFUSION: 123 case STABLE_DIFFUSION:
@@ -124,14 +131,14 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -124,14 +131,14 @@ public class AiModelFactoryImpl implements AiModelFactory {
124 public ImageModel getOrCreateImageModel(AiPlatformEnum platform, String apiKey, String url) { 131 public ImageModel getOrCreateImageModel(AiPlatformEnum platform, String apiKey, String url) {
125 //noinspection EnhancedSwitchMigration 132 //noinspection EnhancedSwitchMigration
126 switch (platform) { 133 switch (platform) {
  134 + case TONG_YI:
  135 + return buildTongYiImagesModel(apiKey);
  136 + case YI_YAN:
  137 + return buildQianFanImageModel(apiKey);
127 case OPENAI: 138 case OPENAI:
128 return buildOpenAiImageModel(apiKey, url); 139 return buildOpenAiImageModel(apiKey, url);
129 case STABLE_DIFFUSION: 140 case STABLE_DIFFUSION:
130 return buildStabilityAiImageModel(apiKey, url); 141 return buildStabilityAiImageModel(apiKey, url);
131 - case TONG_YI:  
132 - return SpringUtil.getBean(TongYiImagesModel.class);  
133 - case YI_YAN:  
134 - return buildQianFanImageModel(apiKey);  
135 default: 142 default:
136 throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform)); 143 throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform));
137 } 144 }
@@ -175,6 +182,14 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -175,6 +182,14 @@ public class AiModelFactoryImpl implements AiModelFactory {
175 return new TongYiAutoConfiguration().tongYiChatClient(generation, chatOptions, connectionProperties); 182 return new TongYiAutoConfiguration().tongYiChatClient(generation, chatOptions, connectionProperties);
176 } 183 }
177 184
  185 + private static TongYiImagesModel buildTongYiImagesModel(String key) {
  186 + ImageSynthesis imageSynthesis = SpringUtil.getBean(ImageSynthesis.class);
  187 + TongYiImagesProperties imagesOptions = SpringUtil.getBean(TongYiImagesProperties.class);
  188 + TongYiConnectionProperties connectionProperties = new TongYiConnectionProperties();
  189 + connectionProperties.setApiKey(key);
  190 + return new TongYiAutoConfiguration().tongYiImagesClient(imageSynthesis, imagesOptions, connectionProperties);
  191 + }
  192 +
178 /** 193 /**
179 * 可参考 {@link QianFanAutoConfiguration#qianFanChatModel(QianFanConnectionProperties, QianFanChatProperties, RestClient.Builder, RetryTemplate, ResponseErrorHandler)} 194 * 可参考 {@link QianFanAutoConfiguration#qianFanChatModel(QianFanConnectionProperties, QianFanChatProperties, RestClient.Builder, RetryTemplate, ResponseErrorHandler)}
180 */ 195 */
@@ -188,6 +203,18 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -188,6 +203,18 @@ public class AiModelFactoryImpl implements AiModelFactory {
188 } 203 }
189 204
190 /** 205 /**
  206 + * 可参考 {@link QianFanAutoConfiguration#qianFanImageModel(QianFanConnectionProperties, QianFanImageProperties, RestClient.Builder, RetryTemplate, ResponseErrorHandler)}
  207 + */
  208 + private QianFanImageModel buildQianFanImageModel(String key) {
  209 + List<String> keys = StrUtil.split(key, '|');
  210 + Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式");
  211 + String appKey = keys.get(0);
  212 + String secretKey = keys.get(1);
  213 + QianFanImageApi qianFanApi = new QianFanImageApi(appKey, secretKey);
  214 + return new QianFanImageModel(qianFanApi);
  215 + }
  216 +
  217 + /**
191 * 可参考 {@link YudaoAiAutoConfiguration#deepSeekChatModel(YudaoAiProperties)} 218 * 可参考 {@link YudaoAiAutoConfiguration#deepSeekChatModel(YudaoAiProperties)}
192 */ 219 */
193 private static DeepSeekChatModel buildDeepSeekChatModel(String apiKey) { 220 private static DeepSeekChatModel buildDeepSeekChatModel(String apiKey) {
@@ -246,8 +273,4 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -246,8 +273,4 @@ public class AiModelFactoryImpl implements AiModelFactory {
246 return new StabilityAiImageModel(stabilityAiApi); 273 return new StabilityAiImageModel(stabilityAiApi);
247 } 274 }
248 275
249 - private QianFanImageModel buildQianFanImageModel(String key) {  
250 - List<String> keys = StrUtil.split(key, '|');  
251 - return new QianFanImageModel(new QianFanImageApi(keys.get(0), keys.get(1)));  
252 - }  
253 } 276 }
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/image/OpenAiImageModelTests.java
@@ -21,7 +21,7 @@ public class OpenAiImageModelTests { @@ -21,7 +21,7 @@ public class OpenAiImageModelTests {
21 "https://api.holdai.top", 21 "https://api.holdai.top",
22 "sk-dZEPiVaNcT3FHhef51996bAa0bC74806BeAb620dA5Da10Bf", 22 "sk-dZEPiVaNcT3FHhef51996bAa0bC74806BeAb620dA5Da10Bf",
23 RestClient.builder()); 23 RestClient.builder());
24 - private final OpenAiImageModel imageClient = new OpenAiImageModel(imageApi); 24 + private final OpenAiImageModel imageModel = new OpenAiImageModel(imageApi);
25 25
26 @Test 26 @Test
27 @Disabled 27 @Disabled
@@ -34,7 +34,7 @@ public class OpenAiImageModelTests { @@ -34,7 +34,7 @@ public class OpenAiImageModelTests {
34 ImagePrompt prompt = new ImagePrompt("中国长城!", options); 34 ImagePrompt prompt = new ImagePrompt("中国长城!", options);
35 35
36 // 方法调用 36 // 方法调用
37 - ImageResponse response = imageClient.call(prompt); 37 + ImageResponse response = imageModel.call(prompt);
38 // 打印结果 38 // 打印结果
39 System.out.println(response); 39 System.out.println(response);
40 } 40 }
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/image/QianFanImageTests.java
1 package cn.iocoder.yudao.framework.ai.image; 1 package cn.iocoder.yudao.framework.ai.image;
2 2
3 -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 3 +import org.junit.jupiter.api.Disabled;
4 import org.junit.jupiter.api.Test; 4 import org.junit.jupiter.api.Test;
5 -import org.springframework.ai.image.ImageOptionsBuilder;  
6 import org.springframework.ai.image.ImagePrompt; 5 import org.springframework.ai.image.ImagePrompt;
7 import org.springframework.ai.image.ImageResponse; 6 import org.springframework.ai.image.ImageResponse;
8 import org.springframework.ai.qianfan.QianFanImageModel; 7 import org.springframework.ai.qianfan.QianFanImageModel;
9 import org.springframework.ai.qianfan.QianFanImageOptions; 8 import org.springframework.ai.qianfan.QianFanImageOptions;
10 -import org.springframework.ai.qianfan.api.QianFanApi;  
11 import org.springframework.ai.qianfan.api.QianFanImageApi; 9 import org.springframework.ai.qianfan.api.QianFanImageApi;
12 10
  11 +import static cn.iocoder.yudao.framework.ai.image.StabilityAiImageModelTests.viewImage;
  12 +
13 /** 13 /**
14 - * 百度千帆 image 14 + * {@link QianFanImageModel} 集成测试类
15 */ 15 */
16 public class QianFanImageTests { 16 public class QianFanImageTests {
17 17
18 - @Test  
19 - public void callTest() {  
20 - // todo @芋艿 千帆sdk有个错误,暂时没找到问题  
21 - QianFanImageApi qianFanImageApi = new QianFanImageApi(  
22 - "ghbbvbW2t7HK7WtYmEITAupm", "njJEr5AsQ5fkB3ucYYDjiQqsOZK20SGb");  
23 - QianFanImageModel qianFanImageModel = new QianFanImageModel(qianFanImageApi); 18 + private final QianFanImageApi imageApi = new QianFanImageApi(
  19 + "qS8k8dYr2nXunagK4SSU8Xjj", "pHGbx51ql2f0hOyabQvSZezahVC3hh3e");
  20 + private final QianFanImageModel imageModel = new QianFanImageModel(imageApi);
24 21
  22 + @Test
  23 + @Disabled
  24 + public void testCall() {
  25 + // 准备参数
  26 + // 只支持 1024x1024、768x768、768x1024、1024x768、576x1024、1024x576
25 QianFanImageOptions imageOptions = QianFanImageOptions.builder() 27 QianFanImageOptions imageOptions = QianFanImageOptions.builder()
26 - .withWidth(512)  
27 - .withHeight(512) 28 + .withModel(QianFanImageApi.ImageModel.Stable_Diffusion_XL.getValue())
  29 + .withWidth(1024).withHeight(1024)
  30 + .withN(1)
28 .build(); 31 .build();
29 - ImagePrompt imagePrompt = new ImagePrompt("薄涂炫酷少女头像,田野花朵盛开", imageOptions);  
30 - ImageResponse call = qianFanImageModel.call(imagePrompt);  
31 - System.err.println(JsonUtils.toJsonString(call));  
32 - }  
33 -  
34 - @Test  
35 - public void call2Test() {  
36 - // 官方测试 test https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-qianfan/src/test/java/org/springframework/ai/qianfan/image/QianFanImageModelIT.java  
37 - var options = ImageOptionsBuilder.builder().withHeight(1024).withWidth(1024).build();  
38 - var instructions = "薄涂炫酷少女头像,田野花朵盛开";  
39 -  
40 - ImagePrompt imagePrompt = new ImagePrompt(instructions, options);  
41 -  
42 - QianFanImageApi qianFanImageApi = new QianFanImageApi(  
43 - "ghbbvbW2t7HK7WtYmEITAupm", "njJEr5AsQ5fkB3ucYYDjiQqsOZK20SGb");  
44 - QianFanImageModel imageModel = new QianFanImageModel(qianFanImageApi);  
45 - ImageResponse imageResponse = imageModel.call(imagePrompt); 32 + ImagePrompt prompt = new ImagePrompt("good", imageOptions);
  33 +
  34 + // 方法调用
  35 + ImageResponse response = imageModel.call(prompt);
  36 + // 打印结果
  37 + String b64Json = response.getResult().getOutput().getB64Json();
  38 + System.out.println(response);
  39 + viewImage(b64Json);
46 } 40 }
47 41
48 } 42 }
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/image/StabilityAiImageModelTests.java
@@ -24,7 +24,7 @@ public class StabilityAiImageModelTests { @@ -24,7 +24,7 @@ public class StabilityAiImageModelTests {
24 24
25 private final StabilityAiApi imageApi = new StabilityAiApi( 25 private final StabilityAiApi imageApi = new StabilityAiApi(
26 "sk-e53UqbboF8QJCscYvzJscJxJXoFcFg4iJjl1oqgE7baJETmx"); 26 "sk-e53UqbboF8QJCscYvzJscJxJXoFcFg4iJjl1oqgE7baJETmx");
27 - private final StabilityAiImageModel imageClient = new StabilityAiImageModel(imageApi); 27 + private final StabilityAiImageModel imageModel = new StabilityAiImageModel(imageApi);
28 28
29 @Test 29 @Test
30 @Disabled 30 @Disabled
@@ -37,7 +37,7 @@ public class StabilityAiImageModelTests { @@ -37,7 +37,7 @@ public class StabilityAiImageModelTests {
37 ImagePrompt prompt = new ImagePrompt("great wall", options); 37 ImagePrompt prompt = new ImagePrompt("great wall", options);
38 38
39 // 方法调用 39 // 方法调用
40 - ImageResponse response = imageClient.call(prompt); 40 + ImageResponse response = imageModel.call(prompt);
41 // 打印结果 41 // 打印结果
42 String b64Json = response.getResult().getOutput().getB64Json(); 42 String b64Json = response.getResult().getOutput().getB64Json();
43 System.out.println(response); 43 System.out.println(response);
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/image/TongYiImagesModelTest.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.ai.image;
  2 +
  3 +import com.alibaba.cloud.ai.tongyi.image.TongYiImagesModel;
  4 +import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesis;
  5 +import com.alibaba.dashscope.utils.Constants;
  6 +import org.junit.jupiter.api.Disabled;
  7 +import org.junit.jupiter.api.Test;
  8 +import org.springframework.ai.image.ImageOptions;
  9 +import org.springframework.ai.image.ImagePrompt;
  10 +import org.springframework.ai.image.ImageResponse;
  11 +import org.springframework.ai.openai.OpenAiImageOptions;
  12 +
  13 +/**
  14 + * {@link com.alibaba.cloud.ai.tongyi.image.TongYiImagesModel} 集成测试类
  15 + *
  16 + * @author fansili
  17 + */
  18 +public class TongYiImagesModelTest {
  19 +
  20 + private final ImageSynthesis imageApi = new ImageSynthesis();
  21 + private final TongYiImagesModel imageModel = new TongYiImagesModel(imageApi);
  22 +
  23 + static {
  24 + Constants.apiKey = "sk-Zsd81gZYg7";
  25 + }
  26 +
  27 + @Test
  28 + @Disabled
  29 + public void imageCallTest() {
  30 + // 准备参数
  31 + ImageOptions options = OpenAiImageOptions.builder()
  32 + .withModel(ImageSynthesis.Models.WANX_V1)
  33 + .withHeight(256).withWidth(256)
  34 + .build();
  35 + ImagePrompt prompt = new ImagePrompt("中国长城!", options);
  36 +
  37 + // 方法调用
  38 + ImageResponse response = imageModel.call(prompt);
  39 + // 打印结果
  40 + System.out.println(response);
  41 + }
  42 +
  43 +}
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/image/TongYiImagesModelTests.java deleted
1 -package cn.iocoder.yudao.framework.ai.image;  
2 -  
3 -import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesis;  
4 -import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesisParam;  
5 -import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesisResult;  
6 -import com.alibaba.dashscope.exception.NoApiKeyException;  
7 -import com.alibaba.dashscope.utils.Constants;  
8 -import com.alibaba.fastjson.JSON;  
9 -import org.junit.jupiter.api.Test;  
10 -  
11 -import java.util.Map;  
12 -  
13 -// TODO @fan:改成 TongYiImagesModel 哈  
14 -/**  
15 - * 通义万象  
16 - */  
17 -public class TongYiImagesModelTests {  
18 -  
19 - @Test  
20 - public void imageCallTest() throws NoApiKeyException {  
21 - // 设置 api key  
22 - Constants.apiKey = "sk-Zsd81gZYg7";  
23 - ImageSynthesisParam param =  
24 - ImageSynthesisParam.builder()  
25 - .model(ImageSynthesis.Models.WANX_V1)  
26 - .n(4)  
27 - .size("1024*1024")  
28 - .prompt("雄鹰自由自在的在蓝天白云下飞翔")  
29 - .build();  
30 - // 创建 ImageSynthesis  
31 - ImageSynthesis is = new ImageSynthesis();  
32 - // 调用 call 生成 image  
33 - ImageSynthesisResult call = is.call(param);  
34 - System.err.println(JSON.toJSON(call));  
35 - for (Map<String, String> result : call.getOutput().getResults()) {  
36 - System.err.println("地址: " + result.get("url"));  
37 - }  
38 - }  
39 -}