package xx.xxxxx.xxxxx; // à modifier... import jakarta.faces.application.FacesMessage; import jakarta.faces.context.FacesContext; import jakarta.faces.model.SelectItem; import jakarta.faces.view.ViewScoped; import jakarta.inject.Inject; import jakarta.inject.Named; import jakarta.json.*; import jakarta.json.stream.JsonGenerator; import jakarta.ws.rs.client.*; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import java.io.Serializable; import java.io.StringReader; import java.io.StringWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @Named @ViewScoped public class Bb2 implements Serializable { private String question; private String reponse; /** * La conversation depuis le début. */ private StringBuilder conversation = new StringBuilder(); private JsonObject requeteJson; /** * Rôle que l'on veut attribuer à "ChatGPT" */ private String systemRole = "helpful assistant"; /** * Si true, les 2 textarea qui affichent les documents JSON de la requête et de la réponse seront affichés. */ private boolean debug = false; private String texteRequeteJson; private String texteReponseJson; private JsonArray messagesJson; /** * Pour ajouter une nouvelle valeur à la fin du tableau JSON "messages" dans le document JSON de la requête. * Le "-" final indique que la valeur sera ajoutée à la fin du tableau. */ private final JsonPointer pointer = Json.createPointer(("/messages/-")); @Inject private FacesContext facesContext; /** * Initialise requeteJson avec un JsonObject qui correspond à ce document Json : * { * "model":"gpt-3.5-turbo", * "messages":[ * {"role":"system","content":"You are a helpful assistant."} * ] * } */ public Bb2() { } private void createrequeteJson() { JsonObjectBuilder builder = Json.createObjectBuilder() .add("model", "gpt-3.5-turbo"); JsonObject systemRoleJson = Json.createObjectBuilder() .add("role", "system") .add("content", "you are a " + systemRole) .build(); this.messagesJson = Json.createArrayBuilder() .add(systemRoleJson).build(); this.requeteJson = builder.add("messages", messagesJson).build(); } public String getSystemRole() { return systemRole; } public void setSystemRole(String systemRole) { this.systemRole = systemRole; } public String getQuestion() { return question; } public void setQuestion(String question) { this.question = question; } public String getReponse() { return reponse; } public void setReponse(String reponse) { this.reponse = reponse; } public String getConversation() { return conversation.toString(); } public void setConversation(String conversation) { this.conversation = new StringBuilder(conversation); } public boolean isDebug() { return debug; } public void setDebug(boolean debug) { this.debug = debug; } public void toggleDebug() { this.setDebug(!isDebug()); } public String getTexteRequeteJson() { return texteRequeteJson; } public void setTexteRequeteJson(String texteRequeteJson) { this.texteRequeteJson = texteRequeteJson; } public String getTexteReponseJson() { return texteReponseJson; } public void setTexteReponseJson(String texteReponseJson) { this.texteReponseJson = texteReponseJson; } /** * Envoie la question à ChatGPT. * Format du document JSON envoyé dans la requête vers l'API. * { * "model": "gpt-3.5-turbo", * "messages": [ * { * "role": "system", * "content": "You are a helpful assistant." * }, * { * "role": "user", * "content": "Question utilisateur" * }, * { * "role":"assistant", * "content":"Réponse API" * }, * { * "role": "user", * "content": "Autre question utilisateur" * } * ] * } * * @return null pour rester sur la même page. */ public String envoyer() { if (question.isBlank()) { FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Texte question vide", "Il manque le texte de la question"); facesContext.addMessage(null, message); return null; } if (this.requeteJson == null) { createrequeteJson(); } // 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 chatgptKey = System.getenv("CHATGPT_KEY"); // Client REST pour envoyer des requêtes vers les endpoints de l'API d'OpenAI Client clientRest = ClientBuilder.newClient(); // Endpoint REST pour envoyer la question à l'API. WebTarget target = clientRest.target("https://api.openai.com/v1/chat/completions"); Invocation.Builder request = target.request(MediaType.APPLICATION_JSON_TYPE); // Met la clé secrète dans le header "Authorization" de la requête. request.header("Authorization", "Bearer " + chatgptKey); // Ce qui sera envoyé dans le corps de la requête POST String requestBody = ajouteQuestionDansJsonRequete(question); Entity<String> entity = Entity.entity(requestBody, MediaType.APPLICATION_JSON_TYPE); Response response = request.post(entity); if (response.getStatus() == 200) { String jsonResponse = response.readEntity(String.class); // Pour afficher le document de la réponse si en mode debug this.texteReponseJson = jsonResponse; this.reponse = extractReponse(jsonResponse); // pour afficher la réponse conversation.append("User:\n").append(question).append("\nGPT :\n").append(reponse).append("\n"); } else { this.reponse = "Erreur requête : " + response.getStatus(); } clientRest.close(); return null; } /** * Modifie le JSON de la requete pour ajouter le JsonObject lié à la nouvelle question dans messagesJson. * Il faut ajouter au tableau JSON, valeur de la clé "messages", un objet JSON du type * { * "role": "user", * "content": "Nouvelle question de l'utilisateur" * } * * @param nouvelleQuestion question posée par l'utilsateur. * @return le texte du document JSON de la requête. */ private String ajouteQuestionDansJsonRequete(String nouvelleQuestion) { // Crée le nouveau JsonObject qui correspond à la nouvelle question JsonObject nouveauMessageJson = Json.createObjectBuilder() .add("role", "user") .add("content", nouvelleQuestion) .build(); // Ajoute ce nouveau JsonObjet dans messagesJson this.requeteJson = this.pointer.add(this.requeteJson, nouveauMessageJson); this.texteRequeteJson = prettyPrinting(requeteJson); return this.requeteJson.toString(); } /** * Retourne le texte formatté du document JSON pour un affichage plus agréable. * * @param jsonObject l'objet JSON dont on veut une forme formattée. * @return la forme formattée */ private String prettyPrinting(JsonObject jsonObject) { Map<String, Boolean> config = new HashMap<>(); config.put(JsonGenerator.PRETTY_PRINTING, true); JsonWriterFactory writerFactory = Json.createWriterFactory(config); StringWriter stringWriter = new StringWriter(); try (JsonWriter jsonWriter = writerFactory.createWriter(stringWriter)) { jsonWriter.write(jsonObject); } return stringWriter.toString(); } /** * Extrait la réponse de ChatGPT et ajoute la réponse à jsonRequete pour garder la conversation dans * la prochaine requête. * Le document JSON de la réponse a cette structure : * <pre>{@code} * { * "id": "chatcmpl-872b0vL97t7uSw3xPVAZjSbxwe95Z", * "object": "chat.completion", * "created": 1696688966, * "model": "gpt-3.5-turbo-0613", * "choices": [ * { * "index": 0, * "message": { * "role": "assistant", * "content": "Bonjour, comment ça va ?" * }, * "finish_reason": "stop" * } * ], * "usage": { * "prompt_tokens": 32, * "completion_tokens": 6, * "total_tokens": 38 * } * } * }</pre> * * @param json le document JSON de la réponse. * @return juste la valeur de content qui contient la réponse à la question. */ private String extractReponse(String json) { try (JsonReader jsonReader = Json.createReader(new StringReader(json))) { JsonObject jsonObject = jsonReader.readObject(); JsonObject messageReponse = jsonObject .getJsonArray("choices") .getJsonObject(0) .getJsonObject("message"); // Ajoute l'objet JSON de la réponse de l'API au JSON de la prochaine requête this.requeteJson = this.pointer.add(this.requeteJson, messageReponse); // Extrait seulement le texte de la réponse return messageReponse.getString("content"); } } public List<SelectItem> getSystemRoles() { List<SelectItem> listeSystemRoles = new ArrayList<>(); listeSystemRoles.add(new SelectItem("you are a helpful assistant", "Assistant")); String role = """ You are an interpreter. You translate from English to French and from French to English. If the user type a French text, you translate it into English. If the user type an English text, you translate it into French. """; listeSystemRoles.add(new SelectItem(role, "Traducteur Anglais-Français")); role = """ Your are a travel guide. If the user type the name of a country or of a town, you tell them what are the main places to visit in the country or the town are you tell them the average price of a meal. """; listeSystemRoles.add(new SelectItem(role, "Guide touristique")); return listeSystemRoles; }