# Ответ Opus 4.8 на вопросы по архитектуре **Дата:** 2026-06-23 --- ## Вопрос 1: Параллельные вызовы LLM **Ключевой вопрос — допники кумулятивны или независимы.** - Если допник №3 может менять услугу, добавленную допником №2 — цепочка семантически последовательна, распараллелить нельзя. - Если каждый допник трогает свой непересекающийся набор услуг — параллелить можно. **Рекомендация:** оставить последовательным. Реальный выигрыш: - Фаза 1 (параллельно) — textify + подготовка контекста (CPU/IO, дёшево) - Фаза 2 (последовательно) — LLM+apply по цепочке - Или: более быстрая модель / меньше токенов / стриминг прогресса При параллельном apply_events seq поедет в гонку — ещё одна причина не параллелить. --- ## Вопрос 2: Атомарный seq без гонки **Рекомендация (по предпочтению):** 1. Таблица-счётчик `contract_seq(contract_id PK, last_seq)` + `UPDATE ... SET last_seq=last_seq+1 WHERE contract_id=? RETURNING last_seq`. Атомарно, без DDL, без гонок. 2. Advisory lock: `SELECT pg_advisory_xact_lock(contract_id)` в начале транзакции, затем MAX+1. Минимальная правка. `INSERT ... SELECT MAX+1` не атомарен. `CREATE SEQUENCE` на контракт — DDL-мусор. --- ## Вопрос 3: Стабильность name_hash **Рекомендация (комбинация):** - В промпт подавать spec_current с готовыми идентификаторами (hash). LLM для UPDATE/DELETE выбирает из готового списка, не сочиняет имя. - Хэш считать только на сервере из нормализованного имени: Unicode NFKC → схлопнуть пробелы → lower → trim. - Fuzzy (`similarity()`) только как fallback-страховка, не основной механизм. --- ## Вопрос 4: Имена в UPDATE/DELETE в UI **Рекомендация: вариант 2 — обогащать в Python перед SSE.** - spec_current — источник истины, имя для любого target_hash известно. - LLM вообще не должна возвращать имена для UPDATE/DELETE — только идентификатор. - Вариант 1 (LLM возвращает имена) — лишние токены + риск рассинхрона. - Вариант 3 (JS) — лишние round-trip'ы. --- ## Вопрос 5: Мульти-юзеры и рост данных 1. `prompts` одна таблица + `user_id TEXT` — норм для 10-10000 юзеров. Индекс `(user_id)`. Партиционирование преждевременно. 2. `spec_events` 10K строк — мизер для PG. Партиционирование ближе к 10M+ строк. 3. `BYTEA` 30MB — спокойно в PostgreSQL (TOAST). Вынести в S3 когда объём начнёт раздувать бэкапы. --- ## Сквозная связь В3 и В4 решаются одним сдвигом — давать LLM spec_current с готовыми ID и считать/резолвить всё на сервере. Это убирает и нестабильные хэши, и пустые ячейки в UI. **Уточнение Opus:** допники у вас кумулятивные (могут менять то, что добавили предыдущие) или независимые? --- ## Уточнения (23.06.2026) ### 1. Advisory lock в CFML **Да, безопасен.** `pg_advisory_xact_lock` — транзакционный: снимается автоматически при завершении транзакции (и commit, и rollback). Условия в Lucee: - Лок и INSERT в одном `` — одно соединение, одна транзакция - `pg_advisory_xact_lock` требует быть внутри транзакции — `` гарантирует Ключ: `pg_advisory_xact_lock(hashtext(contract_id))`. Коллизии hashtext безвредны — два контракта просто сериализуются. ### 2. NFKC-нормализация — в PostgreSQL Делать в PostgreSQL, в том же выражении что считает хэш. Python не дублировать. NFKC НЕ объединяет разные тире. Нужна связка в одном выражении: ```sql lower(trim(regexp_replace(translate(normalize(name, NFKC), '–—‑−«»""', '------""'), '\s+', ' ', 'g'))) ``` Всё одним выражением — для INSERT-хэша и для lookup. ### 3. Формат spec_current в промпте с готовыми ID Давать LLM компактный список с коротким суррогатным id (`r1, r2...`): ```json {"current_spec": [ {"id": "r1", "name": "Аренда стойко-места...", "price": 15000, "qty": 2, "sum": 30000, "date_start": "2025-01-01"} ]} ``` Инструкция в промпте: - UPDATE: `{"op":"UPDATE","target_id":"r1","new_values":{...}}` - DELETE: `{"op":"DELETE","target_id":"r2"}` - ADD: `{"op":"ADD","new_row":{...}}` (без id) LLM не видит хэши — только выбирает из готового набора. Сервер маппит `id → name_hash`. Устойчивее, меньше токенов, нет шанса опечатки в md5.