| """REPL minimal de Q&A NL → JeuxDeMots. |
| |
| Usage : |
| python -m jdm_agent.apps.qa_cli |
| python -m jdm_agent.apps.qa_cli --question "synonymes de voiture" |
| python -m jdm_agent.apps.qa_cli --provider ollama --model llama3.1:8b |
| |
| Variables d'environnement (alternatives aux flags) : |
| LLM_PROVIDER, LLM_MODEL, LLM_TEMPERATURE |
| ANTHROPIC_API_KEY, OPENAI_API_KEY (selon provider) |
| """ |
| from __future__ import annotations |
|
|
| from jdm_agent.apps import _console |
|
|
| import argparse |
| import os |
| import sys |
| from typing import Optional |
|
|
| from jdm_agent.client import JDMClient |
| from jdm_agent.tools.jdm_agent import ask, build_jdm_agent, stream |
| from jdm_agent.tools.llm_factory import get_llm |
|
|
|
|
| BANNER = """ |
| ╔══════════════════════════════════════════════════════════════╗ |
| ║ JDM Q&A — réponses ancrées dans le graphe JeuxDeMots ║ |
| ║ Tape une question (en français) ou 'exit' pour quitter. ║ |
| ║ '/help' pour l'aide, '/tools' pour lister les outils. ║ |
| ╚══════════════════════════════════════════════════════════════╝ |
| """ |
|
|
| HELP = """ |
| Commandes : |
| /help cette aide |
| /tools liste les outils JDM disponibles |
| /verbose on|off active/désactive la trace des appels d'outils |
| /clear efface l'écran |
| exit | quit quitter |
| |
| Exemples de questions : |
| - Qu'est-ce qu'un chat ? |
| - Donne-moi des synonymes de voiture. |
| - Quel est le rapport entre chat et internet ? |
| - Le mot "avocat" a-t-il plusieurs sens ? |
| - Quelle est la couleur du sang d'après JDM ? |
| """ |
|
|
|
|
| def _print_tool_calls(tool_calls: list[dict]) -> None: |
| if not tool_calls: |
| return |
| print("\n[outils appelés]") |
| for tc in tool_calls: |
| args = tc.get("args") or {} |
| args_str = ", ".join(f"{k}={v!r}" for k, v in args.items()) |
| print(f" • {tc['name']}({args_str})") |
|
|
|
|
| def _stream_printer(verbose: bool): |
| """Imprime un événement par étape de l'agent — montre que ça avance.""" |
| import time |
| t0 = [time.time()] |
|
|
| def on_event(ev: dict) -> None: |
| dt = time.time() - t0[0] |
| kind = ev["kind"] |
| if kind == "AIMessage": |
| tcs = ev.get("tool_calls") or [] |
| if tcs: |
| for tc in tcs: |
| args = ", ".join(f"{k}={v!r}" for k, v in (tc.get("args") or {}).items()) |
| print(f" ⏱ {dt:5.1f}s → appel {tc['name']}({args})", flush=True) |
| else: |
| |
| preview = (ev.get("content") or "").strip().replace("\n", " ")[:80] |
| if preview: |
| print(f" ⏱ {dt:5.1f}s ← réponse du modèle ({len(ev['content'])} chars)", flush=True) |
| elif kind == "ToolMessage": |
| content = ev.get("content") or "" |
| preview = content[:100].replace("\n", " ") |
| print(f" ⏱ {dt:5.1f}s ← outil {ev.get('name')} renvoie {len(content)} chars : {preview}…", flush=True) |
| t0[0] = time.time() |
|
|
| return on_event if verbose else None |
|
|
|
|
| def run_repl(provider: Optional[str], model: Optional[str], verbose: bool) -> int: |
| print(BANNER) |
| print(f"Provider : {provider or os.environ.get('LLM_PROVIDER', 'anthropic')}") |
| print(f"Modèle : {model or os.environ.get('LLM_MODEL', 'claude-sonnet-4-5')}") |
| print("Chargement du client JDM et du modèle…") |
|
|
| client = JDMClient() |
| try: |
| llm = get_llm(provider=provider, model=model) |
| agent = build_jdm_agent(client=client, llm=llm) |
| except Exception as e: |
| print(f"\n[erreur] impossible d'initialiser le modèle: {e}", file=sys.stderr) |
| return 2 |
|
|
| print("Prêt.\n") |
| show_tools = verbose |
| while True: |
| try: |
| q = input("> ").strip() |
| except (EOFError, KeyboardInterrupt): |
| print() |
| break |
| if not q: |
| continue |
| if q.lower() in {"exit", "quit"}: |
| break |
| if q == "/help": |
| print(HELP) |
| continue |
| if q == "/clear": |
| os.system("cls" if os.name == "nt" else "clear") |
| continue |
| if q.startswith("/verbose"): |
| show_tools = "on" in q |
| print(f"verbose = {show_tools}") |
| continue |
| if q == "/tools": |
| from jdm_agent.tools.jdm_tools import ALL_TOOLS |
| for t in ALL_TOOLS: |
| print(f" - {t.name}: {t.description.splitlines()[0]}") |
| continue |
|
|
| try: |
| print("(réflexion en cours…)", flush=True) |
| on_event = _stream_printer(show_tools) |
| out = stream(agent, q, on_event=on_event) |
| except Exception as e: |
| print(f"[erreur] {e}", file=sys.stderr) |
| continue |
|
|
| print() |
| print(out["answer"]) |
| print() |
|
|
| client.close() |
| return 0 |
|
|
|
|
| def main() -> int: |
| p = argparse.ArgumentParser(description="REPL Q&A JeuxDeMots (LangChain agent).") |
| p.add_argument("--provider", default=None, help="LLM provider (anthropic, openai, ollama, ...)") |
| p.add_argument("--model", default=None, help="Nom du modèle (claude-sonnet-4-5, gpt-4o, llama3.1:8b, ...)") |
| p.add_argument("--question", "-q", default=None, help="Pose une seule question puis quitte.") |
| p.add_argument("--verbose", action="store_true", help="Affiche les appels d'outils.") |
| args = p.parse_args() |
|
|
| if args.question: |
| client = JDMClient() |
| llm = get_llm(provider=args.provider, model=args.model) |
| agent = build_jdm_agent(client=client, llm=llm) |
| if args.verbose: |
| print("(réflexion en cours…)", flush=True) |
| out = stream(agent, args.question, on_event=_stream_printer(True)) |
| else: |
| out = ask(agent, args.question) |
| print() |
| print(out["answer"]) |
| client.close() |
| return 0 |
|
|
| return run_repl(args.provider, args.model, args.verbose) |
|
|
|
|
| if __name__ == "__main__": |
| sys.exit(main()) |
|
|