Commit f20c27a7eff09108dc33dbed9145b3ca6f7927ee

Authored by YunaiV
1 parent 9ddd2edd

【代码评审】AI:写作实现

yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/ErrorCodeConstants.java
@@ -33,7 +33,7 @@ public interface ErrorCodeConstants { @@ -33,7 +33,7 @@ public interface ErrorCodeConstants {
33 // ========== API 聊天消息 1-040-004-000 ========== 33 // ========== API 聊天消息 1-040-004-000 ==========
34 34
35 ErrorCode CHAT_MESSAGE_NOT_EXIST = new ErrorCode(1_040_004_000, "消息不存在!"); 35 ErrorCode CHAT_MESSAGE_NOT_EXIST = new ErrorCode(1_040_004_000, "消息不存在!");
36 - ErrorCode CHAT_STREAM_ERROR = new ErrorCode(1_040_004_001, "Stream 对话异常!"); 36 + ErrorCode CHAT_STREAM_ERROR = new ErrorCode(1_040_004_001, "对话生成异常!");
37 37
38 // ========== API 绘画 1-040-005-000 ========== 38 // ========== API 绘画 1-040-005-000 ==========
39 39
@@ -48,6 +48,6 @@ public interface ErrorCodeConstants { @@ -48,6 +48,6 @@ public interface ErrorCodeConstants {
48 48
49 // ========== API 写作 1-022-007-000 ========== 49 // ========== API 写作 1-022-007-000 ==========
50 ErrorCode WRITE_NOT_EXISTS = new ErrorCode(1_022_007_000, "作文不存在!"); 50 ErrorCode WRITE_NOT_EXISTS = new ErrorCode(1_022_007_000, "作文不存在!");
51 - ErrorCode WRITE_STREAM_ERROR = new ErrorCode(1_022_07_001, "Stream 对话异常!"); 51 + ErrorCode WRITE_STREAM_ERROR = new ErrorCode(1_022_07_001, "写作生成异常!");
52 52
53 } 53 }
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/write/AiLanguageEnum.java
@@ -6,6 +6,7 @@ import lombok.Getter; @@ -6,6 +6,7 @@ import lombok.Getter;
6 6
7 import java.util.Arrays; 7 import java.util.Arrays;
8 8
  9 +// TODO @xin:写作的几个,不用枚举类哈。直接搞字段就好了。AiWriteTypeEnum 还是需要的哈
9 @AllArgsConstructor 10 @AllArgsConstructor
10 @Getter 11 @Getter
11 public enum AiLanguageEnum implements IntArrayValuable { 12 public enum AiLanguageEnum implements IntArrayValuable {
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/write/vo/AiWriteGenerateReqVO.java
1 package cn.iocoder.yudao.module.ai.controller.admin.write.vo; 1 package cn.iocoder.yudao.module.ai.controller.admin.write.vo;
2 2
  3 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  4 +import cn.iocoder.yudao.module.ai.enums.write.AiWriteTypeEnum;
3 import io.swagger.v3.oas.annotations.media.Schema; 5 import io.swagger.v3.oas.annotations.media.Schema;
4 import jakarta.validation.constraints.NotNull; 6 import jakarta.validation.constraints.NotNull;
5 import lombok.Data; 7 import lombok.Data;
@@ -8,6 +10,11 @@ import lombok.Data; @@ -8,6 +10,11 @@ import lombok.Data;
8 @Data 10 @Data
9 public class AiWriteGenerateReqVO { 11 public class AiWriteGenerateReqVO {
10 12
  13 + @Schema(description = "写作类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  14 + @InEnum(AiWriteTypeEnum.class)
  15 + private Integer type;
  16 +
  17 + // TODO @xin:如果非必填,可以不用写 requiredMode
11 @Schema(description = "写作内容提示", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1.撰写:田忌赛马;2.回复:不批") 18 @Schema(description = "写作内容提示", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1.撰写:田忌赛马;2.回复:不批")
12 private String prompt; 19 private String prompt;
13 20
@@ -30,7 +37,4 @@ public class AiWriteGenerateReqVO { @@ -30,7 +37,4 @@ public class AiWriteGenerateReqVO {
30 @NotNull(message = "语言不能为空") 37 @NotNull(message = "语言不能为空")
31 private Integer language; 38 private Integer language;
32 39
33 -  
34 - @Schema(description = "写作类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")  
35 - private Integer type; //参见 AiWriteTypeEnum 枚举  
36 } 40 }
37 \ No newline at end of file 41 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/write/AiWriteDO.java
1 package cn.iocoder.yudao.module.ai.dal.dataobject.write; 1 package cn.iocoder.yudao.module.ai.dal.dataobject.write;
2 2
  3 +import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
4 import com.baomidou.mybatisplus.annotation.IdType; 5 import com.baomidou.mybatisplus.annotation.IdType;
5 import com.baomidou.mybatisplus.annotation.TableId; 6 import com.baomidou.mybatisplus.annotation.TableId;
@@ -35,6 +36,18 @@ public class AiWriteDO extends BaseDO { @@ -35,6 +36,18 @@ public class AiWriteDO extends BaseDO {
35 private Integer type; 36 private Integer type;
36 37
37 /** 38 /**
  39 + * 模型
  40 + */
  41 + private String model;
  42 +
  43 + /**
  44 + * 平台
  45 + *
  46 + * 枚举 {@link AiPlatformEnum}
  47 + */
  48 + private String platform;
  49 +
  50 + /**
38 * 生成内容提示 51 * 生成内容提示
39 */ 52 */
40 private String prompt; 53 private String prompt;
@@ -70,16 +83,6 @@ public class AiWriteDO extends BaseDO { @@ -70,16 +83,6 @@ public class AiWriteDO extends BaseDO {
70 private Integer language; 83 private Integer language;
71 84
72 /** 85 /**
73 - * 模型  
74 - */  
75 - private String model;  
76 -  
77 - /**  
78 - * 平台  
79 - */  
80 - private String platform;  
81 -  
82 - /**  
83 * 错误信息 86 * 错误信息
84 */ 87 */
85 private String errorMessage; 88 private String errorMessage;
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/write/AiWriteService.java
@@ -11,7 +11,6 @@ import reactor.core.publisher.Flux; @@ -11,7 +11,6 @@ import reactor.core.publisher.Flux;
11 */ 11 */
12 public interface AiWriteService { 12 public interface AiWriteService {
13 13
14 -  
15 /** 14 /**
16 * 生成写作内容 15 * 生成写作内容
17 * 16 *
@@ -21,5 +20,4 @@ public interface AiWriteService { @@ -21,5 +20,4 @@ public interface AiWriteService {
21 */ 20 */
22 Flux<CommonResult<String>> generateWriteContent(AiWriteGenerateReqVO generateReqVO, Long userId); 21 Flux<CommonResult<String>> generateWriteContent(AiWriteGenerateReqVO generateReqVO, Long userId);
23 22
24 -  
25 } 23 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/write/AiWriteServiceImpl.java
@@ -46,23 +46,22 @@ public class AiWriteServiceImpl implements AiWriteService { @@ -46,23 +46,22 @@ public class AiWriteServiceImpl implements AiWriteService {
46 @Resource 46 @Resource
47 private AiChatModelService chatModalService; 47 private AiChatModelService chatModalService;
48 @Resource 48 @Resource
49 - private AiWriteMapper writeMapper;  
50 - 49 + private AiWriteMapper writeMapper; // TODO @xin:上面空一行;因为同类之间不要空行,非同类空行;
51 50
52 @Override 51 @Override
53 public Flux<CommonResult<String>> generateWriteContent(AiWriteGenerateReqVO generateReqVO, Long userId) { 52 public Flux<CommonResult<String>> generateWriteContent(AiWriteGenerateReqVO generateReqVO, Long userId) {
54 - //TODO 芋艿 写作的模型配置放哪好 先用千问测试  
55 // 1.1 校验模型 53 // 1.1 校验模型
  54 + // TODO @xin:可以约定大于配置先,查询某个名字。例如说,写作助手!然后写作助手,上面是有个 model 的,可以使用它。
56 AiChatModelDO model = chatModalService.validateChatModel(14L); 55 AiChatModelDO model = chatModalService.validateChatModel(14L);
57 StreamingChatModel chatClient = apiKeyService.getStreamingChatClient(model.getKeyId()); 56 StreamingChatModel chatClient = apiKeyService.getStreamingChatClient(model.getKeyId());
58 AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform()); 57 AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform());
59 ChatOptions chatOptions = buildChatOptions(platform, model.getModel(), model.getTemperature(), model.getMaxTokens()); 58 ChatOptions chatOptions = buildChatOptions(platform, model.getModel(), model.getTemperature(), model.getMaxTokens());
60 59
61 - //1.2 插入写作信息 60 + // 1.2 插入写作信息
62 AiWriteDO writeDO = BeanUtils.toBean(generateReqVO, AiWriteDO.class); 61 AiWriteDO writeDO = BeanUtils.toBean(generateReqVO, AiWriteDO.class);
63 writeMapper.insert(writeDO.setUserId(userId).setModel(model.getModel()).setPlatform(platform.getPlatform())); 62 writeMapper.insert(writeDO.setUserId(userId).setModel(model.getModel()).setPlatform(platform.getPlatform()));
64 63
65 - //2.1 构建提示词 64 + // 2.1 构建提示词
66 Prompt prompt = new Prompt(buildWritingPrompt(generateReqVO), chatOptions); 65 Prompt prompt = new Prompt(buildWritingPrompt(generateReqVO), chatOptions);
67 Flux<ChatResponse> streamResponse = chatClient.stream(prompt); 66 Flux<ChatResponse> streamResponse = chatClient.stream(prompt);
68 // 2.2 流式返回 67 // 2.2 流式返回
@@ -81,7 +80,6 @@ public class AiWriteServiceImpl implements AiWriteService { @@ -81,7 +80,6 @@ public class AiWriteServiceImpl implements AiWriteService {
81 }).onErrorResume(error -> Flux.just(error(ErrorCodeConstants.WRITE_STREAM_ERROR))); 80 }).onErrorResume(error -> Flux.just(error(ErrorCodeConstants.WRITE_STREAM_ERROR)));
82 } 81 }
83 82
84 -  
85 private String buildWritingPrompt(AiWriteGenerateReqVO generateReqVO) { 83 private String buildWritingPrompt(AiWriteGenerateReqVO generateReqVO) {
86 String template; 84 String template;
87 Integer writeType = generateReqVO.getType(); 85 Integer writeType = generateReqVO.getType();
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/midjourney/api/MidjourneyApi.java
@@ -9,12 +9,17 @@ import lombok.Data; @@ -9,12 +9,17 @@ import lombok.Data;
9 import lombok.Getter; 9 import lombok.Getter;
10 import lombok.extern.slf4j.Slf4j; 10 import lombok.extern.slf4j.Slf4j;
11 import org.springframework.ai.openai.api.ApiUtils; 11 import org.springframework.ai.openai.api.ApiUtils;
  12 +import org.springframework.http.HttpRequest;
  13 +import org.springframework.http.HttpStatusCode;
  14 +import org.springframework.web.reactive.function.client.ClientResponse;
12 import org.springframework.web.reactive.function.client.WebClient; 15 import org.springframework.web.reactive.function.client.WebClient;
13 import reactor.core.publisher.Mono; 16 import reactor.core.publisher.Mono;
14 17
15 import java.util.Collection; 18 import java.util.Collection;
16 import java.util.List; 19 import java.util.List;
17 import java.util.Map; 20 import java.util.Map;
  21 +import java.util.function.Function;
  22 +import java.util.function.Predicate;
18 23
19 /** 24 /**
20 * Midjourney API 25 * Midjourney API
@@ -25,6 +30,16 @@ import java.util.Map; @@ -25,6 +30,16 @@ import java.util.Map;
25 @Slf4j 30 @Slf4j
26 public class MidjourneyApi { 31 public class MidjourneyApi {
27 32
  33 + private final Predicate<HttpStatusCode> STATUS_PREDICATE = status -> !status.is2xxSuccessful();
  34 +
  35 + private final Function<Object, Function<ClientResponse, Mono<? extends Throwable>>> EXCEPTION_FUNCTION =
  36 + reqParam -> response -> response.bodyToMono(String.class).handle((responseBody, sink) -> {
  37 + HttpRequest request = response.request();
  38 + log.error("[midjourney-api] 调用失败!请求方式:[{}],请求地址:[{}],请求参数:[{}],响应数据: [{}]",
  39 + request.getMethod(), request.getURI(), reqParam, responseBody);
  40 + sink.error(new IllegalStateException("[midjourney-api] 调用失败!"));
  41 + });
  42 +
28 private final WebClient webClient; 43 private final WebClient webClient;
29 44
30 /** 45 /**
@@ -80,17 +95,11 @@ public class MidjourneyApi { @@ -80,17 +95,11 @@ public class MidjourneyApi {
80 } 95 }
81 96
82 private String post(String uri, Object body) { 97 private String post(String uri, Object body) {
83 - // 1、发送 post 请求  
84 return webClient.post() 98 return webClient.post()
85 .uri(uri) 99 .uri(uri)
86 .body(Mono.just(JsonUtils.toJsonString(body)), String.class) 100 .body(Mono.just(JsonUtils.toJsonString(body)), String.class)
87 .retrieve() 101 .retrieve()
88 - .onStatus(status -> !status.is2xxSuccessful(),  
89 - response -> response.bodyToMono(String.class)  
90 - .handle((respBody, sink) -> {  
91 - log.error("【Midjourney api】调用失败!resp: 【{}】", respBody);  
92 - sink.error(new IllegalStateException("【Midjourney api】调用失败!"));  
93 - })) 102 + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(body))
94 .bodyToMono(String.class) 103 .bodyToMono(String.class)
95 .block(); 104 .block();
96 } 105 }