atlasbot: improve insight voice and avoid repeats

This commit is contained in:
Brad Stein 2026-01-27 18:43:03 -03:00
parent 58dab1ca79
commit 4e6d4f43b2
2 changed files with 70 additions and 18 deletions

View File

@ -16,7 +16,7 @@ spec:
labels:
app: atlasbot
annotations:
checksum/atlasbot-configmap: manual-atlasbot-54
checksum/atlasbot-configmap: manual-atlasbot-55
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"

View File

@ -1640,27 +1640,49 @@ def _hardware_insight(inventory: list[dict[str, Any]]) -> str:
rpi5 = groups.get("rpi5") or []
rpi4 = groups.get("rpi4") or []
amd64 = groups.get("amd64") or []
parts: list[str] = []
if rpi5:
parts.append(f"rpi5={len(rpi5)}")
if rpi4:
parts.append(f"rpi4={len(rpi4)}")
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 ""
parts.append(f"jetson={len(jetsons)} ({jetson_names})")
if amd64:
parts.append(f"amd64={len(amd64)}")
return ", ".join(parts)
def _recent_insight_keys(history_lines: list[str]) -> set[str]:
used: set[str] = set()
for line in history_lines[-10:]:
lower = normalize_query(line)
if not lower:
continue
if "postgres" in lower or "connections" in lower:
used.add("postgres")
if "atlas mixes" in lower or "hardware" in lower or "rpi" in lower or "jetson" in lower:
used.add("hardware")
if "busiest cpu" in lower or "cpu right now" in lower or "cpu " in lower:
used.add("cpu")
if "ram usage" in lower or "memory" in lower:
used.add("ram")
if "pods" in lower:
used.add("pods")
if "not ready" in lower:
used.add("availability")
return used
def _select_insight(
prompt: str,
candidates: list[tuple[str, str, str]],
*,
used_keys: set[str] | None = None,
) -> tuple[str, str, str] | None:
if not candidates:
return None
used = used_keys or set()
q = normalize_query(prompt)
prefer_keys: list[str] = []
if any(word in q for word in ("unconventional", "weird", "odd", "unique", "surprising")):
@ -1668,11 +1690,21 @@ def _select_insight(
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:
for candidate in candidates:
if candidate[0] not in used:
return candidate
return candidates[1]
if prefer_keys:
for key, text, conf in candidates:
if key in prefer_keys and key not in used:
return key, text, conf
for key, text, conf in candidates:
if key in prefer_keys:
return key, text, conf
if used:
for candidate in candidates:
if candidate[0] not in used:
return candidate
return candidates[0]
@ -1681,29 +1713,45 @@ def _format_insight_text(key: str, text: str) -> str:
if not cleaned:
return ""
if key == "hardware":
counts = cleaned.replace("Hardware mix includes ", "")
return f"Atlas mixes Raspberry Pi, Jetson, and AMD64 nodes ({counts})."
counts = (
cleaned.replace("Hardware mix includes ", "")
.replace("Atlas mixes tiny ", "")
.replace("Atlas mixes ", "")
.replace("which is unusual for a homelab cluster", "")
.strip()
.strip(".")
)
return f"the mixed hardware stack ({counts}) is a bit unconventional for a homelab."
if key == "postgres":
detail = cleaned.replace("Postgres is at ", "")
return f"Postgres looks healthy at {detail}."
return f"Postgres looks healthy at {detail}; that suggests moderate load."
if key == "pods":
detail = cleaned.replace("There are ", "")
return f"Pods look stable with {detail}."
if key == "availability":
return cleaned + "."
if key in ("cpu", "ram"):
return cleaned + "."
suffix = " That likely marks the busiest workload right now." if key == "cpu" else " That box is carrying the heaviest memory load."
return cleaned + "." + suffix
return cleaned + "."
def _insight_prefix(prompt: str) -> str:
q = normalize_query(prompt)
if "coolest" in q:
return "If I had to pick the coolest detail, it's "
if "favorite" in q or "favourite" in q:
return "My favorite detail is "
if "trivia" in q:
return "A bit of trivia I like: "
if "most interesting" in q:
return "The most interesting detail to me is "
if any(word in q for word in ("another", "else", "different", "other")):
return "Another interesting detail: "
if any(word in q for word in ("unconventional", "weird", "odd", "unique", "surprising")):
return "What stands out is that "
if any(word in q for word in ("interesting", "notable", "fun", "cool")):
return "One notable detail: "
return "One thing I'd highlight is "
return ""
@ -1782,11 +1830,13 @@ def cluster_answer(
inventory: list[dict[str, Any]],
snapshot: dict[str, Any] | None,
workloads: list[dict[str, Any]] | None,
history_lines: list[str] | None = None,
) -> str:
metrics_summary = snapshot_context(prompt, snapshot)
if _is_insight_query(prompt):
candidates = _insight_candidates(inventory, snapshot)
selected = _select_insight(prompt, candidates)
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)
@ -2363,6 +2413,7 @@ class _AtlasbotHandler(BaseHTTPRequestHandler):
inventory=inventory,
snapshot=snapshot,
workloads=workloads,
history_lines=history_lines,
)
if not answer:
answer = fallback
@ -2843,6 +2894,7 @@ def sync_loop(token: str, room_id: str):
inventory=inventory,
snapshot=snapshot,
workloads=workloads,
history_lines=history[hist_key],
)
if not reply:
reply = fallback