File size: 7,421 Bytes
65d97fa
 
3afd61a
65d97fa
 
3afd61a
 
65d97fa
 
 
3afd61a
65d97fa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3afd61a
 
65d97fa
 
3afd61a
 
 
65d97fa
3afd61a
65d97fa
3afd61a
 
 
65d97fa
 
 
3afd61a
65d97fa
 
 
3afd61a
65d97fa
 
3afd61a
65d97fa
 
3afd61a
65d97fa
 
3afd61a
65d97fa
 
 
 
 
 
 
 
 
3afd61a
65d97fa
 
 
 
 
 
 
590d088
3afd61a
590d088
 
 
65d97fa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3afd61a
65d97fa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
590d088
 
 
 
 
65d97fa
 
 
 
 
 
 
 
 
 
3afd61a
65d97fa
 
 
3afd61a
65d97fa
590d088
3afd61a
590d088
 
65d97fa
 
 
 
 
 
 
 
3afd61a
 
65d97fa
 
 
 
 
 
 
 
 
590d088
 
3afd61a
590d088
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3afd61a
65d97fa
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
from pypdf import PdfReader
from .chunker import Chunker
from .amodel import AModel
from .store import Store

CHUNK_CHAR_COUNT = 1000
CHUNK_OVERLAP = 200

class Rag:
    '''
        RAG naïf
        Classe qui s'occupe de toute la chaine du RAG.
        Elle permet :
            d'interroger un llm directement (sans RAG) avec ask_llm()
            d'interroger le RAG lui même avec ask_rag()
            d'ajouter des documents à la base de données du RAG
            de remettre la base à zéro
            de créer des vecteurs
            de charger des pdf
    '''

    # Le prompt qui sera utilisé uniquement avec le RAG
    prompt_template = """
        En vous basant **uniquement** sur les informations fournies dans le contexte 
        ci-dessous, répondez à la question posée.
        Les équations seront écrites en latex.
        Si vous ne trouvez pas la réponse dans le contexte, répondez "Je ne sais pas".
        Contexte : {context}
        Question : {question}
    """

    def __init__(self, llm:AModel, emb:AModel, store_dir:str)->None:
        '''
            Constructeur du Rag
            Args: 
                llm: le model de langage
                emb: le model d'embeddings
                store_dir: le répertoire de persistance de la base de données ou None pour éphémère
            Exception:
                Si le store ne peut pas se créer (répertoire inaccessible par ex.)
        '''
        self.llm:AModel = llm
        self.emb:AModel = emb
        self.store_dir:str = store_dir
        try:
            self.emb_store = Store(store_dir) # persistant
            # self.emb_store = Store(None) # éphémère
        except:
            raise

    def get_llm_name(self):
        return self.llm.get_llm_name()
    
    def get_feature_name(self):
        return self.emb.get_feature_name()
    
    def get_temperature(self):
        return self.llm.get_temperature()
    
    def set_temperature(self, temperature:float):
        self.llm.set_temperature(temperature)

    def reset_store(self):
        self.emb_store.reset()

    def delete_collection(self, name:str)->None:
        self.emb_store.delete_collection(name)

    def create_vectors(self, chunks:list[str])->list[list[float]]:
        '''
            Renvoie les vecteurs correspondant à 'chunks', calculés par 'emb'
            Args:
                chunks: les extraits de texte à calculer
            Return:
                la liste des vecteurs calculés
        '''        
        vectors:list = []
        tokens:int = 0
        try:
            vectors:list[list[float]] = self.emb.create_vectors(chunks) # batch si le model le permet
            return vectors
        except:
            raise

    def load_pdf(self, file_name:str)->str:
        ''' Charge le fichier 'file_name' et renvoie son contenu sous forme de texte. '''
        reader = PdfReader(file_name)
        content = ""
        for page in reader.pages:
            content += page.extract_text() + "\n"
        return content
    
    def get_chunks(self, text:str)->list:
        '''
            Découpe le 'text' en chunks de taille chunk_size avec un recouvrement
            Args:
                text: Le texte à découper
            Return:
                La liste des chunks
        '''
        chunker = Chunker()
        chunks = chunker.split_basic(text=text, char_count=CHUNK_CHAR_COUNT, overlap=CHUNK_OVERLAP)
        return chunks

    def add_pdf_to_store(self, file_name:str, collection_name:str)->None:
        '''
            Ajoute un pdf à la base de données du RAG.
            Args:
                file_name: le chemin vers le fichier à ajouter
                collection_name: Le nom de la collection dans laquelle il faut ajouter les chunks
                    La collection est créée si elle n'existe pas.
        '''
        text:str = self.load_pdf(file_name)
        chunks:list[str] = self.get_chunks(text)
        self.add_chunks_to_store(chunks=chunks, collection_name=collection_name, source=file_name)

    def add_pdf_stream_to_store(self, stream, collection_name:str)->None:
        '''
            Ajoute un stream provenant de file_uploader de streamlit par exemple
        '''
        text:str = self.load_pdf(stream)
        chunks:list[str] = self.get_chunks(text)
        self.add_chunks_to_store(chunks=chunks, collection_name=collection_name, source="stream")

    def add_chunks_to_store(self, chunks:list[str], collection_name:str, source:str)->None:
        '''
            Ajoute des chunks à la base de données du RAG.
            Args:
                chunks: les chunks à ajouter
                collection_name: Le nom de la collection dans laquelle il faut ajouter les chunks
                    La collection est créée si elle n'existe pas.
                source: la source des chunks (nom du fichier, url ...)
        ''' 
        try:       
            vectors = self.create_vectors(chunks=chunks)
        except:
            raise
        self.emb_store.add_to_collection(
            collection_name=collection_name, 
            source=source, 
            vectors=vectors,
            chunks=chunks
        )


    def ask_llm(self, question:str)->str:
        '''
            Pose une question au llm, attend sa réponse et la renvoie.
            Args:
                question: La question qu'on veut lui poser
            Returns:
                La réponse du llm
        '''
        try:
            return self.llm.ask_llm(question=question)
        except:
            return "Error while comminicating with model !"

    def ask_rag(self, question:str, collection_name:str)->tuple[str, str, list[str], list[str]]:
        '''
            Pose une question au RAG, attend sa réponse et la renvoie.
            Args:
                question: La question qu'on veut lui poser
                collection_name: le nom de la collection que l'on veut interroger
            Returns:
                Le prompt effectivement donné au llm
                La réponse du llm
                Les sources du RAG utilisées
                Les ids des documents du RAG
        '''
        if not question:
            return "", "Error: No question !", [], []
        if not collection_name:
            return "", "Error: No collection specified !", [], []
        if not collection_name in self.emb_store.get_collection_names():
            return "", "Error: {name} is no more in the database !".format(name=collection_name), [], []
        try:
            # Transformer la 'question' en vecteur avec emb_model
            query_vector:list[float] = self.emb.create_vector(question) 
            # Récupérer les chunks du store similaires à la question
            chunks, sources, ids = self.emb_store.get_similar_chunks( 
                query_vector=query_vector, 
                count=2, 
                collection_name=collection_name
            )
            # Préparer le prompt final à partir du prompt_template
            prompt:str = self.prompt_template.format( 
                context="\n\n\n".join(chunks),
                question=question
            )
            # demander au llm_model de répondre
            resp:str = self.ask_llm(question=prompt)

            return prompt, resp, sources, ids
        except:
            return "", "Error with communicating with model !", [], []