Discord Bot Coding AI: Создайте его с помощью LLM API
Кодящий ИИ в виде Discord-бота отвечает на вопросы по программированию на вашем сервере, используя языковую модель через API. Этот туториал поможет создать такого бота примерно за 70 строк на Python и охватывает части, которые обычно ломаются.
Обновлено

Ключевые моменты
- Боту нужны discord.py, пакет openai, токен бота и ключ LLM API.
- Включите Intent Message Content в портале разработчика Discord, иначе бот будет получать пустые сообщения.
- Храните короткую историю по каналам и обрезайте её, так как модель ничего не помнит между вызовами.
- Разбивайте ответы на 2 000 символов и ограничивайте параллельные вызовы, чтобы не превысить лимит параллельных запросов в 8.
Что будет делать бот
Цель — создать Discord Bot Coding AI, который реагирует, когда его упоминают. Пользователь пишет «@helper почему list comprehension возвращает None?», бот отправляет сообщение модели, и ответ модели возвращается как ответ в том же канале. Бот также запоминает последние несколько реплик каждого канала, поэтому последующие вопросы работают.
Дизайн состоит из трех частей. discord.py обрабатывает подключение к Discord. OpenAI Python SDK общается с LLM API, который здесь работает, так как API совместимо с OpenAI. Небольшой словарь хранит историю разговора. Для начала база данных не нужна.
Вам понадобится Python 3.9 или новее, аккаунт Discord, позволяющий создавать приложения, и API-ключ со страницы API-ключей. Новые аккаунты могут одним кликом получить $0,50 пробного баланса, чего достаточно для создания и тестирования этого бота.
Шаг 1: создание приложения Discord
- Откройте портал разработчика Discord и создайте новое приложение.
- В разделе Bot создайте пользователя бота и скопируйте его токен. Относитесь к нему как к паролю.
- На той же странице включите Message Content Intent в разделе Privileged Gateway Intents. Без него бот получает сообщения с пустым текстом, и вы потратите час, пытаясь понять, почему он никогда не отвечает.
- В OAuth2 сгенерируйте ссылку приглашения с областью
botи правами на чтение и отправку сообщений. Откройте ссылку и добавьте бота на тестовый сервер.
Установите библиотеки и задайте две переменные окружения.
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}'Шаг 2: код бота
Сохраните это как bot.py. Он отвечает при упоминании, хранит двенадцать сообщений истории на канал, показывает индикатор набора, пока ждет, и разбивает длинные ответы.
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"])
Запустите его с помощью python bot.py, затем упомяните бота на вашем тестовом сервере. Первый ответ должен прийти через несколько секунд.
Почему код написан именно так
История обрезана намеренно
У модели нет памяти, поэтому каждый вызов отправляет недавние реплики. Без del log[:-MAX_TURNS] список рос бы бесконечно. В итоге он достиг бы окна в 100 000 токенов, и ваши расходы росли бы с каждым сообщением. Двенадцать записей достаточно для разговора об одной ошибке. Для сервера с длинными ветками увеличьте это значение или обобщите старые реплики.
Лимит Discord в 2 000 символов
Discord отклоняет сообщения длиннее 2 000 символов, а ответы с кодом достигают этого быстро. Вспомогательная функция split_reply разбивает текст по новой строке рядом с лимитом. Она не знает о блоках кода, поэтому блок может быть разрезан посередине. Если это вас беспокоит, добавьте логику, закрывающую блок в конце одной части и открывающую его в начале следующей.
Семафор для параллельных запросов
Полный ключ позволяет 8 параллельных запросов, а пробный — 2. На загруженном сервере может возникнуть несколько вопросов одновременно. Семафор ставит в очередь дополнительные вызовы внутри вашего процесса, вместо того чтобы отправлять их и получать 429 с кодом ошибки concurrency. Если вы все еще на пробном ключе, измените Semaphore(6) на Semaphore(2).
Ошибки не оплачиваются
Неудачные или отклоненные запросы не тарифицируются, поэтому повторная попытка после ошибки не удваивает стоимость. Код удаляет реплику пользователя из истории при ошибке, поэтому повторная попытка не оставляет висящий вопрос, который модель уже видела дважды.
Улучшение качества ответов
Как только базовый цикл заработает, несколько небольших изменений значительно его улучшат.
- Потоковая передача ответа. Передайте
stream=Trueи редактируйте сообщение бота примерно раз в секунду уже полученным текстом. Не редактируйте после каждого токена, так как Discord применяет свои собственные лимиты на редактирование сообщений. Потоковый запрос удерживает слот параллельных запросов до конца, поэтому держите семафор вокруг всего потока. - Используйте слэш-команды. Команда вроде
/askизбегает парсинга упоминаний и вообще не требует Message Content intent, что также упрощает проверку бота в будущем, если он вырастет за пределы небольшого числа серверов. - История на пользователя. Привязывайте историю к каналу и пользователю, если несколько человек задают нерелевантные вопросы в одном канале.
- Настройте системный промпт. Укажите языки, которые использует ваше сообщество, просите короткие ответы и скажите модели задавать уточняющий вопрос, если проблема неясна. Изменения промпта бесплатны и часто лучше изменений модели.
- Снизьте температуру. Для кода значения между 0,2 и 0,4 дают более стабильные результаты, чем по умолчанию.
Устранение типичных сбоев
Когда новый бот молчит, причина почти всегда одна из перечисленных ниже. Проверяйте их в этом порядке.
- Бот онлайн, но никогда не отвечает. Message Content intent отключен на портале разработчика или в коде отсутствует
intents.message_content = True. Должны быть установлены оба значения. - Он отвечает только в некоторых каналах. У бота нет прав на чтение или отправку сообщений в остальных. Проверьте переопределения прав канала, а не только роль на весь сервер.
- Каждый ответ — сообщение «занято». Вероятно, вы достигаете 429. Выведите тело исключения один раз. Если код говорит
rate_limited, вы превысили лимит в 300 запросов в минуту на полном ключе или 60 на пробном. Если говоритconcurrency, уменьшите размер семафора. - Ошибка аутентификации при первом вызове. Ключ LLM отсутствует в окружении процесса, запускающего бота. Ключ, экспортированный в одной вкладке терминала, не виден сервису, запущенному в другом месте.
- Ответ обрезан.
max_tokens=900в примере останавливает длинные ответы. Увеличьте его или удалите, так какmax_tokensнеобязателен, и завершение может использовать оставшуюся часть окна. - Бот отвечает на свои собственные сообщения. Этот цикл возникает, если убрать проверку
message.author.bot. Оставьте её.
Для отладки логируйте значение X-Request-Id для каждого вызова. С OpenAI SDK вы можете прочитать его из сырого ответа, вызвав llm.chat.completions.with_raw_response.create(...), а затем прочитав .headers. Если вы обратитесь в поддержку по поводу конкретной ошибки, этот ID позволит им найти точный запрос.
Сколько это стоит
Тариф составляет $0,15 за 1 млн входных токенов и $0,50 за 1 млн выходных токенов, без подписки. Возьмём типичный обмен: системный промпт плюс двенадцать записей истории составляют около 2 500 входных токенов, а ответ — около 400 токенов.
Ввод: 2 500 x $0,15 / 1 000 000 = $0,000375. Вывод: 400 x $0,50 / 1 000 000 = $0,0002. Один ответ стоит около $0,000575. Сообщество, задающее 500 вопросов в день, потратит 500 x $0,000575 = $0,2875, чуть меньше тридцати центов. Ваши цифры будут отличаться при более длинных вставках кода, поэтому записывайте в лог resp.usage в течение недели и читайте реальные средние значения. На странице цен показаны варианты пополнения: любая целая сумма от $20 до $500, с +5% от $50, +10% от $100 и +15% от $200, оплата картой или криптовалютой.
Чтобы ограничить злоупотребления, добавьте задержку между запросами для каждого пользователя и ограничение на длину вопроса. Эти две проверки лучше всего защищают ваш баланс.
Примечания по хостингу и безопасности
Бот — это долгоживущий процесс. Достаточно небольшой виртуальной машины или контейнерного хоста, поскольку основная нагрузка ложится на API. Запускайте его под менеджером процессов, чтобы он перезапускался после сбоя, и храните оба токена в переменных окружения или хранилище секретов, никогда не в репозитории.
Подумайте о том, что разрешает ваш сервер. Модель настроена на ответы без отказов по контенту для законного использования взрослыми, поэтому она будет обсуждать тестирование безопасности или острую художественную литературу, которую более строгий ассистент может отклонить. Если в вашем сообществе есть младшие участники, установите правила в системном промпте, ограничьте бота определенными каналами и помните, что условия использования самого Discord также применяются к вашему боту. Контент, сексуализирующий несовершеннолетних, запрещен в этом API без исключений.
Полные сведения о параметрах, таких как stop, seed и вызов функций, доступны в документации API. Если вы хотите, чтобы бот выполнял код или читал репозитории, используйте вызов функций, и вы решаете, какие функции может запрашивать модель. Для использования этой модели в программировании см. страницу о модели ИИ без цензуры для кода.
Дополнительные руководства
Вопросы и ответы
Нужен ли мне Message Content intent?
Да, если бот читает обычные сообщения и упоминания. Если вы используете только слэш-команды, это не требуется.
Может ли бот передавать ответы потоком?
Да. Установите stream=True и обновляйте сообщение в Discord с умеренной частотой, например, раз в секунду, чтобы оставаться в рамках ограничений самого Discord.
Что произойдет, если слишком много людей зададут вопросы одновременно?
После 8 одновременных запросов по полному ключу API возвращает HTTP 429 с кодом concurrency. Семафор в вашем коде будет ставить в очередь дополнительные вопросы.
Будет ли бот помнить разговоры после перезапуска?
Нет, с используемым здесь словарем. Храните историю в SQLite или Redis, если вам нужно, чтобы она сохранялась после перезапусков.
Безопасен ли API-ключ в боте Discord?
Да, если он остается на вашем сервере в переменной окружения. Никогда не публикуйте его в канале и не фиксируйте в публичном репозитории.
Ваш ключ — всего одна форма
Создайте аккаунт, скопируйте ключ, измените базовый URL. Это вся настройка.
Получить API-ключ