"""Ollama VM123 + fallback — Spec 029 T0/T1.""" from __future__ import annotations import os import httpx OLLAMA_BASE_URL = os.getenv("OLLAMA_BASE_URL", "http://10.10.10.123:11434").rstrip("/") AGENTIC_LLM_MODEL = os.getenv("AGENTIC_LLM_MODEL", "qwen2.5:7b-instruct") AGENTIC_EMBED_MODEL = os.getenv("AGENTIC_EMBED_MODEL", "nomic-embed-text") AGENTIC_LLM_ENABLED = os.getenv("AGENTIC_LLM_ENABLED", "false").lower() in ("1", "true", "yes") def ollama_available() -> bool: try: with httpx.Client(timeout=3.0) as c: return c.get(f"{OLLAMA_BASE_URL}/api/tags").status_code == 200 except Exception: return False def _chat(prompt: str, *, system: str | None = None, max_tokens: int = 800) -> tuple[str, str]: if not AGENTIC_LLM_ENABLED or not ollama_available(): return ("", "t0") messages = [] if system: messages.append({"role": "system", "content": system}) messages.append({"role": "user", "content": prompt}) try: with httpx.Client(timeout=120.0) as c: r = c.post( f"{OLLAMA_BASE_URL}/api/chat", json={"model": AGENTIC_LLM_MODEL, "messages": messages, "stream": False}, ) if r.status_code == 200: txt = (r.json().get("message") or {}).get("content", "").strip() if txt: return txt, AGENTIC_LLM_MODEL except Exception: pass return ("", "t0-fallback") def advise_human_action( *, finding_title: str, finding_detail: str, kb_snippets: list[str] | None = None ) -> tuple[str, str]: prompt = ( "Advisor Agentic Ops Ligbox. Português BR, máx 6 frases. O que fazer AGORA?\n" f"Problema: {finding_title}\nDetalhe: {finding_detail}\n" f"KB: {'---'.join(kb_snippets or [])[:2500] or 'N/A'}" ) txt, model = _chat(prompt) if txt: return txt, model return (f"Investigar manualmente: {finding_title}", "t0") def chat_context( *, question: str, kb_snippets: list[str] | None = None, findings_summary: str | None = None, user_role: str = "technician", ) -> tuple[str, str]: """T1 — resposta contextual para janela Desk / bot interno.""" system = ( "És o copiloto Agentic Ops da Ligbox (VM112 wizard, VM122 Desk, VM123 finance). " "Responde em português BR, objectivo, com passos acionáveis. " "Nunca inventes credenciais. Se não souberes, diz o que verificar." ) ctx = [] if findings_summary: ctx.append(f"Findings abertos:\n{findings_summary[:2000]}") if kb_snippets: ctx.append("KB:\n" + "\n---\n".join(kb_snippets[:6])[:4000]) prompt = ( f"Perfil utilizador: {user_role}\n" f"Contexto ops:\n{chr(10).join(ctx) or 'N/A'}\n\n" f"Pergunta: {question}" ) txt, model = _chat(prompt, system=system) if txt: return txt, model return ( "Modo T0 activo — LLM indisponível. Consulte findings e audit log no painel Agentic Ops.", "t0", )