Quellcode durchsuchen

Return response as json

atsachlaris vor 2 Tagen
Ursprung
Commit
6e2e00831c

+ 2 - 2
src/main/java/es/uv/saic/service/EnhancementService.java

@@ -23,12 +23,12 @@ public class EnhancementService {
     private final GroqProxy groqProxy;
     private final ExtractionResponseMapper  extractionResponseMapper;
 
-    public String calculateScoreAndProduceCommentsWithSingleCall(ExtractionRequest extractionRequest, String provider, String model) {
+    public ExtractionResponse calculateScoreAndProduceCommentsWithSingleCall(ExtractionRequest extractionRequest, String provider, String model) {
         String asCsv = extractCsv(extractionRequest);
         return calculateScoreAndProduceCommentsFromCsv(asCsv, provider, model);
     }
 
-    public String calculateScoreAndProduceCommentsFromCsv(String asCsv, String provider, String model) {
+    public ExtractionResponse calculateScoreAndProduceCommentsFromCsv(String asCsv, String provider, String model) {
         if (StringUtils.isBlank(asCsv)) {
             throw new IllegalArgumentException("CSV content is empty");
         }

+ 2 - 1
src/main/java/es/uv/saic/service/ExtractionResponse.java

@@ -20,5 +20,6 @@ record Item(
         String code,
         String group,
         Double score,
-        String level
+        String level,
+        String comment
 ) {}

+ 86 - 3
src/main/java/es/uv/saic/service/ExtractionResponseMapper.java

@@ -1,13 +1,96 @@
 package es.uv.saic.service;
 
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.stereotype.Component;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Pattern;
+
 @Component
 public class ExtractionResponseMapper {
-    
+    private static final Pattern JSON_BLOCK_PATTERN = Pattern.compile("(?s)```(?:json)?\\s*(\\{.*})\\s*```");
+
+    private final ObjectMapper objectMapper = new ObjectMapper();
+
+    public ExtractionResponse mapResponse(String llmResponse) {
+        if (StringUtils.isBlank(llmResponse)) {
+            return new ExtractionResponse(
+                    "",
+                    "",
+                    new Items(0, "Warning", List.of()),
+                    new Items(0, "Critical", List.of())
+            );
+        }
+
+        String payload = extractJsonPayload(llmResponse);
+        try {
+            LlmExtractionResponse llmParsed = objectMapper.readValue(payload, LlmExtractionResponse.class);
+            return mapToExtractionResponse(llmResponse, llmParsed);
+        } catch (JsonProcessingException e) {
+            throw new IllegalArgumentException("LLM response is not valid JSON for expected schema", e);
+        }
+    }
 
-    public String mapResponse(String llmResponse) {
-        return null;
+    private ExtractionResponse mapToExtractionResponse(String rawAnswer, LlmExtractionResponse llmParsed) {
+        List<Item> warnings = new ArrayList<>();
+        List<Item> criticals = new ArrayList<>();
+        List<LlmItem> items = llmParsed.items() == null ? List.of() : llmParsed.items();
+
+        for (LlmItem llmItem : items) {
+            if (llmItem == null) {
+                continue;
+            }
+            Item item = new Item(
+                    llmItem.code(),
+                    llmItem.group(),
+                    llmItem.score(),
+                    llmItem.level(),
+                    llmItem.comment()
+            );
+            String level = StringUtils.defaultString(llmItem.level()).toLowerCase(Locale.ROOT);
+            if (level.contains("critical")) {
+                criticals.add(item);
+            } else if (level.contains("warning")) {
+                warnings.add(item);
+            }
+        }
+
+        return new ExtractionResponse(
+                rawAnswer,
+                llmParsed.okSummary(),
+                new Items(warnings.size(), "Warning", warnings),
+                new Items(criticals.size(), "Critical", criticals)
+        );
     }
 
+    private String extractJsonPayload(String llmResponse) {
+        String trimmed = llmResponse.trim();
+        var matcher = JSON_BLOCK_PATTERN.matcher(trimmed);
+        if (matcher.matches()) {
+            return matcher.group(1).trim();
+        }
+        return trimmed;
+    }
+
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    private record LlmExtractionResponse(
+            List<LlmItem> items,
+            @JsonProperty("ok_summary")
+            String okSummary
+    ) {}
+
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    private record LlmItem(
+            String code,
+            String group,
+            Double score,
+            String level,
+            String comment
+    ) {}
 }

+ 23 - 10
src/main/java/es/uv/saic/service/SystemPrompt.java

@@ -36,20 +36,33 @@ public class SystemPrompt {
         - Scores of 2.5, 2.7, 2.8, 2.9, 2.99 MUST be Warning
         - Scores of 2.1, 2.3, 2.49 MUST be Critical
     
-        OUTPUT FORMAT:
+        OUTPUT FORMAT (JSON ONLY):
     
-        1. List ONLY Warning and Critical items individually using:
+        Return ONLY valid JSON.
+        Do not include markdown.
+        Do not include explanations outside the JSON.
     
-        - Code
-        - Group
-        - Score
-        - Level
-        - Comment
+        Use this exact structure:
     
-        The comment must be formal, prudent, and institutional.
+        {
+          "items": [
+            {
+              "code": "string",
+              "group": "string",
+              "score": 0.0,
+              "level": "OK | Warning | Critical",
+              "comment": "string"
+            }
+          ],
+          "ok_summary": "string"
+        }
     
-        2. After listing those items, generate ONE general paragraph summarizing all OK items together.
-        Do not individually list OK items.
+        RULES:
+        - "items" must contain ONLY Warning and Critical items
+        - Do NOT include OK items inside "items"
+        - "ok_summary" must contain a general institutional summary of all OK items together
+        - The "level" field MUST strictly follow the classification table
+        - Return valid JSON only
 
     """;
 }

+ 4 - 3
src/main/java/es/uv/saic/web/EnhancementController.java

@@ -2,6 +2,7 @@ package es.uv.saic.web;
 
 import es.uv.saic.extractor.ExtractionRequest;
 import es.uv.saic.service.EnhancementService;
+import es.uv.saic.service.ExtractionResponse;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.http.MediaType;
@@ -27,7 +28,7 @@ public class EnhancementController {
     private final EnhancementService enhancementService;
 
     @PostMapping("single-step")
-    public String singleStepAnalysis(@RequestBody String html,
+    public ExtractionResponse singleStepAnalysis(@RequestBody String html,
                                      @RequestParam(value = "provider", required = false) String provider,
                                      @RequestParam(value = "model", required = false) String model) {
         log.info("====== EnhancementController.singleStepAnalysis start ======");
@@ -41,7 +42,7 @@ public class EnhancementController {
     }
 
     @PostMapping(value = "single-step/file", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
-    public String singleStepAnalysisFile(@RequestPart("file") MultipartFile file,
+    public ExtractionResponse singleStepAnalysisFile(@RequestPart("file") MultipartFile file,
                                          @RequestParam(value = "provider", required = false) String provider,
                                          @RequestParam(value = "model", required = false) String model) {
         log.info("====== EnhancementController.singleStepAnalysisFile start ======");
@@ -66,7 +67,7 @@ public class EnhancementController {
     }
 
     @PostMapping(value = "single-step/csv-file", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
-    public String singleStepAnalysisCsvFile(@RequestPart("file") MultipartFile file,
+    public ExtractionResponse singleStepAnalysisCsvFile(@RequestPart("file") MultipartFile file,
                                             @RequestParam(value = "provider", required = false) String provider,
                                             @RequestParam(value = "model", required = false) String model) {
         log.info("====== EnhancementController.singleStepAnalysisCsvFile start ======");