feat:【ai 大模型】增加 thinking 深度思考

This commit is contained in:
YunaiV
2025-08-23 22:52:36 +08:00
parent 69d99aa4ea
commit 45a9dfc4fa
6 changed files with 60 additions and 11 deletions

View File

@@ -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;

View File

@@ -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<Long> segmentIds;

View File

@@ -87,6 +87,10 @@ public class AiChatMessageDO extends BaseDO {
* 聊天内容
*/
private String content;
/**
* 推理内容
*/
private String reasoningContent;
/**
* 是否携带上下文

View File

@@ -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<Long, AiKnowledgeDocumentDO> 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<AiChatMessageRespVO.KnowledgeSegment> 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)));
}

View File

@@ -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;
}
}

View File

@@ -75,7 +75,8 @@ public class TongYiChatModelTests {
List<Message> 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();