From af253b71dd88e410f62b680608270b095d741c2e Mon Sep 17 00:00:00 2001 From: LJ5O <75009579+LJ5O@users.noreply.github.com> Date: Thu, 5 Feb 2026 14:23:18 +0100 Subject: [PATCH 1/6] State pour l'agent --- AgentReact/utils/StateElements/TodoElement.py | 40 +++++++++++++++++++ AgentReact/utils/state.py | 16 ++++++-- roadmap.md | 2 +- 3 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 AgentReact/utils/StateElements/TodoElement.py diff --git a/AgentReact/utils/StateElements/TodoElement.py b/AgentReact/utils/StateElements/TodoElement.py new file mode 100644 index 0000000..dc79d64 --- /dev/null +++ b/AgentReact/utils/StateElements/TodoElement.py @@ -0,0 +1,40 @@ +# Classes utilisées pour représenter des données +class TodoElement(): + STATE_NOT_STARTED = 0 # Sorte d'enum qui représente l'état d'une tâche + STATE_STARTED = 1 + STATE_COMPLETED = 2 + + name: str + state: int + + def __init__(self, name:str, description:str=None): + self.name = name + self.description = description + self.state = TodoElement.STATE_NOT_STARTED + + def __str__(self)->str: + """ + Affiche la tâche, son nom et son statut + Affichera aussi la description de la tâche si elle a été définie ET est en cours + + Returns: + str: Représentation écrite de la tâche + """ + return f"Tâche \"{self.name}\": {self.__getStateName()}." + \ + (f" Description: {self.description}" if self.description and self.state == TodoElement.STATE_STARTED else '') + + def __getStateName(self)->str: + if self.state == TodoElement.STATE_NOT_STARTED: + return "Non commencée" + elif self.state == TodoElement.STATE_STARTED: + return "En cours" + elif self.state == TodoElement.STATE_COMPLETED: + return "Terminée" + else: + return "Inconnu" + +if __name__ == "__main__": + test = TodoElement("TEST tâche", "OUI") + test.state = TodoElement.STATE_STARTED + print(test) + print([str(test)]) diff --git a/AgentReact/utils/state.py b/AgentReact/utils/state.py index da1fa34..3224440 100644 --- a/AgentReact/utils/state.py +++ b/AgentReact/utils/state.py @@ -1,7 +1,17 @@ from langgraph.graph import StateGraph, MessagesState +from typing import List + +from .StateElements.TodoElement import TodoElement + + +class CustomState(MessagesState): + todo: List[TodoElement] # Les tâches en cours + + ragQuery: str # Requête envoyée au RAG, pour le cross-encodeur + ragDocuments: List[str] # Documents retrouvés par le RAG, pour le cross-encodeur + + # TODO: Ajouter la source des documents sélectionnés pour la fin du rapport ? -class hjgzefvuiyguhzfvihuozdef(MessagesState): # J'ai du mal à nommer mes classes ._. - pass def getState()->StateGraph: """ @@ -10,4 +20,4 @@ def getState()->StateGraph: Returns: StateGraph: prêt à utiliser """ - return StateGraph(hjgzefvuiyguhzfvihuozdef) \ No newline at end of file + return StateGraph(CustomState) \ No newline at end of file diff --git a/roadmap.md b/roadmap.md index 247feed..2215d44 100644 --- a/roadmap.md +++ b/roadmap.md @@ -11,7 +11,7 @@ - [X] Lecture des documents et mise en base de données vectorielle ## Mise en place de l'agent -- [ ] Préparation du `State` +- [X] Préparation du `State` - [ ] Développement des outils de l'agent - [ ] Préparation des nœuds - [ ] Branchement des nœuds entre-eux From dc746edeeac8e05ce3ae4dd9270f958ed25a6e40 Mon Sep 17 00:00:00 2001 From: LJ5O <75009579+LJ5O@users.noreply.github.com> Date: Thu, 5 Feb 2026 16:15:36 +0100 Subject: [PATCH 2/6] Premier test de tools --- AgentReact/utils/VectorDatabase.py | 45 ++++++++ AgentReact/utils/tools.py | 171 +++++++++++++++++++++++++++-- roadmap.md | 4 +- 3 files changed, 210 insertions(+), 10 deletions(-) create mode 100644 AgentReact/utils/VectorDatabase.py diff --git a/AgentReact/utils/VectorDatabase.py b/AgentReact/utils/VectorDatabase.py new file mode 100644 index 0000000..c1d9f0e --- /dev/null +++ b/AgentReact/utils/VectorDatabase.py @@ -0,0 +1,45 @@ +from langchain_huggingface import HuggingFaceEmbeddings +from langchain_chroma import Chroma # TODO plus tard, ramplacer par PG Vector +import sys +from pathlib import Path + +# Permet de garder ChromaDB en mémoire. +# Cette classe est un Singleton, il n'y en aura qu'une seule et unique instance à tout moment +# https://refactoring.guru/design-patterns/singleton +class VectorDatabase: + instance = None + + def __new__(cls): # Selon https://www.geeksforgeeks.org/python/singleton-pattern-in-python-a-complete-guide/ + if cls.instance is None: + cls.instance = super().__new__(cls) + # J'initialise les attributs à None ici, permet de tester si la classe a déjà été init une première fois ou non + cls.instance.__embeddings = None + cls.instance.__chroma = None + return cls.instance + + def __init__(self): + if self.__embeddings is not None: return + + base_dir:str = Path(sys.argv[0]).resolve().parent.as_posix() # Récupérer le chemin vers le point d'entrée du programme + bdd_path:str = base_dir + "/chroma_db/" + + self.__embeddings = HuggingFaceEmbeddings(model_name="jinaai/jina-embeddings-v3", model_kwargs={"trust_remote_code": True}) + self.__chroma = Chroma( + persist_directory=bdd_path, + embedding_function=self.__embeddings + ) + + def getChroma(self)->Chroma: + return self.__chroma + + def getEmbeddings(self)->'Embeddings Hugging Face': + return self.__embeddings + +if __name__ == "__main__": + + test1 = VectorDatabase() + print('TEST 1 INIT') + test2 = VectorDatabase() + + print(test1 is test2) + assert test1 is test2 \ No newline at end of file diff --git a/AgentReact/utils/tools.py b/AgentReact/utils/tools.py index 503531f..8dfb8e0 100644 --- a/AgentReact/utils/tools.py +++ b/AgentReact/utils/tools.py @@ -1,6 +1,12 @@ from langchain.tools import tool +from langgraph.prebuilt import InjectedState from tavily import TavilyClient -from typing import List +from pathlib import Path +from typing import List, Dict +import sys +from .StateElements.TodoElement import TodoElement + +from .VectorDatabase import VectorDatabase @tool def internet_search(query: str)->dict: @@ -16,22 +22,169 @@ def internet_search(query: str)->dict: @tool -def write_file(content: str) -> str: +def write_file(file_path:str, content: str, append:bool=True) -> str: """ - Écrire les données dans un fichier + Ecrire et ajouter du texte dans un fichier. Args: - content (str): Contenu du fichier à écrire + file_path (str): Chemin d'accès relatif vers le fichier à écrire. + content (str): Contenu à écrire dans le fichier. + append (bool, optional): Faut-il AJOUTER(True) au fichier, ou REMPLACER son contenu(False) ? True par défaut. Returns: - str: Résultat de l'écriture + str: Le chemin d'accès relatif vers le fichier en cas de réussite, ou une erreur en cas d'echec """ - print("==ECRITURE FICHIER==") - print(content) - return "Fichier écrit" + try: + base_dir:str = Path(sys.argv[0]).resolve().parent.as_posix() # Récupérer le chemin vers le point d'entrée du programme + file_path:str = base_dir + (file_path if file_path.startswith('/') else f'/{file_path}') # Puis générer le chemin vers le fichier + + mode = "a" if append else "w" # Mode d'écriture + with open(full_path, mode, encoding="utf-8") as f: # Puis j'écris + f.write(content) + + return str(full_path) + + except Exception as e: + return f"Erreur lors de l'écriture: {str(e)}" + +@tool +def editTodo(index:int, state:int, state: Annotated[dict, InjectedState])->bool: # https://stackoverflow.com/a/79525434 + """ + Modifier l'état d'une tâche (TODO) + + Args: + index (int): Index de la tâche à modifier, en commançant à 0 pour la première tâche. + state (int): Nouvel état. 0 pour "non commencé, 1 pour "en cours", 2 pour "complété" + + Returns: + bool: Réussite de l'opération, ou non. + """ + if len(state["todo"]) <= index: + # Erreur, l'index est trop grand + return False + + state["todo"][index].state = state # Modification de l'état de cette tâche + + # Toutes les tâches complétées ? + found = False + for task in state["todo"]: # Pour chaque tâche + if task.state != 2: # Si elle n'est pas terminée + found = True + break + + if not found: state["todo"] = [] # Toutes les tâches terminées, je peux clear la TODO list du state + return True + +@tool +def addTodo(name:str, description:str, state: Annotated[dict, InjectedState])->bool: + """ + Ajouter une nouvelle tâche/TODO + + Args: + name (str): Nom de cette tâche + description (str): Une ou deux phrases pour décrire le travail à effectuer dans ce TODO + + Returns: + bool: Réussite de l'opération, ou non + """ + if state["todo"] is None: state["todo"] = [] + + state["todo"].append(TodoElement(name, description)) + +@tool +def removeTodo(index:int, state: Annotated[dict, InjectedState])->bool: + """ + Retirer une tâche/TODO de la liste des tâches + + Args: + index (int): Position de la tâche dans la liste, commence à 0 pour le premier TODO + + Returns: + bool: Réussite de l'opération, ou non + """ + if len(state["todo"]) <= index: + # Erreur, l'index est trop grand + return False + + state['todo'].pop(index) + return True + +@tool +def read_file(file_path: str) -> str: + """ + Lire le contenu d'un fichier texte. + + Args: + file_path (str): Chemin d'accès relatif vers le fichier à lire. + + Returns: + str: Le contenu du fichier, ou un message d'erreur. + """ + try: + base_dir:str = Path(sys.argv[0]).resolve().parent.as_posix() # Récupérer le chemin vers le point d'entrée du programme + file_path:str = base_dir + (file_path if file_path.startswith('/') else f'/{file_path}') # Puis générer le chemin vers le fichier + + with open(file_path, "r", encoding="utf-8") as f: + content = f.read() + + return content + + except Exception as e: + return f"Erreur lors de la lecture : {str(e)}" + +@tool +def ask_human(request:str)->str: + """ + Demander quelque chose à un assistant humain. Permet d'obtenir des informations supplémentaires, + ou qu'une action soit réalisée. + + Args: + request (str): Ce qui est demandé à l'humain + + Returns: + str: Réponse de l'humain + """ + print("--- L'IA A BESOIN D'UN HUMAIN ! ---") + print(f"L'IA demande : {request}") + + user_response = input("Réponse humaine: ") # Input bloque le système en attendant l'humain + # J'aurais possiblement utiliser d'autres approches comme https://docs.langchain.com/oss/javascript/langchain/human-in-the-loop + # Mais Human in the loop se place AVANT l'outil. Ici, l'outil consiste justement à demander quelque chose à un humain. + + print("-------") + + return user_response + +@tool +def search_in_files(query:str, state: Annotated[dict, InjectedState])->List[str]: + """ + Rechercher quelque chose dans les documents enregistrés localement. + Dans le cas actuel, ces documents sont des rapports hebdomadaires de stage. + + Args: + query (str): La requête recherchée. + + Returns: + List[str]: Échantillons de documents correspondants. + """ + bdd = VectorDatabase() # Récupère l'unique instance de cette BDD, c'est un SIngleton + + retrieved_docs = bdd.getChroma().similarity_search(prompt, k=5) # 5 documents + + # Conversion des documents en texte + docs_content = "\n".join( + [f"Document {i+1}:\n{doc.page_content}" for i,doc in enumerate(retrieved_docs)] + ) + + # Sauvegarde des données dans le State + state["ragQuery"] = query + state["ragDocuments"] = docs_content + + return docs_content # Retourne la liste de documents trouvés + def getTools()->List['Tools']: """ Récupérer la liste des tools """ - return [internet_search, write_file] \ No newline at end of file + return [internet_search, write_file, editTodo, read_file, ask_human, search_in_files, addTodo, removeTodo] \ No newline at end of file diff --git a/roadmap.md b/roadmap.md index 2215d44..a9eaf74 100644 --- a/roadmap.md +++ b/roadmap.md @@ -12,13 +12,15 @@ ## Mise en place de l'agent - [X] Préparation du `State` -- [ ] Développement des outils de l'agent +- [X] Développement des outils de l'agent - [ ] Préparation des nœuds - [ ] Branchement des nœuds entre-eux ## Amélioration de l'agent +- [ ] Cross-encoding sur la sortie du **RAG** - [ ] Sauvegarde de l'état de l'agent - [ ] Système de redémarrage après un arrêt +- [ ] Gestion de la taille du contexte - Résumé de l'historique des messages - [ ] Détection de *prompt injection* - [ ] Génération d'un PDF en sortie du système From 1f96b9a4088a0e6d2f6e4e0aa8d8873f012bcab1 Mon Sep 17 00:00:00 2001 From: LJ5O <75009579+LJ5O@users.noreply.github.com> Date: Thu, 5 Feb 2026 16:27:40 +0100 Subject: [PATCH 3/6] Fix tools Quelques petits fixs --- AgentReact/utils/tools.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/AgentReact/utils/tools.py b/AgentReact/utils/tools.py index 8dfb8e0..d742359 100644 --- a/AgentReact/utils/tools.py +++ b/AgentReact/utils/tools.py @@ -2,7 +2,7 @@ from langchain.tools import tool from langgraph.prebuilt import InjectedState from tavily import TavilyClient from pathlib import Path -from typing import List, Dict +from typing import List, Dict, Annotated import sys from .StateElements.TodoElement import TodoElement @@ -36,7 +36,7 @@ def write_file(file_path:str, content: str, append:bool=True) -> str: """ try: base_dir:str = Path(sys.argv[0]).resolve().parent.as_posix() # Récupérer le chemin vers le point d'entrée du programme - file_path:str = base_dir + (file_path if file_path.startswith('/') else f'/{file_path}') # Puis générer le chemin vers le fichier + full_path:str = base_dir + (file_path if file_path.startswith('/') else f'/{file_path}') # Puis générer le chemin vers le fichier mode = "a" if append else "w" # Mode d'écriture with open(full_path, mode, encoding="utf-8") as f: # Puis j'écris @@ -90,6 +90,7 @@ def addTodo(name:str, description:str, state: Annotated[dict, InjectedState])->b if state["todo"] is None: state["todo"] = [] state["todo"].append(TodoElement(name, description)) + return True @tool def removeTodo(index:int, state: Annotated[dict, InjectedState])->bool: @@ -122,9 +123,9 @@ def read_file(file_path: str) -> str: """ try: base_dir:str = Path(sys.argv[0]).resolve().parent.as_posix() # Récupérer le chemin vers le point d'entrée du programme - file_path:str = base_dir + (file_path if file_path.startswith('/') else f'/{file_path}') # Puis générer le chemin vers le fichier + full_path:str = base_dir + (file_path if file_path.startswith('/') else f'/{file_path}') # Puis générer le chemin vers le fichier - with open(file_path, "r", encoding="utf-8") as f: + with open(full_path, "r", encoding="utf-8") as f: content = f.read() return content @@ -156,7 +157,7 @@ def ask_human(request:str)->str: return user_response @tool -def search_in_files(query:str, state: Annotated[dict, InjectedState])->List[str]: +def search_in_files(query:str, state: Annotated[dict, InjectedState])->str: """ Rechercher quelque chose dans les documents enregistrés localement. Dans le cas actuel, ces documents sont des rapports hebdomadaires de stage. @@ -165,11 +166,11 @@ def search_in_files(query:str, state: Annotated[dict, InjectedState])->List[str] query (str): La requête recherchée. Returns: - List[str]: Échantillons de documents correspondants. + str: Échantillons de documents correspondants, concaténés en une seule chaîne de caractères. """ bdd = VectorDatabase() # Récupère l'unique instance de cette BDD, c'est un SIngleton - retrieved_docs = bdd.getChroma().similarity_search(prompt, k=5) # 5 documents + retrieved_docs = bdd.getChroma().similarity_search(query, k=5) # 5 documents # Conversion des documents en texte docs_content = "\n".join( @@ -178,7 +179,7 @@ def search_in_files(query:str, state: Annotated[dict, InjectedState])->List[str] # Sauvegarde des données dans le State state["ragQuery"] = query - state["ragDocuments"] = docs_content + state["ragDocuments"] = retrieved_docs return docs_content # Retourne la liste de documents trouvés From 14b86641065edba53e758285be1ca1c2f2416da3 Mon Sep 17 00:00:00 2001 From: LJ5O <75009579+LJ5O@users.noreply.github.com> Date: Fri, 6 Feb 2026 16:23:59 +0100 Subject: [PATCH 4/6] Nodes V1 --- AgentReact/utils/nodes.py | 55 +++++++++++++++++++++++++++++++++------ roadmap.md | 3 ++- 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/AgentReact/utils/nodes.py b/AgentReact/utils/nodes.py index 00e7ef4..5703dc7 100644 --- a/AgentReact/utils/nodes.py +++ b/AgentReact/utils/nodes.py @@ -14,21 +14,18 @@ llm = ChatMistralAI( # LLM sans outils ) # NODES -def reponse_question(state: MessagesState): - """Noeud qui réponds à la question, en s'aidant si besoin des outils à disposition""" +def call_to_LLM(state: MessagesState): + """Noeud qui s'occupe de gérer les appels au LLM""" # Initialisation du LLM model = llm.bind_tools(getTools()) # Appel du LLM return {"messages": [model.invoke(state["messages"])]} -tool_node = ToolNode(tools=getTools()) # Node gérant les outils - -# fonction de routage : Après reponse_question, si le LLM veut appeler un outil, on va au tool_node, sinon on termine +# fonction de routage : Après reponse_question, si le LLM veut appeler un outil, on va au tool_node def should_continue(state: MessagesState): """ - Use in the conditional_edge to route to the ToolNode if the last message - has tool calls. Otherwise, route to the end. + Vérifier s'il y a un appel aux outils dans le dernier message """ if isinstance(state, list): ai_message = state[-1] @@ -39,4 +36,46 @@ def should_continue(state: MessagesState): if hasattr(ai_message, "tool_calls") and len(ai_message.tool_calls) > 0: return "tools" - return END \ No newline at end of file + return "no_tools" + +def task_ended(state: MessagesState): + """ + Vérifier si l'agent a terminé son cycle, ou s'il faut le relancer + """ + if isinstance(state, list): + ai_message = state[-1] + elif messages := state.get("messages", []): + ai_message = messages[-1] + else: + raise ValueError(f"No messages found in input state to tool_edge: {state}") + + if "terminé" in ai_message.content.lower(): + return END + return "continue" + + +class BasicToolNode: # De mon ancien projet, https://github.com/LJ5O/Assistant/blob/main/modules/Brain/src/LLM/graph/nodes/BasicToolNode.py + """A node that runs the tools requested in the last AIMessage.""" + + def __init__(self, tools: list) -> None: + self.tools_by_name = {tool.name: tool for tool in tools} + + def __call__(self, inputs: dict): + if messages := inputs.get("messages", []): + message = messages[-1] + else: + raise ValueError("No message found in input") + outputs = [] + for tool_call in message.tool_calls: + #print(tool_call["args"]) + tool_result = self.tools_by_name[tool_call["name"]].invoke( + tool_call["args"] + ) + outputs.append( + ToolMessage( + content=json.dumps(tool_result), + name=tool_call["name"], + tool_call_id=tool_call["id"], + ) + ) + return {"messages": outputs} \ No newline at end of file diff --git a/roadmap.md b/roadmap.md index a9eaf74..eefaeba 100644 --- a/roadmap.md +++ b/roadmap.md @@ -13,8 +13,9 @@ ## Mise en place de l'agent - [X] Préparation du `State` - [X] Développement des outils de l'agent -- [ ] Préparation des nœuds +- [X] Préparation des nœuds - [ ] Branchement des nœuds entre-eux +- [ ] Human in the loop ## Amélioration de l'agent - [ ] Cross-encoding sur la sortie du **RAG** From 1c2f0728ea54a2ee3023dbefca0be4ef763be177 Mon Sep 17 00:00:00 2001 From: LJ5O <75009579+LJ5O@users.noreply.github.com> Date: Fri, 6 Feb 2026 17:38:27 +0100 Subject: [PATCH 5/6] Passage Jina -> intfloat/multilingual-e5-large Jina me donne toujours RuntimeError: The size of tensor a (5) must match the size of tensor b (4) at non-singleton dimension 1 --- AgentReact/utils/VectorDatabase.py | 46 ++++++++---------------------- RAG/init.py | 2 +- 2 files changed, 13 insertions(+), 35 deletions(-) diff --git a/AgentReact/utils/VectorDatabase.py b/AgentReact/utils/VectorDatabase.py index c1d9f0e..4e944b2 100644 --- a/AgentReact/utils/VectorDatabase.py +++ b/AgentReact/utils/VectorDatabase.py @@ -3,43 +3,21 @@ from langchain_chroma import Chroma # TODO plus tard, ramplacer par PG Vector import sys from pathlib import Path -# Permet de garder ChromaDB en mémoire. -# Cette classe est un Singleton, il n'y en aura qu'une seule et unique instance à tout moment -# https://refactoring.guru/design-patterns/singleton -class VectorDatabase: - instance = None +base_dir:str = Path(sys.argv[0]).resolve().parent.as_posix() # Récupérer le chemin vers le point d'entrée du programme +bdd_path:str = base_dir + "/../chroma_db/" - def __new__(cls): # Selon https://www.geeksforgeeks.org/python/singleton-pattern-in-python-a-complete-guide/ - if cls.instance is None: - cls.instance = super().__new__(cls) - # J'initialise les attributs à None ici, permet de tester si la classe a déjà été init une première fois ou non - cls.instance.__embeddings = None - cls.instance.__chroma = None - return cls.instance - - def __init__(self): - if self.__embeddings is not None: return - - base_dir:str = Path(sys.argv[0]).resolve().parent.as_posix() # Récupérer le chemin vers le point d'entrée du programme - bdd_path:str = base_dir + "/chroma_db/" - - self.__embeddings = HuggingFaceEmbeddings(model_name="jinaai/jina-embeddings-v3", model_kwargs={"trust_remote_code": True}) - self.__chroma = Chroma( +EMBEDDINGS = HuggingFaceEmbeddings(model_name="intfloat/multilingual-e5-large", model_kwargs={"trust_remote_code": True}) +CHROMA = Chroma( persist_directory=bdd_path, - embedding_function=self.__embeddings + embedding_function=EMBEDDINGS ) - def getChroma(self)->Chroma: - return self.__chroma +class VectorDatabase: # Classe pour récupérer la BDD - def getEmbeddings(self)->'Embeddings Hugging Face': - return self.__embeddings + @staticmethod + def getChroma()->Chroma: + return CHROMA -if __name__ == "__main__": - - test1 = VectorDatabase() - print('TEST 1 INIT') - test2 = VectorDatabase() - - print(test1 is test2) - assert test1 is test2 \ No newline at end of file + @staticmethod + def getEmbeddings()->'Embeddings Hugging Face': + return EMBEDDINGS \ No newline at end of file diff --git a/RAG/init.py b/RAG/init.py index a328e71..40e52c6 100644 --- a/RAG/init.py +++ b/RAG/init.py @@ -43,7 +43,7 @@ print("===") # Création du modèle d'embeddings # https://docs.langchain.com/oss/python/integrations/text_embedding/huggingfacehub # https://huggingface.co/jinaai/jina-clip-v2 -embeddings = HuggingFaceEmbeddings(model_name="jinaai/jina-embeddings-v3", model_kwargs={"trust_remote_code": True}) +embeddings = HuggingFaceEmbeddings(model_name="intfloat/multilingual-e5-large", model_kwargs={"trust_remote_code": True}) # Stockage des embeddings dans ChromaDB dans un dossier local "chroma_db" vectorstore = Chroma.from_documents(documents=chunks,embedding=embeddings, persist_directory=base_dir.as_posix()+"/chroma_db/",) # https://docs.langchain.com/oss/python/integrations/vectorstores/chroma From 633726b2a0a7816430f97ff13b7ee30bc06d4940 Mon Sep 17 00:00:00 2001 From: LJ5O <75009579+LJ5O@users.noreply.github.com> Date: Fri, 6 Feb 2026 17:46:00 +0100 Subject: [PATCH 6/6] Minimal Viable Product MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit C'est moche, bancal, et mal foutu, mais ça compile et ça crache un rapport de stage dans un fichier --- AgentReact/agent.py | 15 ++++++++------- AgentReact/start.py | 2 +- AgentReact/utils/nodes.py | 2 ++ AgentReact/utils/tools.py | 10 +++++----- agent.png | Bin 0 -> 12190 bytes roadmap.md | 3 ++- 6 files changed, 18 insertions(+), 14 deletions(-) create mode 100644 agent.png diff --git a/AgentReact/agent.py b/AgentReact/agent.py index 39464fd..a21475d 100644 --- a/AgentReact/agent.py +++ b/AgentReact/agent.py @@ -1,8 +1,9 @@ from langgraph.graph import START, END from langgraph.graph.state import CompiledStateGraph -from utils.nodes import reponse_question, tool_node, should_continue +from utils.nodes import call_to_LLM, should_continue, task_ended, BasicToolNode, tool_node from utils.state import getState +from utils.tools import getTools def getGraph()->CompiledStateGraph: """ @@ -14,15 +15,15 @@ def getGraph()->CompiledStateGraph: workflow = getState() # State prêt à utiliser # Définition des sommets du graphe - workflow.add_node(reponse_question) - workflow.add_node("tool_node", tool_node) # N'est pas une fonction, mais une classe instanciée, je dois précisier le nom du node + workflow.add_node(call_to_LLM) + workflow.add_node("tool_node", tool_node)# BasicToolNode(tools=getTools())) # N'est pas une fonction, mais une classe instanciée, je dois précisier le nom du node # Arrêtes - workflow.set_entry_point("reponse_question") - workflow.add_edge("tool_node", "reponse_question") - workflow.add_conditional_edges("reponse_question", should_continue, { + workflow.set_entry_point("call_to_LLM") + workflow.add_edge("tool_node", "call_to_LLM") + workflow.add_conditional_edges("call_to_LLM", should_continue, { "tools":"tool_node", - END:END + "no_tools":END }) return workflow.compile() diff --git a/AgentReact/start.py b/AgentReact/start.py index 6ad670a..96e66a4 100644 --- a/AgentReact/start.py +++ b/AgentReact/start.py @@ -10,6 +10,6 @@ from agent import getGraph mlflow.set_experiment("TEST PROJET") # VOIR AVEC LA COMMANDE "MLFLOW SERVER" mlflow.langchain.autolog() -out_state = getGraph().invoke({'messages':[HumanMessage("What's the price for bitcoin ?")]}) +out_state = getGraph().invoke({'messages':[HumanMessage("Observe la base de documents, et génère un rapport de stage à partir de celle-ci. Ecris le dans un fichier markdown.")]}) for message in out_state['messages']: message.pretty_print() \ No newline at end of file diff --git a/AgentReact/utils/nodes.py b/AgentReact/utils/nodes.py index 5703dc7..5171f9a 100644 --- a/AgentReact/utils/nodes.py +++ b/AgentReact/utils/nodes.py @@ -53,6 +53,8 @@ def task_ended(state: MessagesState): return END return "continue" +tool_node = ToolNode(tools=getTools()) + class BasicToolNode: # De mon ancien projet, https://github.com/LJ5O/Assistant/blob/main/modules/Brain/src/LLM/graph/nodes/BasicToolNode.py """A node that runs the tools requested in the last AIMessage.""" diff --git a/AgentReact/utils/tools.py b/AgentReact/utils/tools.py index d742359..1d9fef3 100644 --- a/AgentReact/utils/tools.py +++ b/AgentReact/utils/tools.py @@ -48,13 +48,13 @@ def write_file(file_path:str, content: str, append:bool=True) -> str: return f"Erreur lors de l'écriture: {str(e)}" @tool -def editTodo(index:int, state:int, state: Annotated[dict, InjectedState])->bool: # https://stackoverflow.com/a/79525434 +def editTodo(index:int, todoState:int, state: Annotated[dict, InjectedState])->bool: # https://stackoverflow.com/a/79525434 """ Modifier l'état d'une tâche (TODO) Args: index (int): Index de la tâche à modifier, en commançant à 0 pour la première tâche. - state (int): Nouvel état. 0 pour "non commencé, 1 pour "en cours", 2 pour "complété" + todoState (int): Nouvel état. 0 pour "non commencé, 1 pour "en cours", 2 pour "complété" Returns: bool: Réussite de l'opération, ou non. @@ -63,7 +63,7 @@ def editTodo(index:int, state:int, state: Annotated[dict, InjectedState])->bool: # Erreur, l'index est trop grand return False - state["todo"][index].state = state # Modification de l'état de cette tâche + state["todo"][index].state = todoState # Modification de l'état de cette tâche # Toutes les tâches complétées ? found = False @@ -168,9 +168,9 @@ def search_in_files(query:str, state: Annotated[dict, InjectedState])->str: Returns: str: Échantillons de documents correspondants, concaténés en une seule chaîne de caractères. """ - bdd = VectorDatabase() # Récupère l'unique instance de cette BDD, c'est un SIngleton + bdd = VectorDatabase.getChroma() # Récupère l'unique instance de cette BDD, c'est un SIngleton - retrieved_docs = bdd.getChroma().similarity_search(query, k=5) # 5 documents + retrieved_docs = bdd.similarity_search(query, k=5) # 5 documents # Conversion des documents en texte docs_content = "\n".join( diff --git a/agent.png b/agent.png new file mode 100644 index 0000000000000000000000000000000000000000..c9200507b1328f8ebde9e60ecd5c75ed5ac16cd7 GIT binary patch literal 12190 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 literal 0 HcmV?d00001 diff --git a/roadmap.md b/roadmap.md index eefaeba..92a67ef 100644 --- a/roadmap.md +++ b/roadmap.md @@ -14,8 +14,9 @@ - [X] Préparation du `State` - [X] Développement des outils de l'agent - [X] Préparation des nœuds -- [ ] Branchement des nœuds entre-eux +- [X] Branchement des nœuds entre-eux, **MVP** - [ ] Human in the loop +- [ ] Amélioration du workflow ## Amélioration de l'agent - [ ] Cross-encoding sur la sortie du **RAG**