EKT Assistant
Чат-консультант для ekt.kz на русском и казахском: наличие по городам, цена и характеристики, честный ответ про сертификат, аналоги с обоснованием…
О проекте
Команда: 'til 3am
Кейс HackAlem.ai: Торговля — Электрокомплект · ekt.kz
Стадия: Рабочий MVP
Задача кейса: AI-консультант по каталогу ekt.kz: товары, аналоги, цены, остатки, корзина
Суть
Чат-консультант для ekt.kz на русском и казахском: наличие по городам, цена и характеристики, честный ответ про сертификат, аналоги с обоснованием, условия покупки и демо-корзина с подтверждением в пределах остатка.
Как устроено
Сообщение сначала разбирается детерминированно: артикул, синонимы, количество (в том числе словами, «екеуін»), город и намерение. LLM (Gemini через OpenAI-совместимый API) подключается, только если разбор ничего не нашёл, и для естественного казахского ответа; guard на числа следит, чтобы модель не меняла цифры. Ответ собирается из выгрузки ekt.kz (dataset/*.json). Агент возвращает только намерение «добавить N шт.», backend создаёт одноразовое предложение на 5 минут и повторно проверяет остаток перед записью.
Ключевые решения
- детерминированный разбор намерений и сущностей
- LLM-fallback для свободного текста
- number guard
- подбор аналогов с обоснованием
- одноразовое подтверждение корзины с TTL
- Playwright e2e-тесты
Особенности
Гибрид «сначала правила, LLM только при необходимости» с защитой цифр от галлюцинаций. Противоречие в данных каталога (160 А в названии и 250 А в свойствах) показывается пользователю. README содержит пошаговую проверку пяти требований кейса и попытки обойти подтверждение.
Стек
Python · FastAPI · React · TypeScript · Vite · Playwright
Модели
Google Gemini · NVIDIA NIM (адаптер)
Проект создан за 5 часов на финале хакатона HackAlem.ai (23 сентября 2026, Астана). Код — BAITC-Hacks/hack-682bab9a-til-3am.
README
README.mdEKT Assistant — ИИ-консультант для ekt.kz
HackAlem AI · кейс ТОО «Электрокомплект» · команда ’til 3am
Чат-ассистент для интернет-магазина электротехники. Покупатель пишет свободным текстом на русском или казахском и сразу получает:
- наличие по городу, цену и характеристики;
- честный ответ про сертификат;
- аналоги с объяснением, если товара нет;
- условия покупки;
- добавление в корзину, но только после явного подтверждения и не больше остатка.

Содержание
- Быстрый запуск
- Как проверить: 5 обязательных требований
- Казахский язык и мобильная версия
- Архитектура
- Безопасность и достоверность
- Данные
- Тесты
- Ограничения
- Структура репозитория
Быстрый запуск
Нужны Python 3.10+ и Node.js 18+. Запуск занимает около 3 минут. Команды даны для Windows PowerShell, для macOS и Linux они в комментариях.
1. Backend (терминал 1)
git clone https://github.com/BAITC-Hacks/hack-682bab9a-til-3am.git
cd hack-682bab9a-til-3am
python -m venv .venv
.venv\Scripts\activate # macOS/Linux: source .venv/bin/activate
python -m pip install -r backend/requirements.txt
python -m uvicorn app.main:app --app-dir backend --host 127.0.0.1 --port 8000
Проверка: откройте http://127.0.0.1:8000/api/v1/health. Должно быть {"status":"ok","products_loaded":40}.
2. Frontend (терминал 2)
cd frontend
npm install
Copy-Item .env.example .env.local # macOS/Linux: cp .env.example .env.local
npm run dev
Откройте http://127.0.0.1:5173. Vite сам перенаправляет запросы /api на backend.
3. LLM (необязательно, нужен для казахского)
Без ключа ассистент полностью работает на русском в детерминированном режиме. Чтобы включить понимание казахского и живой речи:
- Получите бесплатный ключ Google Gemini: https://aistudio.google.com/apikey
- Создайте файл
backend/.env:LLM_BASE_URL=https://generativelanguage.googleapis.com/v1beta/openai LLM_API_KEY=ваш_ключ LLM_TIMEOUT=12 - Перезапустите backend.
Подходит любой OpenAI-совместимый провайдер (Groq, OpenRouter, NVIDIA), примеры в backend/.env.example.
Почему Gemini, а не NVIDIA. Сначала мы подключали LLM через NVIDIA NIM: адаптер и логирование ошибок сохранились в истории коммитов. Но ключ NVIDIA, который был у команды, во время хакатона так и не заработал, и ответов от модели мы не получили. Поэтому переключились на Google Gemini:
- бесплатный ключ получается за минуту;
- модель хорошо понимает казахский и небрежный живой текст;
- у Gemini есть OpenAI-совместимый API, поэтому менять код клиента не пришлось.
Клиент по-прежнему провайдер-независимый. Чтобы вернуть NVIDIA, достаточно указать в backend/.env значения LLM_BASE_URL=https://integrate.api.nvidia.com/v1, LLM_MODEL и LLM_API_KEY.
Если что-то не работает
| Симптом | Решение |
|---|---|
| Чат отвечает про «DEMO-BREAKER», а не про реальные товары | Нет frontend/.env.local: выполните Copy-Item .env.example .env.local в папке frontend и перезапустите npm run dev |
| «Сервер недоступен (404/502)» | Backend не запущен на порту 8000 (шаг 1) |
| Порт 8000 занят | Закройте предыдущий запуск uvicorn или используйте другой порт и поменяйте его в frontend/vite.config.ts |
| Казахский ответ пришёл на русском | LLM не настроена или бесплатный тариф Gemini перегружен. Ответ всё равно верный: он из данных |
Как проверить: 5 обязательных требований
Вводите запросы в чат по порядку, в одной вкладке. Чтобы начать заново с пустой корзиной, обновите страницу.
1. Наличие, характеристики, сертификат
Ввести: Есть 027228 в Алматы?
Ожидается: цена 64 920 ₸, 5 шт. в Алматы, характеристики. В данных каталога номинальный ток указан по-разному: 160 А в названии и 250 А в свойствах. Ассистент не скрывает противоречие, а показывает предупреждение.

Дальше:
а в Нур-Султане?→ 8 шт. Ассистент помнит товар из контекста диалога.покажи сертификат→ «файл сертификата в данных каталога не приложен» и ссылка на карточку. Ассистент ничего не выдумывает.
2. Аналоги, если товара нет
Ввести: Нужен 027230
Ожидается: «нет в наличии» и 2 аналога. На каждой карточке обоснование: что совпадает (тип, ток, серия, полюса), что отличается (отключающая способность), сколько в наличии и напоминание проверить совместимость.

3. Условия покупки
Ввести: Условия доставки и оплаты? или минимальная партия?
Ожидается: оплата, доставка и минимальная партия. В выгрузке ekt.kz этих условий нет, поэтому это условия демомагазина, и ответ помечает это явно.

4. Корзина только после явного подтверждения и в пределах остатка
Ввести: Есть 027228 в Алматы?, затем добавь 2 штуки
Ожидается: предложение на 2 шт. и 129 840 ₸ с кнопкой «Да, добавить». Корзина пока не изменилась.

Подтвердить: нажать «Да, добавить» или написать да, добавь. Только теперь товар попадает в корзину.
Проверка остатка: добавь 4 штуки → отказ: «доступно 5 шт., в корзине уже 2 шт.»

Попробуйте обойти:
дабез предложения → «нечего подтверждать»;- повторное «да» → количество не удваивается;
- два предложения подряд → подтверждается только последнее.
5. Ссылка на корзину
После подтверждения появляется панель корзины: позиции, сумма и ссылка «Перейти к корзине».

Сводка
| # | Требование кейса | Запрос для проверки | Статус |
|---|---|---|---|
| 1 | Наличие, характеристики, сертификат | Есть 027228 в Алматы?, покажи сертификат | ✅ |
| 2 | Аналог с обоснованием при нулевом остатке | Нужен 027230 | ✅ |
| 3 | Оплата, доставка, минимальная партия | Условия доставки и оплаты? | ✅ (демо-условия) |
| 4 | Корзина только после «да, добавь», не больше остатка | добавь 2 штуки → да, добавь → добавь 4 штуки | ✅ |
| 5 | Прямая ссылка на корзину | после подтверждения | ✅ (демокорзина) |
Казахский язык и мобильная версия
Казахский (опционально, требуется LLM)
Ввести по очереди:
Сәлеметсіз бе! 027228 Астанада бар ма?екеуін себетке қосыңызшыиә, қосыңыз
Ассистент понимает казахский (в том числе числа словами: «екеуін» = 2), отвечает по-казахски и добавляет товар после «иә». Цифры в ответе те же, что в каталоге.

Мобильная версия
Вёрстка адаптивная. Автотест проверяет ширину 390 px (iPhone): горизонтальной прокрутки нет. Проверить вручную можно так: F12 → значок телефона.
Архитектура
flowchart LR
U[Покупатель] --> UI[Чат<br/>React + TypeScript + Vite]
UI -->|/api| API[FastAPI<br/>backend/app/main.py]
API --> ORCH[Оркестратор<br/>assistant/orchestrator.py]
ORCH --> SEARCH[Поиск<br/>retrieval.py]
ORCH --> ANALOG[Аналоги<br/>analogs.py]
ORCH -. только разбор текста<br/>и перевод ответа .-> LLM[Gemini<br/>llm_client.py]
SEARCH --> CAT[Каталог и остатки<br/>catalog.py / inventory.py]
ORCH --> FAQ[Условия покупки<br/>faq.py]
API --> CART[Корзина<br/>cart/adapter.py]
CART --> INV2[Повторная проверка остатка]
CAT --> DATA[(dataset/*.json<br/>выгрузка ekt.kz)]
CAT --> DEMO[(demo_data/*.json<br/>синтетика, помечена)]
Как обрабатывается сообщение:
- Детерминированный разбор (мгновенно): артикул, код производителя, слова из названия с синонимами («автомат» → «АВ»), количество (в том числе «две штуки», «екеуін»), город («в Астане» → склад «Нур-Султан»), намерение (добавить, аналог, сертификат, условия).
- LLM подключается только если нужна: если детерминированный разбор ничего не нашёл, Gemini превращает живую фразу в поисковый запрос. Казахский ответ Gemini переписывает по-человечески.
- Ответ собирается только из данных: цена, остаток, характеристики, аналоги.
- Корзина: агент возвращает только намерение «добавить N шт.». Backend создаёт одноразовое предложение, ждёт подтверждения и повторно проверяет остаток.
Подробнее: docs/ARCHITECTURE.md.
API
| Метод | Путь | Назначение |
|---|---|---|
| GET | /api/v1/health | Проверка сервера |
| POST | /api/v1/sessions | Новая анонимная сессия |
| POST | /api/v1/sessions/{id}/messages | Сообщение в чат → ответ, карточки товаров, предложение добавить; при текстовом «да» — обновлённая корзина |
| POST | /api/v1/sessions/{id}/confirmations | Создать предложение (кнопка «Добавить» на карточке) |
| POST | /api/v1/sessions/{id}/confirmations/{cid}/confirm | Подтвердить предложение (кнопка «Да, добавить») |
| GET | /api/v1/sessions/{id}/cart | Текущая корзина и ссылка на неё |
Интерактивная документация: http://127.0.0.1:8000/docs
Безопасность и достоверность
| Требование кейса | Как обеспечено |
|---|---|
| Корзина не меняется без подтверждения | Агент не имеет доступа к корзине. Предложение одноразовое, живёт 5 минут и привязано к сессии, товару и количеству. Текстом подтверждается только последнее предложение |
| Защита от несанкционированного изменения | Чужая сессия не может подтвердить предложение (HTTP 409). Повторное подтверждение не удваивает количество |
| Не больше остатка | Остаток проверяется при создании предложения и ещё раз перед записью, с учётом того, что уже лежит в корзине |
| Недостоверные цены и наличие | Числа берутся только из каталога. Все цифры в ответе LLM сверяются с данными: если модель изменила или добавила число, её ответ отбрасывается. Неизвестный остаток показывается как «не уточнено», а не 0. Противоречия в данных (160/250 А) показываются |
| Объяснимость аналогов | В карточке перечислено, что совпадает, что отличается, сколько в наличии, и есть напоминание проверить совместимость |
| Платёжные данные | Не запрашиваются и не хранятся. Об этом написано в интерфейсе и в ответах об оплате |
| Приватность | Сессия анонимная, персональные данные не собираются. Ключ LLM хранится только на сервере в backend/.env, в git не попадает |
| Текст каталога и сообщения клиента как инструкции | Не выполняются: LLM получает их как данные, а цены и количество из сообщения клиента игнорируются |
Данные
| Файл | Что внутри | Источник |
|---|---|---|
dataset/api_products.json, api_products_page2.json | 40 товаров: название, артикул, цена, фото, ссылка | Выгрузка ekt.kz от партнёра |
dataset/api_products_detail.json | Товар 515291 (027228): характеристики и остатки по 24 складам | Выгрузка ekt.kz от партнёра |
demo_data/stock_overrides.json | Остатки для 4 товаров, чтобы показать сценарий «нет в наличии → аналог» | Синтетика, в интерфейсе помечена «демо» |
demo_data/faq.json | Оплата, доставка, минимальная партия, возврат (с ключевыми словами на русском и казахском) | Допущение демомагазина, помечено в ответе |
Кейс разрешает синтетические данные с сохранением структуры полей. Реальные данные выгрузки не изменяются и не смешиваются с синтетикой.
Тесты
# Backend: 18 тестов по всем обязательным требованиям (без LLM, ключ не нужен)
python -m pip install -r backend/requirements-dev.txt
python -m pytest backend/tests -q
# Frontend e2e: 5 сценариев в браузере, включая мобильную вёрстку (нужен запущенный backend)
cd frontend
$env:E2E_LIVE="1"
$env:CHROME_PATH="C:\Program Files\Google\Chrome\Application\chrome.exe" # или: npx playwright install
npx playwright test tests/live.spec.ts
Скриншоты для README пересоздаются командой $env:SCREENSHOTS="1"; npx playwright test tests/readme-screenshots.spec.ts.
Ограничения
- Данные. Реальные остатки есть только у одного товара из 40, у остальных наличие «не уточнено». Сертификатов и условий покупки в выгрузке нет.
- Корзина демонстрационная. Она хранится в памяти сервера и сбрасывается при перезапуске. Настоящая корзина ekt.kz подключается реализацией
CartAdapter(cart/adapter.py) без изменения логики подтверждения. - Вложения (фото, Excel, Word, PDF) не реализованы.
- Сопутствующие товары не показываются: в поле
RECOMMENDуказаны товары, которых нет в выгрузке, а выдумывать их карточки мы не стали. - История для авторизованных пользователей не реализована: в прототипе нет авторизации.
- Эскалация на менеджера частичная: при неизвестных данных ассистент советует обратиться к менеджеру, но диалог менеджеру не передаётся.
- LLM на бесплатном тарифе иногда отвечает медленно. В этом случае приходит ответ из данных на русском, функциональность не ломается.
Структура репозитория
backend/
app/
main.py HTTP API, сессии, подтверждение корзины
catalog.py загрузка каталога, поиск, синонимы
inventory.py остатки по складам и городам
faq.py условия покупки
cart/adapter.py MockCartAdapter: предложение → подтверждение → проверка остатка
assistant/
orchestrator.py логика ассистента: намерения, ответы, аналоги, корзина
retrieval.py поиск товаров по артикулу, коду и названию
analogs.py объяснимый подбор аналогов
llm_client.py OpenAI-совместимый клиент (Gemini) и проверка чисел
tests/ автотесты backend
.env.example пример настроек LLM
frontend/
src/main.tsx интерфейс чата, карточки, корзина
src/api.ts работа с API
tests/ e2e-тесты Playwright
dataset/ выгрузка ekt.kz (40 товаров + 1 подробная карточка)
demo_data/ синтетические остатки и условия демомагазина
docs/
ARCHITECTURE.md подробная архитектура и контракты
screenshots/ скриншоты из README
Авторы коммитов
История коммитов
- 2026-09-23 17:59docs: explain switch from NVIDIA to GeminiDinara Abdukhamitova
- 2026-09-23 17:57Merge branch 'main' of https://github.com/BAITC-Hacks/hack-682bab9a-til-3am into integration/demoDinara Abdukhamitova
- 2026-09-23 17:57test(frontend): live e2e demo scenario and mobile layout checkDinara Abdukhamitova
- 2026-09-23 17:56docs: drop demo-script reference from READMEDinara Abdukhamitova
- 2026-09-23 17:52test: configure backend test importsDaniyaSeitzhaparova
- 2026-09-23 17:52test: cover five required assistant scenariosDaniyaSeitzhaparova
- 2026-09-23 17:52test: add backend test dependenciesDaniyaSeitzhaparova
- 2026-09-23 17:49chore: default frontend env to live mode, drop dead LLM helperDinara Abdukhamitova
Все коммиты (52)
- 2026-09-23 17:47docs: jury-facing README with setup, verification steps and screenshotsDinara Abdukhamitova
- 2026-09-23 17:35Merge remote-tracking branch 'origin/feature/backend' into feature/backendKurmanay Zhanel
- 2026-09-23 17:34Improve README for project demoKurmanay Zhanel
- 2026-09-23 17:34feat: Gemini fallback chain, Kazakh quantities and fast deterministic pathDinara Abdukhamitova
- 2026-09-23 17:29Initial backend commitKurmanay Zhanel
- 2026-09-23 17:15feat: LLM for Russian/Kazakh understanding and natural answers with number guardDinara Abdukhamitova
- 2026-09-23 17:08feat: confirm cart by text, refresh UI labels and READMEDinara Abdukhamitova
- 2026-09-23 16:59merge: NVIDIA model selection and fallback logging from feature/backendDinara Abdukhamitova
- 2026-09-23 16:51fix: city-aware product cards and honest cart refusal messageDinara Abdukhamitova
- 2026-09-23 16:49chore: select available NVIDIA chat modelDaniyaSeitzhaparova
- 2026-09-23 16:46feat: analogs, demo FAQ, certificates, city stock and cart limitsDinara Abdukhamitova
- 2026-09-23 16:41chore: use available NVIDIA model in env exampleDaniyaSeitzhaparova
- 2026-09-23 16:35style(frontend): restore previous green paletteDinara Abdukhamitova
- 2026-09-23 16:34fix: log NVIDIA adapter fallback reasonDaniyaSeitzhaparova
- 2026-09-23 16:30fix(frontend): recover expired chat sessionsDinara Abdukhamitova
- 2026-09-23 16:28fix(agent): avoid repeating previous product for unrelated queriesDinara Abdukhamitova
- 2026-09-23 16:19merge: update backend conflict labels and NVIDIA fallbackDinara Abdukhamitova
- 2026-09-23 16:18feat: add NVIDIA LLM adapter with fallbackDaniyaSeitzhaparova
- 2026-09-23 16:16Normalize product conflict warning labelsKurmanay Zhanel
- 2026-09-23 16:13fix(frontend): translate catalog conflict warningDinara Abdukhamitova
- 2026-09-23 16:13Merge remote-tracking branch 'origin/feature/backend' into feature/backendKurmanay Zhanel
- 2026-09-23 16:13Add optional NVIDIA client configurationKurmanay Zhanel
- 2026-09-23 15:50fix: parse quantity from follow-up messagesDaniyaSeitzhaparova
- 2026-09-23 15:47feat(frontend): handle live pending confirmationsDinara Abdukhamitova
- 2026-09-23 15:45fix: resolve follow-up product from chat historyDaniyaSeitzhaparova
- 2026-09-23 15:39merge: update backend live confirmation flowDinara Abdukhamitova
- 2026-09-23 15:34merge: assemble backend AI and frontend demoDinara Abdukhamitova
- 2026-09-23 15:33merge: integrate backend and assistant servicesDinara Abdukhamitova
- 2026-09-23 15:28feat(frontend): add confirmed mock cart flowDinara Abdukhamitova
- 2026-09-23 15:24feat(frontend): connect backend v1 assistant APIDinara Abdukhamitova
- 2026-09-23 15:23Expose pending cart confirmation in messagesKurmanay Zhanel
- 2026-09-23 15:20feat: add assistant MVP orchestrationDaniyaSeitzhaparova
- 2026-09-23 15:18Add confirmed mock cart flowKurmanay Zhanel
- 2026-09-23 15:13style(frontend): align UI palette with EKT storefrontDinara Abdukhamitova
- 2026-09-23 15:12Connect assistant handler to backend servicesKurmanay Zhanel
- 2026-09-23 15:11feat: add assistant MVP orchestrationDaniyaSeitzhaparova
- 2026-09-23 15:10Add assistant module boundariesKurmanay Zhanel
- 2026-09-23 15:08Add backend contracts and data repositoriesKurmanay Zhanel
- 2026-09-23 15:02Update dataset directory pathKurmanay Zhanel
- 2026-09-23 14:54Merge branch 'main' of github.com:BAITC-Hacks/hack-682bab9a-til-3amKurmanay Zhanel
- 2026-09-23 14:52refactor(frontend): use synthetic public demo catalogDinara Abdukhamitova
- 2026-09-23 14:47Add initial assistant prototypeKurmanay Zhanel
- 2026-09-23 14:45docs: update project READMEDaniyaSeitzhaparova
- 2026-09-23 14:44feat(frontend): initialize chat interfaceDinara Abdukhamitova
Структура репозитория (60 файлов)
.gitignore README.md backend/.env.example backend/app/__init__.py backend/app/assistant/__init__.py backend/app/assistant/analogs.py backend/app/assistant/contracts.py backend/app/assistant/llm_client.py backend/app/assistant/nvidia.py backend/app/assistant/orchestrator.py backend/app/assistant/retrieval.py backend/app/cart/__init__.py backend/app/cart/adapter.py backend/app/catalog.py backend/app/contracts.py backend/app/faq.py backend/app/inventory.py backend/app/main.py backend/requirements-dev.txt backend/requirements.txt backend/tests/conftest.py backend/tests/test_assistant.py dataset/api_products.json dataset/api_products_detail.json dataset/api_products_page2.json demo_data/faq.json demo_data/stock_overrides.json docs/ARCHITECTURE.md docs/screenshots/01-start.png docs/screenshots/02-stock-specs.png docs/screenshots/03-proposal.png docs/screenshots/04-cart.png docs/screenshots/05-stock-limit.png docs/screenshots/06-analog.png docs/screenshots/07-terms.png docs/screenshots/08-kazakh.png docs/screenshots/09-mobile.png frontend/.env.example frontend/.gitignore frontend/PROMPT.md frontend/README.md frontend/index.html frontend/package-lock.json frontend/package.json frontend/playwright.config.ts frontend/src/App.tsx frontend/src/api.ts frontend/src/cart.css frontend/src/demoCatalog.ts frontend/src/env.d.ts frontend/src/main.tsx frontend/src/style.css frontend/src/styles.css frontend/src/types.ts frontend/src/vite-env.d.ts frontend/tests/chat.spec.ts frontend/tests/live.spec.ts frontend/tests/readme-screenshots.spec.ts frontend/tsconfig.json frontend/vite.config.ts
Обсуждение 0 комментариев
Войдите, чтобы комментироватьПока нет комментариев — будьте первым.