chatpdf_ollama_llama3 / chatpdf.py
papasega's picture
add files
cf3dd65
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
"""