From 9e06d7afc8adb2ee0c51f2b3df454fee8b5add42 Mon Sep 17 00:00:00 2001 From: Brad Stein Date: Tue, 27 Jan 2026 20:02:09 -0300 Subject: [PATCH] atlasbot: route subjective queries to LLM --- services/comms/atlasbot-deployment.yaml | 2 +- services/comms/scripts/atlasbot/bot.py | 60 +++++++++++++++++-------- 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/services/comms/atlasbot-deployment.yaml b/services/comms/atlasbot-deployment.yaml index 609c245..d8ce3ee 100644 --- a/services/comms/atlasbot-deployment.yaml +++ b/services/comms/atlasbot-deployment.yaml @@ -16,7 +16,7 @@ spec: labels: app: atlasbot annotations: - checksum/atlasbot-configmap: manual-atlasbot-68 + checksum/atlasbot-configmap: manual-atlasbot-69 vault.hashicorp.com/agent-inject: "true" vault.hashicorp.com/role: "comms" vault.hashicorp.com/agent-inject-secret-turn-secret: "kv/data/atlas/comms/turn-shared-secret" diff --git a/services/comms/scripts/atlasbot/bot.py b/services/comms/scripts/atlasbot/bot.py index db0f560..141b971 100644 --- a/services/comms/scripts/atlasbot/bot.py +++ b/services/comms/scripts/atlasbot/bot.py @@ -1911,19 +1911,6 @@ def cluster_answer( history_lines: list[str] | None = None, ) -> str: metrics_summary = snapshot_context(prompt, snapshot) - if _is_insight_query(prompt): - candidates = _insight_candidates(inventory, snapshot) - used_keys = _recent_insight_keys(history_lines or []) - selected = _select_insight(prompt, candidates, used_keys=used_keys) - if selected: - key, raw_text, confidence = selected - formatted = _format_insight_text(key, raw_text) - if not formatted: - formatted = raw_text - prefix = _insight_prefix(prompt) - if prefix: - formatted = prefix + formatted - return _format_confidence(formatted, confidence) structured = structured_answer( prompt, inventory=inventory, @@ -2422,6 +2409,17 @@ def _history_payload_lines(history_payload: list[Any]) -> list[str]: return [line for line in lines if line] +def _append_history_context(context: str, history_lines: list[str]) -> str: + lines = [line.strip() for line in history_lines if isinstance(line, str) and line.strip()] + if not lines: + return context + snippet = "\n".join(lines[-6:]) + combined = context + "\nRecent chat:\n" + snippet if context else "Recent chat:\n" + snippet + if len(combined) > MAX_CONTEXT_CHARS: + combined = combined[: MAX_CONTEXT_CHARS - 3].rstrip() + "..." + return combined + + # Internal HTTP endpoint for cluster answers (website uses this). class _AtlasbotHandler(BaseHTTPRequestHandler): server_version = "AtlasbotHTTP/1.0" @@ -2493,15 +2491,25 @@ class _AtlasbotHandler(BaseHTTPRequestHandler): ) fallback = "I don't have enough data to answer that." if cluster_query: - answer = cluster_answer( + facts_answer = cluster_answer( cleaned, inventory=inventory, snapshot=snapshot, workloads=workloads, history_lines=history_lines, ) - if not answer: - answer = fallback + open_ended = _is_subjective_query(cleaned) or _knowledge_intent(cleaned) + if open_ended: + llm_context = _append_history_context(context, history_lines) + answer = ollama_reply( + ("http", "internal"), + cleaned, + context=llm_context, + fallback=facts_answer or fallback, + use_history=False, + ) + else: + answer = facts_answer or fallback else: llm_prompt = cleaned answer = ollama_reply( @@ -2761,11 +2769,13 @@ def _ollama_call(hist_key, prompt: str, *, context: str, use_history: bool = Tru "When a cluster snapshot is provided, never answer about unrelated meanings of 'Atlas' (maps, mythology, Apache Atlas, etc). " "Treat 'hottest' as highest utilization (CPU/RAM/NET/IO) rather than temperature. " "If you infer or synthesize, say 'Based on the snapshot' and keep it brief. " + "For subjective prompts (interesting, favorite, unconventional), pick one or two observations from the context, explain why they stand out in 1-2 sentences, and avoid repeating the same observation as the last response if you can. " "Prefer exact repo paths and Kubernetes resource names when relevant. " "Never include or request secret values. " "Do not suggest commands unless explicitly asked. " "Respond in plain sentences; do not return JSON or code fences unless explicitly asked. " "Translate metrics into natural language instead of echoing raw label/value pairs. " + "Avoid bare lists unless the user asked for a list; weave numbers into sentences. " "Do not answer by only listing runbooks; if the question is about Atlas/Othrys, summarize the cluster first and mention docs only if useful. " "If the question is not about Atlas/Othrys and no cluster context is provided, answer using general knowledge and say when you are unsure. " "If the answer is not grounded in the provided context or tool data, say you do not know. " @@ -2974,15 +2984,27 @@ def sync_loop(token: str, room_id: str): fallback = "I don't have enough data to answer that." if cluster_query: - reply = cluster_answer( + facts_answer = cluster_answer( cleaned_body, inventory=inventory, snapshot=snapshot, workloads=workloads, history_lines=history[hist_key], ) - if not reply: - reply = fallback + open_ended = _is_subjective_query(cleaned_body) or _knowledge_intent(cleaned_body) + if open_ended: + llm_context = _append_history_context(context, history[hist_key]) + reply = ollama_reply_with_thinking( + token, + rid, + hist_key, + cleaned_body, + context=llm_context, + fallback=facts_answer or fallback, + use_history=False, + ) + else: + reply = facts_answer or fallback else: llm_prompt = cleaned_body reply = ollama_reply_with_thinking(