atsachlaris преди 12 часа
родител
ревизия
c2de5b84ad

+ 1 - 1
requests/service.http

@@ -33,7 +33,7 @@ Content-Type: text/html
 --WebAppBoundary--
 
 ### Form with warnings and critical
-POST http://127.0.0.1:8080/enhancements/single-step/file
+POST http://127.0.0.1:8080/enhancements/single-step/file?provider=groq
 Content-Type: multipart/form-data; boundary=WebAppBoundary
 
 --WebAppBoundary

+ 8 - 0
src/main/java/es/uv/saic/llm/AiProxy.java

@@ -0,0 +1,8 @@
+package es.uv.saic.llm;
+
+public interface AiProxy {
+
+    String calculateScoreAndProduceComments(String asCsv);
+
+    String calculateScoreAndProduceComments(String asCsv, String model);
+}

+ 1 - 1
src/main/java/es/uv/saic/llm/GroqProxy.java

@@ -11,7 +11,7 @@ import org.springframework.stereotype.Component;
 import static es.uv.saic.service.SystemPrompt.SCORE_AND_COMMENT_ANALYSIS_PROMPT;
 
 @Component
-public class GroqProxy {
+public class GroqProxy implements AiProxy {
 
     private final ChatClient chatClient;
     private final ObjectMapper objectMapper = new ObjectMapper();

+ 17 - 4
src/main/java/es/uv/saic/llm/LocalLlmProxy.java

@@ -3,26 +3,39 @@ package es.uv.saic.llm;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import lombok.SneakyThrows;
 import org.springframework.ai.chat.client.ChatClient;
+import org.springframework.ai.openai.OpenAiChatOptions;
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
 
 import static es.uv.saic.service.SystemPrompt.SCORE_AND_COMMENT_ANALYSIS_PROMPT;
 
 @Component
-public class LocalLlmProxy {
+public class LocalLlmProxy implements AiProxy {
 
     private final ChatClient localChatClient;
     private final ObjectMapper objectMapper = new ObjectMapper();
 
     public LocalLlmProxy(
-            ChatClient.Builder chatClientBuilder
+            @Qualifier("localChatClient") ChatClient localChatClient
     ) {
-        this.localChatClient = chatClientBuilder.build();
+        this.localChatClient = localChatClient;
     }
 
     @SneakyThrows
     public String calculateScoreAndProduceComments(String asCsv) {
+        return calculateScoreAndProduceComments(asCsv, null);
+    }
+
+    @SneakyThrows
+    public String calculateScoreAndProduceComments(String asCsv, String model) {
+        ChatClient.ChatClientRequestSpec prompt = localChatClient.prompt();
+
+        if (StringUtils.hasText(model)) {
+            prompt = prompt.options(OpenAiChatOptions.builder().model(model).build());
+        }
 
-        return localChatClient.prompt()
+        return prompt
                 .system(SCORE_AND_COMMENT_ANALYSIS_PROMPT)
                 .user("Aquí tienes las tablas: " + objectMapper.writeValueAsString(asCsv))
                 .call()

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

@@ -3,6 +3,7 @@ package es.uv.saic.service;
 import es.uv.saic.extractor.ExtractionRequest;
 import es.uv.saic.extractor.docling.DoclingTableExtractor;
 import es.uv.saic.extractor.HtmlToCsvExtractor;
+import es.uv.saic.llm.GroqProxy;
 import es.uv.saic.llm.LocalLlmProxy;
 import lombok.RequiredArgsConstructor;
 import org.apache.commons.lang3.StringUtils;
@@ -13,14 +14,27 @@ import java.nio.charset.StandardCharsets;
 @Service
 @RequiredArgsConstructor
 public class EnhancementService {
+    private static final String PROVIDER_LOCAL = "local";
+    private static final String PROVIDER_GROQ = "groq";
+
     private final HtmlToCsvExtractor htmlToCsvExtractor;
     private final DoclingTableExtractor doclingTableExtractor;
     private final LocalLlmProxy localLlmProxy;
+    private final GroqProxy groqProxy;
 
     public String calculateScoreAndProduceCommentsWithSingleCall(ExtractionRequest extractionRequest) {
+        return calculateScoreAndProduceCommentsWithSingleCall(extractionRequest, null, null);
+    }
+
+    public String calculateScoreAndProduceCommentsWithSingleCall(ExtractionRequest extractionRequest, String provider, String model) {
         String asCsv = extractCsv(extractionRequest);
+        String selectedProvider = StringUtils.defaultIfBlank(provider, PROVIDER_LOCAL);
 
-        return localLlmProxy.calculateScoreAndProduceComments(asCsv);
+        return switch (selectedProvider.toLowerCase()) {
+            case PROVIDER_LOCAL -> localLlmProxy.calculateScoreAndProduceComments(asCsv, model);
+            case PROVIDER_GROQ -> groqProxy.calculateScoreAndProduceComments(asCsv, model);
+            default -> throw new IllegalArgumentException("Unknown provider: " + provider + ". Supported values: local, groq");
+        };
     }
 
     private String extractCsv(ExtractionRequest request) {

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

@@ -7,6 +7,7 @@ import org.springframework.http.MediaType;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestPart;
+import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 import org.springframework.web.multipart.MultipartFile;
@@ -22,18 +23,32 @@ public class EnhancementController {
     private final EnhancementService enhancementService;
 
     @PostMapping("single-step")
-    public String singleStepAnalysis(@RequestBody String html) {
-        return enhancementService.calculateScoreAndProduceCommentsWithSingleCall(ExtractionRequest.fromHtml(html));
+    public String singleStepAnalysis(@RequestBody String html,
+                                     @RequestParam(value = "provider", required = false) String provider,
+                                     @RequestParam(value = "model", required = false) String model) {
+        try {
+            return enhancementService.calculateScoreAndProduceCommentsWithSingleCall(ExtractionRequest.fromHtml(html), provider, model);
+        } catch (IllegalArgumentException e) {
+            throw new ResponseStatusException(BAD_REQUEST, e.getMessage(), e);
+        }
     }
 
     @PostMapping(value = "single-step/file", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
-    public String singleStepAnalysisFile(@RequestPart("file") MultipartFile file) {
+    public String singleStepAnalysisFile(@RequestPart("file") MultipartFile file,
+                                         @RequestParam(value = "provider", required = false) String provider,
+                                         @RequestParam(value = "model", required = false) String model) {
         if (file.isEmpty()) {
             throw new ResponseStatusException(BAD_REQUEST, "Uploaded file is empty");
         }
 
         try {
-            return enhancementService.calculateScoreAndProduceCommentsWithSingleCall(ExtractionRequest.fromFile(file.getBytes(), file.getOriginalFilename(), file.getContentType()));
+            return enhancementService.calculateScoreAndProduceCommentsWithSingleCall(
+                    ExtractionRequest.fromFile(file.getBytes(), file.getOriginalFilename(), file.getContentType()),
+                    provider,
+                    model
+            );
+        } catch (IllegalArgumentException e) {
+            throw new ResponseStatusException(BAD_REQUEST, e.getMessage(), e);
         } catch (Exception e) {
             throw new ResponseStatusException(BAD_REQUEST, "Unable to read uploaded file", e);
         }

+ 1 - 1
src/main/resources/application-local.properties

@@ -7,7 +7,7 @@ spring.ai.openai.base-url=http://tyrion.uv.es:8090
 spring.ai.openai.api-key=hhOQ6QBqHKtOO9MKAUhIyU9auBkgIF40QJKa24jWJzdtxvdXMLi10xUAWMsdpFP0
 spring.ai.openai.chat.options.model=/media/nas/peerobs_sync/shared/2025-ReviewSim/models/Qwen2.5-7B-Instruct-AWQ
 
-spring.ai.groq.base-url=https://api.groq.com/openai/v1
+spring.ai.groq.base-url=https://api.groq.com/openai
 spring.ai.groq.api-key=YOUR_GROQ_API_KEY
 spring.ai.groq.chat.options.model=llama3-70b-8192