339 lines
20 KiB
Markdown
339 lines
20 KiB
Markdown
# План реализации «Контракт-чек» (Ollama Cloud / hosted LLM)
|
||
|
||
> **Примечание:** этот документ описывает исходную «ленивую» архитектуру с arq+Redis
|
||
> (очередь), Selectel S3 (хранилище) и единым Docker-образом с `MODE=api|worker|bot`.
|
||
> **Этап 0 (прототип) актуален.** Этапы 1+ superseded [`ARCHITECTURE.md`](ARCHITECTURE.md),
|
||
> где реализована production-архитектура: RabbitMQ-конвейер, MinIO, 4 worker-а
|
||
> (extract, prescreen, analyze, notify), aiogram-бот, B2B API, 7 Dockerfile-ов в `srv/`,
|
||
> dependency-groups (PEP 735).
|
||
|
||
> Принцип: ленивая архитектура. Каждый этап — минимальный работающий
|
||
> срез. Никаких абстракций «на потом». K8s, RabbitMQ, микросервисы —
|
||
> не нужны до $10k MRR.
|
||
>
|
||
> **LLM:** [Ollama Cloud](https://ollama.com/cloud) — hosted-инференс
|
||
> открытых моделей. Те же модели и API (`/api/chat`, `format: json`),
|
||
> что и у локального Ollama, но без своего железа: платим подписку +
|
||
> metered overage, инференс бежит у Ollama (NCP-партнёры, в основном США).
|
||
|
||
---
|
||
|
||
## О выборе Ollama Cloud
|
||
|
||
- **API:** стандартный Ollama HTTP API на cloud-эндпоинте. Авторизация —
|
||
API-ключ (bearer) из настроек аккаунта Ollama. Структурный вывод через
|
||
`format: json` (или JSON-schema) для парсимого отчёта.
|
||
- **Модели:** только cloud-enabled (см. `ollama.com/search?c=cloud`).
|
||
Рекомендация — **`qwen2.5:14b`** (сильный русский + reasoning).
|
||
Fallback — более лёгкая **`qwen2.5:7b`** при 429/квоте.
|
||
Теги БЕЗ суффикса `-instruct` (его нет в Ollama): `qwen2.5:14b`,
|
||
`llama3.1:8b`, `gemma2:9b` и т.п.
|
||
- **`format: json` ≠ соответствие схеме.** Ollama гарантирует синтаксис
|
||
JSON; соответствие вашей pydantic-схеме проверяем на клиенте + цикл
|
||
repair/retry. Не полагаемся на модель.
|
||
|
||
## Стоимость и лимиты Ollama Cloud (актуально на момент планирования)
|
||
|
||
| План | Цена | Конкарренси | Назначение | Статус |
|
||
|-------|-------------|-------------|-------------------------|------------------|
|
||
| Free | $0 | 1 | тесты | открыт |
|
||
| Pro | $20/мес | 3 | рабочая лошадка MVP | открыт |
|
||
| Max | $100/мес | 10 | тяжёлый поток | **приостановлен** |
|
||
| Team | $25/seat | — | команда (5 seat min) | waitlist |
|
||
| Enterprise | custom | custom | продакшн SaaS | по запросу |
|
||
|
||
- **Usage:** rolling-лимиты — сессия 5 ч + недельный 7 дн. Pro = 50× Free.
|
||
При превышении — **metered overage** (добиваем баланс, оплата по
|
||
токенам модели). Жёсткой стены нет, но конкарренси = 3 на Pro.
|
||
- **Хостинг: в основном США** (подключаются EU/Singapore). Это **ломает
|
||
152-ФЗ data-residency**, см. раздел «Риски».
|
||
|
||
## Экономика проекта (честно)
|
||
|
||
- Опекс фиксированный до квоты: VPS ~4 €/мес + Ollama Cloud Pro $20/мес +
|
||
Selectel S3 (копейки). Итого ~$25–30/мес на старте.
|
||
- Рост объёма → metered overage Ollama Cloud по токенам. Маржа на
|
||
pay-per-doc (199 ₽) остаётся, **если** один договор съедает мало
|
||
квоты. Это **обязательно измерить на этапе 0** (квота/документ).
|
||
- В отличие от локального Ollama — нет потолка по железу, но есть потолок
|
||
по конкарренси (3) и плавающий cost при overage. В отличие от GigaChat —
|
||
cost в $, не в ₽, и нет РФ-локализации данных.
|
||
|
||
---
|
||
|
||
## Архитектура — hexagonal (ports & adapters)
|
||
|
||
Ядро и адаптеры разделены. Это снимает связность «бот знает про БД/S3/LLM» и
|
||
позволяет добавлять каналы доставки (CLI, веб, B2B API) без дублирования
|
||
бизнес-логики.
|
||
|
||
- **Ядро (application core):**
|
||
- `api` (FastAPI/uvicorn) — владеет БД, S3, кредитами, оплатой; принимает
|
||
документы, **резервирует кредит на enqueue**, ставит arq-задачи в redis,
|
||
отдаёт отчёты/профиль. Аутентификация адаптеров — общий `SERVICE_TOKEN`.
|
||
- `worker` (arq) — разбирает очередь: S3 → экстракт → (OCR) → chunker →
|
||
analyzer → Ollama Cloud → Report. Idempotency + refund при ошибке.
|
||
- Общий доменный пакет `contract_check/` (`extractor`, `chunker`,
|
||
`llm_client`, `analyzer`, `checklist`, `report_schema`, `ocr`, `storage`,
|
||
`db`, `models`, `payments`, `quota`) — используется только ядром.
|
||
- **Адаптеры (delivery = HTTP-клиенты к `api`):**
|
||
- `bot` (aiogram) — Telegram: приём документа → `POST /documents` (multipart)
|
||
→ отдача отчёта. **Не трогает БД/S3/redis/LLM.**
|
||
- `cli` — пользовательский/отладочный CLI поверх api (новый, отдельный от
|
||
прототипа).
|
||
- `web` (React SPA) — этап 2, тоже HTTP-клиент к api.
|
||
- **Прототип `prototype.py` (stage 0)** — standalone, in-process LLM; артефакт
|
||
go/no-go, **не адаптер и не ядро**.
|
||
|
||
> Следствие для планирования: **api + worker строятся на этапе 1** (боту-адаптеру
|
||
> нужен api). Этап 2 = добавление адаптера `web` + подписок (api уже есть).
|
||
|
||
---
|
||
|
||
## Этап 0 — Прототип (1 выходной)
|
||
|
||
**Цель:** доказать, что модель через Ollama Cloud реально находит риски,
|
||
и измерить задержку + **квоту на один договор** (для юнит-экономики).
|
||
|
||
**Деливерэбл:** один файл `prototype.py` — end-to-end.
|
||
|
||
```
|
||
PDF/DOCX → pymupdf/python-docx → текст →
|
||
prompt с чек-листом → Ollama Cloud (format: json) → отчёт в markdown
|
||
```
|
||
|
||
**Что делаем:**
|
||
1. Завести аккаунт Ollama, взять API-ключ, положить в `.env`.
|
||
2. Поставить `pymupdf`, `httpx` в venv.
|
||
3. Один скрипт: читает PDF → достаёт текст → промпт с чек-листом из 10
|
||
пунктов → дёргает Ollama Cloud → печатает отчёт.
|
||
4. Прогнать 3–5 реальных договоров (NDA, оказание услуг, поставка).
|
||
5. Зафиксировать: качество находок, задержка (сек), % битых JSON,
|
||
**сколько квоты/токенов на договор**.
|
||
|
||
**Чего НЕ делаем:** база, веб, бот, оплату, деплой. Один скрипт локально.
|
||
|
||
**Критерий успеха:** отчёт по реальному договору содержит хотя бы 3
|
||
осмысленные находки с цитатами; задержка < 60 сек; квота/договор даёт
|
||
понятную маржинальность при 199 ₽.
|
||
|
||
---
|
||
|
||
## Этап 1 — Telegram-бот MVP (2–3 недели)
|
||
|
||
**Цель:** первые платящие пользователи. Бот = самый быстрый путь до ЦА.
|
||
|
||
### 1.1 Структура проекта
|
||
|
||
```
|
||
contract_check/
|
||
├── pyproject.toml # hatchling, deps (core + adapters)
|
||
├── docker-compose.yml # postgres, redis, api, worker, bot (+ profile app = prototype)
|
||
├── .env.example
|
||
├── src/contract_check/
|
||
│ ├── main.py # диспетчер: MODE=api|worker|bot|cli
|
||
│ ├── prototype.py # stage-0 standalone (in-process LLM) — НЕ в hexagonal
|
||
│ │ # ── ЯДРО (application core): владеет БД/S3/кредитами/LLM ──
|
||
│ ├── api.py # FastAPI: /documents, /reports/{id}, /me, /healthz
|
||
│ ├── worker.py # arq analyze_document(doc_id): S3→extract→OCR→analyze→Report
|
||
│ ├── analyzer.py extractor.py chunker.py ocr.py
|
||
│ ├── llm_client.py checklist.py report_schema.py
|
||
│ ├── storage.py db.py models.py payments.py quota.py
|
||
│ │ # ── АДАПТЕРЫ (delivery): HTTP-клиенты к api, без БД/S3/LLM ──
|
||
│ ├── bot.py # aiogram: приём документа → POST /documents → отчёт
|
||
│ └── cli.py # CLI поверх api (новый, не прототип)
|
||
├── migrations/ # alembic
|
||
└── tests/
|
||
```
|
||
|
||
### 1.2 База данных — 3 таблицы
|
||
|
||
```sql
|
||
users (id, telegram_id, created_at, credits_left)
|
||
documents (id, user_id, s3_key, status, created_at)
|
||
reports (id, document_id, content_json, created_at)
|
||
```
|
||
|
||
`credits_left` — prepaid-кредиты (pay-per-doc). Подписок в v1 нет.
|
||
`status` хранить как `TEXT` + `CHECK`, не Postgres-ENUM (миграции проще).
|
||
|
||
### 1.3 Поток (резервирование кредита на enqueue)
|
||
|
||
```
|
||
Юзер кидает PDF в бот
|
||
→ бот (adapter): multipart'ом шлёт файл в api: POST /documents (+ SERVICE_TOKEN)
|
||
→ api (core): get_or_create_user, проверяет credits_left > 0, РЕЗЕРВИРУЕТ (-= 1),
|
||
грузит файл в S3, создаёт Document(status=queued), ставит arq-таску → 202 + job_id
|
||
→ worker (core): idempotency guard по status, достаёт из S3 → текст → (OCR если скан)
|
||
→ worker: chunker при необходимости → analyzer → Ollama Cloud → отчёт
|
||
→ worker: pydantic-валидация + repair-loop; пишет Report, status=done
|
||
→ при ошибке: status=failed, ВОЗВРАТ кредита (+1)
|
||
→ бот: опрашивает GET /reports/{id} (или push), отправляет отчёт
|
||
(с разбивкой/файлом при >4096 симв.), футер-disclaimer
|
||
```
|
||
|
||
### 1.4 Оплата (минимум)
|
||
|
||
- ЮKassa: бот генерирует ссылку на оплату N ₽ → webhook пополняет
|
||
`credits_left`.
|
||
- Один тариф: 199 ₽ = 1 документ. Без подписок.
|
||
|
||
### 1.5 Деплой
|
||
|
||
- **Один VPS** (Hetzner CX22 ~4 €/мес или Selectel под РФ-локацию БД).
|
||
- Docker Compose: `postgres`, `redis`, `api`, `worker`, `bot`. **Без GPU.**
|
||
(Бот — адаптер к api; веб-адаптер `web` добавится на этапе 2.)
|
||
- S3 — Selectel Object Storage.
|
||
- Домен + HTTPS только для webhook ЮKassa; на старте ngrok (с оговоркой:
|
||
URL на free-ngrok меняется → лучше сразу дешёвый домен + Caddy).
|
||
|
||
### 1.6 Критерий успеха
|
||
|
||
10 платящих. MRR ~2 000 ₽. Отчёты не вызывают жалоб «ничего не нашёл».
|
||
Ollama Cloud Pro покрывает нагрузку без ухода в overage.
|
||
|
||
---
|
||
|
||
## Этап 2 — Веб + подписки (3–4 недели)
|
||
|
||
**Цель:** B2B-веб-интерфейс и подписки.
|
||
|
||
### 2.1 Что добавляем
|
||
|
||
- **`api` уже построен на этапе 1** (T-E1-015). На этапе 2 добавляем: роуты
|
||
подписок/счетов, Telegram Login auth (сессия/JWT) для веб-адаптера, и сам
|
||
веб-адаптер `web` (React SPA) — ещё один HTTP-клиент к api.
|
||
- React SPA (Vite): загрузка, история, профиль, подписка. Отдаётся статикой
|
||
позже (отдельный `web`-контейнер / Nginx).
|
||
- Auth: Telegram Login Widget (нужен публичный HTTPS-домен в BotFather) →
|
||
сессия/JWT.
|
||
- Подписки: solo (1 490 ₽), team (3 990 ₽). ЮKassa recurring.
|
||
|
||
### 2.2 Что меняем в базе
|
||
|
||
```sql
|
||
ALTER TABLE users ADD COLUMN plan TEXT DEFAULT 'free';
|
||
ALTER TABLE users ADD COLUMN plan_renews_at TIMESTAMP;
|
||
CREATE TABLE invoices (id, user_id, amount, status, provider, external_id, created_at);
|
||
```
|
||
|
||
`credits_left` остаётся для pay-per-doc; подписка = безлимит с monthly
|
||
reset через cron-arq-таску.
|
||
|
||
### 2.3 Архитектура — без изменений в ядре
|
||
|
||
`api` и `worker` работают с этапа 1; на этапе 2 добавляется только адаптер
|
||
`web` (React, статика) перед Nginx → HTTPS (Let's Encrypt). Hexagonal-граница
|
||
сохранена: `web` — такой же HTTP-клиент к api, как `bot`. LLM по-прежнему
|
||
Ollama Cloud; при росте — Enterprise тариф или переход на свой GPU.
|
||
|
||
### 2.4 Критерий успеха
|
||
|
||
50 платящих. MRR ~30 000 ₽. Есть хотя бы один team-клиент.
|
||
|
||
---
|
||
|
||
## Этап 3 — B2B API (2 недели, только если есть спрос)
|
||
|
||
**Цель:** сторонние сервисы дёргают анализ через API.
|
||
|
||
### 3.1 Что добавляем
|
||
|
||
- API-ключи: таблица `api_keys`, header `X-API-Key`.
|
||
- Rate-limit: Redis (token bucket). Лимит = **конкарренси/квота Ollama
|
||
Cloud** (3 на Pro), а не ₽.
|
||
- `POST /api/v1/analyze` (multipart) → 202 + job_id →
|
||
`GET /api/v1/reports/{id}`.
|
||
- Дашборд: ключи, usage, счета.
|
||
|
||
### 3.2 Чего НЕ делаем
|
||
|
||
- Нет SDK, нет вебхуков (клиент поллит), нет OAuth2.
|
||
- Один тариф API: 9 900 ₽/мес за 100 запросов.
|
||
|
||
### 3.3 Критерий успеха
|
||
|
||
3 API-клиента. MRR +30 000 ₽.
|
||
|
||
---
|
||
|
||
## Инфраструктура — сводка
|
||
|
||
| Компонент | Этап 0 | Этап 1 | Этап 2–3 |
|
||
|----------------|------------|------------------------------|--------------------------------|
|
||
| Compute | ноутбук | 1 VPS (Docker Compose) | 1 VPS (апгрейд RAM) |
|
||
| LLM | Ollama Cloud (Free/Pro) | Ollama Cloud Pro | Ollama Cloud Pro/Enterprise |
|
||
| DB | — | Postgres (в compose) | Postgres (+ backup cron) |
|
||
| Queue | — | Redis (в compose) | Redis (тот же) |
|
||
| Object storage | — | Selectel S3 | Selectel S3 |
|
||
| OCR | — | Tesseract (локально) | Tesseract (+ Yandex Vision) |
|
||
| Payments | — | ЮKassa | ЮKassa + CloudPayments |
|
||
| Monitoring | — | Docker logs | Uptime Kuma + Sentry (free) |
|
||
| CI/CD | — | git push → ssh deploy | GitHub Actions → build → deploy|
|
||
|
||
**K8s / RabbitMQ / Kafka / Elasticsearch / vLLM / TGI — НЕ НУЖНЫ.** Один
|
||
VPS + Ollama Cloud держит всё до заметного объёма. Свой GPU — только если
|
||
Ollama Cloud overage станет дороже self-host (отдельное решение позже).
|
||
|
||
---
|
||
|
||
## Чек-лист пунктов анализа (v1)
|
||
|
||
Содержимое `checklist.py` — один список, без БД:
|
||
|
||
1. Неустойки / штрафы (размер, односторонний)
|
||
2. Подсудность (чужой регион)
|
||
3. Сроки оплаты (условия, просрочка)
|
||
4. IP-права (кому отходят результаты)
|
||
5. Одностороннее изменение условий
|
||
6. Гарантии и их срок
|
||
7. Форс-мажор (формулировки)
|
||
8. НДС (включён / сверх)
|
||
9. Ответственность сторон (cap, исключения)
|
||
10. Расторжение (условия, уведомление)
|
||
|
||
---
|
||
|
||
## Сроки (реалистично, соло, вечера/выходные)
|
||
|
||
| Этап | Что | Время |
|
||
|------|------------------|-----------|
|
||
| 0 | Прототип | 1 выходной|
|
||
| 1 | Telegram-бот MVP | 2–3 недели|
|
||
| 2 | Веб + подписки | 3–4 недели|
|
||
| 3 | B2B API | 2 недели |
|
||
|
||
**До первого платящего (0+1): ~3–4 недели.** Заложить ~1–2 дня на подбор
|
||
cloud-модели и тюнинг промпта (локальные/open модели капризнее GPT-4o).
|
||
|
||
---
|
||
|
||
## Риски Ollama Cloud (честно)
|
||
|
||
1. **152-ФЗ / data-residency.** Контракт улетает в Ollama Cloud (США).
|
||
Это тот же класс риска, что и GPT-4o. Митигация: disclaimer,
|
||
обезличивание ПДн перед отправкой, либо при необходимости —
|
||
отказ от Ollama Cloud в пользу РФ-LLM (GigaChat/YandexGPT) или своего
|
||
GPU-бокса. **Не позиционировать продукт как «данные не покидают РФ».**
|
||
2. **Квота/конкарренси.** Pro = 3 одновременных модели + rolling usage.
|
||
Бурст платящих юзеров упрётся в очередь/429. Трекать usage,
|
||
алертить у лимита, при росте — overage-баланс или Enterprise.
|
||
3. **Качество open-моделей.** 14B галлюцинирует/пересказывает цитаты
|
||
сильнее GPT-4o/GigaChat. Всегда: цитата + номер пункта + ремонт-цикл
|
||
валидации JSON. Disclaimer «не заменяет юриста» — в каждый отчёт.
|
||
4. **Зависимость от одного провайдера.** Один аккаунт Ollama (нельзя
|
||
несколько). Иметь готовый план Б: GigaChat/YandexGPT-фолбэк или свой
|
||
GPU при блокировке/превышении квоты.
|
||
|
||
---
|
||
|
||
## Что сознательно отложено (YAGNI)
|
||
|
||
- Multi-tenant / организации / роли — пока все юзеры = solo.
|
||
- Шаблоны договоров (генерация) — другой продукт.
|
||
- ЭЦП / Госуслуги — чужой регуляторный ад.
|
||
- Команда юристов (human-in-the-loop) — только если попросят.
|
||
- Mobile app — веб + бот закрывают 100%.
|
||
- White-label — один продукт, один бренд.
|
||
- Свой GPU / vLLM / TGI / RAG-над-векторной-базой — пока Ollama Cloud
|
||
дешевле; пересмотрим при выходе overage в минус.
|