mirror of
https://github.com/migatu/conjurer.git
synced 2026-07-14 13:34:40 +00:00
ai: single-switch GPT/Claude backend for the chat cog
Wire the bot's AI chat pipeline (ai_functions.handle_response) to talk to either OpenAI or the Anthropic Messages API, chosen by one active-config switch. Behaviour on the default "gpt" config is unchanged. constants.py: * guarded `import anthropic` + CLAUDECLIENT (mirrors OPENAICLIENT), netrc machine 'anthropic' / ANTHROPIC_API_KEY; * CLAUDE_LATEST_MODEL / CLAUDE_CHEAP_MODEL (opus-4-8 / haiku-4-5); * AI_CONFIGS + DEFAULT_AI_CONFIG loaded from an optional 3rd element of system_gpt_settings.json (backward compatible - a 2-element file falls back to built-in defaults, active "gpt"). Single switch: CONJURER_AI_CONFIG env > settings "active" > "gpt". ai_functions.py: * provider_generate() dispatches to OpenAI (unchanged openai_call) or the new _anthropic_call() (splits system out, alternating messages, max_tokens, temperature omitted - Opus 4.8 rejects sampling params); * AIError normalises both SDKs' exceptions into one category set so handle_response keeps its single set of in-character error replies; * select_model() reads the active config; legacy "gpt-4o" default auto-maps to the active provider's model so the switch actually changes the backend; * set_active_ai_config()/list_ai_configs() with best-effort persistence back into system_gpt_settings.json index 2. ai_commands.py: * $gadaj_teraz <config> hybrid command (Vykidailo-gated) switches backend at runtime; * graceful guards when OPENAICLIENT is None: personal assistants (OpenAI Assistants API) and DALL-E image gen degrade instead of crashing, so a Claude-only deployment boots. system_gpt_settings.json: add the configs block (gpt/claude/_template) as the collection point for future backends. requirements_bot.txt: add anthropic. bot.env.example: ANTHROPIC_API_KEY + CONJURER_AI_CONFIG. Unit tests cover the message splitter, model selection, config listing, and error mapping. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -45,6 +45,14 @@ class Events(commands.Cog):
|
||||
|
||||
async def cog_load(self):
|
||||
self.logger.info("Starting personal assistants")
|
||||
# Personal assistants use the OpenAI Assistants API (threads/runs), which
|
||||
# has no Anthropic equivalent - skip cleanly when OpenAI isn't wired up
|
||||
# (e.g. a Claude-only deployment) instead of crashing the cog load.
|
||||
if OPENAICLIENT is None:
|
||||
self.logger.warning(
|
||||
"OPENAICLIENT niedostępny - osobiści asystenci (OpenAI Assistants API) wyłączeni"
|
||||
)
|
||||
return
|
||||
for superfryta_id, superfryta in SPECJALNE_ZIEMNIACZKI.items():
|
||||
|
||||
if superfryta[4] != "":
|
||||
@@ -101,6 +109,40 @@ class Events(commands.Cog):
|
||||
else:
|
||||
await ctx.reply("Nope. Nie wiesz jak użyć")
|
||||
|
||||
@commands.hybrid_command(
|
||||
name="gadaj_teraz",
|
||||
description="Przełącz backend AI (np. gpt / claude). Tylko Vykidailo.",
|
||||
)
|
||||
async def gadaj_teraz(self, ctx, nazwa_konfigu: str):
|
||||
async with ctx.channel.typing():
|
||||
is_admin = isinstance(ctx.author, discord.Member) and any(
|
||||
role.name == "Vykidailo" for role in ctx.author.roles
|
||||
)
|
||||
if not is_admin:
|
||||
await discord_friendly_reply(ctx, "Tylko Vykidailo może przełączać AI.")
|
||||
return
|
||||
available = ai_functions.list_ai_configs()
|
||||
if nazwa_konfigu not in available:
|
||||
await discord_friendly_reply(
|
||||
ctx,
|
||||
f"Nie znam configu '{nazwa_konfigu}'. Dostępne: {', '.join(available)}",
|
||||
)
|
||||
return
|
||||
try:
|
||||
cfg = ai_functions.set_active_ai_config(nazwa_konfigu)
|
||||
except (KeyError, RuntimeError) as exc:
|
||||
await discord_friendly_reply(
|
||||
ctx, f"Nie mogę przełączyć na '{nazwa_konfigu}': {exc}"
|
||||
)
|
||||
return
|
||||
self.logger.info(
|
||||
"Przełączono AI na config %s (%s)", nazwa_konfigu, cfg.get("provider")
|
||||
)
|
||||
await discord_friendly_reply(
|
||||
ctx,
|
||||
f"Teraz gadam przez **{nazwa_konfigu}** — {cfg.get('provider')} / {cfg.get('latest_model')}.",
|
||||
)
|
||||
|
||||
@commands.hybrid_command(
|
||||
name="armia_hammera",
|
||||
description="Jeśli nie wiesz jak użyć tej komendy to nawet nie próbuj",
|
||||
@@ -301,6 +343,15 @@ class Events(commands.Cog):
|
||||
if "imaginuje sobie:" in message.content:
|
||||
async with channel.typing():
|
||||
self.logger.info("Poczatek procedury obrazkowej")
|
||||
# Image generation is DALL-E (OpenAI); there is no Anthropic
|
||||
# equivalent, so it stays on OpenAI regardless of the chat
|
||||
# backend. Degrade gracefully when OpenAI isn't configured.
|
||||
if OPENAICLIENT is None:
|
||||
await discord_friendly_reply(
|
||||
message,
|
||||
"*Kondziu rozkłada łapska* — malowanie obrazków jest teraz wyłączone (brak OpenAI).",
|
||||
)
|
||||
return
|
||||
message_content_lower = message_content_lower.replace("imaginuje sobie: ", "")
|
||||
self.logger.debug("Wywolanie obrazka: %s", message_content_lower)
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user