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
|
||||
[1751132597] warming up the model with an empty run
|
||||
[1751134161] 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 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";
|
||||
}
|
||||
|
@ -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<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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
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
|
||||
}
|
||||
}
|
@ -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<ChatMessage> 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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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)) {
|
||||
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