| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148 |
- """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()
|