Commit f20c27a7eff09108dc33dbed9145b3ca6f7927ee
1 parent
9ddd2edd
【代码评审】AI:写作实现
Showing
7 changed files
with
43 additions
and
30 deletions
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 | } |