clairedhx commited on
Commit
759fea8
·
verified ·
1 Parent(s): a218ccd

Upload folder using huggingface_hub

Browse files
1934d09a-c249-4fe5-aa72-584f5845fb98/data_level0.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9effed2377b339647f4386d54aef11d675e1b0cdae38a31f6be2532e4dafac06
3
+ size 1676000
1934d09a-c249-4fe5-aa72-584f5845fb98/header.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e87a1dc8bcae6f2c4bea6d5dd5005454d4dace8637dae29bff3c037ea771411e
3
+ size 100
1934d09a-c249-4fe5-aa72-584f5845fb98/length.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8e9222c5dc2e55306044f19e34222731eaa4ea0851d32260729f57bd568a4aab
3
+ size 4000
1934d09a-c249-4fe5-aa72-584f5845fb98/link_lists.bin ADDED
File without changes
7495d102-62f8-4242-9219-87d4caee7813/data_level0.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a3c5ba4473d921018e74451b89232bb9154a25c602c5a6a6c211841416a634c5
3
+ size 1676000
7495d102-62f8-4242-9219-87d4caee7813/header.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e87a1dc8bcae6f2c4bea6d5dd5005454d4dace8637dae29bff3c037ea771411e
3
+ size 100
7495d102-62f8-4242-9219-87d4caee7813/length.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4934f5eefc80b2267d185fd33d3d1b33b53cd8c8247b25c40d4e45582a94ed93
3
+ size 4000
7495d102-62f8-4242-9219-87d4caee7813/link_lists.bin ADDED
File without changes
README.md CHANGED
@@ -1,12 +1,6 @@
1
  ---
2
- title: PHE Outil IA
3
- emoji: 🚀
4
- colorFrom: red
5
- colorTo: blue
6
  sdk: gradio
7
- sdk_version: 4.42.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
 
1
  ---
2
+ title: PHE_Outil_IA
3
+ app_file: rag_chat_v3.py
 
 
4
  sdk: gradio
5
+ sdk_version: 4.37.1
 
 
6
  ---
 
 
__pycache__/functions_rag_chat_v3.cpython-312.pyc ADDED
Binary file (24 kB). View file
 
chroma.sqlite3 ADDED
Binary file (655 kB). View file
 
functions_rag_chat_v3.py ADDED
@@ -0,0 +1,456 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ import gradio as gr
4
+ from gradio.themes.base import Base
5
+
6
+ import glob
7
+
8
+ from langchain.text_splitter import RecursiveCharacterTextSplitter
9
+ from langchain_community.document_loaders import DirectoryLoader
10
+ from langchain_community.document_loaders import PyPDFLoader
11
+ from langchain_community.document_loaders import TextLoader
12
+ from langchain_community.vectorstores import Chroma
13
+ from langchain_community.embeddings import GPT4AllEmbeddings
14
+ from langchain_community.chat_models import ChatOllama
15
+ from langchain_core.output_parsers import StrOutputParser
16
+ from langchain_core.prompts import ChatPromptTemplate
17
+
18
+ import getpass
19
+
20
+ import json
21
+
22
+
23
+ # Import necessary modules
24
+ from langchain.retrievers import ContextualCompressionRetriever, EnsembleRetriever
25
+ from langchain.retrievers.document_compressors import CrossEncoderReranker
26
+ from langchain_community.cross_encoders import HuggingFaceCrossEncoder
27
+ from langchain_core.output_parsers import StrOutputParser
28
+ from langchain_core.runnables import RunnableLambda, RunnableParallel, RunnablePassthrough
29
+
30
+ from typing import Sequence, Any, Dict
31
+ from langchain.schema import Document
32
+
33
+
34
+
35
+ def chunks_from_pdf(pdf_directory):
36
+ """
37
+ Chunks all pdfs from a directory
38
+ :param pdf_directory: directory of pdfs
39
+ :return: list of chunks
40
+ """
41
+ # fetching all pdfs from the directory and storing them as strings in a list
42
+ docs = []
43
+ for file in glob.glob(pdf_directory + "/*.pdf"):
44
+ loader = PyPDFLoader(file)
45
+ doc = loader.load()
46
+ docs.extend(doc)
47
+
48
+ # split texts into chunks with overlap
49
+ splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(chunk_size=500, chunk_overlap=100)
50
+ splits = splitter.split_documents(docs)
51
+
52
+ print(f"Loaded {len(docs)} documents")
53
+ return splits
54
+
55
+
56
+ def chunks_from_text(text_directory):
57
+ """
58
+ Chunks all text files from a directory
59
+ :param text_directory: directory of text files
60
+ :return: list of chunks
61
+ """
62
+ # fetch all txt files from the firectory and store them in a list
63
+ loader = DirectoryLoader(text_directory, loader_cls=TextLoader) # , glob="**/*.txt")
64
+ docs = loader.load()
65
+
66
+ from langchain_community.document_transformers import LongContextReorder
67
+
68
+ reordering = LongContextReorder()
69
+ reordered_docs = reordering.transform_documents(docs)
70
+
71
+
72
+ # split texts into chunks with overlap
73
+ splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(chunk_size=500, chunk_overlap=100)
74
+ splits = splitter.split_documents(reordered_docs)
75
+
76
+ return splits
77
+
78
+
79
+ def chunking(data_directory, type):
80
+ """
81
+ Automatically calls the correct chunking function, either for pdfs or for txt files
82
+ :param data_directory: directory of data, either ../pdf or ../text
83
+ :return: result from the corresponding chunking function
84
+ """
85
+ if type == "pdf":
86
+ return chunks_from_pdf(data_directory)
87
+ else:
88
+ return chunks_from_text(data_directory)
89
+
90
+
91
+ def create_vector_store(db_directory, chunks, embedding):
92
+ """
93
+ Creates a chromaDB vector embedding store for all chunks of the data
94
+ :param db_directory: directory to persistently store the resulting vector store
95
+ :param chunks: list of chunks of data
96
+ :param embedding: embedding function
97
+ :return: retriever on vector store
98
+ """
99
+ print("Creating vector store (this may take a while)")
100
+ print(f"Creating vector store with {len(chunks)} chunks")
101
+
102
+ # create vector store and index
103
+ vectorstore = Chroma.from_documents(documents=chunks, collection_name="chromemwah", embedding=embedding,
104
+ persist_directory=db_directory)
105
+
106
+ return vectorstore #.as_retriever(search_type="similarity")
107
+
108
+
109
+ def fetch_vector_store(db_directory, embedding):
110
+ """
111
+ Fetches a chromaDB vector embedding store of the data
112
+ :param db_directory: directory where vector store is persistently stored
113
+ :param embedding: embedding function
114
+ :return: retriever on vector store
115
+ """
116
+ print("Fetching vector store")
117
+ print(f"Fetching vector store from {db_directory}")
118
+ vectorstore = Chroma(collection_name="chromemwah", embedding_function=embedding, persist_directory=db_directory)
119
+
120
+ return vectorstore #.as_retriever(search_type="similarity")
121
+
122
+
123
+ def retrieve(retrieving, question):
124
+ """
125
+ Retrieve relevant documents from vector store based on query/question
126
+ :param retrieving: retriever
127
+ :param question: user query
128
+ :return: relevant documents
129
+ """
130
+ print("Retrieving")
131
+ documents = retrieving.get_relevant_documents(question)
132
+ print(f"Retrieved {len(documents)} documents for the question: {question}")
133
+ return documents
134
+
135
+
136
+ def context_formatting(documents):
137
+ """
138
+ Formats retrieved documents to be used as context for the LLM
139
+ :param documents: retrieved documents
140
+ :return: formatted documents
141
+ """
142
+ content = ""
143
+ for index, document in enumerate(documents):
144
+ content = content + "[Extrait " + str(index + 1) + "]=" +"Type du document : " + document.metadata["type"] +". Produit concerné : " + document.metadata["nom_med"] +". Texte extrait : " + document.page_content.replace("\n", " ") + "\n\n"
145
+ return content
146
+
147
+
148
+ def source_formatting_v0(documents):
149
+ """
150
+ Formats retrieved documents to be used as sources for the user
151
+ :param documents: retrieved documents
152
+ :return: formatted documents
153
+ """
154
+ sources = ""
155
+ for i, document in enumerate(documents):
156
+ sources= sources + "Avis numéro " + str(i + 1)+ " (id de l'avis : " + str(document[0].metadata["avis_id"]) + ")\n\n" + "Date de l'avis : " + document[0].metadata["date_avis"] + "\n" + "Medicament : " + document[0].metadata["nom_med"] + "\n" + "Exploitant : " + document[0].metadata["exploitant"] + "\n" + "Indication : " + document[0].metadata["indication"] + "\n\n" #+ "Lien avis : " + document[0].metadata["lien_avis"] +"\n\n"
157
+ for index, doc in enumerate(document):
158
+ sources = sources + "[extrait " + str(index + 1) + "] " + " [" + doc.metadata["type"] + "] " + doc.page_content.replace("\n", " ").replace("+", " ") + "\n"
159
+ sources = sources + "---------------------------------------------------------------------------------- \n"
160
+ return sources.strip()
161
+
162
+ def source_formatting(documents, scores, docs_ejected, scores_ejected):
163
+ """
164
+ Formats retrieved documents to be used as sources for the user
165
+ :param documents: retrieved documents
166
+ :param scores: scores associated with the documents
167
+ :param docs_ejected: documents that were not selected due to low scores
168
+ :param scores_ejected: scores of the rejected documents
169
+ :return: formatted documents
170
+ """
171
+ sources = ""
172
+ for i, (document_group, score_group) in enumerate(zip(documents, scores)):
173
+ sources += (f"Avis numéro {i + 1} (id de l'avis : {document_group[0].metadata['avis_id']}) \n" # Deux espaces pour forcer le saut de ligne
174
+ f"Date de l'avis : {document_group[0].metadata['date_avis']} \n"
175
+ f"Médicament : {document_group[0].metadata['nom_med']} \n"
176
+ f"Exploitant : {document_group[0].metadata['exploitant']} \n"
177
+ f"Lien de l'avis : [{document_group[0].metadata['lien_avis']}]"
178
+ f"({document_group[0].metadata['lien_avis']}) \n" # Lien cliquable en Markdown
179
+ f"Indication : {document_group[0].metadata['indication']} \n\n")
180
+
181
+ for j, (doc, score) in enumerate(zip(document_group, score_group)):
182
+ sources += (f"[Extrait {j + 1}] (Score: {score}) [{doc.metadata['type']}] \n"
183
+ f"```\n{doc.page_content.replace('\n', ' ').replace('+', ' ')}\n``` \n")
184
+ sources += "---------------------------------------------------------------------------------- \n"
185
+
186
+ # Adding ejected chunks
187
+ sources += "\n**Chunks non récupérés lors du scoring de pertinence :**\n"
188
+ for doc, score in zip(docs_ejected, scores_ejected):
189
+ sources += (f"\n(Score: {score}) \n"
190
+ f"<div style='font-size:0.9em;'>\n{doc.page_content.replace('\n', ' ').replace('+', ' ')}\n</div> \n")
191
+
192
+ return sources.strip()
193
+
194
+
195
+
196
+ def generate_sous_questions(question):
197
+
198
+ use_llm = "mistral"
199
+
200
+ # Charger les données d'exemple
201
+ with open('/home/onyxia/phe/scripts/modeles/text_to_SQL/entrainement_initial.json', 'r') as f:
202
+ exemples = json.load(f)
203
+
204
+ # Construire le prompt avec les exemples
205
+ rag_prompt_template = """
206
+ Tu es un assistant pour générer des sous-questions à partir d'une question donnée. On veut séparer la question_donnée en deux parties :
207
+ 1. La partie permettant de filtrer les documents sur leurs metadatas (question_to_sql).
208
+ 2. La partie permettant de récupérer les éléments à analyser et à récupérer dans les textes (question_to_llm).
209
+
210
+ Pour la question_to_sql, il faut générer une question permettant de sélectionner les id des documents concernés par la question_posée, en spécifiant les matadatas à séléctionner cités dans question_posée. Les filtres appliqués ne doivent faire référence qu'à la maladie, l'aire thérapeutique, l'indication, la date, l'asmr, le smr ou le type.
211
+ Sachant que l'asmr ne peut prendre comme valeur que 'I','II','III','IV' ou'V'. Le smr ne peut prendre comme valeur que 'important','modéré','faible' ou 'insuffisant'. Le type ne peut prendre comme valeur que 'avis_ct','transcription_ct','avis_ceesp','transcription_ceesp','questionnaire' ou 'efficience'.
212
+ Dans question_to_sql, il ne doit pas faire mention des informations à chercher dans le texte, mais seulement des metadatas (maladie, l'aire thérapeutique, l'indication, la date, l'asmr, le smr, le type).
213
+
214
+ Pour la question_to_llm, il faut récupérer le fond de la question et ce qui doit être récupéré dans le texte des documents sélectionnés, elle ne doit pas mentionner les informations relatives à la requête SQL, présentent dans question_sql.
215
+
216
+ Tu ne devras génerer des réponses qu'en minuscules, il ne doit y avoir aucune majuscule.
217
+
218
+ En te basant sur ces exemples d'entraînement, tu devras générer en output 'question_to_sql' et 'question_to_llm' en prenant en input 'question_posée'. Tu génereras l'output en suivant ce format : 'question_to_llm # question_to_sql'.
219
+
220
+ Exemples d'entraînement :
221
+
222
+ {examples}
223
+
224
+ Maintenant, à ton tour de générer question_to_llm et question_to_sql en suivant la mise en forme 'question_to_llm # question_to_sql' à partir de la question_posée suivante :
225
+
226
+ question_posée donnée en input : {question}
227
+ question_to_llm et question_to_sql générées en output en lettres minuscules, aucune majuscule :
228
+ """
229
+
230
+ examples = ""
231
+ for exemple in exemples[0:6]:
232
+ examples += (
233
+ f"question_posée donnée en input : {exemple['question_posee']}\n"
234
+ f"question_to_llm et question_to_sql générées en output: {exemple['question_to_llm']} # {exemple['question_to_sql']}\n\n"
235
+ )
236
+
237
+ rag_prompt = ChatPromptTemplate.from_template(rag_prompt_template)
238
+
239
+ # define LLM to be used and the temperature (creativity/randomness) of the model
240
+ print("llm")
241
+ llm = ChatOllama(model=use_llm, temperature=0.8, num_predict=500)
242
+ print("chain")
243
+ # define a LangChain chain
244
+ chain = rag_prompt | llm | StrOutputParser()
245
+ print("invoke")
246
+
247
+ # invoke chain with retrieved documents and the question (user query)
248
+ output = chain.invoke({ "question": question, "examples": examples }).split("#")
249
+
250
+ return output
251
+
252
+ def generate(question, documents, use_llm):
253
+ """
254
+ LLM generates a response based on the question (user query), added context (retrieved documents), and a prompt
255
+ :param question: user query
256
+ :param documents: retrieved documents, formatted
257
+ :param use_llm: which llm to use
258
+ :return: LLM generated response
259
+ """
260
+
261
+ # adapted from https://smith.langchain.com/hub/rlm/rag-prompt
262
+
263
+ rag_prompt = ChatPromptTemplate.from_template("Tu es un assistant devant répondre à la question d'un client qui souhaîte récupérer et analyser des informations sur des documents de la Comission de la Transparence la Haute Autorité de Santé française,"
264
+ " qui est une réunion d'experts médicaux ayant en charge d'évaluer les nouveaux médicaments avant qu'ils ne soient mis sur le marché. Utilise les "
265
+ "extraits des documents récupérés en contexte pour répondre à la question. Si"
266
+ "l'extrait n'est pas utile pour répondre à la question, dis qu'il n'est pas utile. Garde la"
267
+ "réponse conçise, véridique et informative. Réponds toujours en français."
268
+ "Réponds plusieurs fois à la question, à chaque fois en considérant un seul extrait, puis agrège les différentes réponses en une conclusion. Base toi sur l'exemple de mise en forme de réponse pour rédiger la tienne."
269
+ "Chaque extrait donné en contexte est donné avec son titre, son type et son lien. Prends en considération le morceau de texte en considérant son type :"
270
+ "s'il s'agit d'un type avis_ct il s'agit d'une synthèse publiée après la Comission avec les avis finaux des experts sur le médicament. "
271
+ "S'il s'agit d'un type transcription_ct il s'agit d'une retranscription des dialogues ayants eut lieux pendant la comission, condidère alors qu'il s'agit de paroles à interpréter, tu dois donc répondre à la question en disant 'les experts disent --- donc nous pouvons en déduire que ---'."
272
+ "Les sources (extraits) sont indiquées dans le contexte par : "
273
+ "[doc<doc_number>]. TITRE DOCUMENT : --- \n\n TYPE DU DOCUMENT : --- \n\n LIEN DOCUMENT : --- \n\n EXTRAIT : ---- \n\n\n"
274
+ "Exemple de mise en forme de réponse : \n"
275
+ "Dans l' [extrait1], [réponse à la question en ne considérant que l'extrait 1].\n"
276
+ "Dans l' [extrait2], [réponse à la question en ne considérant que l'extrait 2].\n"
277
+ "Dans l' [extrait3], [réponse à la question en ne considérant que l'extrait 3].\n"
278
+ "Ainsi, en considérant les différents extrait, on en déduit que [synthèse des trois réponses précédentes].\n\n\n\n"
279
+ "Question posée par le client : {question} \n"
280
+ "Contexte associé : {context} \n"
281
+ "Réponse générée en français et en suivant la mise en forme de l'exemple :")
282
+ # define LLM to be used and the temperature (creativity/randomness) of the model
283
+
284
+ llm = ChatOllama(model=use_llm, temperature=0.5, num_predict=3000)
285
+
286
+ # define a LangChain chain
287
+ chain = rag_prompt | llm | StrOutputParser()
288
+
289
+
290
+ # invoke chain with retrieved documents and the question (user query)
291
+ output = chain.invoke({"context": documents, "question": question})
292
+
293
+ return output
294
+
295
+
296
+ def generate_agregated(reponses, use_llm):
297
+ """
298
+ LLM generates a response based on the question (user query), added context (retrieved documents), and a prompt
299
+ :param liste_rep: liste des réponses individuelles
300
+ :param use_llm: which llm to use
301
+ :return: LLM generated response
302
+ """
303
+
304
+ # adapted from https://smith.langchain.com/hub/rlm/rag-prompt
305
+
306
+ rag_prompt = ChatPromptTemplate.from_template("Tu es un assistant qui doit synthétiser plusieurs réponses à une question donnée par un client qui souhaîte récupérer et analyser des informations sur des documents de la Comission de la Transparence la Haute Autorité de Santé française,"
307
+ " qui est une réunion d'experts médicaux ayant en charge d'évaluer les nouveaux médicaments avant qu'ils ne soient mis sur le marché sur la base d'un dossier d'étude qui leur est présenté."
308
+ "Pour répondre tu as en contexte plusieurs réponses à la question qui t'ai posée, qui ont été générées par un llm en se basant à chaque fois sur 3 extraits d'un document donné."
309
+ "Ton rôle est de récupérer ces réponses, de vérifier si elles répondent bien à la question posée et d'agréger les informations issues de ces réponses en une petite synthèse pour répondre à la question."
310
+ "Les réponses sont indiquées dans le contexte par : "
311
+ "Document [titre_doc] : 1. [réponse en se basant sur le premier extrait du document [titre_doc]] 2. [réponse en se basant sur le deuxième extrait du document [titre_doc]] 3. [réponse en se basant sur le troisième extrait du document [titre_doc]] "
312
+ "Pour chaque document, vérifie que chaque réponse réponds bien à la question donnée et récupère les informations qui y répondent bien et font sens par rapport à la question."
313
+ " Réponds toujours en français."
314
+ "Question posée par le client : {question} \n"
315
+ "Contexte (réponses générées préalablement) : {context} \n"
316
+ "Synthèse générée en français pour répondre précisemment à la question et à rien d'autre :")
317
+ # define LLM to be used and the temperature (creativity/randomness) of the model
318
+
319
+ llm = ChatOllama(model=use_llm, temperature=0.5, num_predict=3000)
320
+
321
+ # define a LangChain chain
322
+ chain = rag_prompt | llm | StrOutputParser()
323
+
324
+
325
+ # invoke chain with retrieved documents and the question (user query)
326
+ output = chain.invoke({"context": reponses, "question": question})
327
+
328
+ return output
329
+
330
+ def generate_2(question, documents, use_llm):
331
+ """
332
+ LLM generates a response based on the question (user query), added context (retrieved documents), and a prompt
333
+ :param question: user query
334
+ :param documents: retrieved documents, formatted
335
+ :param use_llm: which llm to use
336
+ :return: LLM generated response
337
+ """
338
+
339
+ # adapted from https://smith.langchain.com/hub/rlm/rag-prompt
340
+
341
+ rag_prompt = ChatPromptTemplate.from_template("Tu es un assistant devant répondre à la question d'un client qui souhaîte récupérer et analyser des informations sur des documents de la Comission de la Transparence la Haute Autorité de Santé française,"
342
+ " qui est une réunion d'experts médicaux ayant en charge d'évaluer les nouveaux médicaments avant qu'ils ne soient mis sur le marché. Utilise les "
343
+ "extraits de document récupéré en contexte pour répondre à la question."
344
+ "Réponds à la question, et seulement à la question. N'ajoute aucune information qui ne répond pas à la question. Sois concis et clair."
345
+ "Ne cite pas les extraits de document."
346
+ "Si les etraits donnés en contexte ne permettent pas de répondre à la question, renvoie 'Pas d'élément de réponse dans ces extraits'."
347
+ "Le ou les extraits de document sont indiqués dans le contexte par : "
348
+ "[Extrait num_doc]= Type du document : [type]. Produit concerné : [nom du médicament]. Texte extrait : [extrait du document] "
349
+ "Question : {question} \n"
350
+ "Contexte : {context} \n"
351
+ "Réponse concise à la question '{question}', générée en français:")
352
+ # define LLM to be used and the temperature (creativity/randomness) of the model
353
+
354
+ llm = ChatOllama(model=use_llm, temperature=0.35, num_predict=600)
355
+
356
+ # define a LangChain chain
357
+ chain = rag_prompt | llm | StrOutputParser()
358
+
359
+
360
+ # invoke chain with retrieved documents and the question (user query)
361
+ output = chain.invoke({"context": documents, "question": question})
362
+
363
+ return output
364
+
365
+
366
+
367
+ def generate_agregated_2(reponses, question, use_llm):
368
+ """
369
+ LLM generates a response based on the question (user query), added context (retrieved documents), and a prompt
370
+ :param liste_rep: liste des réponses individuelles
371
+ :param use_llm: which llm to use
372
+ :return: LLM generated response
373
+ """
374
+
375
+ # adapted from https://smith.langchain.com/hub/rlm/rag-prompt
376
+
377
+ rag_prompt = ChatPromptTemplate.from_template("Tu es un assistant qui doit synthétiser plusieurs réponses à une question donnée par un client qui souhaîte récupérer et analyser des informations sur des documents de la Comission de la Transparence la Haute Autorité de Santé française,"
378
+ " qui est une réunion d'experts médicaux ayant en charge d'évaluer les nouveaux médicaments avant qu'ils ne soient mis sur le marché sur la base d'un dossier d'étude qui leur est présenté."
379
+ "Pour répondre tu as en contexte plusieurs réponses à la question qui t'ai posée, qui ont été générées par un llm en se basant à chaque fois sur un extrait de document différent. "
380
+ "Tu as en contexte toutes les réponses générées individuellement sur chaque extrait de document, avec le type de document et le médicament concerné par ce document."
381
+ "Tu citeras les documents avec '[num_doc]' lorsque tu utiliseras une information provennant d'une réponse générée sur un document."
382
+ "Ton rôle est de récupérer ces réponses, de vérifier si elles répondent bien à la question posée et d'agréger les informations issues de ces réponses en une petite synthèse pour répondre à la question."
383
+ "Les réponses sont indiquées dans le contexte par : "
384
+ "Document numéro [num_doc] = Type du document : [type]. Produit concerné : [nom du médicament]. Réponse générée : [réponse générée par llm en se basant sur le document [num_doc]] "
385
+ "Pour chaque document, vérifie que chaque réponse réponds bien à la question donnée, si ce n'est pas le cas ne considère pas cette réponse pour ta synthèse. Récupère les informations qui répondent bien à la question et font sens par rapport à la question."
386
+ " Réponds toujours en français."
387
+ "Question posée par le client : {question} \n"
388
+ "Contexte (réponses générées préalablement) : {context} \n"
389
+ "Synthèse générée en français pour répondre précisemment à la question et à rien d'autre :")
390
+ # define LLM to be used and the temperature (creativity/randomness) of the model
391
+
392
+ llm = ChatOllama(model=use_llm, temperature=0.5, num_predict=3000)
393
+
394
+ # define a LangChain chain
395
+ chain = rag_prompt | llm | StrOutputParser()
396
+
397
+
398
+ # invoke chain with retrieved documents and the question (user query)
399
+ output = chain.invoke({"context": reponses, "question": question})
400
+
401
+ return output
402
+
403
+
404
+
405
+
406
+ def ind_relevant_doc(question, documents, use_llm):
407
+ """
408
+ LLM generates a response based on the question (user query), added context (retrieved documents), and a prompt
409
+ :param question: user query
410
+ :param documents: retrieved documents, formatted
411
+ :param use_llm: which llm to use
412
+ :return: LLM generated response
413
+ """
414
+
415
+ # adapted from https://smith.langchain.com/hub/rlm/rag-prompt
416
+ rag_prompt = ChatPromptTemplate.from_template("Juge la pertinence entre la requête et le document : le document permet-il de répondre à la question? Renvoie 'oui' ou 'non' et rien d'autre."
417
+ "Requête : {question} \n"
418
+ "Document : {context} \n"
419
+ "réponse :")
420
+ # define LLM to be used and the temperature (creativity/randomness) of the model
421
+ llm = ChatOllama(model=use_llm, temperature=0.7, num_predict=3)
422
+ # define a LangChain chain
423
+ chain = rag_prompt | llm | StrOutputParser()
424
+
425
+ # invoke chain with retrieved documents and the question (user query)
426
+ output = chain.invoke({"context": documents, "question": question})
427
+
428
+ return output
429
+
430
+
431
+
432
+ def generate_score(question, documents, use_llm):
433
+ """
434
+ LLM generates a response based on the question (user query), added context (retrieved documents), and a prompt
435
+ :param question: user query
436
+ :param documents: retrieved documents, formatted
437
+ :param use_llm: which llm to use
438
+ :return: LLM generated response
439
+ """
440
+
441
+ # adapted from https://smith.langchain.com/hub/rlm/rag-prompt
442
+ rag_prompt = ChatPromptTemplate.from_template("Sur une échelle de 0 à 5, juge la pertinence entre la requête et le document. Ne renvoie que la note attribuée et rien d'autre."
443
+ "Requête : {question} \n"
444
+ "Document : {context} \n"
445
+ "réponse :")
446
+ # define LLM to be used and the temperature (creativity/randomness) of the model
447
+ llm = ChatOllama(model=use_llm, temperature=0.7, num_predict=1)
448
+ # define a LangChain chain
449
+ chain = rag_prompt | llm | StrOutputParser()
450
+
451
+ # invoke chain with retrieved documents and the question (user query)
452
+ output = chain.invoke({"context": documents, "question": question})
453
+
454
+ return output
455
+
456
+
rag_chat.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
rag_chat_v3.py ADDED
@@ -0,0 +1,523 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # pip install gradio langchain gpt4all chromadb pypdf tiktoken
3
+ # pip install --quiet gradio langchain gpt4all chromadb pypdf tiktoken
4
+
5
+
6
+ # imports
7
+ import os
8
+
9
+ import gradio as gr
10
+ from gradio.themes.base import Base
11
+
12
+ import glob
13
+
14
+ from langchain.text_splitter import RecursiveCharacterTextSplitter
15
+ from langchain_community.document_loaders import DirectoryLoader
16
+ from langchain_community.document_loaders import PyPDFLoader
17
+ from langchain_community.document_loaders import TextLoader
18
+ from langchain_community.vectorstores import Chroma
19
+ from langchain_community.embeddings import GPT4AllEmbeddings
20
+ from langchain_community.chat_models import ChatOllama
21
+ from langchain_core.output_parsers import StrOutputParser
22
+ from langchain_core.prompts import ChatPromptTemplate
23
+
24
+ import getpass
25
+
26
+ import json
27
+
28
+ from tqdm import tqdm
29
+
30
+ # Import necessary modules
31
+ from langchain.retrievers import ContextualCompressionRetriever, EnsembleRetriever
32
+ from langchain.retrievers.document_compressors import CrossEncoderReranker
33
+ from langchain_community.cross_encoders import HuggingFaceCrossEncoder
34
+ from langchain_core.output_parsers import StrOutputParser
35
+ from langchain_core.runnables import RunnableLambda, RunnableParallel, RunnablePassthrough
36
+
37
+ from typing import Sequence, Any, Dict
38
+ from langchain.schema import Document
39
+
40
+
41
+ import time
42
+
43
+
44
+
45
+ from functions_rag_chat_v3 import *
46
+
47
+ os.environ["LANGCHAIN_TRACING_V2"] = "true"
48
+ os.environ["LANGCHAIN_API_KEY"] = os.getenv("LANGCHAIN_API_KEY")
49
+
50
+
51
+
52
+ if __name__ == "__main__":
53
+ """
54
+ main function
55
+ """
56
+ print("Starting program")
57
+
58
+ start_time = time.time()
59
+
60
+ # define what LLM to use
61
+ use_llm = "mistral"
62
+ #use_llm = "phe-v2-gguf"
63
+
64
+ # define what embedding model to use
65
+ from langchain_community.embeddings import HuggingFaceEmbeddings
66
+
67
+
68
+ model_name = "clairedhx/autotrain-v2"
69
+
70
+
71
+ token=os.getenv("hugging_face_token")
72
+
73
+ model_kwargs = {'device': 'cuda', 'token': token}
74
+ encode_kwargs = {'normalize_embeddings': False}
75
+ embedding = HuggingFaceEmbeddings(
76
+ model_name=model_name,
77
+ model_kwargs=model_kwargs,
78
+ encode_kwargs=encode_kwargs
79
+ )
80
+ #print(embedding)
81
+
82
+ end_time = time.time()
83
+ print(f"Temps d'exécution pour l'initialisation des embeddings: {end_time - start_time} secondes")
84
+
85
+ # directory to persistently store the vector embedding store
86
+ db_directory = '/home/onyxia/phe/scripts/chroma_db'
87
+
88
+
89
+ #test a parir de dataframe pour avoir metadata
90
+
91
+ from datetime import datetime
92
+ import pandas as pd
93
+
94
+ #start_time = time.time()
95
+
96
+ #df = pd.read_csv('/home/onyxia/phe/scripts/gestion_base/documents_with_metadata_all_med_21_08_24.csv')
97
+ # Conversion de 'date_avis' en année
98
+ #df['année'] = pd.to_datetime(df['date_avis'], format='%Y-%m-%d').dt.year
99
+
100
+
101
+ from langchain_community.document_loaders import DataFrameLoader
102
+
103
+ #loader = DataFrameLoader(df, page_content_column="texte")
104
+
105
+ #docs= loader.load()
106
+
107
+ #splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(separators=["\n\n", "\n","."], chunk_size=400, chunk_overlap=150)
108
+ #splits = splitter.split_documents(docs)
109
+ #print("splits : ", len(splits))
110
+
111
+ #vectordb= Chroma.from_documents(documents=splits, collection_name="chromemwah", embedding=embedding, persist_directory=db_directory)
112
+ #vectordb.persist()
113
+
114
+ #end_time = time.time()
115
+ print(f"Temps d'exécution pour le chargement des documents, split et créer base chromaDB: {end_time - start_time} secondes")
116
+
117
+
118
+ start_time = time.time()
119
+
120
+ vectordb = Chroma(persist_directory=db_directory, embedding_function=embedding, collection_name="chromemwah")
121
+
122
+ end_time = time.time()
123
+ print(f"Temps d'exécution pour le chargement de la base de données persistante: {end_time - start_time} secondes")
124
+
125
+
126
+ ###############################################
127
+ #RECUPERATION VANNA AI
128
+ ###############################################
129
+
130
+ from dotenv import main
131
+ import os
132
+
133
+ print("Récupération des informations de connection")
134
+
135
+ start_time = time.time()
136
+
137
+ # Charger les variables d'environnement à partir du fichier .env
138
+ main.load_dotenv()
139
+
140
+ # Accéder aux variables d'environnement
141
+ Hostname = os.getenv("Hostname")
142
+ Port = os.getenv("Port")
143
+ Database = os.getenv("Database")
144
+ Username = os.getenv("Username")
145
+ Password = os.getenv("Password")
146
+
147
+ from vanna.ollama import Ollama
148
+ from vanna.chromadb import ChromaDB_VectorStore
149
+
150
+ class MyVanna(ChromaDB_VectorStore, Ollama):
151
+ def __init__(self, config=None):
152
+ ChromaDB_VectorStore.__init__(self, config=config)
153
+ Ollama.__init__(self, config=config)
154
+
155
+ vn = MyVanna(config={'model': 'mistral'})
156
+ vn.connect_to_postgres(host=Hostname, dbname=Database, user=Username, password=Password, port=Port) # Connect to your database here
157
+
158
+ vn.train(ddl="""
159
+ CREATE TABLE IF NOT EXISTS medicaments (
160
+ id SERIAL PRIMARY KEY,
161
+ nom VARCHAR(2555) NOT NULL,
162
+ nombre_avis INTEGER,
163
+ nombre_docs INTEGER,
164
+ DCI VARCHAR(2555),
165
+ exploitant VARCHAR(2555),
166
+ codes_ATC TEXT[],
167
+ cip TEXT[]
168
+ );
169
+
170
+ CREATE TABLE IF NOT EXISTS avis (
171
+ id SERIAL PRIMARY KEY,
172
+ numero_avis VARCHAR(255) NOT NULL,
173
+ maladie VARCHAR(255),
174
+ aires_therapeutiques TEXT[],
175
+ date_avis DATE,
176
+ nombre_docs INTEGER,
177
+ medicament_id INTEGER REFERENCES medicaments(id),
178
+ smr smr_type,
179
+ asmr asmr_type
180
+ );
181
+
182
+ CREATE TABLE IF NOT EXISTS documents (
183
+ id SERIAL PRIMARY KEY,
184
+ titre_doc VARCHAR(300) NOT NULL,
185
+ type document_type NOT NULL,
186
+ indication VARCHAR(100000),
187
+ medicament_id INTEGER REFERENCES medicaments(id),
188
+ avis_id INTEGER REFERENCES avis(id),
189
+ lien_doc VARCHAR(255),
190
+ transcription_ct_associee INTEGER[],
191
+ avis_ct_associe INTEGER[],
192
+ transcription_ceesp_associee INTEGER[],
193
+ avis_ceesp_associe INTEGER[],
194
+ questionnaire_associe INTEGER[],
195
+ texte TEXT -- Nouveau champ pour stocker le texte extrait
196
+ );
197
+ """)
198
+
199
+ import json
200
+
201
+ # Load the JSON file
202
+ with open('/home/onyxia/phe/scripts/modeles/text_to_SQL/entrainement_augmented.json', 'r') as file:
203
+ data = json.load(file)
204
+
205
+ # Train Vanna with the SQL query pairs
206
+ for pair in data:
207
+ question = pair['question_to_sql']
208
+ sql = pair['sql']
209
+ vn.train(question=question.strip(), sql=sql.strip())
210
+
211
+ end_time = time.time()
212
+ print(f"Temps d'exécution pour la connexion à la base de données et l'entraînement de Vanna: {end_time - start_time} secondes")
213
+
214
+
215
+ ####################################################################
216
+ ####################################################################
217
+
218
+
219
+
220
+ RERANKER_CROSS_ENCODER = "BAAI/bge-reranker-base"
221
+ model_hf_cross = HuggingFaceCrossEncoder(model_name=RERANKER_CROSS_ENCODER)
222
+
223
+
224
+
225
+ def complete_rag(question, selected_types, year_start, year_end):
226
+ """
227
+ The process of retrieval augmented generation
228
+ :param question: user query
229
+ :return: sources and LLM ouput, generated using retrieved documents
230
+ """
231
+
232
+ start_time = time.time()
233
+
234
+ vn.connect_to_postgres(host=Hostname, dbname=Database, user=Username, password=Password, port=Port)
235
+ training_data = vn.get_training_data()
236
+ print("training_data")
237
+ print(training_data)
238
+
239
+ sous_questions =generate_sous_questions(question)
240
+ question_llm, question_sql = sous_questions[0], sous_questions[1]
241
+ print("question to sql : ",question_sql)
242
+ print("question to llm : ",question_llm)
243
+
244
+ sql=vn.generate_sql(question=question_sql, allow_llm_to_see_data=True)
245
+ print(' \n \n sql : ',sql)
246
+
247
+ # Récupération des IDs et des liens `lien_med`
248
+ result_sql = vn.run_sql(sql)
249
+ list_id = result_sql['id'].tolist()
250
+
251
+
252
+ print("\n \n list_id : ",list_id)
253
+
254
+ if list_id==[]:
255
+ print("No documents", "Aucun document pouvant répondre à cette question n'a été trouvé dans la base.")
256
+
257
+ end_time = time.time()
258
+ print(f"Temps d'exécution pour complete_rag [split questions, vanna ai]: {end_time - start_time} secondes")
259
+
260
+ start_time = time.time()
261
+
262
+ # Handle the selection of document types
263
+ if "tous" in selected_types:
264
+ selected_types = ['avis_ct', 'transcription_ct', 'avis_ceesp', 'transcription_ceesp', 'questionnaire']
265
+ else:
266
+ selected_types = [doc_type for doc_type in selected_types if doc_type != 'tous']
267
+
268
+ # Convertir les années sélectionnées en entiers
269
+ year_min = int(year_start)
270
+ year_max = int(year_end)
271
+
272
+ # La plage d'années sélectionnée est définie par year_min et year_max
273
+ years = list(range(year_min, year_max + 1))
274
+
275
+ # search_kwargs avec le filtre des années
276
+ search_kwargs = {
277
+ "k": 500,
278
+ "filter": {
279
+ '$and': [
280
+ {'id_doc': {'$in': list_id}},
281
+ {'type': {'$in': selected_types}},
282
+ {'année': {'$in': years}} # Filtre sur les années sélectionnées
283
+ ]
284
+ }
285
+ }
286
+
287
+ retriever = vectordb.as_retriever(search_kwargs=search_kwargs) #{"k": 500, "filter":{'id_doc': {'$in': list_id},'type': {'$in': ['avis_ct', 'transcription_ct']}}})
288
+ compressor = CrossEncoderReranker(model=model_hf_cross, top_n=60)
289
+ retrieval_agent_hg_crossencoder = ContextualCompressionRetriever(base_compressor=compressor, base_retriever=retriever)
290
+
291
+ from langchain_community.retrievers import BM25Retriever
292
+ retrieval_agent_bm25 = BM25Retriever.from_documents(retriever.get_relevant_documents(question_llm), k=60)
293
+
294
+ from langchain.retrievers import EnsembleRetriever
295
+
296
+ # initialize the ensemble retriever
297
+ ensemble_retriever = EnsembleRetriever(
298
+ retrievers=[retrieval_agent_bm25, retrieval_agent_hg_crossencoder], weights=[0.95, 0.05]
299
+ )
300
+
301
+ end_time = time.time()
302
+ print(f"Temps d'exécution pour complete_rag [ensemble retriever]: {end_time - start_time} secondes")
303
+
304
+ start_time = time.time()
305
+
306
+ print("retriever")
307
+ documents = ensemble_retriever.get_relevant_documents(question_llm)
308
+ print(len(documents), " chunks retrouvés")
309
+
310
+ docs_scored=[]
311
+ scores=[]
312
+ all_scores =[]
313
+ for index, doc in enumerate(documents):
314
+ # Passer la liste de documents au lieu d'un seul document
315
+ output = generate_score(question, context_formatting([doc]), "mistral")
316
+ all_scores.append(int(output))
317
+ #if(int(output)>1):
318
+ #docs_scored.append(doc)
319
+ #scores.append(int(output))
320
+ #print(len(docs_scored), "retrouvés après scores")
321
+ print("All scores : ", all_scores)
322
+
323
+ # Trier les documents gardés en fonction des scores
324
+ #docs_with_scores = list(zip(docs_scored, scores))
325
+ #docs_sorted_by_score = sorted(docs_with_scores, key=lambda x: x[1], reverse=True)
326
+ #docs_scored_sorted = [doc for doc, score in docs_sorted_by_score]
327
+ #scores_sorted = [score for doc, score in docs_sorted_by_score]
328
+
329
+ # Trier tous les documents en fonction des scores
330
+ all_docs_with_scores = list(zip(documents, all_scores))
331
+ all_docs_sorted_by_score = sorted(all_docs_with_scores, key=lambda x: x[1], reverse=True)
332
+ all_docs_scored_sorted = [doc for doc, score in all_docs_sorted_by_score]
333
+ all_scores_sorted = [score for doc, score in all_docs_sorted_by_score]
334
+
335
+ docs_ejected=[]
336
+ scores_ejected=[]
337
+ for index, score in enumerate(all_scores_sorted):
338
+ if score>2:
339
+ docs_scored.append(all_docs_scored_sorted[index])
340
+ scores.append(score)
341
+ else:
342
+ docs_ejected.append(all_docs_scored_sorted[index])
343
+ scores_ejected.append(score)
344
+
345
+
346
+ docs_with_scores = list(zip(docs_scored, scores))
347
+
348
+ end_time = time.time()
349
+ print(f"Temps d'exécution pour complete_rag [scoring pertinence]: {end_time - start_time} secondes")
350
+
351
+
352
+ start_time = time.time()
353
+
354
+ from collections import defaultdict
355
+
356
+ # Initialisation des variables pour stocker les documents et les scores regroupés
357
+ from collections import defaultdict
358
+
359
+ grouped_documents = defaultdict(list)
360
+ grouped_scores = defaultdict(list)
361
+
362
+ # On suppose que chaque document a une clé 'avis_id' dans ses métadonnées
363
+ for doc, score in docs_with_scores:
364
+ avis_id = doc.metadata['avis_id'] # Assurez-vous que 'avis_id' est bien dans les métadonnées
365
+ grouped_documents[avis_id].append(doc)
366
+ grouped_scores[avis_id].append(score)
367
+
368
+ # Convertir les dictionnaires en listes de listes
369
+ documents_regroupes_sorted = []
370
+ scores_regroupes_sorted = []
371
+
372
+ for avis_id in grouped_documents.keys():
373
+ # Récupérer les documents et scores pour cet avis_id
374
+ docs = grouped_documents[avis_id]
375
+ scores = grouped_scores[avis_id]
376
+
377
+ # Trier les paires (doc, score) en fonction des scores
378
+ sorted_pairs = sorted(zip(docs, scores), key=lambda x: x[1], reverse=True)
379
+
380
+ # Séparer les documents et scores après tri
381
+ sorted_docs, sorted_scores = zip(*sorted_pairs)
382
+
383
+ # Ajouter les listes triées aux résultats finaux
384
+ documents_regroupes_sorted.append(list(sorted_docs))
385
+ scores_regroupes_sorted.append(list(sorted_scores))
386
+
387
+ # Maintenant, documents_regroupes_sorted et scores_regroupes_sorted sont bien triés
388
+
389
+ # Afficher le nombre de groupes trouvés
390
+ print(f"{len(documents_regroupes_sorted)} avis retrouvés après regroupement des chunks")
391
+
392
+ end_time = time.time()
393
+ print(f"Temps d'exécution pour complete_rag [regroupement chunks par avis]: {end_time - start_time} secondes")
394
+
395
+
396
+ start_time = time.time()
397
+
398
+ # Appel de la fonction source_formatting avec les scores associés
399
+ sources = source_formatting(documents_regroupes_sorted, scores_regroupes_sorted, docs_ejected, scores_ejected)
400
+
401
+
402
+
403
+ outputs = ""
404
+ outputs_for_last_llm =""
405
+
406
+ final_docs=documents_regroupes_sorted
407
+
408
+ for index, doc in enumerate(tqdm(final_docs, desc="question sur chaque chunk - mistral")):
409
+ output = generate_2(question_llm, context_formatting(doc), "mistral")
410
+ outputs += "Réponse à l'avis numéro " + str(index+1) + " : " + output + "\n\n"
411
+
412
+ end_time = time.time()
413
+ print(f"Temps d'exécution pour complete_rag [boucle mistral question sur chaque avis]: {end_time - start_time} secondes")
414
+
415
+ start_time = time.time()
416
+
417
+ output_agreg = generate_agregated_2(outputs, question, "mistral")
418
+ synthese = "SYNTHESE : \n\n" +output_agreg + "\n\n\nREPONSE POUR CHAQUE AVIS : \n\n" + outputs
419
+
420
+ end_time = time.time()
421
+ print(f"Temps d'exécution pour complete_rag [question synthese]: {end_time - start_time} secondes")
422
+
423
+ start_time = time.time()
424
+
425
+ import psycopg2
426
+
427
+ # Connexion à la base de données PostgreSQL
428
+ conn = psycopg2.connect(host=Hostname, dbname=Database, user=Username, password=Password, port=Port)
429
+ cursor = conn.cursor()
430
+
431
+ # Conversion de la liste en une chaîne compatible SQL
432
+ id_string = ','.join(map(str, list_id))
433
+
434
+ # Requête SQL pour obtenir les nombres uniques
435
+ query = f"""
436
+ SELECT
437
+ COUNT(DISTINCT d.avis_id) AS unique_avis_count,
438
+ COUNT(DISTINCT d.medicament_id) AS unique_medicament_count,
439
+ COUNT(DISTINCT d.id) AS document_count
440
+ FROM
441
+ documents d
442
+ WHERE
443
+ d.id IN ({id_string});
444
+ """
445
+
446
+ query_lien_meds = f"""
447
+ SELECT DISTINCT m.lien_med
448
+ FROM documents as d
449
+ JOIN medicaments m ON d.medicament_id = m.id
450
+ WHERE
451
+ d.id IN ({id_string});
452
+ """
453
+
454
+
455
+ # Exécution de la requête
456
+ cursor.execute(query)
457
+ result = cursor.fetchone()
458
+
459
+ # Exécution de la requête pour les liens `lien_med`
460
+ cursor.execute(query_lien_meds)
461
+ result_lien_meds = cursor.fetchall()
462
+
463
+ # Conversion des résultats de `lien_meds` en une liste
464
+ lien_meds = [row[0] for row in result_lien_meds if row[0]] # Évite les valeurs nulles
465
+
466
+
467
+ # Affichage des résultats
468
+ unique_avis_count, unique_medicament_count, document_count = result
469
+ comptes = (f"Nombre de médicaments concernés par la question : {unique_medicament_count}<br>"
470
+ f"Nombre d'avis concernés par la question : {unique_avis_count}<br>"
471
+ f"Nombre de documents concernés par la question : {document_count}<br><br>"
472
+ "Liens des médicaments concernés :<br>" +
473
+ "<br>".join([f"[{lien}]({lien})" for lien in lien_meds])) # Conversion en liens Markdown cliquables avec balises HTML
474
+
475
+ # Fermeture de la connexion
476
+ cursor.close()
477
+ conn.close()
478
+
479
+ end_time = time.time()
480
+ print(f"Temps d'exécution pour complete_rag [recupération effectifs]: {end_time - start_time} secondes")
481
+
482
+
483
+ return sources, synthese, comptes
484
+
485
+
486
+ # for web view of prompting
487
+ # code below is copied from: https://www.youtube.com/watch?v=JEBDfGqrAUA (Project 2)
488
+ with gr.Blocks(theme=Base(), title="Q&A on your data with RAG") as demo:
489
+ gr.Markdown("# Q&A sur les documents de la HAS")
490
+
491
+ # Sélection du type de document
492
+ doc_type_selection = gr.CheckboxGroup(
493
+ choices=["tous", "avis_ct", "transcription_ct", "avis_ceesp", "transcription_ceesp", "questionnaire"],
494
+ label="Sélectionnez les types de documents",
495
+ value=["tous"] # Preselect "tous"
496
+ )
497
+
498
+ # Boîte déroulante pour sélectionner l'année de début
499
+ year_start_dropdown = gr.Dropdown(
500
+ choices=[str(year) for year in range(2000, 2025)], # De 2000 à 2024
501
+ value="2000", # Valeur par défaut
502
+ label="Sélectionnez l'année de début"
503
+ )
504
+
505
+ # Boîte déroulante pour sélectionner l'année de fin
506
+ year_end_dropdown = gr.Dropdown(
507
+ choices=[str(year) for year in range(2000, 2025)], # De 2000 à 2024
508
+ value="2024", # Valeur par défaut
509
+ label="Sélectionnez l'année de fin"
510
+ )
511
+
512
+ textbox = gr.Textbox(label="Question:")
513
+ with gr.Row():
514
+ button = gr.Button("Entrée", variant="primary")
515
+ with gr.Column():
516
+ output3 = gr.Markdown(label="Effectifs")
517
+ output2 = gr.Textbox(lines=1, max_lines=1000, label="Réponse générée")
518
+ output1 = gr.Markdown(label="Sources")
519
+
520
+ # Mise à jour des inputs pour inclure les deux boîtes déroulantes
521
+ button.click(complete_rag, inputs=[textbox, doc_type_selection, year_start_dropdown, year_end_dropdown], outputs=[output1, output2, output3])
522
+
523
+ demo.launch(share=True)
requirements.txt ADDED
Binary file (5.87 kB). View file