atlasbot: use history for subjective follow-ups
This commit is contained in:
parent
e05a949b9f
commit
113bcdeded
@ -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(
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user