diff --git a/atlasbot/llm/prompts.py b/atlasbot/llm/prompts.py index 654f2e5..d12b41f 100644 --- a/atlasbot/llm/prompts.py +++ b/atlasbot/llm/prompts.py @@ -1,6 +1,8 @@ CLUSTER_SYSTEM = ( "You are Atlas, the Titan Lab assistant for the Atlas/Othrys cluster. " "Use the provided context as your source of truth. " + "If a fact or number is not present in the context, say you do not know. " + "Do not invent metrics or capacities. " "If the question is about Atlas/Othrys, respond in short paragraphs. " "Avoid commands unless explicitly asked. " "If information is missing, say so clearly and avoid guessing. " diff --git a/atlasbot/snapshot/builder.py b/atlasbot/snapshot/builder.py index a103630..86c5baa 100644 --- a/atlasbot/snapshot/builder.py +++ b/atlasbot/snapshot/builder.py @@ -1,4 +1,3 @@ -import json import logging import time from typing import Any @@ -157,8 +156,93 @@ def _build_flux(snapshot: dict[str, Any]) -> dict[str, Any]: return {"flux": flux} +def _format_float(value: Any) -> str: + try: + numeric = float(value) + except (TypeError, ValueError): + return str(value) + return f"{numeric:.2f}".rstrip("0").rstrip(".") + + +def _format_names(names: list[str]) -> str: + if not names: + return "" + return ", ".join(sorted(names)) + + def summary_text(snapshot: dict[str, Any] | None) -> str: summary = build_summary(snapshot) if not summary: return "" - return json.dumps(summary, ensure_ascii=True, separators=(",", ":")) + lines: list[str] = [] + + nodes = summary.get("nodes") if isinstance(summary.get("nodes"), dict) else {} + if nodes: + lines.append( + "nodes: total={total}, ready={ready}, not_ready={not_ready}".format( + total=nodes.get("total"), + ready=nodes.get("ready"), + not_ready=nodes.get("not_ready"), + ) + ) + + hardware = summary.get("hardware") if isinstance(summary.get("hardware"), dict) else {} + if hardware: + parts = [] + for key, names in hardware.items(): + if not isinstance(names, list): + continue + label = f"{key}={len(names)}" + name_list = _format_names([str(name) for name in names if name]) + if name_list: + label = f"{label} ({name_list})" + parts.append(label) + if parts: + lines.append("hardware: " + "; ".join(sorted(parts))) + + pods = summary.get("pods") if isinstance(summary.get("pods"), dict) else {} + if pods: + lines.append( + "pods: running={running}, pending={pending}, failed={failed}, succeeded={succeeded}".format( + running=pods.get("running"), + pending=pods.get("pending"), + failed=pods.get("failed"), + succeeded=pods.get("succeeded"), + ) + ) + + postgres = summary.get("postgres") if isinstance(summary.get("postgres"), dict) else {} + if postgres: + hottest = postgres.get("hottest_db") or "" + lines.append( + "postgres: used={used}, max={max}, hottest_db={hottest}".format( + used=postgres.get("used"), + max=postgres.get("max"), + hottest=hottest, + ) + ) + + hottest = summary.get("hottest") if isinstance(summary.get("hottest"), dict) else {} + if hottest: + parts = [] + for key, entry in hottest.items(): + if not isinstance(entry, dict): + continue + node = entry.get("node") + value = _format_float(entry.get("value")) + if node: + parts.append(f"{key}={node} ({value})") + if parts: + lines.append("hottest: " + "; ".join(parts)) + + workloads = summary.get("workloads") + if isinstance(workloads, list) and workloads: + lines.append(f"workloads: total={len(workloads)}") + + flux = summary.get("flux") if isinstance(summary.get("flux"), dict) else {} + if flux: + not_ready = flux.get("not_ready") + if not_ready is not None: + lines.append(f"flux_not_ready: {not_ready}") + + return "\n".join(lines)