Commit 45a9dfc4fac642cb497ad4356d42b5a442f43ded
1 parent
69d99aa4
feat:【ai 大模型】增加 thinking 深度思考
Showing
6 changed files
with
60 additions
and
11 deletions
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageRespVO.java
| ... | ... | @@ -37,6 +37,9 @@ public class AiChatMessageRespVO { |
| 37 | 37 | @Schema(description = "聊天内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "你好,你好啊") |
| 38 | 38 | private String content; |
| 39 | 39 | |
| 40 | + @Schema(description = "推理内容", example = "要达到这个目标,你需要...") | |
| 41 | + private String reasoningContent; | |
| 42 | + | |
| 40 | 43 | @Schema(description = "是否携带上下文", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") |
| 41 | 44 | private Boolean useContext; |
| 42 | 45 | ... | ... |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageSendRespVO.java
| ... | ... | @@ -29,6 +29,9 @@ public class AiChatMessageSendRespVO { |
| 29 | 29 | @Schema(description = "聊天内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "你好,你好啊") |
| 30 | 30 | private String content; |
| 31 | 31 | |
| 32 | + @Schema(description = "推理内容", example = "要达到这个目标,你需要...") | |
| 33 | + private String reasoningContent; | |
| 34 | + | |
| 32 | 35 | @Schema(description = "知识库段落编号数组", example = "[1,2,3]") |
| 33 | 36 | private List<Long> segmentIds; |
| 34 | 37 | ... | ... |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatMessageDO.java
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java
| ... | ... | @@ -3,8 +3,6 @@ package cn.iocoder.yudao.module.ai.service.chat; |
| 3 | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | 4 | import cn.hutool.core.util.ObjUtil; |
| 5 | 5 | import cn.hutool.core.util.StrUtil; |
| 6 | -import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum; | |
| 7 | -import cn.iocoder.yudao.module.ai.util.AiUtils; | |
| 8 | 6 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 9 | 7 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 10 | 8 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| ... | ... | @@ -21,6 +19,7 @@ import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO; |
| 21 | 19 | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiToolDO; |
| 22 | 20 | import cn.iocoder.yudao.module.ai.dal.mysql.chat.AiChatMessageMapper; |
| 23 | 21 | import cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants; |
| 22 | +import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum; | |
| 24 | 23 | import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeDocumentService; |
| 25 | 24 | import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeSegmentService; |
| 26 | 25 | import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchReqBO; |
| ... | ... | @@ -28,6 +27,7 @@ import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchR |
| 28 | 27 | import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService; |
| 29 | 28 | import cn.iocoder.yudao.module.ai.service.model.AiModelService; |
| 30 | 29 | import cn.iocoder.yudao.module.ai.service.model.AiToolService; |
| 30 | +import cn.iocoder.yudao.module.ai.util.AiUtils; | |
| 31 | 31 | import jakarta.annotation.Resource; |
| 32 | 32 | import lombok.extern.slf4j.Slf4j; |
| 33 | 33 | import org.springframework.ai.chat.messages.Message; |
| ... | ... | @@ -118,8 +118,10 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { |
| 118 | 118 | ChatResponse chatResponse = chatModel.call(prompt); |
| 119 | 119 | |
| 120 | 120 | // 3.3 更新响应内容 |
| 121 | - String newContent = chatResponse.getResult().getOutput().getText(); | |
| 122 | - chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(newContent)); | |
| 121 | + String newContent = AiUtils.getChatResponseContent(chatResponse); | |
| 122 | + String newReasoningContent = AiUtils.getChatResponseReasoningContent(chatResponse); | |
| 123 | + chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()) | |
| 124 | + .setContent(newContent).setReasoningContent(newReasoningContent)); | |
| 123 | 125 | // 3.4 响应结果 |
| 124 | 126 | Map<Long, AiKnowledgeDocumentDO> documentMap = knowledgeDocumentService.getKnowledgeDocumentMap( |
| 125 | 127 | convertSet(knowledgeSegments, AiKnowledgeSegmentSearchRespBO::getDocumentId)); |
| ... | ... | @@ -168,6 +170,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { |
| 168 | 170 | |
| 169 | 171 | // 4.3 流式返回 |
| 170 | 172 | StringBuffer contentBuffer = new StringBuffer(); |
| 173 | + StringBuffer reasoningContentBuffer = new StringBuffer(); | |
| 171 | 174 | return streamResponse.map(chunk -> { |
| 172 | 175 | // 处理知识库的返回,只有首次才有 |
| 173 | 176 | List<AiChatMessageRespVO.KnowledgeSegment> segments = null; |
| ... | ... | @@ -181,22 +184,31 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { |
| 181 | 184 | }); |
| 182 | 185 | } |
| 183 | 186 | // 响应结果 |
| 184 | - String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getText() : null; | |
| 185 | - newContent = StrUtil.nullToDefault(newContent, ""); // 避免 null 的 情况 | |
| 186 | - contentBuffer.append(newContent); | |
| 187 | + String newContent = AiUtils.getChatResponseContent(chunk); | |
| 188 | + String newReasoningContent = AiUtils.getChatResponseReasoningContent(chunk); | |
| 189 | + if (StrUtil.isNotEmpty(newContent)) { | |
| 190 | + contentBuffer.append(newContent); | |
| 191 | + } | |
| 192 | + if (StrUtil.isNotEmpty(newReasoningContent)) { | |
| 193 | + reasoningContentBuffer.append(newReasoningContent); | |
| 194 | + } | |
| 187 | 195 | return success(new AiChatMessageSendRespVO() |
| 188 | 196 | .setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class)) |
| 189 | 197 | .setReceive(BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class) |
| 190 | - .setContent(newContent).setSegments(segments))); | |
| 198 | + .setContent(StrUtil.nullToDefault(newContent, "")) // 避免 null 的 情况 | |
| 199 | + .setReasoningContent(StrUtil.nullToDefault(newReasoningContent, "")) // 避免 null 的 情况 | |
| 200 | + .setSegments(segments))); // 知识库返回 | |
| 191 | 201 | }).doOnComplete(() -> { |
| 192 | 202 | // 忽略租户,因为 Flux 异步无法透传租户 |
| 193 | 203 | TenantUtils.executeIgnore(() -> chatMessageMapper.updateById( |
| 194 | - new AiChatMessageDO().setId(assistantMessage.getId()).setContent(contentBuffer.toString()))); | |
| 204 | + new AiChatMessageDO().setId(assistantMessage.getId()).setContent(contentBuffer.toString()) | |
| 205 | + .setReasoningContent(reasoningContentBuffer.toString()))); | |
| 195 | 206 | }).doOnError(throwable -> { |
| 196 | 207 | log.error("[sendChatMessageStream][userId({}) sendReqVO({}) 发生异常]", userId, sendReqVO, throwable); |
| 197 | 208 | // 忽略租户,因为 Flux 异步无法透传租户 |
| 198 | 209 | TenantUtils.executeIgnore(() -> chatMessageMapper.updateById( |
| 199 | - new AiChatMessageDO().setId(assistantMessage.getId()).setContent(throwable.getMessage()))); | |
| 210 | + new AiChatMessageDO().setId(assistantMessage.getId()).setContent(throwable.getMessage()) | |
| 211 | + .setReasoningContent(reasoningContentBuffer.toString()))); | |
| 200 | 212 | }).onErrorResume(error -> Flux.just(error(ErrorCodeConstants.CHAT_STREAM_ERROR))); |
| 201 | 213 | } |
| 202 | 214 | ... | ... |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/util/AiUtils.java
| ... | ... | @@ -11,7 +11,9 @@ import org.springaicommunity.qianfan.QianFanChatOptions; |
| 11 | 11 | import org.springframework.ai.anthropic.AnthropicChatOptions; |
| 12 | 12 | import org.springframework.ai.azure.openai.AzureOpenAiChatOptions; |
| 13 | 13 | import org.springframework.ai.chat.messages.*; |
| 14 | +import org.springframework.ai.chat.model.ChatResponse; | |
| 14 | 15 | import org.springframework.ai.chat.prompt.ChatOptions; |
| 16 | +import org.springframework.ai.deepseek.DeepSeekAssistantMessage; | |
| 15 | 17 | import org.springframework.ai.deepseek.DeepSeekChatOptions; |
| 16 | 18 | import org.springframework.ai.minimax.MiniMaxChatOptions; |
| 17 | 19 | import org.springframework.ai.ollama.api.OllamaOptions; |
| ... | ... | @@ -45,6 +47,7 @@ public class AiUtils { |
| 45 | 47 | switch (platform) { |
| 46 | 48 | case TONG_YI: |
| 47 | 49 | return DashScopeChatOptions.builder().withModel(model).withTemperature(temperature).withMaxToken(maxTokens) |
| 50 | + .withEnableThinking(true) // TODO 芋艿:默认都开启 thinking 模式,后续可以让用户配置 | |
| 48 | 51 | .withToolNames(toolNames).withToolContext(toolContext).build(); |
| 49 | 52 | case YI_YAN: |
| 50 | 53 | return QianFanChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build(); |
| ... | ... | @@ -106,4 +109,27 @@ public class AiUtils { |
| 106 | 109 | return context; |
| 107 | 110 | } |
| 108 | 111 | |
| 112 | + @SuppressWarnings("ConstantValue") | |
| 113 | + public static String getChatResponseContent(ChatResponse response) { | |
| 114 | + if (response == null | |
| 115 | + || response.getResult() == null | |
| 116 | + || response.getResult().getOutput() == null) { | |
| 117 | + return null; | |
| 118 | + } | |
| 119 | + return response.getResult().getOutput().getText(); | |
| 120 | + } | |
| 121 | + | |
| 122 | + @SuppressWarnings("ConstantValue") | |
| 123 | + public static String getChatResponseReasoningContent(ChatResponse response) { | |
| 124 | + if (response == null | |
| 125 | + || response.getResult() == null | |
| 126 | + || response.getResult().getOutput() == null) { | |
| 127 | + return null; | |
| 128 | + } | |
| 129 | + if (response.getResult().getOutput() instanceof DeepSeekAssistantMessage) { | |
| 130 | + return ((DeepSeekAssistantMessage) (response.getResult().getOutput())).getReasoningContent(); | |
| 131 | + } | |
| 132 | + return null; | |
| 133 | + } | |
| 134 | + | |
| 109 | 135 | } |
| 110 | 136 | \ No newline at end of file | ... | ... |
yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/TongYiChatModelTests.java
| ... | ... | @@ -75,7 +75,8 @@ public class TongYiChatModelTests { |
| 75 | 75 | List<Message> messages = new ArrayList<>(); |
| 76 | 76 | messages.add(new UserMessage("详细分析下,如何设计一个电商系统?")); |
| 77 | 77 | DashScopeChatOptions options = DashScopeChatOptions.builder() |
| 78 | - .withModel("qwen3-235b-a22b-thinking-2507") | |
| 78 | +// .withModel("qwen3-235b-a22b-thinking-2507") | |
| 79 | + .withModel("qwen-max-2025-01-25") | |
| 79 | 80 | .withEnableThinking(true) // 必须设置,否则会报错 |
| 80 | 81 | .build(); |
| 81 | 82 | ... | ... |