Telegram бот + ИИ Jlama своими руками

в 17:27, , рубрики: AI, artificial intelligence, java, llm, llm-модели, llm-приложения, spring, telegram, ИИ, искусственный интеллект

Сегодня мы попробуем сделать свой ИИ с телеграм ботом для возможности простого общения с ней. Сразу оговорюсь, мы не будем в очередной раз использовать открытый API ChatGPT или новомодного Deepseek. Мы развернем свой полноценный ИИ локально и сынтегрируем его с телеграм ботом.

LLM модель

Telegram бот + ИИ Jlama своими руками - 1

Первое, что нам нужно сделать – это запустить LLM локально. Задача не такая простая как кажется – современные LLM модели имеют нетривиальные алгоритмы взаимодействия с видеокартой, токенизации текста и т.д. Процесс функционаирования уже обученной LLM называется инференсом. Основные характеристики инференса – это точность и скорость. Подробно останавливаться на них мы не буем, важно понимать саму концепцию. Для реализации такого инференса на чистой java была разработана библиотека Jlama – максимально простой движок инференса для LLM, написанной на голой java без тяжеловесных фрэймворков. В этом и заключается главное отличие Jlama от имеющихся альтернатив.

По факту Jlama дает возможность обслуживать LLM в java окружении напрямую, то есть в той же jvm, где работает наше приложение. В таком подходе есть ряд плюсов: прежде всего это возможность разработчика гибко влиять на функциональность своего приложения с LLM. У Jlama есть одна особенность – для оптимизации движок использует Vector API, а это значит, что какое-то время придется включать preview фичи.

Список доступных моделей перечислен в документации к проекту:

  • Gemma & Gemma 2 Models

  • Llama & Llama2 & Llama3 Models

  • Mistral & Mixtral Models

  • Qwen2 Models

  • IBM Granite Models

  • GPT-2 Models

  • BERT Models

  • BPE Tokenizers

  • WordPiece Tokenizers

Как видите некоторые модели порядком устарели. Я выберу Llama 3 – неплохо обученная модель от Meta. Пока что модель будет воспринимать только английский язык.

Добавляем в проект необходимые зависимости:

implementation("com.github.tjake:jlama-core:$jlamaVersion")
implementation("com.github.tjake:jlama-native:$jlamaVersion:linux-x86_64")

Есть три доступные версии jlama-native: linux-x86_64, macos-x86_64/aarch_64 и windows-x86_64. Выбирайте свою версию в соответствии с вашей ОС, я запускал на ubuntu, поэтому версия linux-x86_64.

Так библиотека очень проста, весь код умещается в одном небольшом классе ArtificialIntelligenceModel:

@Component
public class ArtificialIntelligenceModel {
    @Value("${llm.model-name}")
    private String modelName;

    @Value("${llm.working-directory}")
    private String workingDirectory;

    @Value("${llm.temperature}")
    private String temperature;

    @Value("${llm.ntokens}")
    private String ntokens;

    public String ask(String prompt) throws IOException {
        File localModelPath = new Downloader(workingDirectory, modelName).huggingFaceModel();

        try (AbstractModel model = ModelSupport.loadModel(localModelPath, DType.F32, DType.I8);) {
            PromptContext ctx;
            if (model.promptSupport().isPresent()) {
                ctx = model.promptSupport()
                        .get()
                        .builder()
                        .addSystemMessage("You are a helpful chat bot who writes short responses.")
                        .addUserMessage(prompt)
                        .build();
            } else {
                ctx = PromptContext.of(prompt);
            }
            Generator.Response response = model.generate(UUID.randomUUID(), ctx,
                    Float.parseFloat(temperature),
                    Integer.parseInt(ntokens), (s, f) -> {
                    }
            );
            return response.responseText;
        }
    }
}

Это вся наша модель, довольно компактно. В application.yaml указываем переменную llm.model-name, у нас это будет tjake/Llama-3.2-1B-Instruct-JQ4. Эта модель будет скачана с помощью объекта Downloader и сохранена в директорию llm.working-directory:

File localModelPath = new Downloader(workingDirectory, modelName).huggingFaceModel();

Скачанная модель будет загружена в переменную model:

AbstractModel model = ModelSupport.loadModel(localModelPath, DType.F32, DType.I8);

Теперь нам нужно создать наш промт и отправить его модели. Для этого нам нужно сгенерировать PromptContext с определенными параметрами. Помимо самого текста мы должны указать температуру и количество токенов.

Теперь нам нужно создать наш промт и отправить его модели. Для этого нам нужно сгенерировать PromptContext с определенными параметрами. Помимо самого текста мы должны указать температуру и количество токенов.

Температура — это числовое значение (часто устанавливаемое между 0 и 1, но иногда и выше), которое регулирует распределение вероятностей следующего слова. Другими словами – температура влияет на креативность текста: чем меньше значение, тем меньше будет и креативность полученного результата. В качестве примера выставим значение температуры в 0.

Остался последний важный параметр – это количество токенов. Токен — это набор текстовых символов. LLM разбивают текст не на слова, а на токены. Слишком большие значения ntokens локально лучше не выставлять, чтобы сильно не грузить наши вычислительные ресурсы. Поставим 256.

В итоге получаем PromptContext:

PromptContext ctx;
if (model.promptSupport().isPresent()) {
     ctx = model.promptSupport()
                        .get()
                        .builder()
                        .addSystemMessage("You are a helpful chat bot who writes short responses.")
                        .addUserMessage(prompt)
                        .build();
   } else {
                ctx = PromptContext.of(prompt);
   }

И получение ответа от модели:

Generator.Response response = model.generate(UUID.randomUUID(), ctx,
                    Float.parseFloat(temperature),
                    Integer.parseInt(ntokens), (s, f) -> {
   }

Итоговый результат доступен в поле responseText.

Телеграм бот

Для начала создадим бота с помощью всем известного BotFather.

Telegram бот + ИИ Jlama своими руками - 2

Тут все достаточно просто, вызываем /newbot и вводим нужное нам имя нашего бота. В моем примере это FranticticticChatBot. После создания бота не забываем скопировать API ключ (Use this token to access the HTTP API).

Сам бот также очень простой:

@Slf4j
@Component
@RequiredArgsConstructor
public class AiChatBot implements SpringLongPollingBot, LongPollingSingleThreadUpdateConsumer {
    private static final String START = "/start";

    @Value("${bot.token}")
    private String token;

    private TelegramClient telegramClient;

    private final ArtificialIntelligenceModel model;

    @PostConstruct
    private void init() {
        telegramClient = new OkHttpTelegramClient(getBotToken());
    }

    @Override
    public String getBotToken() {
        return this.token;
    }

    @Override
    public LongPollingUpdateConsumer getUpdatesConsumer() {
        return this;
    }

    @Override
    public void consume(Update update) {
        if (update.hasMessage() && update.getMessage().hasText()) {
            long chatId = update.getMessage().getChatId();
            var text = update.getMessage().getText();

            if(!text.equals(START)) {
                try {
                    var answer = model.ask(text);

                    SendMessage message = SendMessage.builder()
                            .chatId(chatId)
                            .text(answer)
                            .build();

                    telegramClient.execute(message);
                } catch (TelegramApiException | IOException e) {
                    log.error("Error {}", e.getMessage());
                }
            }
        }
    }

    @AfterBotRegistration
    public void afterRegistration(BotSession botSession) {
        log.info("Registered bot running state is: " + botSession.isRunning());
    }
}

Нам потребуются две зависимости:

implementation("org.telegram:telegrambots-springboot-longpolling-starter:$telegramVersion")
implementation("org.telegram:telegrambots-client:$telegramVersion")

В bot.token прописываем полученный при создании бота токен. В качестве клиента будем использовать OkHttpTelegramClient. В методы consume первое, что надо сделать – это получить текстовое сообщение из чата если оно есть:

var text = update.getMessage().getText();

Для получения ответа от модели достаточно вызвать метод ask с полученным из чата текстом:

var answer = model.ask(text);

Ответ от нашего ИИ заворачиваем в объект SendMessage и возвращаем обратно в чат:

telegramClient.execute(message);

Тестируем

Проверяем работу нашего бота с ИИ. Например, отправим ему вопрос: «What is java?». Получаем результат:

Telegram бот + ИИ Jlama своими руками - 3

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

Пример кода доступен тут. В ближайшее время я попробую развернуть бота в Yandex Cloud.

 Подписывайтесь на мой телеграм-канал. Буду рад любым вашим комментариям

Автор: franticticktick

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js