0% ont trouvé ce document utile (0 vote)
7 vues16 pages

FastAPI : Orchestration Multi-Agents NLP

Transféré par

yasmina el hafi
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats DOCX, PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
7 vues16 pages

FastAPI : Orchestration Multi-Agents NLP

Transféré par

yasmina el hafi
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats DOCX, PDF, TXT ou lisez en ligne sur Scribd

Explication du code

Importation des bibliothèques:

Le bloc d’imports initialise toutes les bibliothèques nécessaires au backend FastAPI d’une
application intelligente de traitement documentaire. Il inclut des modules standards pour la
gestion des fichiers (os, shutil, tempfile), des exceptions (traceback, logging), la manipulation
JSON et base64, ainsi que des opérations asynchrones et concurrentes (asyncio, Lock). Côté web,
FastAPI est utilisé avec ses outils pour gérer les requêtes, fichiers, formulaires, ainsi que CORS
pour autoriser les appels cross-origin. Pour le traitement de documents, docx et pdfplumber
(utilisé ailleurs) permettent d’extraire le texte et les tableaux. L'application s’appuie massivement
sur l’écosystème LangChain avec des composants comme UnstructuredPDFLoader,
OllamaEmbeddings, ChatOllama, Chroma pour les bases vectorielles, et des prompts structurés
(ChatPromptTemplate) couplés à des parseurs (StrOutputParser). L’orchestration multi-agent est
gérée via langgraph, avec StateGraph, des types de messages (HumanMessage, AIMessage, etc.),
et la classe BaseTool pour encapsuler des outils spécialisés. Pour le traitement NLP, nltk est utilisé
(avec téléchargement du tagger grammatical), langdetect pour détecter la langue des requêtes,
et TokenTextSplitter pour découper efficacement le texte. Enfin, pymongo et Binary assurent la
persistance des fichiers et des métadonnées dans une base MongoDB.

Configuration d’environnement:
Ce bloc configure l’environnement initial de l’application FastAPI. D’abord, le logging est activé au
niveau INFO pour tracer les événements importants dans la console, à l’aide d’un logger nommé
d’après le module courant (__name__). Ensuite, une instance FastAPI est créée et enrichie avec
un middleware CORS qui autorise les requêtes provenant du frontend en [Link]
utile pour le développement local. La connexion à MongoDB est établie via pymongo sur
localhost:27017, avec deux bases : rago_db pour stocker les documents (docs_collection) et
llm_evaluation pour les tests qualité de LLM (collection1). Un répertoire data/vectors est défini
pour persister les bases vectorielles localement, et deux dictionnaires globaux maintiennent en
mémoire les bases Chroma des utilisateurs (USER_VECTOR_DBS) et des verrous par utilisateur
(USER_LOCKS) pour éviter les accès concurrents. Enfin, un modèle de reranking nommé
bAAI/bge-reranker-large est chargé via CrossEncoder, afin d’améliorer la pertinence des
documents retournés par recherche vectorielle.

Définition de la structure formelle du système multi-agent


Ce bloc définit la structure formelle du système multi-agent. La classe AgentType, une
énumération (Enum), liste les rôles disponibles pour les agents : récupération de documents
(DOCUMENT_RETRIEVER), analyse de contenu (CONTENT_ANALYZER), synthèse de réponse
(RESPONSE_SYNTHESIZER) et évaluation qualité (QUALITY_CONTROLLER). Ensuite, AgentState est
un TypedDict typé avec Annotated, représentant l’état partagé que chaque agent lit et modifie
dans le pipeline. Il contient notamment :
la liste des messages échangés (messages),
la question posée (question),
l’identifiant utilisateur (user_id),
les documents récupérés (retrieved_docs),
le résultat d’analyse (analysis_result),
la réponse synthétisée (synthesized_response),
le score qualité (quality_score),
la réponse finale (final_response),
et le contexte utilisé (context_used).
Cette structure unifiée permet de faire circuler l’information entre agents dans un graphe de
traitement défini par LangGraph.

Outils personnalisés des agents


Cette classe DocumentRetrievalTool, héritée de BaseTool, est un outil personnalisé utilisé par
l’agent de récupération documentaire. Elle sert à interroger la base vectorielle Chroma associée à
un utilisateur donné (user_id) pour récupérer les documents les plus pertinents face à une
requête (query). Lors de l’appel de la méthode _run, l'outil vérifie que l'utilisateur a bien une
base vectorielle, puis utilise as_retriever(k=8) pour extraire jusqu'à 8 documents similaires. Si des
documents sont trouvés, ils sont ensuite rerankés par pertinence à l’aide du modèle bAAI/bge-
reranker-large, qui évalue chaque paire (query, contenu du document) et renvoie un score de
similarité. Les documents sont triés par score décroissant, et seuls les 5 meilleurs sont retournés.
Ce reranking garantit que les documents fournis à l’agent suivant sont réellement les plus proches
contextuellement de la question posée.

class DocumentRetrieverAgent: Cet agent est responsable de la récupération des documents


pertinents à partir de la base vectorielle pour un utilisateur donné.Il reçoit en paramètre un
dictionnaire vector_dbs où chaque clé est un user_id et la valeur est une base vectorielle
(Chroma) contenant les embeddings des documents de cet utilisateur.
Il initialise l’outil DocumentRetrievalTool, un outil personnalisé qui encapsule la logique de
recherche vectorielle . execute est la méthode asynchrone utilisée dans le graphe multi-agent.
Elle prend en entrée un AgentState (un dictionnaire typé représentant l'état partagé entre les
agents).
Elle appelle _run de l’outil DocumentRetrievalTool, qui utilise :
la question (state["question"])
l’identifiant de l’utilisateur (state["user_id"])
Elle stocke les documents pertinents retrouvés dans state["retrieved_docs"].
class ContentAnalyzerAgent: Cet agent est responsable d’analyser le contenu des documents
récupérés pour extraire des informations utiles et répondre à la question.
Il initialise un outil ContentAnalysisTool basé sur un modèle LLM local appelé "llama3.2:1b" (via
ChatOllama).

Cet outil est chargé de formuler une analyse en langage naturel à partir des documents et de la
[Link] prend aussi un AgentState.
Elle exécute l’analyse via _run de ContentAnalysisTool, en lui passant :
les documents récupérés
la question de l’utilisateur
Le résultat de l’analyse est stocké dans state["analysis_result"].

La classe ContentAnalysisTool est un outil basé sur BaseTool qui utilise un LLM (modèle de
langage) pour analyser le contenu de documents récupérés. Lors de son initialisation, elle reçoit
un objet llm (par exemple, un modèle ChatOllama) qui sera utilisé pour générer des réponses. Sa
méthode _run prend une liste de documents et une question utilisateur. Si aucun document n’est
fourni, elle retourne un message d’erreur. Sinon, elle construit un prompt structuré qui demande
au LLM de jouer le rôle d’expert et de fournir une analyse en quatre points : informations clés,
relations entre concepts, éléments manquants, et résumé. Tous les contenus des documents sont
concaténés, puis injectés dans une chaîne composée du prompt, du LLM, et d’un parseur de
sortie (StrOutputParser). Enfin, le résultat retourné est une réponse textuelle structurée générée
par le modèle.

La classe ResponseSynthesizerAgent est un agent dont le rôle est de générer une réponse finale
à la question de l’utilisateur en se basant sur l’analyse précédente. Lors de son initialisation, il
instancie un modèle LLM local (llama3.2:1b) via ChatOllama. Dans sa méthode asynchrone
execute, l’agent commence par détecter la langue de la question (anglais ou français). En fonction
de cette langue, il sélectionne un prompt de synthèse approprié, conçu pour guider le LLM à
produire une réponse claire, concise et bien structurée. Ce prompt inclut l’analyse et la question,
et est ensuite injecté dans une chaîne composée du prompt, du modèle LLM et d’un parseur de
sortie. La réponse générée est stockée dans state["synthesized_response"], prête à être évaluée
ou retournée.
La classe QualityControllerAgent est un agent chargé d’évaluer la qualité de la réponse générée
par le système. Lors de son initialisation, elle utilise le modèle llama3.2:1b via ChatOllama. Dans
sa méthode execute, elle construit un prompt qui demande explicitement au modèle de noter la
réponse sur une échelle de 0 à 1, selon quatre critères : pertinence, précision, complétude et
clarté. Ce prompt est injecté dans une chaîne (prompt | llm | StrOutputParser) avec la question
et la réponse en entrée. Le modèle retourne un texte contenant une ligne de score (SCORE: 0.X)
suivie d’une justification. L’agent extrait ce score, l’ajoute à state["quality_score"], et copie la
réponse dans state["final_response"]. Si le score est jugé trop faible (inférieur à 0.6), un
avertissement est loggé, laissant la possibilité d’implémenter une reformulation plus tard.
Orchestrateur multi-agents
La classe MultiAgentOrchestrator orchestre l'exécution coordonnée de plusieurs agents
spécialisés pour traiter une question utilisateur. À l'initialisation, elle crée quatre agents : un pour
la récupération des documents (DocumentRetrieverAgent), un pour l'analyse du contenu
(ContentAnalyzerAgent), un pour la synthèse de réponse (ResponseSynthesizerAgent), et un pour
le contrôle qualité (QualityControllerAgent). Elle définit ensuite un graphe de workflow
(StateGraph) où chaque nœud représente l’exécution d’un agent, et les transitions suivent l’ordre
logique : récupération → analyse → synthèse → évaluation. Lorsqu’une question est posée via
process_question, l’orchestrateur construit un état initial avec les métadonnées nécessaires
(question, user_id, etc.), puis déclenche l’exécution du graphe. À la fin, il extrait les trois premiers
documents utilisés pour constituer le contexte, compile les résultats, le score de qualité et
l'analyse, puis retourne une réponse complète prête à être affichée à l'utilisateur.

Fonctions utilitaires
Ce bloc regroupe des fonctions utilitaires utilisées pour extraire et nettoyer le contenu de fichiers
avant leur indexation dans la base vectorielle :
-extract_text_from_docx(file_path) :
Extrait le texte brut d’un document .docx en lisant chaque paragraphe, puis les concatène avec
des sauts de ligne. Utilisé pour récupérer le contenu principal des fichiers Word.
-extract_text_from_txt(file_path) :
Lit un fichier texte .txt en entier avec encodage UTF-8 et retourne son contenu. Très simple et
direct.
-clean_text(text) :
Nettoie une chaîne de texte en supprimant :
Les artefacts comme (cid:123) souvent présents dans des PDF extraits,
Les caractères spéciaux non imprimables,
Les retours chariots superflus, les espaces multiples, ou les sauts de ligne excessifs.
Cela prépare un texte propre à injecter dans des embeddings.
-extract_tables_as_text_with_context(file_path, filename) :
Utilise pdfplumber pour extraire toutes les tables contenues dans un fichier PDF. Chaque table
est convertie en texte tabulaire, annotée avec son numéro et sa page. Le résultat est retourné
sous forme d’un bloc texte enrichi d’un en-tête contextuel indiquant l’origine (nom du fichier).
Cette fonction permet d’indexer des tables comme du contenu informatif, souvent négligé par
l’OCR classique.
La fonction extract_tables_from_docx lit les tableaux d’un document Word (.docx), extrait
chaque ligne en joignant les cellules avec des barres verticales, puis renvoie le contenu tabulaire
sous forme de texte avec un en-tête précisant le nom du fichier. generate_unique_filename
garantit qu’un nom de fichier soit unique pour chaque utilisateur en vérifiant dans MongoDB s’il
existe déjà un document portant ce nom, et en ajoutant un suffixe numérique si nécessaire.
format_size convertit une taille en octets en une chaîne lisible avec les unités B, KB ou MB,
utilisée pour afficher la taille des fichiers. Enfin, la classe FileInfo, définie avec Pydantic, structure
les métadonnées des fichiers en précisant leur nom et leur taille au format lisible, utile pour les
réponses d’API.

ENDPOINTS
Cette portion de code définit un endpoint FastAPI (/upload_files/) permettant à un utilisateur
d’envoyer plusieurs fichiers (PDF, DOCX ou TXT) en précisant son identifiant (user_id). Lorsqu'un
utilisateur envoie un fichier, le système crée ou récupère un verrou (mutex) pour éviter les accès
concurrents à ses ressources. Ensuite, il vérifie si une base vectorielle Chroma est déjà associée à
l’utilisateur ; sinon, elle est créée et liée à un modèle d’embedding (yxchia/multilingual-e5-base).
Si nécessaire, l’orchestrateur multi-agent est réinitialisé pour prendre en compte cette nouvelle
base. Chaque fichier est ensuite traité individuellement : il est temporairement enregistré, puis
son texte est extrait selon son type (texte brut ou tableaux), nettoyé, et découpé en segments
(chunks) de 2000 tokens avec chevauchement. Ces segments, accompagnés de métadonnées
(nom, index, taille...), sont insérés dans la base vectorielle. Par ailleurs, le texte entier et le fichier
binaire sont stockés dans une collection MongoDB. À la fin de chaque traitement, le fichier
temporaire est supprimé pour libérer de l’espace disque. Enfin, la fonction retourne un tableau
résumant le statut de traitement de chaque fichier (succès ou erreur), ce qui permet à
l'utilisateur de savoir quels fichiers ont été traités correctement.

Cette partie du code définit un endpoint FastAPI (/ask_question/) permettant à un utilisateur


d’envoyer une question en lien avec les documents qu’il a préalablement téléversés. La classe
QuestionRequest sert de schéma de validation, imposant la présence d’un user_id (identifiant
utilisateur) et d’une question (sous forme de chaîne de caractères). Lorsque l'utilisateur envoie sa
requête, le serveur vérifie d’abord que le système multi-agent (orchestrator) est bien initialisé ;
sinon, une erreur serveur (500) est levée. Ensuite, il s’assure que l’utilisateur concerné dispose
bien d’une base vectorielle dans laquelle des documents ont été indexés. Si ce n’est pas le cas,
une erreur (400) est renvoyée pour indiquer qu’aucun document n’a été chargé. Si tout est en
ordre, la question est transmise à l’orchestrateur multi-agent qui active successivement plusieurs
agents spécialisés (recherche documentaire, analyse de contenu, génération de réponse et
évaluation de qualité) pour produire une réponse précise et structurée. Une fois la réponse
générée, elle est renvoyée à l’utilisateur accompagnée d’un score de qualité. En cas d’erreur
durant ce processus, l’exception est interceptée, enregistrée dans les logs, et une erreur 500 est
renvoyée pour signaler une défaillance du système.
Cette fonction définit un endpoint FastAPI accessible via la méthode GET à l’URL
/get_uploaded_filenames/{user_id}. Elle permet de récupérer la liste des noms des fichiers que
l'utilisateur identifié par user_id a précédemment téléversés et vectorisés, accompagnée de leur
taille. Tout d’abord, le système vérifie que l’utilisateur dispose bien d’une base vectorielle dans
USER_VECTOR_DBS, sinon une erreur 404 est renvoyée. Un verrou (Lock) est ensuite utilisé pour
garantir une lecture sûre des données associées à l’utilisateur. La base vectorielle (Chroma) est
interrogée pour extraire les métadonnées des documents stockés, et un filtre est appliqué pour
exclure les entrées correspondant uniquement à des tableaux (type != "table"). Les noms de
fichiers uniques sont collectés, ainsi que la taille de chaque fichier (formatée lisiblement), pour
être retournés sous forme d’une liste d’objets FileInfo. Si une erreur survient, elle est enregistrée
dans les logs, et une exception HTTP 500 est levée. Enfin, le verrou est systématiquement libéré
grâce au bloc finally, assurant la bonne gestion de la concurrence même en cas d’échec.
Cette fonction définit un endpoint FastAPI accessible via la méthode DELETE à l’URL
/delete_file_vector/, permettant à un utilisateur de supprimer tous les vecteurs associés à un
fichier spécifique dans sa base vectorielle. Elle attend deux paramètres de requête : user_id
(identifiant de l’utilisateur) et filename (nom du fichier à supprimer). Le système commence par
vérifier si l’utilisateur possède bien une base vectorielle dans USER_VECTOR_DBS. Ensuite, il
initialise un verrou pour garantir que l’opération de suppression ne soit pas perturbée par
d’autres accès concurrents. La base vectorielle est ensuite parcourue pour extraire les identifiants
(ids) des vecteurs et leurs métadonnées, dans le but de localiser ceux correspondant au nom de
fichier ciblé. Si aucun vecteur n’est trouvé pour ce fichier, une erreur 404 est renvoyée. Sinon, les
vecteurs sont supprimés par lots de 100 pour optimiser les performances et éviter les
débordements mémoire. À chaque batch supprimé, un message de log est émis. En cas d’erreur
pendant le processus, celle-ci est enregistrée et une erreur 500 est renvoyée à l’utilisateur. Enfin,
si tout se passe bien, un message de succès est retourné, indiquant le nombre de vecteurs
supprimés pour le fichier concerné.
Cette fonction définit un endpoint FastAPI accessible via la méthode GET à l’URL /documents/,
qui permet de récupérer la liste des documents téléversés par un utilisateur spécifique, identifié
par le paramètre user_id. Elle interroge la base de données MongoDB (collection docs_collection)
pour obtenir les fichiers correspondant à cet utilisateur, en sélectionnant uniquement les champs
filename, file_data et timestamp (en excluant l’identifiant _id). Pour chaque document trouvé, le
contenu binaire du fichier est converti en chaîne Base64 afin de pouvoir être renvoyé dans la
réponse JSON, ce qui est nécessaire pour la transmission de données binaires via HTTP.
L’horodatage (timestamp) est également formaté en ISO pour plus de lisibilité. Si aucun
document n’est trouvé, une erreur 404 est levée pour indiquer que l’utilisateur ne possède aucun
fichier. En cas d’erreur technique durant l’exécution, celle-ci est enregistrée dans les logs et une
exception HTTP 500 est renvoyée avec un message d’erreur explicite.

Cette fonction définit un endpoint FastAPI accessible en méthode DELETE via l’URL
/documents/delete/, permettant à un utilisateur de supprimer un document précédemment
téléversé et stocké dans la base de données MongoDB. Elle attend une requête JSON contenant
deux champs : user_id (identifiant de l'utilisateur) et filename (nom du fichier à supprimer). Si
l'un de ces deux champs est manquant, une erreur 400 est immédiatement renvoyée. Une fois
les données validées, la fonction tente de supprimer un document correspondant à ce couple
user_id / filename dans la collection docs_collection. Si aucun document n’est trouvé
(suppression non effectuée), une erreur 404 est retournée pour informer que le fichier n’existe
pas dans la base. En cas de succès, un message de confirmation est renvoyé. Toutes les
opérations sont soigneusement journalisées à l’aide du système de logs, et en cas d’exception
inattendue, l’erreur est enregistrée et une réponse HTTP 500 est émise pour indiquer une erreur
interne du serveur.

Ce bloc de code permet de lancer l’application FastAPI de manière autonome en tant que script
principal. L’instruction if __name__ == "__main__": signifie que ce code ne sera exécuté que si le
fichier est lancé directement (et non importé comme module dans un autre fichier Python). À
l’intérieur, [Link](...) démarre le serveur avec l’application FastAPI app sur l’adresse IP
[Link] (ce qui la rend accessible depuis n’importe quelle machine du réseau) et sur le port 8000.
L’option reload=True permet de redémarrer automatiquement le serveur chaque fois qu’un
changement est détecté dans le code source, ce qui est très pratique en phase de
développement.

Vous aimerez peut-être aussi