mirror of
https://github.com/YunaiV/ruoyi-vue-pro.git
synced 2026-04-19 13:48:37 +00:00
feat:【ai 大模型】增加 thinking 深度思考
This commit is contained in:
@@ -37,6 +37,9 @@ public class AiChatMessageRespVO {
|
|||||||
@Schema(description = "聊天内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "你好,你好啊")
|
@Schema(description = "聊天内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "你好,你好啊")
|
||||||
private String content;
|
private String content;
|
||||||
|
|
||||||
|
@Schema(description = "推理内容", example = "要达到这个目标,你需要...")
|
||||||
|
private String reasoningContent;
|
||||||
|
|
||||||
@Schema(description = "是否携带上下文", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
|
@Schema(description = "是否携带上下文", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
|
||||||
private Boolean useContext;
|
private Boolean useContext;
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ public class AiChatMessageSendRespVO {
|
|||||||
@Schema(description = "聊天内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "你好,你好啊")
|
@Schema(description = "聊天内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "你好,你好啊")
|
||||||
private String content;
|
private String content;
|
||||||
|
|
||||||
|
@Schema(description = "推理内容", example = "要达到这个目标,你需要...")
|
||||||
|
private String reasoningContent;
|
||||||
|
|
||||||
@Schema(description = "知识库段落编号数组", example = "[1,2,3]")
|
@Schema(description = "知识库段落编号数组", example = "[1,2,3]")
|
||||||
private List<Long> segmentIds;
|
private List<Long> segmentIds;
|
||||||
|
|
||||||
|
|||||||
@@ -87,6 +87,10 @@ public class AiChatMessageDO extends BaseDO {
|
|||||||
* 聊天内容
|
* 聊天内容
|
||||||
*/
|
*/
|
||||||
private String content;
|
private String content;
|
||||||
|
/**
|
||||||
|
* 推理内容
|
||||||
|
*/
|
||||||
|
private String reasoningContent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否携带上下文
|
* 是否携带上下文
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ package cn.iocoder.yudao.module.ai.service.chat;
|
|||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.core.util.ObjUtil;
|
import cn.hutool.core.util.ObjUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
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.CommonResult;
|
||||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
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.dataobject.model.AiToolDO;
|
||||||
import cn.iocoder.yudao.module.ai.dal.mysql.chat.AiChatMessageMapper;
|
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.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.AiKnowledgeDocumentService;
|
||||||
import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeSegmentService;
|
import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeSegmentService;
|
||||||
import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchReqBO;
|
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.AiChatRoleService;
|
||||||
import cn.iocoder.yudao.module.ai.service.model.AiModelService;
|
import cn.iocoder.yudao.module.ai.service.model.AiModelService;
|
||||||
import cn.iocoder.yudao.module.ai.service.model.AiToolService;
|
import cn.iocoder.yudao.module.ai.service.model.AiToolService;
|
||||||
|
import cn.iocoder.yudao.module.ai.util.AiUtils;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.ai.chat.messages.Message;
|
import org.springframework.ai.chat.messages.Message;
|
||||||
@@ -118,8 +118,10 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
|
|||||||
ChatResponse chatResponse = chatModel.call(prompt);
|
ChatResponse chatResponse = chatModel.call(prompt);
|
||||||
|
|
||||||
// 3.3 更新响应内容
|
// 3.3 更新响应内容
|
||||||
String newContent = chatResponse.getResult().getOutput().getText();
|
String newContent = AiUtils.getChatResponseContent(chatResponse);
|
||||||
chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(newContent));
|
String newReasoningContent = AiUtils.getChatResponseReasoningContent(chatResponse);
|
||||||
|
chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId())
|
||||||
|
.setContent(newContent).setReasoningContent(newReasoningContent));
|
||||||
// 3.4 响应结果
|
// 3.4 响应结果
|
||||||
Map<Long, AiKnowledgeDocumentDO> documentMap = knowledgeDocumentService.getKnowledgeDocumentMap(
|
Map<Long, AiKnowledgeDocumentDO> documentMap = knowledgeDocumentService.getKnowledgeDocumentMap(
|
||||||
convertSet(knowledgeSegments, AiKnowledgeSegmentSearchRespBO::getDocumentId));
|
convertSet(knowledgeSegments, AiKnowledgeSegmentSearchRespBO::getDocumentId));
|
||||||
@@ -168,6 +170,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
|
|||||||
|
|
||||||
// 4.3 流式返回
|
// 4.3 流式返回
|
||||||
StringBuffer contentBuffer = new StringBuffer();
|
StringBuffer contentBuffer = new StringBuffer();
|
||||||
|
StringBuffer reasoningContentBuffer = new StringBuffer();
|
||||||
return streamResponse.map(chunk -> {
|
return streamResponse.map(chunk -> {
|
||||||
// 处理知识库的返回,只有首次才有
|
// 处理知识库的返回,只有首次才有
|
||||||
List<AiChatMessageRespVO.KnowledgeSegment> segments = null;
|
List<AiChatMessageRespVO.KnowledgeSegment> segments = null;
|
||||||
@@ -181,22 +184,31 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
// 响应结果
|
// 响应结果
|
||||||
String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getText() : null;
|
String newContent = AiUtils.getChatResponseContent(chunk);
|
||||||
newContent = StrUtil.nullToDefault(newContent, ""); // 避免 null 的 情况
|
String newReasoningContent = AiUtils.getChatResponseReasoningContent(chunk);
|
||||||
contentBuffer.append(newContent);
|
if (StrUtil.isNotEmpty(newContent)) {
|
||||||
|
contentBuffer.append(newContent);
|
||||||
|
}
|
||||||
|
if (StrUtil.isNotEmpty(newReasoningContent)) {
|
||||||
|
reasoningContentBuffer.append(newReasoningContent);
|
||||||
|
}
|
||||||
return success(new AiChatMessageSendRespVO()
|
return success(new AiChatMessageSendRespVO()
|
||||||
.setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class))
|
.setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class))
|
||||||
.setReceive(BeanUtils.toBean(assistantMessage, 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(() -> {
|
}).doOnComplete(() -> {
|
||||||
// 忽略租户,因为 Flux 异步无法透传租户
|
// 忽略租户,因为 Flux 异步无法透传租户
|
||||||
TenantUtils.executeIgnore(() -> chatMessageMapper.updateById(
|
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 -> {
|
}).doOnError(throwable -> {
|
||||||
log.error("[sendChatMessageStream][userId({}) sendReqVO({}) 发生异常]", userId, sendReqVO, throwable);
|
log.error("[sendChatMessageStream][userId({}) sendReqVO({}) 发生异常]", userId, sendReqVO, throwable);
|
||||||
// 忽略租户,因为 Flux 异步无法透传租户
|
// 忽略租户,因为 Flux 异步无法透传租户
|
||||||
TenantUtils.executeIgnore(() -> chatMessageMapper.updateById(
|
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)));
|
}).onErrorResume(error -> Flux.just(error(ErrorCodeConstants.CHAT_STREAM_ERROR)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ import org.springaicommunity.qianfan.QianFanChatOptions;
|
|||||||
import org.springframework.ai.anthropic.AnthropicChatOptions;
|
import org.springframework.ai.anthropic.AnthropicChatOptions;
|
||||||
import org.springframework.ai.azure.openai.AzureOpenAiChatOptions;
|
import org.springframework.ai.azure.openai.AzureOpenAiChatOptions;
|
||||||
import org.springframework.ai.chat.messages.*;
|
import org.springframework.ai.chat.messages.*;
|
||||||
|
import org.springframework.ai.chat.model.ChatResponse;
|
||||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||||
|
import org.springframework.ai.deepseek.DeepSeekAssistantMessage;
|
||||||
import org.springframework.ai.deepseek.DeepSeekChatOptions;
|
import org.springframework.ai.deepseek.DeepSeekChatOptions;
|
||||||
import org.springframework.ai.minimax.MiniMaxChatOptions;
|
import org.springframework.ai.minimax.MiniMaxChatOptions;
|
||||||
import org.springframework.ai.ollama.api.OllamaOptions;
|
import org.springframework.ai.ollama.api.OllamaOptions;
|
||||||
@@ -45,6 +47,7 @@ public class AiUtils {
|
|||||||
switch (platform) {
|
switch (platform) {
|
||||||
case TONG_YI:
|
case TONG_YI:
|
||||||
return DashScopeChatOptions.builder().withModel(model).withTemperature(temperature).withMaxToken(maxTokens)
|
return DashScopeChatOptions.builder().withModel(model).withTemperature(temperature).withMaxToken(maxTokens)
|
||||||
|
.withEnableThinking(true) // TODO 芋艿:默认都开启 thinking 模式,后续可以让用户配置
|
||||||
.withToolNames(toolNames).withToolContext(toolContext).build();
|
.withToolNames(toolNames).withToolContext(toolContext).build();
|
||||||
case YI_YAN:
|
case YI_YAN:
|
||||||
return QianFanChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build();
|
return QianFanChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build();
|
||||||
@@ -106,4 +109,27 @@ public class AiUtils {
|
|||||||
return context;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -75,7 +75,8 @@ public class TongYiChatModelTests {
|
|||||||
List<Message> messages = new ArrayList<>();
|
List<Message> messages = new ArrayList<>();
|
||||||
messages.add(new UserMessage("详细分析下,如何设计一个电商系统?"));
|
messages.add(new UserMessage("详细分析下,如何设计一个电商系统?"));
|
||||||
DashScopeChatOptions options = DashScopeChatOptions.builder()
|
DashScopeChatOptions options = DashScopeChatOptions.builder()
|
||||||
.withModel("qwen3-235b-a22b-thinking-2507")
|
// .withModel("qwen3-235b-a22b-thinking-2507")
|
||||||
|
.withModel("qwen-max-2025-01-25")
|
||||||
.withEnableThinking(true) // 必须设置,否则会报错
|
.withEnableThinking(true) // 必须设置,否则会报错
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user