ionosphere commited on
Commit
f6033ca
·
1 Parent(s): 7f91951

Update space

Browse files
README.md CHANGED
@@ -1,13 +1,122 @@
1
  ---
2
  title: Viti
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: Viti
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
  license: mit
11
  ---
12
 
13
+ <!-- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference -->
14
+
15
+ # Application Template - README
16
+
17
+ ## Introduction
18
+
19
+ 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).
20
+
21
+ ### Structure de l'application
22
+
23
+ L'application est structurée en trois parties principales :
24
+
25
+ - **Documents**
26
+ - *Communs*
27
+ - *Vos Documents*
28
+ - **Configurations**
29
+ - *Prompt système*
30
+ - *Paramètres*
31
+ - **Dialogue**
32
+ - *Chatbot*
33
+
34
+ ### Fonctionnement des sections
35
+
36
+ #### 1. Documents
37
+
38
+ - **Documents Communs** :
39
+ 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.
40
+
41
+ - **Vos Documents** :
42
+ 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.
43
+
44
+ #### 2. Configurations
45
+
46
+ - **Prompt système** :
47
+ 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.
48
+
49
+ - **Paramètres** :
50
+ 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.
51
+
52
+ #### 3. Dialogue
53
+
54
+ - **Chatbot** :
55
+ 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.
56
+
57
+ ## Base de Données Vectorielle
58
+
59
+ 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.
60
+
61
+ ### Pinecone
62
+
63
+ 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.
64
+
65
+ Pour que l'intégration fonctionne correctement, vous devez renseigner les variables d'environnement suivantes :
66
+
67
+ - **PINECONE_API_KEY** : Clé d'API fournie par Pinecone pour l'accès à votre compte.
68
+ - **PINECONE_INDEX_NAME** : Le nom de l'index Pinecone dans lequel les vecteurs seront stockés.
69
+ - **PINECONE_NAMESPACE** : Un namespace unique propre à chaque application, utilisé pour organiser les vecteurs.
70
+
71
+ 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.
72
+
73
+
74
+ ## Configuration de l'application
75
+
76
+ 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'*.
77
+ Une fois, vos modifications effectuées, cliquez sur *'Commit changes to main'* pour les enregistrer et relancer automatiquement l'application.
78
+
79
+ #### Paramètres Dynamiques
80
+
81
+ Les paramètres peuvent être ajustés dans la section **variables**, en mettant la liste des variables souhaitées.
82
+ Pour chacune d'entre elles, un *label*, une *key* et optionnelement une valeur par défaut *value* sont nécessaires.
83
+ Pour être prise en compte, ces variables doivent être implémenté dans le prompt template via leur *'key'* sous la forme **{ma_variable}**
84
+
85
+ #### Prompt template
86
+
87
+ Vous pouvez directement spécifier votre prompt template dans la section **prompt_template** du fichier de configuration
88
+
89
+ #### Prompt system
90
+
91
+ Egalement, vous pouvez renseigner un prompt système par défaut, dans la section **prompt_system** du fichier de configuration
92
+
93
+ #### Prompts par Défaut
94
+
95
+ 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**.
96
+ La première tabulation correspond à une catégorie, permettant de faire des regroupements.
97
+ Chaque '-' représente ensuite un prompt qui sera proposé.
98
+
99
+ ## Déploiement
100
+
101
+ Pour déployer cette application sur Huggingface :
102
+
103
+ 1. Dupliquez l'espace Huggingface existant.
104
+ 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 :
105
+ - **APP_NAME** : Nom de votre application
106
+ - **PINECONE_NAMESPACE** : Espace de stockage permanent de votre application
107
+ 3. Ajustez votre configuration dans le fichier *config.yaml* (voir section **Configuration de l'application**)
108
+
109
+
110
+ ## Variables d'environnements
111
+ | Variable | Description
112
+ |----------|----------
113
+ **APP_NAME**|Nom de l'application
114
+ **ANTHROPIC_API_KEY**| Clé API Anthropic
115
+ **MISTRAL_API_KEY**|Clé API Mistral
116
+ **OPENAI_API_KEY**|Clé API OpenAI
117
+ **LLAMA_API_KEY**|Clé Llama API
118
+ **PINECONE_API_KEY**|Clé API Pinecone
119
+ **PINECONE_INDEX_NAME**|Index/BDD Pinecone
120
+ **PINECONE_NAMESPACE**|Espace de stockage propre à l'application
121
+
122
+
app.py CHANGED
@@ -1,89 +1,66 @@
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 Viti"
8
- subtitle = "Poser vos questions"
9
- description = "Demonstrateur Viti"
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 rapport de cout de production"
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
+ config = getYamlConfig()
 
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"] = config['variables']
28
+ st.session_state["prompt_system"] = config['prompt_system']
29
 
 
 
 
 
30
 
31
+ def main():
 
 
32
 
33
+ init_app()
34
 
35
+ st.set_page_config(page_title=GROUP_NAME)
 
 
 
36
 
 
37
  st.logo(LOGO)
38
+ st.title(GROUP_NAME)
39
+
40
+ saved_documents = st.Page("pages/persistent_documents.py", title="Communs", icon="🗃️")
41
+ documents = st.Page("pages/documents.py", title="Vos documents", icon="📂")
42
+ prompt_system = st.Page("pages/prompt_system.py", title="Prompt système", icon="🖊️", default=True)
43
+ form = st.Page("pages/form.py", title="Paramètres", icon="📋")
44
+ chatbot = st.Page("pages/chatbot.py", title="Chatbot", icon="🤖")
45
+
46
+ pg = st.navigation(
47
+ {
48
+ "Documents": [
49
+ saved_documents,
50
+ documents,
51
+ ],
52
+ "Configurations": [
53
+ prompt_system,
54
+ form,
55
+ ],
56
+ "Dialogue": [
57
+ chatbot
58
+ ],
59
+ }
60
  )
61
 
62
+ pg.run()
 
 
 
 
63
 
64
 
65
  if __name__ == "__main__":
66
+ main()
assets/logo.png ADDED
config.yaml ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ prompts:
2
+ general:
3
+ - "Quels sont les principaux défis auxquels les agriculteurs sont confrontés aujourd'hui ?"
4
+ - "Explique-moi les différences entre l'agriculture biologique et l'agriculture conventionnelle."
5
+
6
+ techniques:
7
+ - "Comment fonctionne la rotation des cultures et pourquoi est-elle importante ?"
8
+ - "Explique les avantages de l'irrigation goutte à goutte pour les cultures."
9
+
10
+ variables:
11
+ - label : Indicateur 1
12
+ key : param1
13
+ value :
14
+ - label : Indicateur 2
15
+ key : param2
16
+ value :
17
+
18
+ prompt_system: "
19
+ Tu es un système expert sur le pilotage des données d'une exploitation viticole sur la filière viticole du Bordelais-Libournais.
20
+ Tu réponds toujours par rapport aux données du contexte. Si tu ne connais pas la réponse, ne réponds pas.
21
+ Si cela est possible, réponds sous forme d'indicateurs et de tableaux. Tu répondras en Français.
22
+ "
23
+
24
+ prompt_template: "
25
+ {prompt_system}
26
+
27
+ Dans un premier temps, tu analysera l'indicateur suivant : {param1}
28
+ de l’exploitation en les comparant avec les indicateurs équivalents dans les données de référence.
29
+
30
+ Dans un second temps, tu analysera l'indicateur suivant : {param2}
31
+
32
+ Données de référence : {commonContext}
33
+
34
+ Document de l'utilisateur : {documentContext}
35
+
36
+ Voici l'historique des messages : {messages}
37
+ Les attentes de l'utilisateur sont : {query}
38
+ "
model/ModelIntegrations.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ from .ModelStrategy import ModelStrategy
4
+
5
+ from langchain_openai 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]), splitter[1])
42
+
pages/chatbot.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from langchain_core.messages import AIMessage, HumanMessage
3
+ from model import selector
4
+ from util import getYamlConfig
5
+ from st_copy_to_clipboard import st_copy_to_clipboard
6
+
7
+ def display_messages():
8
+
9
+ for i, message in enumerate(st.session_state.chat_history):
10
+ if isinstance(message, AIMessage):
11
+ with st.chat_message("AI"):
12
+ # Display the model from the kwargs
13
+ model = message.kwargs.get("model", "Unknown Model") # Get the model, default to "Unknown Model"
14
+ st.write(f"**Model :** {model}")
15
+ st.markdown(message.content)
16
+ st_copy_to_clipboard(message.content,key=f"message_{i}")
17
+
18
+ elif isinstance(message, HumanMessage):
19
+ with st.chat_message("Moi"):
20
+ st.write(message.content)
21
+
22
+
23
+ def launchQuery(query: str = None):
24
+
25
+ # Initialize the assistant's response
26
+ full_response = st.write_stream(
27
+ st.session_state["assistant"].ask(
28
+ query,
29
+ prompt_system=st.session_state.prompt_system,
30
+ messages=st.session_state["chat_history"] if "chat_history" in st.session_state else [],
31
+ variables=st.session_state["data_dict"]
32
+ ))
33
+
34
+ # Temporary placeholder AI message in chat history
35
+ st.session_state["chat_history"].append(AIMessage(content=full_response, kwargs={"model": st.session_state["assistant"].getReadableModel()}))
36
+ st.rerun()
37
+
38
+
39
+ def show_prompts():
40
+ yaml_data = getYamlConfig()["prompts"]
41
+
42
+ expander = st.expander("Prompts pré-définis")
43
+
44
+ for categroy in yaml_data:
45
+ expander.write(categroy.capitalize())
46
+
47
+ for item in yaml_data[categroy]:
48
+ if expander.button(item, key=f"button_{item}"):
49
+ launchQuery(item)
50
+
51
+
52
+ def page():
53
+ st.subheader("Posez vos questions")
54
+
55
+ if "assistant" not in st.session_state:
56
+ st.text("Assistant non initialisé")
57
+
58
+ if "chat_history" not in st.session_state:
59
+ st.session_state["chat_history"] = []
60
+
61
+ st.markdown("<style>iframe{height:50px;}</style>", unsafe_allow_html=True)
62
+
63
+ # Collpase for default prompts
64
+ show_prompts()
65
+
66
+ # Models selector
67
+ selector.ModelSelector()
68
+
69
+ # Displaying messages
70
+ display_messages()
71
+
72
+
73
+ user_query = st.chat_input("")
74
+ if user_query is not None and user_query != "":
75
+
76
+ st.session_state["chat_history"].append(HumanMessage(content=user_query))
77
+
78
+ # Stream and display response
79
+ launchQuery(user_query)
80
+
81
+
82
+ 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,47 +10,66 @@ 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] You are an assistant for question-answering tasks. Use only the following pieces of retrieved context
35
- to build an answer for the user. If you don't know the answer, just say that you don't know. Use three sentences
36
- maximum and keep the answer concise. [/INST] </s>
37
- [INST]
38
- Question: {question} Réponds en Français. Answer in French.
39
- Context: {context}
40
- Answer: [/INST]
41
- """
42
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
 
44
  def ingest(self, pdf_file_path: str):
45
  docs = PyPDFLoader(file_path=pdf_file_path).load()
46
 
47
-
48
  chunks = self.text_splitter.split_documents(docs)
49
  chunks = filter_complex_metadata(chunks)
50
 
51
- embeddings = MistralAIEmbeddings(model="mistral-embed", mistral_api_key=env_api_key)
52
-
53
- vector_store = FAISS.from_documents(chunks, embeddings)
54
- # vector_store = Chroma.from_documents(documents=chunks, embedding=embeddings)
55
- self.retriever = vector_store.as_retriever(
56
  search_type="similarity_score_threshold",
57
  search_kwargs={
58
  "k": 3,
@@ -60,18 +77,43 @@ class ChatPDF:
60
  },
61
  )
62
 
63
- self.chain = ({"context": self.retriever, "question": RunnablePassthrough()}
64
- | self.prompt
65
- | self.model
66
- | StrOutputParser())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
 
68
- def ask(self, query: str):
69
- if not self.chain:
70
- return "Ajouter un document PDF d'abord."
 
 
 
 
 
 
 
 
71
 
72
- return self.chain.invoke(query)
73
 
74
  def clear(self):
 
75
  self.vector_store = None
76
  self.retriever = None
77
  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
+ readableModelName = ""
27
 
28
+ def __init__(self, vectore_store=None):
29
+
30
+ # self.model = ChatMistralAI(model=llm_model)
31
+ self.embedding = MistralAIEmbeddings(model="mistral-embed", mistral_api_key=env_api_key)
32
+
33
+ self.text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=100, length_function=len)
34
+
35
+ base_template = getYamlConfig()['prompt_template']
36
+ self.prompt = PromptTemplate.from_template(base_template)
37
+
38
+ self.vector_store = vectore_store
39
+
40
+ def setModel(self, model, readableModelName = ""):
41
+ self.model = model
42
+ self.readableModelName = readableModelName
43
+
44
+ def getReadableModel(self):
45
+ return self.readableModelName
46
+
47
+ def ingestToDb(self, file_path: str, filename: str):
48
+
49
+ docs = PyPDFLoader(file_path=file_path).load()
50
+
51
+ # Extract all text from the document
52
+ text = ""
53
+ for page in docs:
54
+ text += page.page_content
55
+
56
+ # Split the text into chunks
57
+ chunks = self.text_splitter.split_text(text)
58
+
59
+ return self.vector_store.addDoc(filename=filename, text_chunks=chunks, embedding=self.embedding)
60
+
61
+ def getDbFiles(self):
62
+ return self.vector_store.getDocs()
63
 
64
  def ingest(self, pdf_file_path: str):
65
  docs = PyPDFLoader(file_path=pdf_file_path).load()
66
 
 
67
  chunks = self.text_splitter.split_documents(docs)
68
  chunks = filter_complex_metadata(chunks)
69
 
70
+ document_vector_store = FAISS.from_documents(chunks, self.embedding)
71
+
72
+ self.retriever = document_vector_store.as_retriever(
 
 
73
  search_type="similarity_score_threshold",
74
  search_kwargs={
75
  "k": 3,
 
77
  },
78
  )
79
 
80
+ def ask(self, query: str, prompt_system: str, messages: list, variables: list = None):
81
+ self.chain = self.prompt | self.model | StrOutputParser()
82
+
83
+ # Retrieve the context document
84
+ if self.retriever is None:
85
+ documentContext = ''
86
+ else:
87
+ documentContext = self.retriever.invoke(query)
88
+
89
+ # Retrieve the VectoreStore
90
+ contextCommon = self.vector_store.retriever(query, self.embedding)
91
+
92
+ # Dictionnaire de base avec les variables principales
93
+ chain_input = {
94
+ "query": query,
95
+ "documentContext": documentContext,
96
+ "commonContext": contextCommon,
97
+ "prompt_system": prompt_system,
98
+ "messages": messages
99
+ }
100
 
101
+ # Suppression des valeurs nulles (facultatif)
102
+ chain_input = {k: v for k, v in chain_input.items() if v is not None}
103
+
104
+ # Si des variables sous forme de liste sont fournies
105
+ if variables:
106
+ # Convertir la liste en dictionnaire avec 'key' comme clé et 'value' comme valeur
107
+ extra_vars = {item['key']: item['value'] for item in variables if 'key' in item and 'value' in item}
108
+
109
+ # Fusionner avec chain_input
110
+ chain_input.update(extra_vars)
111
+
112
 
113
+ return self.chain.stream(chain_input)
114
 
115
  def clear(self):
116
+ self.document_vector_store = None
117
  self.vector_store = None
118
  self.retriever = None
119
  self.chain = None
requirements.txt CHANGED
@@ -1,9 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
1
  langchain
2
- langchain_mistralai
3
  langchain-community
4
- pysqlite3-binary
5
- streamlit==1.38.0
6
- streamlit-chat
7
- fastembed
8
- faiss-gpu
9
- pypdf
 
 
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-experimental
16
+ langchain-pinecone
17
+ langchain_mistralai
18
+ langchain_anthropic
19
+ llamaapi
20
+ pyyaml
21
+ st_copy_to_clipboard
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