import logging import httpx from app.services.search import SearchResult logger = logging.getLogger(__name__) _SEVERITY_RANK = {"CRITICAL": 0, "ERROR": 1, "WARN": 2, "WARNING": 2} _PROMPT_TEMPLATE = """\ You are a homelab diagnostic assistant. A user described a symptom and the system retrieved relevant log entries. Analyze the log entries below and write a 2-4 sentence plain-language diagnosis. Focus on errors and their likely root cause. Be specific and concise — name the services involved, not generic platitudes. User query: {query} Log entries ({n} shown, highest severity first): {log_block} Diagnosis:""" def _build_context(entries: list[SearchResult], max_entries: int = 25) -> str: ranked = sorted( entries, key=lambda e: (_SEVERITY_RANK.get(e.severity or "", 3), e.timestamp_iso or ""), )[:max_entries] return "\n".join( f"[{e.timestamp_iso or '?'}] [{e.severity or 'INFO'}] {e.text[:200]}" for e in ranked ) def summarize( query: str, entries: list[SearchResult], llm_url: str, llm_model: str, api_key: str | None = None, timeout: float = 120.0, ) -> str | None: if not entries: return None log_block = _build_context(entries) prompt = _PROMPT_TEMPLATE.format(query=query, n=min(len(entries), 25), log_block=log_block) headers = {"Authorization": f"Bearer {api_key}"} if api_key else {} try: resp = httpx.post( f"{llm_url.rstrip('/')}/v1/chat/completions", json={ "model": llm_model, "messages": [{"role": "user", "content": prompt}], "stream": False, }, headers=headers, timeout=timeout, ) resp.raise_for_status() choices = resp.json().get("choices") or [] if not choices: return None return (choices[0].get("message", {}).get("content") or "").strip() or None except Exception as exc: logger.warning("LLM summarization failed (%s): %s", type(exc).__name__, exc) return None