"""Point d'entrée principal — CLI interactif et visualisation du graphe. Ce fichier est FOURNI COMPLET — ce n'est pas un exercice. Il fonctionnera dès que react_agent.py sera implémenté. Usage : uv run python main.py # mode interactif uv run python main.py --verbose # avec traces des outils uv run python main.py --ask "Prix d'un laptop ?" uv run python main.py --visualize # diagramme Mermaid """ import argparse import asyncio import os import sys import uuid from pathlib import Path from dotenv import load_dotenv from langchain_core.messages import HumanMessage load_dotenv() async def chat(question: str, thread_id: str, verbose: bool = False) -> str: """Envoie une question à l'agent et retourne la réponse finale.""" # Import ici pour éviter l'import au démarrage si --visualize from src.agents.react_agent import agent config = {"configurable": {"thread_id": thread_id}} initial_state = {"messages": [HumanMessage(content=question)], "iterations": 0} if verbose: step_num = 0 async for event in agent.astream_events(initial_state, config=config, version="v2"): kind = event.get("event", "") if kind == "on_tool_start": step_num += 1 print(f"\n 🔧 [{step_num}] {event.get('name', '?')} " f"← {event.get('data', {}).get('input', {})}") elif kind == "on_tool_end": out = str(event.get("data", {}).get("output", "")) print(f" → {out[:100]}{'...' if len(out) > 100 else ''}") final = await agent.aget_state(config) msgs = final.values.get("messages", []) return str(msgs[-1].content) if msgs else "(aucune réponse)" else: result = await agent.ainvoke(initial_state, config=config) msgs = result.get("messages", []) return str(msgs[-1].content) if msgs else "(aucune réponse)" async def interactive_loop(verbose: bool = False) -> None: """Boucle interactive. Commandes : exit | reset | verbose""" thread_id = f"session-{uuid.uuid4().hex[:8]}" show_verbose = verbose print(f"\n{'═'*55}") print(" Agent Catalogue — Formation Agents IA S1") print(f"{'═'*55}") print(f" Modèle : {os.getenv('MODEL_NAME', 'llama3.1:8b')}") print(f" Session : {thread_id}") print(f" Aide : exit | reset | verbose") print(f"{'═'*55}\n") while True: try: question = input("Vous : ").strip() except (KeyboardInterrupt, EOFError): print("\nAu revoir !") break if not question: continue if question.lower() in ("exit", "quit", "q"): print("Au revoir !") break if question.lower() == "reset": thread_id = f"session-{uuid.uuid4().hex[:8]}" print(f"Nouvelle session : {thread_id}\n") continue if question.lower() == "verbose": show_verbose = not show_verbose print(f"Verbose {'activé' if show_verbose else 'désactivé'}\n") continue print("\nAgent : ", end="", flush=True) try: response = await chat(question, thread_id, verbose=show_verbose) print(response) except NotImplementedError as e: print(f"\n[TODO] {e}") print("→ Implémentez les fonctions marquées TODO dans src/") except Exception as e: print(f"\n[Erreur] {e}") print("→ Vérifiez qu'Ollama tourne : ollama serve") print() async def ask_once(question: str, verbose: bool = False) -> None: """Pose une question unique.""" thread_id = f"cli-{uuid.uuid4().hex[:8]}" print(f"\nQuestion : {question}\n{'─'*50}") try: response = await chat(question, thread_id, verbose=verbose) print(f"\n{response}\n") except NotImplementedError as e: print(f"\n[TODO] {e}") sys.exit(1) except Exception as e: print(f"\n[Erreur] {e}") sys.exit(1) def visualize_graph() -> None: """Génère le diagramme Mermaid et le sauvegarde dans docs/graphs/.""" from src.agents.react_agent import agent mermaid = agent.get_graph().draw_mermaid() print(f"\n{'═'*50}\n Graphe LangGraph — Mermaid\n{'═'*50}") print(mermaid) path = Path("docs/graphs/agent_react.md") path.parent.mkdir(parents=True, exist_ok=True) path.write_text(f"# Graphe Agent ReAct\n\n```mermaid\n{mermaid}```\n", encoding="utf-8") print(f"\n✅ Sauvegardé dans {path}") print(" → Visualiser sur https://mermaid.live\n") def main() -> None: parser = argparse.ArgumentParser(description="Agent Catalogue — S1") parser.add_argument("--visualize", action="store_true") parser.add_argument("--ask", type=str, metavar="QUESTION") parser.add_argument("--verbose", action="store_true") args = parser.parse_args() if args.visualize: visualize_graph() elif args.ask: asyncio.run(ask_once(args.ask, verbose=args.verbose)) else: asyncio.run(interactive_loop(verbose=args.verbose)) if __name__ == "__main__": main()