refactor(brain): Fast-Path als Skill-Capability — fast_patterns im Manifest
Frueher: Spotify-spezifische Patterns hardcoded in agent.py — jeder neue
Steuer-Skill haette wieder Brain-Code-Aenderungen gebraucht.
Jetzt: jeder Skill deklariert seine eigenen Patterns im Manifest unter
fast_patterns: [{match, args, reply}]. Brain iteriert generisch, kein
Skill bekommt Sonderbehandlung.
- agent.py: _try_skill_fast_path liest aus skills.list_skills(), keine
Spotify-Konstanten mehr. skill_create/skill_update Tool-Schema kennt
fast_patterns (mit Beispiel + Wann-nutzen-Hinweis).
- skills.py: _normalize_fast_patterns validiert Regex + filtert kaputte
Eintraege; create_skill/update_skill akzeptieren das Feld.
- main.py: einmalige Lifespan-Migration — wenn spotify-Skill existiert
und kein fast_patterns hat, werden die alten Hardcoded-Patterns
rueberkopiert. Idempotent, laeuft bei jedem Restart sicher mehrfach.
- seed_rules.py: neue Regel `seed/skill-rule/fast-patterns-for-control`
erklaert ARIA wann sie das Feature nutzen soll (reines Steuern: ja,
kreativer Output / Parametrisierung: nein) — mit Beispiel.
Trade-off: Volume-Patterns (lauter/leiser) fallen aus dem Fast-Path raus,
weil die Multi-Step-Logik (GET state → compute → PUT) sich nicht
deklarativ ausdruecken laesst. Wer das zurueck will: Spotify-Skill um
einen action=volume_relative-Arg erweitern der die Mathe intern macht.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -45,6 +45,54 @@ logger = logging.getLogger("aria-brain")
|
||||
QDRANT_HOST = os.environ.get("QDRANT_HOST", "aria-qdrant")
|
||||
QDRANT_PORT = int(os.environ.get("QDRANT_PORT", "6333"))
|
||||
|
||||
def _seed_spotify_fast_patterns() -> None:
|
||||
"""One-shot Migration: schreibt Standard-Steuer-Patterns ins Spotify-Skill
|
||||
wenn das Skill existiert + aktiv ist + noch keine fast_patterns hat.
|
||||
|
||||
Nach diesem Run kann ARIA die Patterns frei via skill_update aendern."""
|
||||
manifest = skills_mod.read_manifest("spotify")
|
||||
if not manifest:
|
||||
logger.info("[migrate] spotify skill nicht vorhanden — nichts zu tun")
|
||||
return
|
||||
if manifest.get("fast_patterns"):
|
||||
logger.info("[migrate] spotify hat schon fast_patterns (%d) — skip",
|
||||
len(manifest["fast_patterns"]))
|
||||
return
|
||||
default_patterns = [
|
||||
# NEXT
|
||||
{"match": r"^(naechster|nächster|naechste|nächste) (track|song|titel|lied)$",
|
||||
"args": {"path": "/v1/me/player/next", "method": "POST"},
|
||||
"reply": "Spotify: nächster Track ⏭"},
|
||||
{"match": r"^(weiter|skip|ueberspringen|überspringen|ueberspring|überspring)$",
|
||||
"args": {"path": "/v1/me/player/next", "method": "POST"},
|
||||
"reply": "Spotify: nächster Track ⏭"},
|
||||
# PREVIOUS
|
||||
{"match": r"^(vorheriger|vorheriges|letzter|letztes) (track|song|titel|lied)$",
|
||||
"args": {"path": "/v1/me/player/previous", "method": "POST"},
|
||||
"reply": "Spotify: vorheriger Track ⏮"},
|
||||
{"match": r"^(zurueck|zurück)$",
|
||||
"args": {"path": "/v1/me/player/previous", "method": "POST"},
|
||||
"reply": "Spotify: vorheriger Track ⏮"},
|
||||
# PAUSE
|
||||
{"match": r"^(pause|pausiere|pausieren|stop|stopp|halt)$",
|
||||
"args": {"path": "/v1/me/player/pause", "method": "PUT"},
|
||||
"reply": "Spotify: pausiert ⏸"},
|
||||
{"match": r"^(musik|spotify) (pause|aus|stop|stopp)$",
|
||||
"args": {"path": "/v1/me/player/pause", "method": "PUT"},
|
||||
"reply": "Spotify: pausiert ⏸"},
|
||||
# PLAY
|
||||
{"match": r"^(play|weiterspielen|weiter spielen|fortsetzen|abspielen)$",
|
||||
"args": {"path": "/v1/me/player/play", "method": "PUT"},
|
||||
"reply": "Spotify: spielt ▶"},
|
||||
{"match": r"^(musik|spotify) (an|wieder an|weiter|fortsetzen)$",
|
||||
"args": {"path": "/v1/me/player/play", "method": "PUT"},
|
||||
"reply": "Spotify: spielt ▶"},
|
||||
]
|
||||
skills_mod.update_skill("spotify", {"fast_patterns": default_patterns})
|
||||
logger.info("[migrate] spotify fast_patterns gesetzt (%d Eintraege)",
|
||||
len(default_patterns))
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
"""Beim Brain-Start: System-Seed-Regeln idempotent in DB schreiben,
|
||||
@@ -54,6 +102,15 @@ async def lifespan(app: FastAPI):
|
||||
logger.info("Lifespan: seed_rules angewendet (%s)", result)
|
||||
except Exception as exc:
|
||||
logger.exception("Lifespan: seed_rules fehlgeschlagen — Brain startet trotzdem (%s)", exc)
|
||||
|
||||
# Einmalige Migration: Spotify-Skill ohne fast_patterns kriegt die Standard-
|
||||
# Patterns injiziert. Idempotent — wenn schon welche da sind, nichts tun.
|
||||
# ARIA kann sie spaeter via skill_update beliebig erweitern/ersetzen.
|
||||
try:
|
||||
_seed_spotify_fast_patterns()
|
||||
except Exception as exc:
|
||||
logger.warning("Lifespan: spotify fast_patterns Migration: %s", exc)
|
||||
|
||||
task = asyncio.create_task(background_mod.run_loop(agent))
|
||||
logger.info("Lifespan: Trigger-Loop gestartet")
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user