From 84a27ea6c736a36eab77b64f35e0f31fbc92a9ef Mon Sep 17 00:00:00 2001 From: LJ5O <75009579+LJ5O@users.noreply.github.com> Date: Thu, 12 Feb 2026 11:59:55 +0100 Subject: [PATCH] =?UTF-8?q?Ajustements=20et=20tentative=20de=20g=C3=A9n?= =?UTF-8?q?=C3=A9ration=20du=20rapport?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AgentReact/start.py | 2 +- AgentReact/utils/StateElements/TodoElement.py | 2 + AgentReact/utils/nodes.py | 2 +- AgentReact/utils/state.py | 5 +- AgentReact/utils/tools.py | 92 ++++++++++--------- readme.md | 25 +++++ 6 files changed, 82 insertions(+), 46 deletions(-) diff --git a/AgentReact/start.py b/AgentReact/start.py index 92932ce..0284e5a 100644 --- a/AgentReact/start.py +++ b/AgentReact/start.py @@ -22,4 +22,4 @@ initial_input = { config={"configurable": {"thread_id": 'yes'}} # Et je lance ! -streamGraph(initial_input, config, getGraph(), showSysMessages=False) \ No newline at end of file +streamGraph(initial_input, config, getGraph(), showSysMessages=True) \ No newline at end of file diff --git a/AgentReact/utils/StateElements/TodoElement.py b/AgentReact/utils/StateElements/TodoElement.py index 303ead4..03bcf39 100644 --- a/AgentReact/utils/StateElements/TodoElement.py +++ b/AgentReact/utils/StateElements/TodoElement.py @@ -56,6 +56,8 @@ class TodoElement(): TodoElement: instance created from JSON data """ data = json.loads(json_str) if type(json_str) is str else json_str + + if isinstance(data, TodoElement): return data nom_ = data.get("name", "undefined") desc_ = data.get("desc", "undefined") diff --git a/AgentReact/utils/nodes.py b/AgentReact/utils/nodes.py index 3f2f257..6ec1f04 100644 --- a/AgentReact/utils/nodes.py +++ b/AgentReact/utils/nodes.py @@ -111,7 +111,7 @@ def context_shortener(state: CustomState): lastSummarizedMessage = state['lastSummarizedMessage'] # Récupérer l'index du dernier message qui a été résumé else: # Premier passage, je supprime les anciens outils si besoin - rmtree(reports_dir.as_posix()) # Supprimer le dossier + rmtree(reports_dir.as_posix(), ignore_errors=True) # Supprimer le dossier reports_dir.mkdir(parents=True, exist_ok=False) # Créer le dossier messages = [msg for msg in state['messages'][lastSummarizedMessage+1:]] # Récupérer tous les messages après lastSummarizedMessage sans l'inclure diff --git a/AgentReact/utils/state.py b/AgentReact/utils/state.py index d8be13e..bb20465 100644 --- a/AgentReact/utils/state.py +++ b/AgentReact/utils/state.py @@ -1,9 +1,10 @@ from langgraph.graph import StateGraph, MessagesState -from typing import List +from typing import List, Annotated +import operator class CustomState(MessagesState): - todo: List[str] # Les tâches en cours, au format JSON + todo: Annotated[list, operator.add] # Les tâches en cours, au format JSON 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 diff --git a/AgentReact/utils/tools.py b/AgentReact/utils/tools.py index 98ef939..29551fb 100644 --- a/AgentReact/utils/tools.py +++ b/AgentReact/utils/tools.py @@ -5,7 +5,7 @@ from langchain_core.messages import ToolMessage from langgraph.types import Command from tavily import TavilyClient from pathlib import Path -from typing import List, Dict, Annotated +from typing import List, Dict, Annotated, Tuple import sys import os from langgraph.types import interrupt @@ -25,26 +25,53 @@ def append_part_to_report(contenu:str)->str: Returns: str: Retour, une confirmation, ou un message d'erreur """ + # Récupérer le chemin vers le point d'entrée + base_dir: Path = Path(sys.argv[0]).resolve().parent + full_path = base_dir / "RAPPORT_STAGE.md" + + query= interrupt(InterruptPayload({ + 'content': contenu + }).toJSON()) + + response = InterruptPayload.fromJSON(query) + if response.isAccepted(): + with open(full_path, "a", encoding="utf-8") as f: # Écrire le contenu + f.write("\n"+response.get("content")) + + return "Requête acceptée et validée ! Tu peux considérer cette tâche comme complétée." + else: + return "ERREUR! L'utilisateur a refusé ta demande. Tu devrais lui demander pourquoi avoir refusé, et comment améliorer cette partie." + +@tool +def list_files(folder:str)->str: + """ + Retrouver la liste des fichiers dans un dossier + + Args: + folder (str): Le chemin relatif vers le dossier + + Returns: + str: La liste de tous les fichiers dans ce dossier + """ try: - # Récupérer le chemin vers le point d'entrée base_dir: Path = Path(sys.argv[0]).resolve().parent - full_path = reports_dir / "RAPPORT_STAGE.md" + full_path: Path = base_dir / folder - query= interrupt(InterruptPayload({ - content: contenu - }).toJSON()) + if not full_path.exists(): + return f"Le dossier '{folder}' n'existe pas." - response = InterruptPayload.fromJSON(query) - if response.isAccepted(): - with open(full_path, "a", encoding="utf-8") as f: # Écrire le contenu - f.write(response.get("content")) + if not full_path.is_dir(): + return f"Le chemin '{folder}' n'est pas un dossier." - return "Requête acceptée et validée ! Tu peux considérer cette tâche comme complétée." - else: - return "ERREUR! L'utilisateur a refusé ta demande. Tu devrais lui demander pourquoi avoir refusé, et comment améliorer cette partie." + files = [f.name for f in full_path.iterdir()] + + if not files: + return f"Le dossier '{folder}' est vide." + + return "\n".join(files) except Exception as e: - return f"Erreur lors de l'écriture: {str(e)}" + return f"Erreur lors de la lecture du dossier : {str(e)}" @tool @@ -99,40 +126,21 @@ def editTodo(index:int, todoState:int, state: Annotated[dict, InjectedState], to }) @tool -def addTodo(name:str, description:str, state: Annotated[dict, InjectedState], tool_call_id: Annotated[str, InjectedToolCallId])->Command: +def setTodo(todoList:List[Tuple[str, str]], state: Annotated[dict, InjectedState], tool_call_id: Annotated[str, InjectedToolCallId])->Command: """ - Ajouter une nouvelle tâche/TODO + Définir la liste des tâches à faire / TODO. + Permet aussi de la supprimer en appelant avec une liste vide. Args: - name (str): Nom de cette tâche - description (str): Une ou deux phrases pour décrire le travail à effectuer dans ce TODO + todoList (List[Tuple[str, str]]): Une liste de tuples (str, str), donc le premier str est le nom de la tâche, et le second sa description, le travail à effectuer dans ce TODO """ - if "todo" not in state.keys(): state["todo"] = [] + todo = [] - state["todo"] = [TodoElement.fromJSON(e) for e in state["todo"]] # Convertion vers de vraies instances - state["todo"].append(TodoElement(name, description)) + for t in todoList: + todo.append(TodoElement(t[0], t[1])) return Command(update={ "messages": [ToolMessage(content="Réussite!", tool_call_id=tool_call_id)], - "todo": [x.toJSON() for x in state["todo"]] # Update du state, # medium.com/@o39joey/a-comprehensive-guide-to-langgraph-managing-agent-state-with-tools-ae932206c7d7 - }) - -@tool -def removeTodo(index:int, state: Annotated[dict, InjectedState], tool_call_id: Annotated[str, InjectedToolCallId])->Command: - """ - 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 - """ - if "todo" not in state.keys(): return Command(update={"messages": [ToolMessage(content="Echec!", tool_call_id=tool_call_id)]}) - if len(state["todo"]) <= index: - # Erreur, l'index est trop grand - return Command(update={"messages": [ToolMessage(content="Index en dehors de la liste, echec!", tool_call_id=tool_call_id)]}) - - state['todo'].pop(index) - return Command(update={ - "messages": [ToolMessage(content="Réussite!", tool_call_id=tool_call_id)], - "todo": [x for x in state["todo"]] # Update du state, # medium.com/@o39joey/a-comprehensive-guide-to-langgraph-managing-agent-state-with-tools-ae932206c7d7 + "todo": [x.toJSON() for x in todo] # Update du state, # medium.com/@o39joey/a-comprehensive-guide-to-langgraph-managing-agent-state-with-tools-ae932206c7d7 }) @tool @@ -284,7 +292,7 @@ def getTools()->List['Tools']: """ Récupérer la liste des tools """ - return [internet_search, append_part_to_report, editTodo, read_file, search_in_files, addTodo, removeTodo, get_skill] + return [internet_search, append_part_to_report, read_file, search_in_files, get_skill, list_files] # editTodo, setTodo def getWeeklyReportTools()->List['Tools']: """ diff --git a/readme.md b/readme.md index decec4f..966726b 100644 --- a/readme.md +++ b/readme.md @@ -25,3 +25,28 @@ Une fois le dossier **documents_projet** ajouté à la racine, il est possible d ``` python RAG/init.py ``` + +Puis de lancer l'agent +``` +python AgentReact/start.py +``` + +### Exemple de prompt initial +Il faut le coller comme une seule ligne dans l'input, produira des bugs lors de prompts sinon. + +#### Sans TODO +``` +Ton but est d'écrire un rapport de stage sur l'entreprise Diag'n Grow. Commence par préparer un plan avec ton skill "Creation_plan", tu peux rechercher des informations sur l'entreprise avec une recherche internet en utilisant "internet_search". Ensuite, rédige chacune des parties du plan, en utilisant l'outil "append_part_to_report". +Tu as aussi des rapports de chaque semaine de stage dans le dossier `rapports_resumes`, tu peux en lister les fichiers avec l'outil "list_files". +En plus de ces rapports, tu as une base de données de ce qui a été fait, en plus détaillé, avec l'outil "search_in_files". +Bon couraj +``` + + +#### Avec TODO +``` +Ton but est d'écrire un rapport de stage sur l'entreprise Diag'n Grow. Commence par préparer un plan avec ton skill "Creation_plan", tu peux rechercher des informations sur l'entreprise avec une recherche internet en utilisant "internet_search". Ensuite, rédige chacune des parties du plan, en utilisant l'outil "append_part_to_report". En faisant cela, n'oublie pas de créer une liste de tâches(TODO), et de les garder à jour. A chaque fois qu'une partie du rapport est validée, mets à jour ta liste de tâches pour garder une trace de ta progression. +Tu as aussi des rapports de chaque semaine de stage dans le dossier `rapports_resumes`, tu peux en lister les fichiers avec l'outil "list_files". +En plus de ces rapports, tu as une base de données de ce qui a été fait, en plus détaillé, avec l'outil "search_in_files". +Bon couraj +``` \ No newline at end of file