src/main/java/cc/mrbird/febs/ai/req/ai/AiRequest.java | ●●●●● patch | view | raw | blame | history | |
src/main/java/cc/mrbird/febs/ai/res/ai/AiResponse.java | ●●●●● patch | view | raw | blame | history | |
src/main/java/cc/mrbird/febs/ai/res/ai/Evaluation.java | ●●●●● patch | view | raw | blame | history | |
src/main/java/cc/mrbird/febs/ai/res/ai/RadarData.java | ●●●●● patch | view | raw | blame | history | |
src/main/java/cc/mrbird/febs/ai/res/ai/Report.java | ●●●●● patch | view | raw | blame | history | |
src/main/java/cc/mrbird/febs/ai/res/memberTalk/ApiMemberTalkVo.java | ●●●●● patch | view | raw | blame | history | |
src/main/java/cc/mrbird/febs/ai/service/AiService.java | ●●●●● patch | view | raw | blame | history | |
src/main/java/cc/mrbird/febs/ai/service/impl/AiMemberTalkServiceImpl.java | ●●●●● patch | view | raw | blame | history | |
src/main/java/cc/mrbird/febs/ai/service/impl/AiServiceImpl.java | ●●●●● patch | view | raw | blame | history |
src/main/java/cc/mrbird/febs/ai/req/ai/AiRequest.java
New file @@ -0,0 +1,16 @@ package cc.mrbird.febs.ai.req.ai; import io.swagger.annotations.ApiModel; import lombok.Data; /** * @author Administrator */ @Data @ApiModel(value = "AiRequest", description = "参数") public class AiRequest { private String promptTemplate; private String linkId; private String content; } src/main/java/cc/mrbird/febs/ai/res/ai/AiResponse.java
@@ -1,6 +1,7 @@ package cc.mrbird.febs.ai.res.ai; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; /** @@ -10,7 +11,13 @@ @ApiModel(value = "AiResponse", description = "参数") public class AiResponse { @ApiModelProperty(value = "响应") private String code; @ApiModelProperty(value = "描述") private String description; @ApiModelProperty(value = "建议") private String resContext; @ApiModelProperty(value = "亮点、建议、参考答案、核心知识点雷达图表数据") private Report report; } src/main/java/cc/mrbird/febs/ai/res/ai/Evaluation.java
New file @@ -0,0 +1,32 @@ package cc.mrbird.febs.ai.res.ai; import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; /** * @author Administrator */ @Data @ApiModel(value = "Evaluation", description = "参数") public class Evaluation { @ApiModelProperty(value = "亮点") @JsonProperty("highlight") private String highlight; @ApiModelProperty(value = "建议") @JsonProperty("suggestion") private String suggestion; @ApiModelProperty(value = "参考答案") @JsonProperty("reference_answer") private String referenceAnswer; @ApiModelProperty(value = "核心知识点") @JsonProperty("key_knowledge") private String keyKnowledge; } src/main/java/cc/mrbird/febs/ai/res/ai/RadarData.java
New file @@ -0,0 +1,33 @@ package cc.mrbird.febs.ai.res.ai; import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; /** * @author Administrator */ @Data @ApiModel(value = "radar_data", description = "参数") public class RadarData { @ApiModelProperty(value = "问题理解") @JsonProperty("problem_understanding") private double problemUnderstanding; @ApiModelProperty(value = "表达流畅度") @JsonProperty("fluency") private double fluency; @ApiModelProperty(value = "原则体现") @JsonProperty("principle_adherence") private double principleAdherence; @ApiModelProperty(value = "语言逻辑性") @JsonProperty("logicality") private double logicality; @ApiModelProperty(value = "知识点掌握") @JsonProperty("knowledge_mastery") private double knowledgeMastery; } src/main/java/cc/mrbird/febs/ai/res/ai/Report.java
New file @@ -0,0 +1,24 @@ package cc.mrbird.febs.ai.res.ai; import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; /** * @author Administrator */ @Data @ApiModel(value = "Report", description = "参数") public class Report { @ApiModelProperty(value = "雷达图表数据") @JsonProperty("radar_data") private RadarData radarData; @ApiModelProperty(value = "亮点、建议、参考答案、核心知识点") @JsonProperty("evaluation") private Evaluation evaluation; } src/main/java/cc/mrbird/febs/ai/res/memberTalk/ApiMemberTalkVo.java
@@ -1,5 +1,6 @@ package cc.mrbird.febs.ai.res.memberTalk; import cc.mrbird.febs.ai.res.ai.Report; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; @@ -29,7 +30,10 @@ * 内容 */ @ApiModelProperty(value = "内容") @ApiModelProperty(value = "内容(文本格式)") private String context; @ApiModelProperty(value = "内容亮点、建议、参考答案、核心知识点雷达图表数据(数据对象)") private Report report; } src/main/java/cc/mrbird/febs/ai/service/AiService.java
@@ -1,6 +1,8 @@ package cc.mrbird.febs.ai.service; import cc.mrbird.febs.ai.req.ai.AiRequest; import cc.mrbird.febs.ai.res.ai.AiResponse; import cc.mrbird.febs.ai.res.ai.Report; /** * @author Administrator @@ -10,5 +12,12 @@ AiResponse start(String productRoleId, String content); AiResponse question(String promptTemplate,String linkId,String content); AiResponse question(AiRequest aiRequest); /** * 从模型输出中提取并解析报告数据 * @param modelOutput 模型原始输出 * @return 解析后的报告对象 */ Report extractReportData(String modelOutput); } src/main/java/cc/mrbird/febs/ai/service/impl/AiMemberTalkServiceImpl.java
@@ -14,6 +14,7 @@ import cc.mrbird.febs.ai.service.AiService; import cc.mrbird.febs.ai.utils.UUID; import cc.mrbird.febs.common.entity.FebsResponse; import cc.mrbird.febs.common.exception.FebsException; import cc.mrbird.febs.common.utils.LoginUserUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; @@ -59,7 +60,7 @@ productLinkQuery.last("limit 1"); AiProductRoleLink aiProductRoleLink = aiProductRoleLinkService.getByQuery(productLinkQuery); if(ObjectUtil.isNull(aiProductRoleLink)){ throw new RuntimeException("产品AI陪练不存在"); throw new FebsException("产品AI陪练不存在"); } Date nowTime = new Date(); @@ -72,17 +73,18 @@ aiMemberTalk = this.add(memberUuid,productId,nowTime); } AiResponse aiResponse = aiService.start(aiProductRoleLink.getProductRoleId(),"开始出题"); AiResponse aiResponse = aiService.start(aiProductRoleLink.getProductRoleId(),"<strong>\"生成题目\"</strong>"); if(aiResponse.getCode().equals("200")){ aiMemberTalkItemService.add(memberUuid,aiMemberTalk.getId(),1,aiResponse.getResContext(),nowTime); this.updateTimeUpdate(nowTime,aiMemberTalk.getId()); }else{ throw new RuntimeException(aiResponse.getDescription()); throw new FebsException(aiResponse.getDescription()); } ApiMemberTalkVo apiMemberTalkVo = new ApiMemberTalkVo(); apiMemberTalkVo.setMemberTalkId(aiMemberTalk.getId()); apiMemberTalkVo.setType(1); apiMemberTalkVo.setContext(aiResponse.getResContext()); apiMemberTalkVo.setReport(aiResponse.getReport()); return new FebsResponse().success().data(apiMemberTalkVo); } @@ -102,14 +104,14 @@ } public static final String ANSWER_FORMAT = "{}/n回答:{}/n请根据回答给出以下四个方面的总结,这个四个方面分别是亮点、建议、参考答案和核心知识点回顾。重点:四个方面的总结都是必须要有内容。"; public static final String ANSWER_FORMAT = "{}/n[回答]{}/n"; @Override public FebsResponse answer(ApiMemberTalkAnswerDto dto) { String memberUuid = LoginUserUtil.getLoginUser().getMemberUuid(); String memberTalkId = dto.getId(); AiMemberTalk aiMemberTalk = this.getById(memberTalkId); if (ObjectUtil.isNull(aiMemberTalk)){ throw new RuntimeException("产品AI陪练对话不存在"); throw new FebsException("产品AI陪练对话不存在"); } LambdaQueryWrapper<AiProductRoleLink> productLinkQuery = Wrappers.lambdaQuery(AiProductRoleLink.class); @@ -117,7 +119,7 @@ productLinkQuery.last("limit 1"); AiProductRoleLink aiProductRoleLink = aiProductRoleLinkService.getByQuery(productLinkQuery); if(ObjectUtil.isNull(aiProductRoleLink)){ throw new RuntimeException("产品AI陪练不存在"); throw new FebsException("产品AI陪练不存在"); } String reqContext = dto.getReqContext(); @@ -134,21 +136,21 @@ String format = StrUtil.format(ANSWER_FORMAT, aiMemberTalkItem.getContext(), reqContext); log.info("format:{}",format); AiResponse aiResponse = aiService.start(aiProductRoleLink.getProductRoleId(), format); // AiResponse aiResponse = aiService.start(aiProductRoleLink.getProductRoleId(), reqContext); if(aiResponse.getCode().equals("200")){ Date nowTime = new Date(); aiMemberTalkItemService.add(memberUuid,aiMemberTalk.getId(),3,aiResponse.getResContext(),nowTime); this.updateTimeUpdate(nowTime,aiMemberTalk.getId()); }else{ throw new RuntimeException(aiResponse.getDescription()); throw new FebsException(aiResponse.getDescription()); } ApiMemberTalkVo apiMemberTalkVo = new ApiMemberTalkVo(); apiMemberTalkVo.setMemberTalkId(aiMemberTalk.getId()); apiMemberTalkVo.setType(1); apiMemberTalkVo.setType(3); apiMemberTalkVo.setContext(aiResponse.getResContext()); apiMemberTalkVo.setReport(aiResponse.getReport()); return new FebsResponse().success().data(apiMemberTalkVo); } @Override public AiMemberTalk add(String memberUuid, String productId, Date nowTime) { AiMemberTalk aiMemberTalk = new AiMemberTalk(); 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; } }