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

5.9 KiB
Raw Blame History

Ответ 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 в одном <cftransaction> — одно соединение, одна транзакция
  • pg_advisory_xact_lock требует быть внутри транзакции — <cftransaction> гарантирует

Ключ: pg_advisory_xact_lock(hashtext(contract_id)). Коллизии hashtext безвредны — два контракта просто сериализуются.

2. NFKC-нормализация — в PostgreSQL

Делать в PostgreSQL, в том же выражении что считает хэш. Python не дублировать.

NFKC НЕ объединяет разные тире. Нужна связка в одном выражении:

lower(trim(regexp_replace(translate(normalize(name, NFKC), '–—‑−«»""', '------""'), '\s+', ' ', 'g')))

Всё одним выражением — для INSERT-хэша и для lookup.

3. Формат spec_current в промпте с готовыми ID

Давать LLM компактный список с коротким суррогатным id (r1, r2...):

{"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.