atlasbot: add internal endpoint and portal wiring

This commit is contained in:
Brad Stein 2026-01-26 22:43:58 -03:00
parent 689bf10995
commit c8662a624e
5 changed files with 85 additions and 1 deletions

View File

@ -28,6 +28,7 @@ spec:
{{ with secret "kv/data/atlas/shared/chat-ai-keys-runtime" }} {{ with secret "kv/data/atlas/shared/chat-ai-keys-runtime" }}
export CHAT_KEY_MATRIX="{{ .Data.data.matrix }}" export CHAT_KEY_MATRIX="{{ .Data.data.matrix }}"
export CHAT_KEY_HOMEPAGE="{{ .Data.data.homepage }}" export CHAT_KEY_HOMEPAGE="{{ .Data.data.homepage }}"
export AI_ATLASBOT_TOKEN="{{ .Data.data.homepage }}"
{{ end }} {{ end }}
{{ with secret "kv/data/atlas/shared/portal-e2e-client" }} {{ with secret "kv/data/atlas/shared/portal-e2e-client" }}
export PORTAL_E2E_CLIENT_ID="{{ .Data.data.client_id }}" export PORTAL_E2E_CLIENT_ID="{{ .Data.data.client_id }}"
@ -66,6 +67,10 @@ spec:
value: qwen2.5-coder:7b-instruct-q4_0 value: qwen2.5-coder:7b-instruct-q4_0
- name: AI_CHAT_TIMEOUT_SEC - name: AI_CHAT_TIMEOUT_SEC
value: "480" value: "480"
- name: AI_ATLASBOT_ENDPOINT
value: http://atlasbot.comms.svc.cluster.local:8090/v1/answer
- name: AI_ATLASBOT_TIMEOUT_SEC
value: "5"
- name: AI_NODE_NAME - name: AI_NODE_NAME
valueFrom: valueFrom:
fieldRef: fieldRef:

View File

@ -16,7 +16,7 @@ spec:
labels: labels:
app: atlasbot app: atlasbot
annotations: annotations:
checksum/atlasbot-configmap: manual-atlasbot-24 checksum/atlasbot-configmap: manual-atlasbot-25
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"
@ -87,6 +87,11 @@ spec:
value: "480" value: "480"
- name: ATLASBOT_THINKING_INTERVAL_SEC - name: ATLASBOT_THINKING_INTERVAL_SEC
value: "120" value: "120"
- name: ATLASBOT_HTTP_PORT
value: "8090"
ports:
- name: http
containerPort: 8090
resources: resources:
requests: requests:
cpu: 100m cpu: 100m

View File

@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: atlasbot
namespace: comms
labels:
app: atlasbot
spec:
selector:
app: atlasbot
ports:
- name: http
port: 8090
targetPort: 8090
type: ClusterIP

View File

@ -14,6 +14,7 @@ resources:
- guest-register-deployment.yaml - guest-register-deployment.yaml
- guest-register-service.yaml - guest-register-service.yaml
- atlasbot-deployment.yaml - atlasbot-deployment.yaml
- atlasbot-service.yaml
- wellknown.yaml - wellknown.yaml
- atlasbot-rbac.yaml - atlasbot-rbac.yaml
- mas-secrets-ensure-rbac.yaml - mas-secrets-ensure-rbac.yaml

View File

@ -5,6 +5,7 @@ import re
import ssl import ssl
import threading import threading
import time import time
from http.server import BaseHTTPRequestHandler, HTTPServer
from typing import Any from typing import Any
from urllib import error, parse, request from urllib import error, parse, request
@ -1089,6 +1090,62 @@ def _normalize_reply(value: Any) -> str:
return text return text
# Internal HTTP endpoint for cluster answers (website uses this).
class _AtlasbotHandler(BaseHTTPRequestHandler):
server_version = "AtlasbotHTTP/1.0"
def _write_json(self, status: int, payload: dict[str, Any]):
body = json.dumps(payload).encode("utf-8")
self.send_response(status)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def _authorized(self) -> bool:
if not ATLASBOT_INTERNAL_TOKEN:
return True
token = self.headers.get("X-Internal-Token", "")
return token == ATLASBOT_INTERNAL_TOKEN
def do_GET(self): # noqa: N802
if self.path == "/health":
self._write_json(200, {"status": "ok"})
return
self._write_json(404, {"error": "not_found"})
def do_POST(self): # noqa: N802
if self.path != "/v1/answer":
self._write_json(404, {"error": "not_found"})
return
if not self._authorized():
self._write_json(401, {"error": "unauthorized"})
return
try:
length = int(self.headers.get("Content-Length", "0"))
except ValueError:
length = 0
raw = self.rfile.read(length) if length > 0 else b""
try:
payload = json.loads(raw.decode("utf-8")) if raw else {}
except json.JSONDecodeError:
self._write_json(400, {"error": "invalid_json"})
return
prompt = str(payload.get("prompt") or payload.get("question") or "").strip()
if not prompt:
self._write_json(400, {"error": "missing_prompt"})
return
inventory = node_inventory_live()
answer = structured_answer(prompt, inventory=inventory, metrics_summary="")
self._write_json(200, {"answer": answer})
def _start_http_server():
server = HTTPServer(("0.0.0.0", ATLASBOT_HTTP_PORT), _AtlasbotHandler)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
# Conversation state. # Conversation state.
history = collections.defaultdict(list) # (room_id, sender|None) -> list[str] (short transcript) history = collections.defaultdict(list) # (room_id, sender|None) -> list[str] (short transcript)
@ -1326,6 +1383,7 @@ def login_with_retry():
def main(): def main():
load_kb() load_kb()
_start_http_server()
token = login_with_retry() token = login_with_retry()
try: try:
room_id = resolve_alias(token, ROOM_ALIAS) room_id = resolve_alias(token, ROOM_ALIAS)