Spaces:
Running
Running
Feat: Ajout des sources aux réponses de l'API
Browse files- PROCHAINES_ETAPES.md +50 -0
- backend/api/routes/documents.py +46 -28
- backend/api/routes/questions.py +5 -3
- backend/models/question.py +12 -3
- backend/services/document_processor.py +28 -19
- backend/services/question_handler.py +19 -5
- backend/services/vector_store.py +23 -18
- requirements.txt +131 -10
PROCHAINES_ETAPES.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Prochaines Étapes pour l'Assistant Web Éducatif
|
| 2 |
+
|
| 3 |
+
Ce document détaille les prochaines étapes de développement pour faire évoluer le projet de son état actuel (backend fonctionnel) vers une application complète, en se basant sur le cahier des charges et la vision du produit.
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
### Phase 1 : Consolidation du Backend et de l'API
|
| 7 |
+
|
| 8 |
+
L'objectif est de rendre le backend plus robuste et complet.
|
| 9 |
+
|
| 10 |
+
1. **Gestion des Téléversements de Fichiers (Uploads) :**
|
| 11 |
+
* [cite_start]Modifier l'endpoint `POST /documents` pour accepter un **vrai téléversement de fichier PDF** au lieu d'un simple nom de fichier [cite: 244-247, 305-309].
|
| 12 |
+
* Sauvegarder le fichier téléversé dans le dossier `data/documents`.
|
| 13 |
+
* Déclencher automatiquement le processus d'extraction et de vectorisation juste après le téléversement.
|
| 14 |
+
|
| 15 |
+
2. **Affiner le Modèle de Réponse :**
|
| 16 |
+
* [cite_start]Enrichir la réponse de l'API `/ask` pour inclure les **sources exactes** (nom du document, numéro de page, etc.) qui ont servi de contexte [cite: 269-277].
|
| 17 |
+
* Cela implique de stocker plus de métadonnées (comme le numéro de page) lors du découpage du texte.
|
| 18 |
+
|
| 19 |
+
3. **Gestion des Utilisateurs :**
|
| 20 |
+
* [cite_start]Créer des modèles de données et des tables pour les **utilisateurs** (Étudiant, Enseignant, Administrateur) [cite: 1236-1240, 1253-1255].
|
| 21 |
+
* [cite_start]Mettre en place un système d'**authentification** (par exemple, avec JWT) pour sécuriser les endpoints[cite: 71, 132].
|
| 22 |
+
|
| 23 |
+
---
|
| 24 |
+
### Phase 2 : Développement du Frontend
|
| 25 |
+
|
| 26 |
+
L'objectif est de créer une interface utilisateur pour interagir avec le backend.
|
| 27 |
+
|
| 28 |
+
1. **Interface de Questions-Réponses :**
|
| 29 |
+
* [cite_start]Créer une page simple avec un champ de saisie pour poser une question et une zone pour afficher la réponse de l'IA [cite: 258-260, 1142].
|
| 30 |
+
* Connecter cette interface à l'endpoint `/api/v1/ask`.
|
| 31 |
+
|
| 32 |
+
2. **Interface d'Administration :**
|
| 33 |
+
* [cite_start]Développer une page sécurisée pour les enseignants et administrateurs[cite: 236].
|
| 34 |
+
* [cite_start]Créer un formulaire pour le **téléversement des manuels PDF** [cite: 237-242].
|
| 35 |
+
* Afficher la liste des documents déjà présents dans le système.
|
| 36 |
+
|
| 37 |
+
---
|
| 38 |
+
### Phase 3 : Améliorations et Déploiement
|
| 39 |
+
|
| 40 |
+
L'objectif est de préparer le projet pour une utilisation réelle.
|
| 41 |
+
|
| 42 |
+
1. **Amélioration de la Pertinence :**
|
| 43 |
+
* [cite_start]Explorer des **modèles de `sentence-transformers` multilingues** ou plus spécialisés en science pour améliorer la qualité de la recherche sémantique[cite: 80].
|
| 44 |
+
* [cite_start]Permettre à l'utilisateur de noter la pertinence des réponses pour un apprentissage continu (auto-amélioration)[cite: 20, 1271].
|
| 45 |
+
|
| 46 |
+
2. **Mise en place du Cache :**
|
| 47 |
+
* [cite_start]Intégrer **Redis** pour mettre en cache les questions fréquentes et accélérer les temps de réponse, comme spécifié dans l'architecture[cite: 25, 62, 1384].
|
| 48 |
+
|
| 49 |
+
3. **Conteneurisation Complète avec Docker Compose :**
|
| 50 |
+
* [cite_start]Écrire un fichier `docker-compose.yml` pour lancer toute l'application (Backend, PostgreSQL, Redis, Ollama) avec une seule commande, simplifiant ainsi le déploiement [cite: 148-193].
|
backend/api/routes/documents.py
CHANGED
|
@@ -1,46 +1,64 @@
|
|
| 1 |
-
from fastapi import APIRouter, Depends
|
| 2 |
from sqlalchemy.orm import Session
|
|
|
|
|
|
|
|
|
|
| 3 |
from backend.models.document import Document
|
| 4 |
from backend.api.dependencies import get_db
|
| 5 |
-
|
|
|
|
| 6 |
from backend.services.vector_store import VectorStore
|
| 7 |
|
| 8 |
router = APIRouter()
|
| 9 |
|
|
|
|
|
|
|
|
|
|
| 10 |
@router.post("/documents")
|
| 11 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
-
new_document = Document(file_name=
|
| 14 |
db.add(new_document)
|
| 15 |
db.commit()
|
| 16 |
db.refresh(new_document)
|
| 17 |
-
return new_document
|
| 18 |
-
|
| 19 |
-
@router.get("/documents")
|
| 20 |
-
def get_all_documents(db: Session = Depends(get_db)):
|
| 21 |
-
|
| 22 |
-
documents = db.query(Document).all()
|
| 23 |
-
return documents
|
| 24 |
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
file_path = f"data/{document.file_name}"
|
| 32 |
-
text = extract_text_from_pdf(file_path)
|
| 33 |
-
chunks = split_text_into_chunks(text)
|
| 34 |
|
| 35 |
try:
|
| 36 |
vector_store = VectorStore()
|
| 37 |
-
vector_store.add_document_chunks(doc_id=
|
|
|
|
|
|
|
| 38 |
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
"
|
| 43 |
-
"
|
|
|
|
|
|
|
|
|
|
| 44 |
}
|
| 45 |
-
|
| 46 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import APIRouter, Depends, UploadFile, File, Form, HTTPException
|
| 2 |
from sqlalchemy.orm import Session
|
| 3 |
+
import aiofiles
|
| 4 |
+
import os
|
| 5 |
+
|
| 6 |
from backend.models.document import Document
|
| 7 |
from backend.api.dependencies import get_db
|
| 8 |
+
# MODIFIÉ: Importe la nouvelle fonction extract_pages_from_pdf
|
| 9 |
+
from backend.services.document_processor import extract_pages_from_pdf, split_text_into_chunks
|
| 10 |
from backend.services.vector_store import VectorStore
|
| 11 |
|
| 12 |
router = APIRouter()
|
| 13 |
|
| 14 |
+
UPLOAD_DIRECTORY = "data/documents"
|
| 15 |
+
os.makedirs(UPLOAD_DIRECTORY, exist_ok=True)
|
| 16 |
+
|
| 17 |
@router.post("/documents")
|
| 18 |
+
async def create_and_process_document(
|
| 19 |
+
db: Session = Depends(get_db),
|
| 20 |
+
subject: str = Form(...),
|
| 21 |
+
level: str = Form(...),
|
| 22 |
+
file: UploadFile = File(...)
|
| 23 |
+
):
|
| 24 |
+
file_path = os.path.join(UPLOAD_DIRECTORY, file.filename)
|
| 25 |
+
try:
|
| 26 |
+
async with aiofiles.open(file_path, 'wb') as out_file:
|
| 27 |
+
content = await file.read()
|
| 28 |
+
await out_file.write(content)
|
| 29 |
+
except Exception as e:
|
| 30 |
+
raise HTTPException(status_code=500, detail=f"Erreur lors de la sauvegarde du fichier : {e}")
|
| 31 |
|
| 32 |
+
new_document = Document(file_name=file.filename, subject=subject, level=level)
|
| 33 |
db.add(new_document)
|
| 34 |
db.commit()
|
| 35 |
db.refresh(new_document)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
|
| 37 |
+
try:
|
| 38 |
+
# MODIFIÉ: Utilise les nouvelles fonctions pour le traitement
|
| 39 |
+
pages = extract_pages_from_pdf(file_path)
|
| 40 |
+
chunks = split_text_into_chunks(pages)
|
| 41 |
+
except Exception as e:
|
| 42 |
+
raise HTTPException(status_code=500, detail=f"Erreur lors de l'extraction du texte du PDF : {e}")
|
|
|
|
|
|
|
|
|
|
| 43 |
|
| 44 |
try:
|
| 45 |
vector_store = VectorStore()
|
| 46 |
+
vector_store.add_document_chunks(doc_id=new_document.id, chunks=chunks)
|
| 47 |
+
except Exception as e:
|
| 48 |
+
raise HTTPException(status_code=500, detail=f"Erreur lors de la vectorisation : {e}")
|
| 49 |
|
| 50 |
+
return {
|
| 51 |
+
"message": "Document téléversé et traité avec succès !",
|
| 52 |
+
"document_details": {
|
| 53 |
+
"id": new_document.id,
|
| 54 |
+
"file_name": new_document.file_name,
|
| 55 |
+
"subject": new_document.subject,
|
| 56 |
+
"level": new_document.level,
|
| 57 |
+
"chunks_added": len(chunks)
|
| 58 |
}
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
@router.get("/documents")
|
| 62 |
+
def get_all_documents(db: Session = Depends(get_db)):
|
| 63 |
+
documents = db.query(Document).all()
|
| 64 |
+
return documents
|
backend/api/routes/questions.py
CHANGED
|
@@ -1,14 +1,16 @@
|
|
| 1 |
from fastapi import APIRouter
|
| 2 |
-
|
|
|
|
| 3 |
from backend.services.question_handler import QuestionHandler
|
| 4 |
|
| 5 |
router = APIRouter()
|
| 6 |
handler = QuestionHandler()
|
| 7 |
|
| 8 |
-
|
|
|
|
| 9 |
def ask_question(request: QuestionRequest):
|
| 10 |
"""
|
| 11 |
Receives a question, finds context, and returns an AI-generated answer.
|
| 12 |
"""
|
| 13 |
answer_data = handler.get_answer(request.question)
|
| 14 |
-
return answer_data
|
|
|
|
| 1 |
from fastapi import APIRouter
|
| 2 |
+
# MODIFIÉ: Import des nouveaux modèles
|
| 3 |
+
from backend.models.question import QuestionRequest, QuestionResponse
|
| 4 |
from backend.services.question_handler import QuestionHandler
|
| 5 |
|
| 6 |
router = APIRouter()
|
| 7 |
handler = QuestionHandler()
|
| 8 |
|
| 9 |
+
# MODIFIÉ: Utilisation du response_model pour garantir le format de sortie
|
| 10 |
+
@router.post("/ask", response_model=QuestionResponse)
|
| 11 |
def ask_question(request: QuestionRequest):
|
| 12 |
"""
|
| 13 |
Receives a question, finds context, and returns an AI-generated answer.
|
| 14 |
"""
|
| 15 |
answer_data = handler.get_answer(request.question)
|
| 16 |
+
return answer_data
|
backend/models/question.py
CHANGED
|
@@ -1,7 +1,16 @@
|
|
| 1 |
from pydantic import BaseModel, Field
|
| 2 |
-
from typing import Optional
|
| 3 |
|
| 4 |
class QuestionRequest(BaseModel):
|
| 5 |
question: str = Field(..., min_length=5, max_length=500)
|
| 6 |
-
|
| 7 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
from pydantic import BaseModel, Field
|
| 2 |
+
from typing import Optional, List
|
| 3 |
|
| 4 |
class QuestionRequest(BaseModel):
|
| 5 |
question: str = Field(..., min_length=5, max_length=500)
|
| 6 |
+
|
| 7 |
+
# NOUVEAU: Modèle pour une source unique
|
| 8 |
+
class Source(BaseModel):
|
| 9 |
+
document_id: Optional[int]
|
| 10 |
+
page: Optional[int]
|
| 11 |
+
|
| 12 |
+
# NOUVEAU: Modèle pour la réponse complète
|
| 13 |
+
class QuestionResponse(BaseModel):
|
| 14 |
+
question: str
|
| 15 |
+
answer: str
|
| 16 |
+
sources: List[Source]
|
backend/services/document_processor.py
CHANGED
|
@@ -1,26 +1,35 @@
|
|
| 1 |
import fitz # PyMuPDF
|
| 2 |
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
|
|
|
| 3 |
|
| 4 |
-
def extract_text_from_pdf(file_path: str) -> str:
|
| 5 |
-
# ... (cette fonction ne change pas)
|
| 6 |
-
try:
|
| 7 |
-
doc = fitz.open(file_path)
|
| 8 |
-
text = ""
|
| 9 |
-
for page in doc:
|
| 10 |
-
text += page.get_text()
|
| 11 |
-
return text
|
| 12 |
-
except Exception as e:
|
| 13 |
-
print(f"Erreur lors de l'extraction du PDF {file_path}: {e}")
|
| 14 |
-
return ""
|
| 15 |
|
| 16 |
-
def
|
| 17 |
-
"""
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
text_splitter = RecursiveCharacterTextSplitter(
|
| 21 |
-
chunk_size=1000,
|
| 22 |
-
chunk_overlap=200,
|
| 23 |
length_function=len
|
| 24 |
)
|
| 25 |
-
|
| 26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import fitz # PyMuPDF
|
| 2 |
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
| 3 |
+
from typing import List, Dict
|
| 4 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
+
def extract_pages_from_pdf(file_path: str) -> List[Dict]:
|
| 7 |
+
"""Extrait le contenu de chaque page et son numéro."""
|
| 8 |
+
doc = fitz.open(file_path)
|
| 9 |
+
pages_content = []
|
| 10 |
+
for page_num, page in enumerate(doc):
|
| 11 |
+
pages_content.append({
|
| 12 |
+
"page_number": page_num + 1,
|
| 13 |
+
"content": page.get_text()
|
| 14 |
+
})
|
| 15 |
+
return pages_content
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def split_text_into_chunks(pages: List[Dict]) -> List[Dict]:
|
| 19 |
+
"""Découpe le texte de chaque page en morceaux en conservant les métadonnées."""
|
| 20 |
text_splitter = RecursiveCharacterTextSplitter(
|
| 21 |
+
chunk_size=1000,
|
| 22 |
+
chunk_overlap=200,
|
| 23 |
length_function=len
|
| 24 |
)
|
| 25 |
+
|
| 26 |
+
all_chunks = []
|
| 27 |
+
for page in pages:
|
| 28 |
+
chunks_on_page = text_splitter.split_text(page["content"])
|
| 29 |
+
for chunk in chunks_on_page:
|
| 30 |
+
all_chunks.append({
|
| 31 |
+
"text": chunk,
|
| 32 |
+
"metadata": {"page": page["page_number"]}
|
| 33 |
+
})
|
| 34 |
+
|
| 35 |
+
return all_chunks
|
backend/services/question_handler.py
CHANGED
|
@@ -6,14 +6,28 @@ class QuestionHandler:
|
|
| 6 |
self.vector_store = VectorStore()
|
| 7 |
|
| 8 |
def get_answer(self, question: str):
|
| 9 |
-
# 1.
|
| 10 |
-
|
| 11 |
|
| 12 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
answer = generate_response(question, context)
|
| 14 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
return {
|
| 16 |
"question": question,
|
| 17 |
"answer": answer,
|
| 18 |
-
"
|
| 19 |
-
}
|
|
|
|
| 6 |
self.vector_store = VectorStore()
|
| 7 |
|
| 8 |
def get_answer(self, question: str):
|
| 9 |
+
# 1. Trouver les chunks pertinents avec leurs métadonnées
|
| 10 |
+
search_results = self.vector_store.find_similar_chunks(question)
|
| 11 |
|
| 12 |
+
# Extraire le contexte et les sources
|
| 13 |
+
context_texts = search_results["documents"][0]
|
| 14 |
+
sources_metadata = search_results["metadatas"][0]
|
| 15 |
+
|
| 16 |
+
context = "\n---\n".join(context_texts)
|
| 17 |
+
|
| 18 |
+
# 2. Générer une réponse en utilisant le contexte
|
| 19 |
answer = generate_response(question, context)
|
| 20 |
|
| 21 |
+
# 3. Formater les sources pour la réponse finale
|
| 22 |
+
sources = []
|
| 23 |
+
for meta in sources_metadata:
|
| 24 |
+
sources.append({
|
| 25 |
+
"document_id": meta.get("document_id"),
|
| 26 |
+
"page": meta.get("page")
|
| 27 |
+
})
|
| 28 |
+
|
| 29 |
return {
|
| 30 |
"question": question,
|
| 31 |
"answer": answer,
|
| 32 |
+
"sources": sources
|
| 33 |
+
}
|
backend/services/vector_store.py
CHANGED
|
@@ -1,43 +1,48 @@
|
|
| 1 |
import chromadb
|
| 2 |
from sentence_transformers import SentenceTransformer
|
|
|
|
| 3 |
|
| 4 |
class VectorStore:
|
| 5 |
def __init__(self):
|
| 6 |
-
# ... (le début de la classe ne change pas)
|
| 7 |
self.client = chromadb.PersistentClient(path="data/chroma_db")
|
| 8 |
self.embedding_model = SentenceTransformer('all-MiniLM-L6-v2')
|
| 9 |
self.collection = self.client.get_or_create_collection(name="documents")
|
| 10 |
|
| 11 |
-
|
| 12 |
-
|
| 13 |
if not chunks:
|
| 14 |
return
|
| 15 |
|
| 16 |
-
|
| 17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
ids = [f"doc_{doc_id}_chunk_{i}" for i, _ in enumerate(chunks)]
|
| 19 |
|
| 20 |
self.collection.add(
|
| 21 |
embeddings=embeddings,
|
| 22 |
metadatas=metadatas,
|
| 23 |
-
documents=
|
| 24 |
ids=ids
|
| 25 |
)
|
| 26 |
print(f"Ajout de {len(chunks)} chunks pour le document {doc_id} à ChromaDB.")
|
| 27 |
|
| 28 |
-
#
|
| 29 |
-
def find_similar_chunks(self, question: str, n_results: int = 3) ->
|
| 30 |
-
"""
|
| 31 |
-
Trouve les morceaux de texte les plus pertinents pour une question donnée.
|
| 32 |
-
"""
|
| 33 |
-
# Transforme la question en vecteur.
|
| 34 |
query_embedding = self.embedding_model.encode(question)
|
| 35 |
-
|
| 36 |
-
# Interroge la collection ChromaDB.
|
| 37 |
results = self.collection.query(
|
| 38 |
query_embeddings=[query_embedding.tolist()],
|
| 39 |
-
n_results=n_results
|
|
|
|
| 40 |
)
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
return results['documents'][0]
|
|
|
|
| 1 |
import chromadb
|
| 2 |
from sentence_transformers import SentenceTransformer
|
| 3 |
+
from typing import List, Dict
|
| 4 |
|
| 5 |
class VectorStore:
|
| 6 |
def __init__(self):
|
|
|
|
| 7 |
self.client = chromadb.PersistentClient(path="data/chroma_db")
|
| 8 |
self.embedding_model = SentenceTransformer('all-MiniLM-L6-v2')
|
| 9 |
self.collection = self.client.get_or_create_collection(name="documents")
|
| 10 |
|
| 11 |
+
# MODIFIÉ: La fonction accepte maintenant une liste de dictionnaires
|
| 12 |
+
def add_document_chunks(self, doc_id: int, chunks: List[Dict]):
|
| 13 |
if not chunks:
|
| 14 |
return
|
| 15 |
|
| 16 |
+
texts = [chunk["text"] for chunk in chunks]
|
| 17 |
+
embeddings = self.embedding_model.encode(texts)
|
| 18 |
+
|
| 19 |
+
# MODIFIÉ: Les métadonnées incluent maintenant le numéro de page
|
| 20 |
+
metadatas = []
|
| 21 |
+
for i, chunk in enumerate(chunks):
|
| 22 |
+
meta = chunk["metadata"]
|
| 23 |
+
meta["document_id"] = doc_id
|
| 24 |
+
meta["chunk_index"] = i
|
| 25 |
+
metadatas.append(meta)
|
| 26 |
+
|
| 27 |
ids = [f"doc_{doc_id}_chunk_{i}" for i, _ in enumerate(chunks)]
|
| 28 |
|
| 29 |
self.collection.add(
|
| 30 |
embeddings=embeddings,
|
| 31 |
metadatas=metadatas,
|
| 32 |
+
documents=texts,
|
| 33 |
ids=ids
|
| 34 |
)
|
| 35 |
print(f"Ajout de {len(chunks)} chunks pour le document {doc_id} à ChromaDB.")
|
| 36 |
|
| 37 |
+
# MODIFIÉ: La fonction retourne maintenant les documents ET leurs métadonnées
|
| 38 |
+
def find_similar_chunks(self, question: str, n_results: int = 3) -> Dict:
|
| 39 |
+
"""Trouve les chunks pertinents et retourne leur contenu et métadonnées."""
|
|
|
|
|
|
|
|
|
|
| 40 |
query_embedding = self.embedding_model.encode(question)
|
| 41 |
+
|
|
|
|
| 42 |
results = self.collection.query(
|
| 43 |
query_embeddings=[query_embedding.tolist()],
|
| 44 |
+
n_results=n_results,
|
| 45 |
+
include=["documents", "metadatas"] # On demande explicitement les métadonnées
|
| 46 |
)
|
| 47 |
+
|
| 48 |
+
return results
|
|
|
requirements.txt
CHANGED
|
@@ -1,10 +1,131 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
aiofiles==24.1.0
|
| 2 |
+
annotated-types==0.7.0
|
| 3 |
+
anyio==4.10.0
|
| 4 |
+
attrs==25.3.0
|
| 5 |
+
backoff==2.2.1
|
| 6 |
+
bcrypt==4.3.0
|
| 7 |
+
build==1.3.0
|
| 8 |
+
cachetools==5.5.2
|
| 9 |
+
certifi==2025.8.3
|
| 10 |
+
charset-normalizer==3.4.3
|
| 11 |
+
chromadb==1.0.20
|
| 12 |
+
click==8.2.1
|
| 13 |
+
coloredlogs==15.0.1
|
| 14 |
+
distro==1.9.0
|
| 15 |
+
durationpy==0.10
|
| 16 |
+
fastapi==0.116.1
|
| 17 |
+
filelock==3.19.1
|
| 18 |
+
flatbuffers==25.2.10
|
| 19 |
+
fsspec==2025.7.0
|
| 20 |
+
google-auth==2.40.3
|
| 21 |
+
googleapis-common-protos==1.70.0
|
| 22 |
+
greenlet==3.2.4
|
| 23 |
+
grpcio==1.74.0
|
| 24 |
+
h11==0.16.0
|
| 25 |
+
hf-xet==1.1.8
|
| 26 |
+
httpcore==1.0.9
|
| 27 |
+
httptools==0.6.4
|
| 28 |
+
httpx==0.28.1
|
| 29 |
+
huggingface-hub==0.34.4
|
| 30 |
+
humanfriendly==10.0
|
| 31 |
+
idna==3.10
|
| 32 |
+
importlib_metadata==8.7.0
|
| 33 |
+
importlib_resources==6.5.2
|
| 34 |
+
Jinja2==3.1.6
|
| 35 |
+
joblib==1.5.1
|
| 36 |
+
jsonpatch==1.33
|
| 37 |
+
jsonpointer==3.0.0
|
| 38 |
+
jsonschema==4.25.1
|
| 39 |
+
jsonschema-specifications==2025.4.1
|
| 40 |
+
kubernetes==33.1.0
|
| 41 |
+
langchain==0.3.27
|
| 42 |
+
langchain-core==0.3.74
|
| 43 |
+
langchain-text-splitters==0.3.9
|
| 44 |
+
langsmith==0.4.16
|
| 45 |
+
markdown-it-py==4.0.0
|
| 46 |
+
MarkupSafe==3.0.2
|
| 47 |
+
mdurl==0.1.2
|
| 48 |
+
mmh3==5.2.0
|
| 49 |
+
mpmath==1.3.0
|
| 50 |
+
networkx==3.5
|
| 51 |
+
numpy==2.3.2
|
| 52 |
+
nvidia-cublas-cu12==12.8.4.1
|
| 53 |
+
nvidia-cuda-cupti-cu12==12.8.90
|
| 54 |
+
nvidia-cuda-nvrtc-cu12==12.8.93
|
| 55 |
+
nvidia-cuda-runtime-cu12==12.8.90
|
| 56 |
+
nvidia-cudnn-cu12==9.10.2.21
|
| 57 |
+
nvidia-cufft-cu12==11.3.3.83
|
| 58 |
+
nvidia-cufile-cu12==1.13.1.3
|
| 59 |
+
nvidia-curand-cu12==10.3.9.90
|
| 60 |
+
nvidia-cusolver-cu12==11.7.3.90
|
| 61 |
+
nvidia-cusparse-cu12==12.5.8.93
|
| 62 |
+
nvidia-cusparselt-cu12==0.7.1
|
| 63 |
+
nvidia-nccl-cu12==2.27.3
|
| 64 |
+
nvidia-nvjitlink-cu12==12.8.93
|
| 65 |
+
nvidia-nvtx-cu12==12.8.90
|
| 66 |
+
oauthlib==3.3.1
|
| 67 |
+
onnxruntime==1.22.1
|
| 68 |
+
opentelemetry-api==1.36.0
|
| 69 |
+
opentelemetry-exporter-otlp-proto-common==1.36.0
|
| 70 |
+
opentelemetry-exporter-otlp-proto-grpc==1.36.0
|
| 71 |
+
opentelemetry-proto==1.36.0
|
| 72 |
+
opentelemetry-sdk==1.36.0
|
| 73 |
+
opentelemetry-semantic-conventions==0.57b0
|
| 74 |
+
orjson==3.11.2
|
| 75 |
+
overrides==7.7.0
|
| 76 |
+
packaging==25.0
|
| 77 |
+
pillow==11.3.0
|
| 78 |
+
posthog==5.4.0
|
| 79 |
+
protobuf==6.32.0
|
| 80 |
+
psycopg2-binary==2.9.10
|
| 81 |
+
pyasn1==0.6.1
|
| 82 |
+
pyasn1_modules==0.4.2
|
| 83 |
+
pybase64==1.4.2
|
| 84 |
+
pydantic==2.11.7
|
| 85 |
+
pydantic-settings==2.10.1
|
| 86 |
+
pydantic_core==2.33.2
|
| 87 |
+
Pygments==2.19.2
|
| 88 |
+
PyMuPDF==1.26.3
|
| 89 |
+
PyPika==0.48.9
|
| 90 |
+
pyproject_hooks==1.2.0
|
| 91 |
+
python-dateutil==2.9.0.post0
|
| 92 |
+
python-dotenv==1.1.1
|
| 93 |
+
python-multipart==0.0.20
|
| 94 |
+
PyYAML==6.0.2
|
| 95 |
+
referencing==0.36.2
|
| 96 |
+
regex==2025.7.34
|
| 97 |
+
requests==2.32.5
|
| 98 |
+
requests-oauthlib==2.0.0
|
| 99 |
+
requests-toolbelt==1.0.0
|
| 100 |
+
rich==14.1.0
|
| 101 |
+
rpds-py==0.27.0
|
| 102 |
+
rsa==4.9.1
|
| 103 |
+
safetensors==0.6.2
|
| 104 |
+
scikit-learn==1.7.1
|
| 105 |
+
scipy==1.16.1
|
| 106 |
+
sentence-transformers==5.1.0
|
| 107 |
+
setuptools==80.9.0
|
| 108 |
+
shellingham==1.5.4
|
| 109 |
+
six==1.17.0
|
| 110 |
+
sniffio==1.3.1
|
| 111 |
+
SQLAlchemy==2.0.43
|
| 112 |
+
starlette==0.47.2
|
| 113 |
+
sympy==1.14.0
|
| 114 |
+
tenacity==9.1.2
|
| 115 |
+
threadpoolctl==3.6.0
|
| 116 |
+
tokenizers==0.21.4
|
| 117 |
+
torch==2.8.0
|
| 118 |
+
tqdm==4.67.1
|
| 119 |
+
transformers==4.55.4
|
| 120 |
+
triton==3.4.0
|
| 121 |
+
typer==0.16.1
|
| 122 |
+
typing-inspection==0.4.1
|
| 123 |
+
typing_extensions==4.14.1
|
| 124 |
+
urllib3==2.5.0
|
| 125 |
+
uvicorn==0.35.0
|
| 126 |
+
uvloop==0.21.0
|
| 127 |
+
watchfiles==1.1.0
|
| 128 |
+
websocket-client==1.8.0
|
| 129 |
+
websockets==15.0.1
|
| 130 |
+
zipp==3.23.0
|
| 131 |
+
zstandard==0.24.0
|