Ctrl K
ComunidadGuías…

app/ping.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"""Diagnostic /ping : teste le chemin LLM complet maillon par maillon.

Chaîne : bot Telegram -> API FastAPI -> bridge socat -> Ollama -> modèle.
Chaque maillon est testé indépendamment pour nommer précisément celui
qui est cassé en cas d'échec. Lecture seule, sans coût d'inférence pour
les 4 premiers checks ; le 5e fait une micro-inférence (prompt vide de
outils, 1 token) pour prouver la chaîne d'inférence de bout en bout.
"""

import os
import time

import httpx

OLLAMA_BASE_URL = os.environ.get("OLLAMA_BASE_URL", "http://172.20.0.1:11434")
OLLAMA_MODEL = os.environ.get("OLLAMA_MODEL", "qwen2.5:14b")


async def ping_report() -> str:
    """Teste chaque maillon et nomme le premier cassé."""
    lines: list[str] = ["🏓 **Vektor /ping** — chemin LLM complet"]
    failures: list[str] = []

    # 1. Ollama joignable via le bridge (le maillon qui a déjà cassé deux fois)
    t0 = time.perf_counter()
    try:
        async with httpx.AsyncClient(timeout=6) as client:
            response = await client.get(f"{OLLAMA_BASE_URL}/api/tags")
        dt = (time.perf_counter() - t0) * 1000
        if response.status_code == 200:
            names = [m.get("name", "") for m in response.json().get("models", [])]
            lines.append(f"🟢 Bridge Ollama ({OLLAMA_BASE_URL}) — {dt:.0f} ms, {len(names)} modèles")
        else:
            failures.append("bridge Ollama")
            lines.append(f"🔴 Bridge Ollama : HTTP {response.status_code}")
    except httpx.HTTPError as exc:
        failures.append("bridge Ollama")
        lines.append(
            f"🔴 Bridge Ollama ({OLLAMA_BASE_URL}) injoignable "
            f"({exc.__class__.__name__}) — vérifier le conteneur "
            "vektor-ollama-bridge et OLLAMA_NETBIRD_HOST"
        )
        return "\n".join(lines + [f"\n❌ Premier maillon cassé : **{failures[0]}**"])

    # 2. Le modèle configuré est-il présent ?
    model_present = any(OLLAMA_MODEL in name for name in names)
    if model_present:
        lines.append(f"🟢 Modèle `{OLLAMA_MODEL}` présent côté Ollama")
    else:
        failures.append("modèle Ollama")
        lines.append(
            f"🔴 Modèle `{OLLAMA_MODEL}` ABSENT — modèles vus : "
            + ", ".join(names[:5])
            + " — vérifier OLLAMA_MODEL et le tag image (ollama pull)"
        )
        lines.insert(1, f"⚠️ Premier maillon cassé : **{failures[0]}**")
        return "\n".join(lines)

    # 3. Le modèle est-il chargé en RAM (keep_alive) ?
    try:
        async with httpx.AsyncClient(timeout=6) as client:
            ps = await client.get(f"{OLLAMA_BASE_URL}/api/ps")
        loaded = [m.get("name", "") for m in ps.json().get("models", [])]
        if any(OLLAMA_MODEL in name for name in loaded):
            lines.append(f"🟢 Modèle chargé en RAM (keep_alive) : réponse immédiate attendue")
        else:
            lines.append(
                "🟡 Modèle NON chargé en RAM — prochaine question LLM : "
                "+40-60 s de chargement (pas une panne)"
            )
    except httpx.HTTPError:
        lines.append("🟡 /api/ps indisponible (état RAM inconnu)")

    # 4. Inférence réelle de bout en bout (micro-prompt, 1 token attendu)
    t0 = time.perf_counter()
    try:
        async with httpx.AsyncClient(timeout=90) as client:
            response = await client.post(
                f"{OLLAMA_BASE_URL}/api/generate",
                json={
                    "model": OLLAMA_MODEL,
                    "prompt": "Réponds uniquement: OK",
                    "stream": False,
                    "options": {"num_predict": 5},
                },
            )
        dt = time.perf_counter() - t0
        if response.status_code == 200 and response.json().get("response"):
            sample = response.json()["response"].strip()[:40]
            lines.append(f"🟢 Inférence de bout en bout — {dt:.0f} s (réponse: «{sample}»)")
            lines.append("\n✅ **Chemin LLM complet opérationnel**")
        else:
            failures.append("inférence Ollama")
            lines.append(f"🔴 Inférence : HTTP {response.status_code} ou réponse vide")
    except httpx.HTTPError as exc:
        failures.append("inférence Ollama (timeout 90 s — CPU chargé ?)")
        lines.append(f"🔴 Inférence : {exc.__class__.__name__} après 90 s")

    if failures:
        lines.insert(1, f"⚠️ Maillon dégradé : **{failures[0]}**")
    return "\n".join(lines)