Commit c98495dfcb615e028fda95d0c92ed5d70ac5c16f

Authored by cherishsince
2 parents 3d2b0dec 266b6faa

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

Showing 29 changed files with 189 additions and 470 deletions
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/DictTypeConstants.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.enums;
  2 +
  3 +/**
  4 + * AI 字典类型的枚举类
  5 + *
  6 + * @author xiaoxin
  7 + */
  8 +public interface DictTypeConstants {
  9 +
  10 + // ========== AI Write ==========
  11 + String AI_WRITE_FORMAT = "ai_write_format"; // 写作格式
  12 + String AI_WRITE_LENGTH = "ai_write_length"; // 写作长度
  13 + String AI_WRITE_LANGUAGE = "ai_write_language"; // 写作语言
  14 + String AI_WRITE_TONE = "ai_write_tone"; // 写作语气
  15 +
  16 +}
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/model/AiModelEnum.java deleted
1 -package cn.iocoder.yudao.module.ai.enums.model;  
2 -  
3 -import lombok.AllArgsConstructor;  
4 -import lombok.Getter;  
5 -  
6 -// TODO @芋艿:可以考虑清理掉  
7 -/**  
8 - * ai 模型  
9 - *  
10 - * @author: fansili  
11 - * @time: 2024/3/4 12:36  
12 - */  
13 -@Getter  
14 -@AllArgsConstructor  
15 -public enum AiModelEnum {  
16 -  
17 - // open ai  
18 - OPEN_AI_GPT_3_5( "GPT3.5", "gpt-3.5-turbo",null),  
19 - OPEN_AI_GPT_4("GPT4", "gpt-4-turbo",null),  
20 -  
21 - // 千问付费模型  
22 - QWEN_TURBO("通义千问超大规模语言模型", "qwen-turbo", null),  
23 - QWEN_PLUS("通义千问超大规模语言模型增强版", "qwen-plus", null),  
24 - QWEN_MAX("通义千问千亿级别超大规模语言模型", "qwen-max", null),  
25 - QWEN_MAX_0403("通义千问千亿级别超大规模语言模型-0403", "qwen-max-0403", null),  
26 - QWEN_MAX_0107("通义千问千亿级别超大规模语言模型-0107", "qwen-max-0107", null),  
27 - QWEN_MAX_1201("通义千问千亿级别超大规模语言模型-1201", "qwen-max-1201", null),  
28 - QWEN_MAX_LONGCONTEXT("通义千问千亿级别超大规模语言模型-28k tokens", "qwen-max-longcontext", null),  
29 -  
30 - // 千问开源模型  
31 - // https://help.aliyun.com/document_detail/2666503.html?spm=a2c4g.2701795.0.0.26eb34dfKzcWN4  
32 - QWEN_72B_CHAT("通义千问1.5对外开源的72B规模参数量的经过人类指令对齐的chat模型", "qwen-72b-chat", null),  
33 -  
34 - // 一言模型  
35 - ERNIE4_0("ERNIE 4.0", "ERNIE 4.0", "/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions_pro"),  
36 - ERNIE4_3_5_8K("ERNIE-3.5-8K", "ERNIE-3.5-8K", "/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions"),  
37 - ERNIE4_3_5_8K_0205("ERNIE-3.5-8K-0205", "ERNIE-3.5-8K-0205", "/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/ernie-3.5-8k-0205"),  
38 -  
39 - ERNIE4_3_5_8K_1222("ERNIE-3.5-8K-1222", "ERNIE-3.5-8K-1222", "/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/ernie-3.5-8k-1222"),  
40 - ERNIE4_BOT_8K("ERNIE-Bot-8K", "ERNIE-Bot-8K", "/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/ernie_bot_8k"),  
41 - ERNIE4_3_5_4K_0205("ERNIE-3.5-4K-0205", "ERNIE-3.5-4K-0205", "/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/ernie-3.5-4k-0205"),  
42 -  
43 -// 文档地址:https://www.xfyun.cn/doc/spark/Web.html#_1-%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E  
44 -// general指向V1.5版本;  
45 -// generalv2指向V2版本;  
46 -// generalv3指向V3版本;  
47 -// generalv3.5指向V3.5版本;  
48 -  
49 - XING_HUO_1_5("星火大模型1.5", "general", "/v1.1/chat"),  
50 - XING_HUO_2_0("星火大模型2.0", "generalv2", "/v2.1/chat"),  
51 - XING_HUO_3_0("星火大模型3.0", "generalv3", "/v3.1/chat"),  
52 - XING_HUO_3_5("星火大模型3.5", "generalv3.5", "/v3.5/chat"),  
53 -  
54 - // Suno 模型  
55 - SUNO_2( "SUNO-2", "chirp-v2-xxl-alpha",null),  
56 - SUNO_3_0( "SUNO-3.0", "chirp-v3-0",null),  
57 - SUNO_3_5( "SUNO-3.5", "chirp-v3.5",null),  
58 - ;  
59 -  
60 - /**  
61 - * 模型名字 - 用于展示  
62 - */  
63 - private final String name;  
64 - /**  
65 - * 模型标志 - 用于参数传递  
66 - */  
67 - private final String model;  
68 - /**  
69 - * uri地址  
70 - */  
71 - private final String uri;  
72 -  
73 -}  
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/write/AiLanguageEnum.java deleted
1 -package cn.iocoder.yudao.module.ai.enums.write;  
2 -  
3 -import cn.iocoder.yudao.framework.common.core.IntArrayValuable;  
4 -import lombok.AllArgsConstructor;  
5 -import lombok.Getter;  
6 -  
7 -import java.util.Arrays;  
8 -  
9 -// TODO @xin:写作的几个,不用枚举类哈。直接搞字段就好了。AiWriteTypeEnum 还是需要的哈  
10 -@AllArgsConstructor  
11 -@Getter  
12 -public enum AiLanguageEnum implements IntArrayValuable {  
13 -  
14 - AUTO(1, "自动"),  
15 - CHINESE(2, "中文"),  
16 - ENGLISH(3, "英文"),  
17 - KOREAN(4, "韩语"),  
18 - JAPANESE(5, "日语");  
19 -  
20 - /**  
21 - * Language code  
22 - */  
23 - private final Integer language;  
24 - /**  
25 - * Language name  
26 - */  
27 - private final String name;  
28 -  
29 - public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(AiLanguageEnum::getLanguage).toArray();  
30 -  
31 - @Override  
32 - public int[] array() {  
33 - return ARRAYS;  
34 - }  
35 -  
36 - public static AiLanguageEnum valueOfLanguage(Integer language) {  
37 - for (AiLanguageEnum languageEnum : AiLanguageEnum.values()) {  
38 - if (languageEnum.getLanguage().equals(language)) {  
39 - return languageEnum;  
40 - }  
41 - }  
42 - throw new IllegalArgumentException("未知语言: " + language);  
43 - }  
44 -  
45 -}  
46 \ No newline at end of file 0 \ No newline at end of file
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/write/AiWriteFormatEnum.java deleted
1 -package cn.iocoder.yudao.module.ai.enums.write;  
2 -  
3 -import cn.iocoder.yudao.framework.common.core.IntArrayValuable;  
4 -import lombok.AllArgsConstructor;  
5 -import lombok.Getter;  
6 -  
7 -import java.util.Arrays;  
8 -  
9 -/**  
10 - * AI 写作类型的枚举  
11 - *  
12 - * @author xiaoxin  
13 - */  
14 -@AllArgsConstructor  
15 -@Getter  
16 -public enum AiWriteFormatEnum implements IntArrayValuable {  
17 -  
18 - AUTO(1, "自动"),  
19 - EMAIL(2, "电子邮件"),  
20 - MESSAGE(3, "消息"),  
21 - COMMENT(4, "评论"),  
22 - PARAGRAPH(5, "段落"),  
23 - ARTICLE(6, "文章"),  
24 - BLOG_POST(7, "博客文章"),  
25 - IDEA(8, "想法"),  
26 - OUTLINE(9, "大纲");  
27 -  
28 - /**  
29 - * 格式  
30 - */  
31 - private final Integer format;  
32 - /**  
33 - * 格式名  
34 - */  
35 - private final String name;  
36 -  
37 - public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(AiWriteFormatEnum::getFormat).toArray();  
38 -  
39 - @Override  
40 - public int[] array() {  
41 - return ARRAYS;  
42 - }  
43 -  
44 - public static AiWriteFormatEnum valueOfFormat(Integer format) {  
45 - for (AiWriteFormatEnum formatEnum : AiWriteFormatEnum.values()) {  
46 - if (formatEnum.getFormat().equals(format)) {  
47 - return formatEnum;  
48 - }  
49 - }  
50 - throw new IllegalArgumentException("未知格式: " + format);  
51 - }  
52 -  
53 -}  
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/write/AiWriteLengthEnum.java deleted
1 -package cn.iocoder.yudao.module.ai.enums.write;  
2 -  
3 -import cn.iocoder.yudao.framework.common.core.IntArrayValuable;  
4 -import lombok.AllArgsConstructor;  
5 -import lombok.Getter;  
6 -  
7 -import java.util.Arrays;  
8 -  
9 -/**  
10 - * AI 写作类型的枚举  
11 - *  
12 - * @author xiaoxin  
13 - */  
14 -@AllArgsConstructor  
15 -@Getter  
16 -public enum AiWriteLengthEnum implements IntArrayValuable {  
17 -  
18 - AUTO(1, "自动"),  
19 - SHORT(2, "短"),  
20 - MEDIUM(3, "中"),  
21 - LONG(4, "长");  
22 -  
23 - /**  
24 - * 长度  
25 - */  
26 - private final Integer length;  
27 - /**  
28 - * 长度名  
29 - */  
30 - private final String name;  
31 -  
32 - public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(AiWriteLengthEnum::getLength).toArray();  
33 -  
34 - @Override  
35 - public int[] array() {  
36 - return ARRAYS;  
37 - }  
38 -  
39 - public static AiWriteLengthEnum valueOfLength(Integer length) {  
40 - for (AiWriteLengthEnum lengthEnum : AiWriteLengthEnum.values()) {  
41 - if (lengthEnum.getLength().equals(length)) {  
42 - return lengthEnum;  
43 - }  
44 - }  
45 - throw new IllegalArgumentException("未知长度: " + length);  
46 - }  
47 -}  
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/write/AiWriteToneEnum.java deleted
1 -package cn.iocoder.yudao.module.ai.enums.write;  
2 -  
3 -import cn.iocoder.yudao.framework.common.core.IntArrayValuable;  
4 -import lombok.AllArgsConstructor;  
5 -import lombok.Getter;  
6 -  
7 -import java.util.Arrays;  
8 -  
9 -@AllArgsConstructor  
10 -@Getter  
11 -public enum AiWriteToneEnum implements IntArrayValuable {  
12 -  
13 - AUTO(1, "自动"),  
14 - FRIENDLY(2, "友善"),  
15 - CASUAL(3, "随意"),  
16 - KIND(4, "友好"),  
17 - PROFESSIONAL(5, "专业"),  
18 - HUMOROUS(6, "谈谐"),  
19 - INTERESTING(7, "有趣"),  
20 - FORMAL(8, "正式");  
21 -  
22 - /**  
23 - * 语气  
24 - */  
25 - private final Integer tone;  
26 - /**  
27 - * 语气名  
28 - */  
29 - private final String name;  
30 -  
31 - public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(AiWriteToneEnum::getTone).toArray();  
32 -  
33 - @Override  
34 - public int[] array() {  
35 - return ARRAYS;  
36 - }  
37 -  
38 - public static AiWriteToneEnum valueOfTone(Integer tone) {  
39 - for (AiWriteToneEnum toneEnum : AiWriteToneEnum.values()) {  
40 - if (toneEnum.getTone().equals(tone)) {  
41 - return toneEnum;  
42 - }  
43 - }  
44 - throw new IllegalArgumentException("未知语气: " + tone);  
45 - }  
46 -}  
47 \ No newline at end of file 0 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatConversationController.java
@@ -23,7 +23,6 @@ import org.springframework.web.bind.annotation.*; @@ -23,7 +23,6 @@ import org.springframework.web.bind.annotation.*;
23 23
24 import java.util.List; 24 import java.util.List;
25 import java.util.Map; 25 import java.util.Map;
26 -import java.util.function.Consumer;  
27 26
28 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 27 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
29 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; 28 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
@@ -79,9 +78,8 @@ public class AiChatConversationController { @@ -79,9 +78,8 @@ public class AiChatConversationController {
79 return success(true); 78 return success(true);
80 } 79 }
81 80
82 - // TODO 芋艿:这个 url 可以改下  
83 - @DeleteMapping("/delete-my-all-except-pinned")  
84 - @Operation(summary = "删除所有对话(置顶除外)") 81 + @DeleteMapping("/delete-by-unpinned")
  82 + @Operation(summary = "删除未置顶的聊天对话")
85 public CommonResult<Boolean> deleteChatConversationMyByUnpinned() { 83 public CommonResult<Boolean> deleteChatConversationMyByUnpinned() {
86 chatConversationService.deleteChatConversationMyByUnpinned(getLoginUserId()); 84 chatConversationService.deleteChatConversationMyByUnpinned(getLoginUserId());
87 return success(true); 85 return success(true);
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/AiChatMessageRespVO.java
@@ -28,7 +28,7 @@ public class AiChatMessageRespVO { @@ -28,7 +28,7 @@ public class AiChatMessageRespVO {
28 private Long roleId; 28 private Long roleId;
29 29
30 @Schema(description = "模型标志", requiredMode = Schema.RequiredMode.REQUIRED, example = "gpt-3.5-turbo") 30 @Schema(description = "模型标志", requiredMode = Schema.RequiredMode.REQUIRED, example = "gpt-3.5-turbo")
31 - private String model; // 参见 AiOpenAiModelEnum 枚举类 31 + private String model;
32 32
33 @Schema(description = "模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "123") 33 @Schema(description = "模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "123")
34 private Long modelId; 34 private Long modelId;
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.http
1 -  
2 ### 生成图片:OpenAI(DALL) 1 ### 生成图片:OpenAI(DALL)
3 POST {{baseUrl}}/ai/image/draw 2 POST {{baseUrl}}/ai/image/draw
4 Content-Type: application/json 3 Content-Type: application/json
@@ -29,8 +28,7 @@ Authorization: {{token}} @@ -29,8 +28,7 @@ Authorization: {{token}}
29 "style": "vivid" 28 "style": "vivid"
30 } 29 }
31 30
32 -### 生成图片:生成图片  
33 - 31 +### 生成图片:生成图片(Midjourney)
34 POST {{baseUrl}}/ai/image/midjourney/imagine 32 POST {{baseUrl}}/ai/image/midjourney/imagine
35 Content-Type: application/json 33 Content-Type: application/json
36 Authorization: {{token}} 34 Authorization: {{token}}
@@ -40,6 +38,5 @@ Authorization: {{token}} @@ -40,6 +38,5 @@ Authorization: {{token}}
40 "model": "midjourney", 38 "model": "midjourney",
41 "width": "1", 39 "width": "1",
42 "height": "1", 40 "height": "1",
43 - "version": "6.0",  
44 - "base64Array": [] 41 + "version": "6.0"
45 } 42 }
46 \ No newline at end of file 43 \ No newline at end of file
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,7 +6,7 @@ Authorization: {{token}} @@ -6,7 +6,7 @@ Authorization: {{token}}
6 { 6 {
7 "platform": "Suno", 7 "platform": "Suno",
8 "generateMode": 2, 8 "generateMode": 2,
9 - "prompt": "周末啦!", 9 + "prompt": "创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。",
10 "model": "chirp-v3.5", 10 "model": "chirp-v3.5",
11 "tags": ["Happy"], 11 "tags": ["Happy"],
12 "title": "Happy Song" 12 "title": "Happy Song"
@@ -21,6 +21,6 @@ Authorization: {{token}} @@ -21,6 +21,6 @@ Authorization: {{token}}
21 "platform": "Suno", 21 "platform": "Suno",
22 "generateMode": 1, 22 "generateMode": 1,
23 "model": "chirp-v3.5", 23 "model": "chirp-v3.5",
24 - "gptDescriptionPrompt": "今天是星球六,结果是个下雨天,希望心情很美丽", 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/vo/AiSunoGenerateReqVO.java
@@ -46,7 +46,7 @@ public class AiSunoGenerateReqVO { @@ -46,7 +46,7 @@ public class AiSunoGenerateReqVO {
46 46
47 @Schema(description = "模型", requiredMode = Schema.RequiredMode.REQUIRED, example = "chirp-v3.5") 47 @Schema(description = "模型", requiredMode = Schema.RequiredMode.REQUIRED, example = "chirp-v3.5")
48 @NotEmpty(message = "模型不能为空") 48 @NotEmpty(message = "模型不能为空")
49 - private String model; // 参见 AiModelEnum 枚举 49 + private String model;
50 50
51 @Schema(description = "音乐风格", example = "[\"pop\",\"jazz\",\"punk\"]") 51 @Schema(description = "音乐风格", example = "[\"pop\",\"jazz\",\"punk\"]")
52 private List<String> tags; 52 private List<String> tags;
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/write/AiWriteController.java
@@ -26,9 +26,10 @@ public class AiWriteController { @@ -26,9 +26,10 @@ public class AiWriteController {
26 private AiWriteService writeService; 26 private AiWriteService writeService;
27 27
28 @PostMapping(value = "/generate-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) 28 @PostMapping(value = "/generate-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
29 - @PermitAll  
30 @Operation(summary = "写作生成(流式)", description = "流式返回,响应较快") 29 @Operation(summary = "写作生成(流式)", description = "流式返回,响应较快")
  30 + @PermitAll // 解决 SSE 最终响应的时候,会被 Access Denied 拦截的问题
31 public Flux<CommonResult<String>> generateWriteContent(@RequestBody @Valid AiWriteGenerateReqVO generateReqVO) { 31 public Flux<CommonResult<String>> generateWriteContent(@RequestBody @Valid AiWriteGenerateReqVO generateReqVO) {
32 return writeService.generateWriteContent(generateReqVO, getLoginUserId()); 32 return writeService.generateWriteContent(generateReqVO, getLoginUserId());
33 } 33 }
  34 +
34 } 35 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/write/vo/AiWriteGenerateReqVO.java
@@ -14,11 +14,10 @@ public class AiWriteGenerateReqVO { @@ -14,11 +14,10 @@ public class AiWriteGenerateReqVO {
14 @InEnum(AiWriteTypeEnum.class) 14 @InEnum(AiWriteTypeEnum.class)
15 private Integer type; 15 private Integer type;
16 16
17 - // TODO @xin:如果非必填,可以不用写 requiredMode  
18 - @Schema(description = "写作内容提示", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1.撰写:田忌赛马;2.回复:不批") 17 + @Schema(description = "写作内容提示", example = "1.撰写:田忌赛马;2.回复:不批")
19 private String prompt; 18 private String prompt;
20 19
21 - @Schema(description = "原文", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "领导我要辞职") 20 + @Schema(description = "原文", example = "领导我要辞职")
22 private String originalContent; 21 private String originalContent;
23 22
24 @Schema(description = "长度", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") 23 @Schema(description = "长度", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatConversationDO.java
@@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.ai.dal.dataobject.chat; @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.ai.dal.dataobject.chat;
3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
4 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; 4 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
5 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; 5 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
6 -import cn.iocoder.yudao.module.ai.enums.model.AiModelEnum;  
7 import com.baomidou.mybatisplus.annotation.KeySequence; 6 import com.baomidou.mybatisplus.annotation.KeySequence;
8 import com.baomidou.mybatisplus.annotation.TableId; 7 import com.baomidou.mybatisplus.annotation.TableId;
9 import com.baomidou.mybatisplus.annotation.TableName; 8 import com.baomidou.mybatisplus.annotation.TableName;
@@ -73,8 +72,6 @@ public class AiChatConversationDO extends BaseDO { @@ -73,8 +72,6 @@ public class AiChatConversationDO extends BaseDO {
73 private Long modelId; 72 private Long modelId;
74 /** 73 /**
75 * 模型标志 74 * 模型标志
76 - *  
77 - * 枚举 {@link AiModelEnum}  
78 */ 75 */
79 private String model; 76 private String model;
80 77
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatMessageDO.java
@@ -5,7 +5,6 @@ import org.springframework.ai.chat.messages.MessageType; @@ -5,7 +5,6 @@ import org.springframework.ai.chat.messages.MessageType;
5 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 5 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
6 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; 6 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
7 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; 7 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
8 -import cn.iocoder.yudao.module.ai.enums.model.AiModelEnum;  
9 import com.baomidou.mybatisplus.annotation.KeySequence; 8 import com.baomidou.mybatisplus.annotation.KeySequence;
10 import com.baomidou.mybatisplus.annotation.TableName; 9 import com.baomidou.mybatisplus.annotation.TableName;
11 import lombok.*; 10 import lombok.*;
@@ -69,8 +68,6 @@ public class AiChatMessageDO extends BaseDO { @@ -69,8 +68,6 @@ public class AiChatMessageDO extends BaseDO {
69 68
70 /** 69 /**
71 * 模型标志 70 * 模型标志
72 - *  
73 - * 枚举 {@link AiModelEnum}  
74 */ 71 */
75 private String model; 72 private String model;
76 /** 73 /**
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
@@ -4,35 +4,28 @@ import cn.hutool.core.collection.CollUtil; @@ -4,35 +4,28 @@ 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.model.xinghuo.XingHuoChatModel;  
8 -import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoOptions; 7 +import cn.iocoder.yudao.framework.ai.core.util.AiUtils;
9 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 8 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
10 import cn.iocoder.yudao.framework.common.pojo.PageResult; 9 import cn.iocoder.yudao.framework.common.pojo.PageResult;
11 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 10 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
12 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessagePageReqVO; 11 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; 12 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; 13 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendRespVO;
16 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO; 14 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO;
17 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO; 15 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO;
18 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; 16 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; 17 import cn.iocoder.yudao.module.ai.dal.mysql.chat.AiChatMessageMapper;
21 import cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants; 18 import cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants;
22 import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService; 19 import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService;
23 import cn.iocoder.yudao.module.ai.service.model.AiChatModelService; 20 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;  
26 import jakarta.annotation.Resource; 21 import jakarta.annotation.Resource;
27 import lombok.extern.slf4j.Slf4j; 22 import lombok.extern.slf4j.Slf4j;
28 import org.springframework.ai.chat.messages.*; 23 import org.springframework.ai.chat.messages.*;
  24 +import org.springframework.ai.chat.model.ChatModel;
29 import org.springframework.ai.chat.model.ChatResponse; 25 import org.springframework.ai.chat.model.ChatResponse;
30 import org.springframework.ai.chat.model.StreamingChatModel; 26 import org.springframework.ai.chat.model.StreamingChatModel;
31 import org.springframework.ai.chat.prompt.ChatOptions; 27 import org.springframework.ai.chat.prompt.ChatOptions;
32 import org.springframework.ai.chat.prompt.Prompt; 28 import org.springframework.ai.chat.prompt.Prompt;
33 -import org.springframework.ai.ollama.api.OllamaOptions;  
34 -import org.springframework.ai.openai.OpenAiChatOptions;  
35 -import org.springframework.ai.qianfan.QianFanChatOptions;  
36 import org.springframework.stereotype.Service; 29 import org.springframework.stereotype.Service;
37 import org.springframework.transaction.annotation.Transactional; 30 import org.springframework.transaction.annotation.Transactional;
38 import reactor.core.publisher.Flux; 31 import reactor.core.publisher.Flux;
@@ -64,47 +57,37 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { @@ -64,47 +57,37 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
64 @Resource 57 @Resource
65 private AiChatModelService chatModalService; 58 private AiChatModelService chatModalService;
66 @Resource 59 @Resource
67 - private AiChatRoleService chatRoleService;  
68 - @Resource  
69 private AiApiKeyService apiKeyService; 60 private AiApiKeyService apiKeyService;
70 61
71 @Transactional(rollbackFor = Exception.class) 62 @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); 63 + public AiChatMessageSendRespVO sendMessage(AiChatMessageSendReqVO sendReqVO, Long userId) {
  64 + // 1.1 校验对话存在
  65 + AiChatConversationDO conversation = chatConversationService.validateChatConversationExists(sendReqVO.getConversationId());
  66 + if (ObjUtil.notEqual(conversation.getUserId(), userId)) {
  67 + throw exception(CHAT_CONVERSATION_NOT_EXISTS);
  68 + }
  69 + List<AiChatMessageDO> historyMessages = chatMessageMapper.selectListByConversationId(conversation.getId());
  70 + // 1.2 校验模型
  71 + AiChatModelDO model = chatModalService.validateChatModel(conversation.getModelId());
  72 + ChatModel chatClient = apiKeyService.getChatClient(model.getKeyId());
  73 +
  74 + // 2. 插入 user 发送消息
  75 + AiChatMessageDO userMessage = createChatMessage(conversation.getId(), null, model,
  76 + userId, conversation.getRoleId(), MessageType.USER, sendReqVO.getContent(), sendReqVO.getUseContext());
  77 +
  78 + // 3.1 插入 assistant 接收消息
  79 + AiChatMessageDO assistantMessage = createChatMessage(conversation.getId(), userMessage.getId(), model,
  80 + userId, conversation.getRoleId(), MessageType.ASSISTANT, "", sendReqVO.getUseContext());
  81 +
  82 + // 3.2 创建 chat 需要的 Prompt
  83 + Prompt prompt = buildPrompt(conversation, historyMessages, model, sendReqVO);
  84 + ChatResponse chatResponse = chatClient.call(prompt);
  85 +
  86 + // 3.3 段式返回
  87 + String newContent = chatResponse.getResult().getOutput().getContent();
  88 + chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(newContent));
  89 + return new AiChatMessageSendRespVO().setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class))
  90 + .setReceive(BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class).setContent(newContent));
108 } 91 }
109 92
110 @Override 93 @Override
@@ -112,14 +95,12 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { @@ -112,14 +95,12 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
112 // 1.1 校验对话存在 95 // 1.1 校验对话存在
113 AiChatConversationDO conversation = chatConversationService.validateChatConversationExists(sendReqVO.getConversationId()); 96 AiChatConversationDO conversation = chatConversationService.validateChatConversationExists(sendReqVO.getConversationId());
114 if (ObjUtil.notEqual(conversation.getUserId(), userId)) { 97 if (ObjUtil.notEqual(conversation.getUserId(), userId)) {
115 - throw exception(CHAT_CONVERSATION_NOT_EXISTS); // TODO 芋艿:异常情况的对接; 98 + throw exception(CHAT_CONVERSATION_NOT_EXISTS);
116 } 99 }
117 List<AiChatMessageDO> historyMessages = chatMessageMapper.selectListByConversationId(conversation.getId()); 100 List<AiChatMessageDO> historyMessages = chatMessageMapper.selectListByConversationId(conversation.getId());
118 // 1.2 校验模型 101 // 1.2 校验模型
119 AiChatModelDO model = chatModalService.validateChatModel(conversation.getModelId()); 102 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; 103 + StreamingChatModel chatClient = apiKeyService.getChatClient(model.getKeyId());
123 104
124 // 2. 插入 user 发送消息 105 // 2. 插入 user 发送消息
125 AiChatMessageDO userMessage = createChatMessage(conversation.getId(), null, model, 106 AiChatMessageDO userMessage = createChatMessage(conversation.getId(), null, model,
@@ -149,9 +130,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { @@ -149,9 +130,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
149 // TODO @芋艿:失败的情况下,要不要删除消息 130 // TODO @芋艿:失败的情况下,要不要删除消息
150 log.error("[sendChatMessageStream][userId({}) sendReqVO({}) 发生异常]", userId, sendReqVO, throwable); 131 log.error("[sendChatMessageStream][userId({}) sendReqVO({}) 发生异常]", userId, sendReqVO, throwable);
151 chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(throwable.getMessage())); 132 chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(throwable.getMessage()));
152 - }).onErrorResume(error -> {  
153 - return Flux.just(error(ErrorCodeConstants.CHAT_STREAM_ERROR));  
154 - }); 133 + }).onErrorResume(error -> Flux.just(error(ErrorCodeConstants.CHAT_STREAM_ERROR)));
155 } 134 }
156 135
157 private Prompt buildPrompt(AiChatConversationDO conversation, List<AiChatMessageDO> messages, 136 private Prompt buildPrompt(AiChatConversationDO conversation, List<AiChatMessageDO> messages,
@@ -164,46 +143,17 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { @@ -164,46 +143,17 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
164 } 143 }
165 // 1.2 history message 历史消息 144 // 1.2 history message 历史消息
166 List<AiChatMessageDO> contextMessages = filterContextMessages(messages, conversation, sendReqVO); 145 List<AiChatMessageDO> contextMessages = filterContextMessages(messages, conversation, sendReqVO);
167 - contextMessages.forEach(message -> {  
168 - // TODO @芋艿:看看有没优化空间  
169 - if (MessageType.USER.getValue().equals(message.getType())) {  
170 - chatMessages.add(new UserMessage(message.getContent()));  
171 - } else {  
172 - chatMessages.add(new AssistantMessage(message.getContent()));  
173 - }  
174 - }); 146 + contextMessages.forEach(message -> chatMessages.add(AiUtils.buildMessage(message.getType(), message.getContent())));
175 // 1.3 user message 新发送消息 147 // 1.3 user message 新发送消息
176 chatMessages.add(new UserMessage(sendReqVO.getContent())); 148 chatMessages.add(new UserMessage(sendReqVO.getContent()));
177 149
178 // 2. 构建 ChatOptions 对象 150 // 2. 构建 ChatOptions 对象
179 AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform()); 151 AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform());
180 - ChatOptions chatOptions = buildChatOptions(platform, model.getModel(), 152 + ChatOptions chatOptions = AiUtils.buildChatOptions(platform, model.getModel(),
181 conversation.getTemperature(), conversation.getMaxTokens()); 153 conversation.getTemperature(), conversation.getMaxTokens());
182 return new Prompt(chatMessages, chatOptions); 154 return new Prompt(chatMessages, chatOptions);
183 } 155 }
184 156
185 - private static ChatOptions buildChatOptions(AiPlatformEnum platform, String model, Double temperature, Integer maxTokens) {  
186 - Float temperatureF = temperature != null ? temperature.floatValue() : null;  
187 - //noinspection EnhancedSwitchMigration  
188 - switch (platform) {  
189 - case OPENAI:  
190 - return OpenAiChatOptions.builder().withModel(model).withTemperature(temperatureF).withMaxTokens(maxTokens).build();  
191 - case OLLAMA:  
192 - return OllamaOptions.create().withModel(model).withTemperature(temperatureF).withNumPredict(maxTokens);  
193 - case YI_YAN:  
194 - // TODO 芋艿:貌似 model 只要一设置,就报错  
195 -// return QianFanChatOptions.builder().withModel(model).withTemperature(temperatureF).withMaxTokens(maxTokens).build();  
196 - return QianFanChatOptions.builder().withTemperature(temperatureF).withMaxTokens(maxTokens).build();  
197 - case XING_HUO:  
198 - return new XingHuoOptions().setChatModel(XingHuoChatModel.valueOfModel(model)).setTemperature(temperatureF)  
199 - .setMaxTokens(maxTokens);  
200 - case QIAN_WEN:  
201 - return TongYiChatOptions.builder().withModel(model).withTemperature(temperature).withMaxTokens(maxTokens).build();  
202 - default:  
203 - throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform));  
204 - }  
205 - }  
206 -  
207 /** 157 /**
208 * 从历史消息中,获得倒序的 n 组消息作为消息上下文 158 * 从历史消息中,获得倒序的 n 组消息作为消息上下文
209 * 159 *
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,10 +50,10 @@ public class AiMusicServiceImpl implements AiMusicService { @@ -49,10 +50,10 @@ 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();
55 - // TODO 芋艿:这两个貌似一直没跑成功,你那可以么?用的请求是 AiMusicController.http 的 --xin:大部分ok的,补充了error_message  
56 List<SunoApi.MusicData> musicDataList; 57 List<SunoApi.MusicData> musicDataList;
57 if (Objects.equals(AiMusicGenerateModeEnum.DESCRIPTION.getMode(), reqVO.getGenerateMode())) { 58 if (Objects.equals(AiMusicGenerateModeEnum.DESCRIPTION.getMode(), reqVO.getGenerateMode())) {
58 // 1.1 描述模式 59 // 1.1 描述模式
@@ -164,14 +165,9 @@ public class AiMusicServiceImpl implements AiMusicService { @@ -164,14 +165,9 @@ public class AiMusicServiceImpl implements AiMusicService {
164 */ 165 */
165 private List<AiMusicDO> buildMusicDOList(List<SunoApi.MusicData> musicList) { 166 private List<AiMusicDO> buildMusicDOList(List<SunoApi.MusicData> musicList) {
166 return convertList(musicList, musicData -> { 167 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 - } 168 + Integer status = Objects.equals("complete", musicData.status()) ? AiMusicStatusEnum.SUCCESS.getStatus()
  169 + : Objects.equals("error", musicData.status()) ? AiMusicStatusEnum.FAIL.getStatus()
  170 + : AiMusicStatusEnum.IN_PROGRESS.getStatus();
175 return new AiMusicDO() 171 return new AiMusicDO()
176 .setTaskId(musicData.id()).setModel(musicData.modelName()) 172 .setTaskId(musicData.id()).setModel(musicData.modelName())
177 .setDescription(musicData.gptDescriptionPrompt()) 173 .setDescription(musicData.gptDescriptionPrompt())
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/write/AiWriteServiceImpl.java
@@ -2,28 +2,25 @@ package cn.iocoder.yudao.module.ai.service.write; @@ -2,28 +2,25 @@ package cn.iocoder.yudao.module.ai.service.write;
2 2
3 import cn.hutool.core.util.StrUtil; 3 import cn.hutool.core.util.StrUtil;
4 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; 4 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
5 -import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel;  
6 -import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoOptions; 5 +import cn.iocoder.yudao.framework.ai.core.util.AiUtils;
7 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 6 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
8 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 7 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
9 import cn.iocoder.yudao.module.ai.controller.admin.write.vo.AiWriteGenerateReqVO; 8 import cn.iocoder.yudao.module.ai.controller.admin.write.vo.AiWriteGenerateReqVO;
10 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; 9 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
11 import cn.iocoder.yudao.module.ai.dal.dataobject.write.AiWriteDO; 10 import cn.iocoder.yudao.module.ai.dal.dataobject.write.AiWriteDO;
12 import cn.iocoder.yudao.module.ai.dal.mysql.write.AiWriteMapper; 11 import cn.iocoder.yudao.module.ai.dal.mysql.write.AiWriteMapper;
  12 +import cn.iocoder.yudao.module.ai.enums.DictTypeConstants;
13 import cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants; 13 import cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants;
14 -import cn.iocoder.yudao.module.ai.enums.write.*; 14 +import cn.iocoder.yudao.module.ai.enums.write.AiWriteTypeEnum;
15 import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService; 15 import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService;
16 import cn.iocoder.yudao.module.ai.service.model.AiChatModelService; 16 import cn.iocoder.yudao.module.ai.service.model.AiChatModelService;
17 -import com.alibaba.cloud.ai.tongyi.chat.TongYiChatOptions; 17 +import cn.iocoder.yudao.module.system.api.dict.DictDataApi;
18 import jakarta.annotation.Resource; 18 import jakarta.annotation.Resource;
19 import lombok.extern.slf4j.Slf4j; 19 import lombok.extern.slf4j.Slf4j;
20 import org.springframework.ai.chat.model.ChatResponse; 20 import org.springframework.ai.chat.model.ChatResponse;
21 import org.springframework.ai.chat.model.StreamingChatModel; 21 import org.springframework.ai.chat.model.StreamingChatModel;
22 import org.springframework.ai.chat.prompt.ChatOptions; 22 import org.springframework.ai.chat.prompt.ChatOptions;
23 import org.springframework.ai.chat.prompt.Prompt; 23 import org.springframework.ai.chat.prompt.Prompt;
24 -import org.springframework.ai.ollama.api.OllamaOptions;  
25 -import org.springframework.ai.openai.OpenAiChatOptions;  
26 -import org.springframework.ai.qianfan.QianFanChatOptions;  
27 import org.springframework.stereotype.Service; 24 import org.springframework.stereotype.Service;
28 import reactor.core.publisher.Flux; 25 import reactor.core.publisher.Flux;
29 26
@@ -45,25 +42,30 @@ public class AiWriteServiceImpl implements AiWriteService { @@ -45,25 +42,30 @@ public class AiWriteServiceImpl implements AiWriteService {
45 private AiApiKeyService apiKeyService; 42 private AiApiKeyService apiKeyService;
46 @Resource 43 @Resource
47 private AiChatModelService chatModalService; 44 private AiChatModelService chatModalService;
  45 +
  46 + @Resource
  47 + private DictDataApi dictDataApi;
  48 +
48 @Resource 49 @Resource
49 - private AiWriteMapper writeMapper; // TODO @xin:上面空一行;因为同类之间不要空行,非同类空行; 50 + private AiWriteMapper writeMapper;
50 51
51 @Override 52 @Override
52 public Flux<CommonResult<String>> generateWriteContent(AiWriteGenerateReqVO generateReqVO, Long userId) { 53 public Flux<CommonResult<String>> generateWriteContent(AiWriteGenerateReqVO generateReqVO, Long userId) {
53 - // 1.1 校验模型  
54 - // TODO @xin:可以约定大于配置先,查询某个名字。例如说,写作助手!然后写作助手,上面是有个 model 的,可以使用它。  
55 - AiChatModelDO model = chatModalService.validateChatModel(14L);  
56 - StreamingChatModel chatClient = apiKeyService.getStreamingChatClient(model.getKeyId()); 54 + // 1.1 校验模型 TODO 芋艿 是不是取默认的模型也ok?;那可以,有限拿 chatRole 的角色;如果没有,则获取默认的;
  55 + AiChatModelDO model = chatModalService.getRequiredDefaultChatModel();
  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());  
59 58
60 // 1.2 插入写作信息 59 // 1.2 插入写作信息
  60 + // TODO @xin:建议把 writeDO.setUserId(userId).setModel(model.getModel()).setPlatform(platform.getPlatform()),写在 toBean 的 consumer 里;原因是,让这个 set 保持完整性
61 AiWriteDO writeDO = BeanUtils.toBean(generateReqVO, AiWriteDO.class); 61 AiWriteDO writeDO = BeanUtils.toBean(generateReqVO, AiWriteDO.class);
62 writeMapper.insert(writeDO.setUserId(userId).setModel(model.getModel()).setPlatform(platform.getPlatform())); 62 writeMapper.insert(writeDO.setUserId(userId).setModel(model.getModel()).setPlatform(platform.getPlatform()));
63 63
64 // 2.1 构建提示词 64 // 2.1 构建提示词
  65 + ChatOptions chatOptions = AiUtils.buildChatOptions(platform, model.getModel(), model.getTemperature(), model.getMaxTokens());
65 Prompt prompt = new Prompt(buildWritingPrompt(generateReqVO), chatOptions); 66 Prompt prompt = new Prompt(buildWritingPrompt(generateReqVO), chatOptions);
66 Flux<ChatResponse> streamResponse = chatClient.stream(prompt); 67 Flux<ChatResponse> streamResponse = chatClient.stream(prompt);
  68 +
67 // 2.2 流式返回 69 // 2.2 流式返回
68 StringBuffer contentBuffer = new StringBuffer(); 70 StringBuffer contentBuffer = new StringBuffer();
69 return streamResponse.map(chunk -> { 71 return streamResponse.map(chunk -> {
@@ -83,11 +85,13 @@ public class AiWriteServiceImpl implements AiWriteService { @@ -83,11 +85,13 @@ public class AiWriteServiceImpl implements AiWriteService {
83 private String buildWritingPrompt(AiWriteGenerateReqVO generateReqVO) { 85 private String buildWritingPrompt(AiWriteGenerateReqVO generateReqVO) {
84 String template; 86 String template;
85 Integer writeType = generateReqVO.getType(); 87 Integer writeType = generateReqVO.getType();
86 - String format = AiWriteFormatEnum.valueOfFormat(generateReqVO.getFormat()).getName();  
87 - String tone = AiWriteToneEnum.valueOfTone(generateReqVO.getTone()).getName();  
88 - String language = AiLanguageEnum.valueOfLanguage(generateReqVO.getLanguage()).getName();  
89 - String length = AiWriteLengthEnum.valueOfLength(generateReqVO.getLength()).getName(); 88 + String format = dictDataApi.getDictDataLabel(DictTypeConstants.AI_WRITE_FORMAT, generateReqVO.getFormat());
  89 + String tone = dictDataApi.getDictDataLabel(DictTypeConstants.AI_WRITE_TONE, generateReqVO.getFormat());
  90 + String language = dictDataApi.getDictDataLabel(DictTypeConstants.AI_WRITE_LANGUAGE, generateReqVO.getFormat());
  91 + String length = dictDataApi.getDictDataLabel(DictTypeConstants.AI_WRITE_LENGTH, generateReqVO.getFormat());
  92 + // TODO @xin:建议改成 if return 哈;更简洁;
90 if (Objects.equals(writeType, AiWriteTypeEnum.WRITING.getType())) { 93 if (Objects.equals(writeType, AiWriteTypeEnum.WRITING.getType())) {
  94 + // TODO @xin:写成静态枚举哈
91 template = "请撰写一篇关于 [{}] 的文章。文章的内容格式为:[{}],语气为:[{}],语言为:[{}],长度为:[{}]。请确保涵盖主要内容,不需要除了正文内容外的其他回复,如标题、额外的解释或道歉。"; 95 template = "请撰写一篇关于 [{}] 的文章。文章的内容格式为:[{}],语气为:[{}],语言为:[{}],长度为:[{}]。请确保涵盖主要内容,不需要除了正文内容外的其他回复,如标题、额外的解释或道歉。";
92 return StrUtil.format(template, generateReqVO.getPrompt(), format, tone, language, length); 96 return StrUtil.format(template, generateReqVO.getPrompt(), format, tone, language, length);
93 } else if (Objects.equals(writeType, AiWriteTypeEnum.REPLY.getType())) { 97 } else if (Objects.equals(writeType, AiWriteTypeEnum.REPLY.getType())) {
@@ -98,27 +102,4 @@ public class AiWriteServiceImpl implements AiWriteService { @@ -98,27 +102,4 @@ public class AiWriteServiceImpl implements AiWriteService {
98 } 102 }
99 } 103 }
100 104
101 - // TODO 芋艿:复用  
102 - private static ChatOptions buildChatOptions(AiPlatformEnum platform, String model, Double temperature, Integer maxTokens) {  
103 - Float temperatureF = temperature != null ? temperature.floatValue() : null;  
104 - //noinspection EnhancedSwitchMigration  
105 - switch (platform) {  
106 - case OPENAI:  
107 - return OpenAiChatOptions.builder().withModel(model).withTemperature(temperatureF).withMaxTokens(maxTokens).build();  
108 - case OLLAMA:  
109 - return OllamaOptions.create().withModel(model).withTemperature(temperatureF).withNumPredict(maxTokens);  
110 - case YI_YAN:  
111 - // TODO 芋艿:貌似 model 只要一设置,就报错  
112 -// return QianFanChatOptions.builder().withModel(model).withTemperature(temperatureF).withMaxTokens(maxTokens).build();  
113 - return QianFanChatOptions.builder().withTemperature(temperatureF).withMaxTokens(maxTokens).build();  
114 - case XING_HUO:  
115 - return new XingHuoOptions().setChatModel(XingHuoChatModel.valueOfModel(model)).setTemperature(temperatureF)  
116 - .setMaxTokens(maxTokens);  
117 - case QIAN_WEN:  
118 - return TongYiChatOptions.builder().withModel(model).withTemperature(temperature).withMaxTokens(maxTokens).build();  
119 - default:  
120 - throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform));  
121 - }  
122 - }  
123 -  
124 } 105 }
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:
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/util/AiUtils.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.ai.core.util;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
  5 +import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel;
  6 +import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoOptions;
  7 +import com.alibaba.cloud.ai.tongyi.chat.TongYiChatOptions;
  8 +import org.springframework.ai.chat.messages.*;
  9 +import org.springframework.ai.chat.prompt.ChatOptions;
  10 +import org.springframework.ai.ollama.api.OllamaOptions;
  11 +import org.springframework.ai.openai.OpenAiChatOptions;
  12 +import org.springframework.ai.qianfan.QianFanChatOptions;
  13 +
  14 +/**
  15 + * Spring AI 工具类
  16 + *
  17 + * @author 芋道源码
  18 + */
  19 +public class AiUtils {
  20 +
  21 + public static ChatOptions buildChatOptions(AiPlatformEnum platform, String model, Double temperature, Integer maxTokens) {
  22 + Float temperatureF = temperature != null ? temperature.floatValue() : null;
  23 + //noinspection EnhancedSwitchMigration
  24 + switch (platform) {
  25 + case OPENAI:
  26 + return OpenAiChatOptions.builder().withModel(model).withTemperature(temperatureF).withMaxTokens(maxTokens).build();
  27 + case OLLAMA:
  28 + return OllamaOptions.create().withModel(model).withTemperature(temperatureF).withNumPredict(maxTokens);
  29 + case YI_YAN:
  30 + // TODO @xin:貌似 model 只要一设置,就报错;可以排查下
  31 +// return QianFanChatOptions.builder().withModel(model).withTemperature(temperatureF).withMaxTokens(maxTokens).build();
  32 + return QianFanChatOptions.builder().withTemperature(temperatureF).withMaxTokens(maxTokens).build();
  33 + case XING_HUO:
  34 + return new XingHuoOptions().setChatModel(XingHuoChatModel.valueOfModel(model)).setTemperature(temperatureF)
  35 + .setMaxTokens(maxTokens);
  36 + case QIAN_WEN:
  37 + return TongYiChatOptions.builder().withModel(model).withTemperature(temperature).withMaxTokens(maxTokens).build();
  38 + default:
  39 + throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform));
  40 + }
  41 + }
  42 +
  43 + public static Message buildMessage(String type, String content) {
  44 + if (MessageType.USER.getValue().equals(type)) {
  45 + return new UserMessage(content);
  46 + }
  47 + if (MessageType.ASSISTANT.getValue().equals(type)) {
  48 + return new AssistantMessage(content);
  49 + }
  50 + if (MessageType.SYSTEM.getValue().equals(type)) {
  51 + return new SystemMessage(content);
  52 + }
  53 + if (MessageType.FUNCTION.getValue().equals(type)) {
  54 + return new FunctionMessage(content);
  55 + }
  56 + throw new IllegalArgumentException(StrUtil.format("未知消息类型({})", type));
  57 + }
  58 +
  59 +}
0 \ No newline at end of file 60 \ No newline at end of file
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/com/alibaba/cloud/ai/tongyi/chat/TongYiChatModel.java
@@ -236,6 +236,7 @@ public class TongYiChatModel extends @@ -236,6 +236,7 @@ public class TongYiChatModel extends
236 .model(Generation.Models.QWEN_TURBO) 236 .model(Generation.Models.QWEN_TURBO)
237 // {@link GenerationOutput} 237 // {@link GenerationOutput}
238 .resultFormat(ConversationParam.ResultFormat.MESSAGE) 238 .resultFormat(ConversationParam.ResultFormat.MESSAGE)
  239 + .incrementalOutput(true)
239 240
240 .build(); 241 .build();
241 242