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,6 +37,9 @@ public class AiChatMessageRespVO { | ||
| 37 | @Schema(description = "聊天内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "你好,你好啊") | 37 | @Schema(description = "聊天内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "你好,你好啊") |
| 38 | private String content; | 38 | private String content; |
| 39 | 39 | ||
| 40 | + @Schema(description = "推理内容", example = "要达到这个目标,你需要...") | ||
| 41 | + private String reasoningContent; | ||
| 42 | + | ||
| 40 | @Schema(description = "是否携带上下文", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") | 43 | @Schema(description = "是否携带上下文", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") |
| 41 | private Boolean useContext; | 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,6 +29,9 @@ public class AiChatMessageSendRespVO { | ||
| 29 | @Schema(description = "聊天内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "你好,你好啊") | 29 | @Schema(description = "聊天内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "你好,你好啊") |
| 30 | private String content; | 30 | private String content; |
| 31 | 31 | ||
| 32 | + @Schema(description = "推理内容", example = "要达到这个目标,你需要...") | ||
| 33 | + private String reasoningContent; | ||
| 34 | + | ||
| 32 | @Schema(description = "知识库段落编号数组", example = "[1,2,3]") | 35 | @Schema(description = "知识库段落编号数组", example = "[1,2,3]") |
| 33 | private List<Long> segmentIds; | 36 | private List<Long> segmentIds; |
| 34 | 37 |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatMessageDO.java
| @@ -87,6 +87,10 @@ public class AiChatMessageDO extends BaseDO { | @@ -87,6 +87,10 @@ public class AiChatMessageDO extends BaseDO { | ||
| 87 | * 聊天内容 | 87 | * 聊天内容 |
| 88 | */ | 88 | */ |
| 89 | private String content; | 89 | private String content; |
| 90 | + /** | ||
| 91 | + * 推理内容 | ||
| 92 | + */ | ||
| 93 | + private String reasoningContent; | ||
| 90 | 94 | ||
| 91 | /** | 95 | /** |
| 92 | * 是否携带上下文 | 96 | * 是否携带上下文 |
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,8 +3,6 @@ package cn.iocoder.yudao.module.ai.service.chat; | ||
| 3 | import cn.hutool.core.collection.CollUtil; | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | import cn.hutool.core.util.ObjUtil; | 4 | import cn.hutool.core.util.ObjUtil; |
| 5 | import cn.hutool.core.util.StrUtil; | 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 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; | 6 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 9 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 7 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 10 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | 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,6 +19,7 @@ import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO; | ||
| 21 | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiToolDO; | 19 | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiToolDO; |
| 22 | import cn.iocoder.yudao.module.ai.dal.mysql.chat.AiChatMessageMapper; | 20 | import cn.iocoder.yudao.module.ai.dal.mysql.chat.AiChatMessageMapper; |
| 23 | import cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants; | 21 | import cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants; |
| 22 | +import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum; | ||
| 24 | import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeDocumentService; | 23 | import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeDocumentService; |
| 25 | import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeSegmentService; | 24 | import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeSegmentService; |
| 26 | import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchReqBO; | 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,6 +27,7 @@ import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchR | ||
| 28 | import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService; | 27 | import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService; |
| 29 | import cn.iocoder.yudao.module.ai.service.model.AiModelService; | 28 | import cn.iocoder.yudao.module.ai.service.model.AiModelService; |
| 30 | import cn.iocoder.yudao.module.ai.service.model.AiToolService; | 29 | import cn.iocoder.yudao.module.ai.service.model.AiToolService; |
| 30 | +import cn.iocoder.yudao.module.ai.util.AiUtils; | ||
| 31 | import jakarta.annotation.Resource; | 31 | import jakarta.annotation.Resource; |
| 32 | import lombok.extern.slf4j.Slf4j; | 32 | import lombok.extern.slf4j.Slf4j; |
| 33 | import org.springframework.ai.chat.messages.Message; | 33 | import org.springframework.ai.chat.messages.Message; |
| @@ -118,8 +118,10 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | @@ -118,8 +118,10 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | ||
| 118 | ChatResponse chatResponse = chatModel.call(prompt); | 118 | ChatResponse chatResponse = chatModel.call(prompt); |
| 119 | 119 | ||
| 120 | // 3.3 更新响应内容 | 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 | // 3.4 响应结果 | 125 | // 3.4 响应结果 |
| 124 | Map<Long, AiKnowledgeDocumentDO> documentMap = knowledgeDocumentService.getKnowledgeDocumentMap( | 126 | Map<Long, AiKnowledgeDocumentDO> documentMap = knowledgeDocumentService.getKnowledgeDocumentMap( |
| 125 | convertSet(knowledgeSegments, AiKnowledgeSegmentSearchRespBO::getDocumentId)); | 127 | convertSet(knowledgeSegments, AiKnowledgeSegmentSearchRespBO::getDocumentId)); |
| @@ -168,6 +170,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | @@ -168,6 +170,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | ||
| 168 | 170 | ||
| 169 | // 4.3 流式返回 | 171 | // 4.3 流式返回 |
| 170 | StringBuffer contentBuffer = new StringBuffer(); | 172 | StringBuffer contentBuffer = new StringBuffer(); |
| 173 | + StringBuffer reasoningContentBuffer = new StringBuffer(); | ||
| 171 | return streamResponse.map(chunk -> { | 174 | return streamResponse.map(chunk -> { |
| 172 | // 处理知识库的返回,只有首次才有 | 175 | // 处理知识库的返回,只有首次才有 |
| 173 | List<AiChatMessageRespVO.KnowledgeSegment> segments = null; | 176 | List<AiChatMessageRespVO.KnowledgeSegment> segments = null; |
| @@ -181,22 +184,31 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | @@ -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 | return success(new AiChatMessageSendRespVO() | 195 | return success(new AiChatMessageSendRespVO() |
| 188 | .setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class)) | 196 | .setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class)) |
| 189 | .setReceive(BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class) | 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 | }).doOnComplete(() -> { | 201 | }).doOnComplete(() -> { |
| 192 | // 忽略租户,因为 Flux 异步无法透传租户 | 202 | // 忽略租户,因为 Flux 异步无法透传租户 |
| 193 | TenantUtils.executeIgnore(() -> chatMessageMapper.updateById( | 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 | }).doOnError(throwable -> { | 206 | }).doOnError(throwable -> { |
| 196 | log.error("[sendChatMessageStream][userId({}) sendReqVO({}) 发生异常]", userId, sendReqVO, throwable); | 207 | log.error("[sendChatMessageStream][userId({}) sendReqVO({}) 发生异常]", userId, sendReqVO, throwable); |
| 197 | // 忽略租户,因为 Flux 异步无法透传租户 | 208 | // 忽略租户,因为 Flux 异步无法透传租户 |
| 198 | TenantUtils.executeIgnore(() -> chatMessageMapper.updateById( | 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 | }).onErrorResume(error -> Flux.just(error(ErrorCodeConstants.CHAT_STREAM_ERROR))); | 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,7 +11,9 @@ import org.springaicommunity.qianfan.QianFanChatOptions; | ||
| 11 | import org.springframework.ai.anthropic.AnthropicChatOptions; | 11 | import org.springframework.ai.anthropic.AnthropicChatOptions; |
| 12 | import org.springframework.ai.azure.openai.AzureOpenAiChatOptions; | 12 | import org.springframework.ai.azure.openai.AzureOpenAiChatOptions; |
| 13 | import org.springframework.ai.chat.messages.*; | 13 | import org.springframework.ai.chat.messages.*; |
| 14 | +import org.springframework.ai.chat.model.ChatResponse; | ||
| 14 | import org.springframework.ai.chat.prompt.ChatOptions; | 15 | import org.springframework.ai.chat.prompt.ChatOptions; |
| 16 | +import org.springframework.ai.deepseek.DeepSeekAssistantMessage; | ||
| 15 | import org.springframework.ai.deepseek.DeepSeekChatOptions; | 17 | import org.springframework.ai.deepseek.DeepSeekChatOptions; |
| 16 | import org.springframework.ai.minimax.MiniMaxChatOptions; | 18 | import org.springframework.ai.minimax.MiniMaxChatOptions; |
| 17 | import org.springframework.ai.ollama.api.OllamaOptions; | 19 | import org.springframework.ai.ollama.api.OllamaOptions; |
| @@ -45,6 +47,7 @@ public class AiUtils { | @@ -45,6 +47,7 @@ public class AiUtils { | ||
| 45 | switch (platform) { | 47 | switch (platform) { |
| 46 | case TONG_YI: | 48 | case TONG_YI: |
| 47 | return DashScopeChatOptions.builder().withModel(model).withTemperature(temperature).withMaxToken(maxTokens) | 49 | return DashScopeChatOptions.builder().withModel(model).withTemperature(temperature).withMaxToken(maxTokens) |
| 50 | + .withEnableThinking(true) // TODO 芋艿:默认都开启 thinking 模式,后续可以让用户配置 | ||
| 48 | .withToolNames(toolNames).withToolContext(toolContext).build(); | 51 | .withToolNames(toolNames).withToolContext(toolContext).build(); |
| 49 | case YI_YAN: | 52 | case YI_YAN: |
| 50 | return QianFanChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build(); | 53 | return QianFanChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build(); |
| @@ -106,4 +109,27 @@ public class AiUtils { | @@ -106,4 +109,27 @@ public class AiUtils { | ||
| 106 | return context; | 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 | \ No newline at end of file | 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,7 +75,8 @@ public class TongYiChatModelTests { | ||
| 75 | List<Message> messages = new ArrayList<>(); | 75 | List<Message> messages = new ArrayList<>(); |
| 76 | messages.add(new UserMessage("详细分析下,如何设计一个电商系统?")); | 76 | messages.add(new UserMessage("详细分析下,如何设计一个电商系统?")); |
| 77 | DashScopeChatOptions options = DashScopeChatOptions.builder() | 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 | .withEnableThinking(true) // 必须设置,否则会报错 | 80 | .withEnableThinking(true) // 必须设置,否则会报错 |
| 80 | .build(); | 81 | .build(); |
| 81 | 82 |