papasega commited on
Commit
cf3dd65
·
1 Parent(s): 70a2855
app_gradio.py ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import gradio as gr
3
+ import tiktoken
4
+ import os
5
+ import numpy as np
6
+ import requests
7
+ from dotenv import load_dotenv
8
+ from sentence_transformers import SentenceTransformer
9
+ from langchain_community.chat_models import ChatOllama
10
+ from basesrc.strategies import convert_pdf_to_text, split_pages_into_chunks, vectorize
11
+ from prompt_query import generate_prompt
12
+
13
+ load_dotenv()
14
+ CACHE_FOLDER = os.environ["CACHE_FOLDER"]
15
+ CACHE_FOLDER = "./cache" # si toutefois (¨_^)
16
+
17
+ def create_interface(pdf_url, embedding_model, llm_model, top_k=5):
18
+ os.makedirs(CACHE_FOLDER, exist_ok=True)
19
+ response = requests.get(pdf_url)
20
+ pdf_filename = pdf_url.split('/')[-1]
21
+ pdf_path = os.path.join(CACHE_FOLDER, 'document.pdf')
22
+ with open(pdf_path, 'wb') as f:
23
+ f.write(response.content)
24
+
25
+ pages = convert_pdf_to_text(pdf_path)
26
+ print(
27
+ """
28
+ Je suis Llama3, de l'équipe DREAMS TEAM, votre assistant QA pour répondre à vos questions liés aux documents 🙂 !
29
+ Déjà pour info, le nombre de pages de vote document est: """, len(pages)
30
+ )
31
+ tokenizer = tiktoken.get_encoding("cl100k_base")
32
+ chunks = split_pages_into_chunks(pages, 128, tokenizer)
33
+ embedding_model = SentenceTransformer(embedding_model)
34
+
35
+ knowledge_base = vectorize(chunks, embedding_model)
36
+ chunks, embeddings = list(zip(*knowledge_base))
37
+ corpus_embeddings = np.vstack(embeddings)
38
+
39
+ llm_model = ChatOllama(model=llm_model)
40
+
41
+ def chat_interface(question):
42
+ return generate_prompt(question, chunks, corpus_embeddings, embedding_model, llm_model, top_k)
43
+
44
+ examples = [
45
+ ["Quel est ton rôle ? "],
46
+ ["Que nous parle ce document ?"],
47
+ ["Fais-moi un résumé des éléments essentiels de ce document"],
48
+ ["Comment puis-je présenter ce document dans une réunion d'équipe de professionnels ?"]
49
+ ]
50
+
51
+ iface = gr.Interface(
52
+ fn=chat_interface,
53
+ inputs=gr.Textbox(lines=3, label="✨Posez votre question en français 😎", placeholder="Posez une question..."),
54
+ outputs=gr.Textbox(label="Réponse"),
55
+ title=f"Assistant de chat sur le document | {os.path.basename(pdf_filename)} | by PSW",
56
+ description="Posez votre question en français et obtenez une réponse basée sur le contexte du document.",
57
+ examples=examples
58
+ )
59
+
60
+ iface.launch(share=True)
61
+
62
+ if __name__ == "__main__":
63
+ create_interface(
64
+ "https://hellofuture.orange.com/app/uploads/2024/05/2024-Orange-white-paper-on-Mobile-Network-Technology-Evolutions-Beyond-2030.pdf",
65
+ "Sahajtomar/french_semantic",
66
+ "llama3"
67
+ )
68
+
69
+ """ RUN
70
+ python app_gradio.py
71
+ """
basesrc/__pycache__/strategies.cpython-310.pyc ADDED
Binary file (2.17 kB). View file
 
basesrc/strategies.py ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import fitz # PyMuPDF
3
+ import numpy as np
4
+ import itertools as it
5
+ import operator as op
6
+ from typing import List, Tuple
7
+ from sentence_transformers import SentenceTransformer
8
+
9
+
10
+ """
11
+ Sur la partie strategies.py nous allons :
12
+ 1. convertir notre pdf en texte
13
+ 2. diviser le texte en plusieur paragraphes (chunks dans le code)
14
+ 3. transformer le texte en vecteur (embeddings)
15
+ 4. Aller chercher le bon candicat (trouver la réponse pertinente de l'utilisateur dans le pdf)
16
+
17
+ """
18
+
19
+ def convert_pdf_to_text(pdf_data: str) -> List[str]:
20
+ document = fitz.open(pdf_data)
21
+ accumulator: List[str] = []
22
+ for page_num in range(len(document)):
23
+ page = document[page_num]
24
+ text = page.get_text()
25
+ accumulator.append(text)
26
+ document.close()
27
+ return accumulator
28
+
29
+ def split_pages_into_chunks(pages: List[str], chunk_size: int, tokenizer) -> List[str]:
30
+ page_tokens: List[List[int]] = [tokenizer.encode(page) for page in pages]
31
+ document_tokens = list(it.chain(*page_tokens))
32
+
33
+ nb_tokens = len(document_tokens)
34
+ nb_partitions = round(nb_tokens / chunk_size)
35
+
36
+ accumulator: List[str] = []
37
+
38
+ for chunck_tokens in np.array_split(document_tokens, nb_partitions):
39
+ paragraph = tokenizer.decode(chunck_tokens)
40
+ accumulator.append(paragraph)
41
+
42
+ return accumulator
43
+
44
+ def vectorize(chunks: List[str], transformer: SentenceTransformer, device: str = 'cpu') -> List[Tuple[str, np.ndarray]]:
45
+ embeddings = transformer.encode(
46
+ sentences=chunks,
47
+ batch_size=32,
48
+ device=device
49
+ ,show_progress_bar=True,
50
+ )
51
+
52
+ return list(zip(chunks, embeddings))
53
+
54
+ def find_candidates(query_embedding: np.ndarray, chunks: List[str], corpus_embeddings: np.ndarray, top_k: int = 5) -> List[str]:
55
+ dot_product = query_embedding @ corpus_embeddings.T
56
+ norms = np.linalg.norm(query_embedding) * np.linalg.norm(corpus_embeddings, axis=1)
57
+
58
+ weighted_scores = dot_product / norms # Formule de cosine similarity cos(u,v) = u.v / |u||v|
59
+
60
+ zipped_chunks_scores = list(zip(chunks, weighted_scores))
61
+ sorted_chunks_scores = sorted(zipped_chunks_scores, key=op.itemgetter(1), reverse=True)
62
+ selected_candidates = sorted_chunks_scores[:top_k]
63
+
64
+ return list(map(op.itemgetter(0), selected_candidates))
chatpdf.py ADDED
@@ -0,0 +1,178 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import click
3
+ import fitz # PyMuPDF
4
+ import numpy as np
5
+ import itertools as it
6
+ import operator as op
7
+ import requests
8
+ import tiktoken
9
+ from typing import List, Tuple
10
+ from dotenv import load_dotenv
11
+ from sentence_transformers import SentenceTransformer
12
+ from langchain_community.chat_models import ChatOllama
13
+ from langchain_core.output_parsers import StrOutputParser
14
+ from langchain_core.prompts import ChatPromptTemplate
15
+
16
+ load_dotenv()
17
+ protocol_buffers_python_implementation = os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"]
18
+ CACHE_FOLDER = os.environ["CACHE_FOLDER"]
19
+ CACHE_FOLDER = "./cache" # Chemin du dossier cache
20
+
21
+ # Fonctions existantes pour convertir les PDFs et diviser les pages en chunks
22
+ def convert_pdf_to_text(pdf_data: str) -> List[str]:
23
+ document = fitz.open(pdf_data)
24
+ accumulator: List[str] = []
25
+ for page_num in range(len(document)):
26
+ page = document[page_num]
27
+ text = page.get_text()
28
+ accumulator.append(text)
29
+ document.close()
30
+ return accumulator
31
+
32
+ def split_pages_into_chunks(pages: List[str], chunk_size: int, tokenizer) -> List[str]:
33
+ page_tokens: List[List[int]] = [tokenizer.encode(page) for page in pages]
34
+ document_tokens = list(it.chain(*page_tokens))
35
+
36
+ nb_tokens = len(document_tokens)
37
+ nb_partitions = round(nb_tokens / chunk_size)
38
+
39
+ accumulator: List[str] = []
40
+
41
+ for chunck_tokens in np.array_split(document_tokens, nb_partitions):
42
+ paragraph = tokenizer.decode(chunck_tokens)
43
+ accumulator.append(paragraph)
44
+
45
+ return accumulator
46
+
47
+ def vectorize(chunks: List[str], transformer: SentenceTransformer, device: str = 'cpu') -> List[Tuple[str, np.ndarray]]:
48
+ embeddings = transformer.encode(
49
+ sentences=chunks,
50
+ batch_size=32,
51
+ device=device
52
+ ,show_progress_bar=True,
53
+ )
54
+
55
+ return list(zip(chunks, embeddings))
56
+
57
+ def find_candidates(query_embedding: np.ndarray, chunks: List[str], corpus_embeddings: np.ndarray, top_k: int = 5) -> List[str]:
58
+ dot_product = query_embedding @ corpus_embeddings.T
59
+ norms = np.linalg.norm(query_embedding) * np.linalg.norm(corpus_embeddings, axis=1)
60
+
61
+ weighted_scores = dot_product / norms # cosine similarity
62
+
63
+ zipped_chunks_scores = list(zip(chunks, weighted_scores))
64
+ sorted_chunks_scores = sorted(zipped_chunks_scores, key=op.itemgetter(1), reverse=True)
65
+ selected_candidates = sorted_chunks_scores[:top_k]
66
+
67
+ return list(map(op.itemgetter(0), selected_candidates))
68
+
69
+
70
+ def generate_prompt(question, chunks, corpus_embeddings, embedding_model, llm_model, top_k, document_type: str = "ce pdf"):
71
+ query_embedding = embedding_model.encode([question])[0]
72
+ candidates = find_candidates(query_embedding=query_embedding,
73
+ chunks=chunks,
74
+ corpus_embeddings=corpus_embeddings,
75
+ top_k=top_k)
76
+ context = "\n".join(candidates)
77
+ prompt = f"""
78
+ ROLE: Tu es un assistant QA. Tu as été crée par Papa Séga de Orange INNVO.
79
+ Ton rôle est d'aider les utilisateurs à trouver la bonne réponse.
80
+ FONCTIONNEMENT:
81
+ Voici un ensemble de documents dans {document_type}: {context}.
82
+ Tu dois analyser ces documents pour répondre à la question de l'utilisateur.
83
+ Tu dois répondre aux utilisateurs uniquement en français.
84
+ Tu ne dois pas répondre en anglais.
85
+ Vérifie bien que la question est en rapport avec ces documents :
86
+ -SI OUI ALORS :
87
+ - construit une réponse.
88
+ - tu peux reformuler la réponse
89
+ - SI NON ALORS:
90
+ - Si la question est en rapport avec ton fonctionnement alors:
91
+ - rappelle-lui que tu es juste un modèle de QA et ne réponds pas à la question.
92
+ Si Non Alors:
93
+ - il ne faut jamais répondre à cette question qui a été posée par l'utilisateur !
94
+ RAPPEL :
95
+ Ton objectif est d'aider l'utilisateur à trouver les réponses pertinentes sur ces questions !
96
+
97
+ Question de l'utilisateur : {question}
98
+ SI la question posée est hors contexte,
99
+ - ALORS informe l'utilisateur que la question est hors contexte et que tu ne pourras lui donner une réponse.
100
+
101
+ Attention
102
+ - Tu dois être courtois avec les utilisateurs
103
+ - Tu n'as pas le droit de faire des hallucinations.
104
+
105
+ Réponds à la question en te basant UNIQUEMENT sur le contexte suivant :\n{context}\nQuestion : {question}
106
+
107
+ """
108
+
109
+ chain_prompt = ChatPromptTemplate.from_template(prompt)
110
+ chain = chain_prompt | llm_model | StrOutputParser()
111
+
112
+ response = chain.invoke({"topic": question})
113
+
114
+ return response
115
+
116
+ @click.command()
117
+ @click.option('--pdf_url', required=True, help='URL du fichier PDF à télécharger')
118
+ @click.option('--pdf_path', default='document.pdf', help='Chemin local pour enregistrer le fichier PDF téléchargé')
119
+ @click.option('--embedding_model', required=True, help="Modèle d'embedding à utiliser")
120
+ @click.option('--llm_model', required=True, help='Modèle LLM à utiliser')
121
+ @click.option('--top_k', type=int, default=5, help='Nombre de candidats pour la recherche de similarité')
122
+ def main(pdf_url, pdf_path, embedding_model, llm_model, top_k):
123
+ os.makedirs(CACHE_FOLDER, exist_ok=True)
124
+
125
+ response = requests.get(pdf_url)
126
+ with open(pdf_path, 'wb') as f:
127
+ f.write(response.content)
128
+
129
+ pages = convert_pdf_to_text(pdf_path)
130
+ print(
131
+ """
132
+ Je suis Llama3, de l'équipe DREAMS TEAM, votre assistant QA pour répondre à vos questions liés aux documents 🙂 !
133
+ Déjà pour info, le nombre de pages de vote document est: """, len(pages)
134
+ )
135
+ tokenizer = tiktoken.get_encoding("cl100k_base")
136
+ chunks = split_pages_into_chunks(pages, 128, tokenizer)
137
+
138
+ embedding_model = SentenceTransformer(embedding_model)
139
+
140
+ knowledge_base = vectorize(chunks, embedding_model)
141
+ chunks, embeddings = list(zip(*knowledge_base))
142
+ corpus_embeddings = np.vstack(embeddings)
143
+
144
+ llm_model = ChatOllama(model=llm_model)
145
+ print('📑 Voici le contenu de la première page du document 😎:\n', pages[0])
146
+
147
+ keep_looping = True
148
+ while keep_looping:
149
+ try:
150
+ question = input("Entrez votre question ✍️ | (ou tapez 'exit' pour quitter) ✨: ")
151
+ if question.lower() == 'exit':
152
+ break
153
+
154
+ response = generate_prompt(question, chunks, corpus_embeddings, embedding_model, llm_model, top_k)
155
+ print(response)
156
+
157
+ except KeyboardInterrupt:
158
+ print("\nFin de la session de chat 👋.")
159
+ keep_looping = False
160
+
161
+ if __name__ == "__main__":
162
+ main()
163
+
164
+
165
+ """
166
+ export CACHE_FOLDER="./cache" #"/home/pswia/veileAI/volume_models_cache"
167
+
168
+ python chatpdf.py --pdf_url "https://diomayepresident.org/wp-content/uploads/2024/03/Programme-Diomaye-President.pdf" --embedding_model "Sahajtomar/french_semantic" --llm_model "llama3"
169
+
170
+ python chatpdf.py --pdf_url "https://arxiv.org/pdf/2302.09928" --embedding_model "Sahajtomar/french_semantic" --llm_model "llama3"
171
+
172
+
173
+
174
+
175
+ python chatpdf.py --pdf_url "https://hellofuture.orange.com/app/uploads/2024/05/2024-Orange-white-paper-on-Mobile-Network-Technology-Evolutions-Beyond-2030.pdf" --embedding_model "Sahajtomar/french_semantic" --llm_model "llama3" --top_k 5
176
+
177
+
178
+ """
main.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import click
2
+ import numpy as np
3
+ import os
4
+ import tiktoken
5
+ from typing import List, Tuple
6
+ import requests
7
+ from dotenv import load_dotenv
8
+ from sentence_transformers import SentenceTransformer
9
+ from langchain_community.chat_models import ChatOllama
10
+ from basesrc.strategies import convert_pdf_to_text, split_pages_into_chunks, vectorize
11
+ from prompt_query import generate_prompt
12
+
13
+
14
+
15
+ load_dotenv()
16
+ CACHE_FOLDER = os.environ["CACHE_FOLDER"]
17
+ CACHE_FOLDER = "./cache" # si toutefois (¨_^)
18
+
19
+
20
+ @click.command()
21
+ @click.option('--pdf_url', required=True, help='URL du fichier PDF à télécharger')
22
+ @click.option('--pdf_path', default='document.pdf', help='Chemin local pour enregistrer le fichier PDF téléchargé')
23
+ @click.option('--embedding_model', required=True, help="Modèle d'embedding à utiliser")
24
+ @click.option('--llm_model', required=True, help='Modèle LLM à utiliser')
25
+ @click.option('--top_k', type=int, default=5, help='Nombre de candidats pour la recherche de similarité')
26
+ def main(pdf_url:str, pdf_path:str, embedding_model:str, llm_model:str, top_k:int):
27
+ os.makedirs(CACHE_FOLDER, exist_ok=True)
28
+
29
+ response = requests.get(pdf_url)
30
+ with open(pdf_path, 'wb') as f:
31
+ f.write(response.content)
32
+
33
+ pages = convert_pdf_to_text(pdf_path)
34
+ print(
35
+ """
36
+ Je suis Llama3, de l'équipe DREAMS TEAM, votre assistant QA pour répondre à vos questions liés aux documents 🙂 !
37
+ Déjà pour info, le nombre de pages de vote document est: """, len(pages)
38
+ )
39
+ tokenizer = tiktoken.get_encoding("cl100k_base")
40
+ chunks = split_pages_into_chunks(pages, 128, tokenizer)
41
+
42
+ embedding_model = SentenceTransformer(embedding_model)
43
+
44
+ knowledge_base = vectorize(chunks, embedding_model)
45
+ chunks, embeddings = list(zip(*knowledge_base))
46
+ corpus_embeddings = np.vstack(embeddings)
47
+
48
+ llm_model = ChatOllama(model=llm_model)
49
+ print('📑 Voici le contenu de la première page du document 😎:\n', pages[0])
50
+
51
+ keep_looping = True
52
+ while keep_looping:
53
+ try:
54
+ question = input("Entrez votre question ✍️ | (ou tapez 'exit' pour quitter) ✨: ")
55
+ if question.lower() == 'exit':
56
+ break
57
+
58
+ response = generate_prompt(question, chunks, corpus_embeddings, embedding_model, llm_model, top_k)
59
+ colored_response = f"Llama3 : {response}" # la réponse de Llama
60
+ print(colored_response)
61
+
62
+ except KeyboardInterrupt:
63
+ print("\nFin de la session de chat 👋.")
64
+ keep_looping = False
65
+
66
+ if __name__ == "__main__":
67
+ main()
68
+
69
+
70
+ """
71
+ export CACHE_FOLDER="./cache"
72
+ export PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python3
73
+
74
+ python main.py --pdf_url "https://hellofuture.orange.com/app/uploads/2024/05/2024-Orange-white-paper-on-Mobile-Network-Technology-Evolutions-Beyond-2030.pdf" --embedding_model "Sahajtomar/french_semantic" --llm_model "llama3" --top_k 5
75
+
76
+ """
prompt_query.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import tiktoken
3
+ import numpy as np
4
+ from langchain_core.prompts import ChatPromptTemplate
5
+ from langchain_core.output_parsers import StrOutputParser
6
+ from basesrc.strategies import find_candidates
7
+
8
+
9
+ def generate_prompt(question, chunks, corpus_embeddings, embedding_model, llm_model, top_k):
10
+ query_embedding = embedding_model.encode([question])[0]
11
+ candidates = find_candidates(query_embedding=query_embedding,
12
+ chunks=chunks,
13
+ corpus_embeddings=corpus_embeddings,
14
+ top_k=top_k)
15
+ context = "\n".join(candidates)
16
+ prompt = f"""
17
+ ROLE: Tu es un assistant QA. Tu as été crée par Papa Séga de Orange INNVO.
18
+ Ton rôle est d'aider les utilisateurs à trouver la bonne réponse.
19
+ FONCTIONNEMENT:
20
+ Voici un ensemble de documents dans : {context}.
21
+ Tu dois analyser ces documents pour répondre à la question de l'utilisateur.
22
+ Tu dois répondre aux utilisateurs uniquement en français.
23
+ Tu ne dois pas répondre en anglais.
24
+ Vérifie bien que la question est en rapport avec ces documents :
25
+ -SI OUI ALORS :
26
+ - construit une réponse.
27
+ - tu peux reformuler la réponse
28
+ - SI NON ALORS:
29
+ - Si la question est en rapport avec ton fonctionnement alors:
30
+ - rappelle-lui que tu es juste un modèle de QA et ne réponds pas à la question.
31
+ Si Non Alors:
32
+ - il ne faut jamais répondre à cette question qui a été posée par l'utilisateur !
33
+ RAPPEL :
34
+ Ton objectif est d'aider l'utilisateur à trouver les réponses pertinentes sur ces questions !
35
+
36
+ Question de l'utilisateur : {question}
37
+ SI la question posée est hors contexte,
38
+ - ALORS informe l'utilisateur que la question est hors contexte et que tu ne pourras lui donner une réponse.
39
+
40
+ Attention
41
+ - Tu dois être courtois avec les utilisateurs
42
+ - Tu n'as pas le droit de faire des hallucinations.
43
+
44
+ Réponds à la question en te basant UNIQUEMENT sur le contexte suivant :\n{context}\nQuestion : {question}
45
+
46
+ """
47
+
48
+ chain_prompt = ChatPromptTemplate.from_template(prompt)
49
+ chain = chain_prompt | llm_model | StrOutputParser()
50
+
51
+ response = chain.invoke({"topic": question})
52
+
53
+ return response
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ PyMuPDF
2
+ tiktoken
3
+ sentencepiece
4
+ sentence-transformers
5
+ click
6
+ langchain_community
7
+ langchain_core