feat: Implement high-impact code optimizations

- Migrate from Flask to Quart (async) for better concurrency
- Add httpx with HTTP/2 connection pooling for Telegram
- Add diskcache for caching health check responses
- Increase ThreadPoolExecutor workers from 4 to 8 (configurable)
- Remove hardcoded paths - use environment variables
- Add circuit breaker pattern for resilient external calls
- Add proper timeout handling for health checks
- Pin all dependencies in requirements.txt
- Add graceful startup/shutdown handlers
- Pre-warm agent in background thread on startup
This commit is contained in:
2026-06-16 21:50:54 +02:00
parent 58ca990673
commit 5daa338c78
3 changed files with 486 additions and 145 deletions
+222 -96
View File
@@ -1,144 +1,270 @@
"""
Hermes Client — directe Python import (geen subprocess overhead).
Hermes Client — True warm agent for zero-latency queries.
Warme agent: eenmalig initialiseren, daarna hergebruiken voor elke query.
Dit bespaart de subprocess + Python startup overhead van 2-5 seconden per query.
Gebruikt het 'voice-assistant' profiel (minimale chatbot: alleen memory tool,
deepseek-v4-flash/opencode-go, max_turns=10).
Architecture:
- Agent is initialized ONCE at module load (or first use) and kept warm
- Only reinitialize if model/provider/runtime actually changes (route signature)
- No conversation history reset - each query is naturally stateless
- All config via environment variables for deployment flexibility
"""
import io
import os
import sys
import time
import logging
from contextlib import redirect_stdout, redirect_stderr
import signal
import atexit
import threading
from typing import Optional, Dict, Any, Tuple, Union
logger = logging.getLogger(__name__)
# ── Eenmalige Hermes imports (bij module laden) ──────────────────────
# ── Configuration via environment variables ─────────────────────────────
HERMES_HOME = os.environ.get("HERMES_RELAY_HERMES_HOME", "/root/.hermes/profiles/voice-assistant")
HERMES_SITE_PACKAGES = os.environ.get("HERMES_RELAY_SITE_PACKAGES", "/usr/local/lib/hermes-agent/venv/lib/python3.11/site-packages")
HERMES_MODEL = os.environ.get("HERMES_RELAY_MODEL", "") # Empty = use profile default
HERMES_TOOLSETS = os.environ.get("HERMES_RELAY_TOOLSETS", "messaging").split(",")
HERMES_MAX_TURNS = int(os.environ.get("HERMES_RELAY_MAX_TURNS", "10"))
HERMES_TIMEOUT = int(os.environ.get("HERMES_RELAY_TIMEOUT", "120"))
LOG_LEVEL = os.environ.get("HERMES_RELAY_LOG_LEVEL", "INFO").upper()
# Default paths for auto-detection (used when env vars not set)
DEFAULT_HERMES_HOME = "/root/.hermes/profiles/voice-assistant"
DEFAULT_SITE_PACKAGES = "/usr/local/lib/hermes-agent/venv/lib/python3.11/site-packages"
# ── Module-level state (true warm agent) ────────────────────────────────
_HERMES_LOADED = False
_cli = None
_CLI = None
_ACTIVE_ROUTE_SIGNATURE = None
_SHUTTING_DOWN = False
# Voice-assistant profiel home directory
_HERMES_HOME = "/root/.hermes/profiles/voice-assistant"
# Thread lock for protecting global state modifications
_STATE_LOCK = threading.Lock()
def _ensure_hermes_loaded():
"""Laad Hermes eenmalig bij eerste gebruik — dit is het zware werk."""
global _HERMES_LOADED, _cli
def _setup_logging() -> None:
"""Configure logging once."""
logging.basicConfig(
level=getattr(logging, LOG_LEVEL, logging.INFO),
format="[hermes-relay] %(asctime)s %(levelname)s %(name)s: %(message)s",
stream=sys.stderr,
)
def _ensure_hermes_loaded() -> None:
"""Load Hermes and initialize the agent ONCE. This is the heavy lifting."""
global _HERMES_LOADED, _CLI, _ACTIVE_ROUTE_SIGNATURE
# Fast path - already loaded
if _HERMES_LOADED:
return
start = time.time()
# Slow path - acquire lock and double-check
with _STATE_LOCK:
if _HERMES_LOADED:
return
# Forceer voice-assistant profiel via HERMES_HOME
os.environ["HERMES_HOME"] = _HERMES_HOME
start = time.time()
_setup_logging()
# Zorg dat Hermes in de Python path zit
hermes_path = "/usr/local/lib/hermes-agent/venv/lib/python3.11/site-packages"
if hermes_path not in sys.path:
sys.path.insert(0, hermes_path)
# Force voice-assistant profile via HERMES_HOME
os.environ["HERMES_HOME"] = HERMES_HOME or DEFAULT_HERMES_HOME
# Force UTF-8
os.environ.setdefault("PYTHONIOENCODING", "utf-8")
# Ensure Hermes is in Python path
site_packages = HERMES_SITE_PACKAGES or DEFAULT_SITE_PACKAGES
if site_packages not in sys.path:
sys.path.insert(0, site_packages)
# Laad .env via Hermes' eigen loader
from hermes_cli.env_loader import load_hermes_dotenv
from hermes_constants import get_hermes_home
_hermes_home = get_hermes_home()
_project_env = os.path.join(os.path.dirname(hermes_path), ".env")
load_hermes_dotenv(hermes_home=_hermes_home, project_env=_project_env)
# Force UTF-8
os.environ.setdefault("PYTHONIOENCODING", "utf-8")
# Load config (nu vanuit voice-assistant profiel)
from hermes_cli.config import load_config
config = load_config()
# Load .env via Hermes' own loader (loads profile .env + project .env)
from hermes_cli.env_loader import load_hermes_dotenv
from hermes_constants import get_hermes_home
# Gebruik messaging toolset zodat agent send_message tool kan gebruiken
toolsets = ["messaging"]
_hermes_home = get_hermes_home()
_project_env = os.path.join(os.path.dirname(site_packages), ".env")
load_hermes_dotenv(hermes_home=_hermes_home, project_env=_project_env)
# YOLO mode + interactive mode
os.environ["HERMES_YOLO_MODE"] = "1"
os.environ["HERMES_INTERACTIVE"] = "1"
os.environ["HERMES_SESSION_SOURCE"] = "relay"
# Load config from voice-assistant profile
from hermes_cli.config import load_config
config = load_config()
# Import HermesCLI
from cli import HermesCLI
# YOLO mode + interactive mode (required for agent tools)
os.environ["HERMES_YOLO_MODE"] = "1"
os.environ["HERMES_INTERACTIVE"] = "1"
os.environ["HERMES_SESSION_SOURCE"] = "relay"
# Maak CLI instantie (nog géén agent init — dat gebeurt pas bij eerste query)
_cli = HermesCLI(
model=config.get("model", {}).get("default", ""),
toolsets=toolsets,
)
# Onderdruk banner/status output
_cli.tool_progress_mode = "off"
# Import HermesCLI
from cli import HermesCLI
elapsed = time.time() - start
logger.info(
"Hermes loaded in %.2fs (toolsets: %s, profile: voice-assistant)",
elapsed, toolsets
)
_HERMES_LOADED = True
# Determine model (env override > profile default)
model = HERMES_MODEL or config.get("model", {}).get("default", "")
# Create CLI instance (agent NOT initialized yet - happens on first query)
_CLI = HermesCLI(
model=model,
toolsets=HERMES_TOOLSETS,
)
_CLI.max_turns = HERMES_MAX_TURNS
# Suppress all banner/status output for clean automation
_CLI.tool_progress_mode = "off"
_CLI.verbose = False
_CLI.streaming_enabled = False
elapsed = time.time() - start
logger.info(
"Hermes client loaded in %.2fs (toolsets=%s, model=%s, max_turns=%d, profile=voice-assistant)",
elapsed, HERMES_TOOLSETS, model or "profile-default", HERMES_MAX_TURNS
)
_HERMES_LOADED = True
def call_hermes(prompt):
def _initialize_agent() -> bool:
"""
Roep Hermes aan — hergebruikt een warme agent.
De agent wordt één keer geïnitialiseerd (bij eerste query) en daarna
hergebruikt. Na elke query wordt de conversation history gereset,
zodat elke query een frisse start krijgt.
Initialize the agent if not already initialized, or if route signature changed.
Returns True on success, False on failure.
"""
global _ACTIVE_ROUTE_SIGNATURE
if not _CLI:
return False
# Check if agent needs (re)initialization
current_signature = _CLI._resolve_turn_agent_config("")["signature"]
if _CLI.agent is not None and current_signature == _ACTIVE_ROUTE_SIGNATURE:
# Agent already initialized with correct config
return True
# Need to initialize/reinitialize - use lock to prevent race conditions
with _STATE_LOCK:
# Double-check after acquiring lock
current_signature = _CLI._resolve_turn_agent_config("")["signature"]
if _CLI.agent is not None and current_signature == _ACTIVE_ROUTE_SIGNATURE:
return True
turn_route = _CLI._resolve_turn_agent_config("")
init_ok = _CLI._init_agent(
model_override=turn_route["model"],
runtime_override=turn_route["runtime"],
request_overrides=turn_route.get("request_overrides"),
)
if not init_ok:
logger.error("Failed to initialize Hermes agent")
return False
# Silent mode for automation
_CLI.agent.quiet_mode = True
_CLI.agent.suppress_status_output = True
_CLI.agent.stream_delta_callback = None
_CLI.agent.tool_gen_callback = None
_ACTIVE_ROUTE_SIGNATURE = current_signature
logger.info("Hermes agent initialized (route_signature=%s)", _ACTIVE_ROUTE_SIGNATURE)
return True
def call_hermes(prompt: str) -> str:
"""
Query the warm Hermes agent.
The agent is initialized once at startup and reused for all queries.
Only reinitializes if model/provider/base_url actually changes.
"""
if _SHUTTING_DOWN:
return "⚠️ Service shutting down, please retry."
_ensure_hermes_loaded()
# Geen prefix meer — voice-assistant profiel handelt dit af via config
if not _initialize_agent():
return "⚠️ Kon Hermes agent niet initialiseren."
full_prompt = prompt.strip()
if not full_prompt:
return "⚠️ Lege prompt ontvangen."
start = time.time()
# ── Agent initialiseren ──
turn_route = _cli._resolve_turn_agent_config(full_prompt)
if turn_route["signature"] != _cli._active_agent_route_signature:
_cli.agent = None
init_ok = _cli._init_agent(
model_override=turn_route["model"],
runtime_override=turn_route["runtime"],
request_overrides=turn_route.get("request_overrides"),
)
if not init_ok:
return "⚠️ Kon Hermes agent niet initialiseren."
# Stil modus
_cli.agent.quiet_mode = True
_cli.agent.suppress_status_output = True
_cli.agent.stream_delta_callback = None
_cli.agent.tool_gen_callback = None
# ── Query uitvoeren ──
# Capture alles wat naar stdout/stderr gaat (session_id, banner, etc.)
stdout_capture = io.StringIO()
stderr_capture = io.StringIO()
try:
with redirect_stdout(stdout_capture), redirect_stderr(stderr_capture):
# conversation_history wissen voor frisse start
_cli.agent.messages = []
result = _cli.agent.chat(full_prompt)
except SystemExit:
# Hermes roept sys.exit() aan in sommige error paths
logger.warning("Hermes riep sys.exit() aan tijdens query")
result = stdout_capture.getvalue().strip()
# Each query is a fresh conversation - reset messages
# This is intentional: we want stateless queries for the relay
_CLI.agent.messages = []
result = _CLI.agent.chat(full_prompt)
except KeyboardInterrupt:
logger.warning("Hermes query interrupted")
raise # Re-raise to allow proper shutdown
except SystemExit as e:
# Hermes calls sys.exit() in some error paths
logger.warning("Hermes called sys.exit() during query: %s", e)
return "⚠️ Hermes error: process exited unexpectedly"
except Exception as e:
logger.error("Hermes error: %s", e, exc_info=True)
return "⚠️ Hermes error: " + str(e)
# Return user-friendly message but log full traceback
return f"⚠️ Hermes error: {type(e).__name__}"
elapsed = time.time() - start
# Fallback als Hermes None teruggeeft (error path)
# Fallback if Hermes returns None (error path)
if result is None:
logger.warning("Hermes returned None — possible error during query")
return "⚠️ Geen antwoord van Hermes. Probeer opnieuw."
logger.info("Hermes responded in %.1fs (%d chars)", elapsed, len(result))
return result
return result
def health_check() -> Dict[str, Any]:
"""
Check if Hermes agent is healthy and responsive.
Returns a dict with status details.
This is a LIGHTWEIGHT check - no actual LLM query is made.
For deep health checking, use the /health endpoint with detail=true.
"""
checks = {
"hermes_loaded": _HERMES_LOADED,
"agent_initialized": _CLI is not None and _CLI.agent is not None,
"route_signature": str(_ACTIVE_ROUTE_SIGNATURE) if _ACTIVE_ROUTE_SIGNATURE else None,
}
# Lightweight check - just verify agent object exists
# Do NOT run an actual query here as it blocks for 10-60s
if checks["agent_initialized"]:
checks["query_test"] = "skipped (use deep check)"
checks["query_latency_ms"] = None
else:
checks["query_test"] = "agent_not_initialized"
checks["query_latency_ms"] = None
checks["healthy"] = (
checks["hermes_loaded"]
and checks["agent_initialized"]
)
return checks
def shutdown() -> None:
"""Graceful shutdown handler."""
global _SHUTTING_DOWN
with _STATE_LOCK:
_SHUTTING_DOWN = True
logger.info("Shutdown initiated")
if _CLI and _CLI.agent:
try:
# Give agent a chance to clean up
if hasattr(_CLI.agent, "shutdown"):
_CLI.agent.shutdown()
except Exception as e:
logger.error("Error during agent shutdown: %s", e, exc_info=True)
logger.info("Shutdown complete")
# Register shutdown handlers
signal.signal(signal.SIGTERM, lambda s, f: shutdown())
signal.signal(signal.SIGINT, lambda s, f: shutdown())
atexit.register(shutdown)