Files
contracts/History/llm-analysis/opus-questions.md
T

123 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Вопросы к Opus 4.8 по проекту Сверка договоров
**Дата:** 2026-06-23
**Версия:** v1.0.107
---
## Что за проект
Сервис для сверки договоров облачного провайдера. Юзер загружает договоры/допники (docx/pdf), парсер извлекает текст, LLM сравнивает с накопленной спецификацией по цепочке (Event Sourcing). На выходе — какие услуги добавились/изменились/удалились.
**Стек:** Lucee CFML 6.0 (сервер приложений) + PostgreSQL 15 + Python 3 на отдельной ВМ (nginx + convert_server.py). LLM: gpt-oss-120b через api.aillm.ru (бесплатный, без структурного вывода). Фронт: CFML-страницы + JavaScript (EventSource для SSE).
**Масштаб:** сейчас 1 тестовый пользователь. В планах Keycloak, мульти-юзеры.
**Производительность:** 5 файлов (3-35 KB docx) × последовательные вызовы LLM = 80-100 секунд. Каждый вызов LLM: 6-50 секунд.
---
## Вопрос 1: Параллельные вызовы LLM
**Текущая схема:** цикл по supplements последовательно. Для каждого: взять текущую spec_current → textify документа → вызвать LLM → получить ops → apply_events → следующий.
```python
for s in supps:
cur = get_current_spec() # из БД
text = textify(elements_json) # текст документа
ops = call_llm(cur, text) # HTTP к LLM, 6-50с
apply_events(ops) # HTTP к Lucee
# следующий видит обновлённую spec_current
```
**Проблема:** 5 файлов × (12+20+38+7+6)с = 80-100с. LLM-вызовы занимают 95% времени.
**Вопрос:** Можно ли распараллелить вызовы LLM, если известно что первый файл = исходная спецификация (база), а остальные — допники к ней? То есть: файл1 → LLM → накопили. Затем файлы 2-5 запустить параллельно, каждый сравнивается с результатом файла1? Или есть архитектурный паттерн лучше? Что будет с seq в spec_events при параллельных apply_events?
---
## Вопрос 2: Event Sourcing seq без гонки
**Текущая схема:** в apply_events.cfm (Lucee):
```cfm
<cfquery>SELECT COALESCE(MAX(seq),0) FROM spec_events WHERE contract_id=? FOR UPDATE</cfquery>
<cfset seq = result + 1>
<!-- цикл по ops, каждый seq++ -->
```
FOR UPDATE не работает с агрегатной функцией MAX() в PostgreSQL 15 — падает с ошибкой. Убрали.
**Структура:** `spec_events(contract_id, seq)`, UNIQUE(contract_id, seq). Seq монотонный в рамках контракта.
**Вопрос:** Как правильно сделать атомарный инкремент seq без гонки? Варианты:
1. `CREATE SEQUENCE spec_events_seq_{contract_id}` — но это динамический DDL на каждый контракт
2. `LOCK TABLE spec_events IN EXCLUSIVE MODE` — грубо
3. `INSERT ... SELECT COALESCE(MAX(seq),0)+1, ...` — атомарно ли?
4. Отдельная таблица-счётчик с `UPDATE ... RETURNING`
Что рекомендовано для PostgreSQL + CFML?
---
## Вопрос 3: name_hash — стабильность идентификации строк
**Текущая схема:** строка спецификации идентифицируется хэшем:
```sql
md5(lower(trim(name)) || coalesce(date_start, ''))
```
Вычисляется в INSERT (БД) из данных, которые LLM вернула в new_row.
Для UPDATE/DELETE LLM возвращает target_hash — указывает какую строку менять/удалить.
**Проблема:** LLM может написать название услуги чуть иначе в разных файлах:
- Файл1: "Аренда стойко-места, в составе: Номинальная мощность – 10 кВт"
- Файл2: "Аренда стойко-места, в составе: Номинальная мощность - 10 кВт" (другое тире)
- Хэши разные → LLM видит ADD/DELETE вместо UPDATE
**Вопрос:** Как стабилизировать? Варианты:
1. Нормализовать name перед хэшированием (убрать пунктуацию, лишние пробелы)
2. LLM возвращает не target_hash, а явно target_name + date_start, а хэш вычисляется сервером
3. Fuzzy matching: если target_hash не найден, искать близкое имя через `levenshtein()` или `similarity()`
4. Хранить исходный name из первого появления и использовать его как ключ
Что правильнее архитектурно?
---
## Вопрос 4: UPDATE/DELETE без имён в UI
**Проблема:** в результатах сравнения ADD-строки показывают все поля (услуга, цена, кол-во, сумма, дата). UPDATE и DELETE — пустые ячейки.
**Причина:** LLM для UPDATE/DELETE возвращает только target_hash и new_values (для UPDATE). Имени услуги нет — оно было в исходной спецификации. UI строит таблицу из d.ops, где для ADD есть new_row.name, а для UPDATE/DELETE — нет.
**Вопрос:** Где правильнее решать:
1. В промпте LLM — требовать всегда возвращать name в new_values для UPDATE и в отдельном поле для DELETE
2. В Python (convert_server.py) — перед отправкой ops в SSE, дополнить их именами из spec_current (доп. запрос к БД)
3. В UI (JavaScript) — перед рендерингом запрашивать spec_current и резолвить имена
Что даст меньше запросов и меньше шансов рассинхронизации?
---
## Вопрос 5: Мульти-юзеры и рост данных
**Планы:** Keycloak, каждый юзер имеет свои контракты и промпты.
**Таблицы:**
- `documents`, `supplements`, `contracts` — растут с каждым файлом
- `spec_events` — растёт с каждым сравнением (каждый ADD/UPDATE/DELETE — строка)
- `spec_current` — текущая спецификация, O(услуг в контракте)
- `prompts` — одна таблица на всех (в плане: `user_id + timestamp` как ID)
**Вопросы:**
1. `prompts` — одна таблица с `user_id TEXT` норм для масштаба 10-100 юзеров? Или партиционировать?
2. `spec_events` — для 100 контрактов по 20 услуг с 5 версиями каждый = 10K строк. Нужно ли партиционирование сейчас?
3. `documents` хранит бинарные байты (`original_bytes BYTEA`). Для 1000 документов по 30KB = 30MB — норм в PostgreSQL или вынести в S3-совместимое хранилище?
---
## Важно
- **НЕ делай код. Только анализ и рекомендации.**
- Ответь кратко по каждому вопросу: рекомендуемое решение и почему.
- Если нужно уточнение — спроси, но проект небольшой, контекста выше достаточно.