В айти я уже давно, и в какой-то момент проектов стало сильно больше, чем я могу вывезти. Отказаться даже от одного из них — не вариант, деньги нужны катастрофически. Но после того как рабочий день снова закончился к 2–3 ночи, я начал думать, как освободить себе хотя пару часов на поспать и пожить.
Сначала завел таск-трекер, потом вставал пораньше, но кардинально ничего не поменялось.
Тогда залез в статистику экранного времени и увидел, что вишу в телеграме по 5-6 часов в день. И это не только личные переписки с друзьями, еще и коллеги спрашивают одно и тоже: «Подскажи, а что это за схема?, «Где тот файл?», «А где лежат все отчёты?». Сообщения приходят постоянно, даже ночью. Да и я не против помочь, но одно и то же объяснять по 10 раз — реально устал.
Тогда я и решил собрать личного чат-бота в Telegram на базе Gemma 3. Сделал так, чтобы он понимал опечатки, команды и контекст, быстро отвечал. В процессе добавил whitelist для аутентификации (чтобы не каждый мог спамить), шифрование логов и истории чатов (приватность на первом месте), сессионный контекст для персонализированных ответов и RAG-подход с автообновлением базы знаний через Git (теперь FAQ всегда свежий, без галлюцинаций). Плюс деплоили как systemd-сервис для автозапуска — никаких ручных рестартов.
В статье расскажу про Gemma 3 и её фичи, подготовку сервера на Ubuntu 24.04, установку Ollama, Python-приложения с улучшениями, настройку бота и доработки.
Я написал эту статью специально для блога Minervasoft. Компания помогает среднему и крупному бизнесу эффективно внедрять GenAI: объединяет все корпоративные знания в одном источнике — системе управления знаниями Minerva Knowledge — и с помощью авторской методологии делает так, чтобы статьи всегда оставались актуальными, структурированными и понятными. Это снижает риск галлюцинаций и повышает корректность ответов ИИ-агента до 94%.
Про Gemma 3 и её плюшки
Gemma 3 от Google DeepMind вышла в марте 2025-го, и я сразу взял версию на 12 миллиардов параметров. Звучит грамоздко, но на деле — компактная и удобная модель, которая легко запускается на обычном ноуте или сервере. Достаточно мощная для сложных запросов и не требует суперкомпьютера или облака. А с нашими доработками — шифрованием, RAG и автообновлением базы — она превращается в крутого личного ассистента, который реально может помочь.
Когда я выбирал модель для бота, список требований был коротким, но жёстким:
должен работать локально (чтобы данные не утекали в облако)
не тормозить,
понимать русский с опечатками
-
интегрироваться с Telegram через Python.
Gemma 3 подошла идеально: по скорости соперничает с облачными гигантами, но без их минусов. Хватит 16 ГБ RAM и нормального CPU (а с GPU — вообще летает). Она умеет отвечать на вопросы, генерировать код, анализировать тексты.
Как поможет чат-бот на Gemma 3
Представь: коллега пишет «Где инструкция по смене пароля?». Раньше — отвлекаешься, ищешь, объясняешь. Теперь бот мгновенно отвечает: «Вот ссылка на FAQ (из твоей базы), шаг 1: зайди в настройки...».
И это не шаблон — модель понимает контекст, проверяет историю чата (я добавлю сессионный контекст на последние 5-10 сообщений) и тянет данные из RAG (векторная база с Chroma, обновляемая через Git). Плюс, whitelist на user ID — только доверенные могут спросить, а логи шифруются Fernet, чтобы приватность была на уровне.
Ещё бот может автоматизировать FAQ (пароли, инструкции, сбросы с ссылками на источники), анализировать запросы с опечатками, генерировать скрипты (e.g., bash для сетевой проверки), настраивать стиль ответов («серьёзный» или «дружественный»).
Технические требования
Я тестировал Гемму на виртуалке с Ubuntu 24.04. Главное — локальность и отсутствие подписок платежей за запросы или риска, что сервис прикроют "по политическим причинам".
Что понадобится:
Сервер или комп с Ubuntu 24.04 (или похожей Linux-дистрибутивом; Windows тоже потянет, но с Docker проще на Linux).
16 ГБ RAM — комфортный минимум для Gemma 12B (с квантизацией хватит и 8 ГБ, но может притормаживать при RAG-запросах).
20–30 ГБ свободного места: модель ~20 ГБ, плюс ChromaDB для RAG и логи.
GPU опционально, но рекомендуется (NVIDIA с CUDA для ускорения; без него CPU справится, но медленнее).
Python 3.12+ (для бота, LangChain и async; venv для изоляции).
Docker для Ollama (обязательно, чтобы модель запускалась в контейнере).
Дополнительные пакеты: git (для автообновления FAQ), curl/net-tools (для проверок), build-essential (если компиляция нужна).
Для наших фич: pip-пакеты вроде langchain, langchain-huggingface, cryptography (шифрование), gitpython (Git-pull), apscheduler (scheduler обновлений), python-telegram-bot (Telegram API). Всё в requirements.txt — просто pip install -r. И systemd для деплоя как сервиса (автозапуск, рестарты).
Это подойдёт для личного использования или небольшой команды — без бюджета на сервера. Если сервер слабый, лучше поменять модель на 7B. Конечно 12B запустится, но даже добавив swap лучше мониторить с htop, т.к. ИИ кушает ресурсы.

Подготовка сервера для Gemma 3
Прежде чем бот заработает, настроим сервер — я взял виртуалку с Ubuntu 24.04, но подойдёт любой похожий Linux. Всё делаем от пользователя с sudo, чтобы не мучаться с правами.
Первый шаг — обновляем систему и ставим базовые пакеты:
sudo apt update && sudo apt upgrade -y
sudo apt install curl git build-essential python3 python3-pip net-tools docker.io -y
Это даст curl для проверок, git для репозиториев, build-essential на случай компиляции, python3 с pip для бота и docker для Ollama.
Проверяем ресурсы: free -h
для RAM (минимум 16 ГБ, иначе добавьте swap) и lscpu
для CPU (4+ ядер идеально). Если RAM мало, создаём swap 4 ГБ (я себе не делал, у меня 16гб RAM хватило за глаза):
sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
Создаём проект и venv:
mkdir ~/gemma-bot
cd ~/gemma-bot
python3 -m venv venv
source venv/bin/activate
Установка и запуск Ollama с Gemma 3
Теперь перейдём к главному — Ollama как open-source обёртка для Gemma. Она скачивает модель, запускает её и даёт API для интеграции. Python шлёт запрос — Ollama возвращает ответ.
Создаём директорию для данных Ollama:
mkdir -p ~/ollama
Запускаем в Docker:
docker run -d -v ~/ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama
Флаги: -d для фона, -v для персистентности моделей, -p для API-порта. С GPU добавьте --gpus all.
Проверяем: docker ps
(должен быть ollama), логи docker logs ollama
. Тест API: curl
http://localhost:11434
— ответ "Ollama is running". Порт занят? sudo netstat -tuln | grep 11434
.
Загружаем Gemma 3 12B (~20-25 ГБ, проверьте место в ~/ollama):
docker exec -it ollama ollama pull gemma3:12b
Запуск: docker exec -it ollama ollama run gemma3:12b
— интерактив для тестов. Задайте вопрос: "Как перезагрузить роутер?" — модель ответит.
Авто-rеstart: docker update --restart unless-stopped ollama
.
Оптимизация для слабого железа: GPU-check с nvidia-smi в контейнере, ограничьте queue -e OLLAMA_MAX_QUEUE=1
, увеличьте swap.
Теперь Ollama — приложение к боту, API на 11434 готов.

Создание Python-приложения для работы с Gemma 3
Сервер готов, Ollama с Gemma 3 крутится — пора к приложению, которое свяжет модель с Telegram. Беру Python для обработки запросов к Ollama API (порт 11434), интеграции с ботом, RAG для базы знаний и улучшений вроде шифрования, whitelist и истории чатов. Код гибкий, с комментариями для доработок.
Подготовка окружения: В venv ставим пакеты (из requirements.txt или вручную):
pip install requests python-telegram-bot python-dotenv cryptography gitpython apscheduler langchain langchain-community chromadb langchain-huggingface sentence-transformers nest-asyncio
Это для API-запросов, бота, env, шифрования, Git, scheduler, LangChain (RAG/Chroma) и эмбеддингов.
Нужно создать bot.py в ~/gemma-bot и вставить код ниже. Он инициализирует RAG, обновляет FAQ через Git, обрабатывает сообщения с контекстом и пишет зашифрованный лог.
import requests
import json
import os
from dotenv import load_dotenv
from telegram import Update
from telegram.ext import Application, CommandHandler, MessageHandler, filters, ContextTypes
from datetime import datetime
from cryptography.fernet import Fernet
import git
from git.exc import InvalidGitRepositoryError, GitCommandError
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from langchain_community.llms import Ollama
from langchain_community.vectorstores import Chroma
from langchain_huggingface import HuggingFaceEmbeddings # Updated import to avoid deprecation
from langchain_community.document_loaders import TextLoader, JSONLoader
from langchain.chains import RetrievalQA
from langchain.text_splitter import CharacterTextSplitter
import asyncio
import nest_asyncio # Added to allow nested asyncio loops
nest_asyncio.apply() # Apply patch for nested event loops
# Загружаем переменные из .env
load_dotenv()
# Конфигурация
OLLAMA_API_URL = os.getenv("OLLAMA_API_URL", "http://localhost:11434/api/generate").strip('"')
MODEL_NAME = os.getenv("MODEL_NAME", "gemma3:12b").strip('"')
BOT_TOKEN = os.getenv("BOT_TOKEN").strip('"')
ENCRYPT_KEY = os.getenv("ENCRYPT_KEY").strip('"')
REPO_URL = os.getenv("REPO_URL", "https://github.com/yourusername/project.git").strip('"') # Updated to new URL
ALLOWED_USERS = set(os.getenv("ALLOWED_USERS", "").strip('"').split(",")) # Whitelist user IDs
LOCAL_REPO_PATH = os.getenv("LOCAL_REPO_PATH", "/home/ubuntu/gemma-bot/faq_repo").strip('"')
CHROMA_DIR = os.getenv("CHROMA_DIR", "/home/ubuntu/gemma-bot/chroma_db").strip('"')
FAQ_FILES = os.getenv("FAQ_FILES", "faq.md,faq.json").strip('"').split(",") # As string in env
# Проверка переменных
if not BOT_TOKEN or not ENCRYPT_KEY:
raise ValueError("BOT_TOKEN или ENCRYPT_KEY не найдены в .env!")
if not ALLOWED_USERS:
print("Warning: ALLOWED_USERS не задан, доступ открыт для всех.")
# Директории
HISTORY_DIR = "/home/ubuntu/gemma-bot/history"
LOGS_DIR = "/home/ubuntu/gemma-bot/logs"
COUNTER_PATH = "/home/ubuntu/gemma-bot/counter.txt"
os.makedirs(HISTORY_DIR, exist_ok=True)
os.makedirs(LOGS_DIR, exist_ok=True)
os.makedirs(LOCAL_REPO_PATH, exist_ok=True)
os.makedirs(CHROMA_DIR, exist_ok=True)
# Глобальный timestamp для логов
BOT_START_TIMESTAMP = datetime.now().strftime('%Y%m%d_%H%M%S')
LOG_PATH = os.path.join(LOGS_DIR, f"log-{BOT_START_TIMESTAMP}.json.enc")
# Инициализация RAG
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
llm = Ollama(model=MODEL_NAME)
db = None
# Функция для обновления FAQ из Git и перезагрузки ChromaDB
async def update_faq():
try:
try:
repo = git.Repo(LOCAL_REPO_PATH)
repo.remotes.origin.pull()
log_message("INFO", "FAQ репозиторий обновлён из Git.")
except InvalidGitRepositoryError:
git.Repo.clone_from(REPO_URL, LOCAL_REPO_PATH)
log_message("INFO", "Репозиторий клонирован заново.")
except GitCommandError as e:
log_message("ERROR", f"Git ошибка: {str(e)}. Пропускаем обновление.")
return # Skip if clone/pull fails
# Загрузка документов
docs = []
for file in FAQ_FILES:
path = os.path.join(LOCAL_REPO_PATH, file.strip())
if os.path.exists(path):
if file.endswith(".json"):
loader = JSONLoader(path, jq_schema=".[] | .question + \" \" + .answer")
else:
loader = TextLoader(path, encoding="utf-8")
docs.extend(loader.load())
if docs:
splitter = CharacterTextSplitter(chunk_size=500, chunk_overlap=50)
split_docs = splitter.split_documents(docs)
global db
db = Chroma.from_documents(split_docs, embeddings, persist_directory=CHROMA_DIR)
db.persist()
log_message("INFO", "ChromaDB обновлена из FAQ.")
else:
log_message("WARNING", "Нет FAQ файлов в репозитории.")
except Exception as e:
log_message("ERROR", f"Ошибка обновления FAQ: {str(e)}")
# Функция для получения следующего номера чата
def get_next_chat_number():
if not os.path.exists(COUNTER_PATH):
with open(COUNTER_PATH, 'w') as f:
f.write('0')
with open(COUNTER_PATH, 'r+') as f:
num = int(f.read().strip() or '0') + 1
f.seek(0)
f.write(str(num))
f.truncate()
return num
# Функция для логирования (шифрованный JSON)
def log_message(level, message):
entry = {"time": datetime.now().isoformat(), "level": level, "message": message}
fernet = Fernet(ENCRYPT_KEY.encode())
data = []
if os.path.exists(LOG_PATH):
with open(LOG_PATH, 'rb') as f:
encrypted = f.read()
try:
decrypted = fernet.decrypt(encrypted)
data = json.loads(decrypted.decode('utf-8'))
except:
pass
data.append(entry)
json_str = json.dumps(data, ensure_ascii=False).encode('utf-8')
encrypted = fernet.encrypt(json_str)
with open(LOG_PATH, 'wb') as f:
f.write(encrypted)
# Функция для сохранения истории чата (шифрованная)
def save_chat_history(chat_data):
if 'history' not in chat_data or 'history_file' not in chat_data:
return
path = chat_data['history_file']
fernet = Fernet(ENCRYPT_KEY.encode())
json_str = json.dumps(chat_data['history'], ensure_ascii=False).encode('utf-8')
encrypted = fernet.encrypt(json_str)
with open(path, 'wb') as f:
f.write(encrypted)
# Функция для запроса к Gemma с RAG
def query_gemma(prompt):
try:
requests.get("http://localhost:11434", timeout=5)
except requests.ConnectionError:
error_msg = "Ошибка: Ollama не запущен или недоступен."
log_message("ERROR", error_msg)
return error_msg
try:
if db:
qa = RetrievalQA.from_chain_type(llm=llm, chain_type="stuff", retriever=db.as_retriever(search_kwargs={"k": 3}))
response = qa.run(prompt)
else:
# Fallback без RAG
payload = {"model": MODEL_NAME, "prompt": prompt, "stream": False, "options": {"temperature": 0.7, "max_tokens": 200}}
resp = requests.post(OLLAMA_API_URL, json=payload, timeout=120)
resp.raise_for_status()
response = resp.json().get("response", "Ошибка: нет ответа от модели")
return response.strip()
except Exception as e:
error_msg = f"Ошибка при обращении к Ollama/RAG: {str(e)}"
log_message("ERROR", error_msg)
return error_msg
# Обработчик /start
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
user_id = str(update.message.from_user.id)
if ALLOWED_USERS and user_id not in ALLOWED_USERS:
log_message("WARNING", f"Доступ запрещён для user_id={user_id}")
await update.message.reply_text("Доступ запрещён.")
return
chat_id = update.message.chat_id
if 'history_file' not in context.chat_data:
num = get_next_chat_number()
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
file_name = f"chat{num}-{timestamp}.json.enc"
path = os.path.join(HISTORY_DIR, file_name)
context.chat_data['history_file'] = path
context.chat_data['history'] = []
log_message("INFO", f"Новый чат начат: chat_id={chat_id}, user_id={user_id}, file={file_name}")
await update.message.reply_text("Привет! Я бот техподдержки на базе Gemma 3. Задавайте вопросы, и я помогу!")
save_chat_history(context.chat_data)
# Обработчик сообщений
async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
user_id = str(update.message.from_user.id)
if ALLOWED_USERS and user_id not in ALLOWED_USERS:
log_message("WARNING", f"Доступ запрещён для user_id={user_id}")
await update.message.reply_text("Доступ запрещён.")
return
chat_id = update.message.chat_id
user_message = update.message.text
if 'history_file' not in context.chat_data:
num = get_next_chat_number()
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
file_name = f"chat{num}-{timestamp}.json.enc"
path = os.path.join(HISTORY_DIR, file_name)
context.chat_data['history_file'] = path
context.chat_data['history'] = []
log_message("INFO", f"Новый чат начат без /start: chat_id={chat_id}, user_id={user_id}, file={file_name}")
log_message("INFO", f"Получено сообщение от user_id={user_id}, chat_id={chat_id}: {user_message}")
# Добавляем сообщение пользователя в историю
context.chat_data['history'].append({"sender": "user", "message": user_message, "timestamp": datetime.now().isoformat()})
# Формируем промпт с историей (последние 5 сообщений для контекста)
history_context = " ".join([f"{msg['sender']}: {msg['message']}" for msg in context.chat_data['history'][-5:]])
prompt = f"Ты — ассистент технической поддержки. История чата: {history_context}. " \
f"Клиент написал: '{user_message}'. " \
f"Дай краткий, профессиональный ответ на русском языке, используя контекст истории и базу знаний."
response = query_gemma(prompt)
# Добавляем ответ в историю
context.chat_data['history'].append({"sender": "bot", "message": response, "timestamp": datetime.now().isoformat()})
# Ограничиваем историю до 10 записей
if len(context.chat_data['history']) > 10:
context.chat_data['history'] = context.chat_data['history'][-10:]
# Сохраняем историю
save_chat_history(context.chat_data)
log_message("INFO", f"Отправлен ответ user_id={user_id}, chat_id={chat_id}: {response}")
await update.message.reply_text(response)
# Основная функция (async)
async def main():
log_message("INFO", "Бот запущен")
# Инициализация scheduler для автообновления FAQ
scheduler = AsyncIOScheduler()
scheduler.add_job(update_faq, 'interval', minutes=30)
scheduler.start()
# Начальное обновление FAQ с обработкой ошибок
try:
await update_faq()
except Exception as e:
log_message("ERROR", f"Ошибка при начальном обновлении FAQ: {str(e)}")
app = Application.builder().token(BOT_TOKEN).build() # Removed job_queue(None)
app.add_handler(CommandHandler("start", start))
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message))
print("Бот запущен...")
await app.run_polling()
if __name__ == "__main__":
asyncio.run(main())
Разбор кода:
Конфигурация: Env из .env, defaults для путей/URL.
RAG и Git: update_faq клонирует/pull'ит репозиторий, загружает FAQ в Chroma с эмбеддингами (sentence-transformers).
query_gemma: Проверяет Ollama, использует RAG если db готова, fallback на простой запрос.
Handlers: /start/init чата, handle_message с whitelist, историей (контекст в промпте), шифрованием.
Main: Async, scheduler для обновлений, polling для Telegram.
Настройка Telegram-бота
Бот готов в коде — теперь регистрируем его в Telegram через BotFather (@BotFather). Откройте чат и введите /newbot. Укажите имя (e.g., TechSupportGemmaBot) и username (заканчивается на bot, e.g., techsupportgemma_bot). BotFather выдаст токен — вроде 123456:ABC-DEF... Скопируйте и вставьте в .env как BOT_TOKEN=your_token_here.

Для теста добавьте бота в чат или группу.
Запустите python bot.py (или через systemd, о котором ниже). Отправьте /start — бот поздоровается. Задайте вопрос — получите ответ от Gemma с контекстом и RAG.

Оптимизация и доработки
Приложение работает, но я ещё добавил логирование с шифрованием (для приватности), контекст из истории чатов (персонализация), базу знаний с RAG и автообновлением через Git (точные ответы без галлюцинаций), whitelist на user ID (безопасность) и retry для Ollama (стабильность). Всё в том же bot.py — добавьте фрагменты ниже.
Я не буду писать весь код, он есть уже есть в статье полностью, но в общем укажу какие части за это отвечают.
Чтобы ИИ действительно помогал и правильно отвечал на вопросы, необходимо подготовить надёжный источник знаний. Для этого – объединить полезную информацию в одной системе и настроить процессы работы с ней так, чтобы данные всегда оставались актуальными, достоверными и непротиворечивыми.
В Minerva Knowledge можно создавать статьи всей командой, загружать любые файлы и документы и легко находить их с помощью умного поиска, даже если запрос написан с ошибками. А GenAI-ассистент Minerva Copilot, который при подготовке ответов опирается на статьи в Minerva Knowledge, даёт корректные ответы в 94% случаев. А при работе с неструктурированными источниками этот показатель снижается до 50%

Minerva Copilot встраивается в любую рабочую систему и даёт ответы с указанием источника.
Попробовать продукты Minervasoft
Шифрованное логирование: Импорт import logging
(но мы используем custom log_message с Fernet). В handlers: log_message("INFO", f"Сообщение: {user_message}")
.
Контекст истории: В handle_message добавьте историю в промпт (последние 5 сообщений) и сохраняйте в шифрованный JSON.
# Обработчик сообщений
async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
user_id = str(update.message.from_user.id)
if ALLOWED_USERS and user_id not in ALLOWED_USERS:
log_message("WARNING", f"Доступ запрещён для user_id={user_id}")
await update.message.reply_text("Доступ запрещён.")
return
chat_id = update.message.chat_id
user_message = update.message.text
if 'history_file' not in context.chat_data:
num = get_next_chat_number()
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
file_name = f"chat{num}-{timestamp}.json.enc"
path = os.path.join(HISTORY_DIR, file_name)
context.chat_data['history_file'] = path
context.chat_data['history'] = []
log_message("INFO", f"Новый чат начат без /start: chat_id={chat_id}, user_id={user_id}, file={file_name}")
log_message("INFO", f"Получено сообщение от user_id={user_id}, chat_id={chat_id}: {user_message}")
# Добавляем сообщение пользователя в историю
context.chat_data['history'].append({"sender": "user", "message": user_message, "timestamp": datetime.now().isoformat()})
# Формируем промпт с историей (последние 5 сообщений для контекста)
history_context = " ".join([f"{msg['sender']}: {msg['message']}" for msg in context.chat_data['history'][-5:]])
prompt = f"Ты — ассистент технической поддержки. История чата: {history_context}. " \
f"Клиент написал: '{user_message}'. " \
f"Дай краткий, профессиональный ответ на русском языке, используя контекст истории и базу знаний."
response = query_gemma(prompt)
# Добавляем ответ в историю
context.chat_data['history'].append({"sender": "bot", "message": response, "timestamp": datetime.now().isoformat()})
# Ограничиваем историю до 10 записей
if len(context.chat_data['history']) > 10:
context.chat_data['history'] = context.chat_data['history'][-10:]
# Сохраняем историю
save_chat_history(context.chat_data)
log_message("INFO", f"Отправлен ответ user_id={user_id}, chat_id={chat_id}: {response}")
await update.message.reply_text(response)
RAG с Git: update_faq на scheduler (каждые 30 мин pull из repo), Chroma для векторного поиска FAQ (LangChain). В query_gemma: используйте RetrievalQA если db готова.
Whitelist: В handlers проверяйте user_id в ALLOWED_USERS из .env.
Retry: В query_gemma сессия с Retry (3 попытки).
Деплой с systemd
Для автозапуска и рестартов деплоим как сервис. Создайте /etc/systemd/system/gemma-bot.service:
[Unit]
Description=Gemma Bot Service
After=network.target docker.service
Requires=docker.service
[Service]
User=ubuntu
WorkingDirectory=/home/ubuntu/gemma-bot
EnvironmentFile=/home/ubuntu/gemma-bot/.env
ExecStartPre=/bin/bash -c 'until curl --output /dev/null --silent --head --fail http://localhost:11434; do printf "."; sleep 1; done; echo "Ollama ready."'
ExecStart=/home/ubuntu/gemma-bot/venv/bin/python /home/ubuntu/gemma-bot/bot.py
ExecReload=/bin/kill -HUP $MAINPID
Restart=always
RestartSec=10
TimeoutStartSec=300
StandardOutput=journal
StandardError=journal
SyslogIdentifier=gemma-bot
LimitNOFILE=4096
MemoryMax=12G
CPUQuota=400%
PrivateTmp=true
ProtectSystem=strict
[Install]
WantedBy=multi-user.target
Активируйте: sudo systemctl daemon-reload
, sudo systemctl enable gemma-bot.service
, sudo systemctl start gemma-bot.service
. Логи: journalctl -u gemma-bot.service -f
.

Заключение
Вот и готов бот — локальный и умный.

Если у вас есть вопросы или идеи по доработкам, пишите в комментах, я с удовольствием отвечу. Может, я где-то заржавел (давно не кодил такие вещи руками), так что советы по оптимизации кода или фичам будут только в радость.
Также подписывайтесь на блог компании Minervasoft, где больше полезной информации про ИИ и спорные вопросы в найме, менеджменте и планировании.
Комментарии (2)
vrangel
01.08.2025 18:24Также странно, что бот техподдержки рекомендует обратиться... в техподдержку!
4wards1
Пока читал код, глаза лезли на лоб. Сколько там вы в айти? Пишете, что "давно" - это уже больше месяца или ещё нет?
Синхронные запросы через requests внутри асинхронных хэндлеров, ужасный грязный код и свалка из всего подряд в едином файле - кого вы обманываете? Код написан студентом и ChatGPT - в это ещё можно поверить. И то студент как будто не слишком усердный.