| | |
| | | package cc.mrbird.febs.ai.service.impl; |
| | | |
| | | import cc.mrbird.febs.ai.entity.AiTalkItem; |
| | | import cc.mrbird.febs.ai.enumerates.AiTypeEnum; |
| | | import cc.mrbird.febs.ai.entity.AiProductRole; |
| | | import cc.mrbird.febs.ai.req.ai.AiMessage; |
| | | import cc.mrbird.febs.ai.req.ai.AiRequest; |
| | | import cc.mrbird.febs.ai.req.talk.AiTalkAnswerStream; |
| | | import cc.mrbird.febs.ai.res.ai.AiResponse; |
| | | import cc.mrbird.febs.ai.res.ai.RadarDataItem; |
| | | import cc.mrbird.febs.ai.res.ai.Report; |
| | | import cc.mrbird.febs.ai.res.memberTalk.ApiMemberTalkStreamVo; |
| | | import cc.mrbird.febs.ai.service.AiProductRoleService; |
| | | import cc.mrbird.febs.ai.service.AiService; |
| | | import cc.mrbird.febs.ai.service.AiTalkItemService; |
| | | import cc.mrbird.febs.common.entity.FebsResponse; |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.hutool.core.util.StrUtil; |
| | | import cn.hutool.json.JSONUtil; |
| | | import com.fasterxml.jackson.core.JsonProcessingException; |
| | | import com.fasterxml.jackson.databind.JsonNode; |
| | | import com.fasterxml.jackson.databind.ObjectMapper; |
| | |
| | | import org.springframework.beans.factory.annotation.Value; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.util.StringUtils; |
| | | import reactor.core.publisher.Flux; |
| | | |
| | | import javax.annotation.PostConstruct; |
| | | import javax.annotation.PreDestroy; |
| | | import java.util.ArrayList; |
| | | import java.util.Arrays; |
| | | import java.util.List; |
| | | import java.util.concurrent.TimeUnit; |
| | | import java.util.function.Consumer; |
| | |
| | | |
| | | private final AiProductRoleService aiProductRoleService; |
| | | private final ObjectMapper objectMapper; |
| | | private final AiTalkItemService aiTalkItemService; |
| | | |
| | | @Value("${ai.service.ak}") |
| | | private String ak; |
| | |
| | | } |
| | | |
| | | @Override |
| | | public AiResponse start(String productRoleId, String content) { |
| | | public AiResponse start(List<AiMessage> aiMessageDtoList,Integer type,String productRoleId, String content, String question) { |
| | | if (!StringUtils.hasText(productRoleId)) { |
| | | log.warn("productRoleId 不能为空"); |
| | | return buildErrorResponse(CODE_NOT_FOUND, "AI陪练不存在"); |
| | |
| | | return buildErrorResponse(CODE_NOT_FOUND, "AI陪练不存在"); |
| | | } |
| | | |
| | | String promptTemplate = aiProductRole.getPromptTemplate(); |
| | | |
| | | String promptTemplate = "作为一个智能助手,请回答我提出的问题。"; |
| | | if (AiTypeEnum.QUESTION.getCode() == type){ |
| | | promptTemplate = aiProductRole.getPromptHead(); |
| | | } |
| | | if (AiTypeEnum.ANSWER.getCode() == type){ |
| | | promptTemplate = aiProductRole.getPromptTemplate()+question; |
| | | } |
| | | log.info("promptTemplate: {}", promptTemplate); |
| | | String linkId = aiProductRole.getLinkId(); |
| | | String jsonTemplate = aiProductRole.getJsonTemplate(); |
| | | |
| | |
| | | aiRequest.setJsonTemplate(jsonTemplate); |
| | | aiRequest.setLinkId(linkId); |
| | | aiRequest.setContent(content); |
| | | |
| | | if (CollUtil.isNotEmpty(aiMessageDtoList)){ |
| | | aiRequest.setAiMessageDtoList(aiMessageDtoList); |
| | | } |
| | | return this.question(aiRequest); |
| | | } |
| | | |
| | |
| | | return buildErrorResponse(CODE_ERROR, "请求参数不完整"); |
| | | } |
| | | |
| | | final List<ChatMessage> messages = new ArrayList<>(); |
| | | final ChatMessage systemMessage = ChatMessage.builder().role(ChatMessageRole.SYSTEM).content(promptTemplate).build(); |
| | | final ChatMessage userMessage = ChatMessage.builder().role(ChatMessageRole.USER).content(content).build(); |
| | | List<ChatMessage> messages = new ArrayList<>(); |
| | | ChatMessage systemMessage = ChatMessage.builder().role(ChatMessageRole.SYSTEM).content(promptTemplate).build(); |
| | | ChatMessage userMessage = ChatMessage.builder().role(ChatMessageRole.USER).content(content).build(); |
| | | messages.add(systemMessage); |
| | | if (CollUtil.isNotEmpty(aiRequest.getAiMessageDtoList())){ |
| | | aiRequest.getAiMessageDtoList().forEach(aiMessageDto -> { |
| | | ChatMessage message = ChatMessage.builder() |
| | | .role(aiMessageDto.getRole()) |
| | | .content(aiMessageDto.getContent()) |
| | | .build(); |
| | | messages.add(message); |
| | | }); |
| | | } |
| | | messages.add(userMessage); |
| | | |
| | | try { |
| | |
| | | .filter(contentObj -> contentObj != null) |
| | | .map(Object::toString) |
| | | .collect(Collectors.joining()); |
| | | |
| | | Report report = this.extractReportData(result); |
| | | return buildSuccessResponse(report, result); |
| | | } catch (JsonProcessingException e) { |
| | |
| | | log.error("调用AI服务失败,modelId: {}", linkId, e); |
| | | return buildErrorResponse(CODE_ERROR, "AI服务调用失败"); |
| | | } |
| | | } |
| | | |
| | | public static void main(String[] args) { |
| | | Report report = new Report(); |
| | | List<RadarDataItem> radarDataItems = new ArrayList<>(); |
| | | |
| | | RadarDataItem item1 = new RadarDataItem(); |
| | | item1.setName("A"); |
| | | item1.setCode("A"); |
| | | item1.setScore("80"); |
| | | radarDataItems.add(item1); |
| | | |
| | | RadarDataItem item2 = new RadarDataItem(); |
| | | item2.setName("A"); |
| | | item2.setCode("A"); |
| | | item2.setScore("80"); |
| | | radarDataItems.add(item2); |
| | | report.setRadarDataItems(radarDataItems); |
| | | |
| | | System.out.println(JSONUtil.parse( report)); |
| | | |
| | | } |
| | | |
| | | @Override |
| | |
| | | public Report extractReportData(String modelOutput) { |
| | | Matcher matcher = JSON_PATTERN.matcher(modelOutput); |
| | | if (!matcher.find()) { |
| | | log.warn("未匹配到FunctionCall内容,原始输出长度: {}", modelOutput.length()); |
| | | log.warn("未匹配到FunctionCall内容,原始输出长度: {}", modelOutput); |
| | | return null; |
| | | } |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | // 修改服务实现 |
| | | @Override |
| | | public Flux<FebsResponse> answerStream(String question) { |
| | | log.info("----- standard request -----"); |
| | | |
| | | final ChatMessage systemMessage = ChatMessage.builder() |
| | | .role(ChatMessageRole.SYSTEM) |
| | | .content("你是豆包,是由字节跳动开发的 AI 人工智能助手") |
| | | .build(); |
| | | |
| | | final ChatMessage userMessage = ChatMessage.builder() |
| | | .role(ChatMessageRole.USER) |
| | | .content(question) |
| | | .build(); |
| | | |
| | | List<ChatMessage> messages = Arrays.asList(systemMessage, userMessage); |
| | | |
| | | ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder() |
| | | .model("ep-20250805124033-lhxbf") |
| | | .messages(messages) |
| | | .stream(true) |
| | | .thinking(new ChatCompletionRequest.ChatCompletionRequestThinking("enabled")) |
| | | .temperature(0.7) |
| | | .topP(0.9) |
| | | .maxTokens(2048) |
| | | .frequencyPenalty(0.0) |
| | | .build(); |
| | | |
| | | return Flux.from(service.streamChatCompletion(chatCompletionRequest)) |
| | | .map(response -> { |
| | | if (response == null || response.getChoices() == null || response.getChoices().isEmpty()) { |
| | | return new FebsResponse().success().data("END"); |
| | | } |
| | | |
| | | ChatCompletionChoice choice = response.getChoices().get(0); |
| | | if (choice == null || choice.getMessage() == null) { |
| | | return new FebsResponse().success().data("END"); |
| | | } |
| | | |
| | | |
| | | ApiMemberTalkStreamVo apiMemberTalkStreamVo = new ApiMemberTalkStreamVo(); |
| | | // 判断是否触发深度思考,触发则打印模型输出的思维链内容 |
| | | ChatMessage message = choice.getMessage(); |
| | | if (message.getReasoningContent()!= null &&!message.getReasoningContent().isEmpty()) { |
| | | apiMemberTalkStreamVo.setReasoningContent(message.getReasoningContent()); |
| | | // System.out.print(message.getReasoningContent()); |
| | | } |
| | | |
| | | String content = message.getContent() == null ? "" : message.getContent().toString(); |
| | | apiMemberTalkStreamVo.setContent(content); |
| | | System.out.print(content); |
| | | return new FebsResponse().success().data(apiMemberTalkStreamVo); |
| | | }) |
| | | .onErrorResume(throwable -> { |
| | | log.error("流式调用AI服务失败,问题输入: {}", question, throwable); |
| | | FebsResponse errorResponse = new FebsResponse().fail().message("AI服务调用失败"); |
| | | return Flux.just(errorResponse); |
| | | }); |
| | | } |
| | | |
| | | @Override |
| | | public Flux<FebsResponse> answerStreamV2(AiTalkAnswerStream dto) { |
| | | String question = dto.getQuestion(); |
| | | log.info("----- standard request -----"); |
| | | |
| | | // 参数校验 |
| | | if (StrUtil.isBlank(question)) { |
| | | return Flux.just(new FebsResponse().fail().message("问题不能为空")); |
| | | } |
| | | |
| | | List<ChatMessage> messages = new ArrayList<>(); |
| | | |
| | | final ChatMessage systemMessage = ChatMessage.builder() |
| | | .role(ChatMessageRole.SYSTEM) |
| | | .content("你是豆包,是由字节跳动开发的 AI 人工智能助手") |
| | | .build(); |
| | | messages.add(systemMessage); |
| | | |
| | | // 获取历史消息记录 |
| | | if (StrUtil.isNotEmpty(dto.getTalkId())) { |
| | | List<AiTalkItem> aiTalkItems = aiTalkItemService.getListByTalkId(dto.getTalkId()); |
| | | if (CollUtil.isNotEmpty(aiTalkItems)) { |
| | | for (AiTalkItem aiTalkItem : aiTalkItems) { |
| | | ChatMessage chatMessage = buildChatMessageFromItem(aiTalkItem); |
| | | if (chatMessage != null) { |
| | | messages.add(chatMessage); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | final ChatMessage userMessage = ChatMessage.builder() |
| | | .role(ChatMessageRole.USER) |
| | | .content(question) |
| | | .build(); |
| | | messages.add(userMessage); |
| | | |
| | | ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder() |
| | | .model("ep-20250805124033-lhxbf") |
| | | .messages(messages) |
| | | .stream(true) |
| | | .thinking(new ChatCompletionRequest.ChatCompletionRequestThinking("enabled")) |
| | | .temperature(0.7) |
| | | .topP(0.9) |
| | | .maxTokens(2048) |
| | | .frequencyPenalty(0.0) |
| | | .build(); |
| | | |
| | | return Flux.from(service.streamChatCompletion(chatCompletionRequest)) |
| | | .map(response -> { |
| | | if (response == null || response.getChoices() == null || response.getChoices().isEmpty()) { |
| | | return new FebsResponse().success().data("END"); |
| | | } |
| | | |
| | | ChatCompletionChoice choice = response.getChoices().get(0); |
| | | if (choice == null || choice.getMessage() == null) { |
| | | return new FebsResponse().success().data("END"); |
| | | } |
| | | |
| | | ChatMessage message = choice.getMessage(); |
| | | ApiMemberTalkStreamVo apiMemberTalkStreamVo = new ApiMemberTalkStreamVo(); |
| | | |
| | | // 处理 reasoning content |
| | | String reasoningContent = message.getReasoningContent(); |
| | | if (StrUtil.isNotEmpty(reasoningContent)) { |
| | | apiMemberTalkStreamVo.setReasoningContent(reasoningContent); |
| | | log.debug("Reasoning Content: {}", reasoningContent); |
| | | } |
| | | |
| | | // 安全处理 content |
| | | String content = ""; |
| | | if (message.getContent() != null) { |
| | | content = message.getContent().toString(); |
| | | } |
| | | apiMemberTalkStreamVo.setContent(content); |
| | | System.out.print(content); |
| | | log.debug("Content: {}", content); |
| | | |
| | | return new FebsResponse().success().data(apiMemberTalkStreamVo); |
| | | }) |
| | | .onErrorResume(throwable -> { |
| | | log.error("流式调用AI服务失败,问题输入: {}", question, throwable); |
| | | FebsResponse errorResponse = new FebsResponse().fail().message("AI服务调用失败"); |
| | | return Flux.just(errorResponse); |
| | | }); |
| | | } |
| | | |
| | | // 提取为私有方法,提高可读性和复用性 |
| | | private ChatMessage buildChatMessageFromItem(AiTalkItem item) { |
| | | if (item == null) return null; |
| | | |
| | | switch (item.getType()) { |
| | | case 1: |
| | | return ChatMessage.builder() |
| | | .role(ChatMessageRole.USER) |
| | | .content(item.getContext()) |
| | | .build(); |
| | | case 2: |
| | | return ChatMessage.builder() |
| | | .role(ChatMessageRole.ASSISTANT) |
| | | .content(item.getContext()) |
| | | .build(); |
| | | default: |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | private Report tryRepairTruncatedJson(String truncatedJson) { |
| | | String[] repairAttempts = { |
| | | truncatedJson + "\"}}}", |