О проекте
Команда: nurseitai
Кейс HackAlem.ai: Креативные индустрии — Firebird
Стадия: Рабочий MVP
Задача кейса: «#79-lite»: подобрать до 3 подрядчиков для мероприятия и объяснить выбор
Суть
Streamlit-приложение с чат-агентом: он извлекает условия мероприятия из свободного текста, фильтрует каталог и показывает до трёх подрядчиков с цитатами и альтернативами при пустой выдаче.
Как устроено
Сообщение пользователя → LLM (OpenAI или NVIDIA) с function calling уточняет условия и формирует структурированный SearchRequest → Python hard-фильтры (город, категория, формат, дата, бюджет, языки, часы) → ранжирование 80×текстовая близость + 20×доля оставшегося бюджета (TF-IDF со стеммингом русского или OpenAI text-embedding-3-small) → объяснения из цитат профиля. При пустой выдаче фильтры пересчитываются с изменением одного условия (ближайшая дата, минимальное увеличение бюджета и т.п.).
Ключевые решения
- function calling
- TF-IDF
- Russian stemming
- OpenAI embeddings
- hard filtering
- counterfactual alternatives
- dialogue state memory
Особенности
Проработанная память диалога: короткие ответы («без разницы») снимают конкретный фильтр, месяц хранится как период. Модель не может подменить обязательные условия через аргументы инструмента. Match определён как доля подтверждённых условий, а не «вероятность», есть ответ «почему не этот подрядчик».
Стек
Python · Streamlit · unittest
Модели
OpenAI · NVIDIA API · OpenAI text-embedding-3-small
Проект создан за 5 часов на финале хакатона HackAlem.ai (23 сентября 2026, Астана). Код — BAITC-Hacks/hack-57ec179f-nurseitai.
README
README.mdNurseitai
AI-агент для поиска event-подрядчиков с проверяемыми объяснениями.
Опишите мероприятие обычными словами. Агент уточнит условия, проверит каталог и покажет, чем отличаются подходящие исполнители. Прототип для HackAlem AI «Умный подбор подрядчиков».
Запрос → извлечение условий → строгие фильтры → ранжирование → объяснения → TOP-3 и сравнение

Нужен ведущий на свадьбу в Алматы 3 октября 2026 года, бюджет до 1,5 млн тенге, русский язык, 6 часов. Без кринжовых конкурсов.
На эту дату по указанным обязательным условиям вариантов нет. Сервис повторно проверяет фильтры и предлагает 4 октября — два варианта. Перенос применяется только кнопкой пользователя; пожелание подтверждается отдельно по описанию.
Что получает пользователь
- Один поиск: чат — основной интерфейс, «Ручная настройка» корректирует те же условия и общую выдачу.
- Русский календарь: ввод даты
ДД.ММ.ГГГГ, полные названия месяцев и неделя с понедельника; язык браузера не влияет на отображение. Несуществующая дата не запускает поиск со старым значением. - Проверяемый отбор: условия и причины несовпадений рассчитываются по каталогу. Технические данные доступны, если администратор включил диагностику через
AI_DEBUG. - Индивидуальные объяснения: цитата из профиля, соответствие условиям, подтверждение пожеланий или честное отсутствие подтверждения.
- До трёх рекомендаций: показываем максимум три карточки и полное число подходящих, например «Подобрано 3 из 5». Можно сравнить рекомендации или сохранённый shortlist. Причина выдачи менее трёх вариантов видна в основном сообщении.
- Происхождение данных: цена и город, заполненные при подготовке датасета, отмечены рядом со значениями. Синтетические профили сохраняют отдельную отметку; цена обозначена как «от».
- Полезный следующий шаг: ближайшая более поздняя дата, минимальное увеличение бюджета, любой язык, меньшая длительность или настроенная альтернативная категория — с пересчитанным числом вариантов.
- «Почему не этот подрядчик?»: конкретные причины отказа, включая дату, цену, язык и длительность.
- Shortlist и последние запросы: живут в текущей сессии.
Архитектура
flowchart TD
User[Запрос пользователя] --> Agent[LLM: уточнения и вызов функций]
Agent --> Request[Структурированный SearchRequest]
Manual[Ручная настройка] --> Request
Request --> Hard[Python: город / категория / формат / дата / бюджет / языки / часы]
Hard --> Rank[OpenAI embeddings или локальный TF-IDF]
Rank --> Explain[Цитаты + проверки + отдельное объяснение порядка]
Explain --> Results[Общая выдача: до трёх карточек / полное число совпадений / сравнение]
Hard --> Alternatives[Повторные фильтры с изменением одного условия]
Alternatives --> Choice[Пользователь выбирает альтернативу]
Choice --> Request
LLM выбирает инструменты и формулирует ответ. Допуск кандидатов, счётчики и альтернативы рассчитывает Python. Каталог из 66 профилей остаётся источником фактов.
Match и порядок выдачи
Match — доля подтверждённых условий, а не вероятность успеха или оценка качества. У двух кандидатов без пожеланий может быть 100%: оба проходят все фильтры. Различия показываются цитатами, ценой, языками и ограничениями.
Порядок при наличии пожеланий: 80 × текстовая близость + 20 × доля оставшегося бюджета. При отсутствии бюджета вторая часть равна нулю. Без пожеланий — цена, затем ID. Баллы порядка доступны только в диагностических данных при включённом администратором AI_DEBUG и не выдаются за Match.
Пожелания сравниваются с описанием отдельно от города, категории и формата. Для подтверждения используются словоформы и несколько дословных фрагментов. Отрицания обрабатываются консервативно. Синонимы могут повлиять на семантический порядок без подтверждения пожелания. «Квантовый реактор на Марсе» не получает ложного подтверждения за слово «свадьба».
Пожелания вроде «интеллигентный, с тонким юмором» и «с видеоотзывами» извлекаются из обычной речи. Видеоотзывы не меняют услугу на видеосъёмку. При вызове поиска используются сохранённые пожелания пользователя, а обязательные условия защищены от изменений моделью. Повтор одинаковых отклонённых аргументов останавливает цикл и возвращает уточнение.
Семантический режим
Администратор включает семантическое ранжирование через EMBEDDINGS_ENABLED = true в .streamlit/secrets.toml или одноимённую переменную окружения. Используется OpenAI text-embedding-3-small; нужен ключ OpenAI, даже если чат работает через NVIDIA. Отправляются пожелания и описания только прошедших фильтры профилей. Векторы кешируются в памяти сессии по модели и тексту.
По умолчанию работает локальный TF-IDF со стеммингом русского языка. При сбое embeddings поиск продолжает работать локально с явным сообщением. Семантическая близость никогда не отменяет обязательные условия и сама по себе не считается доказательством пожелания.
Запуск
Python 3.10+:
git clone https://github.com/BAITC-Hacks/hack-57ec179f-nurseitai.git
cd hack-57ec179f-nurseitai
python -m venv .venv
Активируйте окружение: Windows PowerShell — .venv\Scripts\Activate.ps1, macOS/Linux — source .venv/bin/activate.
python -m pip install -r requirements.txt
python -m streamlit run app.py
Откройте http://localhost:8501. Для чата скопируйте .streamlit/secrets.toml.example в .streamlit/secrets.toml и заполните ключ выбранного провайдера. Подключение OpenAI / NVIDIA и инструменты агента.
Параметры AI задаёт администратор, переключателей в интерфейсе нет. AI_PROVIDER выбирает openai (по умолчанию) или nvidia; EMBEDDINGS_ENABLED и AI_DEBUG по умолчанию равны false. Можно использовать одноимённые переменные окружения; значения из secrets.toml имеют приоритет.
Без ключей работает ручная настройка с тем же поиском. Секреты исключены из Git. Чат передаёт сообщения и результаты функций выбранному API; embeddings подключаются отдельно и расходуют квоту.
Проверка
python -m unittest discover -s tests -v
Локальные тесты проверяют фильтры, пожелания, ранжирование, альтернативы, протоколы обоих API и интерфейс. Регрессии диалога используют отдельный тестовый каталог: неизвестные услуги, фото+видео с AND, память условий, просмотр неподходящих профилей, пустые ответы и ошибки API. API подменяются: тесты не расходуют квоту и не доказывают качество живой модели.
Потребность и память диалога
До сбора условий проверяется услуга. «Другое» означает неопределённую категорию. Фотосъёмка — «Фотограф», видеосъёмка — «Видеограф»; обе услуги у одного подрядчика требуют обеих категорий. Будки сопоставляются только с явно названной фотобудкой, фотозеркалом или видеобудкой. Если совмещающего специалиста нет, переход к двум отдельным требует выбора пользователя.
Потребность, услуги, условия, результаты и выбранный подрядчик хранятся отдельно. Исправление категории сохраняет условия. «Без разницы» снимает соответствующий фильтр, включая дату; без даты доступность не заявляется. Полный запрос запускает поиск без дополнительного разрешения. Модель не может подменить условия аргументами инструмента.
Выбор подрядчика и изменение условий обрабатываются вместе. Если выбранный профиль выходит за новый бюджет, агент сохраняет выбор и показывает разницу между начальной ценой и бюджетом; другого исполнителя автоматически не выбирает.
Короткие ответы («не важно», «мне всё без разницы», число в ответ на бюджет или часы) относятся к последним заданным вопросам. Уже известные город, услуга и остальные условия сохраняются. Для запроса «фотограф в декабре в Алмате на ближайшую свободную дату» декабрь хранится как период: поиск проверяет дни по порядку со всеми обязательными условиями. Ответ «не важно» на уточнение дня внутри месяца сохраняет месяц; выход за календарь каталога требует нового выбора.
В чат попадает текст, служебный JSON остаётся во внутренней истории. Пустой или технический ответ и сбой после поиска заменяются текстом из проверенных результатов. Разбор распространённых русских формулировок консервативный: нераспознанные значения уточняются или задаются вручную.
Сценарий для демонстрации: Алматы · ведущий · свадьба · 14.10.2026 · 1 500 000 ₸ · русский · 6 часов. Найдено пять вариантов, первые три — Мицури Канроджи, Кики, Эмилия. В карточках разные цитаты. На 03.10.2026 — пустая выдача с предложением 04.10.2026, два варианта. Ответ «без разницы» на бюджет, язык или длительность отключает соответствующий фильтр.
Структура и границы прототипа
| Файл | Назначение |
|---|---|
app.py, assistant_ui.py, results_ui.py | Единый интерфейс и карточки |
assistant.py | Цикл агента, аргументы и протоколы API |
dialogue.py | Услуги, состояние и защита видимого ответа |
date_picker.py, date_picker_frontend/ | Русский календарь и проверка выбранной даты |
recommender.py, preferences.py | Фильтры, ранжирование, цитаты, альтернативы |
semantic.py | Embeddings и кеш |
product.py | Сравнение, история, shortlist, демо-заявки |
data/contractors.csv | Анонимизированный каталог |
data/related_categories.json | Явно заданные замены категорий: ресторан ↔ банкетный зал |
Календарь ограничен 23.09–31.12.2026. Доступность означает отсутствие даты в списке занятости CSV, цена — указанную минимальную цену. Рекламные утверждения профиля не проверены. История и черновики не сохраняются в БД; настоящего бронирования, оплаты и отправки заявок нет. Свободный ответ LLM может ошибаться — карточки показывают вычисленные данные. Публичного развёртывания пока нет.
Авторы коммитов
История коммитов
- 2026-09-23 17:58Show full comparison text in wrapping table cellsNurbol
- 2026-09-23 17:56Remove contact action from contractor cardsNurbol
- 2026-09-23 17:51Fix request intent, contractor selection and top-three recommendationsNurbol
- 2026-09-23 17:38Use Russian calendar and day-month-year date inputNurbol
- 2026-09-23 17:28Remove demo request action from contractor cardsNurbol
- 2026-09-23 17:24Simplify result summaries and profile cards, remove AI settings from UINurbol
- 2026-09-23 17:13Ground profile explanations in catalog facts and ask to refine empty searchesNurbol
- 2026-09-23 17:08Preserve service intent and event state, enforce multi-service search, hide technical outputNurbol
Все коммиты (16)
- 2026-09-23 16:11Make agent the main search flow with verifiable explanations and comparisonNurbol
- 2026-09-23 15:31Make search filters collapsibleNurbol
- 2026-09-23 15:29Ask budget language and duration before agent searchNurbol
- 2026-09-23 15:28Support unrestricted agent searches and return all available contractorsNurbol
- 2026-09-23 15:01Add contractor search AI agent with OpenAI and NVIDIA tool callingNurbol
- 2026-09-23 14:53Fix preference evidence, distinct explanations and verified search alternativesNurbol
- 2026-09-23 14:33Add explainable NLP ranking and polish UINurbol
- 2026-09-23 14:09Build contractor recommendation MVPNurbol
Структура репозитория (26 файлов)
.gitignore .streamlit/secrets.toml.example AGENT_SETUP.md README.md app.py assistant.py assistant_ui.py data/contractors.csv data/related_categories.json date_picker.py date_picker_frontend/index.html dialogue.py docs/results.png preferences.py product.py recommender.py requirements.txt results_ui.py semantic.py tests/test_app.py tests/test_assistant.py tests/test_date_picker.py tests/test_dialogue_regressions.py tests/test_product.py tests/test_provider_transport.py tests/test_recommender.py
Обсуждение 0 комментариев
Войдите, чтобы комментироватьПока нет комментариев — будьте первым.