jdmagent / src /jdm_agent /apps /diagnose.py
expAge
feat: live streaming in qa_cli + layered diagnostic + Windows UTF-8 fix
c19d9d0
"""Diagnostic en couches du système JDM Agent.
Teste séquentiellement :
1. JDMClient brut (HTTP + cache)
2. Outils LangChain (sans LLM)
3. Connectivité Ollama (si choisi)
4. Inférence LLM seule (1 prompt court, pas d'outil)
5. Agent avec un seul tool call
Affiche le timing de chaque étape pour identifier où ça bloque.
Usage :
python -m jdm_agent.apps.diagnose
python -m jdm_agent.apps.diagnose --provider ollama --model llama3.2:3b
"""
from __future__ import annotations
from jdm_agent.apps import _console # noqa: F401 — force stdout UTF-8 (Windows)
import argparse
import os
import sys
import time
import urllib.request
import urllib.error
from jdm_agent.client import JDMClient
from jdm_agent.tools.jdm_tools import set_default_client, get_synonyms
GREEN = "\033[92m"
RED = "\033[91m"
YELLOW = "\033[93m"
RESET = "\033[0m"
def _step(label: str):
print(f"\n── {label} ──", flush=True)
return time.time()
def _ok(t0: float, extra: str = "") -> None:
dt = time.time() - t0
print(f" {GREEN}{RESET} {dt:5.2f}s {extra}", flush=True)
def _fail(t0: float, err: Exception) -> None:
dt = time.time() - t0
print(f" {RED}{RESET} {dt:5.2f}s {type(err).__name__}: {err}", flush=True)
def _warn(msg: str) -> None:
print(f" {YELLOW}!{RESET} {msg}", flush=True)
def check_jdm_client() -> bool:
t = _step("1. JDMClient HTTP")
try:
c = JDMClient()
n = c.node_by_name("chat")
_ok(t, f"node chat id={n.id} w={n.w}")
t2 = time.time()
n2 = c.node_by_name("chat")
dt = time.time() - t2
_ok(t2, f"2e appel (cache disque) en {dt*1000:.1f}ms")
return True
except Exception as e:
_fail(t, e)
return False
def check_tools() -> bool:
t = _step("2. Outils LangChain (sans LLM)")
try:
c = JDMClient()
set_default_client(c)
syns = get_synonyms.invoke({"term": "voiture", "min_weight": 50, "limit": 3})
for s in syns:
print(f" · {s['target']} (w={s['w']})")
_ok(t)
return True
except Exception as e:
_fail(t, e)
return False
def check_ollama(model: str) -> bool:
t = _step(f"3. Ollama (modèle {model})")
url = os.environ.get("OLLAMA_BASE_URL", "http://localhost:11434") + "/api/tags"
try:
with urllib.request.urlopen(url, timeout=3) as r:
import json
data = json.loads(r.read())
tags = [m["name"] for m in data.get("models", [])]
print(f" · serveur joignable ; modèles installés : {tags or '(aucun)'}")
if not any(m.startswith(model.split(':')[0]) for m in tags):
_warn(f"le modèle {model!r} n'est PAS installé. Lancer : ollama pull {model}")
return False
_ok(t)
return True
except urllib.error.URLError as e:
_fail(t, e)
_warn("Ollama ne tourne pas. Démarrer avec : ollama serve")
return False
except Exception as e:
_fail(t, e)
return False
def check_llm_inference(provider: str, model: str) -> bool:
t = _step(f"4. Inférence LLM nue ({provider}:{model}) — peut prendre 10-60s")
try:
from jdm_agent.tools.llm_factory import get_llm
from langchain_core.messages import HumanMessage
llm = get_llm(provider=provider, model=model)
# Force pas d'outils : juste une réponse texte.
out = llm.invoke([HumanMessage(content="Réponds en un seul mot : OUI")])
_ok(t, f"sortie: {(out.content or '').strip()[:80]!r}")
return True
except Exception as e:
_fail(t, e)
return False
def check_agent_one_round(provider: str, model: str) -> bool:
t = _step(f"5. Agent complet : 1 question simple ({provider}:{model})")
try:
from jdm_agent.tools.jdm_agent import build_jdm_agent, stream
client = JDMClient()
set_default_client(client)
from jdm_agent.tools.llm_factory import get_llm
llm = get_llm(provider=provider, model=model)
agent = build_jdm_agent(client=client, llm=llm)
# Stream pour montrer chaque étape.
def on_event(ev):
dt = time.time() - t
kind = ev["kind"]
tcs = ev.get("tool_calls") or []
if kind == "AIMessage" and tcs:
for tc in tcs:
print(f" [{dt:5.1f}s] → appel {tc['name']}({tc.get('args')})")
elif kind == "ToolMessage":
content = (ev.get("content") or "")[:80].replace("\n", " ")
print(f" [{dt:5.1f}s] ← outil {ev.get('name')} : {content}…")
elif kind == "AIMessage":
preview = (ev.get("content") or "").strip().replace("\n", " ")[:80]
if preview:
print(f" [{dt:5.1f}s] ← réponse finale ({len(ev['content'])} chars)")
out = stream(agent, "Donne-moi 2 synonymes de voiture.", on_event=on_event)
print(f" réponse : {out['answer'][:200]}…")
_ok(t)
return True
except Exception as e:
_fail(t, e)
return False
def main() -> int:
p = argparse.ArgumentParser(description="Diagnostic en couches JDM Agent.")
p.add_argument("--provider", default=os.environ.get("LLM_PROVIDER", "ollama"))
p.add_argument("--model", default=os.environ.get("LLM_MODEL", "llama3.2:3b"))
p.add_argument("--skip-llm", action="store_true", help="Saute les étapes 3-5.")
args = p.parse_args()
print(f"{'='*60}\n JDM Agent — diagnostic\n provider={args.provider}, model={args.model}\n{'='*60}")
if not check_jdm_client():
return 1
if not check_tools():
return 1
if args.skip_llm:
print("\n[--skip-llm] : étapes 3-5 sautées.")
return 0
if args.provider == "ollama":
if not check_ollama(args.model):
return 2
if not check_llm_inference(args.provider, args.model):
return 3
if not check_agent_one_round(args.provider, args.model):
return 4
print(f"\n{GREEN}Tout fonctionne.{RESET} Tu peux lancer maintenant :")
print(f" python -m jdm_agent.apps.qa_cli --provider {args.provider} --model {args.model} --verbose")
return 0
if __name__ == "__main__":
sys.exit(main())