Commit f3a6ba834913094cd8b7f6dbf0d46ccd1c3c0bef
Committed by
Gitee
!12 【优化】AI 写作:1. 优先获取写作角色;2. 优化写作提示词
Merge pull request !12 from 小新/master-jdk21-ai
Showing
2 changed files
with
56 additions
and
22 deletions
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/write/AiWriteTypeEnum.java
| 1 | package cn.iocoder.yudao.module.ai.enums.write; | 1 | package cn.iocoder.yudao.module.ai.enums.write; |
| 2 | 2 | ||
| 3 | +import cn.hutool.core.util.ArrayUtil; | ||
| 4 | +import cn.hutool.core.util.StrUtil; | ||
| 3 | import cn.iocoder.yudao.framework.common.core.IntArrayValuable; | 5 | import cn.iocoder.yudao.framework.common.core.IntArrayValuable; |
| 4 | import lombok.AllArgsConstructor; | 6 | import lombok.AllArgsConstructor; |
| 5 | import lombok.Getter; | 7 | import lombok.Getter; |
| @@ -15,8 +17,8 @@ import java.util.Arrays; | @@ -15,8 +17,8 @@ import java.util.Arrays; | ||
| 15 | @Getter | 17 | @Getter |
| 16 | public enum AiWriteTypeEnum implements IntArrayValuable { | 18 | public enum AiWriteTypeEnum implements IntArrayValuable { |
| 17 | 19 | ||
| 18 | - WRITING(1, "撰写"), | ||
| 19 | - REPLY(2, "回复"); | 20 | + WRITING(1, "撰写", "请撰写一篇关于 [{}] 的文章。文章的内容格式:{},语气:{},语言:{},长度:{}。请确保涵盖主要内容,不需要除了正文内容外的其他回复,如标题、额外的解释或道歉。"), |
| 21 | + REPLY(2, "回复", "请针对如下内容:[{}] 做个回复。回复内容参考:[{}], 回复格式:{},语气:{},语言:{},长度:{}。不需要除了正文内容外的其他回复,如标题、开头、额外的解释或道歉。"); | ||
| 20 | 22 | ||
| 21 | /** | 23 | /** |
| 22 | * 类型 | 24 | * 类型 |
| @@ -27,6 +29,11 @@ public enum AiWriteTypeEnum implements IntArrayValuable { | @@ -27,6 +29,11 @@ public enum AiWriteTypeEnum implements IntArrayValuable { | ||
| 27 | */ | 29 | */ |
| 28 | private final String name; | 30 | private final String name; |
| 29 | 31 | ||
| 32 | + /** | ||
| 33 | + * 模版 | ||
| 34 | + */ | ||
| 35 | + private final String template; | ||
| 36 | + | ||
| 30 | public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(AiWriteTypeEnum::getType).toArray(); | 37 | public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(AiWriteTypeEnum::getType).toArray(); |
| 31 | 38 | ||
| 32 | @Override | 39 | @Override |
| @@ -34,4 +41,9 @@ public enum AiWriteTypeEnum implements IntArrayValuable { | @@ -34,4 +41,9 @@ public enum AiWriteTypeEnum implements IntArrayValuable { | ||
| 34 | return ARRAYS; | 41 | return ARRAYS; |
| 35 | } | 42 | } |
| 36 | 43 | ||
| 44 | + public static void validateType(Integer type) { | ||
| 45 | + if (ArrayUtil.contains(ARRAYS, type)) return; | ||
| 46 | + throw new IllegalArgumentException(StrUtil.format("未知写作类型({})", type)); | ||
| 47 | + } | ||
| 48 | + | ||
| 37 | } | 49 | } |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/write/AiWriteServiceImpl.java
| 1 | package cn.iocoder.yudao.module.ai.service.write; | 1 | package cn.iocoder.yudao.module.ai.service.write; |
| 2 | 2 | ||
| 3 | +import cn.hutool.core.collection.CollUtil; | ||
| 3 | import cn.hutool.core.util.StrUtil; | 4 | import cn.hutool.core.util.StrUtil; |
| 4 | import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; | 5 | import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; |
| 5 | import cn.iocoder.yudao.framework.ai.core.util.AiUtils; | 6 | import cn.iocoder.yudao.framework.ai.core.util.AiUtils; |
| 6 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; | 7 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 8 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||
| 7 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | 9 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 8 | import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; | 10 | import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; |
| 11 | +import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole.AiChatRolePageReqVO; | ||
| 9 | import cn.iocoder.yudao.module.ai.controller.admin.write.vo.AiWriteGenerateReqVO; | 12 | import cn.iocoder.yudao.module.ai.controller.admin.write.vo.AiWriteGenerateReqVO; |
| 10 | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; | 13 | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; |
| 14 | +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; | ||
| 11 | import cn.iocoder.yudao.module.ai.dal.dataobject.write.AiWriteDO; | 15 | import cn.iocoder.yudao.module.ai.dal.dataobject.write.AiWriteDO; |
| 12 | import cn.iocoder.yudao.module.ai.dal.mysql.write.AiWriteMapper; | 16 | import cn.iocoder.yudao.module.ai.dal.mysql.write.AiWriteMapper; |
| 13 | import cn.iocoder.yudao.module.ai.enums.DictTypeConstants; | 17 | import cn.iocoder.yudao.module.ai.enums.DictTypeConstants; |
| @@ -15,6 +19,7 @@ import cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants; | @@ -15,6 +19,7 @@ import cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants; | ||
| 15 | import cn.iocoder.yudao.module.ai.enums.write.AiWriteTypeEnum; | 19 | import cn.iocoder.yudao.module.ai.enums.write.AiWriteTypeEnum; |
| 16 | import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService; | 20 | import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService; |
| 17 | import cn.iocoder.yudao.module.ai.service.model.AiChatModelService; | 21 | import cn.iocoder.yudao.module.ai.service.model.AiChatModelService; |
| 22 | +import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService; | ||
| 18 | import cn.iocoder.yudao.module.system.api.dict.DictDataApi; | 23 | import cn.iocoder.yudao.module.system.api.dict.DictDataApi; |
| 19 | import jakarta.annotation.Resource; | 24 | import jakarta.annotation.Resource; |
| 20 | import lombok.extern.slf4j.Slf4j; | 25 | import lombok.extern.slf4j.Slf4j; |
| @@ -25,6 +30,7 @@ import org.springframework.ai.chat.prompt.Prompt; | @@ -25,6 +30,7 @@ import org.springframework.ai.chat.prompt.Prompt; | ||
| 25 | import org.springframework.stereotype.Service; | 30 | import org.springframework.stereotype.Service; |
| 26 | import reactor.core.publisher.Flux; | 31 | import reactor.core.publisher.Flux; |
| 27 | 32 | ||
| 33 | +import java.util.List; | ||
| 28 | import java.util.Objects; | 34 | import java.util.Objects; |
| 29 | 35 | ||
| 30 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.error; | 36 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.error; |
| @@ -43,6 +49,8 @@ public class AiWriteServiceImpl implements AiWriteService { | @@ -43,6 +49,8 @@ public class AiWriteServiceImpl implements AiWriteService { | ||
| 43 | private AiApiKeyService apiKeyService; | 49 | private AiApiKeyService apiKeyService; |
| 44 | @Resource | 50 | @Resource |
| 45 | private AiChatModelService chatModalService; | 51 | private AiChatModelService chatModalService; |
| 52 | + @Resource | ||
| 53 | + private AiChatRoleService chatRoleService; | ||
| 46 | 54 | ||
| 47 | @Resource | 55 | @Resource |
| 48 | private DictDataApi dictDataApi; | 56 | private DictDataApi dictDataApi; |
| @@ -52,15 +60,22 @@ public class AiWriteServiceImpl implements AiWriteService { | @@ -52,15 +60,22 @@ public class AiWriteServiceImpl implements AiWriteService { | ||
| 52 | 60 | ||
| 53 | @Override | 61 | @Override |
| 54 | public Flux<CommonResult<String>> generateWriteContent(AiWriteGenerateReqVO generateReqVO, Long userId) { | 62 | public Flux<CommonResult<String>> generateWriteContent(AiWriteGenerateReqVO generateReqVO, Long userId) { |
| 55 | - // 1.1 校验模型 TODO 芋艿 是不是取默认的模型也ok?;那可以,有限拿 chatRole 的角色;如果没有,则获取默认的; | ||
| 56 | - AiChatModelDO model = chatModalService.getRequiredDefaultChatModel(); | ||
| 57 | - StreamingChatModel chatModel = apiKeyService.getChatModel(model.getKeyId()); | 63 | + // 1.1 获取写作模型 尝试获取写作助手角色,如果没有则使用默认模型 |
| 64 | + AiChatRoleDO writeRole = selectOneWriteRole(); | ||
| 65 | + AiChatModelDO model; | ||
| 66 | + if (Objects.nonNull(writeRole)) { | ||
| 67 | + model = chatModalService.getChatModel(writeRole.getModelId()); | ||
| 68 | + } else { | ||
| 69 | + model = chatModalService.getRequiredDefaultChatModel(); | ||
| 70 | + } | ||
| 71 | + | ||
| 58 | AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform()); | 72 | AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform()); |
| 59 | 73 | ||
| 74 | + StreamingChatModel chatModel = apiKeyService.getChatModel(model.getKeyId()); | ||
| 75 | + | ||
| 60 | // 1.2 插入写作信息 | 76 | // 1.2 插入写作信息 |
| 61 | - // TODO @xin:建议把 writeDO.setUserId(userId).setModel(model.getModel()).setPlatform(platform.getPlatform()),写在 toBean 的 consumer 里;原因是,让这个 set 保持完整性 | ||
| 62 | - AiWriteDO writeDO = BeanUtils.toBean(generateReqVO, AiWriteDO.class); | ||
| 63 | - writeMapper.insert(writeDO.setUserId(userId).setModel(model.getModel()).setPlatform(platform.getPlatform())); | 77 | + AiWriteDO writeDO = BeanUtils.toBean(generateReqVO, AiWriteDO.class, e -> e.setUserId(userId).setModel(model.getModel()).setPlatform(platform.getPlatform())); |
| 78 | + writeMapper.insert(writeDO); | ||
| 64 | 79 | ||
| 65 | // 2.1 构建提示词 | 80 | // 2.1 构建提示词 |
| 66 | ChatOptions chatOptions = AiUtils.buildChatOptions(platform, model.getModel(), model.getTemperature(), model.getMaxTokens()); | 81 | ChatOptions chatOptions = AiUtils.buildChatOptions(platform, model.getModel(), model.getTemperature(), model.getMaxTokens()); |
| @@ -87,23 +102,30 @@ public class AiWriteServiceImpl implements AiWriteService { | @@ -87,23 +102,30 @@ public class AiWriteServiceImpl implements AiWriteService { | ||
| 87 | }).onErrorResume(error -> Flux.just(error(ErrorCodeConstants.WRITE_STREAM_ERROR))); | 102 | }).onErrorResume(error -> Flux.just(error(ErrorCodeConstants.WRITE_STREAM_ERROR))); |
| 88 | } | 103 | } |
| 89 | 104 | ||
| 105 | + private AiChatRoleDO selectOneWriteRole() { | ||
| 106 | + AiChatRoleDO chatRoleDO = null; | ||
| 107 | + PageResult<AiChatRoleDO> writeRolePage = chatRoleService.getChatRolePage(new AiChatRolePageReqVO().setName("写作助手")); | ||
| 108 | + List<AiChatRoleDO> list = writeRolePage.getList(); | ||
| 109 | + if (CollUtil.isNotEmpty(list)) { | ||
| 110 | + chatRoleDO = list.get(0); | ||
| 111 | + } | ||
| 112 | + return chatRoleDO; | ||
| 113 | + } | ||
| 114 | + | ||
| 90 | private String buildWritingPrompt(AiWriteGenerateReqVO generateReqVO) { | 115 | private String buildWritingPrompt(AiWriteGenerateReqVO generateReqVO) { |
| 91 | - String template; | ||
| 92 | - Integer writeType = generateReqVO.getType(); | 116 | + Integer type = generateReqVO.getType(); |
| 93 | String format = dictDataApi.getDictDataLabel(DictTypeConstants.AI_WRITE_FORMAT, generateReqVO.getFormat()); | 117 | String format = dictDataApi.getDictDataLabel(DictTypeConstants.AI_WRITE_FORMAT, generateReqVO.getFormat()); |
| 94 | - String tone = dictDataApi.getDictDataLabel(DictTypeConstants.AI_WRITE_TONE, generateReqVO.getFormat()); | ||
| 95 | - String language = dictDataApi.getDictDataLabel(DictTypeConstants.AI_WRITE_LANGUAGE, generateReqVO.getFormat()); | ||
| 96 | - String length = dictDataApi.getDictDataLabel(DictTypeConstants.AI_WRITE_LENGTH, generateReqVO.getFormat()); | ||
| 97 | - // TODO @xin:建议改成 if return 哈;更简洁; | ||
| 98 | - if (Objects.equals(writeType, AiWriteTypeEnum.WRITING.getType())) { | ||
| 99 | - // TODO @xin:写成静态枚举哈 | ||
| 100 | - template = "请撰写一篇关于 [{}] 的文章。文章的内容格式为:[{}],语气为:[{}],语言为:[{}],长度为:[{}]。请确保涵盖主要内容,不需要除了正文内容外的其他回复,如标题、额外的解释或道歉。"; | ||
| 101 | - return StrUtil.format(template, generateReqVO.getPrompt(), format, tone, language, length); | ||
| 102 | - } else if (Objects.equals(writeType, AiWriteTypeEnum.REPLY.getType())) { | ||
| 103 | - template = "请针对如下内容:[{}] 做个回复。回复内容参考:[{}], 回复的内容格式为:[{}],语气为:[{}],语言为:[{}],长度为:[{}]。不需要除了正文内容外的其他回复,如标题、额外的解释或道歉。"; | ||
| 104 | - return StrUtil.format(template, generateReqVO.getOriginalContent(), generateReqVO.getPrompt(), format, tone, language, length); | 118 | + String tone = dictDataApi.getDictDataLabel(DictTypeConstants.AI_WRITE_TONE, generateReqVO.getTone()); |
| 119 | + String language = dictDataApi.getDictDataLabel(DictTypeConstants.AI_WRITE_LANGUAGE, generateReqVO.getLanguage()); | ||
| 120 | + String length = dictDataApi.getDictDataLabel(DictTypeConstants.AI_WRITE_LENGTH, generateReqVO.getLength()); | ||
| 121 | + String prompt = generateReqVO.getPrompt(); | ||
| 122 | + // 校验写作类型是否合法 | ||
| 123 | + AiWriteTypeEnum.validateType(type); | ||
| 124 | + | ||
| 125 | + if (Objects.equals(type, AiWriteTypeEnum.WRITING.getType())) { | ||
| 126 | + return StrUtil.format(AiWriteTypeEnum.WRITING.getTemplate(), prompt, format, tone, language, length); | ||
| 105 | } else { | 127 | } else { |
| 106 | - throw new IllegalArgumentException(StrUtil.format("未知写作类型({})", writeType)); | 128 | + return StrUtil.format(AiWriteTypeEnum.REPLY.getTemplate(), generateReqVO.getOriginalContent(), prompt, format, tone, language, length); |
| 107 | } | 129 | } |
| 108 | } | 130 | } |
| 109 | 131 |