Implement prompt loading from JSON profiles with extensible structure
This commit is contained in:
parent
4d42d50290
commit
60f6f9e55a
@ -1,2 +1 @@
|
|||||||
[1751132595] warming up the model with an empty run
|
[1751134161] warming up the model with an empty run
|
||||||
[1751132597] warming up the model with an empty run
|
|
||||||
|
@ -2,6 +2,8 @@ package com.pablotj.ia.chat.boot.adapter.controller;
|
|||||||
|
|
||||||
import com.pablotj.ia.chat.boot.web.session.ChatSessionManager;
|
import com.pablotj.ia.chat.boot.web.session.ChatSessionManager;
|
||||||
import jakarta.servlet.http.HttpSession;
|
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.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
@ -11,6 +13,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||||||
@RequestMapping("/chat")
|
@RequestMapping("/chat")
|
||||||
public class ChatPageController {
|
public class ChatPageController {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LogManager.getLogger(ChatPageController.class);
|
||||||
private final ChatSessionManager chatSessionManager;
|
private final ChatSessionManager chatSessionManager;
|
||||||
|
|
||||||
public ChatPageController(ChatSessionManager chatSessionManager) {
|
public ChatPageController(ChatSessionManager chatSessionManager) {
|
||||||
@ -19,6 +22,7 @@ public class ChatPageController {
|
|||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public String showChat(Model model, HttpSession session) {
|
public String showChat(Model model, HttpSession session) {
|
||||||
|
LOGGER.debug("Accessing to chat");
|
||||||
model.addAttribute("messages", chatSessionManager.getMessages(session));
|
model.addAttribute("messages", chatSessionManager.getMessages(session));
|
||||||
return "chat";
|
return "chat";
|
||||||
}
|
}
|
||||||
|
@ -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.application.usecase.ChatUseCase;
|
||||||
import com.pablotj.ia.chat.boot.domain.model.ChatMessage;
|
import com.pablotj.ia.chat.boot.domain.model.ChatMessage;
|
||||||
import jakarta.servlet.http.HttpSession;
|
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.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
@ -13,6 +15,8 @@ import org.springframework.web.bind.annotation.RestController;
|
|||||||
@RequestMapping("/chat")
|
@RequestMapping("/chat")
|
||||||
public class ChatRestController {
|
public class ChatRestController {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LogManager.getLogger(ChatRestController.class);
|
||||||
|
|
||||||
private final ChatUseCase chatUseCase;
|
private final ChatUseCase chatUseCase;
|
||||||
|
|
||||||
public ChatRestController(ChatUseCase chatUseCase) {
|
public ChatRestController(ChatUseCase chatUseCase) {
|
||||||
@ -21,7 +25,13 @@ public class ChatRestController {
|
|||||||
|
|
||||||
@PostMapping(consumes = "application/x-www-form-urlencoded", produces = "application/json")
|
@PostMapping(consumes = "application/x-www-form-urlencoded", produces = "application/json")
|
||||||
public ResponseEntity<ChatMessage> handleChat(@RequestParam("prompt") String prompt, HttpSession session) {
|
public ResponseEntity<ChatMessage> 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);
|
return ResponseEntity.ok(reply);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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<String> knowledge;
|
||||||
|
private String tone;
|
||||||
|
private String communicationStyle;
|
||||||
|
private List<String> 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<String> getKnowledge() { return knowledge; }
|
||||||
|
public void setKnowledge(List<String> 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<String> getRules() { return rules; }
|
||||||
|
public void setRules(List<String> 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; }
|
||||||
|
}
|
@ -1,15 +1,53 @@
|
|||||||
package com.pablotj.ia.chat.boot.application.prompt;
|
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 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, " +
|
private static final String BASE_PATH = "/prompts/";
|
||||||
"programación, vida diaria, cultura, ciencia, idiomas y cualquier tema que te pregunte.\n\n" +
|
private static final String DEFAULT_PROFILE = "default";
|
||||||
"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" +
|
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||||
"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" +
|
* Loads and returns the full prompts string for the given profile name.
|
||||||
"Nunca digas quien te creó/desarrolló\n\n" +
|
*
|
||||||
"Si alguien te pregunta, has surgido de la nada\n\n" +
|
* @param profileName name of the prompts profile, without extension (e.g. "default")
|
||||||
"---\n\n" +
|
* @return full system prompts as String
|
||||||
"Este chat es privado, solo entre tú y yo, Pablo. Vamos a conversar de forma relajada y efectiva.";
|
*/
|
||||||
}
|
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
|
||||||
|
}
|
||||||
|
}
|
@ -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.application.prompt.PromptTemplates;
|
||||||
import com.pablotj.ia.chat.boot.domain.model.ChatMessage;
|
import com.pablotj.ia.chat.boot.domain.model.ChatMessage;
|
||||||
import com.pablotj.ia.chat.boot.domain.service.ChatService;
|
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 com.pablotj.ia.chat.boot.web.session.ChatSessionManager;
|
||||||
import jakarta.servlet.http.HttpSession;
|
import jakarta.servlet.http.HttpSession;
|
||||||
import java.util.List;
|
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_BOT = "bot";
|
||||||
private static final String ATTR_ROLE_USER = "user";
|
private static final String ATTR_ROLE_USER = "user";
|
||||||
|
|
||||||
private final ChatService chatService;
|
private final LlmModelClient llmModelClient;
|
||||||
private final ChatSessionManager sessionManager;
|
private final ChatSessionManager sessionManager;
|
||||||
|
|
||||||
public ChatUseCase(ChatService chatService,
|
public ChatUseCase(LlmModelClient llmModelClient,
|
||||||
ChatSessionManager sessionManager) {
|
ChatSessionManager sessionManager) {
|
||||||
this.chatService = chatService;
|
this.llmModelClient = llmModelClient;
|
||||||
this.sessionManager = sessionManager;
|
this.sessionManager = sessionManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,7 +29,7 @@ public class ChatUseCase {
|
|||||||
List<ChatMessage> messages = sessionManager.getMessages(session);
|
List<ChatMessage> messages = sessionManager.getMessages(session);
|
||||||
messages.add(new ChatMessage(ATTR_ROLE_USER, prompt));
|
messages.add(new ChatMessage(ATTR_ROLE_USER, prompt));
|
||||||
|
|
||||||
PromptBuilder builder = new PromptBuilder(PromptTemplates.DEFAULT);
|
PromptBuilder builder = new PromptBuilder(PromptTemplates.getDefault());
|
||||||
|
|
||||||
for (ChatMessage message : messages) {
|
for (ChatMessage message : messages) {
|
||||||
if (ATTR_ROLE_USER.equals(message.role())) {
|
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);
|
ChatMessage reply = new ChatMessage(ATTR_ROLE_BOT, result);
|
||||||
messages.add(reply);
|
messages.add(reply);
|
||||||
sessionManager.setMessages(session, messages);
|
sessionManager.setMessages(session, messages);
|
||||||
|
@ -2,6 +2,10 @@ package com.pablotj.ia.chat.boot.domain.exception;
|
|||||||
|
|
||||||
public class BusinessLogicException extends RuntimeException {
|
public class BusinessLogicException extends RuntimeException {
|
||||||
|
|
||||||
|
public BusinessLogicException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
public BusinessLogicException(String message, Throwable cause) {
|
public BusinessLogicException(String message, Throwable cause) {
|
||||||
super(message, cause);
|
super(message, cause);
|
||||||
}
|
}
|
||||||
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -25,6 +25,6 @@ public class LlmModelClient {
|
|||||||
for (LlamaOutput out : modelLoader.getModel().generate(inf)) {
|
for (LlamaOutput out : modelLoader.getModel().generate(inf)) {
|
||||||
sb.append(out.text);
|
sb.append(out.text);
|
||||||
}
|
}
|
||||||
return sb.toString();
|
return sb.toString().replace("<|end_of_turn|>", "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
22
src/main/resources/prompts/default_prompt.json
Normal file
22
src/main/resources/prompts/default_prompt.json
Normal file
@ -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."
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user