atlasbot: route subjective queries to LLM
This commit is contained in:
parent
18e543d95a
commit
9e06d7afc8
@ -16,7 +16,7 @@ spec:
|
|||||||
labels:
|
labels:
|
||||||
app: atlasbot
|
app: atlasbot
|
||||||
annotations:
|
annotations:
|
||||||
checksum/atlasbot-configmap: manual-atlasbot-68
|
checksum/atlasbot-configmap: manual-atlasbot-69
|
||||||
vault.hashicorp.com/agent-inject: "true"
|
vault.hashicorp.com/agent-inject: "true"
|
||||||
vault.hashicorp.com/role: "comms"
|
vault.hashicorp.com/role: "comms"
|
||||||
vault.hashicorp.com/agent-inject-secret-turn-secret: "kv/data/atlas/comms/turn-shared-secret"
|
vault.hashicorp.com/agent-inject-secret-turn-secret: "kv/data/atlas/comms/turn-shared-secret"
|
||||||
|
|||||||
@ -1911,19 +1911,6 @@ def cluster_answer(
|
|||||||
history_lines: list[str] | None = None,
|
history_lines: list[str] | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
metrics_summary = snapshot_context(prompt, snapshot)
|
metrics_summary = snapshot_context(prompt, snapshot)
|
||||||
if _is_insight_query(prompt):
|
|
||||||
candidates = _insight_candidates(inventory, snapshot)
|
|
||||||
used_keys = _recent_insight_keys(history_lines or [])
|
|
||||||
selected = _select_insight(prompt, candidates, used_keys=used_keys)
|
|
||||||
if selected:
|
|
||||||
key, raw_text, confidence = selected
|
|
||||||
formatted = _format_insight_text(key, raw_text)
|
|
||||||
if not formatted:
|
|
||||||
formatted = raw_text
|
|
||||||
prefix = _insight_prefix(prompt)
|
|
||||||
if prefix:
|
|
||||||
formatted = prefix + formatted
|
|
||||||
return _format_confidence(formatted, confidence)
|
|
||||||
structured = structured_answer(
|
structured = structured_answer(
|
||||||
prompt,
|
prompt,
|
||||||
inventory=inventory,
|
inventory=inventory,
|
||||||
@ -2422,6 +2409,17 @@ def _history_payload_lines(history_payload: list[Any]) -> list[str]:
|
|||||||
return [line for line in lines if line]
|
return [line for line in lines if line]
|
||||||
|
|
||||||
|
|
||||||
|
def _append_history_context(context: str, history_lines: list[str]) -> str:
|
||||||
|
lines = [line.strip() for line in history_lines if isinstance(line, str) and line.strip()]
|
||||||
|
if not lines:
|
||||||
|
return context
|
||||||
|
snippet = "\n".join(lines[-6:])
|
||||||
|
combined = context + "\nRecent chat:\n" + snippet if context else "Recent chat:\n" + snippet
|
||||||
|
if len(combined) > MAX_CONTEXT_CHARS:
|
||||||
|
combined = combined[: MAX_CONTEXT_CHARS - 3].rstrip() + "..."
|
||||||
|
return combined
|
||||||
|
|
||||||
|
|
||||||
# Internal HTTP endpoint for cluster answers (website uses this).
|
# Internal HTTP endpoint for cluster answers (website uses this).
|
||||||
class _AtlasbotHandler(BaseHTTPRequestHandler):
|
class _AtlasbotHandler(BaseHTTPRequestHandler):
|
||||||
server_version = "AtlasbotHTTP/1.0"
|
server_version = "AtlasbotHTTP/1.0"
|
||||||
@ -2493,15 +2491,25 @@ class _AtlasbotHandler(BaseHTTPRequestHandler):
|
|||||||
)
|
)
|
||||||
fallback = "I don't have enough data to answer that."
|
fallback = "I don't have enough data to answer that."
|
||||||
if cluster_query:
|
if cluster_query:
|
||||||
answer = cluster_answer(
|
facts_answer = cluster_answer(
|
||||||
cleaned,
|
cleaned,
|
||||||
inventory=inventory,
|
inventory=inventory,
|
||||||
snapshot=snapshot,
|
snapshot=snapshot,
|
||||||
workloads=workloads,
|
workloads=workloads,
|
||||||
history_lines=history_lines,
|
history_lines=history_lines,
|
||||||
)
|
)
|
||||||
if not answer:
|
open_ended = _is_subjective_query(cleaned) or _knowledge_intent(cleaned)
|
||||||
answer = fallback
|
if open_ended:
|
||||||
|
llm_context = _append_history_context(context, history_lines)
|
||||||
|
answer = ollama_reply(
|
||||||
|
("http", "internal"),
|
||||||
|
cleaned,
|
||||||
|
context=llm_context,
|
||||||
|
fallback=facts_answer or fallback,
|
||||||
|
use_history=False,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
answer = facts_answer or fallback
|
||||||
else:
|
else:
|
||||||
llm_prompt = cleaned
|
llm_prompt = cleaned
|
||||||
answer = ollama_reply(
|
answer = ollama_reply(
|
||||||
@ -2761,11 +2769,13 @@ def _ollama_call(hist_key, prompt: str, *, context: str, use_history: bool = Tru
|
|||||||
"When a cluster snapshot is provided, never answer about unrelated meanings of 'Atlas' (maps, mythology, Apache Atlas, etc). "
|
"When a cluster snapshot is provided, never answer about unrelated meanings of 'Atlas' (maps, mythology, Apache Atlas, etc). "
|
||||||
"Treat 'hottest' as highest utilization (CPU/RAM/NET/IO) rather than temperature. "
|
"Treat 'hottest' as highest utilization (CPU/RAM/NET/IO) rather than temperature. "
|
||||||
"If you infer or synthesize, say 'Based on the snapshot' and keep it brief. "
|
"If you infer or synthesize, say 'Based on the snapshot' and keep it brief. "
|
||||||
|
"For subjective prompts (interesting, favorite, unconventional), pick one or two observations from the context, explain why they stand out in 1-2 sentences, and avoid repeating the same observation as the last response if you can. "
|
||||||
"Prefer exact repo paths and Kubernetes resource names when relevant. "
|
"Prefer exact repo paths and Kubernetes resource names when relevant. "
|
||||||
"Never include or request secret values. "
|
"Never include or request secret values. "
|
||||||
"Do not suggest commands unless explicitly asked. "
|
"Do not suggest commands unless explicitly asked. "
|
||||||
"Respond in plain sentences; do not return JSON or code fences unless explicitly asked. "
|
"Respond in plain sentences; do not return JSON or code fences unless explicitly asked. "
|
||||||
"Translate metrics into natural language instead of echoing raw label/value pairs. "
|
"Translate metrics into natural language instead of echoing raw label/value pairs. "
|
||||||
|
"Avoid bare lists unless the user asked for a list; weave numbers into sentences. "
|
||||||
"Do not answer by only listing runbooks; if the question is about Atlas/Othrys, summarize the cluster first and mention docs only if useful. "
|
"Do not answer by only listing runbooks; if the question is about Atlas/Othrys, summarize the cluster first and mention docs only if useful. "
|
||||||
"If the question is not about Atlas/Othrys and no cluster context is provided, answer using general knowledge and say when you are unsure. "
|
"If the question is not about Atlas/Othrys and no cluster context is provided, answer using general knowledge and say when you are unsure. "
|
||||||
"If the answer is not grounded in the provided context or tool data, say you do not know. "
|
"If the answer is not grounded in the provided context or tool data, say you do not know. "
|
||||||
@ -2974,15 +2984,27 @@ def sync_loop(token: str, room_id: str):
|
|||||||
fallback = "I don't have enough data to answer that."
|
fallback = "I don't have enough data to answer that."
|
||||||
|
|
||||||
if cluster_query:
|
if cluster_query:
|
||||||
reply = cluster_answer(
|
facts_answer = cluster_answer(
|
||||||
cleaned_body,
|
cleaned_body,
|
||||||
inventory=inventory,
|
inventory=inventory,
|
||||||
snapshot=snapshot,
|
snapshot=snapshot,
|
||||||
workloads=workloads,
|
workloads=workloads,
|
||||||
history_lines=history[hist_key],
|
history_lines=history[hist_key],
|
||||||
)
|
)
|
||||||
if not reply:
|
open_ended = _is_subjective_query(cleaned_body) or _knowledge_intent(cleaned_body)
|
||||||
reply = fallback
|
if open_ended:
|
||||||
|
llm_context = _append_history_context(context, history[hist_key])
|
||||||
|
reply = ollama_reply_with_thinking(
|
||||||
|
token,
|
||||||
|
rid,
|
||||||
|
hist_key,
|
||||||
|
cleaned_body,
|
||||||
|
context=llm_context,
|
||||||
|
fallback=facts_answer or fallback,
|
||||||
|
use_history=False,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
reply = facts_answer or fallback
|
||||||
else:
|
else:
|
||||||
llm_prompt = cleaned_body
|
llm_prompt = cleaned_body
|
||||||
reply = ollama_reply_with_thinking(
|
reply = ollama_reply_with_thinking(
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user