ionosphere
commited on
Commit
·
64f9ede
1
Parent(s):
f676a1d
Update with common
Browse files- README.md +111 -7
- app.py +45 -69
- images/agir.png → assets/logo.png +0 -0
- config.yaml +54 -0
- model/ModelIntegrations.py +42 -0
- model/ModelStrategy.py +6 -0
- model/__init__.py +0 -0
- model/selector.py +42 -0
- pages/chatbot.py +66 -0
- pages/documents.py +35 -0
- pages/form.py +14 -0
- pages/persistent_documents.py +35 -0
- pages/prompt_system.py +12 -0
- rag.py +73 -40
- requirements.txt +18 -6
- util.py +7 -0
- vectore_store/ConnectorStrategy.py +14 -0
- vectore_store/PineconeConnector.py +100 -0
- vectore_store/VectoreStoreManager.py +15 -0
- vectore_store/__init__.py +0 -0
README.md
CHANGED
@@ -1,13 +1,117 @@
|
|
1 |
---
|
2 |
-
title:
|
3 |
-
emoji:
|
4 |
-
colorFrom:
|
5 |
-
colorTo:
|
6 |
sdk: streamlit
|
7 |
-
sdk_version: 1.
|
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 |
-
|
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 |
-
|
|
|
|
|
|
|
21 |
|
22 |
-
|
23 |
|
24 |
-
|
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
|
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 |
-
|
38 |
-
st.session_state["messages"].append((agent_text, False))
|
39 |
|
|
|
|
|
|
|
|
|
40 |
|
41 |
-
|
42 |
-
|
43 |
-
|
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 |
-
|
52 |
-
st.session_state["assistant"].ingest(file_path)
|
53 |
-
os.remove(file_path)
|
54 |
|
|
|
55 |
|
56 |
-
|
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.
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
st.
|
69 |
-
|
70 |
-
|
71 |
-
st.
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
79 |
)
|
80 |
|
81 |
-
|
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 |
-
|
|
|
|
|
|
|
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 |
-
|
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
|
24 |
-
|
25 |
retriever = None
|
26 |
chain = None
|
27 |
|
28 |
-
def __init__(self):
|
29 |
-
|
30 |
-
self.model = ChatMistralAI(model=llm_model)
|
31 |
-
self.
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
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 |
-
|
56 |
-
|
57 |
-
|
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 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
71 |
|
72 |
-
|
73 |
-
if not
|
74 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
75 |
|
76 |
-
return self.chain.invoke(
|
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 |
-
|
3 |
langchain-community
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
|
|
|
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
|