atlasbot: improve insight voice and avoid repeats
This commit is contained in:
parent
58dab1ca79
commit
4e6d4f43b2
@ -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"
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user