From 7bc481c60e8e6173a7a4e0105efa96ba4df82cc6 Mon Sep 17 00:00:00 2001 From: Brad Stein Date: Thu, 5 Feb 2026 15:28:06 -0300 Subject: [PATCH] atlasbot: harden quick intent routing --- atlasbot/engine/answerer.py | 20 +++++++++++++++----- atlasbot/engine/intent_router.py | 6 ++++-- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/atlasbot/engine/answerer.py b/atlasbot/engine/answerer.py index 790f82c..f2bfb9a 100644 --- a/atlasbot/engine/answerer.py +++ b/atlasbot/engine/answerer.py @@ -1729,6 +1729,7 @@ def _spine_answer(intent: IntentMatch, spine_line: str | None) -> str | None: "nodes_count": _spine_nodes_answer, "nodes_ready": _spine_nodes_answer, "nodes_non_rpi": _spine_non_rpi_answer, + "hardware_mix": _spine_hardware_answer, "postgres_connections": _spine_postgres_answer, "postgres_hottest": _spine_postgres_answer, "namespace_most_pods": _spine_namespace_answer, @@ -1759,6 +1760,10 @@ def _spine_non_rpi_answer(line: str) -> str: return line +def _spine_hardware_answer(line: str) -> str: + return line + + def _spine_hottest_answer(kind: str, line: str) -> str: metric = kind.split("_", 1)[1] hottest = _parse_hottest(line, metric) @@ -1799,15 +1804,20 @@ def _spine_from_summary(summary: dict[str, Any]) -> dict[str, str]: def _spine_from_counts(summary: dict[str, Any]) -> dict[str, str]: counts = summary.get("counts") if isinstance(summary.get("counts"), dict) else {} inventory = summary.get("inventory") if isinstance(summary.get("inventory"), dict) else {} + nodes = summary.get("nodes") if isinstance(summary.get("nodes"), dict) else {} workers = inventory.get("workers") if isinstance(inventory.get("workers"), dict) else {} - total = counts.get("nodes_total") - ready = counts.get("nodes_ready") - not_ready = None - if isinstance(inventory.get("not_ready_names"), list): + total = nodes.get("total") + ready = nodes.get("ready") + not_ready = nodes.get("not_ready") + if total is None: + total = counts.get("nodes_total") + if ready is None: + ready = counts.get("nodes_ready") + if not_ready is None and isinstance(inventory.get("not_ready_names"), list): not_ready = len(inventory.get("not_ready_names") or []) workers_ready = workers.get("ready") workers_total = workers.get("total") - if total is None and ready is None: + if total is None and ready is None and not_ready is None: return {} parts = [] if total is not None: diff --git a/atlasbot/engine/intent_router.py b/atlasbot/engine/intent_router.py index feae734..869fefa 100644 --- a/atlasbot/engine/intent_router.py +++ b/atlasbot/engine/intent_router.py @@ -17,8 +17,8 @@ _HOTTEST_TERMS = r"(hottest|hot|highest|max(?:imum)?|peak|top|most|worst|spikies _CPU_TERMS = r"(cpu|processor|processors|compute|core|cores|load|load\\s+avg|load\\s+average|util(?:ization)?|usage)" _RAM_TERMS = r"(ram|memory|mem|heap|rss|resident|swap)" _NET_TERMS = r"(net|network|bandwidth|throughput|traffic|rx|tx|ingress|egress|bits|bytes|packets|pps|bps)" -_IO_TERMS = r"(io|i/o|disk\\s+io|disk\\s+activity|read/?write|storage\\s+io|iops|latency)" -_DISK_TERMS = r"(disk|storage|volume|pvc|filesystem|fs|capacity|space|full|usage)" +_IO_TERMS = r"(\\bio\\b|i/o|disk\\s+io|disk\\s+activity|read/?write|storage\\s+io|iops|latency)" +_DISK_TERMS = r"(disk|storage|volume|pvc|filesystem|fs|capacity|\\bspace\\b|full|usage)" _PG_TERMS = r"(postgres|postgresql|pg\\b|database|db|sql|psql)" _CONN_TERMS = r"(connections?|conn|pool|sessions?|clients?|active\\s+connections?|open\\s+connections?)" _DB_HOT_TERMS = r"(hottest|busiest|most|largest|top|heaviest|noisiest|highest\\s+load)" @@ -26,6 +26,7 @@ _NAMESPACE_TERMS = r"(namespace|namespaces|ns\\b|tenant|workload\\s+namespace)" _PODS_TERMS = r"(pods?|workloads?|tasks?|containers?|deployments?|jobs?|cronjobs?|daemonsets?|statefulsets?)" _NON_RPI_TERMS = r"(non[-\\s]?raspberry|not\\s+raspberry|non[-\\s]?rpi|not\\s+rpi|amd64|x86|x86_64|intel|ryzen|jetson|arm64\\b(?!.*rpi))" _PRESSURE_TERMS = r"(pressure|overload|hotspot|bottleneck|saturation|headroom|strain|stress|critical|warning|at\\s+capacity|near\\s+limit)" +_HARDWARE_TERMS = r"(hardware|arch(?:itecture)?|platform|mix|profile|node\\s+types?)" def route_intent(question: str) -> IntentMatch | None: @@ -49,6 +50,7 @@ def route_intent(question: str) -> IntentMatch | None: IntentMatch("nodes_ready", 85), ), (lambda: _all(_NON_RPI_TERMS) and (_any(_NODE_TERMS) or "cluster" in text), IntentMatch("nodes_non_rpi", 80)), + (lambda: _all(_HARDWARE_TERMS) and (_has(_NODE_TERMS) or "cluster" in text), IntentMatch("hardware_mix", 75)), (lambda: _all(_HOTTEST_TERMS, _CPU_TERMS), IntentMatch("hottest_cpu", 80)), (lambda: _all(_HOTTEST_TERMS, _RAM_TERMS), IntentMatch("hottest_ram", 80)), (lambda: _all(_HOTTEST_TERMS, _NET_TERMS), IntentMatch("hottest_net", 80)),