atlasbot: restore quick fact-sheet injection path

This commit is contained in:
Brad Stein 2026-03-30 04:03:52 -03:00
parent 9911c3fdef
commit edf1d66ae0
2 changed files with 152 additions and 1 deletions

View File

@ -203,7 +203,15 @@ class AnswerEngine:
timeout_sec = min(self._settings.ollama_timeout_sec, time_left) timeout_sec = min(self._settings.ollama_timeout_sec, time_left)
call_count += 1 call_count += 1
messages = build_messages(system, prompt, context=context) messages = build_messages(system, prompt, context=context)
response = await self._llm.chat(messages, model=model or plan.model, timeout_sec=timeout_sec) try:
llm_call = self._llm.chat(messages, model=model or plan.model, timeout_sec=timeout_sec)
if timeout_sec is not None:
response = await asyncio.wait_for(llm_call, timeout=max(0.001, timeout_sec))
else:
response = await llm_call
except asyncio.TimeoutError as exc:
time_budget_hit = True
raise LLMTimeBudgetExceeded("time_budget") from exc
log.info( log.info(
"atlasbot_llm_call", "atlasbot_llm_call",
extra={"extra": {"mode": mode, "tag": tag, "call": call_count, "limit": call_cap}}, extra={"extra": {"mode": mode, "tag": tag, "call": call_count, "limit": call_cap}},
@ -240,6 +248,61 @@ class AnswerEngine:
classify: dict[str, Any] = {} classify: dict[str, Any] = {}
tool_hint: dict[str, Any] | None = None tool_hint: dict[str, Any] | None = None
try: try:
if mode in {"quick", "fast"} and not limitless:
if observer:
observer("factsheet", "building fact sheet")
kb_lines = (
self._kb.chunk_lines(
max_files=plan.kb_max_files,
max_chars=max(1200, plan.kb_max_chars),
)
if self._kb
else []
)
fact_lines = _quick_fact_sheet_lines(
question,
summary_lines,
kb_lines,
limit=max(14, plan.chunk_top * 5),
)
if observer:
observer("quick", "answering from fact sheet")
classify = {
"needs_snapshot": True,
"needs_kb": bool(kb_lines),
"question_type": "quick_factsheet",
"answer_style": "direct",
"follow_up": False,
}
quick_context = _quick_fact_sheet_text(fact_lines)
quick_prompt = (
"Question: "
+ question
+ "\nAnswer using only the Fact Sheet. Keep it to 1-3 sentences. "
+ "If the Fact Sheet is missing key data, say exactly what is missing and suggest atlas-smart."
)
reply = await call_llm(
prompts.ANSWER_SYSTEM,
quick_prompt,
context=quick_context,
model=plan.fast_model,
tag="quick_factsheet",
)
reply = _strip_followup_meta(reply)
scores = _default_scores()
meta = _build_meta(
mode,
call_count,
call_cap,
limit_hit,
time_budget_hit,
time_budget_sec,
classify,
tool_hint,
started,
)
return AnswerResult(reply, scores, meta)
if observer: if observer:
observer("normalize", "normalizing") observer("normalize", "normalizing")
normalize_prompt = prompts.NORMALIZE_PROMPT + "\nQuestion: " + question normalize_prompt = prompts.NORMALIZE_PROMPT + "\nQuestion: " + question
@ -3307,6 +3370,92 @@ def _state_from_payload(payload: dict[str, Any] | None) -> ConversationState | N
) )
def _quick_fact_sheet_lines(
question: str,
summary_lines: list[str],
kb_lines: list[str],
*,
limit: int,
) -> list[str]:
tokens = {
token
for token in re.findall(r"[a-z0-9][a-z0-9_-]{2,}", question.lower())
if token not in GENERIC_METRIC_TOKENS
}
priority_markers = (
"snapshot:",
"nodes_total",
"nodes_ready",
"nodes_not_ready",
"workers_ready",
"workers_not_ready",
"control_plane",
"worker_nodes",
"hottest",
"postgres",
"pods",
"longhorn",
"titan-",
"rpi5",
"rpi4",
"jetson",
"amd64",
)
scored: list[tuple[int, str]] = []
for raw in summary_lines:
line = raw.strip()
if not line:
continue
lowered = line.lower()
score = 0
if any(marker in lowered for marker in priority_markers):
score += 4
overlap = sum(1 for token in tokens if token in lowered)
score += overlap * 3
if len(line) <= 180:
score += 1
if score > 0:
scored.append((score, line))
scored.sort(key=lambda item: item[0], reverse=True)
selected = [line for _, line in scored[:limit]]
if not selected:
selected = [line.strip() for line in summary_lines if line.strip()][:limit]
kb_selected: list[str] = []
for raw in kb_lines:
line = raw.strip()
if not line or len(line) > 220:
continue
lowered = line.lower()
if "kb file:" in lowered or "kb: atlas.json" in lowered:
continue
overlap = sum(1 for token in tokens if token in lowered)
if overlap > 0:
kb_selected.append(line)
elif any(marker in lowered for marker in ("runbook", "titan-", "rpi5", "rpi4", "amd64", "jetson")):
kb_selected.append(line)
if len(kb_selected) >= max(4, limit // 3):
break
merged = []
seen: set[str] = set()
for line in selected + kb_selected:
if line not in seen:
seen.add(line)
merged.append(line)
if len(merged) >= limit:
break
return merged
def _quick_fact_sheet_text(lines: list[str]) -> str:
if not lines:
return "Fact Sheet:\n- No snapshot facts available."
body = "\n".join([f"- {line}" for line in lines])
return "Fact Sheet:\n" + body
def _json_excerpt(summary: dict[str, Any], max_chars: int = 12000) -> str: def _json_excerpt(summary: dict[str, Any], max_chars: int = 12000) -> str:
raw = json.dumps(summary, ensure_ascii=False) raw = json.dumps(summary, ensure_ascii=False)
return raw[:max_chars] return raw[:max_chars]

View File

@ -22,6 +22,8 @@ class FakeLLM:
return '[{"id":"q1","question":"What is Atlas?","priority":1}]' return '[{"id":"q1","question":"What is Atlas?","priority":1}]'
if "sub-question" in prompt: if "sub-question" in prompt:
return "Atlas has 22 nodes." return "Atlas has 22 nodes."
if "Answer using only the Fact Sheet" in prompt:
return "Atlas has 22 nodes."
if "final response" in prompt: if "final response" in prompt:
return "Atlas has 22 nodes." return "Atlas has 22 nodes."
if "Score response quality" in prompt: if "Score response quality" in prompt: