atlasbot: improve runbook and hardware snapshot

This commit is contained in:
Brad Stein 2026-02-01 03:37:52 -03:00
parent 2386d5c832
commit 90688c244d
3 changed files with 53 additions and 0 deletions

View File

@ -297,6 +297,18 @@ class AnswerEngine:
runbook_fix = _needs_runbook_fix(reply, runbook_paths)
runbook_needed = _needs_runbook_reference(normalized, runbook_paths, reply)
needs_evidence = _needs_evidence_fix(reply, classify)
resolved_runbook = None
if runbook_paths and (runbook_fix or runbook_needed):
resolver_prompt = prompts.RUNBOOK_SELECT_PROMPT + "\nQuestion: " + normalized
resolver_raw = await call_llm(
prompts.RUNBOOK_SELECT_SYSTEM,
resolver_prompt,
context="AllowedRunbooks:\n" + "\n".join(runbook_paths),
model=plan.fast_model,
tag="runbook_select",
)
resolver = _parse_json_block(resolver_raw, fallback={})
resolved_runbook = resolver.get("path") if isinstance(resolver.get("path"), str) else None
if (snapshot_context and needs_evidence) or unknown_nodes or unknown_namespaces or runbook_fix or runbook_needed:
if observer:
observer("evidence_fix", "repairing missing evidence")
@ -307,6 +319,8 @@ class AnswerEngine:
extra_bits.append("UnknownNamespaces: " + ", ".join(sorted(unknown_namespaces)))
if runbook_paths:
extra_bits.append("AllowedRunbooks: " + ", ".join(runbook_paths))
if resolved_runbook:
extra_bits.append("ResolvedRunbook: " + resolved_runbook)
if allowed_nodes:
extra_bits.append("AllowedNodes: " + ", ".join(allowed_nodes))
if allowed_namespaces:
@ -847,11 +861,14 @@ def _needs_evidence_fix(reply: str, classify: dict[str, Any]) -> bool:
"need to",
"would need",
"does not provide",
"does not mention",
"not mention",
"not provided",
"not in context",
"not referenced",
"missing",
"no specific",
"no information",
)
if classify.get("needs_snapshot") and any(marker in lowered for marker in missing_markers):
return True

View File

@ -104,6 +104,17 @@ EVIDENCE_FIX_PROMPT = (
"documentation or checklist questions and do not invent new paths."
)
RUNBOOK_SELECT_SYSTEM = (
CLUSTER_SYSTEM
+ " Select the single best runbook path from the allowed list. "
+ "Return JSON only."
)
RUNBOOK_SELECT_PROMPT = (
"Pick the best runbook path for the question from the AllowedRunbooks list. "
"Return JSON with field: path. If none apply, return {\"path\": \"\"}."
)
DRAFT_SELECT_PROMPT = (
"Pick the best draft for accuracy, clarity, and helpfulness. "
"Return JSON with field: best (1-based index)."

View File

@ -625,6 +625,21 @@ def _append_hardware(lines: list[str], summary: dict[str, Any]) -> None:
lines.append("hardware: " + "; ".join(sorted(parts)))
def _append_hardware_groups(lines: list[str], summary: dict[str, Any]) -> None:
hardware = summary.get("hardware") if isinstance(summary.get("hardware"), dict) else {}
if not hardware:
return
parts = []
for key, names in hardware.items():
if not isinstance(names, list):
continue
name_list = _format_names([str(name) for name in names if name])
if name_list:
parts.append(f"{key}={name_list}")
if parts:
lines.append("hardware_nodes: " + "; ".join(sorted(parts)))
def _append_node_ages(lines: list[str], summary: dict[str, Any]) -> None:
ages = summary.get("node_ages") if isinstance(summary.get("node_ages"), list) else []
if not ages:
@ -1308,6 +1323,15 @@ def _append_postgres(lines: list[str], summary: dict[str, Any]) -> None:
hottest=hottest,
)
)
used = postgres.get("used")
max_conn = postgres.get("max")
if used is not None or max_conn is not None:
lines.append(
"postgres_connections_total: used={used}, max={max}".format(
used=_format_float(used),
max=_format_float(max_conn),
)
)
by_db = postgres.get("by_db")
if isinstance(by_db, list) and by_db:
parts = []
@ -1805,6 +1829,7 @@ def summary_text(snapshot: dict[str, Any] | None) -> str:
lines.append("snapshot: " + ", ".join(bits))
_append_nodes(lines, summary)
_append_hardware(lines, summary)
_append_hardware_groups(lines, summary)
_append_lexicon(lines, summary)
_append_pressure(lines, summary)
_append_node_facts(lines, summary)