Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 85 additions & 6 deletions main.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,92 @@
from src.retrievalFunction import retrieve
from src.implementVectorDB import LANGUAGE_MODEL, create_vector_db_from_dataset, VECTOR_DB, reset_vector_db
from src.loadingDataset import load_dataset
from src.loadingDataset import load_multiple_files
import ollama
import os
import easygui as eg

# Réinitialiser au cas où
def select_knowledge_sources():
"""
Menu texte pour choisir le TYPE de source, puis utilise easygui pour sélectionner.
"""
print('\n' + '='*60)
print('Configuration de la base de connaissance')
print('='*60)
print('\nOptions:')
print('1. Sélectionner des fichiers')
print('2. Sélectionner des dossiers')
print('3. Continuer sans charger (utiliser BDD existante)')
print('4. Utiliser les sources par défaut')

while True:
choice = input('\nChoisissez une option (1-4): ').strip()
if choice in ['1', '2', '3', '4']:
break
print("✗ Entrée invalide, veuillez entrer 1, 2, 3 ou 4")

sources = []

if choice == '1':
# Sélectionner plusieurs fichiers via easygui
files = eg.fileopenbox(
msg='Sélectionnez les fichiers à charger',
title='Sélection de fichiers',
multiple=True,
filetypes=["*.txt", "*.pdf", "*.py", "*.json", "*.yaml", "*.*"]
)
if files:
sources = files if isinstance(files, list) else [files]
print(f"\n✓ {len(sources)} fichier(s) sélectionné(s)")
for f in sources:
print(f" - {os.path.basename(f)}")
else:
print("\n✗ Aucun fichier sélectionné")

elif choice == '2':
# Sélectionner plusieurs dossiers via easygui
print("\nVous pouvez sélectionner plusieurs dossiers")
count = 0
while True:
folder = eg.diropenbox(
msg='Sélectionnez un dossier (Annuler pour terminer)',
title='Sélection de dossier'
)
if not folder:
break
sources.append(folder)
count += 1
print(f"✓ Dossier {count} ajouté: {os.path.basename(folder)}")

if sources:
print(f"\n✓ Total: {len(sources)} dossier(s) sélectionné(s)")
else:
print("\n✗ Aucun dossier sélectionné")

elif choice == '3':
# Continuer sans charger
print("✓ Mode sans chargement - Utilisation de la BDD existante")
sources = None

else: # choice == '4'
sources = ['tmp/cat-facts.txt']
print("✓ Sources par défaut chargées")

return sources

# Réinitialiser la base de vecteurs
reset_vector_db()
# Load dataset and create vector database
dataset = load_dataset('tmp/cat-facts.txt')
create_vector_db_from_dataset(dataset)

# Sélectionner les sources de connaissance
sources = select_knowledge_sources()

# Charger et créer la base de données vectorielle si sources sont spécifiées
if sources is not None:
print('\nChargement des sources...')
chunks = load_multiple_files(sources)
create_vector_db_from_dataset(chunks)
else:
print('\n⚠️ Aucune source chargée - Utilisation de la BDD existante')
print('(Assurez-vous que la BDD contient déjà des données)')

print('\n' + '='*50)
print('Chatbot is ready! Type "exit" to quit.')
Expand All @@ -16,7 +95,7 @@
# Main chatbot loop
while True:
# Get user input
input_query = input('Ask me a question: ')
input_query = input('You: ')

# Check if user wants to exit
if input_query.lower() == 'exit':
Expand Down
11 changes: 5 additions & 6 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,26 @@ annotated-types==0.7.0
anyio==4.12.1
certifi==2026.1.4
charset-normalizer==3.4.4
exceptiongroup==1.3.1
easygui==0.98.3
h11==0.16.0
httpcore==1.0.9
httpx==0.28.1
idna==3.11
init==0.1.0
jsonpatch==1.33
jsonpointer==3.0.0
langchain-core==0.3.83
langchain-text-splitters==0.3.11
langsmith==0.4.37
langchain-core==1.2.7
langchain-text-splitters==1.1.0
langsmith==0.6.2
ollama==0.6.1
orjson==3.11.5
packaging==25.0
pydantic==2.12.5
pydantic_core==2.41.5
pypdf==6.6.0
PyYAML==6.0.3
requests==2.32.5
requests-toolbelt==1.0.0
tenacity==9.1.2
terminal==0.4.0
typing-inspection==0.4.2
typing_extensions==4.15.0
urllib3==2.6.3
Expand Down
41 changes: 11 additions & 30 deletions src/implementVectorDB.py
Original file line number Diff line number Diff line change
@@ -1,46 +1,27 @@
import ollama
from langchain_text_splitters import RecursiveCharacterTextSplitter
from src.loadingDataset import load_dataset
from src.loadingDataset import load_multiple_files

EMBEDDING_MODEL = 'hf.co/CompendiumLabs/bge-base-en-v1.5-gguf'
LANGUAGE_MODEL = 'hf.co/bartowski/Llama-3.2-1B-Instruct-GGUF'

VECTOR_DB = []

def add_chunk_to_database(chunk):
embedding = ollama.embed(model=EMBEDDING_MODEL, input=chunk)['embeddings'][0]

VECTOR_DB.append((chunk, embedding))

#Configuration du découpage pour du texte
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # Taille maximale de chaque morceau (en caractères)
chunk_overlap=50, # Chevauchement pour garder le contexte
length_function=len,
is_separator_regex=False,
)

#Configuration du découpage pour du code source
text_splitter_code = RecursiveCharacterTextSplitter(
chunk_size=500, # Taille maximale de chaque morceau (en caractères)
chunk_overlap=50, # Chevauchement pour garder le contexte
length_function=len,
is_separator_regex=False,
separators=["class", "def", "\n\n", "\n", " ", ""],
)

#Fonction pour créer la base de données vectorielle à partir du dataset chargé
def create_vector_db_from_dataset(dataset):
def create_vector_db_from_dataset(chunks):
"""
Ajoute les chunks déjà splitté à la base de données vectorielle.
Les chunks doivent être pré-splitté par load_multiple_files()
"""
chunk_count = 0
for data in dataset:
if data.strip().endswith(('.py', '.js', '.java', '.cpp', '.cs', '.rb', '.go')):
chunks = text_splitter_code.split_text(data)
else:
chunks = text_splitter.split_text(data)

for chunk in chunks:
add_chunk_to_database(chunk)
chunk_count += 1
#print(f'Added chunk {chunk_count}/{len(VECTOR_DB)} to the database')
for chunk in chunks:
add_chunk_to_database(chunk)
chunk_count += 1
#print(f'Added chunk {chunk_count}/{len(VECTOR_DB)} to the database')

def reset_vector_db():
global VECTOR_DB
Expand Down
138 changes: 133 additions & 5 deletions src/loadingDataset.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,138 @@
def load_dataset(filename):
with open(filename,'r') as file:
dataset = file.readlines()
import os
from langchain_text_splitters import RecursiveCharacterTextSplitter
from pypdf import PdfReader

#print(f'Loaded {len(dataset)} entries in the dataset.')
# Stratégies de splitting par type de fichier
SPLITTING_STRATEGIES = {
# Fichiers de code
'code': RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
separators=["class", "def", "\n\n", "\n", " ", ""],
),
# Fichiers JSON/YAML/Config
'config': RecursiveCharacterTextSplitter(
chunk_size=300,
chunk_overlap=30,
separators=["\n", " ", ""],
),
# Fichiers texte brut
'text': RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
separators=["\n\n", "\n", " ", ""],
),
'pdf': RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
separators=["\n\n", "\n", " ", ""],
),
}

return dataset
# Mapping extension -> type
FILE_TYPE_MAP = {
# Code
'.py': 'code', '.js': 'code', '.ts': 'code', '.jsx': 'code', '.tsx': 'code',
'.java': 'code', '.cpp': 'code', '.c': 'code', '.h': 'code',
'.cs': 'code', '.rb': 'code', '.go': 'code', '.rs': 'code',
'.php': 'code', '.swift': 'code', '.kt': 'code', '.sql':'code',
# Config
'.json': 'config', '.yaml': 'config', '.yml': 'config',
'.toml': 'config', '.ini': 'config', '.conf': 'config','.xml':'config',
# Texte
'.txt': 'text', '.md': 'text',
#PDF
'pdf':'pdf',
}

def get_file_type(filepath):
"""Détecte automatiquement le type de fichier."""
_, ext = os.path.splitext(filepath)
return FILE_TYPE_MAP.get(ext.lower(), 'text') # Par défaut: texte

#Pour les fichiers PDF
def load_pdf(filepath):
"""Extrait le texte d'un PDF."""
text = ""
try:
reader = PdfReader(filepath)
for page_num, page in enumerate(reader.pages):
page_text = page.extract_text()
text += f"\n[Page {page_num + 1}]\n{page_text}"
return text
except Exception as e:
print(f"✗ Erreur en lisant le PDF {filepath}: {e}")
return ""

# Fonction principale pour charger et splitter un fichier 'txt'
def load_and_split_file(filepath):
"""
Charge et split un fichier automatiquement selon son type.
Retourne les chunks déjà splitté.
"""
file_type = get_file_type(filepath)
splitter = SPLITTING_STRATEGIES[file_type]

try:
# Traitement spécial pour les PDFs
if file_type == 'pdf':
content = load_pdf(filepath)
else:
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()

chunks = splitter.split_text(content)
print(f"✓ {filepath} ({file_type}) -> {len(chunks)} chunks")
return chunks

except Exception as e:
print(f"✗ Erreur en lisant {filepath}: {e}")
return []

def load_multiple_files(paths):
"""
Charge plusieurs fichiers/dossiers et les split intelligemment.
Supporte: txt, code, json, yaml, pdf, et plus.

Args:
paths (list): Liste de chemins (fichiers ou dossiers)

Returns:
list: Tous les chunks extraits et splittés
"""
all_chunks = []
total_files = 0
total_chunks = 0

for path in paths:
if not os.path.exists(path):
print(f"⚠️ Le chemin n'existe pas: {path}")
continue

try:
if os.path.isdir(path):
# Parcourir le dossier
print(f"\n📁 Traitement du dossier: {path}")
for filename in os.listdir(path):
filepath = os.path.join(path, filename)
if os.path.isfile(filepath):
chunks = load_and_split_file(filepath)
if chunks:
all_chunks.extend(chunks)
total_files += 1
total_chunks += len(chunks)
else:
# Fichier unique
print(f"\n📄 Traitement du fichier: {path}")
chunks = load_and_split_file(path)
if chunks:
all_chunks.extend(chunks)
total_files += 1
total_chunks += len(chunks)

except Exception as e:
print(f"✗ Erreur lors du traitement de {path}: {e}")
continue

print(f"\n✅ Résumé: {total_files} fichier(s) traité(s), {total_chunks} chunk(s) créé(s)")
return all_chunks
Loading