Spaces:
Runtime error
Runtime error
Upload folder using huggingface_hub
Browse files- 1934d09a-c249-4fe5-aa72-584f5845fb98/data_level0.bin +3 -0
- 1934d09a-c249-4fe5-aa72-584f5845fb98/header.bin +3 -0
- 1934d09a-c249-4fe5-aa72-584f5845fb98/length.bin +3 -0
- 1934d09a-c249-4fe5-aa72-584f5845fb98/link_lists.bin +0 -0
- 7495d102-62f8-4242-9219-87d4caee7813/data_level0.bin +3 -0
- 7495d102-62f8-4242-9219-87d4caee7813/header.bin +3 -0
- 7495d102-62f8-4242-9219-87d4caee7813/length.bin +3 -0
- 7495d102-62f8-4242-9219-87d4caee7813/link_lists.bin +0 -0
- README.md +3 -9
- __pycache__/functions_rag_chat_v3.cpython-312.pyc +0 -0
- chroma.sqlite3 +0 -0
- functions_rag_chat_v3.py +456 -0
- rag_chat.ipynb +0 -0
- rag_chat_v3.py +523 -0
- requirements.txt +0 -0
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:
|
| 3 |
-
|
| 4 |
-
colorFrom: red
|
| 5 |
-
colorTo: blue
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version: 4.
|
| 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
|
|
|