feat:【ai 大模型】增加 thinking 深度思考
This commit is contained in:
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -87,6 +87,10 @@ public class AiChatMessageDO extends BaseDO {
|
||||
* 聊天内容
|
||||
*/
|
||||
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.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)));
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user