Ollama AI Discord Bot
Created on: January 10, 2025
Created on: January 10, 2025
Привет!
мне нужен бот на Python(Disnake)
этот бот будет использоваться для общения с локальными Ollama ИИ через Discord сервер
Вот логика работы
Структура каналов Сервера
main
chat
В канале settings происходит настройка
В канале models перечислен список доступных(установленных) моделей
В канале home происходит создание каналов и другие действия
в группе chat находятся все созданные каналы
Логика работы бота
Можно настроить основную модель для всех чатов или в каждом чате настроить локально основную модель с помощью команды в нужном чате или / команды в канале настроек
Так же бот должен отвечать вебхуком в чатах(за исключением когда выполняются какие то настройки в чате)
пользователь не должен писать /send и подобное для отправке сообщения
Каждый веб кух бота имеет следующие вещи
описание профиля(если такое возможно реализовать)
изображение профиля
имя профиля
эти данные можно получить из канала models или файла models.json
где эти данные будут храниться
вебхук зависит от того, какую модель выбрал пользователь
пример сообщения с моделями в канале models
{
"content": "## Доступные модели",
"embeds": [
{
"title": "Описание",
"description": "Оптимизированная архитектура для генеративных задач. Обеспечивает высокую производительность и эффективность при меньших вычислительных ресурсах. Часто используется для диалогов, генерации текста и программного кода.",
"color": 16445723,
"fields": [
{
"name": "Тэги",
"value": "Генерация
, Оптимизация
, MistralAI
",
"inline": true
},
{
"name": "Версии",
"value": "8b
",
"inline": true
}
],
"author": {
"name": "Mistral",
"url": "https://ollama.com/library/mistral",
"icon_url": "https://custom.typingmind.com/assets/models/mistralai.png"
},
"footer": {
"text": "Ollama ID: f974a74358d6"
}
},
{
"title": "Описание",
"description": "Мощная языковая модель второго поколения с акцентом на когнитивное понимание текста. Применяется для сложных логических задач, обработки естественного языка и построения интеллектуальных систем.",
"color": 16445723,
"fields": [
{
"name": "Тэги",
"value": "Когнитивный
, Логика
, GemmaAI
",
"inline": true
},
{
"name": "Версии",
"value": "9b
",
"inline": true
}
],
"author": {
"name": "Gemma 2",
"url": "https://ollama.com/library/gemma2",
"icon_url": "https://custom.typingmind.com/assets/models/gemma.jpg"
},
"footer": {
"text": "Ollama ID: ff02c3702f32"
}
},
{
"title": "Описание",
"description": "Предыдущая версия LLaMA с высокой точностью в текстовых задачах. Хорошо подходит для обработки естественного языка и генерации текста. Обладает улучшенной производительностью по сравнению с ранними версиями.",
"color": 16445723,
"fields": [
{
"name": "Тэги",
"value": "НЛП
, Генерация
, Meta
",
"inline": true
},
{
"name": "Версии",
"value": "8b
",
"inline": true
}
],
"author": {
"name": "LLaMA 3.1",
"url": "https://ollama.com/library/llama3.1",
"icon_url": "https://custom.typingmind.com/assets/models/llama.png"
},
"footer": {
"text": "Ollama ID: 46e0c10c039e"
}
},
{
"title": "Описание",
"description": "Модель с поддержкой работы с изображениями и текстом. Обеспечивает мультизадачность, объединяя обработку визуальных данных и текста, используется в задачах, связанных с генерацией описаний, анализом изображений и мультимодальными приложениями.",
"color": 16445723,
"fields": [
{
"name": "Тэги",
"value": "Мультимодальность
, Зрение
, Meta
",
"inline": true
},
{
"name": "Версии",
"value": "11b
",
"inline": true
}
],
"author": {
"name": "LLaMA 3.2 Vision",
"url": "https://ollama.com/library/llama3.2-vision",
"icon_url": "https://custom.typingmind.com/assets/models/llama.png"
},
"footer": {
"text": "Ollama ID: 085a1fdae525"
}
}
],
"username": "System",
"avatar_url": "https://static10.tgstat.ru/channels/_0/f8/f8ace5cf4ae021370a88c7bf21a168da.jpg",
"attachments": []
}
Привет!
Я помогу тебе создать бота на Python с использованием библиотеки Disnake, который будет взаимодействовать с локальными моделями Ollama AI через Discord сервер. Разберём шаг за шагом, как реализовать требуемую функциональность.
Сначала убедись, что у тебя установлены все необходимые библиотеки. Для этого выполни следующие команды:
bashpip install disnake pip install aiohttp
Также убедись, что у тебя установлен и настроен Ollama на локальном сервере.
Рекомендуется организовать проект следующим образом:
textollama_bot/ │ ├── bot.py ├── config.json ├── models.json └── requirements.txt
bot.py
— основной файл бота.config.json
— файл с конфигурацией бота (токен, ID каналов и т.д.).models.json
— файл с информацией о моделях.requirements.txt
— список зависимостей проекта.Создай файл config.json
и добавь в него следующую структуру:
json{ "token": "YOUR_DISCORD_BOT_TOKEN", "guild_id": YOUR_GUILD_ID, "channels": { "main": { "home": YOUR_HOME_CHANNEL_ID, "models": YOUR_MODELS_CHANNEL_ID, "settings": YOUR_SETTINGS_CHANNEL_ID }, "chat": YOUR_CHAT_CATEGORY_ID } }
Замените YOUR_DISCORD_BOT_TOKEN
, YOUR_GUILD_ID
, YOUR_HOME_CHANNEL_ID
, YOUR_MODELS_CHANNEL_ID
, YOUR_SETTINGS_CHANNEL_ID
и YOUR_CHAT_CATEGORY_ID
на соответствующие значения из вашего Discord сервера.
Сохрани предоставленный тобой пример в файл models.json
. Этот файл будет содержать информацию о доступных моделях.
pythonimport disnake from disnake.ext import commands import json import aiohttp import asyncio # Загрузка конфигурации with open('config.json', 'r', encoding='utf-8') as f: config = json.load(f) with open('models.json', 'r', encoding='utf-8') as f: models_data = json.load(f) intents = disnake.Intents.default() intents.messages = True intents.guilds = True intents.message_content = True bot = commands.Bot(command_prefix='!', intents=intents)
Для хранения глобальных и локальных настроек модели можно использовать простой JSON файл или базу данных. Для примера используем JSON.
pythonSETTINGS_FILE = 'settings.json' # Инициализация файла настроек import os if not os.path.exists(SETTINGS_FILE): with open(SETTINGS_FILE, 'w', encoding='utf-8') as f: json.dump({"global_model": None, "channel_models": {}}, f, ensure_ascii=False, indent=4) def load_settings(): with open(SETTINGS_FILE, 'r', encoding='utf-8') as f: return json.load(f) def save_settings(settings): with open(SETTINGS_FILE, 'w', encoding='utf-8') as f: json.dump(settings, f, ensure_ascii=False, indent=4)
Добавим команды для установки глобальной модели и модели для конкретного канала.
python@bot.slash_command(description="Установить глобальную модель для всех чатов") async def set_global_model(inter: disnake.ApplicationCommandInteraction, model_name: str): settings = load_settings() if model_name not in [model['author']['name'] for model in models_data['embeds']]: await inter.response.send_message("Модель не найдена.", ephemeral=True) return settings['global_model'] = model_name save_settings(settings) await inter.response.send_message(f"Глобальная модель установлена на `{model_name}`.", ephemeral=True) @bot.slash_command(description="Установить модель для текущего канала") async def set_channel_model(inter: disnake.ApplicationCommandInteraction, model_name: str): settings = load_settings() if model_name not in [model['author']['name'] for model in models_data['embeds']]: await inter.response.send_message("Модель не найдена.", ephemeral=True) return settings['channel_models'][str(inter.channel.id)] = model_name save_settings(settings) await inter.response.send_message(f"Модель для канала установлена на `{model_name}`.", ephemeral=True)
Бот должен перехватывать сообщения в каналах категории chat
, отправлять их модели Ollama и отвечать через вебхук.
python@bot.event async def on_message(message): if message.author.bot: return guild = message.guild chat_category_id = config['channels']['chat'] if message.channel.category_id != chat_category_id: return # Не в категории chat settings = load_settings() # Определение модели model_name = settings['channel_models'].get(str(message.channel.id), settings.get('global_model')) if not model_name: await message.channel.send("Не установлена модель для этого канала. Используйте команду настройки.") return # Получение информации о модели model_info = next((model for model in models_data['embeds'] if model['author']['name'] == model_name), None) if not model_info: await message.channel.send("Выбранная модель не найдена.") return # Отправка сообщения в Ollama и получение ответа user_input = message.content ollama_response = await get_ollama_response(model_info['footer']['text'], user_input) # Передаём Ollama ID if not ollama_response: await message.channel.send("Ошибка при получении ответа от модели.") return # Создание или получение вебхука для канала webhook = await get_or_create_webhook(message.channel, model_info) # Отправка ответа через вебхук await webhook.send( content=ollama_response, username=model_info['author']['name'], avatar_url=model_info['author']['icon_url'] ) async def get_or_create_webhook(channel, model_info): webhooks = await channel.webhooks() webhook = None for wh in webhooks: if wh.name == model_info['author']['name']: webhook = wh break if not webhook: webhook = await channel.create_webhook(name=model_info['author']['name'], avatar=bytes(await aiohttp.ClientSession().get(model_info['author']['icon_url']).read())) return webhook
Предполагается, что Ollama предоставляет API для взаимодействия. Если нет, можно использовать вызов командной строки.
pythonasync def get_ollama_response(ollama_id, user_input): # Пример использования HTTP API Ollama # Необходимо заменить URL и параметры на соответствующие вашему серверу Ollama url = f"http://localhost:11434/v1/models/{ollama_id}/generate" payload = { "prompt": user_input, "max_tokens": 150 } headers = { "Content-Type": "application/json" } async with aiohttp.ClientSession() as session: try: async with session.post(url, json=payload, headers=headers) as resp: if resp.status == 200: data = await resp.json() return data.get('response') # Зависит от структуры ответа Ollama else: print(f"Ошибка Ollama: {resp.status}") return None except Exception as e: print(f"Исключение при обращении к Ollama: {e}") return None
Примечание: Необходимо заменить URL и структуру запроса/ответа на соответствующие вашему серверу Ollama.
В канале home
можно добавить команды для создания новых чат-каналов.
python@bot.slash_command(description="Создать новый чат-канал") @commands.has_permissions(manage_channels=True) async def create_chat(inter: disnake.ApplicationCommandInteraction, channel_name: str): guild = inter.guild chat_category = guild.get_channel(config['channels']['chat']) if not chat_category: await inter.response.send_message("Категория chat не найдена.", ephemeral=True) return existing = disnake.utils.get(guild.channels, name=channel_name, category=chat_category) if existing: await inter.response.send_message("Канал с таким именем уже существует.", ephemeral=True) return new_channel = await guild.create_text_channel(channel_name, category=chat_category) await inter.response.send_message(f"Канал `{new_channel.name}` создан.", ephemeral=True)
/models
для отображения моделейВ канале models
бот будет публиковать сообщение с доступными моделями. Можно настроить периодическое обновление или команду для обновления.
python@bot.slash_command(description="Отобразить список доступных моделей") @commands.has_permissions(manage_channels=True) async def list_models(inter: disnake.ApplicationCommandInteraction): models_embed = models_data # Предполагается, что models.json содержит нужную структуру await inter.response.send_message( embed=disnake.Embed.from_dict(models_embed), ephemeral=True )
В конце файла bot.py
добавь:
python@bot.event async def on_ready(): print(f"Бот {bot.user} готов к работе!") bot.run(config['token'])
Обработка ошибок: Добавь обработку возможных ошибок при взаимодействии с API Ollama и Discord.
Логи: Включи логирование для упрощения отладки.
Безопасность: Убедись, что команды настройки доступны только авторизованным пользователям.
Оптимизация: Для больших проектов рекомендуется использовать базу данных (например, SQLite или PostgreSQL) для хранения настроек.
Асинхронность: Убедись, что все взаимодействия с сетью выполняются асинхронно, чтобы не блокировать бот.
Убедись, что models.json
соответствует следующей структуре:
json{ "content": "## Доступные модели", "embeds": [ { "title": "Описание", "description": "Оптимизированная архитектура для генеративных задач...", "color": 16445723, "fields": [ { "name": "Тэги", "value": "`Генерация`, `Оптимизация`, `MistralAI`", "inline": true }, { "name": "Версии", "value": "`8b`", "inline": true } ], "author": { "name": "Mistral", "url": "https://ollama.com/library/mistral", "icon_url": "https://custom.typingmind.com/assets/models/mistralai.png" }, "footer": { "text": "Ollama ID: f974a74358d6" } }, // Другие модели... ], "username": "System", "avatar_url": "https://static10.tgstat.ru/channels/_0/f8/f8ace5cf4ae021370a88c7bf21a168da.jpg", "attachments": [] }
Этот пример предоставляет базовую структуру и функциональность для создания бота, который взаимодействует с локальными моделями Ollama AI через Discord. В зависимости от специфики API Ollama и дополнительных требований, возможно, потребуется внести изменения и улучшения. Удачи в разработке!
при попытке выполнить команду для получения списка моделей я получил ошибку
(.venv) PS C:\Users\main_\my-prog\discord-ai> python bot.py
Бот GPT 4o-mini#5040 готов к работе!
Ignoring exception in slash command 'list_models':
Traceback (most recent call last):
File "C:\Users\main_\my-prog\discord-ai.venv\Lib\site-packages\disnake\ext\commands\slash_core.py", line 754, in invoke
await call_param_func(self.callback, inter, self.cog, **kwargs)
File "C:\Users\main_\my-prog\discord-ai.venv\Lib\site-packages\disnake\ext\commands\params.py", line 1074, in call_param_func
return await maybe_coroutine(safe_call, function, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\main_\my-prog\discord-ai.venv\Lib\site-packages\disnake\utils.py", line 599, in maybe_coroutine
return await value
^^^^^^^^^^^
File "C:\Users\main_\my-prog\discord-ai\bot.py", line 159, in list_models
await inter.response.send_message(
File "C:\Users\main_\my-prog\discord-ai.venv\Lib\site-packages\disnake\interactions\base.py", line 1117, in send_message
await adapter.create_interaction_response(
File "C:\Users\main_\my-prog\discord-ai.venv\Lib\site-packages\disnake\webhook\async_.py", line 212, in request
raise HTTPException(response, data)
disnake.errors.HTTPException: 400 Bad Request (error code: 50035): Invalid Form Body
In data.embeds.0.description: This field is required
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "C:\Users\main_\my-prog\discord-ai.venv\Lib\site-packages\disnake\ext\commands\interaction_bot_base.py", line 1465, in process_application_commands
await app_command.invoke(interaction)
File "C:\Users\main_\my-prog\discord-ai.venv\Lib\site-packages\disnake\ext\commands\slash_core.py", line 763, in invoke
raise CommandInvokeError(exc) from exc
disnake.ext.commands.errors.CommandInvokeError: Command raised an exception: HTTPException: 400 Bad Request (error code: 50035): Invalid Form Body
In data.embeds.0.description: This field is required
Создание канала прошло успешно
Но при выборе модели глобальной или локальной нет предложения для выбора моделей, я должен сам вводить название модели, нужно сделать предложение на основе доступных моделей
я ввел такую команду
/set_global_model model_name: Mistral
бот ответил
Глобальная модель установлена на Mistral.
но в чате мне сказало
Ошибка при получении ответа от модели.
в консоли бота:
Ошибка Ollama: 404
а в консоли ollama
[GIN] 2025/01/10 - 21:46:26 | 404 | 0s | 127.0.0.1 | POST "/v1/models/Ollama ID: f974a74358d6/generate"
Привет!
Извиняюсь за возникшие проблемы. Давай разберём каждую из них по порядку и внесём необходимые исправления.
/list_models
textdisnake.errors.HTTPException: 400 Bad Request (error code: 50035): Invalid Form Body In data.embeds.0.description: This field is required
Ошибка возникает из-за некорректной структуры передаваемых данных в embeds
. Поле description
является обязательным для каждого embed'а, и, вероятно, передаваемый JSON не соответствует ожидаемой структуре.
Убедимся, что каждый embed содержит все необходимые поля, особенно description
. Также убедимся, что models.json
правильно загружается и передаётся в формате, подходящем для создания disnake.Embed
.
/list_models
:python@bot.slash_command(description="Отобразить список доступных моделей") @commands.has_permissions(manage_channels=True) async def list_models(inter: disnake.ApplicationCommandInteraction): try: embeds = [] for model in models_data['embeds']: embed = disnake.Embed( title=model.get('title', 'Без названия'), description=model.get('description', 'Описание отсутствует'), color=model.get('color', 0xFFFFFF) ) for field in model.get('fields', []): embed.add_field( name=field.get('name', 'Без названия'), value=field.get('value', ''), inline=field.get('inline', False) ) author = model.get('author', {}) if author: embed.set_author( name=author.get('name', ''), url=author.get('url', ''), icon_url=author.get('icon_url', '') ) footer = model.get('footer', {}) if footer: embed.set_footer(text=footer.get('text', '')) embeds.append(embed) # Отправляем все embeds for embed in embeds: await inter.response.send_message(embed=embed, ephemeral=True) except Exception as e: await inter.response.send_message(f"Произошла ошибка при отображении моделей: {e}", ephemeral=True)
disnake.Embed
из данных: Вместо передачи словаря напрямую, мы создаём объекты disnake.Embed
, заполняя обязательные поля.description
всегда присутствует.Если количество моделей не превышает 10, можно отправить все embed'ы в одном сообщении:
python@bot.slash_command(description="Отобразить список доступных моделей") @commands.has_permissions(manage_channels=True) async def list_models(inter: disnake.ApplicationCommandInteraction): try: embeds = [] for model in models_data['embeds']: embed = disnake.Embed( title=model.get('title', 'Без названия'), description=model.get('description', 'Описание отсутствует'), color=model.get('color', 0xFFFFFF) ) for field in model.get('fields', []): embed.add_field( name=field.get('name', 'Без названия'), value=field.get('value', ''), inline=field.get('inline', False) ) author = model.get('author', {}) if author: embed.set_author( name=author.get('name', ''), url=author.get('url', ''), icon_url=author.get('icon_url', '') ) footer = model.get('footer', {}) if footer: embed.set_footer(text=footer.get('text', '')) embeds.append(embed) await inter.response.send_message(embeds=embeds, ephemeral=True) except Exception as e: await inter.response.send_message(f"Произошла ошибка при отображении моделей: {e}", ephemeral=True)
Примечание: Discord позволяет отправлять до 10 embed'ов в одном сообщении. Если у вас больше моделей, нужно реализовать пагинацию или разбивать их на несколько сообщений.
textОшибка Ollama: 404 [GIN] 2025/01/10 - 21:46:26 | 404 | 0s | 127.0.0.1 | POST "/v1/models/Ollama ID: f974a74358d6/generate"
URL запроса к Ollama API сформирован неправильно. Вместо чистого ID модели используется строка "Ollama ID: f974a74358d6"
, что приводит к неверному пути и, как следствие, ошибке 404.
Необходимо извлечь только ID модели из поля footer.text
, убрав префикс "Ollama ID: "
.
get_ollama_response
:pythonasync def get_ollama_response(ollama_footer_text, user_input): # Извлекаем только ID модели prefix = "Ollama ID: " if not ollama_footer_text.startswith(prefix): print(f"Неверный формат Ollama ID: {ollama_footer_text}") return None ollama_id = ollama_footer_text[len(prefix):].strip() # Формируем правильный URL url = f"http://localhost:11434/v1/models/{ollama_id}/generate" payload = { "prompt": user_input, "max_tokens": 150 } headers = { "Content-Type": "application/json" } async with aiohttp.ClientSession() as session: try: async with session.post(url, json=payload, headers=headers) as resp: if resp.status == 200: data = await resp.json() return data.get('response') # Проверьте структуру ответа Ollama else: print(f"Ошибка Ollama: {resp.status}") return None except Exception as e: print(f"Исключение при обращении к Ollama: {e}") return None
"Ollama ID: "
из footer.text
, чтобы получить только ID.footer.text
начинается с ожидаемого префикса.ollama_id
для формирования URL.При выполнении команд /set_global_model
и /set_channel_model
пользователь должен вручную вводить имя модели. Желательно предоставить автодополнение или выбор из доступных моделей.
Использовать выбор из списка (choices) в командах slash. Для этого необходимо динамически генерировать список доступных моделей и предоставить их как варианты выбора.
pythonfrom disnake import Option, OptionType def get_model_choices(): return [ disnake.OptionChoice(name=model['author']['name'], value=model['author']['name']) for model in models_data['embeds'] ] @bot.slash_command(description="Установить глобальную модель для всех чатов") async def set_global_model( inter: disnake.ApplicationCommandInteraction, model_name: str = Option( name="model_name", description="Выберите модель из списка", type=OptionType.string, required=True, choices=get_model_choices() ) ): settings = load_settings() if model_name not in [model['author']['name'] for model in models_data['embeds']]: await inter.response.send_message("Модель не найдена.", ephemeral=True) return settings['global_model'] = model_name save_settings(settings) await inter.response.send_message(f"Глобальная модель установлена на `{model_name}`.", ephemeral=True) @bot.slash_command(description="Установить модель для текущего канала") async def set_channel_model( inter: disnake.ApplicationCommandInteraction, model_name: str = Option( name="model_name", description="Выберите модель из списка", type=OptionType.string, required=True, choices=get_model_choices() ) ): settings = load_settings() if model_name not in [model['author']['name'] for model in models_data['embeds']]: await inter.response.send_message("Модель не найдена.", ephemeral=True) return settings['channel_models'][str(inter.channel.id)] = model_name save_settings(settings) await inter.response.send_message(f"Модель для канала установлена на `{model_name}`.", ephemeral=True)
get_model_choices
: Создаёт список OptionChoice
из доступных моделей.Option
с choices
: В командах добавлены параметры с фиксированными вариантами выбора, что позволяет пользователю выбирать модель из предложенного списка, а не вводить её вручную.choices
:Однако, стоит отметить, что в текущей версии Disnake (как и в Discord API) список choices
ограничен до 25 вариантов и должен быть статическим при запуске бота. Если модели могут динамически меняться, лучше обновлять choices
при запуске или использовать подход с автодополнением (autocomplete).
python@bot.slash_command(description="Установить глобальную модель для всех чатов") async def set_global_model( inter: disnake.ApplicationCommandInteraction, model_name: str ): # Логика установки модели остается такой же settings = load_settings() if model_name not in [model['author']['name'] for model in models_data['embeds']]: await inter.response.send_message("Модель не найдена.", ephemeral=True) return settings['global_model'] = model_name save_settings(settings) await inter.response.send_message(f"Глобальная модель установлена на `{model_name}`.", ephemeral=True) @set_global_model.autocomplete("model_name") async def set_global_model_autocomplete( inter: disnake.AutocompleteInteraction, current: str ): choices = [ disnake.OptionChoice(name=model['author']['name'], value=model['author']['name']) for model in models_data['embeds'] if current.lower() in model['author']['name'].lower() ] await inter.response.send_autocomplete(choices[:25]) # Аналогично для set_channel_model @bot.slash_command(description="Установить модель для текущего канала") async def set_channel_model( inter: disnake.ApplicationCommandInteraction, model_name: str ): settings = load_settings() if model_name not in [model['author']['name'] for model in models_data['embeds']]: await inter.response.send_message("Модель не найдена.", ephemeral=True) return settings['channel_models'][str(inter.channel.id)] = model_name save_settings(settings) await inter.response.send_message(f"Модель для канала установлена на `{model_name}`.", ephemeral=True) @set_channel_model.autocomplete("model_name") async def set_channel_model_autocomplete( inter: disnake.AutocompleteInteraction, current: str ): choices = [ disnake.OptionChoice(name=model['author']['name'], value=model['author']['name']) for model in models_data['embeds'] if current.lower() in model['author']['name'].lower() ] await inter.response.send_autocomplete(choices[:25])
.autocomplete
для динамического предоставления вариантов на основе ввода пользователя.Логирование: Добавь логирование для облегчения отладки. Можно использовать встроенный модуль logging
:
pythonimport logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger('ollama_bot') # Используй logger вместо print logger.info("Бот готов к работе!") logger.error(f"Ошибка Ollama: {resp.status}")
Обработка исключений: Улучши обработку исключений везде, где это необходимо, чтобы бот не падал при возникновении ошибок.
Валидация данных: Убедись, что данные из models.json
корректны и содержат все необходимые поля.
Оптимизация вебхуков: В функции get_or_create_webhook
можно оптимизировать получение аватара вебхука, используя кэширование или обработку ошибок.
get_or_create_webhook
:pythonasync def get_or_create_webhook(channel, model_info): webhooks = await channel.webhooks() webhook = disnake.utils.get(webhooks, name=model_info['author']['name']) if webhook: return webhook # Получение изображения профиля try: async with aiohttp.ClientSession() as session: async with session.get(model_info['author']['icon_url']) as resp: if resp.status != 200: print(f"Не удалось получить иконку модели: {resp.status}") avatar = None else: avatar = await resp.read() except Exception as e: print(f"Ошибка при получении иконки модели: {e}") avatar = None # Создание вебхука webhook = await channel.create_webhook( name=model_info['author']['name'], avatar=avatar ) return webhook
disnake.utils.get
: Упрощает поиск вебхука по имени.Для наглядности приведу обновлённые части кода с учётом всех исправлений.
bot.py
pythonimport disnake from disnake.ext import commands import json import aiohttp import asyncio import os import logging # Настройка логирования logging.basicConfig(level=logging.INFO) logger = logging.getLogger('ollama_bot') # Загрузка конфигурации with open('config.json', 'r', encoding='utf-8') as f: config = json.load(f) with open('models.json', 'r', encoding='utf-8') as f: models_data = json.load(f) intents = disnake.Intents.default() intents.messages = True intents.guilds = True intents.message_content = True bot = commands.Bot(command_prefix='!', intents=intents) SETTINGS_FILE = 'settings.json' # Инициализация файла настроек if not os.path.exists(SETTINGS_FILE): with open(SETTINGS_FILE, 'w', encoding='utf-8') as f: json.dump({"global_model": None, "channel_models": {}}, f, ensure_ascii=False, indent=4) def load_settings(): with open(SETTINGS_FILE, 'r', encoding='utf-8') as f: return json.load(f) def save_settings(settings): with open(SETTINGS_FILE, 'w', encoding='utf-8') as f: json.dump(settings, f, ensure_ascii=False, indent=4) def get_model_choices(): return [ disnake.OptionChoice(name=model['author']['name'], value=model['author']['name']) for model in models_data['embeds'] ] @bot.slash_command(description="Установить глобальную модель для всех чатов") async def set_global_model( inter: disnake.ApplicationCommandInteraction, model_name: str ): settings = load_settings() if model_name not in [model['author']['name'] for model in models_data['embeds']]: await inter.response.send_message("Модель не найдена.", ephemeral=True) return settings['global_model'] = model_name save_settings(settings) await inter.response.send_message(f"Глобальная модель установлена на `{model_name}`.", ephemeral=True) @set_global_model.autocomplete("model_name") async def set_global_model_autocomplete( inter: disnake.AutocompleteInteraction, current: str ): choices = [ disnake.OptionChoice(name=model['author']['name'], value=model['author']['name']) for model in models_data['embeds'] if current.lower() in model['author']['name'].lower() ] await inter.response.send_autocomplete(choices[:25]) @bot.slash_command(description="Установить модель для текущего канала") async def set_channel_model( inter: disnake.ApplicationCommandInteraction, model_name: str ): settings = load_settings() if model_name not in [model['author']['name'] for model in models_data['embeds']]: await inter.response.send_message("Модель не найдена.", ephemeral=True) return settings['channel_models'][str(inter.channel.id)] = model_name save_settings(settings) await inter.response.send_message(f"Модель для канала установлена на `{model_name}`.", ephemeral=True) @set_channel_model.autocomplete("model_name") async def set_channel_model_autocomplete( inter: disnake.AutocompleteInteraction, current: str ): choices = [ disnake.OptionChoice(name=model['author']['name'], value=model['author']['name']) for model in models_data['embeds'] if current.lower() in model['author']['name'].lower() ] await inter.response.send_autocomplete(choices[:25]) @bot.slash_command(description="Отобразить список доступных моделей") @commands.has_permissions(manage_channels=True) async def list_models(inter: disnake.ApplicationCommandInteraction): try: embeds = [] for model in models_data['embeds']: embed = disnake.Embed( title=model.get('title', 'Без названия'), description=model.get('description', 'Описание отсутствует'), color=model.get('color', 0xFFFFFF) ) for field in model.get('fields', []): embed.add_field( name=field.get('name', 'Без названия'), value=field.get('value', ''), inline=field.get('inline', False) ) author = model.get('author', {}) if author: embed.set_author( name=author.get('name', ''), url=author.get('url', ''), icon_url=author.get('icon_url', '') ) footer = model.get('footer', {}) if footer: embed.set_footer(text=footer.get('text', '')) embeds.append(embed) if len(embeds) <= 10: await inter.response.send_message(embeds=embeds, ephemeral=True) else: # Если моделей больше 10, отправляем их по частям for embed in embeds: await inter.followup.send(embed=embed, ephemeral=True) except Exception as e: logger.error(f"Ошибка в list_models: {e}") await inter.response.send_message(f"Произошла ошибка при отображении моделей: {e}", ephemeral=True) @bot.slash_command(description="Создать новый чат-канал") @commands.has_permissions(manage_channels=True) async def create_chat(inter: disnake.ApplicationCommandInteraction, channel_name: str): guild = inter.guild chat_category = guild.get_channel(config['channels']['chat']) if not chat_category: await inter.response.send_message("Категория chat не найдена.", ephemeral=True) return existing = disnake.utils.get(guild.channels, name=channel_name, category=chat_category) if existing: await inter.response.send_message("Канал с таким именем уже существует.", ephemeral=True) return new_channel = await guild.create_text_channel(channel_name, category=chat_category) await inter.response.send_message(f"Канал `{new_channel.name}` создан.", ephemeral=True) @bot.event async def on_message(message): if message.author.bot: return guild = message.guild chat_category_id = config['channels']['chat'] if message.channel.category_id != chat_category_id: return # Не в категории chat settings = load_settings() # Определение модели model_name = settings['channel_models'].get(str(message.channel.id), settings.get('global_model')) if not model_name: await message.channel.send("Не установлена модель для этого канала. Используйте команду настройки.") return # Получение информации о модели model_info = next((model for model in models_data['embeds'] if model['author']['name'] == model_name), None) if not model_info: await message.channel.send("Выбранная модель не найдена.") return # Отправка сообщения в Ollama и получение ответа user_input = message.content ollama_response = await get_ollama_response(model_info['footer']['text'], user_input) # Передаём Ollama ID if not ollama_response: await message.channel.send("Ошибка при получении ответа от модели.") return # Создание или получение вебхука для канала webhook = await get_or_create_webhook(message.channel, model_info) # Отправка ответа через вебхук await webhook.send( content=ollama_response, username=model_info['author']['name'], avatar_url=model_info['author']['icon_url'] ) async def get_ollama_response(ollama_footer_text, user_input): # Извлекаем только ID модели prefix = "Ollama ID: " if not ollama_footer_text.startswith(prefix): logger.error(f"Неверный формат Ollama ID: {ollama_footer_text}") return None ollama_id = ollama_footer_text[len(prefix):].strip() # Формируем правильный URL url = f"http://localhost:11434/v1/models/{ollama_id}/generate" payload = { "prompt": user_input, "max_tokens": 150 } headers = { "Content-Type": "application/json" } async with aiohttp.ClientSession() as session: try: async with session.post(url, json=payload, headers=headers) as resp: if resp.status == 200: data = await resp.json() return data.get('response') # Проверьте структуру ответа Ollama else: logger.error(f"Ошибка Ollama: {resp.status}") return None except Exception as e: logger.error(f"Исключение при обращении к Ollama: {e}") return None async def get_or_create_webhook(channel, model_info): webhooks = await channel.webhooks() webhook = disnake.utils.get(webhooks, name=model_info['author']['name']) if webhook: return webhook # Получение изображения профиля try: async with aiohttp.ClientSession() as session: async with session.get(model_info['author']['icon_url']) as resp: if resp.status != 200: logger.error(f"Не удалось получить иконку модели: {resp.status}") avatar = None else: avatar = await resp.read() except Exception as e: logger.error(f"Ошибка при получении иконки модели: {e}") avatar = None # Создание вебхука webhook = await channel.create_webhook( name=model_info['author']['name'], avatar=avatar ) return webhook @bot.event async def on_ready(): logger.info(f"Бот {bot.user} готов к работе!") bot.run(config['token'])
get_ollama_response
: Теперь извлекает чистый ID модели.list_models
: Правильно создаёт и отправляет embed'ы.get_or_create_webhook
: Обрабатывает ошибки при получении аватара и упрощает поиск вебхука.Проверь структуру models.json
: Убедись, что все необходимые поля присутствуют и имеют правильный формат. Пример структуры:
json{ "content": "## Доступные модели", "embeds": [ { "title": "Описание", "description": "Оптимизированная архитектура для генеративных задач...", "color": 16445723, "fields": [ { "name": "Тэги", "value": "`Генерация`, `Оптимизация`, `MistralAI`", "inline": true }, { "name": "Версии", "value": "`8b`", "inline": true } ], "author": { "name": "Mistral", "url": "https://ollama.com/library/mistral", "icon_url": "https://custom.typingmind.com/assets/models/mistralai.png" }, "footer": { "text": "Ollama ID: f974a74358d6" } }, // Другие модели... ], "username": "System", "avatar_url": "https://static10.tgstat.ru/channels/_0/f8/f8ace5cf4ae021370a88c7bf21a168da.jpg", "attachments": [] }
Проверь доступность Ollama API: Убедись, что Ollama работает на localhost:11434
и API доступен по указанному пути. Возможно, стоит проверить URL и порт, если они отличаются.
Проверь структуру ответа Ollama: Убедись, что поле response
существует в JSON-ответе. Если структура другая, скорректируй доступ к данным в функции get_ollama_response
.
Тестирование: После внесённых изменений перезапусти бота и протестируй команды ещё раз. Убедись, что команды работают корректно и ошибки устранены.
Управление зависимостями: Убедись, что все необходимые библиотеки указаны в requirements.txt
.
txtdisnake aiohttp
Теперь бот должен корректно обрабатывать команды, предоставлять пользователю выбор моделей из доступных и правильно взаимодействовать с Ollama API. Если возникнут дополнительные вопросы или проблемы, не стесняйся обращаться за помощью. Удачи в разработке!
правильный ли у меня json файл с моделями?
{
"content": "## Доступные модели",
"embeds": [
{
"title": "Описание",
"description": "Оптимизированная архитектура для генеративных задач. Обеспечивает высокую производительность и эффективность при меньших вычислительных ресурсах. Часто используется для диалогов, генерации текста и программного кода.",
"color": 16445723,
"fields": [
{
"name": "Тэги",
"value": "Генерация
, Оптимизация
, MistralAI
",
"inline": true
},
{
"name": "Версии",
"value": "8b
",
"inline": true
}
],
"author": {
"name": "Mistral",
"url": "https://ollama.com/library/mistral",
"icon_url": "https://custom.typingmind.com/assets/models/mistralai.png"
},
"footer": {
"text": "Ollama ID: f974a74358d6"
}
},
{
"title": "Описание",
"description": "Мощная языковая модель второго поколения с акцентом на когнитивное понимание текста. Применяется для сложных логических задач, обработки естественного языка и построения интеллектуальных систем.",
"color": 16445723,
"fields": [
{
"name": "Тэги",
"value": "Когнитивный
, Логика
, GemmaAI
",
"inline": true
},
{
"name": "Версии",
"value": "9b
",
"inline": true
}
],
"author": {
"name": "Gemma 2",
"url": "https://ollama.com/library/gemma2",
"icon_url": "https://custom.typingmind.com/assets/models/gemma.jpg"
},
"footer": {
"text": "Ollama ID: ff02c3702f32"
}
},
{
"title": "Описание",
"description": "Предыдущая версия LLaMA с высокой точностью в текстовых задачах. Хорошо подходит для обработки естественного языка и генерации текста. Обладает улучшенной производительностью по сравнению с ранними версиями.",
"color": 16445723,
"fields": [
{
"name": "Тэги",
"value": "НЛП
, Генерация
, Meta
",
"inline": true
},
{
"name": "Версии",
"value": "8b
",
"inline": true
}
],
"author": {
"name": "LLaMA 3.1",
"url": "https://ollama.com/library/llama3.1",
"icon_url": "https://custom.typingmind.com/assets/models/llama.png"
},
"footer": {
"text": "Ollama ID: 46e0c10c039e"
}
},
{
"title": "Описание",
"description": "Модель с поддержкой работы с изображениями и текстом. Обеспечивает мультизадачность, объединяя обработку визуальных данных и текста, используется в задачах, связанных с генерацией описаний, анализом изображений и мультимодальными приложениями.",
"color": 16445723,
"fields": [
{
"name": "Тэги",
"value": "Мультимодальность
, Зрение
, Meta
",
"inline": true
},
{
"name": "Версии",
"value": "11b
",
"inline": true
}
],
"author": {
"name": "LLaMA 3.2 Vision",
"url": "https://ollama.com/library/llama3.2-vision",
"icon_url": "https://custom.typingmind.com/assets/models/llama.png"
},
"footer": {
"text": "Ollama ID: 085a1fdae525"
}
}
],
"username": "System",
"avatar_url": "https://static10.tgstat.ru/channels/_0/f8/f8ace5cf4ae021370a88c7bf21a168da.jpg",
"attachments": []
}
Привет!
Давайте проверим ваш models.json
файл на корректность и соответствие ожидаемой структуре. Также предложу несколько дополнительных рекомендаций, чтобы убедиться, что всё работает правильно.
models.json
На первый взгляд, предоставленный вами JSON выглядит корректным и соответствует ожидаемой структуре. Каждый объект в массиве embeds
содержит необходимые поля:
name
, value
и inline
)name
, url
и icon_url
)text
)Вот ваш JSON для наглядности:
json{ "content": "## Доступные модели", "embeds": [ { "title": "Описание", "description": "Оптимизированная архитектура для генеративных задач. Обеспечивает высокую производительность и эффективность при меньших вычислительных ресурсах. Часто используется для диалогов, генерации текста и программного кода.", "color": 16445723, "fields": [ { "name": "Тэги", "value": "`Генерация`, `Оптимизация`, `MistralAI`", "inline": true }, { "name": "Версии", "value": "`8b`", "inline": true } ], "author": { "name": "Mistral", "url": "https://ollama.com/library/mistral", "icon_url": "https://custom.typingmind.com/assets/models/mistralai.png" }, "footer": { "text": "Ollama ID: f974a74358d6" } }, { "title": "Описание", "description": "Мощная языковая модель второго поколения с акцентом на когнитивное понимание текста. Применяется для сложных логических задач, обработки естественного языка и построения интеллектуальных систем.", "color": 16445723, "fields": [ { "name": "Тэги", "value": "`Когнитивный`, `Логика`, `GemmaAI`", "inline": true }, { "name": "Версии", "value": "`9b`", "inline": true } ], "author": { "name": "Gemma 2", "url": "https://ollama.com/library/gemma2", "icon_url": "https://custom.typingmind.com/assets/models/gemma.jpg" }, "footer": { "text": "Ollama ID: ff02c3702f32" } }, { "title": "Описание", "description": "Предыдущая версия LLaMA с высокой точностью в текстовых задачах. Хорошо подходит для обработки естественного языка и генерации текста. Обладает улучшенной производительностью по сравнению с ранними версиями.", "color": 16445723, "fields": [ { "name": "Тэги", "value": "`НЛП`, `Генерация`, `Meta`", "inline": true }, { "name": "Версии", "value": "`8b`", "inline": true } ], "author": { "name": "LLaMA 3.1", "url": "https://ollama.com/library/llama3.1", "icon_url": "https://custom.typingmind.com/assets/models/llama.png" }, "footer": { "text": "Ollama ID: 46e0c10c039e" } }, { "title": "Описание", "description": "Модель с поддержкой работы с изображениями и текстом. Обеспечивает мультизадачность, объединяя обработку визуальных данных и текста, используется в задачах, связанных с генерацией описаний, анализом изображений и мультимодальными приложениями.", "color": 16445723, "fields": [ { "name": "Тэги", "value": "`Мультимодальность`, `Зрение`, `Meta`", "inline": true }, { "name": "Версии", "value": "`11b`", "inline": true } ], "author": { "name": "LLaMA 3.2 Vision", "url": "https://ollama.com/library/llama3.2-vision", "icon_url": "https://custom.typingmind.com/assets/models/llama.png" }, "footer": { "text": "Ollama ID: 085a1fdae525" } } ], "username": "System", "avatar_url": "https://static10.tgstat.ru/channels/_0/f8/f8ace5cf4ae021370a88c7bf21a168da.jpg", "attachments": [] }
Для уверенности, что ваш JSON корректен, можно воспользоваться онлайн-валидаторами, например JSONLint. Вставьте ваш JSON в валидатор и убедитесь, что нет синтаксических ошибок. По предоставленному вами фрагменту ошибок синтаксиса нет.
/list_models
textdisnake.errors.HTTPException: 400 Bad Request (error code: 50035): Invalid Form Body In data.embeds.0.description: This field is required
disnake.Embed
.disnake.Embed
объектов: Убедитесь, что все обязательные поля присутствуют при создании объектов Embed.Убедитесь, что в вашем коде вы правильно преобразуете JSON в объекты disnake.Embed
. Вот обновлённый пример кода для команды /list_models
:
python@bot.slash_command(description="Отобразить список доступных моделей") @commands.has_permissions(manage_channels=True) async def list_models(inter: disnake.ApplicationCommandInteraction): try: embeds = [] for model in models_data['embeds']: embed = disnake.Embed( title=model.get('title', 'Без названия'), description=model.get('description', 'Описание отсутствует'), color=model.get('color', 0xFFFFFF) ) for field in model.get('fields', []): embed.add_field( name=field.get('name', 'Без названия'), value=field.get('value', ''), inline=field.get('inline', False) ) author = model.get('author', {}) if author: embed.set_author( name=author.get('name', ''), url=author.get('url', ''), icon_url=author.get('icon_url', '') ) footer = model.get('footer', {}) if footer: embed.set_footer(text=footer.get('text', '')) embeds.append(embed) if len(embeds) <= 10: await inter.response.send_message(embeds=embeds, ephemeral=True) else: # Если моделей больше 10, отправляем их по частям for embed in embeds: await inter.followup.send(embed=embed, ephemeral=True) except Exception as e: logger.error(f"Ошибка в list_models: {e}") await inter.response.send_message(f"Произошла ошибка при отображении моделей: {e}", ephemeral=True)
textОшибка Ollama: 404 [GIN] 2025/01/10 - 21:46:26 | 404 | 0s | 127.0.0.1 | POST "/v1/models/Ollama ID: f974a74358d6/generate"
В URL для запроса к Ollama API используется полный текст из footer.text
("Ollama ID: f974a74358d6"
), вместо чистого ID модели ("f974a74358d6"
).
Необходимо извлечь только ID модели из footer.text
, удалив префикс "Ollama ID: "
.
Вот исправленная функция get_ollama_response
:
pythonasync def get_ollama_response(ollama_footer_text, user_input): # Извлекаем только ID модели prefix = "Ollama ID: " if not ollama_footer_text.startswith(prefix): logger.error(f"Неверный формат Ollama ID: {ollama_footer_text}") return None ollama_id = ollama_footer_text[len(prefix):].strip() # Формируем правильный URL url = f"http://localhost:11434/v1/models/{ollama_id}/generate" payload = { "prompt": user_input, "max_tokens": 150 } headers = { "Content-Type": "application/json" } async with aiohttp.ClientSession() as session: try: async with session.post(url, json=payload, headers=headers) as resp: if resp.status == 200: data = await resp.json() return data.get('response') # Проверьте структуру ответа Ollama else: logger.error(f"Ошибка Ollama: {resp.status}") return None except Exception as e: logger.error(f"Исключение при обращении к Ollama: {e}") return None
При использовании команд /set_global_model
и /set_channel_model
пользователю необходимо вручную вводить название модели. Желательно предоставить выбор из доступных моделей.
Использовать автодополнение (autocomplete) для параметров команд. Это позволит пользователю выбирать модели из предложенного списка, а не вводить их вручную.
Вот пример реализации автодополнения для команд:
pythonfrom disnake import Option, OptionType def get_model_choices(): return [ disnake.OptionChoice(name=model['author']['name'], value=model['author']['name']) for model in models_data['embeds'] ] @bot.slash_command(description="Установить глобальную модель для всех чатов") async def set_global_model( inter: disnake.ApplicationCommandInteraction, model_name: str ): settings = load_settings() if model_name not in [model['author']['name'] for model in models_data['embeds']]: await inter.response.send_message("Модель не найдена.", ephemeral=True) return settings['global_model'] = model_name save_settings(settings) await inter.response.send_message(f"Глобальная модель установлена на `{model_name}`.", ephemeral=True) @set_global_model.autocomplete("model_name") async def set_global_model_autocomplete( inter: disnake.AutocompleteInteraction, current: str ): choices = [ disnake.OptionChoice(name=model['author']['name'], value=model['author']['name']) for model in models_data['embeds'] if current.lower() in model['author']['name'].lower() ] await inter.response.send_autocomplete(choices[:25]) @bot.slash_command(description="Установить модель для текущего канала") async def set_channel_model( inter: disnake.ApplicationCommandInteraction, model_name: str ): settings = load_settings() if model_name not in [model['author']['name'] for model in models_data['embeds']]: await inter.response.send_message("Модель не найдена.", ephemeral=True) return settings['channel_models'][str(inter.channel.id)] = model_name save_settings(settings) await inter.response.send_message(f"Модель для канала установлена на `{model_name}`.", ephemeral=True) @set_channel_model.autocomplete("model_name") async def set_channel_model_autocomplete( inter: disnake.AutocompleteInteraction, current: str ): choices = [ disnake.OptionChoice(name=model['author']['name'], value=model['author']['name']) for model in models_data['embeds'] if current.lower() in model['author']['name'].lower() ] await inter.response.send_autocomplete(choices[:25])
Примечание: Discord API ограничивает количество choices
до 25. Если у вас больше моделей, вам нужно реализовать пагинацию или другие механизмы выбора.
models.json
Убедитесь, что ваш models.json
всегда содержит корректные данные. Например, все модели должны иметь author.text
в формате "Ollama ID: <ID>"
. Это необходимо для корректного извлечения ID модели.
Добавьте больше логов для упрощения отладки. Например, выводите информацию о загруженных моделях при старте бота:
python@bot.event async def on_ready(): logger.info(f"Бот {bot.user} готов к работе!") logger.info("Загруженные модели:") for model in models_data['embeds']: logger.info(f"- {model['author']['name']} (ID: {model['footer']['text']})")
Убедитесь, что Ollama API действительно доступен по адресу http://localhost:11434
. Проверьте, что Ollama запущен и слушает на этом порту. Вы можете использовать инструменты вроде curl
или Postman
для тестирования доступности API:
bashcurl -X POST http://localhost:11434/v1/models/f974a74358d6/generate -H "Content-Type: application/json" -d '{"prompt": "Hello"}'
Убедитесь, что ответ от Ollama API содержит поле response
. Возможно, структура ответа отличается, и вам нужно скорректировать доступ к данным. Например, если ответ выглядит так:
json{ "generated_text": "Привет! Как я могу помочь?" }
Тогда вам нужно изменить:
pythonreturn data.get('generated_text')
Добавьте обработку исключений и возможность повторных попыток при временных ошибках сети или API.
После внесённых изменений тщательно протестируйте все команды и функциональность бота:
Ваш models.json
файл выглядит корректно и соответствует ожидаемой структуре. Однако важно убедиться, что:
disnake.Embed
.Если после всех исправлений проблемы сохраняются, пожалуйста, предоставьте обновлённые логи ошибок и любые дополнительные детали, чтобы мы могли помочь более точно.
Удачи в разработке вашего бота! Если возникнут дополнительные вопросы, не стесняйтесь обращаться.
(.venv) PS C:\Users\main_\my-prog\discord-ai> python bot.py
WARNING:disnake.client:PyNaCl is not installed, voice will NOT be supported
Traceback (most recent call last):
File "C:\Users\main_\my-prog\discord-ai\bot.py", line 63, in <module>
inter: disnake.AutocompleteInteraction,
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: module 'disnake' has no attribute 'AutocompleteInteraction'
Привет!
Извиняюсь за возникшие сложности. Давайте разберёмся с текущими ошибками и убедимся, что ваш бот работает корректно.
AttributeError: module 'disnake' has no attribute 'AutocompleteInteraction'
Ошибка возникает из-за того, что модуль disnake
не распознаёт AutocompleteInteraction
. Это может быть вызвано несколькими причинами:
disnake
может не поддерживать AutocompleteInteraction
.AutocompleteInteraction
используется неправильно.Обновите библиотеку Disnake до последней версии
Убедитесь, что у вас установлена последняя версия disnake
, которая поддерживает автодополнение. Выполните следующую команду в вашем виртуальном окружении:
bashpip install -U disnake
После обновления проверьте версию disnake
:
bashpython -m pip show disnake
Вы должны увидеть что-то вроде:
textName: disnake Version: X.Y.Z ...
Убедитесь, что версия X.Y.Z
соответствует последней доступной на PyPI.
Исправьте типизацию в функциях автодополнения
Если после обновления ошибка сохраняется, возможно, disnake.AutocompleteInteraction
недоступен или был переименован. Вместо этого можно использовать более общий тип disnake.Interaction
, или вовсе убрать аннотацию типа.
Исправленный код без использования AutocompleteInteraction
:
python@set_global_model.autocomplete("model_name") async def set_global_model_autocomplete(inter, current: str): choices = [ disnake.OptionChoice(name=model['author']['name'], value=model['author']['name']) for model in models_data['embeds'] if current.lower() in model['author']['name'].lower() ] await inter.response.send_autocomplete(choices[:25]) @set_channel_model.autocomplete("model_name") async def set_channel_model_autocomplete(inter, current: str): choices = [ disnake.OptionChoice(name=model['author']['name'], value=model['author']['name']) for model in models_data['embeds'] if current.lower() in model['author']['name'].lower() ] await inter.response.send_autocomplete(choices[:25])
Или с использованием disnake.Interaction
:
python@set_global_model.autocomplete("model_name") async def set_global_model_autocomplete(inter: disnake.Interaction, current: str): choices = [ disnake.OptionChoice(name=model['author']['name'], value=model['author']['name']) for model in models_data['embeds'] if current.lower() in model['author']['name'].lower() ] await inter.response.send_autocomplete(choices[:25]) @set_channel_model.autocomplete("model_name") async def set_channel_model_autocomplete(inter: disnake.Interaction, current: str): choices = [ disnake.OptionChoice(name=model['author']['name'], value=model['author']['name']) for model in models_data['embeds'] if current.lower() in model['author']['name'].lower() ] await inter.response.send_autocomplete(choices[:25])
Проверьте документацию Disnake
Убедитесь, что вы используете правильные классы и методы, соответствующие вашей версии disnake
. Ознакомьтесь с документацией Disnake для уточнения деталей.
WARNING:disnake.client:PyNaCl is not installed, voice will NOT be supported
Эта ошибка предупреждает, что библиотека PyNaCl
не установлена, и функциональность голосовых каналов (voice
) не будет поддерживаться. Если ваш бот не использует голосовые функции, вы можете проигнорировать это предупреждение. Если же планируете использовать голосовые возможности, необходимо установить PyNaCl
.
Если голосовые функции не нужны:
Просто проигнорируйте это предупреждение. Оно не влияет на остальные функции бота.
Если голосовые функции нужны:
Установите библиотеку PyNaCl
:
bashpip install PyNaCl
После установки PyNaCl
предупреждение исчезнет, и голосовые функции будут работать корректно.
Чтобы убедиться, что все работает корректно, приведу полный пример исправленного файла bot.py
с учётом вышеуказанных изменений:
pythonimport disnake from disnake.ext import commands import json import aiohttp import asyncio import os import logging # Настройка логирования logging.basicConfig(level=logging.INFO) logger = logging.getLogger('ollama_bot') # Загрузка конфигурации with open('config.json', 'r', encoding='utf-8') as f: config = json.load(f) with open('models.json', 'r', encoding='utf-8') as f: models_data = json.load(f) intents = disnake.Intents.default() intents.messages = True intents.guilds = True intents.message_content = True bot = commands.Bot(command_prefix='!', intents=intents) SETTINGS_FILE = 'settings.json' # Инициализация файла настроек if not os.path.exists(SETTINGS_FILE): with open(SETTINGS_FILE, 'w', encoding='utf-8') as f: json.dump({"global_model": None, "channel_models": {}}, f, ensure_ascii=False, indent=4) def load_settings(): with open(SETTINGS_FILE, 'r', encoding='utf-8') as f: return json.load(f) def save_settings(settings): with open(SETTINGS_FILE, 'w', encoding='utf-8') as f: json.dump(settings, f, ensure_ascii=False, indent=4) def get_model_choices(): return [ disnake.OptionChoice(name=model['author']['name'], value=model['author']['name']) for model in models_data['embeds'] ] @bot.slash_command(description="Установить глобальную модель для всех чатов") async def set_global_model( inter: disnake.ApplicationCommandInteraction, model_name: str ): settings = load_settings() if model_name not in [model['author']['name'] for model in models_data['embeds']]: await inter.response.send_message("Модель не найдена.", ephemeral=True) return settings['global_model'] = model_name save_settings(settings) await inter.response.send_message(f"Глобальная модель установлена на `{model_name}`.", ephemeral=True) @set_global_model.autocomplete("model_name") async def set_global_model_autocomplete(inter: disnake.Interaction, current: str): choices = [ disnake.OptionChoice(name=model['author']['name'], value=model['author']['name']) for model in models_data['embeds'] if current.lower() in model['author']['name'].lower() ] await inter.response.send_autocomplete(choices[:25]) @bot.slash_command(description="Установить модель для текущего канала") async def set_channel_model( inter: disnake.ApplicationCommandInteraction, model_name: str ): settings = load_settings() if model_name not in [model['author']['name'] for model in models_data['embeds']]: await inter.response.send_message("Модель не найдена.", ephemeral=True) return settings['channel_models'][str(inter.channel.id)] = model_name save_settings(settings) await inter.response.send_message(f"Модель для канала установлена на `{model_name}`.", ephemeral=True) @set_channel_model.autocomplete("model_name") async def set_channel_model_autocomplete(inter: disnake.Interaction, current: str): choices = [ disnake.OptionChoice(name=model['author']['name'], value=model['author']['name']) for model in models_data['embeds'] if current.lower() in model['author']['name'].lower() ] await inter.response.send_autocomplete(choices[:25]) @bot.slash_command(description="Отобразить список доступных моделей") @commands.has_permissions(manage_channels=True) async def list_models(inter: disnake.ApplicationCommandInteraction): try: embeds = [] for model in models_data['embeds']: embed = disnake.Embed( title=model.get('title', 'Без названия'), description=model.get('description', 'Описание отсутствует'), color=model.get('color', 0xFFFFFF) ) for field in model.get('fields', []): embed.add_field( name=field.get('name', 'Без названия'), value=field.get('value', ''), inline=field.get('inline', False) ) author = model.get('author', {}) if author: embed.set_author( name=author.get('name', ''), url=author.get('url', ''), icon_url=author.get('icon_url', '') ) footer = model.get('footer', {}) if footer: embed.set_footer(text=footer.get('text', '')) embeds.append(embed) if len(embeds) <= 10: await inter.response.send_message(embeds=embeds, ephemeral=True) else: # Если моделей больше 10, отправляем их по частям for embed in embeds: await inter.followup.send(embed=embed, ephemeral=True) except Exception as e: logger.error(f"Ошибка в list_models: {e}") await inter.response.send_message(f"Произошла ошибка при отображении моделей: {e}", ephemeral=True) @bot.slash_command(description="Создать новый чат-канал") @commands.has_permissions(manage_channels=True) async def create_chat(inter: disnake.ApplicationCommandInteraction, channel_name: str): guild = inter.guild chat_category = guild.get_channel(config['channels']['chat']) if not chat_category: await inter.response.send_message("Категория chat не найдена.", ephemeral=True) return existing = disnake.utils.get(guild.channels, name=channel_name, category=chat_category) if existing: await inter.response.send_message("Канал с таким именем уже существует.", ephemeral=True) return new_channel = await guild.create_text_channel(channel_name, category=chat_category) await inter.response.send_message(f"Канал `{new_channel.name}` создан.", ephemeral=True) @bot.event async def on_message(message): if message.author.bot: return guild = message.guild chat_category_id = config['channels']['chat'] if message.channel.category_id != chat_category_id: return # Не в категории chat settings = load_settings() # Определение модели model_name = settings['channel_models'].get(str(message.channel.id), settings.get('global_model')) if not model_name: await message.channel.send("Не установлена модель для этого канала. Используйте команду настройки.") return # Получение информации о модели model_info = next((model for model in models_data['embeds'] if model['author']['name'] == model_name), None) if not model_info: await message.channel.send("Выбранная модель не найдена.") return # Отправка сообщения в Ollama и получение ответа user_input = message.content ollama_response = await get_ollama_response(model_info['footer']['text'], user_input) # Передаём Ollama ID if not ollama_response: await message.channel.send("Ошибка при получении ответа от модели.") return # Создание или получение вебхука для канала webhook = await get_or_create_webhook(message.channel, model_info) # Отправка ответа через вебхук await webhook.send( content=ollama_response, username=model_info['author']['name'], avatar_url=model_info['author']['icon_url'] ) async def get_ollama_response(ollama_footer_text, user_input): # Извлекаем только ID модели prefix = "Ollama ID: " if not ollama_footer_text.startswith(prefix): logger.error(f"Неверный формат Ollama ID: {ollama_footer_text}") return None ollama_id = ollama_footer_text[len(prefix):].strip() # Формируем правильный URL url = f"http://localhost:11434/v1/models/{ollama_id}/generate" payload = { "prompt": user_input, "max_tokens": 150 } headers = { "Content-Type": "application/json" } async with aiohttp.ClientSession() as session: try: async with session.post(url, json=payload, headers=headers) as resp: if resp.status == 200: data = await resp.json() return data.get('response') # Проверьте структуру ответа Ollama else: logger.error(f"Ошибка Ollama: {resp.status}") return None except Exception as e: logger.error(f"Исключение при обращении к Ollama: {e}") return None async def get_or_create_webhook(channel, model_info): webhooks = await channel.webhooks() webhook = disnake.utils.get(webhooks, name=model_info['author']['name']) if webhook: return webhook # Получение изображения профиля try: async with aiohttp.ClientSession() as session: async with session.get(model_info['author']['icon_url']) as resp: if resp.status != 200: logger.error(f"Не удалось получить иконку модели: {resp.status}") avatar = None else: avatar = await resp.read() except Exception as e: logger.error(f"Ошибка при получении иконки модели: {e}") avatar = None # Создание вебхука webhook = await channel.create_webhook( name=model_info['author']['name'], avatar=avatar ) return webhook @bot.event async def on_ready(): logger.info(f"Бот {bot.user} готов к работе!") logger.info("Загруженные модели:") for model in models_data['embeds']: logger.info(f"- {model['author']['name']} (ID: {model['footer']['text']})") bot.run(config['token'])
Обновление и корректировка функций автодополнения:
disnake.Interaction
вместо disnake.AutocompleteInteraction
: Это обеспечивает совместимость с текущими версиями disnake
.Логирование:
Исправление функции get_or_create_webhook
:
disnake.utils.get
для поиска вебхука по имени.Обработка ответа Ollama:
response
существует в JSON-ответе от Ollama. Если структура ответа отличается, измените return data.get('response')
на соответствующее поле, например, return data.get('generated_text')
.models.json
Ваш файл models.json
выглядит корректно и соответствует ожидаемой структуре. Однако, чтобы убедиться в его валидности, вы можете воспользоваться JSONLint или другим онлайн-валидатором. По предоставленному фрагменту синтаксических ошибок нет.
Пример структуры models.json
:
json{ "content": "## Доступные модели", "embeds": [ { "title": "Описание", "description": "Оптимизированная архитектура для генеративных задач. Обеспечивает высокую производительность и эффективность при меньших вычислительных ресурсах. Часто используется для диалогов, генерации текста и программного кода.", "color": 16445723, "fields": [ { "name": "Тэги", "value": "`Генерация`, `Оптимизация`, `MistralAI`", "inline": true }, { "name": "Версии", "value": "`8b`", "inline": true } ], "author": { "name": "Mistral", "url": "https://ollama.com/library/mistral", "icon_url": "https://custom.typingmind.com/assets/models/mistralai.png" }, "footer": { "text": "Ollama ID: f974a74358d6" } }, { "title": "Описание", "description": "Мощная языковая модель второго поколения с акцентом на когнитивное понимание текста. Применяется для сложных логических задач, обработки естественного языка и построения интеллектуальных систем.", "color": 16445723, "fields": [ { "name": "Тэги", "value": "`Когнитивный`, `Логика`, `GemmaAI`", "inline": true }, { "name": "Версии", "value": "`9b`", "inline": true } ], "author": { "name": "Gemma 2", "url": "https://ollama.com/library/gemma2", "icon_url": "https://custom.typingmind.com/assets/models/gemma.jpg" }, "footer": { "text": "Ollama ID: ff02c3702f32" } }, { "title": "Описание", "description": "Предыдущая версия LLaMA с высокой точностью в текстовых задачах. Хорошо подходит для обработки естественного языка и генерации текста. Обладает улучшенной производительностью по сравнению с ранними версиями.", "color": 16445723, "fields": [ { "name": "Тэги", "value": "`НЛП`, `Генерация`, `Meta`", "inline": true }, { "name": "Версии", "value": "`8b`", "inline": true } ], "author": { "name": "LLaMA 3.1", "url": "https://ollama.com/library/llama3.1", "icon_url": "https://custom.typingmind.com/assets/models/llama.png" }, "footer": { "text": "Ollama ID: 46e0c10c039e" } }, { "title": "Описание", "description": "Модель с поддержкой работы с изображениями и текстом. Обеспечивает мультизадачность, объединяя обработку визуальных данных и текста, используется в задачах, связанных с генерацией описаний, анализом изображений и мультимодальными приложениями.", "color": 16445723, "fields": [ { "name": "Тэги", "value": "`Мультимодальность`, `Зрение`, `Meta`", "inline": true }, { "name": "Версии", "value": "`11b`", "inline": true } ], "author": { "name": "LLaMA 3.2 Vision", "url": "https://ollama.com/library/llama3.2-vision", "icon_url": "https://custom.typingmind.com/assets/models/llama.png" }, "footer": { "text": "Ollama ID: 085a1fdae525" } } ], "username": "System", "avatar_url": "https://static10.tgstat.ru/channels/_0/f8/f8ace5cf4ae021370a88c7bf21a168da.jpg", "attachments": [] }
Проверьте структуру ответа от Ollama API:
Убедитесь, что API Ollama возвращает поле response
в JSON-ответе. Возможно, структура ответа отличается, и вам нужно скорректировать функцию get_ollama_response
.
Пример проверки ответа:
pythonasync def get_ollama_response(ollama_footer_text, user_input): prefix = "Ollama ID: " if not ollama_footer_text.startswith(prefix): logger.error(f"Неверный формат Ollama ID: {ollama_footer_text}") return None ollama_id = ollama_footer_text[len(prefix):].strip() url = f"http://localhost:11434/v1/models/{ollama_id}/generate" payload = { "prompt": user_input, "max_tokens": 150 } headers = { "Content-Type": "application/json" } async with aiohttp.ClientSession() as session: try: async with session.post(url, json=payload, headers=headers) as resp: if resp.status == 200: data = await resp.json() logger.info(f"Ответ от Ollama: {data}") # Проверьте структуру ответа и извлеките нужное поле return data.get('response') # Или другое поле else: logger.error(f"Ошибка Ollama: {resp.status}") return None except Exception as e: logger.error(f"Исключение при обращении к Ollama: {e}") return None
Тестирование команд:
Логирование:
Используйте логирование для отслеживания работы бота и выявления потенциальных проблем. В приведённом коде уже добавлены логирующие сообщения.
Проверка доступности Ollama API:
Убедитесь, что Ollama API доступен по адресу http://localhost:11434
. Вы можете использовать инструменты вроде curl
или Postman
для проверки:
bashcurl -X POST http://localhost:11434/v1/models/f974a74358d6/generate \ -H "Content-Type: application/json" \ -d '{"prompt": "Привет"}'
Пример ожидаемого ответа:
json{ "response": "Привет! Как я могу помочь?" }
Если ответ отличается, скорректируйте функцию get_ollama_response
в соответствии с реальной структурой ответа.
Обновление models.json
:
Убедитесь, что все модели имеют корректные footer.text
в формате "Ollama ID: <ID>"
. Это необходимо для правильного извлечения ID модели.
Ваш файл models.json
выглядит корректно, и основные ошибки в коде были связаны с:
AutocompleteInteraction
: Обновление disnake
и корректировка типов решает эту проблему.get_ollama_response
решает эту проблему.После внесения всех исправлений перезапустите бота и протестируйте его функциональность. Если возникнут дополнительные вопросы или проблемы, не стесняйтесь обращаться за помощью.
Удачи в разработке вашего Discord-бота!
в дискорде у меня пытались подгрузиться автокомплиты у выбора модели но произошла ошибка
(.venv) PS C:\Users\main_\my-prog\discord-ai> python bot.py
INFO:disnake.client:logging in using static token
INFO:disnake.gateway:Shard ID None has sent the IDENTIFY payload.
INFO:disnake.gateway:Shard ID None has connected to Gateway: ["gateway-prd-us-east1-b-ltj9",{"micros":241569,"calls":["id_created",{"micros":622,"calls":[]},"session_lookup_time",{"micros":296,"calls":[]},"session_lookup_finished",{"micros":12,"calls":[]},"discord-sessions-prd-1-4",{"micros":238992,"calls":["start_session",{"micros":198783,"calls":["discord-api-rpc-5f47bbdcf-xlcbl",{"micros":194256,"calls":["get_user",{"micros":20330},"get_guilds",{"micros":9973},"send_scheduled_deletion_message",{"micros":6},"guild_join_requests",{"micros":52241},"authorized_ip_coro",{"micros":7}]}]},"starting_guild_connect",{"micros":74,"calls":[]},"presence_started",{"micros":327,"calls":[]},"guilds_started",{"micros":78,"calls":[]},"lobbies_started",{"micros":1,"calls":[]},"guilds_connect",{"micros":1,"calls":[]},"presence_connect",{"micros":39701,"calls":[]},"connect_finished",{"micros":39707,"calls":[]},"build_ready",{"micros":18,"calls":[]},"clean_ready",{"micros":1,"calls":[]},"optimize_ready",{"micros":1,"calls":[]},"split_ready",{"micros":0,"calls":[]}]}]}] (Session ID: 8e7f916ee70dc8f77a4a31448bff9aa9, Resume URL: wss://gateway-us-east1-b.discord.gg).
INFO:ollama_bot:Бот System#2089 готов к работе!
INFO:ollama_bot:Загруженные модели:
INFO:ollama_bot:- Mistral (ID: Ollama ID: f974a74358d6)
INFO:ollama_bot:- Gemma 2 (ID: Ollama ID: ff02c3702f32)
INFO:ollama_bot:- LLaMA 3.1 (ID: Ollama ID: 46e0c10c039e)
INFO:ollama_bot:- LLaMA 3.2 Vision (ID: Ollama ID: 085a1fdae525)
Ignoring exception in on_application_command_autocomplete
Traceback (most recent call last):
File "C:\Users\main_\my-prog\discord-ai.venv\Lib\site-packages\disnake\client.py", line 738, in run_event
await coro(*args, **kwargs)
File "C:\Users\main\my-prog\discord-ai.venv\Lib\site-packages\disnake\ext\commands\interaction_bot_base.py", line 1478, in on_application_command_autocomplete
await self.process_app_command_autocompletion(interaction)
File "C:\Users\main_\my-prog\discord-ai.venv\Lib\site-packages\disnake\ext\commands\interaction_bot_base.py", line 1377, in process_app_command_autocompletion
await slash_command.call_relevant_autocompleter(inter)
File "C:\Users\main\my-prog\discord-ai.venv\Lib\site-packages\disnake\ext\commands\slash_core.py", line 706, in call_relevant_autocompleter
choices = await call_autocompleter(focused_option.name, inter, focused_option.value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\main\my-prog\discord-ai.venv\Lib\site-packages\disnake\ext\commands\slash_core.py", line 682, in _call_autocompleter
return await call_autocompleter(self, param, inter, user_input)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\main\my-prog\discord-ai.venv\Lib\site-packages\disnake\ext\commands\slash_core.py", line 97, in call_autocompleter
return await choices
^^^^^^^^^^^^^
File "C:\Users\main\my-prog\discord-ai\bot.py", line 68, in set_global_model_autocomplete
await inter.response.send_autocomplete(choices[:25])
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'InteractionResponse' object has no attribute 'send_autocomplete'. Did you mean: 'autocomplete'?
Привет!
Давайте разберёмся с текущими ошибками и убедимся, что ваш бот работает корректно. Основная проблема сейчас связана с автодополнением в командах set_global_model
и set_channel_model
.
'InteractionResponse' object has no attribute 'send_autocomplete'. Did you mean: 'autocomplete'?
Ошибка возникает из-за того, что метод send_autocomplete
отсутствует в вашем экземпляре InteractionResponse
. Возможные причины:
send_autocomplete
был добавлен в более поздних версиях Disnake. Если у вас установлена более старая версия, этот метод может отсутствовать.Убедитесь, что у вас установлена последняя версия Disnake, которая поддерживает метод send_autocomplete
.
Обновление Disnake:
В вашем виртуальном окружении выполните:
bashpip install -U disnake
Проверка версии Disnake:
Убедитесь, что у вас установлена актуальная версия (на момент написания последняя версия — 2.7.0). Выполните:
bashpython -m pip show disnake
Вы должны увидеть что-то вроде:
textName: disnake Version: 2.7.0 Summary: An API wrapper for Discord ...
Если после обновления Disnake ошибка сохраняется, убедитесь, что вы используете правильные типы и методы. Вот исправленный пример кода с учётом последних изменений:
pythonimport disnake from disnake.ext import commands import json import aiohttp import asyncio import os import logging # Настройка логирования logging.basicConfig(level=logging.INFO) logger = logging.getLogger('ollama_bot') # Загрузка конфигурации with open('config.json', 'r', encoding='utf-8') as f: config = json.load(f) with open('models.json', 'r', encoding='utf-8') as f: models_data = json.load(f) intents = disnake.Intents.default() intents.messages = True intents.guilds = True intents.message_content = True bot = commands.Bot(command_prefix='!', intents=intents) SETTINGS_FILE = 'settings.json' # Инициализация файла настроек if not os.path.exists(SETTINGS_FILE): with open(SETTINGS_FILE, 'w', encoding='utf-8') as f: json.dump({"global_model": None, "channel_models": {}}, f, ensure_ascii=False, indent=4) def load_settings(): with open(SETTINGS_FILE, 'r', encoding='utf-8') as f: return json.load(f) def save_settings(settings): with open(SETTINGS_FILE, 'w', encoding='utf-8') as f: json.dump(settings, f, ensure_ascii=False, indent=4) def get_model_choices(): return [ disnake.OptionChoice(name=model['author']['name'], value=model['author']['name']) for model in models_data['embeds'] ] @bot.slash_command(description="Установить глобальную модель для всех чатов") async def set_global_model( inter: disnake.ApplicationCommandInteraction, model_name: str ): settings = load_settings() if model_name not in [model['author']['name'] for model in models_data['embeds']]: await inter.response.send_message("Модель не найдена.", ephemeral=True) return settings['global_model'] = model_name save_settings(settings) await inter.response.send_message(f"Глобальная модель установлена на `{model_name}`.", ephemeral=True) @set_global_model.autocomplete("model_name") async def set_global_model_autocomplete(inter: disnake.Interaction, current: str): choices = [ disnake.OptionChoice(name=model['author']['name'], value=model['author']['name']) for model in models_data['embeds'] if current.lower() in model['author']['name'].lower() ] await inter.response.send_autocomplete(choices[:25]) @bot.slash_command(description="Установить модель для текущего канала") async def set_channel_model( inter: disnake.ApplicationCommandInteraction, model_name: str ): settings = load_settings() if model_name not in [model['author']['name'] for model in models_data['embeds']]: await inter.response.send_message("Модель не найдена.", ephemeral=True) return settings['channel_models'][str(inter.channel.id)] = model_name save_settings(settings) await inter.response.send_message(f"Модель для канала установлена на `{model_name}`.", ephemeral=True) @set_channel_model.autocomplete("model_name") async def set_channel_model_autocomplete(inter: disnake.Interaction, current: str): choices = [ disnake.OptionChoice(name=model['author']['name'], value=model['author']['name']) for model in models_data['embeds'] if current.lower() in model['author']['name'].lower() ] await inter.response.send_autocomplete(choices[:25]) @bot.slash_command(description="Отобразить список доступных моделей") @commands.has_permissions(manage_channels=True) async def list_models(inter: disnake.ApplicationCommandInteraction): try: embeds = [] for model in models_data['embeds']: embed = disnake.Embed( title=model.get('title', 'Без названия'), description=model.get('description', 'Описание отсутствует'), color=model.get('color', 0xFFFFFF) ) for field in model.get('fields', []): embed.add_field( name=field.get('name', 'Без названия'), value=field.get('value', ''), inline=field.get('inline', False) ) author = model.get('author', {}) if author: embed.set_author( name=author.get('name', ''), url=author.get('url', ''), icon_url=author.get('icon_url', '') ) footer = model.get('footer', {}) if footer: embed.set_footer(text=footer.get('text', '')) embeds.append(embed) if len(embeds) <= 10: await inter.response.send_message(embeds=embeds, ephemeral=True) else: # Если моделей больше 10, отправляем их по частям for embed in embeds: await inter.followup.send(embed=embed, ephemeral=True) except Exception as e: logger.error(f"Ошибка в list_models: {e}") await inter.response.send_message(f"Произошла ошибка при отображении моделей: {e}", ephemeral=True) @bot.slash_command(description="Создать новый чат-канал") @commands.has_permissions(manage_channels=True) async def create_chat(inter: disnake.ApplicationCommandInteraction, channel_name: str): guild = inter.guild chat_category = guild.get_channel(config['channels']['chat']) if not chat_category: await inter.response.send_message("Категория chat не найдена.", ephemeral=True) return existing = disnake.utils.get(guild.channels, name=channel_name, category=chat_category) if existing: await inter.response.send_message("Канал с таким именем уже существует.", ephemeral=True) return new_channel = await guild.create_text_channel(channel_name, category=chat_category) await inter.response.send_message(f"Канал `{new_channel.name}` создан.", ephemeral=True) @bot.event async def on_message(message): if message.author.bot: return guild = message.guild chat_category_id = config['channels']['chat'] if message.channel.category_id != chat_category_id: return # Не в категории chat settings = load_settings() # Определение модели model_name = settings['channel_models'].get(str(message.channel.id), settings.get('global_model')) if not model_name: await message.channel.send("Не установлена модель для этого канала. Используйте команду настройки.") return # Получение информации о модели model_info = next((model for model in models_data['embeds'] if model['author']['name'] == model_name), None) if not model_info: await message.channel.send("Выбранная модель не найдена.") return # Отправка сообщения в Ollama и получение ответа user_input = message.content ollama_response = await get_ollama_response(model_info['footer']['text'], user_input) # Передаём Ollama ID if not ollama_response: await message.channel.send("Ошибка при получении ответа от модели.") return # Создание или получение вебхука для канала webhook = await get_or_create_webhook(message.channel, model_info) # Отправка ответа через вебхук await webhook.send( content=ollama_response, username=model_info['author']['name'], avatar_url=model_info['author']['icon_url'] ) async def get_ollama_response(ollama_footer_text, user_input): # Извлекаем только ID модели prefix = "Ollama ID: " if not ollama_footer_text.startswith(prefix): logger.error(f"Неверный формат Ollama ID: {ollama_footer_text}") return None ollama_id = ollama_footer_text[len(prefix):].strip() # Формируем правильный URL url = f"http://localhost:11434/v1/models/{ollama_id}/generate" payload = { "prompt": user_input, "max_tokens": 150 } headers = { "Content-Type": "application/json" } async with aiohttp.ClientSession() as session: try: async with session.post(url, json=payload, headers=headers) as resp: if resp.status == 200: data = await resp.json() logger.info(f"Ответ от Ollama: {data}") # Проверьте структуру ответа и извлеките нужное поле return data.get('response') # Или другое поле, например, 'generated_text' else: logger.error(f"Ошибка Ollama: {resp.status}") return None except Exception as e: logger.error(f"Исключение при обращении к Ollama: {e}") return None async def get_or_create_webhook(channel, model_info): webhooks = await channel.webhooks() webhook = disnake.utils.get(webhooks, name=model_info['author']['name']) if webhook: return webhook # Получение изображения профиля try: async with aiohttp.ClientSession() as session: async with session.get(model_info['author']['icon_url']) as resp: if resp.status != 200: logger.error(f"Не удалось получить иконку модели: {resp.status}") avatar = None else: avatar = await resp.read() except Exception as e: logger.error(f"Ошибка при получении иконки модели: {e}") avatar = None # Создание вебхука webhook = await channel.create_webhook( name=model_info['author']['name'], avatar=avatar ) return webhook @bot.event async def on_ready(): logger.info(f"Бот {bot.user} готов к работе!") logger.info("Загруженные модели:") for model in models_data['embeds']: logger.info(f"- {model['author']['name']} (ID: {model['footer']['text']})") bot.run(config['token'])
Обновление Disnake: Убедитесь, что у вас установлена последняя версия disnake
, поддерживающая send_autocomplete
.
Исправление типа в автодополнении:
disnake.Interaction
вместо disnake.AutocompleteInteraction
, так как последний может быть недоступен или устаревшим.send_autocomplete
должен работать корректно, если Disnake обновлён.Логирование:
on_ready
для подтверждения загрузки моделей.Проверка структуры ответа от Ollama:
response
. Если структура другая, скорректируйте return data.get('response')
на соответствующее поле, например, return data.get('generated_text')
.Корректное извлечение ID модели:
models.json
поле footer.text
содержит "Ollama ID: <ID>"
. В коде правильно удаляется префикс для получения чистого ID.Обновление Disnake:
Убедитесь, что вы обновили disnake
до последней версии:
bashpip install -U disnake
Проверка версии Disnake:
Убедитесь, что версия disnake
обновилась:
bashpython -m pip show disnake
Вы должны увидеть версию 2.7.0
или более новую.
Тестирование автодополнения:
После обновления и исправления кода перезапустите бота и проверьте команды set_global_model
и set_channel_model
. Попробуйте начать вводить название модели и убедитесь, что предложения появляются корректно.
Проверка ответа Ollama:
Убедитесь, что Ollama API доступен и отвечает правильно. Вы можете использовать curl
или Postman
для тестирования:
bashcurl -X POST http://localhost:11434/v1/models/f974a74358d6/generate \ -H "Content-Type: application/json" \ -d '{"prompt": "Привет"}'
Ожидаемый ответ:
json{ "response": "Привет! Как я могу помочь?" }
Если ответ отличается, скорректируйте функцию get_ollama_response
в соответствии с реальной структурой ответа.
Проверка структуры models.json
:
Ваш models.json
выглядит корректно. Убедитесь, что все поля заполнены правильно и отсутствуют синтаксические ошибки. Вы можете воспользоваться JSONLint для проверки валидности.
Обработка возможных исключений:
Добавьте больше обработок исключений и логов, чтобы легче было отлаживать проблемы.
Проверка наличия моделей:
Убедитесь, что все модели в models.json
имеют корректные footer.text
в формате "Ollama ID: <ID>"
.
Проверка ограничений Discord:
Discord ограничивает количество embeds
в одном сообщении до 10. Если у вас больше моделей, подумайте о пагинации или разделении на несколько сообщений.
Установка PyNaCl (опционально):
Если планируете использовать голосовые функции, установите PyNaCl
:
bashpip install PyNaCl
Если голосовые функции не нужны, это предупреждение можно игнорировать.
После выполнения вышеуказанных шагов ваш бот должен корректно обрабатывать команды с автодополнением и взаимодействовать с Ollama API. Если после обновления Disnake и исправления кода ошибки сохраняются, пожалуйста, предоставьте обновлённые логи ошибок для дальнейшей диагностики.
Если возникнут дополнительные вопросы или проблемы, не стесняйтесь обращаться за помощью. Удачи в разработке вашего Discord-бота!
при отправке сообщения - выдает ошибку 404
[GIN] 2025/01/10 - 22:06:34 | 404 | 0s | 127.0.0.1 | POST "/v1/models/f974a74358d6/generate"
Я проверил в Chatbox ту же модель (Mistral) и здесь все ок работает, но в нем я не заметил запроса на /v1/models/f974a74358d6/generate
[GIN] 2025/01/10 - 22:07:08 | 200 | 25.562ms | 127.0.0.1 | GET "/api/tags"
[GIN] 2025/01/10 - 22:07:13 | 200 | 1.5406ms | 127.0.0.1 | GET "/api/tags"
time=2025-01-10T22:07:24.019+08:00 level=INFO source=sched.go:714 msg="new model will fit in available VRAM in single GPU, loading" model=C:\Users\main_.ollama\models\blobs\sha256-ff82381e2bea77d91c1b824c7afb83f6fb73e9f7de9dda631bcdbca564aa5435 gpu=GPU-624a7906-7a06-3deb-a07b-db17a3b5abd9 parallel=4 available=6543384576 required="5.9 GiB"
time=2025-01-10T22:07:24.040+08:00 level=INFO source=server.go:104 msg="system memory" total="31.9 GiB" free="17.8 GiB" free_swap="15.7 GiB"
time=2025-01-10T22:07:24.040+08:00 level=INFO source=memory.go:356 msg="offload to cuda" layers.requested=-1 layers.model=33 layers.offload=33 layers.split="" memory.available="[6.1 GiB]" memory.gpu_overhead="0 B" memory.required.full="5.9 GiB" memory.required.partial="5.9 GiB" memory.required.kv="1.0 GiB" memory.required.allocations="[5.9 GiB]" memory.weights.total="4.7 GiB" memory.weights.repeating="4.6 GiB" memory.weights.nonrepeating="105.0 MiB" memory.graph.full="560.0 MiB" memory.graph.partial="585.0 MiB"
time=2025-01-10T22:07:24.047+08:00 level=INFO source=server.go:376 msg="starting llama server" cmd="C:\Users\main_\AppData\Local\Programs\Ollama\lib\ollama\runners\cuda_v12_avx\ollama_llama_server.exe runner --model C:\Users\main_\.ollama\models\blobs\sha256-ff82381e2bea77d91c1b824c7afb83f6fb73e9f7de9dda631bcdbca564aa5435 --ctx-size 8192 --batch-size 512 --n-gpu-layers 33 --threads 6 --no-mmap --parallel 4 --port 59528"
time=2025-01-10T22:07:24.260+08:00 level=INFO source=sched.go:449 msg="loaded runners" count=1
time=2025-01-10T22:07:24.260+08:00 level=INFO source=server.go:555 msg="waiting for llama runner to start responding"
time=2025-01-10T22:07:24.261+08:00 level=INFO source=server.go:589 msg="waiting for server to become available" status="llm server error"
time=2025-01-10T22:07:24.613+08:00 level=INFO source=runner.go:945 msg="starting go runner"
ggml_cuda_init: GGML_CUDA_FORCE_MMQ: no
ggml_cuda_init: GGML_CUDA_FORCE_CUBLAS: no
ggml_cuda_init: found 1 CUDA devices:
Device 0: NVIDIA GeForce RTX 3050, compute capability 8.6, VMM: yes
time=2025-01-10T22:07:24.651+08:00 level=INFO source=runner.go:946 msg=system info="CUDA : ARCHS = 600,610,620,700,720,750,800,860,870,890,900 | USE_GRAPHS = 1 | PEER_MAX_BATCH_SIZE = 128 | CPU : SSE3 = 1 | SSSE3 = 1 | AVX = 1 | LLAMAFILE = 1 | AARCH64_REPACK = 1 | cgo(clang)" threads=6
time=2025-01-10T22:07:24.652+08:00 level=INFO source=.:0 msg="Server listening on 127.0.0.1:59528"
llama_load_model_from_file: using device CUDA0 (NVIDIA GeForce RTX 3050) - 7196 MiB free
llama_model_loader: loaded meta data with 25 key-value pairs and 291 tensors from C:\Users\main_.ollama\models\blobs\sha256-ff82381e2bea77d91c1b824c7afb83f6fb73e9f7de9dda631bcdbca564aa5435 (version GGUF V3 (latest))
llama_model_loader: Dumping metadata keys/values. Note: KV overrides do not apply in this output.
llama_model_loader: - kv 0: general.architecture str = llama
llama_model_loader: - kv 1: general.name str = Mistral-7B-Instruct-v0.3
llama_model_loader: - kv 2: llama.block_count u32 = 32
llama_model_loader: - kv 3: llama.context_length u32 = 32768
llama_model_loader: - kv 4: llama.embedding_length u32 = 4096
llama_model_loader: - kv 5: llama.feed_forward_length u32 = 14336
llama_model_loader: - kv 6: llama.attention.head_count u32 = 32
llama_model_loader: - kv 7: llama.attention.head_count_kv u32 = 8
llama_model_loader: - kv 8: llama.rope.freq_base f32 = 1000000.000000
llama_model_loader: - kv 9: llama.attention.layer_norm_rms_epsilon f32 = 0.000010
llama_model_loader: - kv 10: general.file_type u32 = 2
llama_model_loader: - kv 11: llama.vocab_size u32 = 32768
llama_model_loader: - kv 12: llama.rope.dimension_count u32 = 128
llama_model_loader: - kv 13: tokenizer.ggml.model str = llama
llama_model_loader: - kv 14: tokenizer.ggml.pre str = default
llama_model_loader: - kv 15: tokenizer.ggml.tokens arr[str,32768] = ["<unk>", "<s>", "</s>", "[INST]", "[...
time=2025-01-10T22:07:24.762+08:00 level=INFO source=server.go:589 msg="waiting for server to become available" status="llm server loading model"
llama_model_loader: - kv 16: tokenizer.ggml.scores arr[f32,32768] = [0.000000, 0.000000, 0.000000, 0.0000...
llama_model_loader: - kv 17: tokenizer.ggml.token_type arr[i32,32768] = [2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, ...
llama_model_loader: - kv 18: tokenizer.ggml.bos_token_id u32 = 1
llama_model_loader: - kv 19: tokenizer.ggml.eos_token_id u32 = 2
llama_model_loader: - kv 20: tokenizer.ggml.unknown_token_id u32 = 0
llama_model_loader: - kv 21: tokenizer.ggml.add_bos_token bool = true
llama_model_loader: - kv 22: tokenizer.ggml.add_eos_token bool = false
llama_model_loader: - kv 23: tokenizer.chat_template str = {{ bos_token }}{% for message in mess...
llama_model_loader: - kv 24: general.quantization_version u32 = 2
llama_model_loader: - type f32: 65 tensors
llama_model_loader: - type q4_0: 225 tensors
llama_model_loader: - type q6_K: 1 tensors
llm_load_vocab: special_eos_id is not in special_eog_ids - the tokenizer config may be incorrect
llm_load_vocab: special tokens cache size = 771
llm_load_vocab: token to piece cache size = 0.1731 MB
llm_load_print_meta: format = GGUF V3 (latest)
llm_load_print_meta: arch = llama
llm_load_print_meta: vocab type = SPM
llm_load_print_meta: n_vocab = 32768
llm_load_print_meta: n_merges = 0
llm_load_print_meta: vocab_only = 0
llm_load_print_meta: n_ctx_train = 32768
llm_load_print_meta: n_embd = 4096
llm_load_print_meta: n_layer = 32
llm_load_print_meta: n_head = 32
llm_load_print_meta: n_head_kv = 8
llm_load_print_meta: n_rot = 128
llm_load_print_meta: n_swa = 0
llm_load_print_meta: n_embd_head_k = 128
llm_load_print_meta: n_embd_head_v = 128
llm_load_print_meta: n_gqa = 4
llm_load_print_meta: n_embd_k_gqa = 1024
llm_load_print_meta: n_embd_v_gqa = 1024
llm_load_print_meta: f_norm_eps = 0.0e+00
llm_load_print_meta: f_norm_rms_eps = 1.0e-05
llm_load_print_meta: f_clamp_kqv = 0.0e+00
llm_load_print_meta: f_max_alibi_bias = 0.0e+00
llm_load_print_meta: f_logit_scale = 0.0e+00
llm_load_print_meta: n_ff = 14336
llm_load_print_meta: n_expert = 0
llm_load_print_meta: n_expert_used = 0
llm_load_print_meta: causal attn = 1
llm_load_print_meta: pooling type = 0
llm_load_print_meta: rope type = 0
llm_load_print_meta: rope scaling = linear
llm_load_print_meta: freq_base_train = 1000000.0
llm_load_print_meta: freq_scale_train = 1
llm_load_print_meta: n_ctx_orig_yarn = 32768
llm_load_print_meta: rope_finetuned = unknown
llm_load_print_meta: ssm_d_conv = 0
llm_load_print_meta: ssm_d_inner = 0
llm_load_print_meta: ssm_d_state = 0
llm_load_print_meta: ssm_dt_rank = 0
llm_load_print_meta: ssm_dt_b_c_rms = 0
llm_load_print_meta: model type = 7B
llm_load_print_meta: model ftype = Q4_0
llm_load_print_meta: model params = 7.25 B
llm_load_print_meta: model size = 3.83 GiB (4.54 BPW)
llm_load_print_meta: general.name = Mistral-7B-Instruct-v0.3
llm_load_print_meta: BOS token = 1 '<s>'
llm_load_print_meta: EOS token = 2 '</s>'
llm_load_print_meta: UNK token = 0 '<unk>'
llm_load_print_meta: LF token = 781 '<0x0A>'
llm_load_print_meta: EOG token = 2 '</s>'
llm_load_print_meta: max token length = 48
llm_load_tensors: offloading 32 repeating layers to GPU
llm_load_tensors: offloading output layer to GPU
llm_load_tensors: offloaded 33/33 layers to GPU
llm_load_tensors: CUDA_Host model buffer size = 72.00 MiB
llm_load_tensors: CUDA0 model buffer size = 3850.02 MiB
llama_new_context_with_model: n_seq_max = 4
llama_new_context_with_model: n_ctx = 8192
llama_new_context_with_model: n_ctx_per_seq = 2048
llama_new_context_with_model: n_batch = 2048
llama_new_context_with_model: n_ubatch = 512
llama_new_context_with_model: flash_attn = 0
llama_new_context_with_model: freq_base = 1000000.0
llama_new_context_with_model: freq_scale = 1
llama_new_context_with_model: n_ctx_per_seq (2048) < n_ctx_train (32768) -- the full capacity of the model will not be utilized
llama_kv_cache_init: CUDA0 KV buffer size = 1024.00 MiB
llama_new_context_with_model: KV self size = 1024.00 MiB, K (f16): 512.00 MiB, V (f16): 512.00 MiB
llama_new_context_with_model: CUDA_Host output buffer size = 0.56 MiB
llama_new_context_with_model: CUDA0 compute buffer size = 560.00 MiB
llama_new_context_with_model: CUDA_Host compute buffer size = 24.01 MiB
llama_new_context_with_model: graph nodes = 1030
llama_new_context_with_model: graph splits = 2
time=2025-01-10T22:07:34.775+08:00 level=INFO source=server.go:594 msg="llama runner started in 10.52 seconds"
llama_model_loader: loaded meta data with 25 key-value pairs and 291 tensors from C:\Users\main_.ollama\models\blobs\sha256-ff82381e2bea77d91c1b824c7afb83f6fb73e9f7de9dda631bcdbca564aa5435 (version GGUF V3 (latest))
llama_model_loader: Dumping metadata keys/values. Note: KV overrides do not apply in this output.
llama_model_loader: - kv 0: general.architecture str = llama
llama_model_loader: - kv 1: general.name str = Mistral-7B-Instruct-v0.3
llama_model_loader: - kv 2: llama.block_count u32 = 32
llama_model_loader: - kv 3: llama.context_length u32 = 32768
llama_model_loader: - kv 4: llama.embedding_length u32 = 4096
llama_model_loader: - kv 5: llama.feed_forward_length u32 = 14336
llama_model_loader: - kv 6: llama.attention.head_count u32 = 32
llama_model_loader: - kv 7: llama.attention.head_count_kv u32 = 8
llama_model_loader: - kv 8: llama.rope.freq_base f32 = 1000000.000000
llama_model_loader: - kv 9: llama.attention.layer_norm_rms_epsilon f32 = 0.000010
llama_model_loader: - kv 10: general.file_type u32 = 2
llama_model_loader: - kv 11: llama.vocab_size u32 = 32768
llama_model_loader: - kv 12: llama.rope.dimension_count u32 = 128
llama_model_loader: - kv 13: tokenizer.ggml.model str = llama
llama_model_loader: - kv 14: tokenizer.ggml.pre str = default
llama_model_loader: - kv 15: tokenizer.ggml.tokens arr[str,32768] = ["<unk>", "<s>", "</s>", "[INST]", "[...
llama_model_loader: - kv 16: tokenizer.ggml.scores arr[f32,32768] = [0.000000, 0.000000, 0.000000, 0.0000...
llama_model_loader: - kv 17: tokenizer.ggml.token_type arr[i32,32768] = [2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, ...
llama_model_loader: - kv 18: tokenizer.ggml.bos_token_id u32 = 1
llama_model_loader: - kv 19: tokenizer.ggml.eos_token_id u32 = 2
llama_model_loader: - kv 20: tokenizer.ggml.unknown_token_id u32 = 0
llama_model_loader: - kv 21: tokenizer.ggml.add_bos_token bool = true
llama_model_loader: - kv 22: tokenizer.ggml.add_eos_token bool = false
llama_model_loader: - kv 23: tokenizer.chat_template str = {{ bos_token }}{% for message in mess...
llama_model_loader: - kv 24: general.quantization_version u32 = 2
llama_model_loader: - type f32: 65 tensors
llama_model_loader: - type q4_0: 225 tensors
llama_model_loader: - type q6_K: 1 tensors
llm_load_vocab: special_eos_id is not in special_eog_ids - the tokenizer config may be incorrect
llm_load_vocab: special tokens cache size = 771
llm_load_vocab: token to piece cache size = 0.1731 MB
llm_load_print_meta: format = GGUF V3 (latest)
llm_load_print_meta: arch = llama
llm_load_print_meta: vocab type = SPM
llm_load_print_meta: n_vocab = 32768
llm_load_print_meta: n_merges = 0
llm_load_print_meta: vocab_only = 1
llm_load_print_meta: model type = ?B
llm_load_print_meta: model ftype = all F32
llm_load_print_meta: model params = 7.25 B
llm_load_print_meta: model size = 3.83 GiB (4.54 BPW)
llm_load_print_meta: general.name = Mistral-7B-Instruct-v0.3
llm_load_print_meta: BOS token = 1 '<s>'
llm_load_print_meta: EOS token = 2 '</s>'
llm_load_print_meta: UNK token = 0 '<unk>'
llm_load_print_meta: LF token = 781 '<0x0A>'
llm_load_print_meta: EOG token = 2 '</s>'
llm_load_print_meta: max token length = 48
llama_model_load: vocab only - skipping tensors
[GIN] 2025/01/10 - 22:07:40 | 200 | 16.4389845s | 127.0.0.1 | POST "/api/chat"
Привет!
Извиняюсь за продолжающиеся сложности. Давайте подробно разберёмся с текущей ошибкой и убедимся, что бот корректно взаимодействует с Ollama API.
[GIN] 2025-01-10 - 22:06:34 | 404 | 0s | 127.0.0.1 | POST "/v1/models/f974a74358d6/generate"
Ошибка 404 Not Found
указывает на то, что запрашиваемый ресурс не найден на сервере Ollama. В данном случае, бот пытается отправить запрос к пути /v1/models/f974a74358d6/generate
, но такой модели не существует или идентификатор модели неверен.
models.json
файл для модели "Mistral" указан footer.text
как "Ollama ID: f974a74358d6"
."Mistral-7B-Instruct-v0.3"
./v1/models/f974a74358d6/generate
не появляются, что подразумевает использование другого идентификатора или пути.Вероятнее всего, Ollama API ожидает название модели, а не её ID, для формирования запросов. Поэтому необходимо изменить способ идентификации модели в вашем боте.
models.json
для включения названия моделиДобавьте поле model_name
в каждый объект модели, которое будет содержать точное название модели, как оно зарегистрировано в Ollama.
Пример обновлённого models.json
:
json{ "content": "## Доступные модели", "embeds": [ { "title": "Описание", "description": "Оптимизированная архитектура для генеративных задач. Обеспечивает высокую производительность и эффективность при меньших вычислительных ресурсах. Часто используется для диалогов, генерации текста и программного кода.", "color": 16445723, "fields": [ { "name": "Тэги", "value": "`Генерация`, `Оптимизация`, `MistralAI`", "inline": true }, { "name": "Версии", "value": "`8b`", "inline": true } ], "author": { "name": "Mistral", "url": "https://ollama.com/library/mistral", "icon_url": "https://custom.typingmind.com/assets/models/mistralai.png" }, "footer": { "text": "Ollama ID: f974a74358d6" }, "model_name": "Mistral-7B-Instruct-v0.3" }, { "title": "Описание", "description": "Мощная языковая модель второго поколения с акцентом на когнитивное понимание текста. Применяется для сложных логических задач, обработки естественного языка и построения интеллектуальных систем.", "color": 16445723, "fields": [ { "name": "Тэги", "value": "`Когнитивный`, `Логика`, `GemmaAI`", "inline": true }, { "name": "Версии", "value": "`9b`", "inline": true } ], "author": { "name": "Gemma 2", "url": "https://ollama.com/library/gemma2", "icon_url": "https://custom.typingmind.com/assets/models/gemma.jpg" }, "footer": { "text": "Ollama ID: ff02c3702f32" }, "model_name": "Gemma 2" }, { "title": "Описание", "description": "Предыдущая версия LLaMA с высокой точностью в текстовых задачах. Хорошо подходит для обработки естественного языка и генерации текста. Обладает улучшенной производительностью по сравнению с ранними версиями.", "color": 16445723, "fields": [ { "name": "Тэги", "value": "`НЛП`, `Генерация`, `Meta`", "inline": true }, { "name": "Версии", "value": "`8b`", "inline": true } ], "author": { "name": "LLaMA 3.1", "url": "https://ollama.com/library/llama3.1", "icon_url": "https://custom.typingmind.com/assets/models/llama.png" }, "footer": { "text": "Ollama ID: 46e0c10c039e" }, "model_name": "LLaMA-3.1" }, { "title": "Описание", "description": "Модель с поддержкой работы с изображениями и текстом. Обеспечивает мультизадачность, объединяя обработку визуальных данных и текста, используется в задачах, связанных с генерацией описаний, анализом изображений и мультимодальными приложениями.", "color": 16445723, "fields": [ { "name": "Тэги", "value": "`Мультимодальность`, `Зрение`, `Meta`", "inline": true }, { "name": "Версии", "value": "`11b`", "inline": true } ], "author": { "name": "LLaMA 3.2 Vision", "url": "https://ollama.com/library/llama3.2-vision", "icon_url": "https://custom.typingmind.com/assets/models/llama.png" }, "footer": { "text": "Ollama ID: 085a1fdae525" }, "model_name": "LLaMA-3.2-Vision" } ], "username": "System", "avatar_url": "https://static10.tgstat.ru/channels/_0/f8/f8ace5cf4ae021370a88c7bf21a168da.jpg", "attachments": [] }
Примечание: Добавлено поле model_name
, которое соответствует точному названию модели, используемому в Ollama API.
model_name
вместо footer.text
Измените функцию get_ollama_response
так, чтобы она использовала model_name
вместо footer.text
для формирования URL запроса.
Исправленная функция get_ollama_response
:
pythonasync def get_ollama_response(model_name, user_input): # Формируем правильный URL, используя название модели url = f"http://localhost:11434/v1/models/{model_name}/generate" payload = { "prompt": user_input, "max_tokens": 150 } headers = { "Content-Type": "application/json" } async with aiohttp.ClientSession() as session: try: async with session.post(url, json=payload, headers=headers) as resp: if resp.status == 200: data = await resp.json() logger.info(f"Ответ от Ollama: {data}") # Проверьте структуру ответа и извлеките нужное поле return data.get('response') # Или другое поле, например, 'generated_text' else: logger.error(f"Ошибка Ollama: {resp.status}") return None except Exception as e: logger.error(f"Исключение при обращении к Ollama: {e}") return None
model_name
В функции on_message
передавайте model_info['model_name']
вместо model_info['footer']['text']
.
Исправленная часть on_message
:
python@bot.event async def on_message(message): if message.author.bot: return guild = message.guild chat_category_id = config['channels']['chat'] if message.channel.category_id != chat_category_id: return # Не в категории chat settings = load_settings() # Определение модели model_name = settings['channel_models'].get(str(message.channel.id), settings.get('global_model')) if not model_name: await message.channel.send("Не установлена модель для этого канала. Используйте команду настройки.") return # Получение информации о модели model_info = next((model for model in models_data['embeds'] if model['author']['name'] == model_name), None) if not model_info: await message.channel.send("Выбранная модель не найдена.") return # Отправка сообщения в Ollama и получение ответа user_input = message.content ollama_response = await get_ollama_response(model_info['model_name'], user_input) # Передаём model_name if not ollama_response: await message.channel.send("Ошибка при получении ответа от модели.") return # Создание или получение вебхука для канала webhook = await get_or_create_webhook(message.channel, model_info) # Отправка ответа через вебхук await webhook.send( content=ollama_response, username=model_info['author']['name'], avatar_url=model_info['author']['icon_url'] )
model_name
Если вы используете model_name
для идентификации моделей, убедитесь, что автодополнение использует именно его.
Пример автодополнения:
python@set_global_model.autocomplete("model_name") async def set_global_model_autocomplete(inter: disnake.Interaction, current: str): choices = [ disnake.OptionChoice(name=model['model_name'], value=model['model_name']) for model in models_data['embeds'] if current.lower() in model['model_name'].lower() ] await inter.response.send_autocomplete(choices[:25]) @set_channel_model.autocomplete("model_name") async def set_channel_model_autocomplete(inter: disnake.Interaction, current: str): choices = [ disnake.OptionChoice(name=model['model_name'], value=model['model_name']) for model in models_data['embeds'] if current.lower() in model['model_name'].lower() ] await inter.response.send_autocomplete(choices[:25])
Убедитесь, что вебхуки создаются с правильным именем и аватаром. Если вы используете model_name
для идентификации, возможно, стоит использовать его для имени вебхука.
Пример функции get_or_create_webhook
:
pythonasync def get_or_create_webhook(channel, model_info): webhooks = await channel.webhooks() webhook = disnake.utils.get(webhooks, name=model_info['model_name']) if webhook: return webhook # Получение изображения профиля try: async with aiohttp.ClientSession() as session: async with session.get(model_info['author']['icon_url']) as resp: if resp.status != 200: logger.error(f"Не удалось получить иконку модели: {resp.status}") avatar = None else: avatar = await resp.read() except Exception as e: logger.error(f"Ошибка при получении иконки модели: {e}") avatar = None # Создание вебхука webhook = await channel.create_webhook( name=model_info['model_name'], avatar=avatar ) return webhook
Убедитесь, что Ollama API доступен по адресу, который вы используете в коде (http://localhost:11434
). Возможно, порт отличается, или путь к модели другой.
Проверьте доступность сервера Ollama:
bashcurl -X POST http://localhost:11434/v1/models/Mistral-7B-Instruct-v0.3/generate \ -H "Content-Type: application/json" \ -d '{"prompt": "Привет"}'
Проверьте правильность пути и идентификатора модели:
bot.py
Для наглядности приведу обновлённый код bot.py
с учётом всех изменений:
pythonimport disnake from disnake.ext import commands import json import aiohttp import asyncio import os import logging # Настройка логирования logging.basicConfig(level=logging.INFO) logger = logging.getLogger('ollama_bot') # Загрузка конфигурации with open('config.json', 'r', encoding='utf-8') as f: config = json.load(f) with open('models.json', 'r', encoding='utf-8') as f: models_data = json.load(f) intents = disnake.Intents.default() intents.messages = True intents.guilds = True intents.message_content = True bot = commands.Bot(command_prefix='!', intents=intents) SETTINGS_FILE = 'settings.json' # Инициализация файла настроек if not os.path.exists(SETTINGS_FILE): with open(SETTINGS_FILE, 'w', encoding='utf-8') as f: json.dump({"global_model": None, "channel_models": {}}, f, ensure_ascii=False, indent=4) def load_settings(): with open(SETTINGS_FILE, 'r', encoding='utf-8') as f: return json.load(f) def save_settings(settings): with open(SETTINGS_FILE, 'w', encoding='utf-8') as f: json.dump(settings, f, ensure_ascii=False, indent=4) def get_model_choices(): return [ disnake.OptionChoice(name=model['model_name'], value=model['model_name']) for model in models_data['embeds'] ] @bot.slash_command(description="Установить глобальную модель для всех чатов") async def set_global_model( inter: disnake.ApplicationCommandInteraction, model_name: str ): settings = load_settings() if model_name not in [model['model_name'] for model in models_data['embeds']]: await inter.response.send_message("Модель не найдена.", ephemeral=True) return settings['global_model'] = model_name save_settings(settings) await inter.response.send_message(f"Глобальная модель установлена на `{model_name}`.", ephemeral=True) @set_global_model.autocomplete("model_name") async def set_global_model_autocomplete(inter: disnake.Interaction, current: str): choices = [ disnake.OptionChoice(name=model['model_name'], value=model['model_name']) for model in models_data['embeds'] if current.lower() in model['model_name'].lower() ] await inter.response.send_autocomplete(choices[:25]) @bot.slash_command(description="Установить модель для текущего канала") async def set_channel_model( inter: disnake.ApplicationCommandInteraction, model_name: str ): settings = load_settings() if model_name not in [model['model_name'] for model in models_data['embeds']]: await inter.response.send_message("Модель не найдена.", ephemeral=True) return settings['channel_models'][str(inter.channel.id)] = model_name save_settings(settings) await inter.response.send_message(f"Модель для канала установлена на `{model_name}`.", ephemeral=True) @set_channel_model.autocomplete("model_name") async def set_channel_model_autocomplete(inter: disnake.Interaction, current: str): choices = [ disnake.OptionChoice(name=model['model_name'], value=model['model_name']) for model in models_data['embeds'] if current.lower() in model['model_name'].lower() ] await inter.response.send_autocomplete(choices[:25]) @bot.slash_command(description="Отобразить список доступных моделей") @commands.has_permissions(manage_channels=True) async def list_models(inter: disnake.ApplicationCommandInteraction): try: embeds = [] for model in models_data['embeds']: embed = disnake.Embed( title=model.get('title', 'Без названия'), description=model.get('description', 'Описание отсутствует'), color=model.get('color', 0xFFFFFF) ) for field in model.get('fields', []): embed.add_field( name=field.get('name', 'Без названия'), value=field.get('value', ''), inline=field.get('inline', False) ) author = model.get('author', {}) if author: embed.set_author( name=author.get('name', ''), url=author.get('url', ''), icon_url=author.get('icon_url', '') ) footer = model.get('footer', {}) if footer: embed.set_footer(text=footer.get('text', '')) embeds.append(embed) if len(embeds) <= 10: await inter.response.send_message(embeds=embeds, ephemeral=True) else: # Если моделей больше 10, отправляем их по частям for embed in embeds: await inter.followup.send(embed=embed, ephemeral=True) except Exception as e: logger.error(f"Ошибка в list_models: {e}") await inter.response.send_message(f"Произошла ошибка при отображении моделей: {e}", ephemeral=True) @bot.slash_command(description="Создать новый чат-канал") @commands.has_permissions(manage_channels=True) async def create_chat(inter: disnake.ApplicationCommandInteraction, channel_name: str): guild = inter.guild chat_category = guild.get_channel(config['channels']['chat']) if not chat_category: await inter.response.send_message("Категория chat не найдена.", ephemeral=True) return existing = disnake.utils.get(guild.channels, name=channel_name, category=chat_category) if existing: await inter.response.send_message("Канал с таким именем уже существует.", ephemeral=True) return new_channel = await guild.create_text_channel(channel_name, category=chat_category) await inter.response.send_message(f"Канал `{new_channel.name}` создан.", ephemeral=True) @bot.event async def on_message(message): if message.author.bot: return guild = message.guild chat_category_id = config['channels']['chat'] if message.channel.category_id != chat_category_id: return # Не в категории chat settings = load_settings() # Определение модели model_name = settings['channel_models'].get(str(message.channel.id), settings.get('global_model')) if not model_name: await message.channel.send("Не установлена модель для этого канала. Используйте команду настройки.") return # Получение информации о модели model_info = next((model for model in models_data['embeds'] if model['model_name'] == model_name), None) if not model_info: await message.channel.send("Выбранная модель не найдена.") return # Отправка сообщения в Ollama и получение ответа user_input = message.content ollama_response = await get_ollama_response(model_info['model_name'], user_input) # Передаём model_name if not ollama_response: await message.channel.send("Ошибка при получении ответа от модели.") return # Создание или получение вебхука для канала webhook = await get_or_create_webhook(message.channel, model_info) # Отправка ответа через вебхук await webhook.send( content=ollama_response, username=model_info['author']['name'], avatar_url=model_info['author']['icon_url'] ) async def get_ollama_response(model_name, user_input): # Формируем правильный URL, используя название модели url = f"http://localhost:11434/v1/models/{model_name}/generate" payload = { "prompt": user_input, "max_tokens": 150 } headers = { "Content-Type": "application/json" } async with aiohttp.ClientSession() as session: try: async with session.post(url, json=payload, headers=headers) as resp: if resp.status == 200: data = await resp.json() logger.info(f"Ответ от Ollama: {data}") # Проверьте структуру ответа и извлеките нужное поле return data.get('response') # Или другое поле, например, 'generated_text' else: logger.error(f"Ошибка Ollama: {resp.status}") return None except Exception as e: logger.error(f"Исключение при обращении к Ollama: {e}") return None async def get_or_create_webhook(channel, model_info): webhooks = await channel.webhooks() webhook = disnake.utils.get(webhooks, name=model_info['model_name']) if webhook: return webhook # Получение изображения профиля try: async with aiohttp.ClientSession() as session: async with session.get(model_info['author']['icon_url']) as resp: if resp.status != 200: logger.error(f"Не удалось получить иконку модели: {resp.status}") avatar = None else: avatar = await resp.read() except Exception as e: logger.error(f"Ошибка при получении иконки модели: {e}") avatar = None # Создание вебхука webhook = await channel.create_webhook( name=model_info['model_name'], avatar=avatar ) return webhook @bot.event async def on_ready(): logger.info(f"Бот {bot.user} готов к работе!") logger.info("Загруженные модели:") for model in models_data['embeds']: logger.info(f"- {model['model_name']} (ID: {model['footer']['text']})") bot.run(config['token'])
Использование model_name
вместо footer.text
:
models.json
добавлено поле model_name
, соответствующее точному названию модели в Ollama.model_name
для формирования URL запроса.Исправление функции get_ollama_response
:
model_name
и использует его для формирования правильного URL запроса к Ollama API.Обновление функции get_or_create_webhook
:
model_name
вместо author.name
, что обеспечивает уникальность и правильную идентификацию.Логирование:
После внесённых изменений необходимо:
Перезапустить Ollama:
Перезапустить бота:
bashpython bot.py
Проверьте команду /list_models
:
Установите глобальную модель:
/set_global_model
и выберите модель из автодополнения.Отправьте сообщение в чат-канал:
Проверьте логи Ollama:
Валидация модели:
Проверка структуры ответа Ollama:
response
. Если структура ответа отличается, измените return data.get('response')
на соответствующее поле, например, return data.get('generated_text')
.Логирование ошибок:
Проверка доступности API:
Документация Ollama:
После внесения всех вышеуказанных изменений бот должен корректно взаимодействовать с Ollama API, используя точные названия моделей вместо их ID. Это позволит избежать ошибок 404 и обеспечить корректную работу функциональности автодополнения.
Если после всех изменений проблема сохраняется, пожалуйста, предоставьте обновлённые логи ошибок и дополнительную информацию о настройках Ollama, чтобы мы могли более точно диагностировать проблему.
Удачи в разработке вашего Discord-бота! Если возникнут дополнительные вопросы или проблемы, не стесняйтесь обращаться за помощью.