Initial release v1.0 - Flask bridge tussen Node-RED en Hermes Agent
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
venv/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
.env
|
||||||
|
*.log
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
# Hermes Relay
|
||||||
|
|
||||||
|
Flask bridge tussen Node-RED en Hermes Agent.
|
||||||
|
|
||||||
|
## Architectuur
|
||||||
|
|
||||||
|
```
|
||||||
|
Node-RED (192.168.1.125) ──POST /ask──▶ Hermes Relay (192.168.1.74:8650)
|
||||||
|
│
|
||||||
|
├──▶ hermes chat -q (subprocess)
|
||||||
|
│ │
|
||||||
|
│ ▼
|
||||||
|
│ JSON response ──▶ Node-RED
|
||||||
|
│
|
||||||
|
└──▶ hermes send --to telegram
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Telegram chat
|
||||||
|
```
|
||||||
|
|
||||||
|
## Endpoints
|
||||||
|
|
||||||
|
### `POST /ask`
|
||||||
|
Verwacht JSON body:
|
||||||
|
```json
|
||||||
|
{ "payload": "Wat is het weer in Best?" }
|
||||||
|
```
|
||||||
|
|
||||||
|
Response:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "ok",
|
||||||
|
"answer": "Het is 22°C en zonnig in Best...",
|
||||||
|
"elapsed_seconds": 8.3,
|
||||||
|
"telegram_sent": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `GET /health`
|
||||||
|
Health check voor monitoring.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /root/hermes-relay
|
||||||
|
python3 -m venv venv
|
||||||
|
source venv/bin/activate
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
# Test
|
||||||
|
python app.py
|
||||||
|
|
||||||
|
# Of als service
|
||||||
|
sudo systemctl enable hermes-relay
|
||||||
|
sudo systemctl start hermes-relay
|
||||||
|
```
|
||||||
|
|
||||||
|
## Node-RED Nodes
|
||||||
|
|
||||||
|
### Versturen (HTTP Request node)
|
||||||
|
- **Method:** POST
|
||||||
|
- **URL:** `http://192.168.1.74:8650/ask`
|
||||||
|
- **Return:** a parsed JSON object
|
||||||
|
- **Timeout:** 120000 (ms) — Hermes kan even duren
|
||||||
|
|
||||||
|
### Ontvangen
|
||||||
|
Het antwoord komt terug als `msg.payload`:
|
||||||
|
- `msg.payload.answer` — het Hermes antwoord
|
||||||
|
- `msg.payload.elapsed_seconds` — hoe lang het duurde
|
||||||
|
- `msg.payload.telegram_sent` — of het ook naar Telegram is gestuurd
|
||||||
|
- `msg.payload.status` — "ok" of "error"
|
||||||
|
|
||||||
|
### Health check (optioneel)
|
||||||
|
- **Method:** GET
|
||||||
|
- **URL:** `http://192.168.1.74:8650/health`
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
"""
|
||||||
|
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)
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
flask>=3.0
|
||||||
|
requests>=2.31
|
||||||
Reference in New Issue
Block a user