atlasbot: refactor spine and intent routing
This commit is contained in:
parent
d85ac70c83
commit
c6b25c27c1
@ -58,6 +58,15 @@ class InsightGuardInput:
|
|||||||
facts: list[str]
|
facts: list[str]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ContradictionContext:
|
||||||
|
call_llm: Callable[..., Awaitable[str]]
|
||||||
|
question: str
|
||||||
|
reply: str
|
||||||
|
facts: list[str]
|
||||||
|
plan: "ModePlan"
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class EvidenceItem:
|
class EvidenceItem:
|
||||||
path: str
|
path: str
|
||||||
@ -768,11 +777,7 @@ class AnswerEngine:
|
|||||||
use_guard = True
|
use_guard = True
|
||||||
if mode in {"smart", "genius"}:
|
if mode in {"smart", "genius"}:
|
||||||
decision = await _contradiction_decision(
|
decision = await _contradiction_decision(
|
||||||
call_llm,
|
ContradictionContext(call_llm, normalized, reply, facts_used, plan),
|
||||||
normalized,
|
|
||||||
reply,
|
|
||||||
facts_used,
|
|
||||||
plan,
|
|
||||||
attempts=3 if mode == "genius" else 1,
|
attempts=3 if mode == "genius" else 1,
|
||||||
)
|
)
|
||||||
use_guard = decision.get("use_facts", True)
|
use_guard = decision.get("use_facts", True)
|
||||||
@ -1616,46 +1621,68 @@ def _line_starting_with(lines: list[str], prefix: str) -> str | None:
|
|||||||
|
|
||||||
def _spine_lines(lines: list[str]) -> dict[str, str]:
|
def _spine_lines(lines: list[str]) -> dict[str, str]:
|
||||||
spine: dict[str, str] = {}
|
spine: dict[str, str] = {}
|
||||||
|
_spine_nodes(lines, spine)
|
||||||
|
_spine_hardware(lines, spine)
|
||||||
|
_spine_hottest(lines, spine)
|
||||||
|
_spine_postgres(lines, spine)
|
||||||
|
_spine_namespaces(lines, spine)
|
||||||
|
_spine_pressure(lines, spine)
|
||||||
|
return spine
|
||||||
|
|
||||||
|
|
||||||
|
def _spine_nodes(lines: list[str], spine: dict[str, str]) -> None:
|
||||||
nodes_line = _line_starting_with(lines, "nodes:")
|
nodes_line = _line_starting_with(lines, "nodes:")
|
||||||
if nodes_line:
|
if nodes_line:
|
||||||
spine["nodes_count"] = nodes_line
|
spine["nodes_count"] = nodes_line
|
||||||
spine["nodes_ready"] = nodes_line
|
spine["nodes_ready"] = nodes_line
|
||||||
else:
|
return
|
||||||
nodes_total = _line_starting_with(lines, "nodes_total:")
|
nodes_total = _line_starting_with(lines, "nodes_total:")
|
||||||
nodes_ready = _line_starting_with(lines, "nodes_ready:")
|
nodes_ready = _line_starting_with(lines, "nodes_ready:")
|
||||||
if nodes_total:
|
if nodes_total:
|
||||||
spine["nodes_count"] = nodes_total
|
spine["nodes_count"] = nodes_total
|
||||||
if nodes_ready:
|
if nodes_ready:
|
||||||
spine["nodes_ready"] = nodes_ready
|
spine["nodes_ready"] = nodes_ready
|
||||||
|
|
||||||
|
|
||||||
|
def _spine_hardware(lines: list[str], spine: dict[str, str]) -> None:
|
||||||
hardware_line = _line_starting_with(lines, "hardware_nodes:")
|
hardware_line = _line_starting_with(lines, "hardware_nodes:")
|
||||||
if not hardware_line:
|
if not hardware_line:
|
||||||
hardware_line = _line_starting_with(lines, "hardware:")
|
hardware_line = _line_starting_with(lines, "hardware:")
|
||||||
if hardware_line:
|
if hardware_line:
|
||||||
spine["nodes_non_rpi"] = hardware_line
|
spine["nodes_non_rpi"] = hardware_line
|
||||||
|
|
||||||
|
|
||||||
|
def _spine_hottest(lines: list[str], spine: dict[str, str]) -> None:
|
||||||
hottest_line = _line_starting_with(lines, "hottest:")
|
hottest_line = _line_starting_with(lines, "hottest:")
|
||||||
if hottest_line:
|
if not hottest_line:
|
||||||
spine["hottest_cpu"] = hottest_line
|
return
|
||||||
spine["hottest_ram"] = hottest_line
|
for key in ("hottest_cpu", "hottest_ram", "hottest_net", "hottest_io", "hottest_disk"):
|
||||||
spine["hottest_net"] = hottest_line
|
spine[key] = hottest_line
|
||||||
spine["hottest_io"] = hottest_line
|
|
||||||
spine["hottest_disk"] = hottest_line
|
|
||||||
|
def _spine_postgres(lines: list[str], spine: dict[str, str]) -> None:
|
||||||
postgres_total = _line_starting_with(lines, "postgres_connections_total:")
|
postgres_total = _line_starting_with(lines, "postgres_connections_total:")
|
||||||
if postgres_total:
|
if postgres_total:
|
||||||
spine["postgres_connections"] = postgres_total
|
spine["postgres_connections"] = postgres_total
|
||||||
postgres_line = _line_starting_with(lines, "postgres:")
|
postgres_line = _line_starting_with(lines, "postgres:")
|
||||||
if postgres_line:
|
if postgres_line:
|
||||||
spine["postgres_hottest"] = postgres_line
|
spine["postgres_hottest"] = postgres_line
|
||||||
|
|
||||||
|
|
||||||
|
def _spine_namespaces(lines: list[str], spine: dict[str, str]) -> None:
|
||||||
namespaces_top = _line_starting_with(lines, "namespaces_top:")
|
namespaces_top = _line_starting_with(lines, "namespaces_top:")
|
||||||
if namespaces_top:
|
if namespaces_top:
|
||||||
spine["namespace_most_pods"] = namespaces_top
|
spine["namespace_most_pods"] = namespaces_top
|
||||||
|
|
||||||
|
|
||||||
|
def _spine_pressure(lines: list[str], spine: dict[str, str]) -> None:
|
||||||
pressure_line = _line_starting_with(lines, "pressure_nodes:")
|
pressure_line = _line_starting_with(lines, "pressure_nodes:")
|
||||||
if pressure_line:
|
if pressure_line:
|
||||||
spine["pressure_summary"] = pressure_line
|
spine["pressure_summary"] = pressure_line
|
||||||
else:
|
return
|
||||||
load_line = _line_starting_with(lines, "node_load_top:")
|
load_line = _line_starting_with(lines, "node_load_top:")
|
||||||
if load_line:
|
if load_line:
|
||||||
spine["pressure_summary"] = load_line
|
spine["pressure_summary"] = load_line
|
||||||
return spine
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_group_line(line: str) -> dict[str, list[str]]:
|
def _parse_group_line(line: str) -> dict[str, list[str]]:
|
||||||
@ -1694,40 +1721,62 @@ def _parse_hottest(line: str, metric: str) -> str | None:
|
|||||||
def _spine_answer(intent: IntentMatch, spine_line: str | None) -> str | None:
|
def _spine_answer(intent: IntentMatch, spine_line: str | None) -> str | None:
|
||||||
if not spine_line:
|
if not spine_line:
|
||||||
return None
|
return None
|
||||||
|
handlers = {
|
||||||
|
"nodes_count": _spine_nodes_answer,
|
||||||
|
"nodes_ready": _spine_nodes_answer,
|
||||||
|
"nodes_non_rpi": _spine_non_rpi_answer,
|
||||||
|
"postgres_connections": _spine_postgres_answer,
|
||||||
|
"postgres_hottest": _spine_postgres_answer,
|
||||||
|
"namespace_most_pods": _spine_namespace_answer,
|
||||||
|
"pressure_summary": _spine_pressure_answer,
|
||||||
|
}
|
||||||
kind = intent.kind
|
kind = intent.kind
|
||||||
if kind == "nodes_count":
|
if kind.startswith("hottest_"):
|
||||||
|
return _spine_hottest_answer(kind, spine_line)
|
||||||
|
handler = handlers.get(kind)
|
||||||
|
if handler:
|
||||||
|
return handler(spine_line)
|
||||||
return f"From the latest snapshot: {spine_line}."
|
return f"From the latest snapshot: {spine_line}."
|
||||||
if kind == "nodes_ready":
|
|
||||||
return f"From the latest snapshot: {spine_line}."
|
|
||||||
if kind == "nodes_non_rpi":
|
def _spine_nodes_answer(line: str) -> str:
|
||||||
groups = _parse_group_line(spine_line)
|
return f"From the latest snapshot: {line}."
|
||||||
non_rpi = []
|
|
||||||
|
|
||||||
|
def _spine_non_rpi_answer(line: str) -> str:
|
||||||
|
groups = _parse_group_line(line)
|
||||||
|
non_rpi: list[str] = []
|
||||||
for key, nodes in groups.items():
|
for key, nodes in groups.items():
|
||||||
if key.lower().startswith("rpi"):
|
if key.lower().startswith("rpi"):
|
||||||
continue
|
continue
|
||||||
non_rpi.extend(nodes)
|
non_rpi.extend(nodes)
|
||||||
if non_rpi:
|
if non_rpi:
|
||||||
return "Non‑Raspberry Pi nodes: " + ", ".join(non_rpi) + "."
|
return "Non‑Raspberry Pi nodes: " + ", ".join(non_rpi) + "."
|
||||||
return f"From the latest snapshot: {spine_line}."
|
return f"From the latest snapshot: {line}."
|
||||||
if kind.startswith("hottest_"):
|
|
||||||
|
|
||||||
|
def _spine_hottest_answer(kind: str, line: str) -> str:
|
||||||
metric = kind.split("_", 1)[1]
|
metric = kind.split("_", 1)[1]
|
||||||
hottest = _parse_hottest(spine_line, metric)
|
hottest = _parse_hottest(line, metric)
|
||||||
if hottest:
|
if hottest:
|
||||||
return f"From the latest snapshot: {hottest}."
|
return f"From the latest snapshot: {hottest}."
|
||||||
return f"From the latest snapshot: {spine_line}."
|
return f"From the latest snapshot: {line}."
|
||||||
if kind == "postgres_connections":
|
|
||||||
return f"From the latest snapshot: {spine_line}."
|
|
||||||
if kind == "postgres_hottest":
|
def _spine_postgres_answer(line: str) -> str:
|
||||||
return f"From the latest snapshot: {spine_line}."
|
return f"From the latest snapshot: {line}."
|
||||||
if kind == "namespace_most_pods":
|
|
||||||
payload = spine_line.split(":", 1)[1] if ":" in spine_line else spine_line
|
|
||||||
|
def _spine_namespace_answer(line: str) -> str:
|
||||||
|
payload = line.split(":", 1)[1] if ":" in line else line
|
||||||
top = payload.split(";")[0].strip()
|
top = payload.split(";")[0].strip()
|
||||||
if top:
|
if top:
|
||||||
return f"Namespace with most pods: {top}."
|
return f"Namespace with most pods: {top}."
|
||||||
return f"From the latest snapshot: {spine_line}."
|
return f"From the latest snapshot: {line}."
|
||||||
if kind == "pressure_summary":
|
|
||||||
return f"From the latest snapshot: {spine_line}."
|
|
||||||
return f"From the latest snapshot: {spine_line}."
|
def _spine_pressure_answer(line: str) -> str:
|
||||||
|
return f"From the latest snapshot: {line}."
|
||||||
|
|
||||||
|
|
||||||
async def _select_metric_chunks(
|
async def _select_metric_chunks(
|
||||||
@ -2309,25 +2358,21 @@ def _needs_evidence_guard(reply: str, facts: list[str]) -> bool:
|
|||||||
|
|
||||||
|
|
||||||
async def _contradiction_decision(
|
async def _contradiction_decision(
|
||||||
call_llm: Callable[..., Awaitable[str]],
|
ctx: ContradictionContext,
|
||||||
question: str,
|
|
||||||
draft: str,
|
|
||||||
facts_used: list[str],
|
|
||||||
plan: "ModePlan",
|
|
||||||
attempts: int = 1,
|
attempts: int = 1,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
best = {"use_facts": True, "confidence": 50}
|
best = {"use_facts": True, "confidence": 50}
|
||||||
facts_block = "\n".join(facts_used[:12])
|
facts_block = "\n".join(ctx.facts[:12])
|
||||||
for idx in range(max(1, attempts)):
|
for idx in range(max(1, attempts)):
|
||||||
variant = f"Variant: {idx + 1}" if attempts > 1 else ""
|
variant = f"Variant: {idx + 1}" if attempts > 1 else ""
|
||||||
prompt = (
|
prompt = (
|
||||||
prompts.CONTRADICTION_PROMPT.format(question=question, draft=draft, facts=facts_block)
|
prompts.CONTRADICTION_PROMPT.format(question=ctx.question, draft=ctx.reply, facts=facts_block)
|
||||||
+ ("\n" + variant if variant else "")
|
+ ("\n" + variant if variant else "")
|
||||||
)
|
)
|
||||||
raw = await call_llm(
|
raw = await ctx.call_llm(
|
||||||
prompts.CONTRADICTION_SYSTEM,
|
prompts.CONTRADICTION_SYSTEM,
|
||||||
prompt,
|
prompt,
|
||||||
model=plan.fast_model,
|
model=ctx.plan.fast_model,
|
||||||
tag="contradiction",
|
tag="contradiction",
|
||||||
)
|
)
|
||||||
data = _parse_json_block(raw, fallback={})
|
data = _parse_json_block(raw, fallback={})
|
||||||
|
|||||||
@ -33,33 +33,34 @@ def route_intent(question: str) -> IntentMatch | None:
|
|||||||
if not text:
|
if not text:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if re.search(_COUNT_TERMS, text) and (re.search(_NODE_TERMS, text) or "cluster" in text):
|
def _has(pattern: str) -> bool:
|
||||||
return IntentMatch("nodes_count", 90)
|
return bool(re.search(pattern, text))
|
||||||
if re.search(_READY_TERMS, text) and (re.search(_NODE_TERMS, text) or "cluster" in text or "workers" in text):
|
|
||||||
return IntentMatch("nodes_ready", 85)
|
|
||||||
if re.search(_NON_RPI_TERMS, text) and (re.search(_NODE_TERMS, text) or "cluster" in text):
|
|
||||||
return IntentMatch("nodes_non_rpi", 80)
|
|
||||||
|
|
||||||
if re.search(_HOTTEST_TERMS, text) and re.search(_CPU_TERMS, text):
|
def _all(*patterns: str) -> bool:
|
||||||
return IntentMatch("hottest_cpu", 80)
|
return all(_has(pat) for pat in patterns)
|
||||||
if re.search(_HOTTEST_TERMS, text) and re.search(_RAM_TERMS, text):
|
|
||||||
return IntentMatch("hottest_ram", 80)
|
|
||||||
if re.search(_HOTTEST_TERMS, text) and re.search(_NET_TERMS, text):
|
|
||||||
return IntentMatch("hottest_net", 80)
|
|
||||||
if re.search(_HOTTEST_TERMS, text) and re.search(_IO_TERMS, text):
|
|
||||||
return IntentMatch("hottest_io", 80)
|
|
||||||
if re.search(_HOTTEST_TERMS, text) and re.search(_DISK_TERMS, text):
|
|
||||||
return IntentMatch("hottest_disk", 80)
|
|
||||||
|
|
||||||
if re.search(_PG_TERMS, text) and re.search(_CONN_TERMS, text):
|
def _any(*patterns: str) -> bool:
|
||||||
return IntentMatch("postgres_connections", 80)
|
return any(_has(pat) for pat in patterns)
|
||||||
if re.search(_PG_TERMS, text) and re.search(_DB_HOT_TERMS, text):
|
|
||||||
return IntentMatch("postgres_hottest", 75)
|
|
||||||
|
|
||||||
if re.search(_NAMESPACE_TERMS, text) and re.search(_PODS_TERMS, text):
|
intents = [
|
||||||
return IntentMatch("namespace_most_pods", 75)
|
(lambda: _all(_COUNT_TERMS) and (_has(_NODE_TERMS) or "cluster" in text), IntentMatch("nodes_count", 90)),
|
||||||
|
(
|
||||||
if re.search(_PRESSURE_TERMS, text) and re.search(_NODE_TERMS, text):
|
lambda: _all(_READY_TERMS) and (_any(_NODE_TERMS) or "cluster" in text or "workers" in text),
|
||||||
return IntentMatch("pressure_summary", 70)
|
IntentMatch("nodes_ready", 85),
|
||||||
|
),
|
||||||
|
(lambda: _all(_NON_RPI_TERMS) and (_any(_NODE_TERMS) or "cluster" in text), IntentMatch("nodes_non_rpi", 80)),
|
||||||
|
(lambda: _all(_HOTTEST_TERMS, _CPU_TERMS), IntentMatch("hottest_cpu", 80)),
|
||||||
|
(lambda: _all(_HOTTEST_TERMS, _RAM_TERMS), IntentMatch("hottest_ram", 80)),
|
||||||
|
(lambda: _all(_HOTTEST_TERMS, _NET_TERMS), IntentMatch("hottest_net", 80)),
|
||||||
|
(lambda: _all(_HOTTEST_TERMS, _IO_TERMS), IntentMatch("hottest_io", 80)),
|
||||||
|
(lambda: _all(_HOTTEST_TERMS, _DISK_TERMS), IntentMatch("hottest_disk", 80)),
|
||||||
|
(lambda: _all(_PG_TERMS, _CONN_TERMS), IntentMatch("postgres_connections", 80)),
|
||||||
|
(lambda: _all(_PG_TERMS, _DB_HOT_TERMS), IntentMatch("postgres_hottest", 75)),
|
||||||
|
(lambda: _all(_NAMESPACE_TERMS, _PODS_TERMS), IntentMatch("namespace_most_pods", 75)),
|
||||||
|
(lambda: _all(_PRESSURE_TERMS, _NODE_TERMS), IntentMatch("pressure_summary", 70)),
|
||||||
|
]
|
||||||
|
|
||||||
|
for predicate, result in intents:
|
||||||
|
if predicate():
|
||||||
|
return result
|
||||||
return None
|
return None
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user