diff --git a/agents/__init__.py b/agents/__init__.py index 8064a30..48ae91a 100644 --- a/agents/__init__.py +++ b/agents/__init__.py @@ -64,3 +64,4 @@ def load_all_agents() -> None: import skills.media_info # noqa: F401 import skills.seerr # noqa: F401 import skills.triage # noqa: F401 + import skills.easter_eggs # noqa: F401 diff --git a/agents/media_agent.py b/agents/media_agent.py index 657f61b..a599b63 100644 --- a/agents/media_agent.py +++ b/agents/media_agent.py @@ -14,7 +14,7 @@ media_agent = Agent( agent_id="media-agent", description="Media assistant — handles movie/TV/subtitle/ticket requests " "via Seerr, Jellyfin, Sonarr, etc.", - skills=["media_info", "seerr", "triage"], + skills=["media_info", "seerr", "triage", "easter_eggs"], base_prompt=( "You are a media assistant connected to Seerr and other media services. " "Help users discover, request, and troubleshoot their media library. " diff --git a/main.py b/main.py index ef42ea4..b03c488 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,5 @@ import logging +from contextlib import asynccontextmanager from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware @@ -23,10 +24,22 @@ from agents import load_all_agents # noqa: E402 load_all_agents() +# --------------------------------------------------------------------------- +# Lifespan +# --------------------------------------------------------------------------- +@asynccontextmanager +async def lifespan(app: FastAPI): + from bot.discord_bot import start_in_background # noqa: E402 + + start_in_background() + + yield + + # --------------------------------------------------------------------------- # App # --------------------------------------------------------------------------- -app = FastAPI() +app = FastAPI(lifespan=lifespan) app.add_middleware( CORSMiddleware, @@ -47,14 +60,4 @@ app.state.agent_graphs: dict = {} # --------------------------------------------------------------------------- # Routers # --------------------------------------------------------------------------- -app.include_router(v1_router, prefix="/v1") - -# --------------------------------------------------------------------------- -# Discord bot — launched once on app startup (not at import time, which -# would double-fire under uvicorn --reload). -# --------------------------------------------------------------------------- -@app.on_event("startup") -async def _start_discord_bot() -> None: - from bot.discord_bot import start_in_background # noqa: E402 - - start_in_background() \ No newline at end of file +app.include_router(v1_router, prefix="/v1") \ No newline at end of file diff --git a/skills/easter_eggs.py b/skills/easter_eggs.py new file mode 100644 index 0000000..835ea1f --- /dev/null +++ b/skills/easter_eggs.py @@ -0,0 +1,66 @@ +""" +Easter eggs skill — theme-aware persona adapter. + +When a user's message contains trigger words from a known fandom/universe, +the LLM adopts that theme's persona flavor while still performing all +requested actions normally. Functionality is never sacrificed for a reference. + +Add a new theme by adding one entry to THEMES — no code changes needed. +""" + +from skills import Skill, register + +THEMES = { + "naruto": { + "triggers": [ + "rasengan", "sasuke", "naruto", "kakashi", "sakura", "hokage", + "chidori", "sharingan", "kurama", "dattebayo", "believe it", + "hidden leaf", "konoha", "akatsuki", "itachi", "jiraiya", + "shippuden", "boruto", "sensei", "ninja", "tsunade", "orochimaru", + "tailed beast", "jinchuriki", "rinnegan", "byakugan", "genin", + "chunin", "jonin", "anbu", "uchiha", "hyuga", "uzumaki", + ], + "persona": ( + "Adopt the speaking style of a ninja from the Hidden Leaf Village. " + "If someone screems 'Rasengan!' in their request, respond with 'SOOSSKEEE!' " + "If someone screams 'SOSSSKEE!' in their request, respond with 'RESEENNGGAANN!' " + "Stay fully functional — carry out all requested actions normally, but only if something is requested" + ), + }, +} + +# --------------------------------------------------------------------------- +# Build the prompt fragment from THEMES +# --------------------------------------------------------------------------- +theme_blocks = [] +for name, theme in THEMES.items(): + all_triggers = theme["triggers"] + # Show first 8 triggers + count of remaining + shown = all_triggers[:8] + suffix = f" (+{len(all_triggers) - 8} more)" if len(all_triggers) > 8 else "" + triggers_str = ", ".join(f'"{t}"' for t in shown) + suffix + theme_blocks.append( + f"### {name.upper()}\n" + f"Triggers (case-insensitive, substring match): {triggers_str}\n" + f"Persona: {theme['persona']}" + ) + +easter_eggs_skill = Skill( + name="easter_eggs", + description="Theme-aware persona adapter — flavors responses " + "when users mention known fandoms/universes.", + prompt_fragment=( + "## Themed Personas\n\n" + "Before responding, scan the user's message for these themes " + "(case-insensitive, substring match). If a theme matches, adopt its " + "persona flavor while still performing all requested actions normally. " + "Never skip functionality for the sake of a reference.\n\n" + "If multiple themes match, pick the one with the most trigger hits.\n" + "If no theme matches, respond with your normal base persona.\n\n" + + "\n\n".join(theme_blocks) + ), + tools=[], + execute=None, +) + +register(easter_eggs_skill)