- BrainTools - https://www.braintools.ru -
Представьте консультанта в DNS/Ситилинке, который не навязывает «вот этот блок питания потому что остался на складе», а спокойно объясняет, чем один БП лучше другого под ваш билд, помнит, о чём вы спрашивали раньше, и ещё просит вежливый фидбек.
Я решил собрать такого консультанта в виде Telegram-бота «Кремний» — RAG-бота по железу на бесплатных инструментах: Telegram Bot API, Groq (Llama 3.1 8B), sentence-transformers и чуть-чуть боли [1] с NumPy и Pterodactyl.
Хотелось не просто «ещё одного бота», а что-то ближе к человеческому диалогу: минимум кнопок, максимум свободного текста, плюс нормальный контекст про товары и ответы, основанные на фактах, а не на фантазиях модели.
Требования к «Кремнию» получились такие:
Отвечать на вопросы про комплектующие: видеокарты, материнки, блоки питания, корпуса и т.п. (описания, совместимость, сценарии).
Уметь говорить про цены и основные параметры (TDP, форм-фактор, количество линий PCIe и т.д.) в рамках небольшой базы знаний.
Работать как «почти человек»: пользователь пишет руками, без меню и клавиатур; бот парсит естественный язык и помогает.
Поддерживать RAG: вытаскивать из своей базы релевантные документы и кормить их LLM, чтобы ответы были опорой на данные.
Собирает фидбек, но не навязчиво — отдельной командой, с персональным ответом от LLM на отзыв.
Чтобы уложиться в «бесплатный/учебный» формат, собрал следующий стек:
Telegram-слой: python-telegram-bot 22.5, async-обвязка вокруг Bot API, ApplicationBuilder, MessageHandler, /start, /help, /feedback.
Генерация: Groq API c моделью llama-3.1-8b-instant через Python-клиент groq (OpenAI-совместимый протокол, бесплатный фритир для прототипов).
RAG: эмбеддинги из sentence-transformers (all-MiniLM-L6-v2) + простой векторный поиск по косинусу через numpy.
База знаний: вручную собранный mini-каталог комплектующих (материнские платы, видеокарты, БП, корпуса и т.д.) + блоки про доставку и гарантию.
Деплой: локально — обычный venv; в проде — контейнер на Pterodactyl с requirements.txt и парой сюрпризов от NumPy 2.x и PyTorch.
Компоненты общаются между собой довольно тривиально: Telegram даёт текст → RAG достаёт релевантные документы → LLM отвечает в стиле «Кремния» → опционально запрашиваем отзыв через /feedback.
Сухой консультант по железу — это скучно; на Хабре такие боты долго не живут даже в личке.
Поэтому для системного промпта зашил персоналию:
«Ты — Кремний, виртуальный консультант по железу и сборкам ПК».
Общается на «ты», без токсичности, но с лёгким ироничным тоном (в духе знакомого продавана, который сам собирает себе NAS по ночам).
Делает отсылки к типичным сценариям: «собираю бюджетный билд для шутеров», «тихий домашний NAS», «комп под Stable Diffusion».
Жёстко фиксирован: «если чего-то нет в базе знаний — честно скажи, что не знаешь, и не придумывай характеристики».
Это важно: LLM по умолчанию любит «додумывать», а RAG-боту по железу простительно не всё — особенно, когда дело доходит до совместимости БП и видеокарты.
Базу сделал в виде обычного Python-списка словарей KNOWLEDGE_ITEMS в knowledge_base.py.
Каждый элемент — это либо product, либо блок shipping/warranty, например:
{
"id": "gpu_4070_dual",
"type": "product",
"category": "видеокарты",
"name": "GeForce RTX 4070 Dual",
"description": (
"RTX 4070 с двухвентиляторным охлаждением, TDP около 200 Вт. "
"Подходит для сборок с БП от 650 Вт и хорошей вентиляцией корпуса."
),
"sizes": ["2.5-слота", "длина 270 мм"],
"price_rub": 65000,
"chipset": "NVIDIA RTX 4070",
"power": "1x 8-pin",
}
Такое представление удобно и для RAG (есть чистый текст), и для будущего расширения (можно потом вынести в JSON/БД без переписывания логики).
RAG-часть лежит в bot.py и использует sentence-transformers/all-MiniLM-L6-v2:
emb_model = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")
DOC_TEXTS = [doc_to_text(item) for item in KNOWLEDGE_ITEMS]
DOC_EMB = emb_model.encode(DOC_TEXTS, normalize_embeddings=True)
DOC_EMB = np.array(DOC_EMB, dtype="float32")
doc_to_text собирает внятный текст из полей продукта: название, категория, описание, питалово, форм-фактор, цены и т.п., чтобы модель embeddings не ломала голову над сырым JSON.
Дальше всё по канону:
def retrieve_relevant_docs(query: str, top_k: int = 3):
query_emb = emb_model.encode([query], normalize_embeddings=True)[0]
sims = DOC_EMB @ query_emb # косинус, т.к. всё уже нормализовано
top_idx = np.argsort(-sims)[:top_k]
...
Так как база небольшая (десятки/сотни элементов), никакой FAISS, Chroma и прочий зоопарк здесь не нужен — обычный numpy вполне достаточно.
Чтобы не упираться в платный OpenAI и одновременно не поднимать локальную LLM, взял Groq: он даёт быстрый доступ к Llama 3.1 8B/70B через OpenAI совместимый API.
Инициализация клиента элементарна:
from groq import Groq
groq_client = Groq(api_key=os.getenv("GROQ_API_KEY"))
LLM_MODEL = "llama-3.1-8b-instant"
Вызов чата тоже классический:
completion = groq_client.chat.completions.create(
model=LLM_MODEL,
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "system", "content": rag_context_block},
{"role": "user", "content": user_message},
],
temperature=0.4,
max_tokens=800,
)
answer = completion.choices[0].message.content.strip()
Бонус Groq — для учебных и pet-проектов этого объёма бесплатного лимита хватает с запасом, а задержки по сравнению с OpenAI заметно меньше.
С точки зрения [2] Telegram бот — обычное приложение на python-telegram-bot 22.5:
/start — приветствие и краткая инструкция: как задавать вопросы, какие темы «Кремний» покрывает.
/help — краткое повторение [3] возможностей плюс примеры запросов (про «тихий билд в mini-ITX», «комп для ML» и т.п.).
/feedback — запуск режима обратной связи; следующее сообщение пользователя считается отзывом.
Все остальные текстовые сообщения без слэшей идут в handle_message, который решает, что делать: ожидать отзыв или обрабатывать запрос через RAG+LLM.
Кнопок по минимуму: никакой клавиатуры с «Узнать цену», «Показать доставку» — пользователь пишет свободным текстом, а задача бота — понять, что имелось в виду (как минимум в пределах базы знаний).
Первую версию я сделал по ТЗ: бот после каждого ответа просил оценить общение. Выглядело это как надоедливый поп-ап на сайте:
Пользователь: «Собери мне билд под 100к» → бот отвечает → сразу «Поставь оценку от 1 до 5».
В реальном сценарии это то, за что хочется снижать карму, а не повышать. Поэтому в версии для «Хабра» логика [4] другая:
Фидбек запрашивается только, если пользователь сам вызывает /feedback.
Бот хранит последнюю реплику пользователя и свой ответ в USER_STATE[user_id].
Когда приходит отзыв, отдельный вызов LLM анализирует, что было в диалоге, и генерит человеческую благодарность/извинение/ответ.
Код примерно такой:
USER_STATE = {
user_id: {
"awaiting_feedback": bool,
"last_user_message": str,
"last_bot_answer": str,
}
}
async def feedback_command(update, context):
state = USER_STATE.setdefault(user_id, {...})
if not state["last_bot_answer"]:
await update.message.reply_text(
"Мы ещё толком не пообщались — сначала задай вопрос, "
"а потом уже можно оценить беседу."
)
return
state["awaiting_feedback"] = True
await update.message.reply_text(
"Напиши, пожалуйста, короткий отзыв о нашей беседе."
)
Заодно такая схема хорошо демонстрирует на собеседовании, как можно использовать LLM не только «для ответов», но и для обработки мета-информации (фидбек, теги, аннотации).
Отдельная мини-история достойна раздела «Что пошло не так».
Сначала я честно сделал pip freeze на локальной машине и скормил этот requirements.txt Pterodactyl-контейнеру.
В результате контейнер радостно начал ставить torch==2.9.1 и заодно потащил за собой кучу nvidia-* пакетов для CUDA, пока не умер с Exit code: 137 и Out of memory: true.
Лекарство: оставить только то, что реально нужно, и явно указать CPU-сборку торча:
python-telegram-bot==22.5
groq==0.36.0
python-dotenv==1.2.1
numpy==1.26.4
sentence-transformers==3.0.1
--extra-index-url https://download.pytorch.org/whl/cpu
torch==2.2.2
Так удалось избавиться от лишних CUDA-зависимостей и ужаться до адекватного размера по памяти [5].
Следующая засада выглядела так:
A module that was compiled using NumPy 1.x cannot be run in NumPy 2.3.5...
RuntimeError: Numpy is not available
Это «подарок» от NumPy 2.x: многие бинарные модули (включая часть PyTorch-сборок) собраны против NumPy 1.x, и при наличии NumPy 2.3.5 PyTorch просто считает, что NumPy у тебя «нет».
Решение максимально приземлённое и советуется во всех обсуждениях: зафиксировать numpy, обычно 1.26.4, как в примере выше.
После понижения NumPy:
Предупреждение исчезло.
sentence-transformers спокойно делает emb.numpy() внутри .encode, и бот успешно считает эмбеддинги при старте.
Даже в таком «учебном» виде у бота уже есть чем похвастаться: живая персоналия, RAG по базе железа, фидбек через LLM и продакшен-подобный деплой на Pterodactyl.
Но если захочется развивать идею, вот несколько направлений:
Нормальный векторный стор: FAISS или аналог, чтобы держать сотни/тысячи позиций, а не десятки.
Переранкер: поверх MiniLM добавить reranking (например, через тот же LLM), чтобы ещё точнее выбирать контекст.
Диалоговая память: хранить не только последний вопрос/ответ, а сессию, и давать LLM больше истории общения (особенно важно, когда пользователь уточняет: «а блок питания к нему подойдёт?»).
Онлайн-цены и наличие: завести лёгкий интеграционный слой с API магазина (если это уже не учебный, а боевой проект).
Автор: Efrosim123
Источник [6]
Сайт-источник BrainTools: https://www.braintools.ru
Путь до страницы источника: https://www.braintools.ru/article/22326
URLs in this post:
[1] боли: http://www.braintools.ru/article/9901
[2] зрения: http://www.braintools.ru/article/6238
[3] повторение: http://www.braintools.ru/article/4012
[4] логика: http://www.braintools.ru/article/7640
[5] памяти: http://www.braintools.ru/article/4140
[6] Источник: https://habr.com/ru/articles/969740/?utm_source=habrahabr&utm_medium=rss&utm_campaign=969740
Нажмите здесь для печати.