v1.1: Warme agent — directe Python import i.p.v. subprocess

- Vervang subprocess calls naar hermes chat -q door directe Python import
- Nieuwe hermes_client.py met warme AIAgent (eenmalig init, hergebruik per query)
- Initializing agent overhead: 3-5s per query -> 0.48s eenmalig
- Queries na eerste zijn ~5s (alleen LLM API tijd)
- Auxiliary vision provider leeggezet (geen DNS lookup naar Nous)
This commit is contained in:
2026-06-03 04:53:42 +02:00
parent 5f83c410be
commit 1bac8db3c5
2 changed files with 177 additions and 68 deletions
+46 -68
View File
@@ -1,47 +1,53 @@
"""
Hermes Relay — Flask bridge between Node-RED and Hermes Agent.
Hermes Relay — Flask bridge tussen Node-RED en Hermes Agent (v2.0).
Node-RED sends a POST with msg.payload → this script calls `hermes chat -q`
via subprocess → returns the answer as JSON response AND sends it to the
Telegram chat via `hermes send`.
Gebruikt een warme Hermes agent (directe Python import) in plaats van
subprocess per query. Dit bespaart ~3-5 seconden startup tijd per query.
Node-RED stuurt POST met msg.payload → Hermes (direct) → Antwoord + Telegram.
Endpoints:
POST /ask — main relay endpoint
GET /health — health check for Node-RED monitoring
Architecture:
Node-RED (192.168.1.125) ──POST──▶ Hermes Relay (192.168.1.74:8650)
│
├──▶ hermes chat -q (subprocess)
│ │
│ ▼
│ JSON response ──▶ Node-RED
│
└──▶ hermes send --to telegram
│
▼
Telegram chat
GET /health — health check
"""
import subprocess
import os
import sys
import time
import logging
# ── Hermes path toevoegen (zodat we Hermes direct kunnen importeren) ───
_HERMES_SITE = '/usr/local/lib/hermes-agent/venv/lib/python3.11/site-packages'
if _HERMES_SITE not in sys.path:
sys.path.insert(0, _HERMES_SITE)
from flask import Flask, request, jsonify
# ── Config ──────────────────────────────────────────────────────────────
TIMEOUT = 120 # max seconden voor Hermes query
TG_TARGET = "telegram" # hermes send target
PROMPT_PREFIX = "/fast" # wordt aan elke prompt toegevoegd
MODEL = "qwen3.7-max" # model override (leeg = default uit config)
# ── Logging ─────────────────────────────────────────────────────────────
logging.basicConfig(
level=logging.INFO,
format="[hermes-relay] %(asctime)s %(levelname)s %(message)s",
stream=sys.stderr,
)
logger = logging.getLogger(__name__)
# ── Flask app ───────────────────────────────────────────────────────────
app = Flask(__name__)
# ── Config ──────────────────────────────────────────────────────────────
HERMES_BIN = "/usr/local/bin/hermes"
TIMEOUT = 120 # seconds — hermes can take a while on complex queries
TG_TARGET = "telegram" # hermes send target (home channel)
# Prompt & Model
PROMPT_PREFIX = "/fast" # prepended to every query (empty string to disable)
MODEL = "qwen3.7-max" # model override (empty string to use default)
# ── Hermes warm houden ──────────────────────────────────────────────────
# Import de Hermes client (triggert eenmalige module laden)
HERMES_BIN = "/usr/local/bin/hermes" # nog steeds nodig voor hermes send
from hermes_client import call_hermes as _hermes_query
def send_telegram(text: str) -> bool:
"""Send a message via hermes send. Returns True on success."""
"""Stuur bericht via hermes send subprocess."""
import subprocess
try:
result = subprocess.run(
[HERMES_BIN, "send", "--to", TG_TARGET, "--quiet", text],
@@ -51,45 +57,18 @@ def send_telegram(text: str) -> bool:
)
return result.returncode == 0
except Exception as e:
print(f"[hermes-relay] Telegram send failed: {e}", file=sys.stderr)
logger.error(f"Telegram send failed: {e}")
return False
def call_hermes(prompt: str) -> str:
"""Call hermes chat -q and return stdout."""
# Build full prompt with prefix
full_prompt = f"{PROMPT_PREFIX} {prompt}".strip() if PROMPT_PREFIX else prompt
cmd = [HERMES_BIN, "chat", "-q", full_prompt, "-Q", "--yolo"]
# Add model override if configured
if MODEL:
cmd.extend(["-m", MODEL])
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=TIMEOUT,
)
if result.returncode != 0:
return f"⚠️ Hermes exited with code {result.returncode}\nstderr: {result.stderr.strip()}"
return result.stdout.strip()
except subprocess.TimeoutExpired:
return f"⚠️ Timeout — Hermes took longer than {TIMEOUT}s"
except Exception as e:
return f"⚠️ Error: {e}"
# ── Routes ──────────────────────────────────────────────────────────────
@app.route("/ask", methods=["POST"])
def ask():
"""Main relay endpoint. Expects JSON body with 'payload' field."""
"""Main relay endpoint."""
start = time.time()
# Accept both JSON body and form data
# Parse input
if request.is_json:
data = request.get_json(silent=True) or {}
prompt = data.get("payload", "")
@@ -99,15 +78,15 @@ def ask():
if not prompt:
return jsonify({"status": "error", "message": "No payload received"}), 400
print(f"[hermes-relay] Received: {prompt[:100]}...")
logger.info(f"Received: {prompt[:100]}...")
# Call Hermes
answer = call_hermes(prompt)
# Roep Hermes aan (direct, geen subprocess)
answer = _hermes_query(prompt)
elapsed = round(time.time() - start, 1)
print(f"[hermes-relay] Answer ({elapsed}s): {answer[:200]}...")
logger.info(f"Answer ({elapsed}s): {answer[:200]}...")
# Also send to Telegram
# Stuur ook naar Telegram
tg_msg = f"🤖 Hermes Relay\n\nVraag: {prompt}\n\nAntwoord: {answer}"
tg_sent = send_telegram(tg_msg)
@@ -121,16 +100,15 @@ def ask():
@app.route("/health", methods=["GET"])
def health():
"""Health check — use this in Node-RED to monitor relay status."""
"""Health check voor Node-RED monitoring."""
return jsonify({
"status": "ok",
"service": "hermes-relay",
"hermes_bin": HERMES_BIN,
"timeout": TIMEOUT,
"prompt_prefix": PROMPT_PREFIX or "(none)",
"model": MODEL or "(default)",
"prompt_prefix": PROMPT_PREFIX or "(none)",
})
if __name__ == "__main__":
logger.info("Starting Hermes Relay v2.0 (warme agent)...")
app.run(host="0.0.0.0", port=8650, debug=False)