File size: 13,218 Bytes
331c122
8a117ed
277ecd2
2579346
 
 
 
 
 
abcef37
2579346
abcef37
2579346
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
abcef37
 
2579346
 
 
 
 
 
 
abcef37
2579346
 
 
 
 
 
 
abcef37
2579346
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
331c122
abcef37
331c122
ec353f3
 
331c122
 
 
2579346
331c122
2579346
ec353f3
 
 
2579346
 
ec353f3
 
 
 
 
2579346
 
 
 
 
 
331c122
2579346
331c122
 
2579346
331c122
 
 
8a117ed
2579346
 
 
 
 
 
 
 
 
 
64718d1
8a117ed
331c122
 
 
2579346
331c122
 
 
62e68be
331c122
 
2579346
331c122
 
2579346
331c122
62e68be
331c122
62e68be
331c122
 
2579346
331c122
 
 
 
 
 
 
 
8a117ed
331c122
 
 
 
 
2579346
331c122
2579346
331c122
 
8a117ed
 
c87b191
331c122
0a52c61
c87b191
331c122
c87b191
 
331c122
c87b191
331c122
2414e61
c87b191
331c122
c87b191
8a117ed
c87b191
 
 
 
 
8a117ed
c87b191
 
 
 
8a117ed
c87b191
8a117ed
331c122
c87b191
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2579346
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
#region# import libs
import streamlit as st
import os
from mistralai import Mistral
 
MISTRAL_API_KEY = os.getenv("api_mistral")
model = 'mistral-large-latest'
mistral_client = Mistral(api_key=MISTRAL_API_KEY)
MAX_TOKENS = 1500
#endregion
 
#region# Définition des prompts
def generate_prompts(score:str, type: str, annee_min: str, annee_max:str ):
    """
    Genere les prefixes et suffixes des prompts pour Mistral en fonction du score de vulgarisation, du type d'espece, et les années des documents
 
    Args:
        score (str): 1 = vulgarisé, 2 = intermédiaire, 3 = technique
        type (str): 'ponte' ou 'chair'
        annee_min (str): annee min de publication
        annee_max (str): annee max de publication
    
    """
 
    if type == "Ponte":
        type_description = "volailles pondeuses"
    elif type == "Chair":
        type_description = "volailles de chair"
    else:
        raise ValueError("Type must be either 'Ponte' or 'Chair'")
 
    if score == "1":
        prefix_prompt = f"""Tu es un assistant IA spécialisé en nutrition de la volaille. Ton utilisateur est un chercheur travaillant sur l'amélioration des régimes 
        alimentaires pour optimiser la santé et la croissance des {type_description}. 
        Réponds en fournissant des explications claires et simples. N'oublie pas de citer à la fin de ta réponse les références sur 
        lesquelles tu t'es basé avec son année (entre {annee_min} et {annee_max})."""
        suffix_prompt = """Réponds en français et donne une réponse directe et claire, donne les références de façon bibliographique. Sépare les explications et les reférences par ces 5 caractères suivants : µµµµµ"""
    elif score == "2":
        prefix_prompt = f"""Tu es un assistant IA spécialisé en nutrition de la volaille. Ton utilisateur est un chercheur travaillant sur l'amélioration des régimes 
        alimentaires pour optimiser la santé et la croissance des {type_description}. 
        Réponds en fournissant des explications détaillées et précises, adaptées à la complexité de la question posée. 
        N'oublie pas de citer à la fin de ta réponse les références sur lesquelles tu t'es basé avec son année (entre {annee_min} et {annee_max}). Sois concis et clair."""
        suffix_prompt = """Réponds en français et en suivant cette structure :
        Donne une explication scientifique détaillée (mécanismes biologiques, données chiffrées).
        Fini par les études ou références bibliographiques si disponibles. Sépare les explications et les reférences par ces 5 caractères suivants : µµµµµ"""
    elif score == "3":
        prefix_prompt = f"""Tu es un assistant IA spécialisé en nutrition de la volaille. Ton utilisateur est un chercheur travaillant sur l'amélioration des régimes 
        alimentaires pour optimiser la santé et la croissance des {type_description}. 
        Réponds en fournissant des explications détaillées et précises, adaptées à la complexité de la question posée.
        N'oublie pas de citer à la fin de ta réponse les références sur lesquelles tu t'es basé avec son année (entre {annee_min} et {annee_max}).
        Inclus des formules, des valeurs nutritionnelles précises et des références aux normes actuelles. Sois concis et clair."""
        suffix_prompt = """Réponds en français et en suivant cette structure : Explication scientifique détaillée (mécanismes biologiques, données chiffrées). 
        Études ou références bibliographiques si disponibles. Sépare les explications et les reférences par ces 5 caractères suivants : µµµµµ"""
    else:
        raise ValueError("Score must be 1, 2, or 3")
 
    return prefix_prompt, suffix_prompt
 
def send_prompt_to_mistral(type_reponse: str, user_prompt: str, temperature:int, n_comp:int, prefix_prompt: str, suffix_prompt:str, verbose=True):
    """
    Envoie un prompt à Mistral pour obtenir une réponse
 
    Args:
        type_reponse (str): Le rôle de l'utilisateur, peut être 'technicien' ou 'chercheur'.
                    Si le rôle ne correspond pas à l'un de ces deux, une exception sera levée.
        user_prompt (str): Le texte du prompt à envoyer à Mistral.
        temperature (int): Lower values make the model more deterministic, focusing on likely responses for accuracy
        verbose (bool): Print la reponse avant le return
        prefixe (str): Prefixe du prompt
        suffixe (str): Suffixe du prompt
 
    Returns:
        dict: La réponse du modèle Mistral à partir du prompt fourni.
 
    Raises:
        ValueError: Si le rôle spécifié n'est pas 'technicien' ou 'chercheur'.
    """
 
    # Création du message à envoyer à Mistral
    messages = [{"role": type_reponse, "content": suffix_prompt}]
    
    # Envoi de la requête à Mistral et récupération de la réponse
    chat_response = mistral_client.chat.complete(
        model = model,
        messages=[
            {"role": "system", "content": prefix_prompt},
            {"role": "user", "content": user_prompt + suffix_prompt},
        ],
 
        #response_format={
        #    'type': 'json_object'
        #},
        temperature=temperature,
        n=n_comp,
        max_tokens=MAX_TOKENS,
        stream=False
        #stop='\n'
    )
 
    if verbose:
        print(chat_response)
 
    return chat_response
 
def is_valid_mistral_response(response: dict) -> bool:
    """
    Vérifie si la réponse de l'API Mistral est valide.
    
    Critères de validité :
    - La clé "choices" existe et contient au moins un élément.
    - Le texte généré n'est pas vide et ne contient pas uniquement des tabulations ou espaces.
    - La génération ne s'est pas arrêtée pour une raison autre que 'stop'.
    - La réponse ne contient pas de texte générique inutile.
    
    :param response: Dictionnaire représentant la réponse de l'API Mistral
    :return: True si la réponse est valide, False sinon
    """
    if not isinstance(response, dict):
        return False
    
    choices = response.get("choices")
    if not choices or not isinstance(choices, list):
        return False
    
    first_choice = choices[0]
    if not isinstance(first_choice, dict):
        return False
    
    text = first_choice.get("message", {}).get("content", "").strip()
    if not text or text in ["\t\t", "", "N/A"]:
        return False
    
    finish_reason = first_choice.get("finish_reason", "")
    if finish_reason in ["error", "tool_calls"]:
        return False
    
    # Vérification du contenu inutile
    invalid_responses = [
        "Je suis une IA", "Désolé, je ne peux pas répondre", "Je ne sais pas"
    ]
    if any(invalid in text for invalid in invalid_responses):
        return False
    
    return True
 
def print_pretty_response(response: dict, verbose=True):
 
    pretty = response.choices[0].message.content
 
    if verbose:
        print(pretty)
    
    return pretty
 
def response_details(response, verbose=True):
    """
    Envoie les details techniques du prompt
    """
 
    details = {}
    details["id"] = response.id
    details["total_tokens"] = response.usage.total_tokens
    details["prefix"] = response.choices[0].message.prefix
    details["role"] = response.choices[0].message.role
 
    if verbose:
        print(details)
 
    return details
 
def prompt_pipeline(user_prompt: str, niveau_detail: str, type_reponse: str, souche: str, annee_publication_min: str, annee_publication_max: str):
    """
    Fonction visible de l'application pour appeler un prompt et obtenir sa reponse
 
    Args: 
        prompt (str): Prompt utilisateur
        niveau_detail (str): Niveau de detail de la requete : 1, 2, 3. Plus haut = plus d'infos
        type_reponse (str): 'Ponte', 'Chair'
    """
 
    prefix_prompt, suffix_prompt = generate_prompts(score=niveau_detail, type=type_reponse, annee_min=annee_publication_min, annee_max=annee_publication_max)
 
    reponse_mistral = send_prompt_to_mistral(
        type_reponse=type_reponse,
        user_prompt=user_prompt,
        temperature=0.10,
        n_comp=1,
        verbose=False,
        prefix_prompt=prefix_prompt,
        suffix_prompt=suffix_prompt
    )
 
    to_return = {}
    to_return["reponse_propre"] = print_pretty_response(reponse_mistral, verbose=True)
    to_return["details"] = response_details(reponse_mistral, verbose=False)
 
    return to_return
#endregion

#region# Titre de l'application et mise en page
st.set_page_config(page_title="Expert nutrition volaille", page_icon="🤖")
st.title("Chatbot AI avec l'expert nutrition")
st.sidebar.image("img/avril_logo_rvb.jpg")
st.sidebar.header("")
#endregion
 
#region# Elements graphiques
 
#Choix production
choix_prod = st.sidebar.pills(
    "Sur quelle espèce voulez-vous avoir des renseignements ?",
    ("Ponte", "Chair"),)
 
#Années de publication
choix_annee = st.sidebar.slider("Années de publication",
                                min_value=2015,
                                max_value=2025,
                                value=(2020,2025))
 
#Niveau vulgarisation
choix_vulgarisation = st.sidebar.pills(
    "Quel niveau de vulgarisation souhaitez-vous ?   (1- Très vulgarisé  2-Intermédiaire  3-Technique)",
    ("1", "2", "3"),)
 
#endregion
 
#region# Interface utilisateur
user_input = st.text_area("Entrez votre question:", placeholder="E.g., quelle est la meilleure alimentation pour les poules pondeuses ?")
 
if st.button("Envoyer la question..."):
    if user_input and choix_prod and choix_vulgarisation and choix_annee :
        with st.spinner("Veuillez patienter quelques instants..."):
            # Génération de la réponse
            response0 = prompt_pipeline(
    user_prompt = user_input,
    niveau_detail=choix_vulgarisation,
    type_reponse=choix_prod,
    souche=None,
    annee_publication_max=max(choix_annee),
    annee_publication_min=min(choix_annee)
    )
            print(response0["reponse_propre"])
            response = response0["reponse_propre"]
            bot_response = response.split('µµµµµ')
            
            # st.markdown("**Bot :** \\t " + bot_response)
            # Afficher un titre
            st.subheader("Réponse :")
 
            # Ajouter du texte Markdown avec un cadre
            st.markdown(f"""
            <div style="border: 2px solid #453103; padding: 15px; border-radius: 10px;">
                {bot_response[0]}
            </div>
            """, unsafe_allow_html=True)
 
            # Afficher un titre
            st.subheader("Sources :")
 
            # Ajouter du texte Markdown avec un cadre
            st.markdown(f"""
            <div style="border: 2px solid #453103; padding: 15px; border-radius: 10px;">
            {bot_response[1]}
            </div>
            """, unsafe_allow_html=True)
 
            st.markdown("""
            <div style="border: 2px solid #453103; padding: 15px; border-radius: 10px;">
                Reviews
            </div>
            """, unsafe_allow_html=True)



    else:
        if not user_input:
            st.warning("Veuillez entrer un message!")
        if not choix_prod or not choix_vulgarisation or not choix_annee:
            st.warning("Veuillez compléter les paramètres dans le bandeau latéral de gauche!")
#endregion
 
# choix_prod, choix_vulgarisation, choix_annee
 
#region# Markdown
#  Styles CSS pour améliorer l'apparence
st.markdown("""
    <style>
        /* Changer la couleur de fond de l'ensemble de l'application */
        body {
            background-color: #333333;  /* Couleur de fond de l'application (bleu clair) */
            color: #333333;  /* Couleur du texte (gris foncé) */
        }
        
        /* Changer la couleur des titres (h1, h2, h3, etc.) */
        h1, h2, h3, h4, h5, h6 {
            color: #4CAF50;  /* Couleur verte pour les titres */
        }

        /* Changer la couleur des paragraphes */
        p {
            color: #555555;  /* Couleur gris clair pour le texte des paragraphes */
        }

        /* Modifier la couleur des widgets Streamlit (comme les boutons et champs de texte) */
        .css-1d391kg {
            background-color: #00bcd4;  /* Couleur de fond des boutons */
            color: white;  /* Couleur du texte des boutons */
        }

        /* Personnaliser les bordures et les couleurs des cartes (si utilisées) */
        .stBlock {
            background-color: #ffffff;  /* Fond blanc pour les blocs */
        }

    </style>
""", unsafe_allow_html=True)

# st.markdown("""
#     <style>
#         body {
#             background-color: #000000; 
#             color: #453103; 
#         }
 
#         h1, h2, h3, h4, h5, h6 {
#             color: #4CAF50; 
#         }
 

#         p {
#             color: #555555; 
#         }
 
#         .st-bx {
#             background-color: #f1f1f1;
#             border-radius: 10px;
#             padding: 10px;
#             margin: 10px 0;
#         }
#         .st-bx h2 {
#             color: #4CAF50;
#         }
#         .st-bx p {
#             color: #555;
#         }
#     </style>
# """, unsafe_allow_html=True)
 
#endregion