Spaces:
Runtime error
Runtime error
File size: 7,404 Bytes
cf3dd65 |
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 |
import os
import click
import fitz # PyMuPDF
import numpy as np
import itertools as it
import operator as op
import requests
import tiktoken
from typing import List, Tuple
from dotenv import load_dotenv
from sentence_transformers import SentenceTransformer
from langchain_community.chat_models import ChatOllama
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
load_dotenv()
protocol_buffers_python_implementation = os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"]
CACHE_FOLDER = os.environ["CACHE_FOLDER"]
CACHE_FOLDER = "./cache" # Chemin du dossier cache
# Fonctions existantes pour convertir les PDFs et diviser les pages en chunks
def convert_pdf_to_text(pdf_data: str) -> List[str]:
document = fitz.open(pdf_data)
accumulator: List[str] = []
for page_num in range(len(document)):
page = document[page_num]
text = page.get_text()
accumulator.append(text)
document.close()
return accumulator
def split_pages_into_chunks(pages: List[str], chunk_size: int, tokenizer) -> List[str]:
page_tokens: List[List[int]] = [tokenizer.encode(page) for page in pages]
document_tokens = list(it.chain(*page_tokens))
nb_tokens = len(document_tokens)
nb_partitions = round(nb_tokens / chunk_size)
accumulator: List[str] = []
for chunck_tokens in np.array_split(document_tokens, nb_partitions):
paragraph = tokenizer.decode(chunck_tokens)
accumulator.append(paragraph)
return accumulator
def vectorize(chunks: List[str], transformer: SentenceTransformer, device: str = 'cpu') -> List[Tuple[str, np.ndarray]]:
embeddings = transformer.encode(
sentences=chunks,
batch_size=32,
device=device
,show_progress_bar=True,
)
return list(zip(chunks, embeddings))
def find_candidates(query_embedding: np.ndarray, chunks: List[str], corpus_embeddings: np.ndarray, top_k: int = 5) -> List[str]:
dot_product = query_embedding @ corpus_embeddings.T
norms = np.linalg.norm(query_embedding) * np.linalg.norm(corpus_embeddings, axis=1)
weighted_scores = dot_product / norms # cosine similarity
zipped_chunks_scores = list(zip(chunks, weighted_scores))
sorted_chunks_scores = sorted(zipped_chunks_scores, key=op.itemgetter(1), reverse=True)
selected_candidates = sorted_chunks_scores[:top_k]
return list(map(op.itemgetter(0), selected_candidates))
def generate_prompt(question, chunks, corpus_embeddings, embedding_model, llm_model, top_k, document_type: str = "ce pdf"):
query_embedding = embedding_model.encode([question])[0]
candidates = find_candidates(query_embedding=query_embedding,
chunks=chunks,
corpus_embeddings=corpus_embeddings,
top_k=top_k)
context = "\n".join(candidates)
prompt = f"""
ROLE: Tu es un assistant QA. Tu as été crée par Papa Séga de Orange INNVO.
Ton rôle est d'aider les utilisateurs à trouver la bonne réponse.
FONCTIONNEMENT:
Voici un ensemble de documents dans {document_type}: {context}.
Tu dois analyser ces documents pour répondre à la question de l'utilisateur.
Tu dois répondre aux utilisateurs uniquement en français.
Tu ne dois pas répondre en anglais.
Vérifie bien que la question est en rapport avec ces documents :
-SI OUI ALORS :
- construit une réponse.
- tu peux reformuler la réponse
- SI NON ALORS:
- Si la question est en rapport avec ton fonctionnement alors:
- rappelle-lui que tu es juste un modèle de QA et ne réponds pas à la question.
Si Non Alors:
- il ne faut jamais répondre à cette question qui a été posée par l'utilisateur !
RAPPEL :
Ton objectif est d'aider l'utilisateur à trouver les réponses pertinentes sur ces questions !
Question de l'utilisateur : {question}
SI la question posée est hors contexte,
- ALORS informe l'utilisateur que la question est hors contexte et que tu ne pourras lui donner une réponse.
Attention
- Tu dois être courtois avec les utilisateurs
- Tu n'as pas le droit de faire des hallucinations.
Réponds à la question en te basant UNIQUEMENT sur le contexte suivant :\n{context}\nQuestion : {question}
"""
chain_prompt = ChatPromptTemplate.from_template(prompt)
chain = chain_prompt | llm_model | StrOutputParser()
response = chain.invoke({"topic": question})
return response
@click.command()
@click.option('--pdf_url', required=True, help='URL du fichier PDF à télécharger')
@click.option('--pdf_path', default='document.pdf', help='Chemin local pour enregistrer le fichier PDF téléchargé')
@click.option('--embedding_model', required=True, help="Modèle d'embedding à utiliser")
@click.option('--llm_model', required=True, help='Modèle LLM à utiliser')
@click.option('--top_k', type=int, default=5, help='Nombre de candidats pour la recherche de similarité')
def main(pdf_url, pdf_path, embedding_model, llm_model, top_k):
os.makedirs(CACHE_FOLDER, exist_ok=True)
response = requests.get(pdf_url)
with open(pdf_path, 'wb') as f:
f.write(response.content)
pages = convert_pdf_to_text(pdf_path)
print(
"""
Je suis Llama3, de l'équipe DREAMS TEAM, votre assistant QA pour répondre à vos questions liés aux documents 🙂 !
Déjà pour info, le nombre de pages de vote document est: """, len(pages)
)
tokenizer = tiktoken.get_encoding("cl100k_base")
chunks = split_pages_into_chunks(pages, 128, tokenizer)
embedding_model = SentenceTransformer(embedding_model)
knowledge_base = vectorize(chunks, embedding_model)
chunks, embeddings = list(zip(*knowledge_base))
corpus_embeddings = np.vstack(embeddings)
llm_model = ChatOllama(model=llm_model)
print('📑 Voici le contenu de la première page du document 😎:\n', pages[0])
keep_looping = True
while keep_looping:
try:
question = input("Entrez votre question ✍️ | (ou tapez 'exit' pour quitter) ✨: ")
if question.lower() == 'exit':
break
response = generate_prompt(question, chunks, corpus_embeddings, embedding_model, llm_model, top_k)
print(response)
except KeyboardInterrupt:
print("\nFin de la session de chat 👋.")
keep_looping = False
if __name__ == "__main__":
main()
"""
export CACHE_FOLDER="./cache" #"/home/pswia/veileAI/volume_models_cache"
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"
python chatpdf.py --pdf_url "https://arxiv.org/pdf/2302.09928" --embedding_model "Sahajtomar/french_semantic" --llm_model "llama3"
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
""" |