Spaces:
Sleeping
Sleeping
#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="VolAI", page_icon="🤖") | |
st.title("VolAI, le chatbot expert nutrition de volailles") | |
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"),) | |
#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"),) | |
#Années de publication | |
choix_annee = st.sidebar.slider("Années de publication", | |
min_value=2015, | |
max_value=2025, | |
value=(2020,2025)) | |
#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) | |
#encadré Reviews | |
# 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 | |
# st.markdown(""" | |
# <style> | |
# 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 | |