Commit 4ef55fa921ae3b95c47b606fdf1a7dd6a57194a1

Authored by xiaoxin
2 parents 793aeea2 1093e295

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

# Conflicts:
#	yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/AiMusicController.http
Showing 14 changed files with 86 additions and 110 deletions
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatMessageController.http
1 -  
2 -### chat call  
3 -POST {{baseUrl}}/admin-api/ai/chat/message/send 1 +### 发送消息(段式)
  2 +POST {{baseUrl}}/ai/chat/message/send
4 Content-Type: application/json 3 Content-Type: application/json
5 Authorization: {{token}} 4 Authorization: {{token}}
6 5
7 { 6 {
8 - "conversationId": "1781604279872581649", 7 + "conversationId": "1781604279872581724",
9 "content": "你是 OpenAI 么?" 8 "content": "你是 OpenAI 么?"
10 } 9 }
11 10
12 -  
13 ### 发送消息(流式) 11 ### 发送消息(流式)
14 POST {{baseUrl}}/ai/chat/message/send-stream 12 POST {{baseUrl}}/ai/chat/message/send-stream
15 Content-Type: application/json 13 Content-Type: application/json
@@ -20,11 +18,10 @@ Authorization: {{token}} @@ -20,11 +18,10 @@ Authorization: {{token}}
20 "content": "1+1=?" 18 "content": "1+1=?"
21 } 19 }
22 20
23 -### message list  
24 -GET {{baseUrl}}/admin-api/ai/chat/message/list-by-conversation-id?conversationId=1781604279872581649 21 +### 获得指定对话的消息列表
  22 +GET {{baseUrl}}/ai/chat/message/list-by-conversation-id?conversationId=1781604279872581649
25 Authorization: {{token}} 23 Authorization: {{token}}
26 24
27 -  
28 -### message delete  
29 -DELETE {{baseUrl}}/admin-api/ai/chat/message/delete?id=50  
30 -Authorization: {{token}} 25 +### 删除消息
  26 +DELETE {{baseUrl}}/ai/chat/message/delete?id=50
  27 +Authorization: {{token}}
31 \ No newline at end of file 28 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatMessageController.java
@@ -21,10 +21,10 @@ import io.swagger.v3.oas.annotations.Parameter; @@ -21,10 +21,10 @@ import io.swagger.v3.oas.annotations.Parameter;
21 import io.swagger.v3.oas.annotations.tags.Tag; 21 import io.swagger.v3.oas.annotations.tags.Tag;
22 import jakarta.annotation.Resource; 22 import jakarta.annotation.Resource;
23 import jakarta.annotation.security.PermitAll; 23 import jakarta.annotation.security.PermitAll;
  24 +import jakarta.validation.Valid;
24 import lombok.extern.slf4j.Slf4j; 25 import lombok.extern.slf4j.Slf4j;
25 import org.springframework.http.MediaType; 26 import org.springframework.http.MediaType;
26 import org.springframework.security.access.prepost.PreAuthorize; 27 import org.springframework.security.access.prepost.PreAuthorize;
27 -import org.springframework.validation.annotation.Validated;  
28 import org.springframework.web.bind.annotation.*; 28 import org.springframework.web.bind.annotation.*;
29 import reactor.core.publisher.Flux; 29 import reactor.core.publisher.Flux;
30 30
@@ -51,14 +51,14 @@ public class AiChatMessageController { @@ -51,14 +51,14 @@ public class AiChatMessageController {
51 51
52 @Operation(summary = "发送消息(段式)", description = "一次性返回,响应较慢") 52 @Operation(summary = "发送消息(段式)", description = "一次性返回,响应较慢")
53 @PostMapping("/send") 53 @PostMapping("/send")
54 - public CommonResult<AiChatMessageRespVO> sendMessage(@Validated @RequestBody AiChatMessageSendReqVO sendReqVO) {  
55 - return success(chatMessageService.sendMessage(sendReqVO)); 54 + public CommonResult<AiChatMessageSendRespVO> sendMessage(@Valid @RequestBody AiChatMessageSendReqVO sendReqVO) {
  55 + return success(chatMessageService.sendMessage(sendReqVO, getLoginUserId()));
56 } 56 }
57 57
58 @Operation(summary = "发送消息(流式)", description = "流式返回,响应较快") 58 @Operation(summary = "发送消息(流式)", description = "流式返回,响应较快")
59 @PostMapping(value = "/send-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) 59 @PostMapping(value = "/send-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
60 @PermitAll // 解决 SSE 最终响应的时候,会被 Access Denied 拦截的问题 60 @PermitAll // 解决 SSE 最终响应的时候,会被 Access Denied 拦截的问题
61 - public Flux<CommonResult<AiChatMessageSendRespVO>> sendChatMessageStream(@Validated @RequestBody AiChatMessageSendReqVO sendReqVO) { 61 + public Flux<CommonResult<AiChatMessageSendRespVO>> sendChatMessageStream(@Valid @RequestBody AiChatMessageSendReqVO sendReqVO) {
62 return chatMessageService.sendChatMessageStream(sendReqVO, getLoginUserId()); 62 return chatMessageService.sendChatMessageStream(sendReqVO, getLoginUserId());
63 } 63 }
64 64
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageSendRespVO.java
@@ -31,14 +31,6 @@ public class AiChatMessageSendRespVO { @@ -31,14 +31,6 @@ public class AiChatMessageSendRespVO {
31 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) 31 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
32 private LocalDateTime createTime; 32 private LocalDateTime createTime;
33 33
34 - // ========== 扩展字段 ==========  
35 -  
36 - @Schema(description = "用户头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://iocoder.cn/1.png")  
37 - private String userAvatar;  
38 -  
39 - @Schema(description = "角色头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://iocoder.cn/2.png")  
40 - private String roleAvatar;  
41 -  
42 } 34 }
43 35
44 } 36 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/AiImageController.java
@@ -68,7 +68,7 @@ public class AiImageController { @@ -68,7 +68,7 @@ public class AiImageController {
68 68
69 @Operation(summary = "生成图片") 69 @Operation(summary = "生成图片")
70 @PostMapping("/draw") 70 @PostMapping("/draw")
71 - public CommonResult<Long> drawImage(@Validated @RequestBody AiImageDrawReqVO drawReqVO) { 71 + public CommonResult<Long> drawImage(@Valid @RequestBody AiImageDrawReqVO drawReqVO) {
72 return success(imageService.drawImage(getLoginUserId(), drawReqVO)); 72 return success(imageService.drawImage(getLoginUserId(), drawReqVO));
73 } 73 }
74 74
@@ -84,7 +84,7 @@ public class AiImageController { @@ -84,7 +84,7 @@ public class AiImageController {
84 84
85 @Operation(summary = "【Midjourney】生成图片") 85 @Operation(summary = "【Midjourney】生成图片")
86 @PostMapping("/midjourney/imagine") 86 @PostMapping("/midjourney/imagine")
87 - public CommonResult<Long> midjourneyImagine(@Validated @RequestBody AiMidjourneyImagineReqVO reqVO) { 87 + public CommonResult<Long> midjourneyImagine(@Valid @RequestBody AiMidjourneyImagineReqVO reqVO) {
88 Long imageId = imageService.midjourneyImagine(getLoginUserId(), reqVO); 88 Long imageId = imageService.midjourneyImagine(getLoginUserId(), reqVO);
89 return success(imageId); 89 return success(imageId);
90 } 90 }
@@ -92,14 +92,14 @@ public class AiImageController { @@ -92,14 +92,14 @@ public class AiImageController {
92 @Operation(summary = "【Midjourney】通知图片进展", description = "由 Midjourney Proxy 回调") 92 @Operation(summary = "【Midjourney】通知图片进展", description = "由 Midjourney Proxy 回调")
93 @PostMapping("/midjourney/notify") // 必须是 POST 方法,否则会报错 93 @PostMapping("/midjourney/notify") // 必须是 POST 方法,否则会报错
94 @PermitAll 94 @PermitAll
95 - public CommonResult<Boolean> midjourneyNotify(@Validated @RequestBody MidjourneyApi.Notify notify) { 95 + public CommonResult<Boolean> midjourneyNotify(@Valid @RequestBody MidjourneyApi.Notify notify) {
96 imageService.midjourneyNotify(notify); 96 imageService.midjourneyNotify(notify);
97 return success(true); 97 return success(true);
98 } 98 }
99 99
100 @Operation(summary = "【Midjourney】Action 操作(二次生成图片)", description = "例如说:放大、缩小、U1、U2 等") 100 @Operation(summary = "【Midjourney】Action 操作(二次生成图片)", description = "例如说:放大、缩小、U1、U2 等")
101 @PostMapping("/midjourney/action") 101 @PostMapping("/midjourney/action")
102 - public CommonResult<Long> midjourneyAction(@Validated @RequestBody AiMidjourneyActionReqVO reqVO) { 102 + public CommonResult<Long> midjourneyAction(@Valid @RequestBody AiMidjourneyActionReqVO reqVO) {
103 Long imageId = imageService.midjourneyAction(getLoginUserId(), reqVO); 103 Long imageId = imageService.midjourneyAction(getLoginUserId(), reqVO);
104 return success(imageId); 104 return success(imageId);
105 } 105 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/AiMusicController.http
@@ -6,8 +6,8 @@ Authorization: {{token}} @@ -6,8 +6,8 @@ Authorization: {{token}}
6 { 6 {
7 "platform": "Suno", 7 "platform": "Suno",
8 "generateMode": 2, 8 "generateMode": 2,
9 - "prompt": "周末啦!",  
10 - "model": "chirp-v3-5", 9 + "prompt": "创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。",
  10 + "model": "chirp-v3.5",
11 "tags": ["Happy"], 11 "tags": ["Happy"],
12 "title": "Happy Song" 12 "title": "Happy Song"
13 } 13 }
@@ -20,7 +20,7 @@ Authorization: {{token}} @@ -20,7 +20,7 @@ Authorization: {{token}}
20 { 20 {
21 "platform": "Suno", 21 "platform": "Suno",
22 "generateMode": 1, 22 "generateMode": 1,
23 - "model": "chirp-v3-5",  
24 - "prompt": "今天是星球六,结果是个下雨天,希望心情很美丽", 23 + "model": "chirp-v3.5",
  24 + "prompt": "happy music",
25 "makeInstrumental": false 25 "makeInstrumental": false
26 } 26 }
27 \ No newline at end of file 27 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/AiMusicController.java
@@ -38,6 +38,10 @@ public class AiMusicController { @@ -38,6 +38,10 @@ public class AiMusicController {
38 @PostMapping("/generate") 38 @PostMapping("/generate")
39 @Operation(summary = "音乐生成") 39 @Operation(summary = "音乐生成")
40 public CommonResult<List<Long>> generateMusic(@RequestBody @Valid AiSunoGenerateReqVO reqVO) { 40 public CommonResult<List<Long>> generateMusic(@RequestBody @Valid AiSunoGenerateReqVO reqVO) {
  41 +// if (true) {
  42 +// musicService.syncMusic();
  43 +// return null;
  44 +// }
41 return success(musicService.generateMusic(getLoginUserId(), reqVO)); 45 return success(musicService.generateMusic(getLoginUserId(), reqVO));
42 } 46 }
43 47
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageService.java
@@ -2,8 +2,9 @@ package cn.iocoder.yudao.module.ai.service.chat; @@ -2,8 +2,9 @@ package cn.iocoder.yudao.module.ai.service.chat;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 import cn.iocoder.yudao.framework.common.pojo.PageResult; 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 -import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationPageReqVO;  
6 -import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.*; 5 +import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessagePageReqVO;
  6 +import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO;
  7 +import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendRespVO;
7 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO; 8 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO;
8 import reactor.core.publisher.Flux; 9 import reactor.core.publisher.Flux;
9 10
@@ -22,9 +23,10 @@ public interface AiChatMessageService { @@ -22,9 +23,10 @@ public interface AiChatMessageService {
22 * 发送消息 23 * 发送消息
23 * 24 *
24 * @param sendReqVO 发送信息 25 * @param sendReqVO 发送信息
  26 + * @param userId 用户编号
25 * @return 发送结果 27 * @return 发送结果
26 */ 28 */
27 - AiChatMessageRespVO sendMessage(AiChatMessageSendReqVO sendReqVO); 29 + AiChatMessageSendRespVO sendMessage(AiChatMessageSendReqVO sendReqVO, Long userId);
28 30
29 /** 31 /**
30 * 发送消息 32 * 发送消息
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java
@@ -10,22 +10,20 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult; @@ -10,22 +10,20 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
10 import cn.iocoder.yudao.framework.common.pojo.PageResult; 10 import cn.iocoder.yudao.framework.common.pojo.PageResult;
11 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 11 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
12 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessagePageReqVO; 12 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;  
14 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO; 13 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO;
15 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendRespVO; 14 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendRespVO;
16 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO; 15 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO;
17 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO; 16 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO;
18 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; 17 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
19 -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;  
20 import cn.iocoder.yudao.module.ai.dal.mysql.chat.AiChatMessageMapper; 18 import cn.iocoder.yudao.module.ai.dal.mysql.chat.AiChatMessageMapper;
21 import cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants; 19 import cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants;
22 import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService; 20 import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService;
23 import cn.iocoder.yudao.module.ai.service.model.AiChatModelService; 21 import cn.iocoder.yudao.module.ai.service.model.AiChatModelService;
24 -import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService;  
25 import com.alibaba.cloud.ai.tongyi.chat.TongYiChatOptions; 22 import com.alibaba.cloud.ai.tongyi.chat.TongYiChatOptions;
26 import jakarta.annotation.Resource; 23 import jakarta.annotation.Resource;
27 import lombok.extern.slf4j.Slf4j; 24 import lombok.extern.slf4j.Slf4j;
28 import org.springframework.ai.chat.messages.*; 25 import org.springframework.ai.chat.messages.*;
  26 +import org.springframework.ai.chat.model.ChatModel;
29 import org.springframework.ai.chat.model.ChatResponse; 27 import org.springframework.ai.chat.model.ChatResponse;
30 import org.springframework.ai.chat.model.StreamingChatModel; 28 import org.springframework.ai.chat.model.StreamingChatModel;
31 import org.springframework.ai.chat.prompt.ChatOptions; 29 import org.springframework.ai.chat.prompt.ChatOptions;
@@ -64,47 +62,37 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { @@ -64,47 +62,37 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
64 @Resource 62 @Resource
65 private AiChatModelService chatModalService; 63 private AiChatModelService chatModalService;
66 @Resource 64 @Resource
67 - private AiChatRoleService chatRoleService;  
68 - @Resource  
69 private AiApiKeyService apiKeyService; 65 private AiApiKeyService apiKeyService;
70 66
71 @Transactional(rollbackFor = Exception.class) 67 @Transactional(rollbackFor = Exception.class)
72 - public AiChatMessageRespVO sendMessage(AiChatMessageSendReqVO req) {  
73 - return null; // TODO 芋艿:一起改  
74 -// Long loginUserId = SecurityFrameworkUtils.getLoginUserId();  
75 -// // 查询对话  
76 -// AiChatConversationDO conversation = chatConversationService.validateExists(req.getConversationId());  
77 -// // 获取对话模型  
78 -// AiChatModelDO chatModel = chatModalService.validateChatModel(conversation.getModelId());  
79 -// // 获取角色信息  
80 -// AiChatRoleDO chatRoleDO = conversation.getRoleId() != null ? chatRoleService.validateChatRole(conversation.getRoleId()) : null;  
81 -// // 获取 client 类型  
82 -// AiPlatformEnum platformEnum = AiPlatformEnum.validatePlatform(chatModel.getPlatform());  
83 -// // 保存 chat message  
84 -// createChatMessage(conversation.getId(), MessageType.USER, loginUserId, conversation.getRoleId(),  
85 -// chatModel.getModel(), chatModel.getId(), req.getContent());  
86 -// String content = null;  
87 -// int tokens = 0;  
88 -// try {  
89 -// // 创建 chat 需要的 Prompt  
90 -// Prompt prompt = new Prompt(req.getContent());  
91 -// // TODO @芋艿 @范 看要不要支持这些  
92 -//// req.setTopK(req.getTopK());  
93 -//// req.setTopP(req.getTopP());  
94 -//// req.setTemperature(req.getTemperature());  
95 -// // 发送 call 调用  
96 -// ChatClient chatClient = chatClientFactory.getChatClient(platformEnum);  
97 -// ChatResponse call = chatClient.call(prompt);  
98 -// content = call.getResult().getOutput().getContent();  
99 -// // 更新 conversation  
100 -// } catch (Exception e) {  
101 -// content = ExceptionUtil.getMessage(e);  
102 -// } finally {  
103 -// // 保存 chat message  
104 -// createChatMessage(conversation.getId(), MessageType.SYSTEM, loginUserId, conversation.getRoleId(),  
105 -// chatModel.getModel(), chatModel.getId(), content);  
106 -// }  
107 -// return new AiChatMessageRespVO().setContent(content); 68 + public AiChatMessageSendRespVO sendMessage(AiChatMessageSendReqVO sendReqVO, Long userId) {
  69 + // 1.1 校验对话存在
  70 + AiChatConversationDO conversation = chatConversationService.validateChatConversationExists(sendReqVO.getConversationId());
  71 + if (ObjUtil.notEqual(conversation.getUserId(), userId)) {
  72 + throw exception(CHAT_CONVERSATION_NOT_EXISTS);
  73 + }
  74 + List<AiChatMessageDO> historyMessages = chatMessageMapper.selectListByConversationId(conversation.getId());
  75 + // 1.2 校验模型
  76 + AiChatModelDO model = chatModalService.validateChatModel(conversation.getModelId());
  77 + ChatModel chatClient = apiKeyService.getChatClient(model.getKeyId());
  78 +
  79 + // 2. 插入 user 发送消息
  80 + AiChatMessageDO userMessage = createChatMessage(conversation.getId(), null, model,
  81 + userId, conversation.getRoleId(), MessageType.USER, sendReqVO.getContent(), sendReqVO.getUseContext());
  82 +
  83 + // 3.1 插入 assistant 接收消息
  84 + AiChatMessageDO assistantMessage = createChatMessage(conversation.getId(), userMessage.getId(), model,
  85 + userId, conversation.getRoleId(), MessageType.ASSISTANT, "", sendReqVO.getUseContext());
  86 +
  87 + // 3.2 创建 chat 需要的 Prompt
  88 + Prompt prompt = buildPrompt(conversation, historyMessages, model, sendReqVO);
  89 + ChatResponse chatResponse = chatClient.call(prompt);
  90 +
  91 + // 3.3 段式返回
  92 + String newContent = chatResponse.getResult().getOutput().getContent();
  93 + chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(newContent));
  94 + return new AiChatMessageSendRespVO().setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class))
  95 + .setReceive(BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class).setContent(newContent));
108 } 96 }
109 97
110 @Override 98 @Override
@@ -112,14 +100,12 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { @@ -112,14 +100,12 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
112 // 1.1 校验对话存在 100 // 1.1 校验对话存在
113 AiChatConversationDO conversation = chatConversationService.validateChatConversationExists(sendReqVO.getConversationId()); 101 AiChatConversationDO conversation = chatConversationService.validateChatConversationExists(sendReqVO.getConversationId());
114 if (ObjUtil.notEqual(conversation.getUserId(), userId)) { 102 if (ObjUtil.notEqual(conversation.getUserId(), userId)) {
115 - throw exception(CHAT_CONVERSATION_NOT_EXISTS); // TODO 芋艿:异常情况的对接; 103 + throw exception(CHAT_CONVERSATION_NOT_EXISTS);
116 } 104 }
117 List<AiChatMessageDO> historyMessages = chatMessageMapper.selectListByConversationId(conversation.getId()); 105 List<AiChatMessageDO> historyMessages = chatMessageMapper.selectListByConversationId(conversation.getId());
118 // 1.2 校验模型 106 // 1.2 校验模型
119 AiChatModelDO model = chatModalService.validateChatModel(conversation.getModelId()); 107 AiChatModelDO model = chatModalService.validateChatModel(conversation.getModelId());
120 - StreamingChatModel chatClient = apiKeyService.getStreamingChatClient(model.getKeyId());  
121 - // 1.3 获取用户头像、角色头像  
122 - AiChatRoleDO role = conversation.getRoleId() != null ? chatRoleService.getChatRole(conversation.getRoleId()) : null; 108 + StreamingChatModel chatClient = apiKeyService.getChatClient(model.getKeyId());
123 109
124 // 2. 插入 user 发送消息 110 // 2. 插入 user 发送消息
125 AiChatMessageDO userMessage = createChatMessage(conversation.getId(), null, model, 111 AiChatMessageDO userMessage = createChatMessage(conversation.getId(), null, model,
@@ -149,9 +135,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { @@ -149,9 +135,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
149 // TODO @芋艿:失败的情况下,要不要删除消息 135 // TODO @芋艿:失败的情况下,要不要删除消息
150 log.error("[sendChatMessageStream][userId({}) sendReqVO({}) 发生异常]", userId, sendReqVO, throwable); 136 log.error("[sendChatMessageStream][userId({}) sendReqVO({}) 发生异常]", userId, sendReqVO, throwable);
151 chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(throwable.getMessage())); 137 chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(throwable.getMessage()));
152 - }).onErrorResume(error -> {  
153 - return Flux.just(error(ErrorCodeConstants.CHAT_STREAM_ERROR));  
154 - }); 138 + }).onErrorResume(error -> Flux.just(error(ErrorCodeConstants.CHAT_STREAM_ERROR)));
155 } 139 }
156 140
157 private Prompt buildPrompt(AiChatConversationDO conversation, List<AiChatMessageDO> messages, 141 private Prompt buildPrompt(AiChatConversationDO conversation, List<AiChatMessageDO> messages,
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiApiKeyService.java
@@ -8,7 +8,7 @@ import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeyPageR @@ -8,7 +8,7 @@ import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeyPageR
8 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeySaveReqVO; 8 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeySaveReqVO;
9 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO; 9 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO;
10 import jakarta.validation.Valid; 10 import jakarta.validation.Valid;
11 -import org.springframework.ai.chat.model.StreamingChatModel; 11 +import org.springframework.ai.chat.model.ChatModel;
12 import org.springframework.ai.image.ImageModel; 12 import org.springframework.ai.image.ImageModel;
13 13
14 import java.util.List; 14 import java.util.List;
@@ -76,12 +76,12 @@ public interface AiApiKeyService { @@ -76,12 +76,12 @@ public interface AiApiKeyService {
76 // ========== 与 spring-ai 集成 ========== 76 // ========== 与 spring-ai 集成 ==========
77 77
78 /** 78 /**
79 - * 获得 StreamingChatClient 对象 79 + * 获得 ChatModel 对象
80 * 80 *
81 * @param id 编号 81 * @param id 编号
82 - * @return StreamingChatClient 对象 82 + * @return ChatModel 对象
83 */ 83 */
84 - StreamingChatModel getStreamingChatClient(Long id); 84 + ChatModel getChatClient(Long id);
85 85
86 /** 86 /**
87 * 获得 ImageClient 对象 87 * 获得 ImageClient 对象
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiApiKeyServiceImpl.java
@@ -12,7 +12,7 @@ import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeySaveR @@ -12,7 +12,7 @@ import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeySaveR
12 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO; 12 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO;
13 import cn.iocoder.yudao.module.ai.dal.mysql.model.AiApiKeyMapper; 13 import cn.iocoder.yudao.module.ai.dal.mysql.model.AiApiKeyMapper;
14 import jakarta.annotation.Resource; 14 import jakarta.annotation.Resource;
15 -import org.springframework.ai.chat.model.StreamingChatModel; 15 +import org.springframework.ai.chat.model.ChatModel;
16 import org.springframework.ai.image.ImageModel; 16 import org.springframework.ai.image.ImageModel;
17 import org.springframework.stereotype.Service; 17 import org.springframework.stereotype.Service;
18 import org.springframework.validation.annotation.Validated; 18 import org.springframework.validation.annotation.Validated;
@@ -98,10 +98,10 @@ public class AiApiKeyServiceImpl implements AiApiKeyService { @@ -98,10 +98,10 @@ public class AiApiKeyServiceImpl implements AiApiKeyService {
98 // ========== 与 spring-ai 集成 ========== 98 // ========== 与 spring-ai 集成 ==========
99 99
100 @Override 100 @Override
101 - public StreamingChatModel getStreamingChatClient(Long id) { 101 + public ChatModel getChatClient(Long id) {
102 AiApiKeyDO apiKey = validateApiKey(id); 102 AiApiKeyDO apiKey = validateApiKey(id);
103 AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform()); 103 AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform());
104 - return clientFactory.getOrCreateStreamingChatClient(platform, apiKey.getApiKey(), apiKey.getUrl()); 104 + return clientFactory.getOrCreateChatClient(platform, apiKey.getApiKey(), apiKey.getUrl());
105 } 105 }
106 106
107 @Override 107 @Override
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicServiceImpl.java
@@ -21,6 +21,7 @@ import cn.iocoder.yudao.module.infra.api.file.FileApi; @@ -21,6 +21,7 @@ import cn.iocoder.yudao.module.infra.api.file.FileApi;
21 import jakarta.annotation.Resource; 21 import jakarta.annotation.Resource;
22 import lombok.extern.slf4j.Slf4j; 22 import lombok.extern.slf4j.Slf4j;
23 import org.springframework.stereotype.Service; 23 import org.springframework.stereotype.Service;
  24 +import org.springframework.transaction.annotation.Transactional;
24 25
25 import java.util.*; 26 import java.util.*;
26 27
@@ -49,6 +50,7 @@ public class AiMusicServiceImpl implements AiMusicService { @@ -49,6 +50,7 @@ public class AiMusicServiceImpl implements AiMusicService {
49 private FileApi fileApi; 50 private FileApi fileApi;
50 51
51 @Override 52 @Override
  53 + @Transactional(rollbackFor = Exception.class)
52 public List<Long> generateMusic(Long userId, AiSunoGenerateReqVO reqVO) { 54 public List<Long> generateMusic(Long userId, AiSunoGenerateReqVO reqVO) {
53 // 1. 调用 Suno 生成音乐 55 // 1. 调用 Suno 生成音乐
54 SunoApi sunoApi = apiKeyService.getSunoApi(); 56 SunoApi sunoApi = apiKeyService.getSunoApi();
@@ -164,14 +166,9 @@ public class AiMusicServiceImpl implements AiMusicService { @@ -164,14 +166,9 @@ public class AiMusicServiceImpl implements AiMusicService {
164 */ 166 */
165 private List<AiMusicDO> buildMusicDOList(List<SunoApi.MusicData> musicList) { 167 private List<AiMusicDO> buildMusicDOList(List<SunoApi.MusicData> musicList) {
166 return convertList(musicList, musicData -> { 168 return convertList(musicList, musicData -> {
167 - Integer status;  
168 - if (Objects.equals("complete", musicData.status())) {  
169 - status = AiMusicStatusEnum.SUCCESS.getStatus();  
170 - } else if (Objects.equals("error", musicData.status())) {  
171 - status = AiMusicStatusEnum.FAIL.getStatus();  
172 - } else {  
173 - status = AiMusicStatusEnum.IN_PROGRESS.getStatus();  
174 - } 169 + Integer status = Objects.equals("complete", musicData.status()) ? AiMusicStatusEnum.SUCCESS.getStatus()
  170 + : Objects.equals("error", musicData.status()) ? AiMusicStatusEnum.FAIL.getStatus()
  171 + : AiMusicStatusEnum.IN_PROGRESS.getStatus();
175 return new AiMusicDO() 172 return new AiMusicDO()
176 .setTaskId(musicData.id()).setModel(musicData.modelName()) 173 .setTaskId(musicData.id()).setModel(musicData.modelName())
177 .setDescription(musicData.gptDescriptionPrompt()) 174 .setDescription(musicData.gptDescriptionPrompt())
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/write/AiWriteServiceImpl.java
@@ -53,7 +53,7 @@ public class AiWriteServiceImpl implements AiWriteService { @@ -53,7 +53,7 @@ public class AiWriteServiceImpl implements AiWriteService {
53 // 1.1 校验模型 53 // 1.1 校验模型
54 // TODO @xin:可以约定大于配置先,查询某个名字。例如说,写作助手!然后写作助手,上面是有个 model 的,可以使用它。 54 // TODO @xin:可以约定大于配置先,查询某个名字。例如说,写作助手!然后写作助手,上面是有个 model 的,可以使用它。
55 AiChatModelDO model = chatModalService.validateChatModel(14L); 55 AiChatModelDO model = chatModalService.validateChatModel(14L);
56 - StreamingChatModel chatClient = apiKeyService.getStreamingChatClient(model.getKeyId()); 56 + StreamingChatModel chatClient = apiKeyService.getChatClient(model.getKeyId());
57 AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform()); 57 AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform());
58 ChatOptions chatOptions = buildChatOptions(platform, model.getModel(), model.getTemperature(), model.getMaxTokens()); 58 ChatOptions chatOptions = buildChatOptions(platform, model.getModel(), model.getTemperature(), model.getMaxTokens());
59 59
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiClientFactory.java
@@ -3,7 +3,7 @@ package cn.iocoder.yudao.framework.ai.core.factory; @@ -3,7 +3,7 @@ package cn.iocoder.yudao.framework.ai.core.factory;
3 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; 3 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
4 import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; 4 import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
5 import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; 5 import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
6 -import org.springframework.ai.chat.model.StreamingChatModel; 6 +import org.springframework.ai.chat.model.ChatModel;
7 import org.springframework.ai.image.ImageModel; 7 import org.springframework.ai.image.ImageModel;
8 8
9 /** 9 /**
@@ -14,26 +14,26 @@ import org.springframework.ai.image.ImageModel; @@ -14,26 +14,26 @@ import org.springframework.ai.image.ImageModel;
14 public interface AiClientFactory { 14 public interface AiClientFactory {
15 15
16 /** 16 /**
17 - * 基于指定配置,获得 StreamingChatClient 对象 17 + * 基于指定配置,获得 ChatModel 对象
18 * 18 *
19 * 如果不存在,则进行创建 19 * 如果不存在,则进行创建
20 * 20 *
21 * @param platform 平台 21 * @param platform 平台
22 * @param apiKey API KEY 22 * @param apiKey API KEY
23 * @param url API URL 23 * @param url API URL
24 - * @return StreamingChatClient 对象 24 + * @return ChatModel 对象
25 */ 25 */
26 - StreamingChatModel getOrCreateStreamingChatClient(AiPlatformEnum platform, String apiKey, String url); 26 + ChatModel getOrCreateChatClient(AiPlatformEnum platform, String apiKey, String url);
27 27
28 /** 28 /**
29 - * 基于默认配置,获得 StreamingChatClient 对象 29 + * 基于默认配置,获得 ChatModel 对象
30 * 30 *
31 * 默认配置,指的是在 application.yaml 配置文件中的 spring.ai 相关的配置 31 * 默认配置,指的是在 application.yaml 配置文件中的 spring.ai 相关的配置
32 * 32 *
33 * @param platform 平台 33 * @param platform 平台
34 - * @return StreamingChatClient 对象 34 + * @return ChatModel 对象
35 */ 35 */
36 - StreamingChatModel getDefaultStreamingChatClient(AiPlatformEnum platform); 36 + ChatModel getDefaultChatClient(AiPlatformEnum platform);
37 37
38 /** 38 /**
39 * 基于默认配置,获得 ImageClient 对象 39 * 基于默认配置,获得 ImageClient 对象
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiClientFactoryImpl.java
@@ -23,7 +23,7 @@ import org.springframework.ai.autoconfigure.openai.OpenAiAutoConfiguration; @@ -23,7 +23,7 @@ import org.springframework.ai.autoconfigure.openai.OpenAiAutoConfiguration;
23 import org.springframework.ai.autoconfigure.qianfan.QianFanAutoConfiguration; 23 import org.springframework.ai.autoconfigure.qianfan.QianFanAutoConfiguration;
24 import org.springframework.ai.autoconfigure.qianfan.QianFanChatProperties; 24 import org.springframework.ai.autoconfigure.qianfan.QianFanChatProperties;
25 import org.springframework.ai.autoconfigure.qianfan.QianFanConnectionProperties; 25 import org.springframework.ai.autoconfigure.qianfan.QianFanConnectionProperties;
26 -import org.springframework.ai.chat.model.StreamingChatModel; 26 +import org.springframework.ai.chat.model.ChatModel;
27 import org.springframework.ai.image.ImageModel; 27 import org.springframework.ai.image.ImageModel;
28 import org.springframework.ai.ollama.OllamaChatModel; 28 import org.springframework.ai.ollama.OllamaChatModel;
29 import org.springframework.ai.ollama.api.OllamaApi; 29 import org.springframework.ai.ollama.api.OllamaApi;
@@ -50,9 +50,9 @@ import java.util.List; @@ -50,9 +50,9 @@ import java.util.List;
50 public class AiClientFactoryImpl implements AiClientFactory { 50 public class AiClientFactoryImpl implements AiClientFactory {
51 51
52 @Override 52 @Override
53 - public StreamingChatModel getOrCreateStreamingChatClient(AiPlatformEnum platform, String apiKey, String url) {  
54 - String cacheKey = buildClientCacheKey(StreamingChatModel.class, platform, apiKey, url);  
55 - return Singleton.get(cacheKey, (Func0<StreamingChatModel>) () -> { 53 + public ChatModel getOrCreateChatClient(AiPlatformEnum platform, String apiKey, String url) {
  54 + String cacheKey = buildClientCacheKey(ChatModel.class, platform, apiKey, url);
  55 + return Singleton.get(cacheKey, (Func0<ChatModel>) () -> {
56 //noinspection EnhancedSwitchMigration 56 //noinspection EnhancedSwitchMigration
57 switch (platform) { 57 switch (platform) {
58 case OPENAI: 58 case OPENAI:
@@ -74,7 +74,7 @@ public class AiClientFactoryImpl implements AiClientFactory { @@ -74,7 +74,7 @@ public class AiClientFactoryImpl implements AiClientFactory {
74 } 74 }
75 75
76 @Override 76 @Override
77 - public StreamingChatModel getDefaultStreamingChatClient(AiPlatformEnum platform) { 77 + public ChatModel getDefaultChatClient(AiPlatformEnum platform) {
78 //noinspection EnhancedSwitchMigration 78 //noinspection EnhancedSwitchMigration
79 switch (platform) { 79 switch (platform) {
80 case OPENAI: 80 case OPENAI: