Commit f3a6ba834913094cd8b7f6dbf0d46ccd1c3c0bef

Authored by 芋道源码
Committed by Gitee
2 parents 870b1bf3 6e71b721

!12 【优化】AI 写作:1. 优先获取写作角色;2. 优化写作提示词

Merge pull request !12 from 小新/master-jdk21-ai
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