package xx.xxxxx.streamingaveclangchain.gpt; // A modifier import dev.langchain4j.data.message.SystemMessage; import dev.langchain4j.memory.ChatMemory; import dev.langchain4j.memory.chat.TokenWindowChatMemory; import dev.langchain4j.model.Tokenizer; import dev.langchain4j.model.chat.StreamingChatLanguageModel; import dev.langchain4j.model.openai.OpenAiStreamingChatModel; import dev.langchain4j.model.openai.OpenAiTokenizer; import dev.langchain4j.service.AiServices; import dev.langchain4j.service.TokenStream; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.Dependent; import jakarta.faces.push.Push; import jakarta.faces.push.PushContext; import jakarta.inject.Inject; import java.util.concurrent.ExecutionException; import java.util.logging.Logger; /** * Gère l'interface avec l'API OpenAI en utilisant LangChain4j. * Son rôle est essentiellement de lancer une requête à chaque nouvelle * question qu'on veut envoyer à l'API. * <p> * De portée dépendent pour réinitialiser la conversation à chaque fois que * l'instance qui l'utilise est renouvelée. * Par exemple, si l'instance qui l'utilise est de portée View, la conversation est * réinitialisée à chaque fois que l'utilisateur quitte la page en cours. * Cette classe doit implémenter Serializable pour être injectée dans un bean JSF de portée View. */ @Dependent public class OpenAiClient implements java.io.Serializable { private static final Logger logger = Logger.getLogger("xx.xxxxx.streamingaveclangchain.gpt.OpenAiClient"); // A modifier /** * Modèle de l'API. */ private String modelName = "gpt-4o-mini"; private final StreamingChatLanguageModel model; private String systemRole; private ChatMemory chatMemory; private String reponse; private final Assistant assistant; /** * PushContext pour envoyer des messages aux clients via le websocket. */ @Inject @Push(channel = "chat") private PushContext webSocketPourChat; public OpenAiClient() { // Récupère la clé secrète pour travailler avec l'API d'OpenAI, mise dans une variable d'environnement // du système d'exploitation. String openAiKey = System.getenv("CHATGPT_KEY"); logger.info("Clé secrète OpenAI: " + openAiKey); // Utilisé pour limiter la taille de la mémoire de chat à 1000 tokens Tokenizer tokenizer = new OpenAiTokenizer(modelName); this.chatMemory = TokenWindowChatMemory.withMaxTokens(1000, tokenizer); this.model = OpenAiStreamingChatModel.withApiKey(openAiKey); this.assistant = AiServices.builder(Assistant.class) .streamingChatLanguageModel(this.model) .chatMemory(chatMemory) .build(); } public void setSystemRole(String systemRole) { this.systemRole = systemRole; // Inutile ? this.chatMemory.add(new SystemMessage(systemRole)); } public String getReponse() { return this.reponse; } /** * Envoie une requête à l'API OpenAI. * Déclenche le streaming pour que les tokens de la réponse soient récupérés et affichés. * Quand le streaming est terminé, la réponse est ajoutée à la mémoire de chat et mise à * disposition pour être affichée et le message "streamingfinished" est envoyé dans le websocket. * * @param question la question. * @throws ExecutionException, InterruptedException à cause de CompletableFuture. */ public void envoyerRequete(String question) throws ExecutionException, InterruptedException { // Réinitialise la réponse pour que la nouvelle réponse soit affichée seulement // après que le streaming soit terminé. this.reponse = ""; TokenStream tokenStream = this.assistant.chat(question); tokenStream.onNext(token -> { logger.info("**" + token); // Envoie le token dans le websocket webSocketPourChat.send(token); }) .onComplete(response -> { this.reponse = response.content().text(); // Ajoute la réponse à la mémoire de chat this.chatMemory.add(response.content()); // Envoie un message pour dire que le streaming est terminé. // Sera traité par le client pour afficher la réponse. webSocketPourChat.send("streamingfinished"); }) .onError(throwable -> { // TODO: Gérer l'erreur throwable.printStackTrace(); }) .start(); } }