atlasbot: use history for subjective follow-ups

This commit is contained in:
Brad Stein 2026-01-27 18:32:27 -03:00
parent e05a949b9f
commit 113bcdeded

View File

@ -191,6 +191,10 @@ _INSIGHT_HINT_WORDS = {
"cool", "cool",
"unique", "unique",
"notable", "notable",
"coolest",
"favorite",
"favourite",
"trivia",
} }
_OVERVIEW_HINT_WORDS = { _OVERVIEW_HINT_WORDS = {
@ -1550,6 +1554,21 @@ def _is_insight_query(query: str) -> bool:
return False return False
def _is_subjective_query(query: str) -> bool:
q = normalize_query(query)
if not q:
return False
return any(word in q for word in _INSIGHT_HINT_WORDS) or any(
phrase in q
for phrase in (
"what do you think",
"your favorite",
"your favourite",
"your opinion",
)
)
def _is_overview_query(query: str) -> bool: def _is_overview_query(query: str) -> bool:
q = normalize_query(query) q = normalize_query(query)
if not q: if not q:
@ -1602,9 +1621,9 @@ def _insight_candidates(
if postgres_line: if postgres_line:
candidates.append(("postgres", postgres_line, "high")) candidates.append(("postgres", postgres_line, "high"))
hardware_line = _hardware_mix_line(inventory) hardware_insight = _hardware_insight(inventory)
if hardware_line: if hardware_insight:
candidates.append(("hardware", hardware_line, "medium")) candidates.append(("hardware", hardware_insight, "medium"))
pods_line = _pods_summary_line(metrics) pods_line = _pods_summary_line(metrics)
if pods_line: if pods_line:
@ -1613,6 +1632,29 @@ def _insight_candidates(
return candidates return candidates
def _hardware_insight(inventory: list[dict[str, Any]]) -> str:
if not inventory:
return ""
groups = _group_nodes(inventory)
jetsons = groups.get("jetson") or []
rpi5 = groups.get("rpi5") or []
rpi4 = groups.get("rpi4") or []
amd64 = groups.get("amd64") or []
if jetsons:
jetson_names = ", ".join(jetsons[:2])
return (
f"Atlas mixes tiny Raspberry Pi nodes with Jetson accelerators ({jetson_names}) "
f"and AMD64 servers, which is unusual for a homelab cluster."
)
if amd64 and (rpi5 or rpi4):
return (
"Atlas mixes small ARM boards with a couple of AMD64 machines, "
"so workloads can land on either low-power or high-power nodes."
)
line = _hardware_mix_line(inventory)
return line.replace("Hardware mix includes ", "Atlas mixes ") if line else ""
def _select_insight( def _select_insight(
prompt: str, prompt: str,
candidates: list[tuple[str, str, str]], candidates: list[tuple[str, str, str]],
@ -1623,6 +1665,8 @@ def _select_insight(
prefer_keys: list[str] = [] prefer_keys: list[str] = []
if any(word in q for word in ("unconventional", "weird", "odd", "unique", "surprising")): if any(word in q for word in ("unconventional", "weird", "odd", "unique", "surprising")):
prefer_keys.extend(["hardware", "availability"]) prefer_keys.extend(["hardware", "availability"])
if any(word in q for word in ("coolest", "favorite", "favourite", "trivia", "fun")):
prefer_keys.extend(["hardware", "cpu", "ram"])
if any(word in q for word in ("another", "else", "different", "other")) and len(candidates) > 1: if any(word in q for word in ("another", "else", "different", "other")) and len(candidates) > 1:
return candidates[1] return candidates[1]
if prefer_keys: if prefer_keys:
@ -2284,7 +2328,24 @@ class _AtlasbotHandler(BaseHTTPRequestHandler):
snapshot = _snapshot_state() snapshot = _snapshot_state()
inventory = _snapshot_inventory(snapshot) or node_inventory_live() inventory = _snapshot_inventory(snapshot) or node_inventory_live()
workloads = _snapshot_workloads(snapshot) workloads = _snapshot_workloads(snapshot)
cluster_query = _is_cluster_query(cleaned, inventory=inventory, workloads=workloads) history_payload = payload.get("history") or []
history_lines: list[str] = []
if isinstance(history_payload, list):
for item in history_payload[-10:]:
if isinstance(item, dict):
content = item.get("content") or item.get("message") or ""
if isinstance(content, str) and content.strip():
history_lines.append(content.strip())
elif isinstance(item, str) and item.strip():
history_lines.append(item.strip())
history_cluster = _history_mentions_cluster(
history_lines,
inventory=inventory,
workloads=workloads,
)
cluster_query = _is_cluster_query(cleaned, inventory=inventory, workloads=workloads) or (
_is_subjective_query(cleaned) and history_cluster
)
context = "" context = ""
if cluster_query: if cluster_query:
context = build_context( context = build_context(
@ -2329,6 +2390,22 @@ history = collections.defaultdict(list) # (room_id, sender|None) -> list[str] (
def key_for(room_id: str, sender: str, is_dm: bool): def key_for(room_id: str, sender: str, is_dm: bool):
return (room_id, None) if is_dm else (room_id, sender) return (room_id, None) if is_dm else (room_id, sender)
def _history_mentions_cluster(
history_lines: list[str],
*,
inventory: list[dict[str, Any]] | None = None,
workloads: list[dict[str, Any]] | None = None,
) -> bool:
recent = [line for line in history_lines[-8:] if isinstance(line, str)]
for line in recent:
cleaned = normalize_query(line)
if not cleaned:
continue
if _is_cluster_query(cleaned, inventory=inventory, workloads=workloads):
return True
return False
def build_context( def build_context(
prompt: str, prompt: str,
*, *,
@ -2734,7 +2811,14 @@ def sync_loop(token: str, room_id: str):
if not inventory: if not inventory:
inventory = _snapshot_inventory(snapshot) inventory = _snapshot_inventory(snapshot)
workloads = _snapshot_workloads(snapshot) workloads = _snapshot_workloads(snapshot)
cluster_query = _is_cluster_query(cleaned_body, inventory=inventory, workloads=workloads) history_cluster = _history_mentions_cluster(
history[hist_key],
inventory=inventory,
workloads=workloads,
)
cluster_query = _is_cluster_query(cleaned_body, inventory=inventory, workloads=workloads) or (
_is_subjective_query(cleaned_body) and history_cluster
)
context = "" context = ""
if cluster_query: if cluster_query:
context = build_context( context = build_context(