Spaces:
Runtime error
Runtime error
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 | |
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 | |
""" |