package com.iailab.module.ai.service.mindmap; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.StrUtil; import com.iailab.framework.ai.core.enums.AiModelTypeEnum; import com.iailab.framework.ai.core.enums.AiPlatformEnum; import com.iailab.framework.ai.core.util.AiUtils; import com.iailab.framework.common.pojo.CommonResult; import com.iailab.framework.common.pojo.PageResult; import com.iailab.framework.common.util.object.BeanUtils; import com.iailab.framework.tenant.core.util.TenantUtils; import com.iailab.module.ai.controller.admin.mindmap.vo.AiMindMapGenerateReqVO; import com.iailab.module.ai.controller.admin.mindmap.vo.AiMindMapPageReqVO; import com.iailab.module.ai.dal.dataobject.mindmap.AiMindMapDO; import com.iailab.module.ai.dal.dataobject.model.AiChatRoleDO; import com.iailab.module.ai.dal.dataobject.model.AiModelDO; import com.iailab.module.ai.dal.mysql.mindmap.AiMindMapMapper; import com.iailab.module.ai.enums.AiChatRoleEnum; import com.iailab.module.ai.enums.ErrorCodeConstants; import com.iailab.module.ai.service.model.AiChatRoleService; import com.iailab.module.ai.service.model.AiModelService; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.messages.SystemMessage; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.prompt.ChatOptions; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.stereotype.Service; import reactor.core.publisher.Flux; import java.util.ArrayList; import java.util.List; import static com.iailab.framework.common.exception.util.ServiceExceptionUtil.exception; import static com.iailab.framework.common.pojo.CommonResult.error; import static com.iailab.framework.common.pojo.CommonResult.success; import static com.iailab.module.ai.enums.ErrorCodeConstants.*; /** * AI 思维导图 Service 实现类 * * @author xiaoxin */ @Service @Slf4j public class AiMindMapServiceImpl implements AiMindMapService { @Resource private AiModelService modalService; @Resource private AiChatRoleService chatRoleService; @Resource private AiMindMapMapper mindMapMapper; @Override public Flux> generateMindMap(AiMindMapGenerateReqVO generateReqVO, Long userId) { // 1. 获取导图模型。尝试获取思维导图助手角色,如果没有则使用默认模型 AiChatRoleDO role = CollUtil.getFirst( chatRoleService.getChatRoleListByName(AiChatRoleEnum.AI_MIND_MAP_ROLE.getName())); // 1.1 获取导图执行模型 AiModelDO model = getModel(role); // 1.2 获取角色设定消息 String systemMessage = role != null && StrUtil.isNotBlank(role.getSystemMessage()) ? role.getSystemMessage() : AiChatRoleEnum.AI_MIND_MAP_ROLE.getSystemMessage(); // 1.3 校验平台 AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform()); ChatModel chatModel = modalService.getChatModel(model.getId()); // 2. 插入思维导图信息 AiMindMapDO mindMapDO = BeanUtils.toBean(generateReqVO, AiMindMapDO.class, mindMap -> mindMap.setUserId(userId) .setPlatform(platform.getPlatform()).setModelId(model.getId()).setModel(model.getModel())); mindMapMapper.insert(mindMapDO); // 3.1 构建 Prompt,并进行调用 Prompt prompt = buildPrompt(generateReqVO, model, systemMessage); Flux streamResponse = chatModel.stream(prompt); // 3.2 流式返回 StringBuffer contentBuffer = new StringBuffer(); return streamResponse.map(chunk -> { String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getText() : null; newContent = StrUtil.nullToDefault(newContent, ""); // 避免 null 的 情况 contentBuffer.append(newContent); // 响应结果 return success(newContent); }).doOnComplete(() -> { // 忽略租户,因为 Flux 异步无法透传租户 TenantUtils.executeIgnore(() -> mindMapMapper.updateById(new AiMindMapDO().setId(mindMapDO.getId()).setGeneratedContent(contentBuffer.toString()))); }).doOnError(throwable -> { log.error("[generateWriteContent][generateReqVO({}) 发生异常]", generateReqVO, throwable); // 忽略租户,因为 Flux 异步无法透传租户 TenantUtils.executeIgnore(() -> mindMapMapper.updateById(new AiMindMapDO().setId(mindMapDO.getId()).setErrorMessage(throwable.getMessage()))); }).onErrorResume(error -> Flux.just(error(ErrorCodeConstants.WRITE_STREAM_ERROR))); } private Prompt buildPrompt(AiMindMapGenerateReqVO generateReqVO, AiModelDO model, String systemMessage) { // 1. 构建 message 列表 List chatMessages = buildMessages(generateReqVO, systemMessage); // 2. 构建 options 对象 AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform()); ChatOptions options = AiUtils.buildChatOptions(platform, model.getModel(), model.getTemperature(), model.getMaxTokens()); return new Prompt(chatMessages, options); } private static List buildMessages(AiMindMapGenerateReqVO generateReqVO, String systemMessage) { List chatMessages = new ArrayList<>(); // 1. 角色设定 if (StrUtil.isNotBlank(systemMessage)) { chatMessages.add(new SystemMessage(systemMessage)); } // 2. 用户输入 chatMessages.add(new UserMessage(generateReqVO.getPrompt())); return chatMessages; } private AiModelDO getModel(AiChatRoleDO role) { AiModelDO model = null; if (role != null && role.getModelId() != null) { model = modalService.getModel(role.getModelId()); } if (model == null) { model = modalService.getRequiredDefaultModel(AiModelTypeEnum.CHAT.getType()); } // 校验模型存在、且合法 if (model == null) { throw exception(MODEL_NOT_EXISTS); } if (ObjUtil.notEqual(model.getType(), AiModelTypeEnum.CHAT.getType())) { throw exception(MODEL_USE_TYPE_ERROR); } return model; } @Override public void deleteMindMap(Long id) { // 校验存在 validateMindMapExists(id); // 删除 mindMapMapper.deleteById(id); } private void validateMindMapExists(Long id) { if (mindMapMapper.selectById(id) == null) { throw exception(MIND_MAP_NOT_EXISTS); } } @Override public PageResult getMindMapPage(AiMindMapPageReqVO pageReqVO) { return mindMapMapper.selectPage(pageReqVO); } }