wip(referenz): Stand des abgebrochenen Laufs A2 gesichert (5 von 17 Dateien)
Ampel / ampel (push) Successful in 21s
Ampel / ampel (push) Successful in 21s
This commit is contained in:
+2
-9
@@ -19,13 +19,6 @@ MODELS_DIR = Path(os.environ.get("MC_MODELS_DIR", "/srv/models"))
|
|||||||
# Persistent neben den Modellen (übersteht Deploys). TTL = Frische-Fenster.
|
# Persistent neben den Modellen (übersteht Deploys). TTL = Frische-Fenster.
|
||||||
DISCOVER_CACHE_PATH = Path(os.environ.get("MC_DISCOVER_CACHE", str(MODELS_DIR / "mc2-discover.json")))
|
DISCOVER_CACHE_PATH = Path(os.environ.get("MC_DISCOVER_CACHE", str(MODELS_DIR / "mc2-discover.json")))
|
||||||
DISCOVER_TTL = int(os.environ.get("MC_DISCOVER_TTL", "43200")) # 12 h
|
DISCOVER_TTL = int(os.environ.get("MC_DISCOVER_TTL", "43200")) # 12 h
|
||||||
# Geteiltes Gedächtnis (SQLite, WAL). Persistent neben den Modellen.
|
|
||||||
# Hinweis: nur noch für die einmalige Mem0-Migration relevant — das aktive Gedächtnis
|
|
||||||
# liegt jetzt in Mem0/Chroma hinter dem Sidecar (siehe MEM0_SERVICE_URL).
|
|
||||||
MEMORY_DB = Path(os.environ.get("MC_MEMORY_DB", str(MODELS_DIR / "mc2-memory.db")))
|
|
||||||
# Mem0-Sidecar (auto-lernendes, semantisches Gedächtnis). Läuft im ~/.mem0/venv (Python 3.12),
|
|
||||||
# weil mem0+chromadb unter dem 3.14-Backend nicht laufen. MC2 spricht ihn lokal per HTTP an.
|
|
||||||
MEM0_SERVICE_URL = os.environ.get("MC_MEM0_SERVICE_URL", "http://127.0.0.1:8765").rstrip("/")
|
|
||||||
# Befehl-Vorlage für llama-swap: {model}=GGUF-Pfad, {ctx}=Kontext, ${PORT} bleibt stehen.
|
# Befehl-Vorlage für llama-swap: {model}=GGUF-Pfad, {ctx}=Kontext, ${PORT} bleibt stehen.
|
||||||
# Hinweis: --prompt-cache/--prompt-cache-all sind llama-CLI-Flags, NICHT llama-server —
|
# Hinweis: --prompt-cache/--prompt-cache-all sind llama-CLI-Flags, NICHT llama-server —
|
||||||
# llama-server lehnt sie ab ("invalid argument") und startet dann nicht. Prompt-Caching
|
# llama-server lehnt sie ab ("invalid argument") und startet dann nicht. Prompt-Caching
|
||||||
@@ -72,7 +65,7 @@ V1_UPSTREAM = os.environ.get("MC_V1_UPSTREAM", "").rstrip("/")
|
|||||||
HERMES_API_URL = os.environ.get("HERMES_API_URL", "http://127.0.0.1:8642").rstrip("/")
|
HERMES_API_URL = os.environ.get("HERMES_API_URL", "http://127.0.0.1:8642").rstrip("/")
|
||||||
# API-Key der Hermes-`api_server`-Plattform (~/.hermes/.env: API_SERVER_KEY). Nötig für
|
# API-Key der Hermes-`api_server`-Plattform (~/.hermes/.env: API_SERVER_KEY). Nötig für
|
||||||
# /v1/chat/completions (Voice-Pipeline) — Bearer-Auth, sonst 401. Derselbe volle Agent
|
# /v1/chat/completions (Voice-Pipeline) — Bearer-Auth, sonst 401. Derselbe volle Agent
|
||||||
# (Tools + geteiltes Mem0) wie CLI/Telegram, nur über HTTP.
|
# (Tools + geteiltes Gedächtnis) wie CLI/Telegram, nur über HTTP.
|
||||||
def _read_hermes_env(key: str) -> str:
|
def _read_hermes_env(key: str) -> str:
|
||||||
"""Liest einen Schlüssel aus ~/.hermes/.env (Fallback, falls nicht in der Prozess-Env).
|
"""Liest einen Schlüssel aus ~/.hermes/.env (Fallback, falls nicht in der Prozess-Env).
|
||||||
Der MC2-Dienst erbt die Hermes-Secrets sonst nicht."""
|
Der MC2-Dienst erbt die Hermes-Secrets sonst nicht."""
|
||||||
@@ -97,7 +90,7 @@ HERMES_API_KEY = (
|
|||||||
HERMES_API_MODEL = os.environ.get("HERMES_API_MODEL", "hermes")
|
HERMES_API_MODEL = os.environ.get("HERMES_API_MODEL", "hermes")
|
||||||
|
|
||||||
# --- Voice-Sidecar (STT faster-whisper + TTS Piper/Chatterbox) ---------------
|
# --- Voice-Sidecar (STT faster-whisper + TTS Piper/Chatterbox) ---------------
|
||||||
# Eigenes Python-3.12-venv (~/.voice/venv), analog Mem0-Sidecar. MC2 proxyt nach außen.
|
# Eigenes Python-3.12-venv (~/.voice/venv). MC2 proxyt nach außen.
|
||||||
VOICE_SERVICE_URL = os.environ.get("MC_VOICE_SERVICE_URL", "http://127.0.0.1:8650").rstrip("/")
|
VOICE_SERVICE_URL = os.environ.get("MC_VOICE_SERVICE_URL", "http://127.0.0.1:8650").rstrip("/")
|
||||||
# Box-Konsole: ttyd-Web-Terminal (echte Login-Shell). Bindet NUR an Loopback
|
# Box-Konsole: ttyd-Web-Terminal (echte Login-Shell). Bindet NUR an Loopback
|
||||||
# (127.0.0.1:7682, base-path /console) und wird von MC2 über den ohnehin offenen Port 9001
|
# (127.0.0.1:7682, base-path /console) und wird von MC2 über den ohnehin offenen Port 9001
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import os
|
|||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from config import GATEWAY_URL, HERMES_API_URL, LLAMA_SWAP_URL, MEM0_SERVICE_URL, V1_UPSTREAM, VOICE_SERVICE_URL
|
from config import GATEWAY_URL, HERMES_API_URL, LLAMA_SWAP_URL, V1_UPSTREAM, VOICE_SERVICE_URL
|
||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from services import backup as backup_svc
|
from services import backup as backup_svc
|
||||||
@@ -42,13 +42,6 @@ def history(minutes: int = 60) -> dict:
|
|||||||
return metrics_history.history(minutes)
|
return metrics_history.history(minutes)
|
||||||
|
|
||||||
|
|
||||||
def _mem0_reachable() -> bool:
|
|
||||||
try:
|
|
||||||
return httpx.get(f"{MEM0_SERVICE_URL}/health", timeout=2).status_code == 200
|
|
||||||
except Exception:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def _voice_reachable() -> bool:
|
def _voice_reachable() -> bool:
|
||||||
try:
|
try:
|
||||||
return httpx.get(f"{VOICE_SERVICE_URL}/health", timeout=2).status_code == 200
|
return httpx.get(f"{VOICE_SERVICE_URL}/health", timeout=2).status_code == 200
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ Voice-Endpoints für „Mit Hermes reden" (Browser-Voice + 3D-Avatar).
|
|||||||
|
|
||||||
Dünner Layer: STT/TTS werden zum Voice-Sidecar (:8650) geproxyt; der Chat geht an den
|
Dünner Layer: STT/TTS werden zum Voice-Sidecar (:8650) geproxyt; der Chat geht an den
|
||||||
Hermes-`api_server` (:8642, OpenAI-kompatibel) — denselben vollen Agenten mit Tools +
|
Hermes-`api_server` (:8642, OpenAI-kompatibel) — denselben vollen Agenten mit Tools +
|
||||||
geteiltem Mem0 wie CLI/Telegram. Mit stabilem `X-Hermes-Session-Id` hält die Plattform den
|
geteiltem Gedächtnis wie CLI/Telegram. Mit stabilem `X-Hermes-Session-Id` hält die Plattform den
|
||||||
Transcript server-seitig, daher schickt der Client je Turn nur die neue User-Nachricht.
|
Transcript server-seitig, daher schickt der Client je Turn nur die neue User-Nachricht.
|
||||||
|
|
||||||
LAN-only (kein Token in der 2.0-Phase), wie die übrigen MC2-Endpoints.
|
LAN-only (kein Token in der 2.0-Phase), wie die übrigen MC2-Endpoints.
|
||||||
@@ -169,7 +169,7 @@ def voice_metrics() -> dict:
|
|||||||
@router.get("/voice/trace")
|
@router.get("/voice/trace")
|
||||||
def voice_trace(limit: int = 20) -> dict:
|
def voice_trace(limit: int = 20) -> dict:
|
||||||
"""Per-Turn-Trace: die letzten `limit` Chat-Turns mit Stufen-Breakdown (STT · Vision · Hirn ·
|
"""Per-Turn-Trace: die letzten `limit` Chat-Turns mit Stufen-Breakdown (STT · Vision · Hirn ·
|
||||||
Generierung, Mem0 als Unter-Detail). Neueste zuerst. Für die Latenz-Ansicht im Cockpit —
|
Generierung, Gedächtnis als Unter-Detail). Neueste zuerst. Für die Latenz-Ansicht im Cockpit —
|
||||||
damit man den EINEN langsamen Turn sieht, den ein Durchschnitt verschluckt."""
|
damit man den EINEN langsamen Turn sieht, den ein Durchschnitt verschluckt."""
|
||||||
return {"turns": get_trace(limit)}
|
return {"turns": get_trace(limit)}
|
||||||
|
|
||||||
@@ -289,7 +289,7 @@ async def voice_chat(body: ChatIn) -> StreamingResponse:
|
|||||||
headers["X-Hermes-Session-Key"] = body.session_key
|
headers["X-Hermes-Session-Key"] = body.session_key
|
||||||
|
|
||||||
async def gen():
|
async def gen():
|
||||||
# Per-Turn-Trace: sammelt STT (davor, geparkt) + Vision + Hirn-TTFT + Generierung + Mem0
|
# Per-Turn-Trace: sammelt STT (davor, geparkt) + Vision + Hirn-TTFT + Generierung + Gedächtnis
|
||||||
# (Rückruf während) zu EINEM Datensatz -> die Latenz-Ansicht zeigt den einzelnen Hänger.
|
# (Rückruf während) zu EINEM Datensatz -> die Latenz-Ansicht zeigt den einzelnen Hänger.
|
||||||
trace = TurnTrace(session_id=body.session_id, kind="voice")
|
trace = TurnTrace(session_id=body.session_id, kind="voice")
|
||||||
first = True
|
first = True
|
||||||
@@ -325,7 +325,7 @@ async def voice_chat(body: ChatIn) -> StreamingResponse:
|
|||||||
payload = {"model": body.model or HERMES_API_MODEL, "messages": messages, "stream": True}
|
payload = {"model": body.model or HERMES_API_MODEL, "messages": messages, "stream": True}
|
||||||
# Lucys Hirn (Qwen3.6) ist ein Thinking-Modell -> für die gesprochene Assistentin Thinking AUS,
|
# Lucys Hirn (Qwen3.6) ist ein Thinking-Modell -> für die gesprochene Assistentin Thinking AUS,
|
||||||
# sonst generiert es tausende Reasoning-Token VOR der kurzen Antwort (gemessen: 11k Token, ~30s TTFB).
|
# sonst generiert es tausende Reasoning-Token VOR der kurzen Antwort (gemessen: 11k Token, ~30s TTFB).
|
||||||
# Gleiches Muster wie die fast-Spur im Gateway (gateway_proxy.py) und die Mem0-Extraktion.
|
# Gleiches Muster wie die fast-Spur im Gateway (gateway_proxy.py) und die Gedächtnis-Extraktion.
|
||||||
if os.environ.get("MC_VOICE_NO_THINK", "1") not in ("0", "false", "False"):
|
if os.environ.get("MC_VOICE_NO_THINK", "1") not in ("0", "false", "False"):
|
||||||
payload["chat_template_kwargs"] = {"enable_thinking": False}
|
payload["chat_template_kwargs"] = {"enable_thinking": False}
|
||||||
try:
|
try:
|
||||||
@@ -342,7 +342,7 @@ async def voice_chat(body: ChatIn) -> StreamingResponse:
|
|||||||
if first: # Time-To-First-Byte des Hermes-Streams (Verbindungs-Overhead)
|
if first: # Time-To-First-Byte des Hermes-Streams (Verbindungs-Overhead)
|
||||||
trace.note_ttfb()
|
trace.note_ttfb()
|
||||||
first = False
|
first = False
|
||||||
# Erster CONTENT-Delta = echte Hirn-Latenz (Agent-Overhead + Mem0 + LLM-TTFT) —
|
# Erster CONTENT-Delta = echte Hirn-Latenz (Agent-Overhead + Gedächtnis + LLM-TTFT) —
|
||||||
# chat_ttfb misst nur den SSE-Start (~5 ms) und ist dafür blind.
|
# chat_ttfb misst nur den SSE-Start (~5 ms) und ist dafür blind.
|
||||||
if first_content and b'"content"' in chunk:
|
if first_content and b'"content"' in chunk:
|
||||||
trace.note_first_content()
|
trace.note_first_content()
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ def list_snapshots() -> dict:
|
|||||||
|
|
||||||
@router.get("/zeitmaschine/inhalt")
|
@router.get("/zeitmaschine/inhalt")
|
||||||
def snapshot_contents(file: str) -> dict:
|
def snapshot_contents(file: str) -> dict:
|
||||||
"""Top-Level-Komponenten eines Snapshots (mem0, hermes, llama-swap, MANIFEST …)."""
|
"""Top-Level-Komponenten eines Snapshots (hermes, llama-swap, MANIFEST …)."""
|
||||||
if not _FILE_RX.match(file):
|
if not _FILE_RX.match(file):
|
||||||
raise HTTPException(400, "Ungültiger Snapshot-Name.")
|
raise HTTPException(400, "Ungültiger Snapshot-Name.")
|
||||||
p = backup_svc.BACKUP_DIR / file
|
p = backup_svc.BACKUP_DIR / file
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Health-Wächter der Box (Lucy-Proaktivität, Faden A3).
|
Health-Wächter der Box (Lucy-Proaktivität, Faden A3).
|
||||||
|
|
||||||
Prüft periodisch die Kern-Dienste (Engine, Agent-Hirn, Hermes-Gateway, Mem0,
|
Prüft periodisch die Kern-Dienste (Engine, Agent-Hirn, Hermes-Gateway,
|
||||||
Voice-Sidecar, Platte) und meldet ZUSTANDSWECHSEL in den Melde-Briefkasten
|
Voice-Sidecar, Platte) und meldet ZUSTANDSWECHSEL in den Melde-Briefkasten
|
||||||
(services/announce.py → Lucy spricht es) und via notify.sh (Telegram).
|
(services/announce.py → Lucy spricht es) und via notify.sh (Telegram).
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user