Files
hermes-relay/app.py
T

137 lines
4.6 KiB
Python

"""
Hermes Relay — Flask bridge between Node-RED and Hermes Agent.
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`.
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
"""
import subprocess
import sys
import time
from flask import Flask, request, jsonify
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)
def send_telegram(text: str) -> bool:
"""Send a message via hermes send. Returns True on success."""
try:
result = subprocess.run(
[HERMES_BIN, "send", "--to", TG_TARGET, "--quiet", text],
capture_output=True,
text=True,
timeout=30,
)
return result.returncode == 0
except Exception as e:
print(f"[hermes-relay] Telegram send failed: {e}", file=sys.stderr)
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."""
start = time.time()
# Accept both JSON body and form data
if request.is_json:
data = request.get_json(silent=True) or {}
prompt = data.get("payload", "")
else:
prompt = request.form.get("payload", "")
if not prompt:
return jsonify({"status": "error", "message": "No payload received"}), 400
print(f"[hermes-relay] Received: {prompt[:100]}...")
# Call Hermes
answer = call_hermes(prompt)
elapsed = round(time.time() - start, 1)
print(f"[hermes-relay] Answer ({elapsed}s): {answer[:200]}...")
# Also send to Telegram
tg_msg = f"🤖 Hermes Relay\n\nVraag: {prompt}\n\nAntwoord: {answer}"
tg_sent = send_telegram(tg_msg)
return jsonify({
"status": "ok",
"answer": answer,
"elapsed_seconds": elapsed,
"telegram_sent": tg_sent,
})
@app.route("/health", methods=["GET"])
def health():
"""Health check — use this in Node-RED to monitor relay status."""
return jsonify({
"status": "ok",
"service": "hermes-relay",
"hermes_bin": HERMES_BIN,
"timeout": TIMEOUT,
"prompt_prefix": PROMPT_PREFIX or "(none)",
"model": MODEL or "(default)",
})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8650, debug=False)