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

—

от автора

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

LLM модель

Первое, что нам нужно сделать – это запустить 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.

Тут все достаточно просто, вызываем /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?». Получаем результат:

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

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

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


ссылка на оригинал статьи https://habr.com/ru/articles/877746/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *