PL ▾
Pobierz klucz API

Bot Discord AI do kodowania: Zbuduj go z API LLM

Bot Discord AI do kodowania odpowiada na pytania programistyczne na Twoim serwerze, używając modelu językowego za pośrednictwem API. Ten samouczek buduje go w około 70 liniach Pythona i omawia elementy, które zwykle powodują błędy.

Zaktualizowano

Abstrakcyjna postać bota otoczona świecącymi dymkami rozmowy, ilustrująca bota Discorda napędzanego przez API LLM

Kluczowe punkty

  • Bot wymaga discord.py, pakietu openai, tokena bota i klucza API LLM.
  • Włącz uprawnienie Message Content w portalu deweloperskim Discorda, w przeciwnym razie bot będzie widział puste wiadomości.
  • Przechowuj krótką historię na kanał i ją przycinaj, ponieważ model nie pamięta niczego między wywołaniami.
  • Dziel odpowiedzi na fragmenty po 2000 znaków i ogranicz równoległe wywołania, aby mieścić się w limicie 8 równoległych zapytań.

Co będzie robił bot

Celem jest bot Discord AI do kodowania, który reaguje na wzmianki. Użytkownik pisze „@helper dlaczego moja list comprehension zwraca None?”, bot wysyła wiadomość do modelu, a odpowiedź modelu wraca jako odpowiedź na tym samym kanale. Bot pamięta też kilka ostatnich zwrotów każdego kanału, dzięki czemu pytania dodatkowe działają.

Projekt składa się z trzech elementów. discord.py obsługuje połączenie z Discordem. Python SDK OpenAI komunikuje się z API LLM, które działa tutaj, ponieważ API jest kompatybilne z OpenAI. Mały słownik przechowuje historię rozmowy. Nie potrzebujesz bazy danych na start.

Potrzebujesz Pythona 3.9 lub nowszego, konta Discord pozwalającego na tworzenie aplikacji oraz klucza API ze strony klucza API. Nowe konta mogą jednorazowo zaksięgować $0,50 kredytu próbnego jednym kliknięciem, co wystarczy do zbudowania i przetestowania tego bota.

Krok 1: utwórz aplikację Discord

  1. Otwórz portal deweloperski Discorda i utwórz nową aplikację.
  2. W sekcji Bot utwórz użytkownika bota i skopiuj jego token. Traktuj go jak hasło.
  3. Na tej samej stronie włącz Message Content Intent w sekcji Privileged Gateway Intents. Bez tego bot odbierze wiadomości z pustym tekstem, a Ty spędzisz godzinę zastanawiając się, dlaczego nic nie odpowiada.
  4. W OAuth2 wygeneruj link zaproszenia z zakresem bot i uprawnieniami do odczytu i wysyłania wiadomości. Otwórz link i dodaj bota do testowego serwera.

Zainstaluj biblioteki i ustaw dwie zmienne środowiskowe.

pip install discord.py openai

export DISCORD_TOKEN="your-discord-bot-token"
export LLM_API_KEY="your-llm-api-key"

# quick check that the LLM key works, using model uncensored
curl -s https://api.codingllmapi.com/v1/chat/completions \
  -H "Authorization: Bearer $LLM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"uncensored","messages":[{"role":"user","content":"ping"}],"max_tokens":5}'

Krok 2: kod bota

Zapisz to jako bot.py. Odpowiada na wzmianki, przechowuje dwanaście wiadomości historii na kanał, pokazuje wskaźnik pisania podczas oczekiwania i dzieli długie odpowiedzi.

import asyncio
import os

import discord
from openai import AsyncOpenAI, RateLimitError, APIError

llm = AsyncOpenAI(
    base_url="https://api.codingllmapi.com/v1",
    api_key=os.environ["LLM_API_KEY"],
)

SYSTEM = (
    "You are a helpful programming assistant in a Discord server. "
    "Answer briefly. Put code in fenced blocks. Say so when you are unsure."
)
MAX_TURNS = 12
history = {}                     # channel id -> list of messages
slots = asyncio.Semaphore(6)     # stay under the 8 concurrent request cap

intents = discord.Intents.default()
intents.message_content = True
bot = discord.Client(intents=intents)


def split_reply(text, limit=1900):
    parts = []
    while len(text) > limit:
        cut = text.rfind("\n", 0, limit)
        if cut < limit // 2:
            cut = limit
        parts.append(text[:cut])
        text = text[cut:].lstrip("\n")
    parts.append(text)
    return parts


@bot.event
async def on_message(message):
    if message.author.bot or bot.user not in message.mentions:
        return

    question = message.clean_content.replace(f"@{bot.user.display_name}", "").strip()
    if not question:
        await message.reply("Ask me a coding question after the mention.")
        return

    log = history.setdefault(message.channel.id, [])
    log.append({"role": "user", "content": question})
    del log[:-MAX_TURNS]

    async with message.channel.typing():
        try:
            async with slots:
                resp = await llm.chat.completions.create(
                    model="uncensored",
                    messages=[{"role": "system", "content": SYSTEM}] + log,
                    temperature=0.4,
                    max_tokens=900,
                )
        except RateLimitError:
            await message.reply("The model is busy. Try again in a few seconds.")
            log.pop()
            return
        except APIError:
            await message.reply("The model call failed. Try again shortly.")
            log.pop()
            return

    answer = resp.choices[0].message.content or "(empty reply)"
    log.append({"role": "assistant", "content": answer})
    for part in split_reply(answer):
        await message.reply(part)


bot.run(os.environ["DISCORD_TOKEN"])

Uruchom go poleceniem python bot.py, a następnie wspomnij bota na swoim serwerze testowym. Pierwsza odpowiedź powinna nadejść w ciągu kilku sekund.

Dlaczego kod jest napisany w ten sposób

Historia jest celowo przycinana

Model nie ma pamięci, więc każde wywołanie wysyła ostatnie zwroty. Bez del log[:-MAX_TURNS] lista rosłaby w nieskończoność. W końcu osiągnęłaby okno 100 000 tokenów, a koszty rosłyby z każdą wiadomością. Dwanaście wpisów wystarczy na rozmowę o jednym błędzie. Na serwerze z długimi wątkami zwiększ tę liczbę lub podsumuj starsze zwroty.

Limit 2000 znaków Discorda

Discord odrzuca wiadomości dłuższe niż 2000 znaków, a odpowiedzi z kodem trafiają w ten limit szybko. Pomocnik split_reply dzieli tekst przy nowej linii w pobliżu limitu. Nie zna znaczników kodu, więc blok może zostać ucięty w środku. Jeśli to Cię irytuje, dodaj logikę zamykającą znacznik na końcu jednej części i otwierającą go na początku kolejnej.

Semafor dla równoległości

Pełny klucz pozwala na 8 równoległych zapytań, a klucz próbny na 2. Na obciążonym serwerze może pojawić się kilka pytań naraz. Semafor kolejkuje dodatkowe wywołania wewnątrz Twojego procesu zamiast wysyłać je i otrzymywać błąd 429 z kodem błędu concurrency. Jeśli nadal korzystasz z klucza próbnego, zmień Semaphore(6) na Semaphore(2).

Błędy nie są rozliczane

Nieudane lub odrzucone zapytania nie są rozliczane, więc ponowna próba po błędzie nie podwaja kosztu. Kod usuwa zwrot użytkownika z historii przy błędzie, więc ponowna próba nie pozostawia wiszącego pytania, które model widział już dwukrotnie.

Poprawa jakości odpowiedzi

Gdy podstawowa pętla działa, kilka małych zmian znacznie ją poprawia.

  • Strumieniuj odpowiedź. Przekaż stream=True i edytuj wiadomość bota mniej więcej co sekundę, wstawiając dotychczasowy tekst. Nie edytuj przy każdym tokenie, ponieważ Discord stosuje własne limity zapytań do edycji wiadomości. Żądanie strumieniowe zajmuje slot równoległości do końca, więc trzymaj semafor wokół całego strumienia.
  • Używaj poleceń slash. Polecenie takie jak /ask unika parsowania wzmianek i w ogóle nie wymaga uprawnienia Message Content, co również ułatwia weryfikację bota później, jeśli wyrośnie poza małą liczbę serwerów.
  • Historia na użytkownika. Grupuj historię według kanału i użytkownika, jeśli kilka osób zadaje niepowiązane pytania na jednym kanale.
  • Dostosuj prompt systemowy. Wymień języki używane przez Twoją społeczność, poproś o krótkie odpowiedzi i powiedz modelowi, aby zadał pytanie doprecyzowujące, gdy problem jest niejasny. Zmiany promptu są darmowe i często lepsze niż zmiany modelu.
  • Obniż temperaturę. Dla kodu wartości między 0,2 a 0,4 dają stabilniejsze wyniki niż domyślne.

Rozwiązywanie typowych problemów

Gdy nowy bot milczy, przyczyną jest prawie zawsze jedna z poniższych pozycji. Sprawdź je w tej kolejności.

  1. Bot jest online, ale nigdy nie odpowiada. Uprawnienie Message Content jest wyłączone w portalu deweloperskim lub w kodzie brakuje intents.message_content = True. Obie muszą być ustawione.
  2. Odpowiada tylko na niektórych kanałach. Botowi brakuje uprawnień do odczytu lub wysyłania wiadomości na pozostałych. Sprawdź nadpisywania uprawnień kanału, a nie tylko rolę na poziomie serwera.
  3. Każda odpowiedź to komunikat „busy”. Prawdopodobnie trafiasz w limit 429. Wypisz raz treść wyjątku. Jeśli kod to rate_limited, przekroczono limit 300 zapytań na minutę dla pełnego klucza lub 60 dla klucza próbnego. Jeśli to concurrency, zmniejsz rozmiar semafora.
  4. Błąd uwierzytelnienia pojawia się przy pierwszym wywołaniu. Brakuje klucza LLM w środowisku procesu uruchamiającego bota. Klucz wyeksportowany w jednej karcie terminala nie jest widoczny dla usługi uruchomionej gdzie indziej.
  5. Odpowiedź jest ucięta. max_tokens=900 w przykładzie zatrzymuje długie odpowiedzi. Zwiększ go lub usuń, ponieważ max_tokens jest opcjonalne, a uzupełnienie może użyć reszty okna.
  6. Bot odpowiada na własne wiadomości. Ta pętla występuje, jeśli usuniesz sprawdzenie message.author.bot. Zostaw je.

Do debugowania loguj wartość X-Request-Id dla każdego wywołania. W przypadku użycia OpenAI SDK możesz ją odczytać z surowej odpowiedzi, wywołując llm.chat.completions.with_raw_response.create(...), a następnie odczytując .headers. Jeśli skontaktujesz się z działem wsparcia w sprawie konkretnej awarii, ten identyfikator pozwoli im znaleźć dokładne zapytanie.

Ile to kosztuje

Ceny to $0,15 za 1 mln tokenów wejściowych i $0,50 za 1 mln tokenów wyjściowych, bez subskrypcji. Weźmy typowy przykład: prompt systemowy plus dwanaście wpisów z historii to około 2500 tokenów wejściowych, a odpowiedź to około 400 tokenów.

Wejście: 2500 x $0,15 / 1 000 000 = $0,000375. Wyjście: 400 x $0,50 / 1 000 000 = $0,0002. Jedna odpowiedź kosztuje około $0,000575. Społeczność, która zadaje 500 pytań dziennie, wyda 500 x $0,000575 = $0,2875, czyli nieco mniej niż 30 centów. Twoje liczby będą inne przy dłuższych wklejaniach kodu, więc loguj resp.usage przez tydzień i odczytaj rzeczywiste średnie. Strona ceny pokazuje opcje doładowania: dowolna kwota całkowita od $20 do $500, z +5% od $50, +10% od $100 i +15% od $200, płatna kartą lub kryptowalutami.

Aby ograniczyć nadużycia, dodaj cooldown na użytkownika i limit długości pytania. Te dwa sprawdzenia lepiej chronią Twój stan konta niż jakiekolwiek inne dodatki.

Uwagi dotyczące hostingu i bezpieczeństwa

Bot to proces długotrwały. Wystarczy mała maszyna wirtualna lub host kontenerów, ponieważ ciężka praca odbywa się po stronie API. Uruchom go pod menedżerem procesów, aby restartował się po awarii, i przechowuj oba tokeny w zmiennych środowiskowych lub w magazynie sekretów, nigdy w repozytorium.

Zastanów się, na na na co pozwala Twój serwer. Model jest dostrojony do odpowiadania bez odmów treści dla zgodnego z prawem użytku przez dorosłych, więc będzie omawiał testy bezpieczeństwa lub odważną fikcję, które ściślejszy asystent mógłby odrzucić. Jeśli Twoja społeczność obejmuje młodszych członków, ustaw zasady w promptie systemowym, ogranicz bota do określonych kanałów i pamiętaj, że regulamin Discorda dotyczy również Twojego bota. Treści seksualizujące nieletnichchch są zabronione w tym API bez wyjątku.

Pełne szczegóły parametrów, takie jak stop, seed i wywoływanie funkcji, znajdziesz w dokumentacji API. Jeśli chcesz, aby bot uruchamiał kod lub czytał repozytoria, wywoływanie funkcji jest właściwą drogą, a Ty decydujesz, jakie funkcje model może żądać. Dla użycia tego modelu skupionego na programowaniu, zobacz stronę o modelu AI bez cenzury do kodowania.

Pytania i odpowiedzi

Czy potrzebuję uprawnienia Message Content?

Tak, jeśli bot czyta zwykłe wiadomości i wzmianki. Jeśli używasz tylko poleceń slash, nie jest to potrzebne.

Czy bot może strumieniować odpowiedzi?

Tak. Ustaw stream=True i edytuj wiadomość Discord w spokojnym interwale, np. raz na sekundę, aby pozostać w granicach własnych limitów Discorda.

Co się stanie, jeśli zbyt wiele osób zapyta jednocześnie?

Powyżej 8 równoległych zapytań dla pełnego klucza API zwraca HTTP 429 z kodem concurrency. Semafor w Twoim kodzie kolejkuje dodatkowe pytania.

Czy bot zapamięta rozmowy po restarcie?

Nie przy użyciu słownika używanego tutaj. Przechowuj historię w SQLite lub Redis, jeśli potrzebujesz, aby przetrwała restarty.

Czy klucz API jest bezpieczny w bocie Discord?

Tak, jeśli pozostaje na Twoim serwerze w zmiennej środowiskowej. Nigdy nie publikuj go na kanale ani nie dodawaj do publicznego repozytorium.

Twój klucz jest o jeden formularz stąd

Utwórz konto, skopiuj klucz, zmień bazowy URL. To cała konfiguracja.

Pobierz klucz API