From 5a624d468c8f4eddd89c8cf9b99eb6466fa21481 Mon Sep 17 00:00:00 2001
From: Administrator <15274802129@163.com>
Date: Wed, 06 Aug 2025 17:55:18 +0800
Subject: [PATCH] feat(ai): 增加 AI 陪练报告数据解析功能 - 新增 Report、RadarData 和 Evaluation 类用于解析报告数据 - 在 AiService 接口中添加 extractReportData 方法 - 在 AiServiceImpl 中实现报告数据的提取和解析 - 更新 ApiMemberTalkVo,增加 report 字段用于存储解析后的报告数据 - 修改前端相关的回答格式和类型

---
 src/main/java/cc/mrbird/febs/ai/service/impl/AiServiceImpl.java |  144 ++++++++++++++++++++++++++++++++++++++++++-----
 1 files changed, 128 insertions(+), 16 deletions(-)

diff --git a/src/main/java/cc/mrbird/febs/ai/service/impl/AiServiceImpl.java b/src/main/java/cc/mrbird/febs/ai/service/impl/AiServiceImpl.java
index 0ef2ae9..3ab9f2c 100644
--- a/src/main/java/cc/mrbird/febs/ai/service/impl/AiServiceImpl.java
+++ b/src/main/java/cc/mrbird/febs/ai/service/impl/AiServiceImpl.java
@@ -1,13 +1,15 @@
 package cc.mrbird.febs.ai.service.impl;
 
 import cc.mrbird.febs.ai.entity.AiProductRole;
+import cc.mrbird.febs.ai.req.ai.AiRequest;
 import cc.mrbird.febs.ai.res.ai.AiResponse;
+import cc.mrbird.febs.ai.res.ai.Report;
 import cc.mrbird.febs.ai.service.AiProductRoleService;
 import cc.mrbird.febs.ai.service.AiService;
-import com.volcengine.ark.runtime.model.completion.chat.ChatCompletionChoice;
-import com.volcengine.ark.runtime.model.completion.chat.ChatCompletionRequest;
-import com.volcengine.ark.runtime.model.completion.chat.ChatMessage;
-import com.volcengine.ark.runtime.model.completion.chat.ChatMessageRole;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.volcengine.ark.runtime.model.completion.chat.*;
 import com.volcengine.ark.runtime.service.ArkService;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
@@ -22,6 +24,8 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
 /**
@@ -37,6 +41,7 @@
     private static final String CODE_ERROR = "500";
 
     private final AiProductRoleService aiProductRoleService;
+    private final ObjectMapper objectMapper;
 
     @Value("${ai.service.ak}")
     private String ak;
@@ -90,11 +95,19 @@
             return buildErrorResponse(CODE_ERROR, "角色配置不完整");
         }
 
-        return question(promptTemplate, linkId, content);
+        AiRequest aiRequest = new AiRequest();
+        aiRequest.setPromptTemplate(promptTemplate);
+        aiRequest.setLinkId(linkId);
+        aiRequest.setContent(content);
+
+        return question(aiRequest);
     }
 
     @Override
-    public AiResponse question(String promptTemplate, String linkId, String content) {
+    public AiResponse question(AiRequest aiRequest) {
+        String promptTemplate = aiRequest.getPromptTemplate();
+        String linkId = aiRequest.getLinkId();
+        String content = aiRequest.getContent();
         if (!StringUtils.hasText(promptTemplate) || !StringUtils.hasText(linkId) || !StringUtils.hasText(content)) {
             log.warn("请求参数不完整,promptTemplate: {}, linkId: {}, content: {}", promptTemplate, linkId, content);
             return buildErrorResponse(CODE_ERROR, "请求参数不完整");
@@ -106,16 +119,44 @@
         messages.add(systemMessage);
         messages.add(userMessage);
 
-        ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
-                .model(linkId)
-                .messages(messages)
-                .temperature(1.0)
-                .topP(0.7)
-                .maxTokens(4096)
-                .frequencyPenalty(0.0)
-                .build();
-
+        // 生成 JSON Schema
+        String schemaJson = "{\n" +
+                "     \"radar_data\": {\n" +
+                "       \"problem_understanding\": \"object\",\n" +
+                "       \"fluency\": \"object\",\n" +
+                "       \"principle_adherence\": \"object\",\n" +
+                "       \"logicality\": \"object\",\n" +
+                "       \"knowledge_mastery\": \"object\"\n" +
+                "     },\n" +
+                "     \"evaluation\": {\n" +
+                "       \"highlight\": \"object\",\n" +
+                "       \"suggestion\": \"object\",\n" +
+                "       \"reference_answer\": \"object\",\n" +
+                "       \"key_knowledge\": \"object\"\n" +
+                "     }\n" +
+                "   }";
         try {
+            JsonNode schemaNode = objectMapper.readTree(schemaJson);
+            // 配置响应格式
+            ChatCompletionRequest.ChatCompletionRequestResponseFormat responseFormat = new ChatCompletionRequest.ChatCompletionRequestResponseFormat(
+                    "json_schema",
+                    new ResponseFormatJSONSchemaJSONSchemaParam(
+                            "ai_response",
+                            "json数据响应",
+                            schemaNode,
+                            true
+                    )
+            );
+            ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
+                    .model(linkId)
+                    .messages(messages)
+                    .stream(false)
+                    .responseFormat(responseFormat)
+                    .temperature(1.0)
+                    .topP(0.7)
+                    .maxTokens(4096)
+                    .frequencyPenalty(0.0)
+                    .build();
             List<ChatCompletionChoice> choices = service.createChatCompletion(chatCompletionRequest).getChoices();
             String result = choices.stream()
                     .map(choice -> choice.getMessage().getContent())
@@ -123,11 +164,73 @@
                     .map(Object::toString)
                     .collect(Collectors.joining());
 
-            return buildSuccessResponse(result);
+            Report report = this.extractReportData(result);
+            return buildSuccessResponse(report, result);
+        } catch (JsonProcessingException e) {
+            log.error("初始化AI服务失败,JSON格式化输出初始化失败", e);
+            return buildErrorResponse(CODE_ERROR, "AI服务调用失败");
         } catch (Exception e) {
             log.error("调用AI服务失败,modelId: {}, content: {}", linkId, content, e);
             return buildErrorResponse(CODE_ERROR, "AI服务调用失败");
         }
+    }
+
+    private static final Pattern JSON_PATTERN = Pattern.compile(
+            "<\\|FunctionCallBegin\\|>(.*?)<\\|FunctionCallEnd\\|>",
+            Pattern.DOTALL
+    );
+
+    @Override
+    public Report extractReportData(String modelOutput) {
+        // 提取JSON部分
+        Matcher matcher = JSON_PATTERN.matcher(modelOutput);
+        if (!matcher.find()) {
+            log.warn("未匹配到FunctionCall内容,原始输出: {}", modelOutput);
+            return null;
+        }
+
+        String jsonContent = matcher.group(1);
+        log.debug("提取到的JSON内容: {}", jsonContent);
+
+        // 解析JSON到Report对象
+        try {
+            return objectMapper.readValue(jsonContent, Report.class);
+        } catch (JsonProcessingException e) {
+            log.error("JSON解析失败,原始内容: {}", jsonContent, e);
+            // 尝试修复截断的JSON(可选)
+            Report repairedReport = tryRepairTruncatedJson(jsonContent);
+            if (repairedReport != null) {
+                log.info("成功修复截断的JSON");
+                return repairedReport;
+            }
+            return null;
+        }
+    }
+
+    /**
+     * 尝试修复截断的JSON字符串
+     * @param truncatedJson 可能被截断的JSON字符串
+     * @return 修复后的Report对象,如果无法修复则返回null
+     */
+    private Report tryRepairTruncatedJson(String truncatedJson) {
+        // 简单的修复策略:尝试添加缺失的结束括号
+        String[] repairAttempts = {
+                truncatedJson + "\"}}}",
+                truncatedJson + "}}}",
+                truncatedJson + "}}"
+        };
+
+        for (String attempt : repairAttempts) {
+            try {
+                return objectMapper.readValue(attempt, Report.class);
+            } catch (JsonProcessingException e) {
+                log.debug("修复尝试失败: {}", attempt);
+                continue;
+            }
+        }
+
+        log.warn("无法修复截断的JSON: {}", truncatedJson);
+        return null;
     }
 
     private AiResponse buildErrorResponse(String code, String description) {
@@ -144,4 +247,13 @@
         response.setResContext(result);
         return response;
     }
+
+    private AiResponse buildSuccessResponse(Report report, String result) {
+        AiResponse response = new AiResponse();
+        response.setCode(CODE_SUCCESS);
+        response.setDescription("成功");
+        response.setResContext(result);
+        response.setReport(report);
+        return response;
+    }
 }

--
Gitblit v1.9.1