Skip to content

Repository files navigation

TenderAI — веб-сервис умного поиска тендеров с помощью искусственного интеллекта

Прототип веб-сервиса, который принимает запрос на естественном языке, находит подходящие закупки по 44-ФЗ и 223-ФЗ и объясняет условия участия с помощью RAG-ассистента, подтверждая каждое утверждение ссылкой на карточку закупки.

Проект закрывает пять задач:

Задача Где смотреть
1 Прототип дизайна веб-сервиса frontend/index.html, dist/tenderai-prototype.html
2 Прототип RAG-ассистента backend/app/rag/
3 Frontend и backend модули frontend/, backend/
4 Тестирование работоспособности tests/, docs/testing-report.md
5 Сайт стартап-проекта site/index.html

Живая версия

Сайт публикуется автоматически из ветки main (см. .github/workflows/deploy-pages.yml).


Быстрый старт

Вариант 1. Без установки — автономный HTML-прототип

Откройте dist/tenderai-prototype.html двойным кликом. Файл самодостаточен: внутри и интерфейс, и данные, и офлайн-версия поискового движка. Backend не нужен.

Вариант 2. Полный запуск сервиса

pip install -r requirements.txt

cd backend
uvicorn app.main:app --reload --port 8000
Адрес Что это
http://localhost:8000/ Интерфейс поиска и RAG-ассистента
http://localhost:8000/startup Сайт стартап-проекта
http://localhost:8000/docs Swagger UI (документация API)

Тестирование

pytest                              # 70 автотестов
python scripts/run_evaluation.py    # прогон + отчёт в docs/testing-report.md

Пересборка данных и автономного прототипа

python scripts/generate_dataset.py    # синтетический корпус закупок
python scripts/build_standalone.py    # dist/tenderai-prototype.html + dist/deploy/

Публикация сайта стартап-проекта

python scripts/build_standalone.py создаёт папку dist/deploy/ — её можно опубликовать на любом бесплатном статическом хостинге. Пошаговые инструкции для GitHub Pages, Netlify и Cloudflare Pages — в docs/deploy.md.


Архитектура

Запрос пользователя
      │
      ▼
[1] Разбор запроса ─────────► закон, регион, категория, НМЦК, СМП, актуальность
      │                        (backend/app/rag/query_parser.py)
      ▼
[2] Гибридный поиск
      ├── BM25 (лексика, точные термины, реестровые номера)  ─ bm25.py
      └── Плотные векторы (семантика, словоформы)            ─ embeddings.py + vector_store.py
      │
      ▼
[3] Слияние RRF (Reciprocal Rank Fusion)                     ─ retriever.py
      ▼
[4] Жёсткие фильтры + ограниченный бизнес-реранк             ─ retriever.py
      ▼
[5] Сборка контекста и генерация ответа                      ─ assistant.py + llm.py
      ▼
[6] Guardrail: проверка ссылок [n]                           ─ assistant.py
      ▼
Ответ со ссылками на источники

Структура репозитория

backend/
  app/
    main.py               точка входа FastAPI
    api/routes.py         HTTP-эндпоинты
    api/schemas.py        Pydantic-схемы запросов и ответов
    core/config.py        конфигурация через переменные окружения
    core/store.py         загрузка данных и сборка индекса
    rag/
      text.py             нормализация, токенизация, стемминг русского текста
      bm25.py             BM25 (Okapi) + нечёткое сопоставление опечаток
      embeddings.py       провайдеры эмбеддингов (local / OpenAI / Yandex / GigaChat)
      vector_store.py     векторное хранилище в памяти
      chunking.py         разбиение карточки закупки на смысловые чанки
      query_parser.py     разбор естественно-языкового запроса
      retriever.py        гибридный поиск, RRF, фильтры, реранк
      llm.py              провайдеры генерации + офлайн-фолбэк
      assistant.py        RAG-конвейер и guardrails
    data/
      tenders.json        корпус закупок
      schema.md           описание формата карточки
      eis_adapter.py      адаптер к выгрузке ЕИС
frontend/
  index.html              одностраничное приложение (поиск, ассистент, аналитика)
  offline-engine.js       офлайн-движок для автономного прототипа
site/
  index.html              сайт стартап-проекта
tests/                    автотесты (API, качество поиска, guardrails, производительность)
scripts/                  генерация данных, сборка прототипа, прогон тестирования
docs/                     отчёт о тестировании и метрики
dist/                     автономный HTML-прототип и папка для публикации
docs/deploy.md            инструкции по публикации сайта

Ключевые технические решения

Гибридный поиск вместо одного метода. BM25 хорошо находит точные термины, коды ОКПД2 и реестровые номера, но не понимает синонимы. Векторный поиск понимает смысл, но размывает точные совпадения. Результаты объединяются алгоритмом Reciprocal Rank Fusion — он не требует калибровки шкал двух ранжирований.

Ограниченный бизнес-реранк. Свежесть, статус и совпадение атрибутов дают надбавку не более 20% к оценке релевантности. Без такого ограничения бизнес-признаки перевешивали релевантность: на размеченном наборе это стоило ~0.16 MRR (проверяется тестом test_hybrid_beats_lexical_only).

Фильтры применяются после ранжирования, а не до отсечения кандидатов. Иначе узкий запрос («ИТ-закупки в Москве до 20 млн по 44-ФЗ») мог не найти существующие совпадения, потому что они не попали в первые N кандидатов.

Сменные провайдеры ИИ. Эмбеддинги и генерация вынесены за интерфейс: локальный режим (без ключей и сети), OpenAI, YandexGPT, GigaChat. При недоступности внешнего API сервис деградирует к локальному режиму, а не отдаёт ошибку.

Guardrails против галлюцинаций. Ответ формируется только из найденного контекста; каждая ссылка [n] проверяется на существование источника; при нулевой выдаче ассистент прямо сообщает, что закупок не найдено.


Конфигурация

Все настройки — через переменные окружения (см. .env.example):

Переменная По умолчанию Назначение
EMBEDDINGS_PROVIDER local local / openai / yandex / gigachat
LLM_PROVIDER local local / openai / yandex / gigachat
OPENAI_API_KEY ключ OpenAI
OPENAI_BASE_URL https://api.openai.com/v1 адрес OpenAI-совместимого API
YANDEX_API_KEY, YANDEX_FOLDER_ID доступ к YandexGPT
GIGACHAT_API_KEY доступ к GigaChat
DEFAULT_TOP_K 10 размер выдачи по умолчанию
CORS_ORIGINS * разрешённые источники для CORS

Без ключей сервис полностью работоспособен в локальном режиме.


API

Метод Путь Назначение
GET /api/health Состояние сервиса и размер индекса
GET /api/meta Справочники для фильтров
POST /api/search Поиск закупок: запрос, фильтры, сортировка, пагинация
GET /api/tenders/{id} Карточка закупки и похожие закупки
GET /api/similar/{id} Похожие закупки
POST /api/assistant/ask Вопрос RAG-ассистенту, ответ со ссылками
GET /api/analytics/overview Аналитика по корпусу закупок

Пример:

curl -X POST http://localhost:8000/api/search \
  -H 'Content-Type: application/json' \
  -d '{"query":"разработка информационной системы в Москве до 20 млн по 44-ФЗ"}'

Результаты тестирования

Полный отчёт — docs/testing-report.md, машиночитаемые метрики — docs/metrics.json.

Показатель Значение
Автотесты 70 пройдено, 0 провалено
P@1 68%
Recall@5 / Recall@10 95% / 97%
MRR 0.803
Латентность поиска, p95 ~80 мс
Корректность цитирования 8/8 без замечаний

Ограничения прототипа

  1. Корпус закупок синтетический — детерминированно сгенерирован и повторяет структуру выгрузки ЕИС. Для продуктива нужен коннектор (app/data/eis_adapter.py) и синхронизация.
  2. Локальный эмбеддер — хеширующий векторизатор; он работает без ключей, но уступает полноценной модели. Для продуктива подключается внешний провайдер.
  3. Индекс хранится в памяти процесса. Для миллионов карточек нужен pgvector / Qdrant и инкрементальная индексация.
  4. Нет аутентификации, мультитенантности и сохранённых подписок на запросы.

About

TenderAI — веб-сервис умного поиска тендеров с помощью искусственного интеллекта (прототип + сайт стартап-проекта)

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages