Сегодня мы попробуем сделать свой ИИ с телеграм ботом для возможности простого общения с ней. Сразу оговорюсь, мы не будем в очередной раз использовать открытый API ChatGPT или новомодного Deepseek. Мы развернем свой полноценный ИИ локально и сынтегрируем его с телеграм ботом.
LLM модель
![Telegram бот + ИИ Jlama своими руками - 1 Telegram бот + ИИ Jlama своими руками - 1](https://www.pvsm.ru/images/2025/01/29/Telegram-bot-ii-Jlama-svoimi-rukami.jpg)
Первое, что нам нужно сделать – это запустить 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 Telegram бот + ИИ Jlama своими руками - 2](https://www.pvsm.ru/images/2025/01/29/Telegram-bot-ii-Jlama-svoimi-rukami-2.png)
Тут все достаточно просто, вызываем /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 Telegram бот + ИИ Jlama своими руками - 3](https://www.pvsm.ru/images/2025/01/29/Telegram-bot-ii-Jlama-svoimi-rukami-3.jpg)
Ответ получим небыстро, так как модель требует определённого времени на работу своих алгоритмов. Кроме того, не стоит забывать, что качество ответа будет зависеть как от самой модели, так и от ее параметров – в основном это температура и количество токенов. Если попробовать поиграться с этими значениями, то можно получить разные результаты.
Пример кода доступен тут. В ближайшее время я попробую развернуть бота в Yandex Cloud
.
Подписывайтесь на мой телеграм-канал. Буду рад любым вашим комментариям
Автор: franticticktick