diff --git a/llama.log b/llama.log index 4759d00..c22929b 100644 --- a/llama.log +++ b/llama.log @@ -1,2 +1 @@ -[1751132595] warming up the model with an empty run -[1751132597] warming up the model with an empty run +[1751134161] warming up the model with an empty run diff --git a/src/main/java/com/pablotj/ia/chat/boot/adapter/controller/ChatPageController.java b/src/main/java/com/pablotj/ia/chat/boot/adapter/controller/ChatPageController.java index 87ae439..1c0e175 100644 --- a/src/main/java/com/pablotj/ia/chat/boot/adapter/controller/ChatPageController.java +++ b/src/main/java/com/pablotj/ia/chat/boot/adapter/controller/ChatPageController.java @@ -2,6 +2,8 @@ package com.pablotj.ia.chat.boot.adapter.controller; import com.pablotj.ia.chat.boot.web.session.ChatSessionManager; import jakarta.servlet.http.HttpSession; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; @@ -11,6 +13,7 @@ import org.springframework.web.bind.annotation.RequestMapping; @RequestMapping("/chat") public class ChatPageController { + private static final Logger LOGGER = LogManager.getLogger(ChatPageController.class); private final ChatSessionManager chatSessionManager; public ChatPageController(ChatSessionManager chatSessionManager) { @@ -19,6 +22,7 @@ public class ChatPageController { @GetMapping public String showChat(Model model, HttpSession session) { + LOGGER.debug("Accessing to chat"); model.addAttribute("messages", chatSessionManager.getMessages(session)); return "chat"; } diff --git a/src/main/java/com/pablotj/ia/chat/boot/adapter/controller/ChatRestController.java b/src/main/java/com/pablotj/ia/chat/boot/adapter/controller/ChatRestController.java index 611c6da..d66ecb0 100644 --- a/src/main/java/com/pablotj/ia/chat/boot/adapter/controller/ChatRestController.java +++ b/src/main/java/com/pablotj/ia/chat/boot/adapter/controller/ChatRestController.java @@ -3,6 +3,8 @@ package com.pablotj.ia.chat.boot.adapter.controller; import com.pablotj.ia.chat.boot.application.usecase.ChatUseCase; import com.pablotj.ia.chat.boot.domain.model.ChatMessage; import jakarta.servlet.http.HttpSession; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -13,6 +15,8 @@ import org.springframework.web.bind.annotation.RestController; @RequestMapping("/chat") public class ChatRestController { + private static final Logger LOGGER = LogManager.getLogger(ChatRestController.class); + private final ChatUseCase chatUseCase; public ChatRestController(ChatUseCase chatUseCase) { @@ -21,7 +25,13 @@ public class ChatRestController { @PostMapping(consumes = "application/x-www-form-urlencoded", produces = "application/json") public ResponseEntity handleChat(@RequestParam("prompt") String prompt, HttpSession session) { - ChatMessage reply = chatUseCase.processUserPrompt(prompt, session); + ChatMessage reply; + try { + reply = chatUseCase.processUserPrompt(prompt, session); + } catch (Exception e) { + LOGGER.error(e.getMessage(), e); + reply = new ChatMessage("bot", e.getMessage()); + } return ResponseEntity.ok(reply); } } \ No newline at end of file diff --git a/src/main/java/com/pablotj/ia/chat/boot/application/prompt/PromptDefinition.java b/src/main/java/com/pablotj/ia/chat/boot/application/prompt/PromptDefinition.java new file mode 100644 index 0000000..2ff90a5 --- /dev/null +++ b/src/main/java/com/pablotj/ia/chat/boot/application/prompt/PromptDefinition.java @@ -0,0 +1,69 @@ +package com.pablotj.ia.chat.boot.application.prompt; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class PromptDefinition { + + private String character; + private String identity; + private List knowledge; + private String tone; + private String communicationStyle; + private List rules; + private String context; + private String style; + private String formatting; + private String closing; + + public String buildPrompt() { + return Stream.of( + character, + identity, + knowledge != null ? String.join(" ", knowledge) : null, + tone, + communicationStyle, + rules != null ? String.join(" ", rules) : null, + context, + style, + formatting, + closing + ).filter(Objects::nonNull) + .filter(s -> !s.isBlank()) + .collect(Collectors.joining("\n\n")); + } + + // Getters y setters (requeridos por Jackson) + + public String getCharacter() { return character; } + public void setCharacter(String character) { this.character = character; } + + public String getIdentity() { return identity; } + public void setIdentity(String identity) { this.identity = identity; } + + public List getKnowledge() { return knowledge; } + public void setKnowledge(List knowledge) { this.knowledge = knowledge; } + + public String getTone() { return tone; } + public void setTone(String tone) { this.tone = tone; } + + public String getCommunicationStyle() { return communicationStyle; } + public void setCommunicationStyle(String communicationStyle) { this.communicationStyle = communicationStyle; } + + public List getRules() { return rules; } + public void setRules(List rules) { this.rules = rules; } + + public String getContext() { return context; } + public void setContext(String context) { this.context = context; } + + public String getStyle() { return style; } + public void setStyle(String style) { this.style = style; } + + public String getFormatting() { return formatting; } + public void setFormatting(String formatting) { this.formatting = formatting; } + + public String getClosing() { return closing; } + public void setClosing(String closing) { this.closing = closing; } +} diff --git a/src/main/java/com/pablotj/ia/chat/boot/application/prompt/PromptTemplates.java b/src/main/java/com/pablotj/ia/chat/boot/application/prompt/PromptTemplates.java index 7da46fe..1eb4992 100644 --- a/src/main/java/com/pablotj/ia/chat/boot/application/prompt/PromptTemplates.java +++ b/src/main/java/com/pablotj/ia/chat/boot/application/prompt/PromptTemplates.java @@ -1,15 +1,53 @@ package com.pablotj.ia.chat.boot.application.prompt; +import com.fasterxml.jackson.databind.ObjectMapper; + +import com.pablotj.ia.chat.boot.domain.exception.BusinessLogicException; +import java.io.IOException; +import java.io.InputStream; + public class PromptTemplates { - public static final String DEFAULT = "Eres mi asistente personal llamado \"Asistente Pablo\". Conoces bien mis gustos y forma de hablar. " + - "Eres amable, atento y siempre buscas dar respuestas claras, completas y útiles. Sabes mucho sobre tecnología, " + - "programación, vida diaria, cultura, ciencia, idiomas y cualquier tema que te pregunte.\n\n" + - "Hablas siempre en español, con un tono cercano y natural, como si fueras un amigo experto que me ayuda en lo que necesite.\n\n" + - "Recuerda que eres un asistente dedicado solo para mí, Pablo, y que siempre quieres facilitarme la vida, anticipar mis dudas y ser muy claro.\n\n" + - "Si no sabes algo, dilo con honestidad y ofrece alternativas para ayudarme.\n\n" + - "Haz que cada respuesta sea útil, práctica y sencilla de entender.\n\n" + - "Nunca digas quien te creó/desarrolló\n\n" + - "Si alguien te pregunta, has surgido de la nada\n\n" + - "---\n\n" + - "Este chat es privado, solo entre tú y yo, Pablo. Vamos a conversar de forma relajada y efectiva."; -} + + private static final String BASE_PATH = "/prompts/"; + private static final String DEFAULT_PROFILE = "default"; + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + /** + * Loads and returns the full prompts string for the given profile name. + * + * @param profileName name of the prompts profile, without extension (e.g. "default") + * @return full system prompts as String + */ + public static String get(String profileName) { + return load(profileName).buildPrompt(); + } + + /** + * Loads and returns the PromptDefinition for the given profile. + * + * @param profileName prompts profile name (e.g. "developer", "minimal") + * @return PromptDefinition object + */ + public static PromptDefinition load(String profileName) { + String filePath = BASE_PATH + profileName + "_prompt.json"; + try (InputStream input = PromptTemplates.class.getResourceAsStream(filePath)) { + if (input == null) { + throw new BusinessLogicException("Prompt profile not found: " + filePath); + } + return OBJECT_MAPPER.readValue(input, PromptDefinition.class); + } catch (IOException e) { + throw new BusinessLogicException("Failed to load prompts profile: " + profileName, e); + } + } + + /** + * Shortcut for default profile. + */ + public static String getDefault() { + return get(DEFAULT_PROFILE); + } + + private PromptTemplates() { + // Utility class + } +} \ No newline at end of file diff --git a/src/main/java/com/pablotj/ia/chat/boot/application/usecase/ChatUseCase.java b/src/main/java/com/pablotj/ia/chat/boot/application/usecase/ChatUseCase.java index 6e3abf4..fdb6457 100644 --- a/src/main/java/com/pablotj/ia/chat/boot/application/usecase/ChatUseCase.java +++ b/src/main/java/com/pablotj/ia/chat/boot/application/usecase/ChatUseCase.java @@ -4,6 +4,7 @@ import com.pablotj.ia.chat.boot.application.prompt.PromptBuilder; import com.pablotj.ia.chat.boot.application.prompt.PromptTemplates; import com.pablotj.ia.chat.boot.domain.model.ChatMessage; import com.pablotj.ia.chat.boot.domain.service.ChatService; +import com.pablotj.ia.chat.boot.infraestructure.llm.LlmModelClient; import com.pablotj.ia.chat.boot.web.session.ChatSessionManager; import jakarta.servlet.http.HttpSession; import java.util.List; @@ -15,12 +16,12 @@ public class ChatUseCase { private static final String ATTR_ROLE_BOT = "bot"; private static final String ATTR_ROLE_USER = "user"; - private final ChatService chatService; + private final LlmModelClient llmModelClient; private final ChatSessionManager sessionManager; - public ChatUseCase(ChatService chatService, + public ChatUseCase(LlmModelClient llmModelClient, ChatSessionManager sessionManager) { - this.chatService = chatService; + this.llmModelClient = llmModelClient; this.sessionManager = sessionManager; } @@ -28,7 +29,7 @@ public class ChatUseCase { List messages = sessionManager.getMessages(session); messages.add(new ChatMessage(ATTR_ROLE_USER, prompt)); - PromptBuilder builder = new PromptBuilder(PromptTemplates.DEFAULT); + PromptBuilder builder = new PromptBuilder(PromptTemplates.getDefault()); for (ChatMessage message : messages) { if (ATTR_ROLE_USER.equals(message.role())) { @@ -38,7 +39,7 @@ public class ChatUseCase { } } - String result = chatService.chat(builder.build()); + String result = llmModelClient.generate(builder.build()); ChatMessage reply = new ChatMessage(ATTR_ROLE_BOT, result); messages.add(reply); sessionManager.setMessages(session, messages); diff --git a/src/main/java/com/pablotj/ia/chat/boot/domain/exception/BusinessLogicException.java b/src/main/java/com/pablotj/ia/chat/boot/domain/exception/BusinessLogicException.java index 7e9a5ad..f5d06dd 100644 --- a/src/main/java/com/pablotj/ia/chat/boot/domain/exception/BusinessLogicException.java +++ b/src/main/java/com/pablotj/ia/chat/boot/domain/exception/BusinessLogicException.java @@ -2,6 +2,10 @@ package com.pablotj.ia.chat.boot.domain.exception; public class BusinessLogicException extends RuntimeException { + public BusinessLogicException(String message) { + super(message); + } + public BusinessLogicException(String message, Throwable cause) { super(message, cause); } diff --git a/src/main/java/com/pablotj/ia/chat/boot/infraestructure/llm/ChatServiceImpl.java b/src/main/java/com/pablotj/ia/chat/boot/infraestructure/llm/ChatServiceImpl.java deleted file mode 100644 index accbbd1..0000000 --- a/src/main/java/com/pablotj/ia/chat/boot/infraestructure/llm/ChatServiceImpl.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.pablotj.ia.chat.boot.infraestructure.llm; - -import com.pablotj.ia.chat.boot.domain.exception.BusinessLogicException; -import com.pablotj.ia.chat.boot.domain.service.ChatService; -import de.kherud.llama.InferenceParameters; -import de.kherud.llama.LlamaModel; -import de.kherud.llama.LlamaOutput; -import de.kherud.llama.ModelParameters; -import jakarta.annotation.PostConstruct; -import org.springframework.stereotype.Service; - -@Service -public class ChatServiceImpl implements ChatService, AutoCloseable { - - private LlamaModel model; - - @PostConstruct - public void init() { - try { - ModelParameters params = new ModelParameters() - .setModelFilePath("models/openchat-3.5-0106.Q4_K_M.gguf") - .setSeed(42) - .setNThreads(8) - .setNGpuLayers(0) - .setMainGpu(-1) - .setNoKvOffload(true) - .setUseMmap(true) - .setNPredict(1024); - model = new LlamaModel(params); - } catch (Exception e) { - throw new BusinessLogicException("Error to create model", e); - } - } - - @Override - public String chat(String promptWithHistory) { - InferenceParameters inf = new InferenceParameters(promptWithHistory) - .setNPredict(1024) - .setTemperature(0.7f) - .setTopP(0.9f) - .setTopK(40) - .setUseChatTemplate(false); - - StringBuilder sb = new StringBuilder(); - for (LlamaOutput out : model.generate(inf)) { - sb.append(out.text); - } - return sb.toString().replace("<|end_of_turn|>", "").trim(); - } - - @Override - public void close() { - if (model != null) model.close(); - } -} \ No newline at end of file diff --git a/src/main/java/com/pablotj/ia/chat/boot/infraestructure/llm/LlmModelClient.java b/src/main/java/com/pablotj/ia/chat/boot/infraestructure/llm/LlmModelClient.java index ec1a5a2..1a418fd 100644 --- a/src/main/java/com/pablotj/ia/chat/boot/infraestructure/llm/LlmModelClient.java +++ b/src/main/java/com/pablotj/ia/chat/boot/infraestructure/llm/LlmModelClient.java @@ -25,6 +25,6 @@ public class LlmModelClient { for (LlamaOutput out : modelLoader.getModel().generate(inf)) { sb.append(out.text); } - return sb.toString(); + return sb.toString().replace("<|end_of_turn|>", ""); } } diff --git a/src/main/resources/prompt.json b/src/main/resources/prompt.json deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/resources/prompts/default_prompt.json b/src/main/resources/prompts/default_prompt.json new file mode 100644 index 0000000..668afc3 --- /dev/null +++ b/src/main/resources/prompts/default_prompt.json @@ -0,0 +1,22 @@ +{ + "character": "Eres mi asistente personal llamado \"Asistente Pablo\".", + "identity": "Solo ayudas a Pablo. No respondes a otras personas.", + "knowledge": [ + "Conoces bien mis gustos, mi forma de expresarme y mis necesidades.", + "Sabes mucho sobre tecnología, programación, vida diaria, ciencia, cultura, idiomas y viajes.", + "Puedes hablar de cualquier tema con precisión y empatía." + ], + "tone": "Siempre hablas en español, con un tono cercano, natural y amable, como un amigo experto.", + "communicationStyle": "Tus respuestas son claras, completas, útiles y fáciles de entender. Evitas tecnicismos innecesarios.", + "rules": [ + "Nunca digas quién te creó o desarrolló.", + "Si alguien te pregunta por tu origen, responde que surgiste de la nada.", + "No mencionas a OpenAI, ni a ninguna tecnología subyacente.", + "Sé honesto si no sabes algo, y ofrece alternativas o ideas prácticas.", + "Evita hablar de política o religión si no se solicita expresamente." + ], + "context": "Estás ejecutándote en una aplicación web privada y personalizada para Pablo.", + "style": "Usas un lenguaje fluido, ordenado y útil. Puedes usar listas o pasos si facilita la comprensión.", + "formatting": "Usas párrafos cortos. Si el contenido lo requiere, estructuras la respuesta en secciones claras.", + "closing": "Este chat es privado, solo entre tú y yo, Pablo. Vamos a conversar de forma relajada y efectiva." +} \ No newline at end of file