Ollama AI Discord Bot

Creato il: 10 gennaio 2025

Risposto usando O1 Mini da Chat01

Domanda

Привет!
мне нужен бот на Python(Disnake)
этот бот будет использоваться для общения с локальными Ollama ИИ через Discord сервер
Вот логика работы

Структура каналов Сервера

main

  • home
  • models
  • settings

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": []
}

Pensiero

Создаю бота

Пользователь хочет бот в Disnake для взаимодействия с локальным AI Ollama через Discord сервер. Приводит структуру каналов и логику бота, включая конфигурацию моделей и обработку webhooks.

Пялясь в каналы

Структура каналов включает основной, модели и настройки, а также чат с {channels}. Настройки позволяют конфигурировать модели для чатов или глобально.

Разбирая канальные утрачивания

Бот слышит сообщения в чатах, определяет модель, отправляет запрос к Ollama AI и возвращает ответ через webhook. Оцениваю структуру каналов и команды в настройках.

Думал для 4 секунд

Risposta

Привет!

Я помогу тебе создать бота на Python с использованием библиотеки Disnake, который будет взаимодействовать с локальными моделями Ollama AI через Discord сервер. Разберём шаг за шагом, как реализовать требуемую функциональность.

Шаг 1: Установка необходимых библиотек

Сначала убедись, что у тебя установлены все необходимые библиотеки. Для этого выполни следующие команды:

bash
pip install disnake pip install aiohttp

Также убедись, что у тебя установлен и настроен Ollama на локальном сервере.

Шаг 2: Структура проекта

Рекомендуется организовать проект следующим образом:

text
ollama_bot/ ├── bot.py ├── config.json ├── models.json └── requirements.txt
  • bot.py — основной файл бота.
  • config.json — файл с конфигурацией бота (токен, ID каналов и т.д.).
  • models.json — файл с информацией о моделях.
  • requirements.txt — список зависимостей проекта.

Шаг 3: Конфигурация

config.json

Создай файл 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

Сохрани предоставленный тобой пример в файл models.json. Этот файл будет содержать информацию о доступных моделях.

Шаг 4: Реализация бота

Импорт необходимых модулей и загрузка конфигурации

python
import 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.

python
SETTINGS_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

Предполагается, что Ollama предоставляет API для взаимодействия. Если нет, можно использовать вызов командной строки.

python
async 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 )

Шаг 5: Запуск бота

В конце файла bot.py добавь:

python
@bot.event async def on_ready(): print(f"Бот {bot.user} готов к работе!") bot.run(config['token'])

Дополнительные рекомендации

  1. Обработка ошибок: Добавь обработку возможных ошибок при взаимодействии с API Ollama и Discord.

  2. Логи: Включи логирование для упрощения отладки.

  3. Безопасность: Убедись, что команды настройки доступны только авторизованным пользователям.

  4. Оптимизация: Для больших проектов рекомендуется использовать базу данных (например, SQLite или PostgreSQL) для хранения настроек.

  5. Асинхронность: Убедись, что все взаимодействия с сетью выполняются асинхронно, чтобы не блокировать бот.

Пример файла models.json

Убедись, что 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 и дополнительных требований, возможно, потребуется внести изменения и улучшения. Удачи в разработке!

Domanda

при попытке выполнить команду для получения списка моделей я получил ошибку
(.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"

Pensiero

Отслеживаю команду

Пробую команду /list_models, но имею ошибку из-за отсутствия desription в embed.

Уточняю детали

Окей, я разбираюсь с командой /list_models и URL для взаимодействия с Ollama. Это приводит меня к мысли, что правильное форматирование и точность данных критичны.

Думал для несколько секунд

Risposta

Привет!

Извиняюсь за возникшие проблемы. Давай разберём каждую из них по порядку и внесём необходимые исправления.

Проблема 1: Ошибка при выполнении команды /list_models

Ошибка:

text
disnake.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)

Объяснение изменений:

  1. Создание disnake.Embed из данных: Вместо передачи словаря напрямую, мы создаём объекты disnake.Embed, заполняя обязательные поля.
  2. Проверка наличия обязательных полей: Убедились, что description всегда присутствует.
  3. Отправка нескольких сообщений: Если моделей много, можно отправлять их по одному. Однако, если их количество велико, стоит рассмотреть пагинацию или отправку в одно сообщение с несколькими embed'ами (Discord ограничивает до 10 embed'ов на сообщение).

Альтернативный подход: Отправка всех embed'ов в одном сообщении

Если количество моделей не превышает 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'ов в одном сообщении. Если у вас больше моделей, нужно реализовать пагинацию или разбивать их на несколько сообщений.

Проблема 2: Ошибка при обращении к Ollama API

Ошибка:

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:

python
async 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

Объяснение изменений:

  1. Извлечение чистого ID модели: Удаляем префикс "Ollama ID: " из footer.text, чтобы получить только ID.
  2. Проверка формата: Добавили проверку, что footer.text начинается с ожидаемого префикса.
  3. Формирование правильного URL: Используем чистый ollama_id для формирования URL.

Проблема 3: Отсутствие предложений при выборе модели

Описание:

При выполнении команд /set_global_model и /set_channel_model пользователь должен вручную вводить имя модели. Желательно предоставить автодополнение или выбор из доступных моделей.

Решение:

Использовать выбор из списка (choices) в командах slash. Для этого необходимо динамически генерировать список доступных моделей и предоставить их как варианты выбора.

Обновлённые команды с выбором моделей:

python
from 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)

Объяснение изменений:

  1. Функция get_model_choices: Создаёт список OptionChoice из доступных моделей.
  2. Использование 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])

Объяснение:

  1. Автодополнение: Используем декоратор .autocomplete для динамического предоставления вариантов на основе ввода пользователя.
  2. Фильтрация вариантов: Отображаются только те модели, которые содержат введённый текст.

Дополнительные рекомендации

  1. Логирование: Добавь логирование для облегчения отладки. Можно использовать встроенный модуль logging:

    python
    import logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger('ollama_bot') # Используй logger вместо print logger.info("Бот готов к работе!") logger.error(f"Ошибка Ollama: {resp.status}")
  2. Обработка исключений: Улучши обработку исключений везде, где это необходимо, чтобы бот не падал при возникновении ошибок.

  3. Валидация данных: Убедись, что данные из models.json корректны и содержат все необходимые поля.

  4. Оптимизация вебхуков: В функции get_or_create_webhook можно оптимизировать получение аватара вебхука, используя кэширование или обработку ошибок.

Исправленный код для get_or_create_webhook:

python
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: 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

Объяснение изменений:

  1. Использование disnake.utils.get: Упрощает поиск вебхука по имени.
  2. Обработка ошибок при получении аватара: Если изображение не может быть получено, вебхук создаётся без аватара.

Итоговый исправленный код

Для наглядности приведу обновлённые части кода с учётом всех исправлений.

bot.py

python
import 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'])

Объяснение:

  1. Логирование: Добавлено для облегчения отладки.
  2. Исправленные команды с автодополнением: Пользователь теперь может выбирать модели из предложенного списка.
  3. Исправленная функция get_ollama_response: Теперь извлекает чистый ID модели.
  4. Исправленная команда list_models: Правильно создаёт и отправляет embed'ы.
  5. Улучшенная функция get_or_create_webhook: Обрабатывает ошибки при получении аватара и упрощает поиск вебхука.

Дополнительные шаги

  1. Проверь структуру 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": [] }
  2. Проверь доступность Ollama API: Убедись, что Ollama работает на localhost:11434 и API доступен по указанному пути. Возможно, стоит проверить URL и порт, если они отличаются.

  3. Проверь структуру ответа Ollama: Убедись, что поле response существует в JSON-ответе. Если структура другая, скорректируй доступ к данным в функции get_ollama_response.

  4. Тестирование: После внесённых изменений перезапусти бота и протестируй команды ещё раз. Убедись, что команды работают корректно и ошибки устранены.

  5. Управление зависимостями: Убедись, что все необходимые библиотеки указаны в requirements.txt.

    txt
    disnake aiohttp

Заключение

Теперь бот должен корректно обрабатывать команды, предоставлять пользователю выбор моделей из доступных и правильно взаимодействовать с Ollama API. Если возникнут дополнительные вопросы или проблемы, не стесняйся обращаться за помощью. Удачи в разработке!

Domanda

правильный ли у меня 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": []
}

Pensiero

Пересматриваю параметры

Исправляю bot, чтобы корректно обрабатывать /list_models и /set_global_model, включая добавление 'description' и корректное извлечение ID модели.

Проверяю структуру

Я проверяю файл JSON на соответствие ожидаемой структуре для бота, включая ключ 'embeds' и его подключи, такие как 'title', 'description', 'color', 'fields', 'author' и 'footer'.

Проверяю детали

Смотрю на JSON, чтобы убедиться, что ключ 'embeds' и его подключи такие как 'title', 'description', 'color', 'fields', 'author' и 'footer' присутствуют и правильно структурированы.

Разбираю структуру

Окей, приношу извинения за путаницу. Возможно, проблема не в 'description' в JSON, а в другой части кода. Давайте посмотрим, как лучше диагностировать.

Проверяю JSON

JSON корректен, но стоит удостовериться, что все 'description' присутствуют и правильно закодированы.

Думал для 7 секунд

Risposta

Привет!

Давайте проверим ваш models.json файл на корректность и соответствие ожидаемой структуре. Также предложу несколько дополнительных рекомендаций, чтобы убедиться, что всё работает правильно.

Проверка структуры models.json

На первый взгляд, предоставленный вами JSON выглядит корректным и соответствует ожидаемой структуре. Каждый объект в массиве embeds содержит необходимые поля:

  • title
  • description
  • color
  • fields (массив с объектами, содержащими name, value и inline)
  • authorname, url и icon_url)
  • footertext)

Вот ваш 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

Для уверенности, что ваш JSON корректен, можно воспользоваться онлайн-валидаторами, например JSONLint. Вставьте ваш JSON в валидатор и убедитесь, что нет синтаксических ошибок. По предоставленному вами фрагменту ошибок синтаксиса нет.

Возможные причины проблем и рекомендации

1. Ошибка при выполнении команды /list_models

Сообщение об ошибке:

text
disnake.errors.HTTPException: 400 Bad Request (error code: 50035): Invalid Form Body In data.embeds.0.description: This field is required

Возможные причины:

  • Неправильная обработка JSON в коде бота: Возможно, бот пытается напрямую отправить JSON, а не преобразует его в объекты 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)

2. Ошибка при обращении к Ollama API

Сообщение об ошибке:

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:

python
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

3. Отсутствие предложений при выборе модели

Описание:

При использовании команд /set_global_model и /set_channel_model пользователю необходимо вручную вводить название модели. Желательно предоставить выбор из доступных моделей.

Решение:

Использовать автодополнение (autocomplete) для параметров команд. Это позволит пользователю выбирать модели из предложенного списка, а не вводить их вручную.

Вот пример реализации автодополнения для команд:

python
from 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. Если у вас больше моделей, вам нужно реализовать пагинацию или другие механизмы выбора.

Дополнительные рекомендации

1. Валидация models.json

Убедитесь, что ваш models.json всегда содержит корректные данные. Например, все модели должны иметь author.text в формате "Ollama ID: <ID>". Это необходимо для корректного извлечения ID модели.

2. Логирование

Добавьте больше логов для упрощения отладки. Например, выводите информацию о загруженных моделях при старте бота:

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']})")

3. Проверка доступности Ollama API

Убедитесь, что Ollama API действительно доступен по адресу http://localhost:11434. Проверьте, что Ollama запущен и слушает на этом порту. Вы можете использовать инструменты вроде curl или Postman для тестирования доступности API:

bash
curl -X POST http://localhost:11434/v1/models/f974a74358d6/generate -H "Content-Type: application/json" -d '{"prompt": "Hello"}'

4. Проверка структуры ответа Ollama

Убедитесь, что ответ от Ollama API содержит поле response. Возможно, структура ответа отличается, и вам нужно скорректировать доступ к данным. Например, если ответ выглядит так:

json
{ "generated_text": "Привет! Как я могу помочь?" }

Тогда вам нужно изменить:

python
return data.get('generated_text')

5. Обработка исключений и повторные попытки

Добавьте обработку исключений и возможность повторных попыток при временных ошибках сети или API.

6. Тестирование

После внесённых изменений тщательно протестируйте все команды и функциональность бота:

  1. /list_models: Убедитесь, что все модели отображаются корректно.
  2. /set_global_model и /set_channel_model: Проверьте, что модели устанавливаются правильно и выбираются из предложенного списка.
  3. Чат-каналы: Отправьте сообщение в чат-канал и убедитесь, что бот отвечает через вебхук с использованием правильной модели.

Итог

Ваш models.json файл выглядит корректно и соответствует ожидаемой структуре. Однако важно убедиться, что:

  1. Бот правильно обрабатывает этот JSON и создаёт объекты disnake.Embed.
  2. Ollama API доступен и принимает запросы по правильному URL с корректным ID модели.
  3. Структура ответа от Ollama совпадает с тем, что ожидает ваш бот.

Если после всех исправлений проблемы сохраняются, пожалуйста, предоставьте обновлённые логи ошибок и любые дополнительные детали, чтобы мы могли помочь более точно.

Удачи в разработке вашего бота! Если возникнут дополнительные вопросы, не стесняйтесь обращаться.

Domanda

(.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'

Pensiero

Ищет решения

Я работаю над Discord бота на Python, используя библиотеку Disnake. Встретил несколько проблем — ошибки с командой /list_models и неправильным использованием модели в API Ollama.

Ищу причину ошибки

Похоже, библиотека Disnake не имеет атрибута AutocompleteInteraction. Важно проверить документацию Disnake, чтобы понять, какой тип использовать для автозаполненных взаимодействий.

Проверяю документацию

Вижу, что в Disnake есть класс AutocompleteInteraction. Возможно, используемся устаревшую версию Disnake или ошибка в имени класса.

Решаю проблему

Для устранения ошибки атрибута, стоит проверить, что Disnake обновлен до последней версии, поддерживающей AutocompleteInteraction. Возможно, нужна корректировка типа, чтобы избежать этой ошибки.

Думал для 7 секунд

Risposta

Привет!

Извиняюсь за возникшие сложности. Давайте разберёмся с текущими ошибками и убедимся, что ваш бот работает корректно.

Ошибка 1: AttributeError: module 'disnake' has no attribute 'AutocompleteInteraction'

Причина:

Ошибка возникает из-за того, что модуль disnake не распознаёт AutocompleteInteraction. Это может быть вызвано несколькими причинами:

  1. Устаревшая версия Disnake: Ваша версия disnake может не поддерживать AutocompleteInteraction.
  2. Неправильный импорт или использование: Возможно, класс AutocompleteInteraction используется неправильно.

Решение:

  1. Обновите библиотеку Disnake до последней версии

    Убедитесь, что у вас установлена последняя версия disnake, которая поддерживает автодополнение. Выполните следующую команду в вашем виртуальном окружении:

    bash
    pip install -U disnake

    После обновления проверьте версию disnake:

    bash
    python -m pip show disnake

    Вы должны увидеть что-то вроде:

    text
    Name: disnake Version: X.Y.Z ...

    Убедитесь, что версия X.Y.Z соответствует последней доступной на PyPI.

  2. Исправьте типизацию в функциях автодополнения

    Если после обновления ошибка сохраняется, возможно, 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])
  3. Проверьте документацию Disnake

    Убедитесь, что вы используете правильные классы и методы, соответствующие вашей версии disnake. Ознакомьтесь с документацией Disnake для уточнения деталей.

Ошибка 2: WARNING:disnake.client:PyNaCl is not installed, voice will NOT be supported

Причина:

Эта ошибка предупреждает, что библиотека PyNaCl не установлена, и функциональность голосовых каналов (voice) не будет поддерживаться. Если ваш бот не использует голосовые функции, вы можете проигнорировать это предупреждение. Если же планируете использовать голосовые возможности, необходимо установить PyNaCl.

Решение:

  1. Если голосовые функции не нужны:

    Просто проигнорируйте это предупреждение. Оно не влияет на остальные функции бота.

  2. Если голосовые функции нужны:

    Установите библиотеку PyNaCl:

    bash
    pip install PyNaCl

    После установки PyNaCl предупреждение исчезнет, и голосовые функции будут работать корректно.

Дополнительные рекомендации и исправленный код

Чтобы убедиться, что все работает корректно, приведу полный пример исправленного файла bot.py с учётом вышеуказанных изменений:

python
import 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'])

Объяснение внесённых изменений:

  1. Обновление и корректировка функций автодополнения:

    • Использование disnake.Interaction вместо disnake.AutocompleteInteraction: Это обеспечивает совместимость с текущими версиями disnake.
    • Удаление аннотации типа: Если возникнут проблемы с типами, можно убрать аннотацию или использовать более общий тип.
  2. Логирование:

    • Добавлено логирование для упрощения отладки и отслеживания работы бота.
  3. Исправление функции get_or_create_webhook:

    • Упрощено использование disnake.utils.get для поиска вебхука по имени.
    • Добавлена обработка ошибок при получении аватара.
  4. Обработка ответа 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": [] }

Дополнительные шаги для отладки

  1. Проверьте структуру ответа от Ollama API:

    Убедитесь, что API Ollama возвращает поле response в JSON-ответе. Возможно, структура ответа отличается, и вам нужно скорректировать функцию get_ollama_response.

    Пример проверки ответа:

    python
    async 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
  2. Тестирование команд:

    • /list_models: Убедитесь, что все модели отображаются корректно в виде embed-сообщений.
    • /set_global_model и /set_channel_model: Проверьте, что модели устанавливаются правильно и выбираются из предложенного списка автодополнения.
    • Чат-каналы: Отправьте сообщение в чат-канал и убедитесь, что бот отвечает через вебхук с использованием правильной модели.
  3. Логирование:

    Используйте логирование для отслеживания работы бота и выявления потенциальных проблем. В приведённом коде уже добавлены логирующие сообщения.

  4. Проверка доступности Ollama API:

    Убедитесь, что Ollama API доступен по адресу http://localhost:11434. Вы можете использовать инструменты вроде curl или Postman для проверки:

    bash
    curl -X POST http://localhost:11434/v1/models/f974a74358d6/generate \ -H "Content-Type: application/json" \ -d '{"prompt": "Привет"}'

    Пример ожидаемого ответа:

    json
    { "response": "Привет! Как я могу помочь?" }

    Если ответ отличается, скорректируйте функцию get_ollama_response в соответствии с реальной структурой ответа.

  5. Обновление models.json:

    Убедитесь, что все модели имеют корректные footer.text в формате "Ollama ID: <ID>". Это необходимо для правильного извлечения ID модели.

Итог

Ваш файл models.json выглядит корректно, и основные ошибки в коде были связаны с:

  1. Использованием неверного класса AutocompleteInteraction: Обновление disnake и корректировка типов решает эту проблему.
  2. Ошибкой взаимодействия с Ollama API из-за неправильного извлечения ID модели: Исправление функции get_ollama_response решает эту проблему.

После внесения всех исправлений перезапустите бота и протестируйте его функциональность. Если возникнут дополнительные вопросы или проблемы, не стесняйтесь обращаться за помощью.

Удачи в разработке вашего Discord-бота!

Domanda

в дискорде у меня пытались подгрузиться автокомплиты у выбора модели но произошла ошибка
(.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'?

Pensiero

Ищу причину ошибки

Пользователь сталкивается с ошибкой AttributeError в своем коде на Python с использованием Disnake, указывающей на вызов отсутствующего метода send_autocomplete.

Ищу точность

Пользователь выясняет, что метод с именем 'send_autocomplete' отсутствует в 'inter.response' и пытается понять, как правильно ответить на автодополнение в Disnake.

Ищу обновление или ошибку

Похоже, проблема в устаревшей версии Disnake или опечатке. Может, метод 'send_autocomplete' отсутствует в текущей версии.

Ищу оптимальный путь

Пользователь рассматривает код и методику наведенного Disnake, чтобы понять, почему метод 'send_autocomplete' не найден, и обдумывает обновление версии or приведение к синтаксису актуального метода.

Обновление Disnake

Судя по последней документации, метод 'send_autocomplete' появился в Disnake 2.7.0 и выше. Похоже, у пользователя устаревшая версия или ошибка в имени метода.

Обновление и проверка

Пользователь подозревает, что обновление Disnake до последней версии и сверка имени метода 'send_autocomplete' может помочь в решении проблемы.

Обновление и корректировка

Пользователь должен убедиться, что у него последняя версия Disnake, и проверить правильность имен методов для автодополнения. Это может устранить возникшие ошибки.

Оставляя предпочтения

В текущей ситуации лучше оставить тайп аннотации, так как это, похоже, не решит проблемы. Важно обновить Disnake и заменить метод 'send_autocomplete' на актуальный, корректные все ID в models.json.

Рассматриваю пути обновления

Предлагаю обновить Disnake до последней версии, чтобы устранить проблему с методом 'send_autocomplete', и корректировать модели в файле models.json.

Думал для 21 секунд

Risposta

Привет!

Давайте разберёмся с текущими ошибками и убедимся, что ваш бот работает корректно. Основная проблема сейчас связана с автодополнением в командах set_global_model и set_channel_model.

Ошибка: 'InteractionResponse' object has no attribute 'send_autocomplete'. Did you mean: 'autocomplete'?

Причина:

Ошибка возникает из-за того, что метод send_autocomplete отсутствует в вашем экземпляре InteractionResponse. Возможные причины:

  1. Устаревшая версия Disnake: Метод send_autocomplete был добавлен в более поздних версиях Disnake. Если у вас установлена более старая версия, этот метод может отсутствовать.
  2. Неправильное использование метода: Возможно, есть опечатка или неправильное использование метода.

Решение:

Шаг 1: Обновите библиотеку Disnake до последней версии

Убедитесь, что у вас установлена последняя версия Disnake, которая поддерживает метод send_autocomplete.

  1. Обновление Disnake:

    В вашем виртуальном окружении выполните:

    bash
    pip install -U disnake
  2. Проверка версии Disnake:

    Убедитесь, что у вас установлена актуальная версия (на момент написания последняя версия — 2.7.0). Выполните:

    bash
    python -m pip show disnake

    Вы должны увидеть что-то вроде:

    text
    Name: disnake Version: 2.7.0 Summary: An API wrapper for Discord ...

Шаг 2: Исправьте код автодополнения

Если после обновления Disnake ошибка сохраняется, убедитесь, что вы используете правильные типы и методы. Вот исправленный пример кода с учётом последних изменений:

python
import 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'])

Объяснение внесённых изменений:

  1. Обновление Disnake: Убедитесь, что у вас установлена последняя версия disnake, поддерживающая send_autocomplete.

  2. Исправление типа в автодополнении:

    • Используйте disnake.Interaction вместо disnake.AutocompleteInteraction, так как последний может быть недоступен или устаревшим.
    • Метод send_autocomplete должен работать корректно, если Disnake обновлён.
  3. Логирование:

    • Добавлено логирование ответа от Ollama для лучшей отладки.
    • Добавлены дополнительные сообщения в on_ready для подтверждения загрузки моделей.
  4. Проверка структуры ответа от Ollama:

    • Убедитесь, что ответ от Ollama содержит поле response. Если структура другая, скорректируйте return data.get('response') на соответствующее поле, например, return data.get('generated_text').
  5. Корректное извлечение ID модели:

    • В вашем models.json поле footer.text содержит "Ollama ID: <ID>". В коде правильно удаляется префикс для получения чистого ID.

Проверка и отладка:

  1. Обновление Disnake:

    Убедитесь, что вы обновили disnake до последней версии:

    bash
    pip install -U disnake
  2. Проверка версии Disnake:

    Убедитесь, что версия disnake обновилась:

    bash
    python -m pip show disnake

    Вы должны увидеть версию 2.7.0 или более новую.

  3. Тестирование автодополнения:

    После обновления и исправления кода перезапустите бота и проверьте команды set_global_model и set_channel_model. Попробуйте начать вводить название модели и убедитесь, что предложения появляются корректно.

  4. Проверка ответа Ollama:

    Убедитесь, что Ollama API доступен и отвечает правильно. Вы можете использовать curl или Postman для тестирования:

    bash
    curl -X POST http://localhost:11434/v1/models/f974a74358d6/generate \ -H "Content-Type: application/json" \ -d '{"prompt": "Привет"}'

    Ожидаемый ответ:

    json
    { "response": "Привет! Как я могу помочь?" }

    Если ответ отличается, скорректируйте функцию get_ollama_response в соответствии с реальной структурой ответа.

  5. Проверка структуры models.json:

    Ваш models.json выглядит корректно. Убедитесь, что все поля заполнены правильно и отсутствуют синтаксические ошибки. Вы можете воспользоваться JSONLint для проверки валидности.

Дополнительные рекомендации:

  1. Обработка возможных исключений:

    Добавьте больше обработок исключений и логов, чтобы легче было отлаживать проблемы.

  2. Проверка наличия моделей:

    Убедитесь, что все модели в models.json имеют корректные footer.text в формате "Ollama ID: <ID>".

  3. Проверка ограничений Discord:

    Discord ограничивает количество embeds в одном сообщении до 10. Если у вас больше моделей, подумайте о пагинации или разделении на несколько сообщений.

  4. Установка PyNaCl (опционально):

    Если планируете использовать голосовые функции, установите PyNaCl:

    bash
    pip install PyNaCl

    Если голосовые функции не нужны, это предупреждение можно игнорировать.

Итог

После выполнения вышеуказанных шагов ваш бот должен корректно обрабатывать команды с автодополнением и взаимодействовать с Ollama API. Если после обновления Disnake и исправления кода ошибки сохраняются, пожалуйста, предоставьте обновлённые логи ошибок для дальнейшей диагностики.

Если возникнут дополнительные вопросы или проблемы, не стесняйтесь обращаться за помощью. Удачи в разработке вашего Discord-бота!

Domanda

при отправке сообщения - выдает ошибку 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"

Pensiero

Диагностика ошибок

Я разбираюсь с ботом на Disnake, который взаимодействует с сервером Ollama AI. Возникают ошибки HTTP 400 и 404. Странно, что запрос отрабатывает в Chatbox, но не в боте.

Проверьте путь

Проверка кода показывает, что отсутствует запрос POST к нужному эндпоинту. Важно убедиться, что передается только ID модели без префикса.

Проверка префикса

Модель действительно использует префикс "Ollama ID: ", что подтверждает необходимость корректной обработки ID. Важное замечание: не все действия в коде учитывают этот префикс должным образом.

Проверяю модель

Проверяю правильность ID модели Mistral-7B-Instruct-v0.3 в Ollama. Обычный путь API или его точка может отличаться.

Ищу модель

Проверяю наличие модели в Ollama по имени и ID, убедившись, что используемые идентификаторы совпадают. Странно, что Chatbox работает с моделью, но не делает запрос к "/v1/models/f974a74358d6/generate".

Проверяю правильность значений

Окей, давайте посмотрим. Убедитесь, что модель Ava доступна по ID в Ollama. Возможно, API Ollama ожидает имя модели. Попробуйте изменить модели.json, чтобы использовать только имя модели.

Выясняю API

Проверяю, как Ollama API относится к моделям, используя имя вместо ID. Модель Ava доступна по правильному пути в Ollama. Странно, что запрос не проходит.

Использую имя модели

Использование имени модели вместо ID в запросе может помочь решить ошибку 404. Окей, давайте посмотрим, как это можно улучшить.

Использование имени

Окей, давайте посмотрим, что использование имени модели вместо ID в запросе может улучшить ситуацию. Возможно, стоит добавить имя модели отдельно в models.json или изменить footer.text на имя.

Проверка идентификатора

Лог файлы показывают, что идентификатор модели должен быть "Mistral-7B-Instruct-v0.3". Возможно, models.json нужно обновить для включения поля general.name или использовать model['general']['name'] в коде.

Использование имени

Для исправления ошибки 404 предлагаю в запросе использовать имя модели вместо ID, а в models.json включить только имя модели.

Использую имя модели

Включение имени модели в models.json или использование model['general']['name'] в коде могут помочь устранить ошибку 404.

Предлагаю обновить

Рекомендуется использовать имя модели вместо ID в запросе к API, а также проверить правильность идентификатора модели в Ollama.

Думал для 20 секунд

Risposta

Привет!

Извиняюсь за продолжающиеся сложности. Давайте подробно разберёмся с текущей ошибкой и убедимся, что бот корректно взаимодействует с Ollama API.

Проблема: Ошибка 404 при отправке запроса к 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".
  • В логах бота видно, что запрос отправляется именно к этому ID.
  • Однако в Ollama сервере модель зарегистрирована под именем "Mistral-7B-Instruct-v0.3".
  • В Chatbox модель работает корректно, но запросы к /v1/models/f974a74358d6/generate не появляются, что подразумевает использование другого идентификатора или пути.

Решение:

Вероятнее всего, Ollama API ожидает название модели, а не её ID, для формирования запросов. Поэтому необходимо изменить способ идентификации модели в вашем боте.

Шаг 1: Обновите 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.

Шаг 2: Обновите код бота для использования model_name вместо footer.text

Измените функцию get_ollama_response так, чтобы она использовала model_name вместо footer.text для формирования URL запроса.

Исправленная функция get_ollama_response:

python
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

Шаг 3: Обновите обработчик сообщений для передачи 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'] )

Шаг 4: Обновите функции автодополнения для использования 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])

Шаг 5: Обновите функцию создания вебхука (опционально)

Убедитесь, что вебхуки создаются с правильным именем и аватаром. Если вы используете model_name для идентификации, возможно, стоит использовать его для имени вебхука.

Пример функции get_or_create_webhook:

python
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

Шаг 6: Проверка доступности Ollama API

Убедитесь, что Ollama API доступен по адресу, который вы используете в коде (http://localhost:11434). Возможно, порт отличается, или путь к модели другой.

  1. Проверьте доступность сервера Ollama:

    bash
    curl -X POST http://localhost:11434/v1/models/Mistral-7B-Instruct-v0.3/generate \ -H "Content-Type: application/json" \ -d '{"prompt": "Привет"}'
  2. Проверьте правильность пути и идентификатора модели:

    • Если API Ollama использует название модели, убедитесь, что вы используете точное название, как зарегистрировано.

Шаг 7: Обновлённый полный код bot.py

Для наглядности приведу обновлённый код bot.py с учётом всех изменений:

python
import 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'])

Объяснение изменений:

  1. Использование model_name вместо footer.text:

    • В models.json добавлено поле model_name, соответствующее точному названию модели в Ollama.
    • В функциях автодополнения и обработке сообщений используется model_name для формирования URL запроса.
  2. Исправление функции get_ollama_response:

    • Теперь функция принимает model_name и использует его для формирования правильного URL запроса к Ollama API.
  3. Обновление функции get_or_create_webhook:

    • Вебхуки теперь создаются с использованием model_name вместо author.name, что обеспечивает уникальность и правильную идентификацию.
  4. Логирование:

    • Добавлено логирование ответов от Ollama для упрощения отладки.

Шаг 8: Перезапустите Ollama и бота

После внесённых изменений необходимо:

  1. Перезапустить Ollama:

    • Убедитесь, что Ollama запущен и модель доступна по новому пути.
  2. Перезапустить бота:

    • Запустите бота заново:
      bash
      python bot.py
    • Убедитесь, что в логах бота отображаются корректные модели без префикса в ID.

Шаг 9: Тестирование

  1. Проверьте команду /list_models:

    • Убедитесь, что все модели отображаются корректно в виде embed-сообщений.
  2. Установите глобальную модель:

    • Используйте команду /set_global_model и выберите модель из автодополнения.
  3. Отправьте сообщение в чат-канал:

    • Убедитесь, что бот корректно отправляет запрос к Ollama API и отвечает в Discord.
  4. Проверьте логи Ollama:

    • Убедитесь, что запросы идут к правильному пути и модель отвечает корректно.

Дополнительные рекомендации:

  1. Валидация модели:

    • Убедитесь, что модель, к которой вы обращаетесь, действительно загружена и доступна через Ollama API.
  2. Проверка структуры ответа Ollama:

    • Проверьте, содержит ли ответ от Ollama поле response. Если структура ответа отличается, измените return data.get('response') на соответствующее поле, например, return data.get('generated_text').
  3. Логирование ошибок:

    • Используйте логирование для отслеживания всех этапов взаимодействия, что поможет быстро выявлять и исправлять ошибки.
  4. Проверка доступности API:

    • Убедитесь, что Ollama API доступен из той же сети, где работает бот. Возможно, потребуется настроить брандмауэр или параметры сети.
  5. Документация Ollama:

    • Ознакомьтесь с официальной документацией Ollama API, чтобы убедиться, что вы используете правильные эндпоинты и параметры.

Заключение

После внесения всех вышеуказанных изменений бот должен корректно взаимодействовать с Ollama API, используя точные названия моделей вместо их ID. Это позволит избежать ошибок 404 и обеспечить корректную работу функциональности автодополнения.

Если после всех изменений проблема сохраняется, пожалуйста, предоставьте обновлённые логи ошибок и дополнительную информацию о настройках Ollama, чтобы мы могли более точно диагностировать проблему.

Удачи в разработке вашего Discord-бота! Если возникнут дополнительные вопросы или проблемы, не стесняйтесь обращаться за помощью.

Condividi questa Q&A