В заметке расскажу как на python сделать простого чат бота для телеграм на базе последней версии llm модели llama-3.

Предположим у нас уже установлен python и CUDA (если хотите использовать gpu для ускорения). Для взаимодействия с моделью на python есть несколько вариантов, чтобы не усложнять будем использовать библиотеку llama.cpp и квантованную модель в формате GGUF. Обратите внимание, нужна Instruct версия.

Подготовка

В телеграм с помощью @BotFather создайте нового бота и получите токен.

Скачайте с huggingface.co модель, как вариант отсюда https://huggingface.co/bartowski/Meta-Llama-3-8B-Instruct-GGUF/tree/main, либо можно найти подходящую по запросу llama-3 8b gguf.

В каталоге с проектом создаем виртуальное окружения для python и активируем его:

python -m venv venv
. venv/bin/activate

Устанавливаем библиотеку для работы с моделью:

pip install llama-cpp-python

Устанавливаем telebot для создания телеграм бота

pip install telebot python-dotenv

Работа с моделью

Давайте проверим как в принципе взаимодействовать с моделью из python.

Инициализируем модель:

from llama_cpp import Llama

llm = Llama(
    model_path="./models/Meta-Llama-3-8B-Instruct-Q4_K_M.gguf",
    chat_format="llama-3",
    # n_gpu_layers=-1, # для использования GPU
    # seed=1337, # установить конкретный seed
    # n_ctx=8192, # установить размер контекста
)

Попробуем получить какой-нибудь вывод от модели:

messages = [
    { "role": "system", "content": "Ты полезный ИИ помощник." },
    { "role": "user", "content": "Привет! Ты кто?" },
]

output = llm.create_chat_completion(messages)
print(output)

Получаем на выходе:

{
    'id': 'chatcmpl-24b5cdf3-945d-41b2-ad30-d4d24226a4e4',
    object': 'chat.completion',
    'created': 1714558400,
    'model': './models/Meta-Llama-3-8B-Instruct-Q4_K_M.gguf',
    'choices': [
        {
            'index': 0,
            'message': {
                'role': 'assistant',
                'content': 'Привет! Я - LLaMA, искусственный интеллект, созданный Meta AI.
                        Моя задача - помочь людям в их повседневной жизни, ответить на вопросы, дать советы и просто пообщаться.
                        Я могу генерировать текст, отвечать на вопросы, переводить языки и многое другое! Как я могу помочь вам сегодня?'
                },
            'logprobs': None,
            'finish_reason': 'stop'
        }],
    'usage': {
        'prompt_tokens': 33,
        'completion_tokens': 88,
        'total_tokens': 121
        }
}

Модель работает, можно идти дальше.

Телеграм бот

Теперь сделаем простого бота который будет получать сообщения от пользователя и отправлять ему ответ:

from llama_cpp import Llama
import telebot
from telebot.types import Message
from dotenv import load_dotenv

load_dotenv()

TG_TOKEN = os.getenv("TG_TOKEN")
bot = telebot.TeleBot(TG_TOKEN)

@bot.message_handler(commands=["start", "help"])
def send_welcome(message: Message):
    bot.send_message(message.chat.id, "Я ИИ бот на базе llama-3.")

# Сделаем простую историю общения. Инициализируем ее системным сообщением.
messages = [ { "role": "system", "content": "Ты полезный ИИ помощник." } ]

# создаем обработчик текстовых сообщений
@bot.message_handler(func=lambda message: True)
def message_handler(message: Message):
    chat_id = message.chat.id
    # добавляем в историю сообщение пользователя
    messages.append({"role": "user", "content": message.text})
    # получаем ответ от модели
    out = llm.create_chat_completion(messages)
    # из объекта получаем текст сообщения
    reply = out["choices"][0]["message"]["content"]
    # отправляем текст от ИИ в чат пользователю
    bot.send_message(chat_id, reply)
    # сохраняем ответ ИИ в историю
    messages.append({"role": "assistant", "content": reply})

print("bot is ready")
bot.infinity_polling() # запускаем пулинг

Создаём .env файл с переменной TG_TOKEN с токеном бота, запускаем скрипт и идем в телеграм к боту проверять:

Немного улучшим бота

Чат работает, но тут есть как минимум три проблемы:

  1. Общая история сообщений для всех пользователей
  2. Не учитывается длинна контекста. Для стандартной llama-3 модели размер контекста 8K токенов

Немного перепишем бота и сделаем наивное отсечение истории с глубиной в 30 сообщений. Столько последних сообщений бот будет “помнить”.

import datetime
import os

import telebot
from dotenv import load_dotenv
from llama_cpp import Llama
from telebot.types import Message

load_dotenv()

TG_TOKEN = os.getenv("TG_TOKEN")
bot = telebot.TeleBot(TG_TOKEN)

@bot.message_handler(commands=["start", "help"])
def send_welcome(message: Message):
    bot.send_message(message.chat.id, "Я ИИ бот на базе llama-3.")

llm = Llama(
    model_path="./models/Meta-Llama-3-8B-Instruct-Q4_K_M.gguf",
    chat_format="llama-3",
    verbose=False,
)

# словарь для хранения историй сообщений
user_message_history = {}

@bot.message_handler(content_types=["text"])
def message_handler(message: Message):
    chat_id = message.chat.id
    user_id = message.from_user.id

    # Получаем историю сообщений текущего пользователя
    user_history = user_message_history.get(user_id, [])
    user_history.append({"role": "user", "content": message.text})

    #  Добавим в контест текущую дату и время
    current_date_time = datetime.datetime.now().strftime("%d %B %Y, %H:%M MSK")
    messages = [ { "role": "system", "content": f"Ты полезный ИИ помощник.\nТекущая дата: {current_date_time}" } ]
    for msg in user_history:
        messages.append(msg)

    # Симулируем что бот печатает ответ
    bot.send_chat_action(chat_id, "typing")

    # Получаем ответ от модели
    out = llm.create_chat_completion(messages)
    reply = out["choices"][0]["message"]["content"]

    # Добавляем ответ бота в историю пользователя
    user_history.append({"role": "assistant", "content": reply})
    # Сохраняем усеченную историю
    user_message_history[user_id] = user_history[-30:]

    # Отправляем ответ пользователю
    bot.send_message(chat_id, reply)

print("bot is ready")
bot.infinity_polling() # запускаем пулинг

Проверим работу контекста и памяти:

Бот смог понять из системного сообщения какая сейчас дата и смог вспомнить о чем я его спрашивал.

Обратите внимание, это proof of concept, бот не для продакшена и в зависимости от ваших ресурсов сможет обработать ограниченное число одновременных (параллельных) запросов. Для чего-то более жизнеспособного можно например организовать очередь сообщений (queue, redis, rabbitqm…) для их последовательной обработки.