From 45a9dfc4fac642cb497ad4356d42b5a442f43ded Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 23 Aug 2025 22:52:36 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E3=80=90ai=20=E5=A4=A7=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B=E3=80=91=E5=A2=9E=E5=8A=A0=20thinking=20=E6=B7=B1?= =?UTF-8?q?=E5=BA=A6=E6=80=9D=E8=80=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chat/vo/message/AiChatMessageRespVO.java | 3 ++ .../vo/message/AiChatMessageSendRespVO.java | 3 ++ .../dal/dataobject/chat/AiChatMessageDO.java | 4 +++ .../chat/AiChatMessageServiceImpl.java | 32 +++++++++++++------ .../iocoder/yudao/module/ai/util/AiUtils.java | 26 +++++++++++++++ .../core/model/chat/TongYiChatModelTests.java | 3 +- 6 files changed, 60 insertions(+), 11 deletions(-) diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageRespVO.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageRespVO.java index 5d44e4f967..79d41d463d 100644 --- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageRespVO.java +++ b/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 { @Schema(description = "聊天内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "你好,你好啊") private String content; + @Schema(description = "推理内容", example = "要达到这个目标,你需要...") + private String reasoningContent; + @Schema(description = "是否携带上下文", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") private Boolean useContext; diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageSendRespVO.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageSendRespVO.java index 245a19f7cb..2da260867e 100644 --- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageSendRespVO.java +++ b/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 { @Schema(description = "聊天内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "你好,你好啊") private String content; + @Schema(description = "推理内容", example = "要达到这个目标,你需要...") + private String reasoningContent; + @Schema(description = "知识库段落编号数组", example = "[1,2,3]") private List segmentIds; diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatMessageDO.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatMessageDO.java index 2364d750cb..60c413c470 100644 --- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatMessageDO.java +++ b/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 { * 聊天内容 */ private String content; + /** + * 推理内容 + */ + private String reasoningContent; /** * 是否携带上下文 diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java index 4af65bd8fb..adbd377efa 100644 --- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java +++ b/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; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum; -import cn.iocoder.yudao.module.ai.util.AiUtils; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; @@ -21,6 +19,7 @@ import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO; import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiToolDO; import cn.iocoder.yudao.module.ai.dal.mysql.chat.AiChatMessageMapper; import cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants; +import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum; import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeDocumentService; import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeSegmentService; import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchReqBO; @@ -28,6 +27,7 @@ import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchR import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService; import cn.iocoder.yudao.module.ai.service.model.AiModelService; import cn.iocoder.yudao.module.ai.service.model.AiToolService; +import cn.iocoder.yudao.module.ai.util.AiUtils; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.ai.chat.messages.Message; @@ -118,8 +118,10 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { ChatResponse chatResponse = chatModel.call(prompt); // 3.3 更新响应内容 - String newContent = chatResponse.getResult().getOutput().getText(); - chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(newContent)); + String newContent = AiUtils.getChatResponseContent(chatResponse); + String newReasoningContent = AiUtils.getChatResponseReasoningContent(chatResponse); + chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()) + .setContent(newContent).setReasoningContent(newReasoningContent)); // 3.4 响应结果 Map documentMap = knowledgeDocumentService.getKnowledgeDocumentMap( convertSet(knowledgeSegments, AiKnowledgeSegmentSearchRespBO::getDocumentId)); @@ -168,6 +170,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { // 4.3 流式返回 StringBuffer contentBuffer = new StringBuffer(); + StringBuffer reasoningContentBuffer = new StringBuffer(); return streamResponse.map(chunk -> { // 处理知识库的返回,只有首次才有 List segments = null; @@ -181,22 +184,31 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { }); } // 响应结果 - String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getText() : null; - newContent = StrUtil.nullToDefault(newContent, ""); // 避免 null 的 情况 - contentBuffer.append(newContent); + String newContent = AiUtils.getChatResponseContent(chunk); + String newReasoningContent = AiUtils.getChatResponseReasoningContent(chunk); + if (StrUtil.isNotEmpty(newContent)) { + contentBuffer.append(newContent); + } + if (StrUtil.isNotEmpty(newReasoningContent)) { + reasoningContentBuffer.append(newReasoningContent); + } return success(new AiChatMessageSendRespVO() .setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class)) .setReceive(BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class) - .setContent(newContent).setSegments(segments))); + .setContent(StrUtil.nullToDefault(newContent, "")) // 避免 null 的 情况 + .setReasoningContent(StrUtil.nullToDefault(newReasoningContent, "")) // 避免 null 的 情况 + .setSegments(segments))); // 知识库返回 }).doOnComplete(() -> { // 忽略租户,因为 Flux 异步无法透传租户 TenantUtils.executeIgnore(() -> chatMessageMapper.updateById( - new AiChatMessageDO().setId(assistantMessage.getId()).setContent(contentBuffer.toString()))); + new AiChatMessageDO().setId(assistantMessage.getId()).setContent(contentBuffer.toString()) + .setReasoningContent(reasoningContentBuffer.toString()))); }).doOnError(throwable -> { log.error("[sendChatMessageStream][userId({}) sendReqVO({}) 发生异常]", userId, sendReqVO, throwable); // 忽略租户,因为 Flux 异步无法透传租户 TenantUtils.executeIgnore(() -> chatMessageMapper.updateById( - new AiChatMessageDO().setId(assistantMessage.getId()).setContent(throwable.getMessage()))); + new AiChatMessageDO().setId(assistantMessage.getId()).setContent(throwable.getMessage()) + .setReasoningContent(reasoningContentBuffer.toString()))); }).onErrorResume(error -> Flux.just(error(ErrorCodeConstants.CHAT_STREAM_ERROR))); } diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/util/AiUtils.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/util/AiUtils.java index 24ef4be2ad..35fb26d2cf 100644 --- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/util/AiUtils.java +++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/util/AiUtils.java @@ -11,7 +11,9 @@ import org.springaicommunity.qianfan.QianFanChatOptions; import org.springframework.ai.anthropic.AnthropicChatOptions; import org.springframework.ai.azure.openai.AzureOpenAiChatOptions; import org.springframework.ai.chat.messages.*; +import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.prompt.ChatOptions; +import org.springframework.ai.deepseek.DeepSeekAssistantMessage; import org.springframework.ai.deepseek.DeepSeekChatOptions; import org.springframework.ai.minimax.MiniMaxChatOptions; import org.springframework.ai.ollama.api.OllamaOptions; @@ -45,6 +47,7 @@ public class AiUtils { switch (platform) { case TONG_YI: return DashScopeChatOptions.builder().withModel(model).withTemperature(temperature).withMaxToken(maxTokens) + .withEnableThinking(true) // TODO 芋艿:默认都开启 thinking 模式,后续可以让用户配置 .withToolNames(toolNames).withToolContext(toolContext).build(); case YI_YAN: return QianFanChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build(); @@ -106,4 +109,27 @@ public class AiUtils { return context; } + @SuppressWarnings("ConstantValue") + public static String getChatResponseContent(ChatResponse response) { + if (response == null + || response.getResult() == null + || response.getResult().getOutput() == null) { + return null; + } + return response.getResult().getOutput().getText(); + } + + @SuppressWarnings("ConstantValue") + public static String getChatResponseReasoningContent(ChatResponse response) { + if (response == null + || response.getResult() == null + || response.getResult().getOutput() == null) { + return null; + } + if (response.getResult().getOutput() instanceof DeepSeekAssistantMessage) { + return ((DeepSeekAssistantMessage) (response.getResult().getOutput())).getReasoningContent(); + } + return null; + } + } \ No newline at end of file diff --git a/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/TongYiChatModelTests.java b/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/TongYiChatModelTests.java index da7baaebcf..8a4544967f 100644 --- a/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/TongYiChatModelTests.java +++ b/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 { List messages = new ArrayList<>(); messages.add(new UserMessage("详细分析下,如何设计一个电商系统?")); DashScopeChatOptions options = DashScopeChatOptions.builder() - .withModel("qwen3-235b-a22b-thinking-2507") +// .withModel("qwen3-235b-a22b-thinking-2507") + .withModel("qwen-max-2025-01-25") .withEnableThinking(true) // 必须设置,否则会报错 .build();