atlasbot: add internal endpoint and portal wiring
This commit is contained in:
parent
689bf10995
commit
c8662a624e
@ -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:
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
15
services/comms/atlasbot-service.yaml
Normal file
15
services/comms/atlasbot-service.yaml
Normal 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
|
||||||
@ -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
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user