ionosphere commited on
Commit
64f9ede
·
1 Parent(s): f676a1d

Update with common

Browse files
README.md CHANGED
@@ -1,13 +1,117 @@
1
  ---
2
- title: GC
3
- emoji: 💻
4
- colorFrom: green
5
- colorTo: red
6
  sdk: streamlit
7
- sdk_version: 1.38.0
8
  app_file: app.py
9
  pinned: false
10
- license: mit
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Common
3
+ emoji: 🏢
4
+ colorFrom: purple
5
+ colorTo: indigo
6
  sdk: streamlit
7
+ sdk_version: 1.39.0
8
  app_file: app.py
9
  pinned: false
 
10
  ---
11
 
12
+ <!-- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference -->
13
+
14
+ # Application Template - README
15
+
16
+ ## Introduction
17
+
18
+ Cette application sert de base/template pour en déployer d'autres sur le même modèle via Huggingface. Elle est facilement duplicable en dupliquant l'espace. Elle est décomposée en plusieurs sections pour offrir une gestion complète des documents et des dialogues avec une Intelligence Artificielle (IA).
19
+
20
+ ### Structure de l'application
21
+
22
+ L'application est structurée en trois parties principales :
23
+
24
+ - **Documents**
25
+ - *Communs*
26
+ - *Vos Documents*
27
+ - **Configurations**
28
+ - *Prompt système*
29
+ - *Paramètres*
30
+ - **Dialogue**
31
+ - *Chatbot*
32
+
33
+ ### Fonctionnement des sections
34
+
35
+ #### 1. Documents
36
+
37
+ - **Documents Communs** :
38
+ Cette section permet de déposer des documents accessibles à tous les utilisateurs de l'application. Ces documents sont vectorisés et stockés dans une base de données vectorielle (voir section dédiée). Ils seront explorés lors des interactions avec l'IA.
39
+
40
+ - **Vos Documents** :
41
+ Chaque utilisateur peut uploader ses propres documents, qui seront pris en compte pendant sa session. Ces documents sont temporaires et ne sont accessibles que durant la session active de l'utilisateur.
42
+
43
+ #### 2. Configurations
44
+
45
+ - **Prompt système** :
46
+ Cette section permet de configurer le *prompt système* utilisé lors des conversations avec l'IA. Ce prompt influence le comportement de l'IA pendant le dialogue.
47
+
48
+ - **Paramètres** :
49
+ Ici, vous pouvez ajuster les paramètres dynamiques de l'application, tels que les préférences utilisateurs ou les options spécifiques à votre usage.
50
+
51
+ #### 3. Dialogue
52
+
53
+ - **Chatbot** :
54
+ Une interface de discussion avec l'IA, où il est possible de choisir le modèle d'IA avec lequel interagir. Vous pouvez aussi commencer la conversation à partir d'un *prompt* pré-défini.
55
+
56
+ ## Base de Données Vectorielle
57
+
58
+ La base de données vectorielle permet de stocker de manière permanente les différents vecteurs de documents, afin de faciliter leur recherche et leur utilisation dans les conversations avec l'IA.
59
+
60
+ ### Pinecone
61
+
62
+ L'application utilise **Pinecone**, une solution cloud pour la gestion de bases de données vectorielles. Pinecone simplifie la gestion des vecteurs et permet une intégration efficace avec l'application.
63
+
64
+ Pour que l'intégration fonctionne correctement, vous devez renseigner les variables d'environnement suivantes :
65
+
66
+ - **PINECONE_API_KEY** : Clé d'API fournie par Pinecone pour l'accès à votre compte.
67
+ - **PINECONE_INDEX_NAME** : Le nom de l'index Pinecone dans lequel les vecteurs seront stockés.
68
+ - **PINECONE_NAMESPACE** : Un namespace unique propre à chaque application, utilisé pour organiser les vecteurs.
69
+
70
+ Ces informations sont disponibles directement dans votre compte Pinecone, et doivent être correctement configurées pour permettre le fonctionnement de la base de données vectorielle.
71
+
72
+
73
+ ## Configuration de l'application
74
+
75
+ Vous pouvez configurer votre application plus finement en la personalisant en fonction de vos besoins. Ces configurations se font dans le fichier *config.yaml* accessible dans la partie *Files* de votre espace Huggingface. La modification se fait ensuite via le bouton *'edit'*.
76
+ Une fois, vos modifications effectuées, cliquez sur *'Commit changes to main'* pour les enregistrer et relancer automatiquement l'application.
77
+
78
+ #### Paramètres Dynamiques
79
+
80
+ Les paramètres peuvent être ajustés dans la section **variables**, en mettant la liste des variables souhaitées.
81
+ Pour chacune d'entre elles, un *label*, une *key* et optionnelement une valeur par défaut *value* sont nécessaires.
82
+ Pour être prise en compte, ces variables doivent être implémenté dans le prompt template via leur *'key'* sous la forme **{ma_variable}**
83
+
84
+ #### Prompt template
85
+
86
+ Vous pouvez directement spécifier votre prompt template dans la section **prompt_template** du fichier de configuration
87
+
88
+ #### Prompts par Défaut
89
+
90
+ Des *prompts* par défaut peuvent être définis pour démarrer les conversations avec l'IA. Ces *prompts* sont personnalisables dans la section **prompts**.
91
+ La première tabulation correspond à une catégorie, permettant de faire des regroupements.
92
+ Chaque '-' représente ensuite un prompt qui sera proposé.
93
+
94
+ ## Déploiement
95
+
96
+ Pour déployer cette application sur Huggingface :
97
+
98
+ 1. Dupliquez l'espace Huggingface existant.
99
+ 2. Renseignez les variables d'environnements. Il vous sera demandé de rentrer toutes les variables d'environnements. Vous les variables qui seront propres à votre application :
100
+ - **APP_NAME** : Nom de votre application
101
+ - **PINECONE_NAMESPACE** : Espace de stockage permanent de votre application
102
+ 3. Ajustez votre configuration dans le fichier *config.yaml* (voir section **Configuration de l'application**)
103
+
104
+
105
+ ## Variables d'environnements
106
+ | Variable | Description
107
+ |----------|----------
108
+ **APP_NAME**|Nom de l'application
109
+ **ANTHROPIC_API_KEY**| Clé API Anthropic
110
+ **MISTRAL_API_KEY**|Clé API Mistral
111
+ **OPENAI_API_KEY**|Clé API OpenAI
112
+ **LLAMA_API_KEY**|Clé Llama API
113
+ **PINECONE_API_KEY**|Clé API Pinecone
114
+ **PINECONE_INDEX_NAME**|Index/BDD Pinecone
115
+ **PINECONE_NAMESPACE**|Espace de stockage propre à l'application
116
+
117
+
app.py CHANGED
@@ -1,89 +1,65 @@
1
- import os
2
- import tempfile
3
  import streamlit as st
4
- from streamlit_chat import message
5
- from rag import ChatPDF
6
-
7
- title = "Simulateur IA Grandes cultures"
8
- subtitle = "Poser vos questions"
9
- description = "Demonstrateur GC"
10
- LOGO = "images/agir.png"
11
- form_help ="Vous pouvez compléter les informations ci-dessous pour personnaliser votre expérience"
12
- placeholder = (
13
- "Vous pouvez me posez une question sur vos attentes, appuyer sur Entrée pour valider"
14
- )
15
- placeholder_doc = (
16
- "Vous pouvez charger un grand livre ou des écritures comptables au format PDF"
17
- )
18
- placeholder_url = "Récupérer les données de ce lien."
19
 
20
- # st.title(title)
 
 
 
21
 
22
- st.set_page_config(page_title=title)
23
 
24
- def display_messages():
25
- st.subheader(subtitle)
26
- for i, (msg, is_user) in enumerate(st.session_state["messages"]):
27
- message(msg, is_user=is_user, key=str(i))
28
- st.session_state["thinking_spinner"] = st.empty()
29
 
 
 
30
 
31
- def process_input():
32
- if st.session_state["user_input"] and len(st.session_state["user_input"].strip()) > 0:
33
- user_text = st.session_state["user_input"].strip()
34
- with st.session_state["thinking_spinner"], st.spinner(f"Je réfléchis"):
35
- agent_text = st.session_state["assistant"].ask(user_text)
36
 
37
- st.session_state["messages"].append((user_text, True))
38
- st.session_state["messages"].append((agent_text, False))
39
 
 
 
 
 
40
 
41
- def read_and_save_file():
42
- st.session_state["assistant"].clear()
43
- st.session_state["messages"] = []
44
- st.session_state["user_input"] = ""
45
 
46
- for file in st.session_state["file_uploader"]:
47
- with tempfile.NamedTemporaryFile(delete=False) as tf:
48
- tf.write(file.getbuffer())
49
- file_path = tf.name
50
 
51
- with st.session_state["ingestion_spinner"], st.spinner(f"Chargement {file.name}"):
52
- st.session_state["assistant"].ingest(file_path)
53
- os.remove(file_path)
54
 
 
55
 
56
- def page():
57
- if len(st.session_state) == 0:
58
- st.session_state["messages"] = []
59
- st.session_state["assistant"] = ChatPDF()
60
 
61
-
62
  st.logo(LOGO)
63
- st.sidebar.markdown(form_help)
64
- info1 = st.sidebar.text_input("Info 1", type="default")
65
- info1 = st.sidebar.text_input("Info 2", type="default")
66
- info1 = st.sidebar.text_input("Info 3", type="default")
67
-
68
- st.header(title)
69
-
70
- st.subheader("Charger un ou plusieurs documents")
71
- st.caption(placeholder_doc)
72
- st.file_uploader(
73
- "Charger un document",
74
- type=["pdf"],
75
- key="file_uploader",
76
- on_change=read_and_save_file,
77
- label_visibility="collapsed",
78
- accept_multiple_files=True,
 
 
 
 
 
 
79
  )
80
 
81
- st.session_state["ingestion_spinner"] = st.empty()
82
-
83
- display_messages()
84
- st.caption(placeholder)
85
- st.text_input("Message", key="user_input", on_change=process_input)
86
 
87
 
88
  if __name__ == "__main__":
89
- page()
 
 
 
1
  import streamlit as st
2
+ import os
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
 
4
+ from dotenv import load_dotenv
5
+ from rag import Rag
6
+ from vectore_store.PineconeConnector import PineconeConnector
7
+ from vectore_store.VectoreStoreManager import VectoreStoreManager
8
 
9
+ from util import getYamlConfig
10
 
11
+ load_dotenv()
 
 
 
 
12
 
13
+ GROUP_NAME = os.environ.get("APP_NAME")
14
+ LOGO = "assets/logo.png"
15
 
16
+ def init_app():
 
 
 
 
17
 
18
+ data_dict = getYamlConfig()['variables']
 
19
 
20
+ if len(st.session_state) == 0:
21
+ # Define Vectore store strategy
22
+ pinecone_connector = PineconeConnector()
23
+ vs_manager = VectoreStoreManager(pinecone_connector)
24
 
25
+ st.session_state["messages"] = []
26
+ st.session_state["assistant"] = Rag(vectore_store=vs_manager)
27
+ st.session_state["data_dict"] = data_dict
 
28
 
 
 
 
 
29
 
30
+ def main():
 
 
31
 
32
+ init_app()
33
 
34
+ st.set_page_config(page_title=GROUP_NAME)
 
 
 
35
 
 
36
  st.logo(LOGO)
37
+ st.title(GROUP_NAME)
38
+
39
+ saved_documents = st.Page("pages/persistent_documents.py", title="Communs", icon="🗃️")
40
+ documents = st.Page("pages/documents.py", title="Vos documents", icon="📂")
41
+ prompt_system = st.Page("pages/prompt_system.py", title="Prompt système", icon="🖊️", default=True)
42
+ form = st.Page("pages/form.py", title="Paramètres", icon="📋")
43
+ chatbot = st.Page("pages/chatbot.py", title="Chatbot", icon="🤖")
44
+
45
+ pg = st.navigation(
46
+ {
47
+ "Documents": [
48
+ saved_documents,
49
+ documents,
50
+ ],
51
+ "Configurations": [
52
+ prompt_system,
53
+ form,
54
+ ],
55
+ "Dialogue": [
56
+ chatbot
57
+ ],
58
+ }
59
  )
60
 
61
+ pg.run()
 
 
 
 
62
 
63
 
64
  if __name__ == "__main__":
65
+ main()
images/agir.png → assets/logo.png RENAMED
File without changes
config.yaml ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ prompts:
2
+ cultures:
3
+ - "Quelle est le cout de production de 15 Ha de blé tendre dans la région Picardie ?"
4
+ - "Quelle est le cout de production du colza dans la région Picardie ?"
5
+ materiels:
6
+ - "Quel est le cout total avec carburant en €/h d'un tracteur 4 roues motrices de 200 ch ?"
7
+ - "Quel est le cout total avec carburant en €/h d'une moissonneuse batteuse avec une coupe de 7,5 m ?"
8
+
9
+ variables:
10
+ - label : Mon paramètre 1
11
+ key : param1
12
+ value :
13
+ - label : Mon paramètre 2
14
+ key : param2
15
+ value :
16
+ - label : Mon paramètre 3
17
+ key : param3
18
+ value :
19
+
20
+ prompt_template: "
21
+ Paramètre 1 : {param1}
22
+ Paramètre 2 : {param2}
23
+ Paramètre 3 : {param3}
24
+
25
+ Documents partagées : {commonContext}
26
+
27
+ Document utilisateur : {documentContext}
28
+
29
+ Tu es un expert en gestion agricole des grandes cultures dans les régions Centre, Ile de france, Hauts de france et Grand est.
30
+ Tu sais calculer une marge brute par hectare et un coût de revient par hectare à partir de documents statistiques agricoles français.
31
+ Si tu ne connais pas la réponse, ne réponds pas. Tu répondras toujours en Français.
32
+
33
+ Le coût de production ou coût de revient (CR) est calculé en additionnant toutes les charges opérationnelles (engrais, produits phytosanitaires, semences) (CO) par hectare ainsi que les charges de structure (main d'œuvre, foncier, matériel, bâtiment) (CS) par hectare selon la formule : CR = CO + CS
34
+
35
+ Le coût de production en grande culture se situe en moyenne entre 500 € / hectare et 2000 € / hectare.
36
+
37
+ La marge brute (MB) est calculée en additionnant tous les produits (Ventes & indemnité, Aides couplées) (PR) issue de la culture et en soustrayant les charges opérationnelles (CO) selon la formule suivante : MB = PR - CO
38
+
39
+ La marge brute (MB) en grande culture se situe en moyenne entre 300 € / hectare et 1500 € / hectare.
40
+
41
+ La marge nette (MN) est calculée en additionnant tous les produits (Ventes & indemnité, Aides couplées) (PR) issue de la culture et en soustrayant le coût de revient (CR) selon la formule suivante : MN = PR - CR
42
+
43
+ La marge nette (MN) en grande culture se situe en moyenne entre 100 € / hectare et 800 € / hectare.
44
+
45
+ A partir des documents du contexte, tu vas produire des tableaux et des indicateurs de coût selon les cultures mentionnées par l'utilisateur.
46
+ Pour le coût de production (CR), il est impératif de détailler l'ensemble des calculs poste par poste pour les charges opérationnelles (engrais, produits phytosanitaires, semences) (CO) et pour les charges de structure (main d'oeuvre, foncier, matériel, batiment) (CS).
47
+
48
+ La présentation sera effectuée dès que possible en format tableau. Si une information est demandée pour une superficie précise, les tableaux devront fournir, sur la première colonne, une information à l'hectare et sur la seconde, une information pour la superficie indiquée par l'utilisateur. Si aucune superficie n'est précisée par le client, l'information sera donnée par hectare.
49
+
50
+ {prompt_system}
51
+
52
+ Voici l'historique des messages : {messages}
53
+ La demande de l'utilisateur est : {query}
54
+ "
model/ModelIntegrations.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ from .ModelStrategy import ModelStrategy
4
+
5
+ from langchain_community.chat_models import ChatOpenAI
6
+ from langchain_mistralai.chat_models import ChatMistralAI
7
+ from langchain_anthropic import ChatAnthropic
8
+
9
+ from llamaapi import LlamaAPI
10
+ from langchain_experimental.llms import ChatLlamaAPI
11
+
12
+ class MistralModel(ModelStrategy):
13
+ def get_model(self, model_name):
14
+ return ChatMistralAI(model=model_name)
15
+
16
+
17
+ class OpenAIModel(ModelStrategy):
18
+ def get_model(self, model_name):
19
+ return ChatOpenAI(model=model_name)
20
+
21
+
22
+ class AnthropicModel(ModelStrategy):
23
+ def get_model(self, model_name):
24
+ return ChatAnthropic(model=model_name)
25
+
26
+
27
+ class LlamaAPIModel(ModelStrategy):
28
+ def get_model(self, model_name):
29
+ llama = LlamaAPI(os.environ.get("LLAMA_API_KEY"))
30
+ return ChatLlamaAPI(client=llama, model=model_name)
31
+
32
+ class ModelManager():
33
+ def __init__(self):
34
+ self.models = {
35
+ "mistral": MistralModel(),
36
+ "openai": OpenAIModel(),
37
+ "anthropic": AnthropicModel(),
38
+ "llama": LlamaAPIModel()
39
+ }
40
+
41
+ def get_model(self, provider, model_name):
42
+ return self.models[provider].get_model(model_name)
model/ModelStrategy.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ from abc import ABC, abstractmethod
2
+
3
+ class ModelStrategy(ABC):
4
+ @abstractmethod
5
+ def get_model(self, model_name):
6
+ pass
model/__init__.py ADDED
File without changes
model/selector.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from .ModelIntegrations import ModelManager
3
+
4
+ def ModelSelector():
5
+ # Dictionnaire des modèles par fournisseur
6
+ model_providers = {
7
+ "Mistral": {
8
+ "mistral-large-latest": "mistral.mistral-large-latest",
9
+ "open-mixtral-8x7b": "mistral.open-mixtral-8x7b",
10
+ },
11
+ "OpenAI": {
12
+ "gpt-4o": "openai.gpt-4o",
13
+ },
14
+ "Anthropic": {
15
+ "claude-3-5-sonnet-20240620": "anthropic.claude-3-5-sonnet-20240620",
16
+ "claude-3-opus-20240229": "anthropic.claude-3-opus-20240229",
17
+ "claude-3-sonnet-20240229": "anthropic.claude-3-sonnet-20240229",
18
+ },
19
+ # "llama": {
20
+ # "llama3.2-11b-vision": "llama.llama3.2-11b-vision",
21
+ # "llama3.2-1b": "llama.llama3.2-1b",
22
+ # "llama3.2-3b": "llama.llama3.2-3b"
23
+ # }
24
+ }
25
+
26
+ # Créer une liste avec les noms de modèle, groupés par fournisseur (fournisseur - modèle)
27
+ model_options = []
28
+ model_mapping = {}
29
+
30
+ for provider, models in model_providers.items():
31
+ for model_name, model_instance in models.items():
32
+ option_name = f"{provider} - {model_name}"
33
+ model_options.append(option_name)
34
+ model_mapping[option_name] = model_instance
35
+
36
+ # Sélection d'un modèle via un seul sélecteur
37
+ selected_model_option = st.selectbox("Choisissez votre modèle", options=model_options)
38
+
39
+ if(st.session_state["assistant"]):
40
+ splitter = model_mapping[selected_model_option].split(".")
41
+ st.session_state["assistant"].setModel(ModelManager().get_model(splitter[0], splitter[1]))
42
+
pages/chatbot.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import yaml
3
+ import os
4
+ from streamlit_chat import message
5
+ from model import selector
6
+
7
+ def display_messages():
8
+ for i, (msg, is_user) in enumerate(st.session_state["messages"]):
9
+ message(msg, is_user=is_user, key=str(i))
10
+ st.session_state["thinking_spinner"] = st.empty()
11
+
12
+
13
+ # Charger les données YAML
14
+ def load_yaml(file_path):
15
+ with open(file_path, 'r', encoding='utf-8') as file:
16
+ data = yaml.safe_load(file)
17
+ return data
18
+
19
+
20
+ def process_input():
21
+ if "user_input" in st.session_state and st.session_state["user_input"] and len(st.session_state["user_input"].strip()) > 0:
22
+ user_text = st.session_state["user_input"].strip()
23
+
24
+ prompt_sys = st.session_state.prompt_system if 'prompt_system' in st.session_state and st.session_state.prompt_system != '' else ""
25
+
26
+ with st.session_state["thinking_spinner"], st.spinner(f"Je réfléchis"):
27
+ agent_text = st.session_state["assistant"].ask(user_text, prompt_sys, st.session_state["messages"] if "messages" in st.session_state else [], variables=st.session_state["data_dict"])
28
+
29
+ st.session_state["messages"].append((user_text, True))
30
+ st.session_state["messages"].append((agent_text, False))
31
+
32
+ @st.dialog("Choisissez votre question prédéfinie")
33
+ def show_prompts():
34
+ file_path = os.path.join(os.path.dirname(__file__), '..', 'config.yaml')
35
+ yaml_data = load_yaml(file_path)
36
+
37
+ # Boucle à travers les éléments YAML
38
+ for categroy in yaml_data['prompts']: # Exemple avec la catégorie conversation
39
+ st.write(categroy.capitalize())
40
+
41
+ for item in yaml_data['prompts'][categroy]:
42
+ if st.button(item, key=f"button_{item}"):
43
+ st.session_state["user_input"] = item
44
+ st.rerun()
45
+
46
+ def page():
47
+ st.subheader("Posez vos questions")
48
+
49
+ if "user_input" in st.session_state:
50
+ process_input()
51
+
52
+ if "assistant" not in st.session_state:
53
+ st.text("Assistant non initialisé")
54
+
55
+ # Bouton pour ouvrir la modale
56
+ if st.button("Questions pré-définies"):
57
+ show_prompts()
58
+
59
+
60
+ selector.ModelSelector()
61
+
62
+ display_messages()
63
+
64
+ st.text_input("Message", key="user_input", on_change=process_input)
65
+
66
+ page()
pages/documents.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import tempfile
3
+ import streamlit as st
4
+
5
+ def read_and_save_file():
6
+ st.session_state["messages"] = []
7
+ st.session_state["user_input"] = ""
8
+
9
+ for file in st.session_state["file_uploader"]:
10
+ with tempfile.NamedTemporaryFile(delete=False) as tf:
11
+ tf.write(file.getbuffer())
12
+ file_path = tf.name
13
+
14
+ with st.session_state["ingestion_spinner"], st.spinner(f"Chargement {file.name}"):
15
+ st.session_state["assistant"].ingest(file_path)
16
+ os.remove(file_path)
17
+
18
+
19
+
20
+ def page():
21
+ st.subheader("Charger vos documents")
22
+
23
+ # File uploader
24
+ uploaded_file = st.file_uploader(
25
+ "Télécharger un ou plusieurs documents",
26
+ type=["pdf"],
27
+ key="file_uploader",
28
+ accept_multiple_files=True,
29
+ on_change=read_and_save_file,
30
+ )
31
+
32
+
33
+ st.session_state["ingestion_spinner"] = st.empty()
34
+
35
+ page()
pages/form.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+
3
+ def page():
4
+ st.subheader("Définissez vos paramètres")
5
+
6
+ # Boucle pour créer des inputs basés sur data_dict (qui est une liste ici)
7
+ for param in st.session_state.data_dict:
8
+ # Utilisation de la clé 'label' et 'value' pour afficher et récupérer les valeurs
9
+ value = st.text_input(label=param['label'], value=param['value'] if param['value'] else "")
10
+
11
+ # Mettre à jour la valeur dans le dictionnaire après la saisie utilisateur
12
+ param['value'] = value
13
+
14
+ page()
pages/persistent_documents.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import tempfile
3
+ import streamlit as st
4
+
5
+ def uploadToDb():
6
+
7
+ for file in st.session_state["file_uploader_commun"]:
8
+ with tempfile.NamedTemporaryFile(delete=False) as tf:
9
+ tf.write(file.getbuffer())
10
+ file_path = tf.name
11
+
12
+ with st.session_state["ingestion_spinner"], st.spinner(f"Chargement {file.name}"):
13
+ st.session_state["assistant"].ingestToDb(file_path, filename=file.name)
14
+ os.remove(file_path)
15
+
16
+ def page():
17
+ st.subheader("Montez des documents communs")
18
+
19
+ st.file_uploader(
20
+ "Télécharger un documents",
21
+ type=["pdf"],
22
+ key="file_uploader_commun",
23
+ accept_multiple_files=True,
24
+ on_change=uploadToDb,
25
+ )
26
+
27
+ st.session_state["ingestion_spinner"] = st.empty()
28
+
29
+ st.divider()
30
+ st.write("Documents dans la base de données")
31
+
32
+ for doc in st.session_state["assistant"].vector_store.getDocs():
33
+ st.write(" - "+doc)
34
+
35
+ page()
pages/prompt_system.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+
3
+ def page():
4
+
5
+ st.subheader("Renseignez votre prompt système")
6
+
7
+ prompt = st.text_area("Prompt system", st.session_state.prompt_system if 'prompt_system' in st.session_state else "")
8
+
9
+ # Session State also supports attribute based syntax
10
+ st.session_state['prompt_system'] = prompt
11
+
12
+ page()
rag.py CHANGED
@@ -1,7 +1,5 @@
1
  import os
2
- # __import__('pysqlite3')
3
- # import sys
4
- # sys.modules['sqlite3'] = sys.modules.pop('pysqlite3')
5
  from dotenv import load_dotenv
6
  from langchain_community.vectorstores import FAISS
7
  from langchain_mistralai.chat_models import ChatMistralAI
@@ -12,51 +10,61 @@ from langchain.text_splitter import RecursiveCharacterTextSplitter
12
  from langchain.schema.runnable import RunnablePassthrough
13
  from langchain.prompts import PromptTemplate
14
  from langchain_community.vectorstores.utils import filter_complex_metadata
15
- #add new import
16
  from langchain_community.document_loaders.csv_loader import CSVLoader
17
 
 
 
 
18
  # load .env in local dev
19
  load_dotenv()
20
  env_api_key = os.environ.get("MISTRAL_API_KEY")
21
- llm_model = "open-mixtral-8x7b"
22
 
23
- class ChatPDF:
24
- vector_store = None
25
  retriever = None
26
  chain = None
27
 
28
- def __init__(self):
29
- # https://python.langchain.com/docs/integrations/chat/mistralai/
30
- self.model = ChatMistralAI(model=llm_model)
31
- self.text_splitter = RecursiveCharacterTextSplitter(chunk_size=1024, chunk_overlap=100)
32
- self.prompt = PromptTemplate.from_template(
33
- """
34
- <s> [INST] Vous échangez en français et avec précision.
35
- Vous êtes un assistant comptable spécialisé dans la comptabilité agricole en grandes cultures.
36
- Vous devez analyser les documents ci-dessous et calculer les couts de productions.
37
- Les documents fournis représente la comptabilité de l'exploitation agricole.
38
- Vous devez répondre sous forme de tableaux et de textes.
39
- Vous devez répondre de façon synthétique et argumentée.
40
- [/INST] </s>
41
- [INST]
42
- Question: {question}
43
- Context: {context}
44
- Answer: [/INST]
45
- """
46
- )
 
 
 
 
 
 
 
 
 
 
 
 
47
 
48
  def ingest(self, pdf_file_path: str):
49
  docs = PyPDFLoader(file_path=pdf_file_path).load()
50
 
51
-
52
  chunks = self.text_splitter.split_documents(docs)
53
  chunks = filter_complex_metadata(chunks)
54
 
55
- embeddings = MistralAIEmbeddings(model="mistral-embed", mistral_api_key=env_api_key)
56
-
57
- vector_store = FAISS.from_documents(chunks, embeddings)
58
- # vector_store = Chroma.from_documents(documents=chunks, embedding=embeddings)
59
- self.retriever = vector_store.as_retriever(
60
  search_type="similarity_score_threshold",
61
  search_kwargs={
62
  "k": 3,
@@ -64,18 +72,43 @@ class ChatPDF:
64
  },
65
  )
66
 
67
- self.chain = ({"context": self.retriever, "question": RunnablePassthrough()}
68
- | self.prompt
69
- | self.model
70
- | StrOutputParser())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
 
72
- def ask(self, query: str):
73
- if not self.chain:
74
- return "Ajouter un document PDF d'abord."
 
 
 
 
 
 
 
 
75
 
76
- return self.chain.invoke(query)
77
 
78
  def clear(self):
 
79
  self.vector_store = None
80
  self.retriever = None
81
  self.chain = None
 
1
  import os
2
+
 
 
3
  from dotenv import load_dotenv
4
  from langchain_community.vectorstores import FAISS
5
  from langchain_mistralai.chat_models import ChatMistralAI
 
10
  from langchain.schema.runnable import RunnablePassthrough
11
  from langchain.prompts import PromptTemplate
12
  from langchain_community.vectorstores.utils import filter_complex_metadata
 
13
  from langchain_community.document_loaders.csv_loader import CSVLoader
14
 
15
+ from util import getYamlConfig
16
+
17
+
18
  # load .env in local dev
19
  load_dotenv()
20
  env_api_key = os.environ.get("MISTRAL_API_KEY")
 
21
 
22
+ class Rag:
23
+ document_vector_store = None
24
  retriever = None
25
  chain = None
26
 
27
+ def __init__(self, vectore_store=None):
28
+
29
+ # self.model = ChatMistralAI(model=llm_model)
30
+ self.embedding = MistralAIEmbeddings(model="mistral-embed", mistral_api_key=env_api_key)
31
+
32
+ self.text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=100, length_function=len)
33
+
34
+ base_template = getYamlConfig()['prompt_template']
35
+ self.prompt = PromptTemplate.from_template(base_template)
36
+
37
+ self.vector_store = vectore_store
38
+
39
+ def setModel(self, model):
40
+ self.model = model
41
+
42
+ def ingestToDb(self, file_path: str, filename: str):
43
+
44
+ docs = PyPDFLoader(file_path=file_path).load()
45
+
46
+ # Extract all text from the document
47
+ text = ""
48
+ for page in docs:
49
+ text += page.page_content
50
+
51
+ # Split the text into chunks
52
+ chunks = self.text_splitter.split_text(text)
53
+
54
+ return self.vector_store.addDoc(filename=filename, text_chunks=chunks, embedding=self.embedding)
55
+
56
+ def getDbFiles(self):
57
+ return self.vector_store.getDocs()
58
 
59
  def ingest(self, pdf_file_path: str):
60
  docs = PyPDFLoader(file_path=pdf_file_path).load()
61
 
 
62
  chunks = self.text_splitter.split_documents(docs)
63
  chunks = filter_complex_metadata(chunks)
64
 
65
+ document_vector_store = FAISS.from_documents(chunks, self.embedding)
66
+
67
+ self.retriever = document_vector_store.as_retriever(
 
 
68
  search_type="similarity_score_threshold",
69
  search_kwargs={
70
  "k": 3,
 
72
  },
73
  )
74
 
75
+ def ask(self, query: str, prompt_system: str, messages: list, variables: list = None):
76
+ self.chain = self.prompt | self.model | StrOutputParser()
77
+
78
+ # Retrieve the context document
79
+ if self.retriever is None:
80
+ documentContext = ''
81
+ else:
82
+ documentContext = self.retriever.invoke(query)
83
+
84
+ # Retrieve the VectoreStore
85
+ contextCommon = self.vector_store.retriever(query, self.embedding)
86
+
87
+ # Dictionnaire de base avec les variables principales
88
+ chain_input = {
89
+ "query": query,
90
+ "documentContext": documentContext,
91
+ "commonContext": contextCommon,
92
+ "prompt_system": prompt_system,
93
+ "messages": messages
94
+ }
95
 
96
+ # Suppression des valeurs nulles (facultatif)
97
+ chain_input = {k: v for k, v in chain_input.items() if v is not None}
98
+
99
+ # Si des variables sous forme de liste sont fournies
100
+ if variables:
101
+ # Convertir la liste en dictionnaire avec 'key' comme clé et 'value' comme valeur
102
+ extra_vars = {item['key']: item['value'] for item in variables if 'key' in item and 'value' in item}
103
+
104
+ # Fusionner avec chain_input
105
+ chain_input.update(extra_vars)
106
+
107
 
108
+ return self.chain.invoke(chain_input)
109
 
110
  def clear(self):
111
+ self.document_vector_store = None
112
  self.vector_store = None
113
  self.retriever = None
114
  self.chain = None
requirements.txt CHANGED
@@ -1,8 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
1
  langchain
2
- langchain_mistralai
3
  langchain-community
4
- streamlit==1.38.0
5
- streamlit-chat
6
- pypdf
7
- fastembed
8
- faiss-gpu
 
 
1
+ streamlit==1.37.0
2
+ streamlit_chat
3
+ python-dotenv
4
+ pymupdf
5
+ pypdf
6
+ python-multipart
7
+ pydantic
8
+ pinecone-notebooks
9
+ pinecone-client[grpc]
10
+ async-timeout
11
+ typing-extensions
12
  langchain
13
+ langchain-openai
14
  langchain-community
15
+ langchain-pinecone
16
+ langchain_mistralai
17
+ langchain_anthropic
18
+ langchain-experimental
19
+ llamaapi
20
+ pyyaml
util.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ import os
2
+ import yaml
3
+
4
+ def getYamlConfig():
5
+ file_path = os.path.join(os.path.dirname(__file__), 'config.yaml')
6
+ with open(file_path, 'r', encoding='utf-8') as file:
7
+ return yaml.safe_load(file)
vectore_store/ConnectorStrategy.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from abc import ABC, abstractmethod
2
+
3
+ class ConnectorStrategy(ABC):
4
+ @abstractmethod
5
+ def getDocs(self):
6
+ pass
7
+
8
+ @abstractmethod
9
+ def addDoc(self, filename, text_chunks, embedding):
10
+ pass
11
+
12
+ @abstractmethod
13
+ def retriever(self, query, embedding):
14
+ pass
vectore_store/PineconeConnector.py ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+
4
+ from .ConnectorStrategy import ConnectorStrategy
5
+
6
+ from pinecone import Pinecone, ServerlessSpec
7
+ from langchain_openai import OpenAIEmbeddings
8
+ from langchain_pinecone import PineconeVectorStore
9
+ from langchain_core.documents import Document
10
+
11
+ import unicodedata
12
+ import time
13
+
14
+ class PineconeConnector(ConnectorStrategy):
15
+ def __init__(self):
16
+
17
+ load_dotenv()
18
+
19
+ pinecone_api_key = os.environ.get("PINECONE_API_KEY")
20
+
21
+ self.index_name = os.environ.get("PINECONE_INDEX_NAME")
22
+ self.namespace = os.environ.get("PINECONE_NAMESPACE")
23
+
24
+
25
+ pc = Pinecone(api_key=pinecone_api_key)
26
+
27
+ existing_indexes = [index_info["name"] for index_info in pc.list_indexes()]
28
+
29
+ if self.index_name not in existing_indexes:
30
+ pc.create_index(
31
+ name=self.index_name,
32
+ dimension=3072,
33
+ metric="cosine",
34
+ spec=ServerlessSpec(cloud="aws", region="us-east-1"),
35
+ )
36
+ while not pc.describe_index(self.index_name).status["ready"]:
37
+ time.sleep(1)
38
+
39
+ self.index = pc.Index(self.index_name)
40
+
41
+
42
+ def getDocs(self):
43
+ # Simulate getting docs from Pinecone
44
+
45
+ docs_names = []
46
+ for ids in self.index.list(namespace=self.namespace):
47
+ for id in ids:
48
+ name_doc = "_".join(id.split("_")[:-1])
49
+ if name_doc not in docs_names:
50
+ docs_names.append(name_doc)
51
+
52
+ return docs_names
53
+
54
+
55
+ def addDoc(self, filename, text_chunks, embedding):
56
+ try:
57
+ vector_store = PineconeVectorStore(index=self.index, embedding=embedding,namespace=self.namespace)
58
+
59
+ file_name = filename.split(".")[0].replace(" ","_").replace("-","_").replace(".","_").replace("/","_").replace("\\","_").strip()
60
+
61
+ documents = []
62
+ uuids = []
63
+
64
+ for i, chunk in enumerate(text_chunks):
65
+ clean_filename = remove_non_standard_ascii(file_name)
66
+ uuid = f"{clean_filename}_{i}"
67
+
68
+ document = Document(
69
+ page_content=chunk,
70
+ metadata={ "filename":filename, "chunk_id":uuid },
71
+ )
72
+
73
+ uuids.append(uuid)
74
+ documents.append(document)
75
+
76
+
77
+ vector_store.add_documents(documents=documents, ids=uuids)
78
+
79
+ return {"filename_id":clean_filename}
80
+
81
+ except Exception as e:
82
+ print(e)
83
+ return False
84
+
85
+ def retriever(self, query, embedding):
86
+
87
+ vector_store = PineconeVectorStore(index=self.index, embedding=embedding,namespace=self.namespace)
88
+
89
+ retriever = vector_store.as_retriever(
90
+ search_type="similarity_score_threshold",
91
+ search_kwargs={"k": 3, "score_threshold": 0.6},
92
+ )
93
+
94
+ return retriever.invoke(query)
95
+
96
+
97
+ def remove_non_standard_ascii(input_string: str) -> str:
98
+ normalized_string = unicodedata.normalize('NFKD', input_string)
99
+ return ''.join(char for char in normalized_string if 'a' <= char <= 'z' or 'A' <= char <= 'Z' or char.isdigit() or char in ' .,!?')
100
+
vectore_store/VectoreStoreManager.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from vectore_store import ConnectorStrategy
2
+
3
+
4
+ class VectoreStoreManager:
5
+ def __init__(self, strategy: ConnectorStrategy):
6
+ self.strategy = strategy
7
+
8
+ def getDocs(self):
9
+ return self.strategy.getDocs()
10
+
11
+ def addDoc(self, filename, text_chunks, embedding):
12
+ self.strategy.addDoc(filename, text_chunks, embedding)
13
+
14
+ def retriever(self, query, embedding):
15
+ return self.strategy.retriever(query, embedding)
vectore_store/__init__.py ADDED
File without changes