Add initial project structure with chatbot implementation and requirements

This commit is contained in:
sepehr 2025-03-07 22:33:27 +01:00
parent 2023059e3d
commit 229ba53246
9 changed files with 3275 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
apigit.txt

View File

@ -0,0 +1,53 @@
# RAG Chatbot
This repository contains a Retrieval Augmented Generation (RAG) chatbot implementation that can process data and answer questions based on the provided context.
## Requirements
### Python Version
⚠️ **Important**: This project requires Python version lower than 3.12. Python 3.11 works correctly.
## Installation
1. Clone this repository:
```bash
git clone <repository-url>
cd <repository-name>
```
2. Install the required dependencies:
```bash
pip install -r requirement.txt
```
## Usage
### Command Line Interface
Run the chatbot in terminal mode:
```bash
python cli.py
```
### Web Interface
Launch the Gradio web interface:
```bash
python gradio_chatbot.py
```
### RAG Implementation
If you want to import the RAG functionality in your own Python script:
```python
from rag_chatbot import RagChatbot
chatbot = RagChatbot()
response = chatbot.query("your question here")
```
## PDF Processing
The repository includes a Jupyter notebook [`final_pdf.ipynb`](final_pdf.ipynb) for processing PDF documents as knowledge sources for the chatbot.
## Project Structure
- [`cli.py`](cli.py): Command-line interface implementation
- [`gradio_chatbot.py`](gradio_chatbot.py): Gradio web interface
- [`rag_chatbot.py`](rag_chatbot.py): Core RAG implementation
- [`final_pdf.ipynb`](final_pdf.ipynb): Jupyter notebook for PDF processing

Binary file not shown.

View File

@ -0,0 +1,11 @@
{
"folders": [
{
"path": "."
},
{
"path": "../Rag_Modeling/document"
}
],
"settings": {}
}

70
cli.py Normal file
View File

@ -0,0 +1,70 @@
# cli.py
from rag_chatbot import MultimodalRAGChatbot
def main():
# Initialiser le chatbot
chatbot = MultimodalRAGChatbot(
qdrant_url="http://localhost:6333",
qdrant_collection_name="my_documents",
ollama_model="llama3.2"
)
print("Chatbot RAG Multimodal")
print("Tapez 'exit' pour quitter ou 'clear' pour effacer l'historique")
while True:
# Récupérer la question
query = input("\nVotre question: ")
# Quitter si demandé
if query.lower() in ["exit", "quit", "q"]:
break
# Effacer l'historique si demandé
if query.lower() == "clear":
chatbot.clear_history()
print("Historique effacé")
continue
# Demander si mode streaming
stream_mode = input("Mode streaming? (y/n): ").lower() == 'y'
# Traitement de la requête
result = chatbot.chat(query, stream=stream_mode)
# Si pas de streaming, afficher la réponse texte
if not stream_mode:
print("\n" + "="*50)
print("Réponse:")
print(result["response"])
print("="*50)
# Afficher les informations sur les sources
print("\nSources trouvées:")
print(f"- {len(result['texts'])} textes")
print(f"- {len(result['images'])} images")
print(f"- {len(result['tables'])} tableaux")
# Afficher les images si demandé
if result["images"]:
show_images = input("\nAfficher les images? (y/n): ").lower() == 'y'
if show_images:
for i, img in enumerate(result["images"]):
print(f"\nImage {i+1}: {img['caption']} (Source: {img['source']}, Page: {img['page']})")
print(f"Description: {img['description']}")
chatbot.display_image(img["image_data"], img["caption"])
# Afficher les tableaux si demandé
if result["tables"]:
show_tables = input("\nAfficher les tableaux? (y/n): ").lower() == 'y'
if show_tables:
for i, table in enumerate(result["tables"]):
print(f"\nTableau {i+1}: {table['caption']} (Source: {table['source']}, Page: {table['page']})")
print(f"Description: {table['description']}")
print("\nContenu:")
print("```")
print(chatbot.format_table(table["table_data"]))
print("```")
if __name__ == "__main__":
main()

2295
final_pdf.ipynb Normal file

File diff suppressed because one or more lines are too long

514
gradio_chatbot.py Normal file
View File

@ -0,0 +1,514 @@
import gradio as gr
import base64
from io import BytesIO
from PIL import Image
import pandas as pd
import traceback
import threading
import queue
import time
from rag_chatbot import MultimodalRAGChatbot
from langchain.callbacks.base import BaseCallbackHandler
# Handler personnalisé pour capturer les tokens en streaming
class GradioStreamingHandler(BaseCallbackHandler):
def __init__(self):
self.tokens_queue = queue.Queue()
self.full_text = ""
def on_llm_new_token(self, token, **kwargs):
self.tokens_queue.put(token)
self.full_text += token
# Fonction pour créer un objet Image à partir des données base64
def base64_to_image(base64_data):
"""Convertit une image base64 en objet Image pour l'affichage direct"""
try:
if not base64_data:
return None
image_bytes = base64.b64decode(base64_data)
image = Image.open(BytesIO(image_bytes))
return image
except Exception as e:
print(f"Erreur lors de la conversion d'image: {e}")
return None
# Configuration pour initialiser le chatbot
qdrant_url = "http://localhost:6333"
qdrant_collection_name = "my_documents"
embedding_model = "mxbai-embed-large"
ollama_url = "http://127.0.0.1:11434"
default_model = "llama3.1"
# Liste des modèles disponibles
AVAILABLE_MODELS = ["llama3.1", "llama3.2", "deepseek-r1:14b"]
# Initialiser le chatbot RAG avec le modèle par défaut
rag_bot = MultimodalRAGChatbot(
qdrant_url=qdrant_url,
qdrant_collection_name=qdrant_collection_name,
ollama_model=default_model,
embedding_model=embedding_model,
ollama_url=ollama_url
)
print(f"Chatbot initialisé avec modèle: {default_model}")
# Variables globales pour stocker les images et tableaux de la dernière requête
current_images = []
current_tables = []
# Fonction pour changer de modèle
def change_model(model_name):
global rag_bot
try:
# Réinitialiser le chatbot avec le nouveau modèle
rag_bot = MultimodalRAGChatbot(
qdrant_url=qdrant_url,
qdrant_collection_name=qdrant_collection_name,
ollama_model=model_name,
embedding_model=embedding_model,
ollama_url=ollama_url
)
print(f"Modèle changé pour: {model_name}")
return f"✅ Modèle changé pour: {model_name}"
except Exception as e:
print(f"Erreur lors du changement de modèle: {e}")
return f"❌ Erreur: {str(e)}"
# Fonction de traitement des requêtes avec support du streaming dans Gradio
def process_query(message, history, streaming, show_sources, max_images):
global current_images, current_tables
if not message.strip():
return history, "", None, None
current_images = []
current_tables = []
try:
if streaming:
# Version avec streaming dans Gradio
history = history + [(message, "")]
# 1. Récupérer les documents pertinents
docs = rag_bot._retrieve_relevant_documents(message)
# 2. Préparer le contexte et l'historique
context = rag_bot._format_documents(docs)
history_text = rag_bot._format_chat_history()
# 3. Préparer le prompt
from langchain.prompts import ChatPromptTemplate
prompt_template = ChatPromptTemplate.from_template("""
Tu es un assistant documentaire spécialisé qui utilise toutes les informations disponibles dans le contexte fourni.
Instructions spécifiques:
1. Pour chaque image mentionnée dans le contexte, inclue TOUJOURS dans ta réponse:
- La légende/caption exacte de l'image
- La source et le numéro de page
- Une description brève de ce qu'elle montre
2. Pour chaque tableau mentionné dans le contexte, inclue TOUJOURS:
- Le titre/caption exact du tableau
- La source et le numéro de page
- Ce que contient et signifie le tableau
3. Lorsque tu cites des équations mathématiques:
- Utilise la syntaxe LaTeX exacte comme dans le document ($...$ ou $$...$$)
- Reproduis-les fidèlement sans modification
4. IMPORTANT: Ne pas inventer d'informations - si une donnée n'est pas explicitement fournie dans le contexte,
indique clairement "Cette information n'est pas disponible dans les documents fournis."
5. Cite précisément les sources pour chaque élément d'information (format: [Source, Page]).
Historique de conversation:
{chat_history}
Contexte (à utiliser pour répondre):
{context}
Question: {question}
Réponds de façon structurée et précise en intégrant activement les images, tableaux et équations disponibles dans le contexte.
""")
# 4. Formater les messages pour le LLM
messages = prompt_template.format_messages(
chat_history=history_text,
context=context,
question=message
)
# 5. Créer un handler de streaming personnalisé
from langchain_ollama import ChatOllama
handler = GradioStreamingHandler()
# 6. Créer un modèle LLM avec notre handler
streaming_llm = ChatOllama(
model=rag_bot.llm.model,
base_url=rag_bot.llm.base_url,
streaming=True,
callbacks=[handler]
)
# 7. Lancer la génération dans un thread pour ne pas bloquer l'UI
def generate_response():
streaming_llm.invoke(messages)
thread = threading.Thread(target=generate_response)
thread.start()
# 8. Récupérer les tokens et mettre à jour l'interface
partial_response = ""
# Attendre les tokens avec un timeout
while thread.is_alive() or not handler.tokens_queue.empty():
try:
token = handler.tokens_queue.get(timeout=0.05)
partial_response += token
history[-1] = (message, partial_response)
yield history, "", None, None
except queue.Empty:
continue
# 9. Thread terminé, mettre à jour l'historique de conversation du chatbot
rag_bot.chat_history.append({"role": "user", "content": message})
rag_bot.chat_history.append({"role": "assistant", "content": partial_response})
# 10. Récupérer les sources, images, tableaux
texts, images, tables = rag_bot._process_documents(docs)
# Préparer les informations sur les sources
source_info = ""
if texts:
source_info += f"📚 {len(texts)} textes • "
if images:
source_info += f"🖼️ {len(images)} images • "
if tables:
source_info += f"📊 {len(tables)} tableaux"
if source_info:
source_info = "Sources trouvées: " + source_info
# 11. Traiter les images
if show_sources and images:
images = images[:max_images]
for img in images:
img_data = img.get("image_data")
if img_data:
image = base64_to_image(img_data)
if image:
current_images.append({
"image": image,
"caption": img.get("caption", ""),
"source": img.get("source", ""),
"page": img.get("page", ""),
"description": img.get("description", "")
})
# 12. Traiter les tableaux
if show_sources and tables:
for table in tables:
current_tables.append({
"data": rag_bot.format_table(table.get("table_data", "")),
"caption": table.get("caption", ""),
"source": table.get("source", ""),
"page": table.get("page", ""),
"description": table.get("description", "")
})
# 13. Retourner les résultats finaux
yield history, source_info, display_images(), display_tables()
else:
# Version sans streaming (code existant)
result = rag_bot.chat(message, stream=False)
history = history + [(message, result["response"])]
# Préparer les informations sur les sources
source_info = ""
if "texts" in result:
source_info += f"📚 {len(result['texts'])} textes • "
if "images" in result:
source_info += f"🖼️ {len(result['images'])} images • "
if "tables" in result:
source_info += f"📊 {len(result['tables'])} tableaux"
if source_info:
source_info = "Sources trouvées: " + source_info
# Traiter les images et tableaux
if show_sources and "images" in result and result["images"]:
images = result["images"][:max_images]
for img in images:
img_data = img.get("image_data")
if img_data:
image = base64_to_image(img_data)
if image:
current_images.append({
"image": image,
"caption": img.get("caption", ""),
"source": img.get("source", ""),
"page": img.get("page", ""),
"description": img.get("description", "")
})
if show_sources and "tables" in result and result["tables"]:
tables = result["tables"]
for table in tables:
current_tables.append({
"data": rag_bot.format_table(table.get("table_data", "")),
"caption": table.get("caption", ""),
"source": table.get("source", ""),
"page": table.get("page", ""),
"description": table.get("description", "")
})
return history, source_info, display_images(), display_tables()
except Exception as e:
error_msg = f"Une erreur est survenue: {str(e)}"
traceback_text = traceback.format_exc()
print(error_msg)
print(traceback_text)
history = history + [(message, error_msg)]
return history, "Erreur lors du traitement de la requête", None, None
# Fonctions pour afficher les images et tableaux
def display_images():
if not current_images:
return None
gallery = []
for img_data in current_images:
image = img_data["image"]
if image:
caption = f"{img_data['caption']} (Source: {img_data['source']}, Page: {img_data['page']})"
gallery.append((image, caption))
return gallery if gallery else None
def display_tables():
if not current_tables:
return None
html = ""
for table in current_tables:
html += f"""
<div style="margin-bottom: 20px; border: 1px solid #ddd; padding: 15px; border-radius: 8px;">
<h3>{table['caption']}</h3>
<p style="color:#666; font-size:0.9em;">Source: {table['source']}, Page: {table['page']}</p>
<p><strong>Description:</strong> {table['description']}</p>
<div style="background-color:#f5f5f5; padding:10px; border-radius:5px; overflow:auto;">
<pre>{table['data']}</pre>
</div>
</div>
"""
return html if html else None
# Fonction pour réinitialiser l'historique
def reset_conversation():
global current_images, current_tables
current_images = []
current_tables = []
rag_bot.clear_history()
return [], "", None, None
# Interface Gradio
with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue")) as demo:
gr.Markdown("# 📚 Assistant documentaire intelligent")
with gr.Row():
with gr.Column(scale=2):
chat_interface = gr.Chatbot(
height=600,
show_label=False,
layout="bubble",
elem_id="chatbot"
)
with gr.Row():
msg = gr.Textbox(
show_label=False,
placeholder="Posez votre question...",
container=False,
scale=4
)
submit_btn = gr.Button("Envoyer", variant="primary", scale=1)
clear_btn = gr.Button("Effacer la conversation")
source_info = gr.Markdown("", elem_id="sources_info")
with gr.Column(scale=1):
with gr.Accordion("Options", open=True):
# Sélecteur de modèle
model_selector = gr.Dropdown(
choices=AVAILABLE_MODELS,
value=default_model,
label="Modèle Ollama",
info="Choisir le modèle de language à utiliser"
)
model_status = gr.Markdown(f"Modèle actuel: **{default_model}**")
streaming = gr.Checkbox(
label="Mode streaming",
value=True,
info="Voir les réponses s'afficher progressivement"
)
show_sources = gr.Checkbox(label="Afficher les sources", value=True)
max_images = gr.Slider(
minimum=1,
maximum=10,
value=3,
step=1,
label="Nombre max d'images"
)
gr.Markdown("---")
gr.Markdown("### 🖼️ Images pertinentes")
image_gallery = gr.Gallery(
label="Images pertinentes",
show_label=False,
columns=2,
height=300,
object_fit="contain"
)
gr.Markdown("### 📊 Tableaux")
tables_display = gr.HTML()
# Connecter le changement de modèle
model_selector.change(
fn=change_model,
inputs=model_selector,
outputs=model_status
)
# Configuration des actions
msg.submit(
process_query,
inputs=[msg, chat_interface, streaming, show_sources, max_images],
outputs=[chat_interface, source_info, image_gallery, tables_display]
).then(lambda: "", outputs=msg)
submit_btn.click(
process_query,
inputs=[msg, chat_interface, streaming, show_sources, max_images],
outputs=[chat_interface, source_info, image_gallery, tables_display]
).then(lambda: "", outputs=msg)
clear_btn.click(
reset_conversation,
outputs=[chat_interface, source_info, image_gallery, tables_display]
)
# Support amélioré pour les équations mathématiques avec KaTeX
gr.Markdown("""
<style>
.gradio-container {max-width: 1200px !important}
#chatbot {height: 600px; overflow-y: auto;}
#sources_info {margin-top: 10px; color: #666;}
/* Style pour les équations */
.katex { font-size: 1.1em !important; }
.math-inline { background: #f8f9fa; padding: 2px 5px; border-radius: 4px; }
.math-display { background: #f8f9fa; margin: 10px 0; padding: 10px; border-radius: 5px; overflow-x: auto; text-align: center; }
</style>
<!-- Chargement de KaTeX -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.css">
<script src="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/contrib/auto-render.min.js"></script>
<script>
// Fonction pour rendre les équations avec KaTeX
function renderMathInElement(element) {
if (!window.renderMathInElement) return;
window.renderMathInElement(element, {
delimiters: [
{left: '$$', right: '$$', display: true},
{left: '$', right: '$', display: false},
{left: '\\(', right: '\\)', display: false},
{left: '\\[', right: '\\]', display: true}
],
throwOnError: false,
trust: true,
strict: false
});
}
// Fonction pour remplacer les underscores échappés qui posent problème
function fixUnderscores(element) {
const messages = element.querySelectorAll('.message');
messages.forEach(msg => {
const text = msg.innerHTML;
// Remplacer les patterns comme u_(i) par u_{i} pour une meilleure compatibilité LaTeX
const fixed = text.replace(/([a-zA-Z])_\(([^)]+)\)/g, '$1_{$2}');
// Remplacer également les & qui peuvent causer des problèmes
const cleanAmpersand = fixed.replace(/&amp;/g, '');
if (text !== cleanAmpersand) {
msg.innerHTML = cleanAmpersand;
}
});
}
// Observer les changements dans le chat
function setupMathObserver() {
const chatElement = document.getElementById('chatbot');
if (!chatElement) {
setTimeout(setupMathObserver, 500);
return;
}
const observer = new MutationObserver((mutations) => {
mutations.forEach(mutation => {
if (mutation.type === 'childList' || mutation.type === 'subtree') {
const messages = chatElement.querySelectorAll('.message');
if (messages.length > 0) {
// D'abord corriger les underscores problématiques
fixUnderscores(chatElement);
// Puis rendre les équations
messages.forEach(msg => {
renderMathInElement(msg);
});
}
}
});
});
observer.observe(chatElement, {
childList: true,
subtree: true,
characterData: true
});
// Rendre les équations déjà présentes
renderMathInElement(document);
}
// Initialisation lorsque la page est chargée
document.addEventListener('DOMContentLoaded', function() {
// Attendre que KaTeX soit chargé
if (window.renderMathInElement) {
setupMathObserver();
} else {
// Attendre le chargement de KaTeX
document.querySelector('script[src*="auto-render.min.js"]').onload = setupMathObserver;
}
});
</script>
""")
if __name__ == "__main__":
demo.queue()
demo.launch(share=False, inbrowser=True)

286
rag_chatbot.py Normal file
View File

@ -0,0 +1,286 @@
from typing import Dict, List, Any, Optional
import base64
from io import BytesIO
import pandas as pd
from PIL import Image
# Remplacer les imports dépréciés par les nouveaux packages
from langchain_qdrant import QdrantVectorStore
from langchain_ollama import OllamaEmbeddings, ChatOllama
from langchain.prompts import ChatPromptTemplate
from langchain.schema import Document
from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
from qdrant_client import QdrantClient
class MultimodalRAGChatbot:
"""
Chatbot RAG multimodal qui utilise Qdrant pour stocker les documents
"""
def __init__(
self,
qdrant_url: str = "http://localhost:6333",
qdrant_collection_name: str = "my_documents",
ollama_model: str = "llama3.1",
embedding_model: str = "mxbai-embed-large",
ollama_url: str = "http://localhost:11434" # Ajout de ce paramètre
):
"""
Initialise le chatbot RAG avec Qdrant
"""
# Initialiser le modèle d'embedding
self.embeddings = OllamaEmbeddings(
model=embedding_model,
base_url=ollama_url # Utilisation de l'URL d'Ollama
)
# Créer le client Qdrant
self.client = QdrantClient(url=qdrant_url)
# Se connecter à la collection existante
self.vector_store = QdrantVectorStore(
client=self.client,
collection_name=qdrant_collection_name,
embedding=self.embeddings
)
# Initialiser le retriever
self.retriever = self.vector_store.as_retriever(
search_type="similarity",
search_kwargs={"k": 5}
)
# Initialiser les modèles LLM
self.llm = ChatOllama(
model=ollama_model,
base_url=ollama_url # Utilisation de l'URL d'Ollama
)
self.streaming_llm = ChatOllama(
model=ollama_model,
base_url=ollama_url, # Utilisation de l'URL d'Ollama
streaming=True,
callbacks=[StreamingStdOutCallbackHandler()]
)
# Historique des conversations
self.chat_history = []
print(f"Chatbot initialisé avec modèle: {ollama_model}")
print(f"Utilisant embeddings: {embedding_model}")
print(f"Connecté à Qdrant: {qdrant_url}, collection: {qdrant_collection_name}")
print(f"Ollama URL: {ollama_url}")
def chat(self, query: str, stream: bool = False):
"""
Traite une question de l'utilisateur et retourne une réponse
"""
# 1. Récupérer les documents pertinents
docs = self._retrieve_relevant_documents(query)
# 2. Préparer le contexte à partir des documents
context = self._format_documents(docs)
# 3. Préparer l'historique des conversations
history_text = self._format_chat_history()
# 4. Créer le prompt
prompt_template = ChatPromptTemplate.from_template("""
Tu es un assistant intelligent qui répond aux questions en utilisant uniquement
les informations fournies dans le contexte. Si tu ne trouves pas l'information
dans le contexte, dis simplement que tu ne sais pas. Lorsque tu mentionnes une
image ou un tableau, décris brièvement son contenu en te basant sur les
descriptions fournies.
Historique de conversation:
{chat_history}
Contexte:
{context}
Question de l'utilisateur: {question}
Réponds de façon concise et précise en citant les sources pertinentes.
""")
# 5. Générer la réponse
llm = self.streaming_llm if stream else self.llm
if stream:
print("\nRéponse:")
# Formater les messages pour le LLM
messages = prompt_template.format_messages(
chat_history=history_text,
context=context,
question=query
)
# Appeler le LLM
response = llm.invoke(messages)
answer = response.content
# 6. Mettre à jour l'historique des conversations
self.chat_history.append({"role": "user", "content": query})
self.chat_history.append({"role": "assistant", "content": answer})
# 7. Traiter les documents pour la sortie
texts, images, tables = self._process_documents(docs)
# 8. Préparer la réponse
result = {
"response": answer,
"texts": texts,
"images": images,
"tables": tables
}
return result
def _retrieve_relevant_documents(self, query: str, k: int = 5) -> List[Document]:
"""
Récupère les documents pertinents de la base Qdrant
"""
return self.vector_store.similarity_search(query, k=k)
def _format_documents(self, docs: List[Document]) -> str:
"""
Formate les documents pour le contexte
"""
formatted_docs = []
for i, doc in enumerate(docs):
metadata = doc.metadata
# Déterminer le type de document et le formater en conséquence
if "image_base64" in metadata:
# Image
formatted_docs.append(
f"[IMAGE {i+1}]\n"
f"Source: {metadata.get('source', 'Inconnue')}\n"
f"Page: {metadata.get('page_number', '')}\n"
f"Caption: {metadata.get('caption', '')}\n"
f"Description: {doc.page_content}\n"
)
elif "table_content" in metadata:
# Tableau
formatted_docs.append(
f"[TABLEAU {i+1}]\n"
f"Source: {metadata.get('source', 'Inconnue')}\n"
f"Page: {metadata.get('page_number', '')}\n"
f"Caption: {metadata.get('caption', '')}\n"
f"Description: {doc.page_content}\n"
)
else:
# Texte
formatted_docs.append(
f"[TEXTE {i+1}]\n"
f"Source: {metadata.get('source', 'Inconnue')}\n"
f"Page: {metadata.get('page_number', '')}\n"
f"{doc.page_content}\n"
)
return "\n".join(formatted_docs)
def _format_chat_history(self) -> str:
"""
Formate l'historique des conversations
"""
if not self.chat_history:
return "Pas d'historique de conversation."
formatted_history = []
for message in self.chat_history:
role = "Utilisateur" if message["role"] == "user" else "Assistant"
formatted_history.append(f"{role}: {message['content']}")
return "\n".join(formatted_history)
def _process_documents(self, docs: List[Document]):
"""
Traite les documents pour séparer textes, images et tableaux
"""
texts = []
images = []
tables = []
for doc in docs:
metadata = doc.metadata
# Déterminer le type de document
if "image_base64" in metadata:
# C'est une image
images.append({
"image_data": metadata.get("image_base64", ""),
"description": doc.page_content,
"caption": metadata.get("caption", ""),
"source": metadata.get("source", ""),
"page": metadata.get("page_number", "")
})
elif "table_content" in metadata:
# C'est un tableau
tables.append({
"table_data": metadata.get("table_content", ""),
"description": doc.page_content,
"caption": metadata.get("caption", ""),
"source": metadata.get("source", ""),
"page": metadata.get("page_number", "")
})
else:
# C'est du texte
texts.append({
"content": doc.page_content,
"source": metadata.get("source", ""),
"page": metadata.get("page_number", "")
})
return texts, images, tables
def clear_history(self):
"""
Efface l'historique de conversation
"""
self.chat_history = []
def display_image(self, image_data: str, caption: str = ""):
"""
Affiche une image à partir de sa représentation base64
"""
try:
# Décodage de l'image base64
image_bytes = base64.b64decode(image_data)
image = Image.open(BytesIO(image_bytes))
# Affichage selon l'environnement
try:
from IPython.display import display
print(f"Caption: {caption}")
display(image)
except ImportError:
image.show()
return True
except Exception as e:
print(f"Erreur lors de l'affichage de l'image: {e}")
return False
def format_table(self, table_data: str) -> str:
"""
Formate les données d'un tableau pour l'affichage
"""
try:
# Si format markdown
if isinstance(table_data, str) and table_data.strip().startswith("|"):
return table_data
# Essayer de parser comme JSON
import json
try:
data = json.loads(table_data)
df = pd.DataFrame(data)
return df.to_string(index=False)
except:
# Si échec, retourner les données brutes
return str(table_data)
except Exception as e:
return f"Erreur lors du formatage du tableau: {e}\n{table_data}"

45
requirement.txt Normal file
View File

@ -0,0 +1,45 @@
# Core LangChain packages
langchain>=0.1.0
langchain-community>=0.0.1
langchain-ollama>=0.0.1
langchain-qdrant>=0.0.1
# Vector database
qdrant-client>=1.6.0
# LLM interface
ollama>=0.1.0
# Document processing with specific versions
pytesseract>=0.3.10
unstructured==0.10.30
pdfminer.six==20221105
pdf2image>=1.16.0
pypdf>=3.15.0
# OCR and image processing
pillow_heif>=0.13.0
Pillow>=10.0.0
# Data processing and visualization
pandas>=2.0.0
# UI and interface
gradio>=4.0.0
# Other utilities
ipython>=8.0.0
uuid>=1.30
onnx
pdf2image
pdfminer.six
pikepdf
pi_heif
pypdf
google-cloud-vision
effdet
# Do not move to constraints.in, otherwise unstructured-inference will not be upgraded
# when unstructured library is.
unstructured-inference>=0.8.7
unstructured.pytesseract>=0.3.12