Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -1,80 +1,267 @@
|
|
1 |
#region# import libs
|
2 |
import streamlit as st
|
3 |
import os
|
4 |
-
from
|
5 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6 |
#endregion
|
7 |
-
|
8 |
#region# Titre de l'application et mise en page
|
9 |
st.set_page_config(page_title="Expert nutrition volaille", page_icon="🤖")
|
10 |
st.title("Chatbot AI avec l'expert nutrition")
|
11 |
st.sidebar.image("img/avril_logo_rvb.jpg")
|
12 |
st.sidebar.header("")
|
13 |
#endregion
|
14 |
-
|
15 |
#region# Elements graphiques
|
16 |
-
|
17 |
#Choix production
|
18 |
choix_prod = st.sidebar.pills(
|
19 |
"Sur quelle espèce voulez-vous avoir des renseignements ?",
|
20 |
-
("
|
21 |
-
|
22 |
-
#Niveau vulgarisation
|
23 |
-
choix_vulgarisation = st.sidebar.pills(
|
24 |
-
"Quel niveau de vulgarisation souhaitez-vous ? (1- Très vulgarisé 2-Intermédiaire 3-Technique)",
|
25 |
-
("1", "2", "3"),)
|
26 |
-
|
27 |
-
|
28 |
#Années de publication
|
29 |
choix_annee = st.sidebar.slider("Années de publication",
|
30 |
min_value=2015,
|
31 |
max_value=2025,
|
32 |
value=(2020,2025))
|
33 |
-
|
34 |
-
#
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
@st.cache_data
|
40 |
-
def load_model():
|
41 |
-
return pipeline("text-generation", model="gpt2")
|
42 |
-
|
43 |
-
model = load_model()
|
44 |
#endregion
|
45 |
-
|
46 |
#region# Interface utilisateur
|
47 |
-
st.sidebar.header("")
|
48 |
user_input = st.text_area("Entrez votre question:", placeholder="E.g., quelle est la meilleure alimentation pour les poules pondeuses ?")
|
49 |
-
|
50 |
if st.button("Envoyer la question..."):
|
51 |
if user_input and choix_prod and choix_vulgarisation and choix_annee :
|
52 |
with st.spinner("Veuillez patienter quelques instants..."):
|
53 |
# Génération de la réponse
|
54 |
-
|
55 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
56 |
|
57 |
# st.markdown("**Bot :** \\t " + bot_response)
|
58 |
# Afficher un titre
|
59 |
st.subheader("Réponse :")
|
60 |
-
|
61 |
# Ajouter du texte Markdown avec un cadre
|
62 |
st.markdown(f"""
|
63 |
<div style="border: 2px solid #453103; padding: 15px; border-radius: 10px;">
|
64 |
{bot_response}
|
65 |
</div>
|
66 |
""", unsafe_allow_html=True)
|
67 |
-
|
68 |
# Afficher un titre
|
69 |
st.subheader("Sources :")
|
70 |
-
|
71 |
# Ajouter du texte Markdown avec un cadre
|
72 |
st.markdown("""
|
73 |
<div style="border: 2px solid #453103; padding: 15px; border-radius: 10px;">
|
74 |
-
Sources
|
75 |
</div>
|
76 |
""", unsafe_allow_html=True)
|
77 |
-
|
78 |
st.markdown("""
|
79 |
<div style="border: 2px solid #453103; padding: 15px; border-radius: 10px;">
|
80 |
Reviews
|
@@ -89,30 +276,30 @@ if st.button("Envoyer la question..."):
|
|
89 |
if not choix_prod or not choix_vulgarisation or not choix_annee:
|
90 |
st.warning("Veuillez compléter les paramètres dans le bandeau latéral de gauche!")
|
91 |
#endregion
|
92 |
-
|
93 |
# choix_prod, choix_vulgarisation, choix_annee
|
94 |
-
|
95 |
#region# Markdown
|
96 |
# Styles CSS pour améliorer l'apparence
|
97 |
st.markdown("""
|
98 |
<style>
|
99 |
-
|
100 |
/* Changer la couleur de fond de l'ensemble de l'application */
|
101 |
body {
|
102 |
background-color: #feffb3; /* Couleur de fond bleu clair (par exemple) */
|
103 |
color: #453103; /* Couleur de la police en gris foncé */
|
104 |
}
|
105 |
-
|
106 |
/* Personnaliser la couleur des titres */
|
107 |
h1, h2, h3, h4, h5, h6 {
|
108 |
color: #4CAF50; /* Changer la couleur des titres en vert */
|
109 |
}
|
110 |
-
|
111 |
/* Personnaliser la couleur des paragraphes */
|
112 |
p {
|
113 |
color: #555555; /* Couleur du texte dans les paragraphes */
|
114 |
}
|
115 |
-
|
116 |
.st-bx {
|
117 |
background-color: #f1f1f1;
|
118 |
border-radius: 10px;
|
@@ -127,8 +314,5 @@ st.markdown("""
|
|
127 |
}
|
128 |
</style>
|
129 |
""", unsafe_allow_html=True)
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
#endregion
|
|
|
1 |
#region# import libs
|
2 |
import streamlit as st
|
3 |
import os
|
4 |
+
from mistralai import Mistral
|
5 |
+
|
6 |
+
MISTRAL_API_KEY = os.getenv("api_mistral")
|
7 |
+
model = 'mistral-large-latest'
|
8 |
+
mistral_client = Mistral(api_key=MISTRAL_API_KEY)
|
9 |
+
MAX_TOKENS = 1500
|
10 |
+
|
11 |
+
def generate_prompts(score:str, type: str, annee_min: str, annee_max:str ):
|
12 |
+
"""
|
13 |
+
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
|
14 |
+
|
15 |
+
Args:
|
16 |
+
score (str): 1 = vulgarisé, 2 = intermédiaire, 3 = technique
|
17 |
+
type (str): 'ponte' ou 'chair'
|
18 |
+
annee_min (str): annee min de publication
|
19 |
+
annee_max (str): annee max de publication
|
20 |
+
|
21 |
+
"""
|
22 |
+
|
23 |
+
if type == "Ponte":
|
24 |
+
type_description = "volailles pondeuses"
|
25 |
+
elif type == "Chair":
|
26 |
+
type_description = "volailles de chair"
|
27 |
+
else:
|
28 |
+
raise ValueError("Type must be either 'Ponte' or 'Chair'")
|
29 |
+
|
30 |
+
if score == "1":
|
31 |
+
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
|
32 |
+
alimentaires pour optimiser la santé et la croissance des {type_description}.
|
33 |
+
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
|
34 |
+
lesquelles tu t'es basé avec son année (entre {annee_min} et {annee_max})."""
|
35 |
+
suffix_prompt = """Réponds en français et donne une réponse directe et claire, donne les références de façon bibliographique."""
|
36 |
+
elif score == "2":
|
37 |
+
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
|
38 |
+
alimentaires pour optimiser la santé et la croissance des {type_description}.
|
39 |
+
Réponds en fournissant des explications détaillées et précises, adaptées à la complexité de la question posée.
|
40 |
+
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."""
|
41 |
+
suffix_prompt = """Réponds en français et en suivant cette structure :
|
42 |
+
Donne une explication scientifique détaillée (mécanismes biologiques, données chiffrées).
|
43 |
+
Fini par les études ou références bibliographiques si disponibles."""
|
44 |
+
elif score == "3":
|
45 |
+
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
|
46 |
+
alimentaires pour optimiser la santé et la croissance des {type_description}.
|
47 |
+
Réponds en fournissant des explications détaillées et précises, adaptées à la complexité de la question posée.
|
48 |
+
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}).
|
49 |
+
Inclus des formules, des valeurs nutritionnelles précises et des références aux normes actuelles. Sois concis et clair."""
|
50 |
+
suffix_prompt = """Réponds en français et en suivant cette structure : Explication scientifique détaillée (mécanismes biologiques, données chiffrées).
|
51 |
+
Études ou références bibliographiques si disponibles."""
|
52 |
+
else:
|
53 |
+
raise ValueError("Score must be 1, 2, or 3")
|
54 |
+
|
55 |
+
return prefix_prompt, suffix_prompt
|
56 |
+
|
57 |
+
def send_prompt_to_mistral(type_reponse: str, user_prompt: str, temperature:int, n_comp:int, prefix_prompt: str, suffix_prompt:str, verbose=True):
|
58 |
+
"""
|
59 |
+
Envoie un prompt à Mistral pour obtenir une réponse
|
60 |
+
|
61 |
+
Args:
|
62 |
+
type_reponse (str): Le rôle de l'utilisateur, peut être 'technicien' ou 'chercheur'.
|
63 |
+
Si le rôle ne correspond pas à l'un de ces deux, une exception sera levée.
|
64 |
+
user_prompt (str): Le texte du prompt à envoyer à Mistral.
|
65 |
+
temperature (int): Lower values make the model more deterministic, focusing on likely responses for accuracy
|
66 |
+
verbose (bool): Print la reponse avant le return
|
67 |
+
prefixe (str): Prefixe du prompt
|
68 |
+
suffixe (str): Suffixe du prompt
|
69 |
+
|
70 |
+
Returns:
|
71 |
+
dict: La réponse du modèle Mistral à partir du prompt fourni.
|
72 |
+
|
73 |
+
Raises:
|
74 |
+
ValueError: Si le rôle spécifié n'est pas 'technicien' ou 'chercheur'.
|
75 |
+
"""
|
76 |
+
|
77 |
+
# Création du message à envoyer à Mistral
|
78 |
+
messages = [{"role": type_reponse, "content": suffix_prompt}]
|
79 |
+
|
80 |
+
# Envoi de la requête à Mistral et récupération de la réponse
|
81 |
+
chat_response = mistral_client.chat.complete(
|
82 |
+
model = model,
|
83 |
+
messages=[
|
84 |
+
{"role": "system", "content": prefix_prompt},
|
85 |
+
{"role": "user", "content": user_prompt + suffix_prompt},
|
86 |
+
],
|
87 |
+
|
88 |
+
#response_format={
|
89 |
+
# 'type': 'json_object'
|
90 |
+
#},
|
91 |
+
temperature=temperature,
|
92 |
+
n=n_comp,
|
93 |
+
max_tokens=MAX_TOKENS,
|
94 |
+
stream=False
|
95 |
+
#stop='\n'
|
96 |
+
)
|
97 |
+
|
98 |
+
if verbose:
|
99 |
+
print(chat_response)
|
100 |
+
|
101 |
+
return chat_response
|
102 |
+
|
103 |
+
def is_valid_mistral_response(response: dict) -> bool:
|
104 |
+
"""
|
105 |
+
Vérifie si la réponse de l'API Mistral est valide.
|
106 |
+
|
107 |
+
Critères de validité :
|
108 |
+
- La clé "choices" existe et contient au moins un élément.
|
109 |
+
- Le texte généré n'est pas vide et ne contient pas uniquement des tabulations ou espaces.
|
110 |
+
- La génération ne s'est pas arrêtée pour une raison autre que 'stop'.
|
111 |
+
- La réponse ne contient pas de texte générique inutile.
|
112 |
+
|
113 |
+
:param response: Dictionnaire représentant la réponse de l'API Mistral
|
114 |
+
:return: True si la réponse est valide, False sinon
|
115 |
+
"""
|
116 |
+
if not isinstance(response, dict):
|
117 |
+
return False
|
118 |
+
|
119 |
+
choices = response.get("choices")
|
120 |
+
if not choices or not isinstance(choices, list):
|
121 |
+
return False
|
122 |
+
|
123 |
+
first_choice = choices[0]
|
124 |
+
if not isinstance(first_choice, dict):
|
125 |
+
return False
|
126 |
+
|
127 |
+
text = first_choice.get("message", {}).get("content", "").strip()
|
128 |
+
if not text or text in ["\t\t", "", "N/A"]:
|
129 |
+
return False
|
130 |
+
|
131 |
+
finish_reason = first_choice.get("finish_reason", "")
|
132 |
+
if finish_reason in ["error", "tool_calls"]:
|
133 |
+
return False
|
134 |
+
|
135 |
+
# Vérification du contenu inutile
|
136 |
+
invalid_responses = [
|
137 |
+
"Je suis une IA", "Désolé, je ne peux pas répondre", "Je ne sais pas"
|
138 |
+
]
|
139 |
+
if any(invalid in text for invalid in invalid_responses):
|
140 |
+
return False
|
141 |
+
|
142 |
+
return True
|
143 |
+
|
144 |
+
def print_pretty_response(response: dict, verbose=True):
|
145 |
+
|
146 |
+
pretty = response.choices[0].message.content
|
147 |
+
|
148 |
+
if verbose:
|
149 |
+
print(pretty)
|
150 |
+
|
151 |
+
return pretty
|
152 |
+
|
153 |
+
def response_details(response, verbose=True):
|
154 |
+
"""
|
155 |
+
Envoie les details techniques du prompt
|
156 |
+
"""
|
157 |
+
|
158 |
+
details = {}
|
159 |
+
details["id"] = response.id
|
160 |
+
details["total_tokens"] = response.usage.total_tokens
|
161 |
+
details["prefix"] = response.choices[0].message.prefix
|
162 |
+
details["role"] = response.choices[0].message.role
|
163 |
+
|
164 |
+
if verbose:
|
165 |
+
print(details)
|
166 |
+
|
167 |
+
return details
|
168 |
+
|
169 |
+
def prompt_pipeline(user_prompt: str, niveau_detail: str, type_reponse: str, souche: str, annee_publication_min: str, annee_publication_max: str):
|
170 |
+
"""
|
171 |
+
Fonction visible de l'application pour appeler un prompt et obtenir sa reponse
|
172 |
+
|
173 |
+
Args:
|
174 |
+
prompt (str): Prompt utilisateur
|
175 |
+
niveau_detail (str): Niveau de detail de la requete : 1, 2, 3. Plus haut = plus d'infos
|
176 |
+
type_reponse (str): 'Ponte', 'Chair'
|
177 |
+
"""
|
178 |
+
|
179 |
+
prefix_prompt, suffix_prompt = generate_prompts(score=niveau_detail, type=type_reponse, annee_min=annee_publication_min, annee_max=annee_publication_max)
|
180 |
+
|
181 |
+
reponse_mistral = send_prompt_to_mistral(
|
182 |
+
type_reponse=type_reponse,
|
183 |
+
user_prompt=user_prompt,
|
184 |
+
temperature=0.10,
|
185 |
+
n_comp=1,
|
186 |
+
verbose=False,
|
187 |
+
prefix_prompt=prefix_prompt,
|
188 |
+
suffix_prompt=suffix_prompt
|
189 |
+
)
|
190 |
+
|
191 |
+
to_return = {}
|
192 |
+
to_return["reponse_propre"] = print_pretty_response(reponse_mistral, verbose=True)
|
193 |
+
to_return["details"] = response_details(reponse_mistral, verbose=False)
|
194 |
+
|
195 |
+
return to_return
|
196 |
+
|
197 |
#endregion
|
198 |
+
|
199 |
#region# Titre de l'application et mise en page
|
200 |
st.set_page_config(page_title="Expert nutrition volaille", page_icon="🤖")
|
201 |
st.title("Chatbot AI avec l'expert nutrition")
|
202 |
st.sidebar.image("img/avril_logo_rvb.jpg")
|
203 |
st.sidebar.header("")
|
204 |
#endregion
|
205 |
+
|
206 |
#region# Elements graphiques
|
207 |
+
|
208 |
#Choix production
|
209 |
choix_prod = st.sidebar.pills(
|
210 |
"Sur quelle espèce voulez-vous avoir des renseignements ?",
|
211 |
+
("Ponte", "Chair"),)
|
212 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
213 |
#Années de publication
|
214 |
choix_annee = st.sidebar.slider("Années de publication",
|
215 |
min_value=2015,
|
216 |
max_value=2025,
|
217 |
value=(2020,2025))
|
218 |
+
|
219 |
+
#Niveau vulgarisation
|
220 |
+
choix_vulgarisation = st.sidebar.pills(
|
221 |
+
"Quel niveau de vulgarisation souhaitez-vous ? (1- Très vulgarisé 2-Intermédiaire 3-Technique)",
|
222 |
+
("1", "2", "3"),)
|
223 |
+
|
|
|
|
|
|
|
|
|
|
|
224 |
#endregion
|
225 |
+
|
226 |
#region# Interface utilisateur
|
|
|
227 |
user_input = st.text_area("Entrez votre question:", placeholder="E.g., quelle est la meilleure alimentation pour les poules pondeuses ?")
|
228 |
+
|
229 |
if st.button("Envoyer la question..."):
|
230 |
if user_input and choix_prod and choix_vulgarisation and choix_annee :
|
231 |
with st.spinner("Veuillez patienter quelques instants..."):
|
232 |
# Génération de la réponse
|
233 |
+
response0 = prompt_pipeline(
|
234 |
+
user_prompt = user_input,
|
235 |
+
niveau_detail=choix_vulgarisation,
|
236 |
+
type_reponse=choix_prod,
|
237 |
+
souche=None,
|
238 |
+
annee_publication_max=max(choix_annee),
|
239 |
+
annee_publication_min=min(choix_annee)
|
240 |
+
)
|
241 |
+
print(response0["reponse_propre"])
|
242 |
+
response = response0["reponse_propre"]
|
243 |
+
bot_response = response
|
244 |
|
245 |
# st.markdown("**Bot :** \\t " + bot_response)
|
246 |
# Afficher un titre
|
247 |
st.subheader("Réponse :")
|
248 |
+
|
249 |
# Ajouter du texte Markdown avec un cadre
|
250 |
st.markdown(f"""
|
251 |
<div style="border: 2px solid #453103; padding: 15px; border-radius: 10px;">
|
252 |
{bot_response}
|
253 |
</div>
|
254 |
""", unsafe_allow_html=True)
|
255 |
+
|
256 |
# Afficher un titre
|
257 |
st.subheader("Sources :")
|
258 |
+
|
259 |
# Ajouter du texte Markdown avec un cadre
|
260 |
st.markdown("""
|
261 |
<div style="border: 2px solid #453103; padding: 15px; border-radius: 10px;">
|
|
|
262 |
</div>
|
263 |
""", unsafe_allow_html=True)
|
264 |
+
|
265 |
st.markdown("""
|
266 |
<div style="border: 2px solid #453103; padding: 15px; border-radius: 10px;">
|
267 |
Reviews
|
|
|
276 |
if not choix_prod or not choix_vulgarisation or not choix_annee:
|
277 |
st.warning("Veuillez compléter les paramètres dans le bandeau latéral de gauche!")
|
278 |
#endregion
|
279 |
+
|
280 |
# choix_prod, choix_vulgarisation, choix_annee
|
281 |
+
|
282 |
#region# Markdown
|
283 |
# Styles CSS pour améliorer l'apparence
|
284 |
st.markdown("""
|
285 |
<style>
|
286 |
+
|
287 |
/* Changer la couleur de fond de l'ensemble de l'application */
|
288 |
body {
|
289 |
background-color: #feffb3; /* Couleur de fond bleu clair (par exemple) */
|
290 |
color: #453103; /* Couleur de la police en gris foncé */
|
291 |
}
|
292 |
+
|
293 |
/* Personnaliser la couleur des titres */
|
294 |
h1, h2, h3, h4, h5, h6 {
|
295 |
color: #4CAF50; /* Changer la couleur des titres en vert */
|
296 |
}
|
297 |
+
|
298 |
/* Personnaliser la couleur des paragraphes */
|
299 |
p {
|
300 |
color: #555555; /* Couleur du texte dans les paragraphes */
|
301 |
}
|
302 |
+
|
303 |
.st-bx {
|
304 |
background-color: #f1f1f1;
|
305 |
border-radius: 10px;
|
|
|
314 |
}
|
315 |
</style>
|
316 |
""", unsafe_allow_html=True)
|
317 |
+
|
318 |
+
#endregion
|
|
|
|
|
|