From cc3739efe4d93d6673aa57c0eda054b3a38e815d Mon Sep 17 00:00:00 2001 From: Brad Stein Date: Mon, 2 Feb 2026 02:16:00 -0300 Subject: [PATCH] atlasbot: enforce metric routing for namespace pods --- atlasbot/engine/answerer.py | 53 ++++++++++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/atlasbot/engine/answerer.py b/atlasbot/engine/answerer.py index 120e877..a140651 100644 --- a/atlasbot/engine/answerer.py +++ b/atlasbot/engine/answerer.py @@ -227,11 +227,19 @@ class AnswerEngine: ) if any(term in normalized.lower() for term in cluster_terms): classify["needs_snapshot"] = True + lowered_norm = normalized.lower() + if ( + ("namespace" in lowered_norm and ("pod" in lowered_norm or "pods" in lowered_norm)) + or re.search(r"\bmost\s+pods\b", lowered_norm) + or re.search(r"\bpods\s+running\b", lowered_norm) + ): + classify["question_type"] = "metric" + classify["needs_snapshot"] = True if re.search(r"\b(how many|count|number of|list)\b", normalized.lower()): classify["question_type"] = "metric" hottest_terms = ("hottest", "highest", "lowest", "most") - metric_terms = ("cpu", "ram", "memory", "net", "network", "io", "disk", "load", "usage") - lowered_question = normalized.lower() + metric_terms = ("cpu", "ram", "memory", "net", "network", "io", "disk", "load", "usage", "pod", "pods", "namespace") + lowered_question = f"{question} {normalized}".lower() if any(term in lowered_question for term in hottest_terms) and any(term in lowered_question for term in metric_terms): classify["question_type"] = "metric" @@ -312,15 +320,24 @@ class AnswerEngine: if self._settings.debug_pipeline: _debug_log("metric_facts_selected", {"facts": metric_facts}) if classify.get("question_type") in {"metric", "diagnostic"} and not metric_facts: - for line in summary_lines: - if "hardware_usage_top:" in line: - metric_facts = [seg.strip() for seg in line.split(" | ") if seg.strip().startswith("hardware_usage_top:")] - break - if not metric_facts: + lowered_q = f"{question} {normalized}".lower() + if "namespace" in lowered_q and "pod" in lowered_q: for line in summary_lines: - if "hardware_usage_avg:" in line: - metric_facts = [seg.strip() for seg in line.split(" | ") if seg.strip().startswith("hardware_usage_avg:")] + if line.startswith("namespaces_top:"): + metric_facts = [line] break + if not metric_facts: + hardware_tokens = ("hardware", "class", "type", "rpi", "jetson", "amd64", "arm64") + if any(tok in lowered_q for tok in hardware_tokens): + for line in summary_lines: + if "hardware_usage_top:" in line: + metric_facts = [seg.strip() for seg in line.split(" | ") if seg.strip().startswith("hardware_usage_top:")] + break + if not metric_facts: + for line in summary_lines: + if "hardware_usage_avg:" in line: + metric_facts = [seg.strip() for seg in line.split(" | ") if seg.strip().startswith("hardware_usage_avg:")] + break if metric_facts: key_facts = _merge_fact_lines(metric_facts, key_facts) if self._settings.debug_pipeline: @@ -513,6 +530,19 @@ class AnswerEngine: if classify.get("question_type") in {"metric", "diagnostic"} and metric_facts: reply = _metric_fact_guard(reply, metric_facts, keyword_tokens) + if classify.get("question_type") in {"metric", "diagnostic"}: + lowered_q = f"{question} {normalized}".lower() + if ("io" in lowered_q or "i/o" in lowered_q) and ("node" in lowered_q or "nodes" in lowered_q): + io_facts = _extract_hottest_facts(summary_lines, lowered_q) + io_line = next((fact for fact in io_facts if fact.startswith("hottest_io_node")), None) + if io_line: + reply = f"From the latest snapshot: {io_line}." + if "namespace" in lowered_q and "pod" in lowered_q: + for line in summary_lines: + if line.startswith("namespaces_top:"): + reply = f"From the latest snapshot: {line}." + break + if classify.get("question_type") in {"metric", "diagnostic"}: lowered_q = f"{question} {normalized}".lower() if any(tok in lowered_q for tok in ("hardware", "class", "type", "rpi", "jetson", "amd64", "arm64")) and any( @@ -1091,6 +1121,8 @@ def _extract_hottest_facts(lines: list[str], question: str) -> list[str]: if not facts: return [] wanted = [] + if ("io" in lowered or "i/o" in lowered) and ("disk" in lowered or "storage" in lowered): + return [fact for fact in facts if fact.startswith("hottest_io_node")] if any(term in lowered for term in ("cpu", "processor")): wanted.append("hottest_cpu_node") if any(term in lowered for term in ("ram", "memory")): @@ -1156,6 +1188,9 @@ def _metric_candidate_lines(lines: list[str], keywords: list[str] | None, limit: "p95", "percent", "pressure", + "pod", + "pods", + "namespace", } candidates: list[str] = [] for line in lines: