| | |
| | | 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.RadarDataItem; |
| | | import cc.mrbird.febs.ai.res.ai.Report; |
| | | import cc.mrbird.febs.ai.service.AiProductRoleService; |
| | | import cc.mrbird.febs.ai.service.AiService; |
| | | import cn.hutool.json.JSONUtil; |
| | | import com.fasterxml.jackson.core.JsonProcessingException; |
| | | import com.fasterxml.jackson.databind.JsonNode; |
| | | import com.fasterxml.jackson.databind.ObjectMapper; |
| | |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | import java.util.concurrent.TimeUnit; |
| | | import java.util.function.Consumer; |
| | | import java.util.regex.Matcher; |
| | | import java.util.regex.Pattern; |
| | | import java.util.stream.Collectors; |
| | |
| | | public class AiServiceImpl implements AiService { |
| | | |
| | | private static final String CODE_SUCCESS = "200"; |
| | | private static final String CODE_GOING_ON = "199"; |
| | | private static final String CODE_NOT_FOUND = "201"; |
| | | private static final String CODE_ERROR = "500"; |
| | | |
| | | private static final String SCHEMA_JSON = "{\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" + |
| | | " }"; |
| | | |
| | | private final AiProductRoleService aiProductRoleService; |
| | | private final ObjectMapper objectMapper; |
| | |
| | | |
| | | @PostConstruct |
| | | public void init() { |
| | | ConnectionPool connectionPool = new ConnectionPool(10, 30, TimeUnit.SECONDS); |
| | | // 增加连接池大小和存活时间 |
| | | ConnectionPool connectionPool = new ConnectionPool(32, 60, TimeUnit.SECONDS); |
| | | Dispatcher dispatcher = new Dispatcher(); |
| | | // 增加并发请求数量 |
| | | dispatcher.setMaxRequests(128); |
| | | dispatcher.setMaxRequestsPerHost(32); |
| | | |
| | | this.service = ArkService.builder() |
| | | .dispatcher(dispatcher) |
| | | .connectionPool(connectionPool) |
| | |
| | | |
| | | String promptTemplate = aiProductRole.getPromptTemplate(); |
| | | String linkId = aiProductRole.getLinkId(); |
| | | String jsonTemplate = aiProductRole.getJsonTemplate(); |
| | | |
| | | if (!StringUtils.hasText(promptTemplate) || !StringUtils.hasText(linkId)) { |
| | | log.warn("角色配置不完整,promptTemplate 或 linkId 为空,productRoleId: {}", productRoleId); |
| | | if ( |
| | | !StringUtils.hasText(promptTemplate) |
| | | || !StringUtils.hasText(linkId) |
| | | || !StringUtils.hasText(jsonTemplate) |
| | | ) { |
| | | log.warn("角色配置不完整,promptTemplate 或 linkId 或 jsonTemplate为空,productRoleId: {}", productRoleId); |
| | | return buildErrorResponse(CODE_ERROR, "角色配置不完整"); |
| | | } |
| | | |
| | | AiRequest aiRequest = new AiRequest(); |
| | | aiRequest.setPromptTemplate(promptTemplate); |
| | | aiRequest.setJsonTemplate(jsonTemplate); |
| | | aiRequest.setLinkId(linkId); |
| | | aiRequest.setContent(content); |
| | | |
| | | return question(aiRequest); |
| | | return this.question(aiRequest); |
| | | } |
| | | |
| | | @Override |
| | |
| | | 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); |
| | | String jsonTemplate = aiRequest.getJsonTemplate(); |
| | | if ( |
| | | !StringUtils.hasText(promptTemplate) |
| | | || !StringUtils.hasText(linkId) |
| | | || !StringUtils.hasText(content) |
| | | || !StringUtils.hasText(jsonTemplate) |
| | | ) { |
| | | log.warn("请求参数不完整,promptTemplate: {}, linkId: {}, content: {}, jsonTemplate: {}", promptTemplate, linkId, content, jsonTemplate); |
| | | return buildErrorResponse(CODE_ERROR, "请求参数不完整"); |
| | | } |
| | | |
| | |
| | | messages.add(systemMessage); |
| | | messages.add(userMessage); |
| | | |
| | | // 生成 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); |
| | | // 配置响应格式 |
| | | JsonNode schemaNode = objectMapper.readTree(jsonTemplate); |
| | | ChatCompletionRequest.ChatCompletionRequestResponseFormat responseFormat = new ChatCompletionRequest.ChatCompletionRequestResponseFormat( |
| | | "json_schema", |
| | | new ResponseFormatJSONSchemaJSONSchemaParam( |
| | |
| | | .messages(messages) |
| | | .stream(false) |
| | | .responseFormat(responseFormat) |
| | | .temperature(1.0) |
| | | .topP(0.7) |
| | | .maxTokens(4096) |
| | | .temperature(0.7) // 降低温度参数,提高确定性,可能提升速度 |
| | | .topP(0.9) // 调整topP参数 |
| | | .maxTokens(2048) // 减少最大token数 |
| | | .frequencyPenalty(0.0) |
| | | .build(); |
| | | |
| | | List<ChatCompletionChoice> choices = service.createChatCompletion(chatCompletionRequest).getChoices(); |
| | | String result = choices.stream() |
| | | .map(choice -> choice.getMessage().getContent()) |
| | |
| | | log.error("初始化AI服务失败,JSON格式化输出初始化失败", e); |
| | | return buildErrorResponse(CODE_ERROR, "AI服务调用失败"); |
| | | } catch (Exception e) { |
| | | log.error("调用AI服务失败,modelId: {}, content: {}", linkId, content, 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 void streamQuestion(AiRequest aiRequest, Consumer<AiResponse> callback) { |
| | | |
| | | 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); |
| | | } |
| | | |
| | | 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(); |
| | | messages.add(systemMessage); |
| | | messages.add(userMessage); |
| | | |
| | | try { |
| | | JsonNode schemaNode = objectMapper.readTree(SCHEMA_JSON); |
| | | ChatCompletionRequest.ChatCompletionRequestResponseFormat responseFormat = new ChatCompletionRequest.ChatCompletionRequestResponseFormat( |
| | | "json_schema", |
| | | new ResponseFormatJSONSchemaJSONSchemaParam( |
| | | "ai_response", |
| | | "json数据响应", |
| | | schemaNode, |
| | | true |
| | | ) |
| | | ); |
| | | ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder() |
| | | .model(linkId) |
| | | .messages(messages) |
| | | .stream(true) // 启用流式响应 |
| | | .responseFormat(responseFormat) |
| | | .temperature(0.7) |
| | | .topP(0.9) |
| | | .maxTokens(2048) |
| | | .build(); |
| | | |
| | | service.streamChatCompletion(chatCompletionRequest) |
| | | .doOnError(Throwable::printStackTrace) // 处理错误 |
| | | .blockingForEach(response -> { |
| | | AiResponse partialResponse = new AiResponse(); |
| | | if (response.getChoices() != null && !response.getChoices().isEmpty()) { |
| | | String responseStr = String.valueOf(response.getChoices().get(0).getMessage().getContent()); |
| | | if (responseStr != null) { |
| | | // 构造部分响应并回调 |
| | | partialResponse = buildGOINGONResponse(responseStr); |
| | | } |
| | | }else{ |
| | | partialResponse = buildPartialResponse("成功"); |
| | | } |
| | | callback.accept(partialResponse); |
| | | }); |
| | | // service.streamChatCompletion(chatCompletionRequest) |
| | | // .doOnError(throwable -> { |
| | | // log.error("流式调用AI服务失败", throwable); |
| | | // callback.accept(buildErrorResponse(CODE_ERROR, "AI服务调用失败")); |
| | | // }) |
| | | // .subscribe(chatCompletionChunk -> { |
| | | // // 处理每个数据块 |
| | | // Object chunkContent = chatCompletionChunk.getChoices().get(0).getMessage().getContent(); |
| | | // // 构造部分响应并回调 |
| | | // AiResponse partialResponse = buildGOINGONResponse(chunkContent); |
| | | // callback.accept(partialResponse); |
| | | // }); |
| | | } catch (Exception e) { |
| | | log.error("调用AI服务失败", e); |
| | | callback.accept(buildErrorResponse(CODE_ERROR, "AI服务调用失败")); |
| | | } |
| | | } |
| | | |
| | | private AiResponse buildGOINGONResponse(Object chunkContent) { |
| | | AiResponse response = new AiResponse(); |
| | | response.setCode(CODE_GOING_ON); |
| | | response.setDescription("成功"); |
| | | response.setResContext(chunkContent.toString()); |
| | | return response; |
| | | } |
| | | |
| | | private AiResponse buildPartialResponse(Object chunkContent) { |
| | | AiResponse response = new AiResponse(); |
| | | response.setCode(CODE_SUCCESS); |
| | | response.setDescription("成功"); |
| | | response.setResContext(chunkContent.toString()); |
| | | return response; |
| | | } |
| | | |
| | | |
| | | private static final Pattern JSON_PATTERN = Pattern.compile( |
| | | "<\\|FunctionCallBegin\\|>(.*?)<\\|FunctionCallEnd\\|>", |
| | |
| | | |
| | | @Override |
| | | public Report extractReportData(String modelOutput) { |
| | | // 提取JSON部分 |
| | | Matcher matcher = JSON_PATTERN.matcher(modelOutput); |
| | | if (!matcher.find()) { |
| | | log.warn("未匹配到FunctionCall内容,原始输出: {}", modelOutput); |
| | | log.warn("未匹配到FunctionCall内容,原始输出长度: {}", modelOutput.length()); |
| | | return null; |
| | | } |
| | | |
| | | String jsonContent = matcher.group(1); |
| | | log.debug("提取到的JSON内容: {}", jsonContent); |
| | | log.debug("提取到的JSON内容长度: {}", jsonContent.length()); |
| | | |
| | | // 解析JSON到Report对象 |
| | | try { |
| | | return objectMapper.readValue(jsonContent, Report.class); |
| | | } catch (JsonProcessingException e) { |
| | | log.error("JSON解析失败,原始内容: {}", jsonContent, e); |
| | | // 尝试修复截断的JSON(可选) |
| | | log.error("JSON解析失败,原始内容长度: {}", jsonContent.length(), e); |
| | | Report repairedReport = tryRepairTruncatedJson(jsonContent); |
| | | if (repairedReport != null) { |
| | | log.info("成功修复截断的JSON"); |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 尝试修复截断的JSON字符串 |
| | | * @param truncatedJson 可能被截断的JSON字符串 |
| | | * @return 修复后的Report对象,如果无法修复则返回null |
| | | */ |
| | | private Report tryRepairTruncatedJson(String truncatedJson) { |
| | | // 简单的修复策略:尝试添加缺失的结束括号 |
| | | String[] repairAttempts = { |
| | | truncatedJson + "\"}}}", |
| | | truncatedJson + "}}}", |
| | |
| | | } |
| | | } |
| | | |
| | | log.warn("无法修复截断的JSON: {}", truncatedJson); |
| | | log.warn("无法修复截断的JSON,原始内容长度: {}", truncatedJson.length()); |
| | | return null; |
| | | } |
| | | |