О проекте
Задача — собрать проверяемый поиск по разнородным тендерным файлам. От исходного документа до ответа остаётся трасса document → chunk → embedding → hit → citation, поэтому результат можно вернуть к конкретному фрагменту.
TenderLens принимает HTML, PDF, XML, JSON и TXT, приводит документы к одному контракту и режет их на стабильные chunks. Индексация идёт через NATS JetStream. bge-m3 в MWS считает embeddings, pgvector ищет близкие фрагменты, а gpt-oss-120b формирует ответ только по найденному контексту и возвращает источники.
Что отличает реализацию
Каждый ответ проходит одну трассу: document → chunk → bge-m3 → pgvector → gpt-oss-120b → citation. Повтор события не размножает chunks. Посторонний запрос отсекается до генерации. Корпус синтетический; очередь, индексатор, БД, векторный поиск и обе модели MWS работают реально.
Состав прикладного кода
Доли по размеру прикладных файлов Git в закреплённой версии. Каждый файл отнесён к одной подсистеме по правилам scripts/generate-stack-share.mjs; тесты, ассеты, зависимости и generated-файлы исключены. Округление — до 0,1%. Это состав кода, а не уровень владения технологиями.
- Четыре синтетических документа и 16 chunks — небольшой демонстрационный корпус, а не база реальных закупок.
- Качество модели на независимом наборе не измерялось. Показанные cosine scores описывают только запросы из проверки 9 сентября 2026.
- Лимит — 5 Search/Ask запросов в минуту на сессию и 12 запросов в минуту на весь процесс. Запросы пользователей не сохраняются.
- Inference зависит от доступности двух deployment в MWS. При недоступности провайдера readiness становится красным, автоматического перехода на fake нет.
- Worker health подтверждает соединение при старте; отдельный детектор зависшего вычисления не реализован.
Скриншоты, схемы и исходные материалы сохранены на языке оригинала; подписи и описание переведены.
Архитектура и схемы
- Seed отправляет TenderChangedV1 через NATS; индексатор извлекает текст и атомарно заменяет chunks при изменении content_hash.
- bge-m3 возвращает 1024-компонентные embeddings. Индексатор пишет имя модели рядом с каждым chunk, а PostgreSQL VECTOR(1024) выполняет cosine search.
- Ask передаёт gpt-oss-120b только найденные фрагменты. Документы в prompt помечены как данные, а не инструкции.
- Порог 0,40 выбран после замера релевантных и посторонних запросов. Если подходящих chunks нет, generation endpoint не вызывается.
- Внешний crawler на публичном стенде выключен. Корпус синтетический, очередь, БД, векторный поиск и MWS inference работают реально.
Code-map: файлы и связи
Карта привязана к версии 6c005b6ef863. В таблице — экран или API, связанный модуль и данные.
| Функция | Экран / API | Модуль | Данные / проверка |
|---|---|---|---|
| Начальная индексация | CLI portfolio | cli._upsert_demo_record, NatsBroker, IndexerService | sources, tenders, attachments, chunks |
| Embeddings | Indexer worker | MwsAIProvider.embed, IndexerService | bge-m3, VECTOR(1024), embedding_model |
| Поиск | api/v1/search | SearchService, MwsAIProvider.embed | cosine distance, порог 0,40 |
| Ответ по базе | api/v1/ask | SearchService, MwsAIProvider.generate | найденные chunks, gpt-oss-120b, citations |
| Повтор события | api/demo/replay | NatsBroker / content_hash guard | общий read-only корпус |
| Сессия и лимиты | cookie / middleware | portfolio, auth, rate_limit | api_keys, 5/session, 12/process |
Как работать с картой
- Перед изменением найти функцию и пройти связи до API, состояния и хранилища.
- Проверить входы, ошибки, повторные события и зависимые сценарии.
- После изменения обновить карту и затронутые схемы вместе с кодом.
- Выполнить связанные проверки и привязать новые материалы к версии исходников.
Сценарии и API
- Найдите документ о серверах: первый результат должен содержать гарантию 36 месяцев и метку bge-m3.
- Переключитесь на Ask: gpt-oss-120b должен ответить по найденным фрагментам и показать источники.
- Выберите «Нет контекста»: при score ниже 0,40 сервис возвращает отказ без вызова модели генерации.
- Проверьте состояние корпуса: четыре документа, 16 chunks и ready у каждой записи.
Проверка опубликованной версии
CI: CI ↗ · 84c37f0f · исторический запуск другой версии · 2026-08-27T06:43:00Z
CI: Documentation ↗ · 84c37f0f · исторический запуск другой версии · 2026-08-27T06:43:00Z
- Black, Flake8 и MyPy пройдены для версии 6c005b6.
- 129 unit/API тестов пройдены; MkDocs собран в strict режиме, сгенерированный code reference актуален.
- Production-образ проверен с реальными MWS endpoints: readiness, embedding 1024 и grounded Ask.
- В опубликованной версии Search, Ask и no-context прошли через внешний HTTPS; ошибок console и failed requests нет.
- Viewport 390 px проверен без горизонтального overflow.
- GitHub workflow для этой ветки не запускался; указанные проверки выполнены локально и на опубликованном стенде.
Эти проверки относятся к указанной версии. Коммерческие показатели и качество моделей на независимой выборке здесь не измерялись.
Измерения
- Опубликованы десять кадров версии 6c005b6: desktop, mobile, Search, Ask, no-context и validation.
- Четыре ready-документа переиндексированы в 16 векторов bge-m3 размерности 1024.
- В галерейной browser-проверке 9 сентября 2026 desktop Search занял 298 мс, Ask — 1145 мс, no-context — 115 мс; mobile Search — 137 мс, Ask — 1936 мс. Это одиночные измерения, не benchmark.
- Для релевантных тестовых запросов top cosine был 0,604–0,927; для двух посторонних — 0,278 и 0,370. Production threshold выставлен 0,40.
- Текущий снимок двух контейнеров TenderLens: около 121,4 MiB RAM суммарно при простое. Модели выполняются в MWS и не занимают RAM/GPU VPS.
- На момент снимка 9 сентября 2026 было доступно около 5,05 GiB RAM и 105,7 GiB диска на VPS.
Запуск и эксплуатация
Публичная версия работает в AI_MODE=mws. API и indexer обращаются к OpenAI-совместимым MWS endpoints по HTTPS. API key передаётся контейнерам как server-side secret и не попадает в браузер, образ или Git.
bge-m3 используется и при индексации, и при запросе. После смены embedding deployment весь корпус нужно переиндексировать: векторы разных моделей в одном индексе не смешиваются. Readiness проверяет список доступных deployment IDs.
Dockerfile.portfolio собирается вне VPS; API и worker используют один закреплённый образ с отдельными лимитами. PostgreSQL и NATS доступны только во внутренней Docker-сети, API опубликован через Nginx.
После миграций CLI seed создаёт четыре синтетических документа. Повтор события безопасен благодаря content_hash. После рестарта проверяются ready у четырёх документов, 16 chunks, модель bge-m3 и размерность 1024.
Сессионный лимит равен пяти запросам в текущую UTC-минуту, общий лимит процесса — двенадцати. No-context запрос останавливается до Chat Completions. Пользовательские запросы не пишутся в БД.
Откат сохраняет предыдущий образ и release-конфигурацию. Смена embedding provider требует обратной переиндексации. Общий docker prune не используется.
Code-map связывает экран или API с модулем и данными. При правке проверяются обычный запрос, no-context, rate limit, повтор события, readiness и отсутствие ключа MWS в клиентских файлах и логах.
Стек
| FastAPI / Pydantic | HTTP-контракты, валидация, Search и Ask. |
|---|---|
| MWS Model Hub | bge-m3 для embeddings; gpt-oss-120b для ответа по контексту. |
| NATS JetStream | Доставка событий индексатору и повтор. |
| PostgreSQL / pgvector | Документы, attachments, chunks и cosine search. |
| SQLAlchemy / Python | Нормализация, извлечение, chunking и проверка content_hash. |
Материалы и исходники
История реализации: с 20 августа 2026. Публичный стенд: с 7 сентября 2026. Реальные модели MWS подключены 9 сентября 2026.
Архив материалов на языке оригинала.