v2 architecture: Sonnet plan + request docs
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
## Контекст
|
||||
|
||||
Проект «Сверка договоров» — автоматическое сравнение договоров и допсоглашений (ДС) облачного провайдера.
|
||||
|
||||
### Текущая реализация (Lucee CFML + PostgreSQL)
|
||||
|
||||
**Таблицы:**
|
||||
- `contracts` (id UUID, number, client)
|
||||
- `documents` (id UUID, filename, mime_type, original_bytes, elements_json JSONB, parsed_text)
|
||||
- `supplements` (id UUID, contract_id, document_id, type: 'initial'/'additional')
|
||||
- `spec_rows` (id UUID, supplement_id, row_num INTEGER, name TEXT, price NUMERIC, qty NUMERIC, sum NUMERIC, date_start TEXT)
|
||||
- `prompts` (contract_id UUID, prompt_text TEXT)
|
||||
|
||||
**Текущий поток:**
|
||||
1. Загрузка PDF/DOCX через ВМ-прокси (contracts.kube5s.ru) — т.к. k8s Ingress с ddos-guard рвёт HTTP/2 при прямых POST > ~50KB. ВМ проксирует на Lucee (proxy_buffering off, client_max_body_size 100m). Для .doc — конвертация через libreoffice на ВМ.
|
||||
2. Парсинг (PDFBox/POI/Tabula) → elements_json (параграфы + таблицы)
|
||||
3. Для каждого документа отдельный вызов LLM (api.aillm.ru, gpt-oss-120b):
|
||||
- Промпт: «Извлеки строки спецификации в JSON {rows: [{row_num, name, price, qty, sum, date_start}]}»
|
||||
- Ответ LLM: JSON со строками
|
||||
4. Сохраняется в spec_rows (DELETE старых + INSERT новых)
|
||||
5. Сравнение: differ.cfm сравнивает initial VS каждый additional по row_num → added/deleted/changed
|
||||
|
||||
**Проблемы текущего подхода:**
|
||||
- Сравнение по row_num — хрупкое. Если в ДС поменялся порядок строк — всё ломается
|
||||
- Нет кумулятивного статуса — только попарный diff
|
||||
- LLM используется только как экстрактор, а не как интерпретатор изменений
|
||||
- Нет обработки неясных ситуаций (LLM не может сказать «не знаю»)
|
||||
- Нет истории версий (event sourcing)
|
||||
|
||||
**Архитектура загрузки (важно):**
|
||||
- Фронтенд Lucee (contractor.luceek8s.dev.nubes.ru) НЕ может принимать большие POST напрямую — k8s Ingress + ddos-guard рвут HTTP/2 стримы случайным образом (ERR_HTTP2_PROTOCOL_ERROR)
|
||||
- Загрузка идёт через ВМ-прокси (contracts.kube5s.ru, nginx): proxy_buffering off, client_max_body_size 100m
|
||||
- ВМ может выполнять дополнительную логику при необходимости (конвертация .doc, валидация, etc.)
|
||||
- Прямая загрузка на pythonk8s (contractor.pythonk8s.services.ngcloud.ru) имеет ту же проблему с HTTP/2
|
||||
|
||||
**Инфраструктура:**
|
||||
- Lucee 6.0 CFML на k8s (contractor.luceek8s.dev.nubes.ru)
|
||||
- Python Flask на k8s (contractor.pythonk8s.services.ngcloud.ru)
|
||||
- Python Flask на ВМ (contracts.kube5s.ru) — nginx, libreoffice, прокси для k8s
|
||||
- PostgreSQL (через JDBC на Lucee, psycopg2 на Python)
|
||||
- LLM API: api.aillm.ru/v1/chat/completions (gpt-oss-120b, 120B параметров, локальная модель, бесплатно, без ограничений по токенам)
|
||||
- LLM НЕ поддерживает structured output / JSON mode — только промпт
|
||||
- Клиентский HTTP/2 через httpx на Python, cfhttp на Lucee
|
||||
- **Проблема Lucee:** cfhttp блокирует поток на время ответа LLM (до 120с на каждый вызов). Если 5 допников — Lucee висит 10 минут, ничего не отдавая клиенту
|
||||
|
||||
## Что нужно
|
||||
|
||||
Разработать архитектуру и логику **кумулятивного статуса договора** (event sourcing):
|
||||
|
||||
### Идея (требует детальной проработки)
|
||||
1. **LLM получает**: текущую спецификацию договора (все строки) + полный текст ДС
|
||||
2. **LLM возвращает**: список операций (ADD/UPDATE/DELETE/UNRESOLVED):
|
||||
```json
|
||||
[
|
||||
{"action":"UPDATE","target_hash":"abc123","new_values":{"price":150000},"comment":"..."},
|
||||
{"action":"DELETE","target_hash":"def456","comment":"..."},
|
||||
{"action":"ADD","new_row":{"name":"...","price":...,"qty":...,"sum":...,"date_start":"..."}},
|
||||
{"action":"UNRESOLVED","reason":"Не удалось сопоставить строку..."}
|
||||
]
|
||||
```
|
||||
3. **Lucee исполняет** операции как лог (event sourcing), без хардкода
|
||||
4. Связывание строк — по хэшу названия услуги (md5 нормализованного имени), НЕ по row_num
|
||||
|
||||
### Требования к архитектуре
|
||||
- Event sourcing: лог операций, можно восстановить состояние на любую дату
|
||||
- Обработка «полной замены» спецификации в ДС (пачка DELETE + ADD)
|
||||
- UNRESOLVED: если LLM не уверен — оператор решает вручную (нужен UI)
|
||||
- Два режима сравнения: А) ДС → изменения поверх договора, Б) ДС → полная замена спецификации
|
||||
- Отказоустойчивость: если LLM ошибся, не испортить основные данные
|
||||
- Поскольку загрузка идёт через ВМ — можно добавить промежуточную логику на ВМ если нужно
|
||||
|
||||
### Вопрос
|
||||
Разработай полную архитектуру:
|
||||
1. Структуру таблиц БД (лог операций, кумулятивный статус)
|
||||
2. Промпт для LLM (как передать контекст: текущую спецификацию + текст ДС)
|
||||
3. Логику Lucee (CFML) для исполнения лога операций
|
||||
4. Как обрабатывать UNRESOLVED (UI + процесс)
|
||||
5. Как откатывать ошибочные операции
|
||||
6. Нужно ли сохранять старый экстрактор строк или заменить полностью?
|
||||
7. ВМ-прокси как async-буфер для LLM-вызовов
|
||||
- Lucee через cfhttp висит синхронно до 120с на каждый вызов LLM
|
||||
- Предложение: ВМ (Flask + httpx) забирает задачу от Lucee,
|
||||
асинхронно вызывает LLM, отдаёт готовый JSON команд
|
||||
- Lucee не блокируется, httpx работает с HTTP/2 надёжнее cfhttp
|
||||
- Как должен выглядеть API между Lucee и ВМ для этого?
|
||||
- Нужна ли очередь задач или можно синхронно (ВМ ждёт, Lucee ждёт ВМ)?
|
||||
- Стоит ли объединить все вызовы LLM в один запрос к ВМ (все ДС сразу)?
|
||||
Reference in New Issue
Block a user