516 lines
44 KiB
Markdown
516 lines
44 KiB
Markdown
# Полные ответы Опуса — изолированная страница /teach (feedback + обучение)
|
||
|
||
Дата: 26.06.2026 | Сохранено post-factum из чат-сессии.
|
||
|
||
---
|
||
|
||
# ЧАСТЬ 1: Ответ про few-shot и эталонный датасет
|
||
|
||
Хороший вопрос. Дай я сначала посмотрю, как сейчас устроены эти «6 файлов» и формирование промпта, чтобы мнение было по делу.
|
||
|
||
Searching codebase for "формирование промпта для LLM на основе эталонных файлов few-shot examples"
|
||
|
||
Searched for regex `6 (файл|file|example|эталон|reference)`, 1 result
|
||
|
||
Searched for regex `few.?shot|эталон|example|пример|reference|опорн` (`**/contracts-app/**/*.py`), no results
|
||
|
||
Searched for files matching `**/llm_prompt.py`, 1 match
|
||
|
||
Searched for files matching `**/prompt*.{py,cfm}`, 2 matches
|
||
|
||
Searched for regex `opus_cases|out_100files|6 файл|six`, 2 results
|
||
|
||
Read file:///home/naeel/nubes/contracts/contractor/deploy/llm_prompt.py#1-1, lines 1 to 120
|
||
|
||
Read file:///home/naeel/nubes/contracts/testgen/opus_cases
|
||
|
||
Посмотрел, как сейчас устроено: промпт в llm_prompt.py — это хардкод-инструкция с доменным глоссарием и парой ручных few-shot примеров (`ПРИМЕР 1`, `ПРИМЕР 2`), а «6 файлов» — это по сути синтетические кейсы из opus_cases для проверки.
|
||
|
||
Моё мнение по фиче.
|
||
|
||
## Идея здравая, но есть важная развилка
|
||
|
||
Реальные проверенные документы — это очень ценно. Синтетика (gen_opus.py) не воспроизводит грязь реальных договоров: кривые таблицы, объединённые ячейки, «мощность в составе названия», нестандартные формулировки ДС, опечатки в суммах. На 6 искусственных файлах легко получить иллюзию качества, которая рассыпется на первом же настоящем пакете.
|
||
|
||
Но ключевой вопрос: **что именно строить из архива?** Тут смешаны две разные вещи.
|
||
|
||
## 1. Эталонный датасет (input → проверенный output) — да, делать
|
||
|
||
Самое полезное. Но важно: «загрузить архив документов» недостаточно. Чтобы это работало, на каждый документ нужен **проверенный правильный результат** (итоговая спецификация / список ops). Без эталонного ответа это просто куча PDF — не на чем калибровать.
|
||
|
||
То есть фича должна быть не «загрузи архив», а «загрузи архив + зафиксируй проверенную спецификацию как ground-truth». Тогда получаем regression-набор: прогнал промпт → сравнил с эталоном → видишь реальный процент ошибок.
|
||
|
||
## 2. Авто-генерация промпта из этих файлов — осторожно
|
||
|
||
Полностью «формировать промпт» из загруженных файлов я бы не делал. Риски:
|
||
- **Раздувание контекста** — если пихать реальные договоры целиком как few-shot, промпт пухнет, растёт цена/латентность, и модель начинает копировать формат конкретного образца.
|
||
- **Переобучение под layout** — закладываешь форматы 3-х клиентов, ломается на четвёртом.
|
||
- **Конфиденциальность** — реальные договоры с реквизитами/ценами уезжают в LLM на каждом запросе. Это надо явно проговорить с заказчиком.
|
||
|
||
Лучше из архива **извлекать паттерны, а не сам текст**: новые единицы измерения, варианты названий услуг, типы формулировок ДС («изложить в новой редакции» и т.п.) — и обогащать ими глоссарий и правила в промпте. 1-2 самых показательных кейса можно добавить как few-shot, остальное — в eval-набор.
|
||
|
||
## Что я бы предложил по факту
|
||
|
||
- **Разделить два контура**: (а) `golden dataset` из реальных пар вход→проверенный_выход для регрессии; (б) сам промпт — оставить компактным, обогащать его выводами из анализа датасета, а не сырыми файлами.
|
||
- Фича «загрузка архива» → ведёт в **разметку/верификацию** (человек подтверждает правильный результат), а не сразу в промпт.
|
||
- На этом наборе гонять метрику (precision/recall по ops), и менять промпт только если метрика растёт.
|
||
|
||
Резюме: направление правильное — уходить от 6 синтетических файлов надо. Но ценность не в «архиве документов», а в **архиве проверенных пар документ↔результат**. И этим калибровать/тестировать, а не механически генерировать промпт.
|
||
|
||
Это обсуждение — код не трогаю. Скажешь «делай» по конкретному пункту — приступлю.
|
||
|
||
---
|
||
|
||
# ЧАСТЬ 2: Ответ про «типа обучение» (feedback-learning цикл)
|
||
|
||
Да, это сильнее предыдущего варианта — и вот почему, плюс где подвох.
|
||
|
||
## Почему это лучше «загрузки архива»
|
||
|
||
Главная проблема прошлого варианта была: откуда взять «проверенный правильный ответ». Здесь она решается сама собой — **интерфейс исправления ошибок и есть инструмент разметки**. Юзер не размечает абстрактно, а правит конкретный результат системы. На выходе автоматически копится то самое: пара «вход → проверенный выход». Это правильный способ построить эталонный датасет.
|
||
|
||
И что важно — у вас уже есть подходящая структура под это. Результат LLM — это поток операций (`ADD` / `UPDATE` / `DELETE` / `UNRESOLVED`) в llm_prompt.py, а применяются они через событийную модель (apply_events.cfm). Значит правка юзера — это **исправленный поток операций**, а разница «что выдала LLM» vs «как поправил юзер» = чистый сигнал ошибки. Это удобно: не свободный текст «тут неверно», а конкретная дельта.
|
||
|
||
## Но «типа обучения» — это важная оговорка
|
||
|
||
Само по себе это **не обучение модели**. Цикл «учится» только если правки где-то сохраняются и переиспользуются. Если правка живёт в рамках одной сессии и исчезает — система не умнеет, это просто ручная корректировка. Так что ключевое в фиче — не сам акт правки, а **накопление и переиспользование** правок.
|
||
|
||
Дальше есть три уровня, и не надо их путать:
|
||
|
||
1. **Регрессия** (самое дешёвое и полезное сразу) — правки копятся как golden-набор, на нём гоняется промпт, видно реальный % ошибок.
|
||
2. **Prompt-learning** — из частых ошибок осознанно дополняется глоссарий/правила/few-shot в промпте. Это и есть практичное «обучение» в вашем случае.
|
||
3. **Fine-tuning модели** — реальное дообучение. Дорого, нужен объём (сотни-тысячи примеров), отдельная инфраструктура. На старте не нужно.
|
||
|
||
## Где подвох — не вкручивать правки в промпт автоматически
|
||
|
||
Соблазн: «каждую правку юзера сразу добавлять в промпт». Так делать нельзя:
|
||
- **переобучение** под конкретные форматы;
|
||
- **рост контекста** и цены;
|
||
- **конфликтующие правки** от разных юзеров → промпт деградирует.
|
||
|
||
Правильнее: правки идут в накопитель → периодически человек/куратор смотрит агрегат → осознанно обновляет промпт → прогоняет регрессию. Batch, а не автоматом на каждый клик.
|
||
|
||
## Что бы я ещё заложил в дизайн
|
||
|
||
- **Структурировать тип ошибки**, а не свободный текст: «пропущена строка» / «неверная цена» / «надо было UPDATE, а не ADD» / «ложный дубликат вместо UNRESOLVED». Тогда правки можно агрегировать и видеть системные слабости промпта, а не разбирать вручную.
|
||
- Фиксировать **версию промпта**, на которой получен результат — иначе через месяц непонятно, актуальна ли ещё ошибка.
|
||
- Учесть **конфиденциальность**: реальные договоры с реквизитами/ценами копятся в БД — проговорить с заказчиком.
|
||
|
||
Резюме: режим «загрузил → система выдала → юзер поправил → правка сохранилась» — да, делать стоит, это естественный сборщик эталонных данных и ловушка системных ошибок. Только «обучение» держать осознанным (batch-обновление промпта + регрессия), а не авто-инъекцией каждой правки.
|
||
|
||
Код не трогаю — это обсуждение. Скажешь «делай» — распишу конкретную реализацию.
|
||
|
||
---
|
||
|
||
# ЧАСТЬ 3: Короткий вариант для заказчика
|
||
|
||
Вот короткий вариант для заказчика:
|
||
|
||
---
|
||
|
||
**Этап опытной эксплуатации (обучение системы)**
|
||
|
||
На первое время предлагаем режим проверки: вы загружаете реальные документы, система выдаёт результат, а вы отмечаете ошибки — что распознано неверно (пропущена строка, неверная цена, неправильное сопоставление и т.п.).
|
||
|
||
Эти отметки **в обезличенном виде** (без названий компаний, ФИО, реквизитов и иных персональных данных) накапливаются в базе и используются для дальнейшей настройки и обучения системы.
|
||
|
||
Это позволит откалибровать сервис на ваших реальных договорах, а не на тестовых примерах, и системно повышать точность.
|
||
|
||
---
|
||
|
||
Если нужно ещё короче (одним абзацем) или, наоборот, добавить пункт про согласие на обработку данных — скажи, поправлю.
|
||
|
||
---
|
||
|
||
# ЧАСТЬ 4: Ответ про привязку замечаний к строкам таблицы
|
||
|
||
Дай гляну, как сейчас выглядит вывод результата — чтобы предложить привязку замечаний к реальным элементам интерфейса, а не абстрактно.
|
||
|
||
Read file:///home/naeel/nubes/contracts/contractor/deploy/services
|
||
|
||
Read memory file:///home/naeel/.vscode-server/data/User/workspaceStorage/dc98ff3e658364e1826ee6585ebe4b5e/GitHub.copilot-chat/memory-tool/memories/repo/contracts-two-panels.md
|
||
|
||
Searched for regex `target_id|r1|new_row|action.*ADD|операц|spec|строк` (`**/contractor/view.cfm`), no results
|
||
|
||
Searched for files matching `**/contracts-app/site/templates/**`, no matches
|
||
|
||
Не «у каждого абзаца» — это привязка не к тому объекту. Результат работы системы — это не текст-простыня, а **таблица спецификации**: набор строк-услуг, каждая получена операцией LLM (`ADD`/`UPDATE`/`DELETE`/`UNRESOLVED`) и имеет свой id (`r1`, `r2`…). Замечание логично цеплять **к строке результата**, а не к абзацу исходника.
|
||
|
||
## Как я бы это сделал
|
||
|
||
**Уровень 1 — отметка на строке (основное).**
|
||
У каждой строки итоговой таблицы — маленькая иконка «⚠ ошибка». Клик открывает мини-форму с **выбором типа ошибки** (не свободный текст):
|
||
- неверная цена / объём / сумма
|
||
- неверное наименование
|
||
- лишняя строка (надо было удалить)
|
||
- пропущена строка (есть в документе — нет в результате)
|
||
- неверное сопоставление (надо UPDATE существующей, а не новая ADD; или наоборот)
|
||
- неверный тип ДС / режим (partial vs новая редакция)
|
||
|
||
Плюс необязательное поле «комментарий» свободным текстом.
|
||
|
||
**Уровень 2 — отметка «пропущено».**
|
||
Случай «строки вообще нет в результате» не покрывается иконкой на строке. Нужна отдельная кнопка «➕ Система пропустила позицию» над/под таблицей.
|
||
|
||
**Уровень 3 — общая оценка (опционально).**
|
||
Одна кнопка вверху: «Результат в целом верный / есть ошибки». Дёшево, но даёт быстрый сигнал по проценту удачных разборов.
|
||
|
||
## Почему так, а не поле у абзаца
|
||
|
||
- **Структурированный фидбек агрегируется.** «Тип ошибки = неверное сопоставление встречается в 40% ДС» → понятно, что чинить в промпте. Свободный текст у абзацев придётся разбирать вручную, аналитики ноль.
|
||
- **Замечание = дельта операции.** Юзер фактически правит строку → разница «что выдала LLM» vs «как должно быть» и есть обучающий сигнал. Это ложится на вашу событийную модель (apply_events.cfm) почти без натяжки.
|
||
- **Меньше шума.** Поле ввода под каждым абзацем визуально перегружает и провоцирует расплывчатые комментарии.
|
||
|
||
## Что копится в базе (обезличенно)
|
||
|
||
На каждое замечание:
|
||
- тип ошибки + (опц.) комментарий
|
||
- что выдала система по этой строке
|
||
- как поправил юзер (правильное значение)
|
||
- версия промпта
|
||
- **без** названий компаний/ФИО/реквизитов — только структура и числа
|
||
|
||
## Как это сформулировать заказчику (1 фраза)
|
||
|
||
> В режиме проверки у каждой строки результата будет отметка «ошибка» с выбором типа (неверная цена, пропущена позиция, неверное сопоставление и т.п.). Эти отметки в обезличенном виде копятся в базе и используются для настройки точности.
|
||
|
||
Хочешь — распишу конкретную реализацию (поля в БД, эндпоинт сохранения, изменения в шаблоне вывода). Код пока не трогаю, жду «делай».
|
||
|
||
---
|
||
|
||
# ЧАСТЬ 5: Ответ про UI с примером на допник-1-XXX002
|
||
|
||
Отлично, теперь предметно. Твой вывод — это карточка документа + **таблица операций** (`Действие / Услуга / Цена / Кол-во / Сумма / Дата`). Замечания цепляем именно к строкам этой таблицы. Вот как это выглядело бы на твоём примере:
|
||
|
||
```
|
||
✓ допник-1-XXX002-01200_3.docx — 2 оп., partial (14с) [✓ всё верно]
|
||
+2 ~0 -0
|
||
|
||
Действие Услуга Цена Кол-во Сумма Дата ⚠
|
||
ADD WAF: Positive Technologies, в составе… 104021.67 1 104021.67 2026-03-30 [⚠]
|
||
ADD Облачный диск Valo Cloud, в составе… 68700 1 68700 2026-02-01 [⚠]
|
||
|
||
[ ➕ Система пропустила позицию ]
|
||
```
|
||
|
||
**Клик по `[⚠]` в строке** → раскрывается мини-форма прямо под строкой:
|
||
|
||
```
|
||
Тип ошибки: ( ) неверная цена/сумма
|
||
( ) неверное кол-во
|
||
( ) неверное наименование услуги
|
||
(•) лишняя строка — этой операции быть не должно
|
||
( ) неверное действие (должно быть UPDATE/DELETE, а не ADD)
|
||
( ) неверная дата
|
||
Правильное значение: [_____________] (необязательно)
|
||
Комментарий: [_____________] (необязательно)
|
||
[ Сохранить ]
|
||
```
|
||
|
||
**Кнопка `[✓ всё верно]`** вверху карточки — если разбор документа корректен целиком. Один клик = положительный сигнал, ничего расписывать не надо.
|
||
|
||
**Кнопка `➕ Система пропустила позицию`** — для случая, которого нет в таблице (услуга была в документе, но LLM её не извлекла). Открывает форму ввода пропущенной строки.
|
||
|
||
## Что уходит в базу по одному замечанию (обезличенно)
|
||
|
||
На примере второй строки, если юзер укажет «неверная цена, правильно 68000»:
|
||
|
||
```
|
||
operation: ADD
|
||
service: "Облачный диск Valo Cloud, в составе…" ← наименование услуги, без названия клиента
|
||
field: price
|
||
llm_value: 68700
|
||
correct: 68000
|
||
error_type: wrong_price
|
||
prompt_version: v1.0.178
|
||
```
|
||
|
||
Никаких «ЗАО ХХХ002», № договора, ФИО — только структура услуги и числа.
|
||
|
||
## Почему так, а не поле под каждым абзацем
|
||
|
||
Твой реальный вывод подтверждает: это **таблица**, а не текст. Поле у «абзаца» некуда вешать — абзацев тут нет, есть операции. А привязка к операции даёт:
|
||
- агрегацию по типу ошибки (видно, что чинить в промпте),
|
||
- готовую дельту «LLM выдала X → правильно Y» как обучающий сигнал,
|
||
- ровную укладку в вашу событийную модель (apply_events.cfm).
|
||
|
||
Хочешь — распишу конкретику реализации (колонка в таблице вывода, эндпоинт сохранения, структура таблицы в БД). Код не трогаю, жду «делай».
|
||
|
||
---
|
||
|
||
# ЧАСТЬ 6: ДЕТАЛЬНЫЙ ПЛАН — страница /teach
|
||
|
||
Это план, не правка кода — изучу структуру, чтобы план лёг на реальный код, а не в воздух. Читаю ключевые файлы.
|
||
|
||
Read file:///home/naeel/nubes/contracts/contractor/db.cfc
|
||
|
||
Read file:///home/naeel/nubes/contracts/contractor/Application.cfc
|
||
|
||
Read memory file:///home/naeel/.vscode-server/data/User/workspaceStorage/dc98ff3e658364e1826ee6585ebe4b5e/GitHub.copilot-chat/memory-tool/memories/repo/contracts-deploy.md
|
||
|
||
Read memory file:///home/naeel/.vscode-server/data/User/workspaceStorage/dc98ff3e658364e1826ee6585ebe4b5e/GitHub.copilot-chat/memory-tool/memories/repo/contracts-lucee-test.md
|
||
|
||
Изучил структуру. Lucee-приложение с файловой маршрутизацией (`teach.cfm` → `/teach`), PostgreSQL `baza`, есть событийная модель (`spec_events`, `spec_current`) и уже готовый read-only SQL-эндпоинт `/db/query?sql=…` в db.cfc — через него агент и будет читать БД. Вот детальный план.
|
||
|
||
---
|
||
|
||
# План: страница `/teach` (опросник + обучение)
|
||
|
||
## 1. Принцип изоляции — что НЕ трогаем
|
||
|
||
Маршрутизация в Lucee файловая → **новый путь = новый файл**, существующий код не редактируется вообще:
|
||
|
||
| Не трогаем | Добавляем |
|
||
|---|---|
|
||
| `api.cfm`, `process*.cfm`, `apply_events.cfm`, db.cfc, Application.cfc, `upload.cfm`, `view.cfm` | `teach.cfm`, `teach_api.cfm`, `teach_schema.cfm` |
|
||
| существующие таблицы (`spec_events`, `contracts`…) | **одна новая таблица** `feedback` |
|
||
|
||
Никаких `ALTER` существующих таблиц, никаких правок в `db.cfc/schema()`. Только чтение чужих таблиц + запись в свою.
|
||
|
||
## 2. Новые файлы (3 шт.)
|
||
|
||
1. **`teach_schema.cfm`** — одноразовый: `CREATE TABLE IF NOT EXISTS feedback (…)`. Зашёл по URL один раз → таблица создана.
|
||
2. **`teach.cfm`** — сама страница:
|
||
- список уже разобранных договоров/ДС (читает `contracts` + `supplements` + `spec_events`);
|
||
- выбрал ДС → рисует ту же таблицу операций (`Действие/Услуга/Цена/Кол-во/Сумма/Дата`), что в твоём примере, но с колонкой `⚠` и кнопками `[✓ всё верно]`, `[➕ пропущена позиция]`;
|
||
- JS отправляет замечание `POST`-ом на `teach_api.cfm`.
|
||
3. **`teach_api.cfm`** — приём замечания: **параметризованный** `INSERT` в `feedback` (никакого конкатенированного SQL — защита от инъекций). Возвращает JSON `{ok:true}`.
|
||
|
||
> Источник данных для таблицы операций — `spec_events` (`action`, `new_values` JSONB, `comment`, `seq`). При реализации сверим, что карточка «2 оп., partial» строится именно отсюда.
|
||
|
||
## 3. Схема таблицы `feedback` (ядро — продумано под анализ агентом)
|
||
|
||
Главные поля вынесены отдельными колонками (не в JSON), чтобы агент агрегировал простым SQL; значения — в JSONB.
|
||
|
||
```sql
|
||
CREATE TABLE IF NOT EXISTS feedback (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
created_at TIMESTAMPTZ DEFAULT now(),
|
||
|
||
-- привязка (внутренняя трассировка, в обучающий экспорт НЕ идёт)
|
||
contract_id UUID, -- FK-логически на contracts, без жёсткого constraint
|
||
supplement_id UUID,
|
||
event_seq INTEGER, -- какая операция ДС (NULL для "пропущено"/"документ в целом")
|
||
|
||
-- уровень и вердикт
|
||
scope TEXT NOT NULL, -- 'row' | 'missed' | 'document'
|
||
verdict TEXT, -- 'correct' | 'error'
|
||
|
||
-- суть ошибки (для агрегации)
|
||
error_type TEXT, -- wrong_price|wrong_qty|wrong_name|wrong_date|
|
||
-- extra_row|missed_row|wrong_action|wrong_mode
|
||
field TEXT, -- price|qty|sum|name|date_start|action|mode
|
||
|
||
-- обучающий сигнал: что выдала система vs как правильно
|
||
service_name TEXT, -- наименование услуги (обезличено)
|
||
llm_value JSONB, -- что выдала LLM
|
||
correct_value JSONB, -- что указал юзер
|
||
|
||
-- контекст результата
|
||
prompt_version TEXT, -- версия промпта на момент разбора
|
||
doc_mode TEXT, -- partial | full_replace
|
||
comment TEXT, -- свободный комментарий юзера (необяз.)
|
||
reviewer TEXT -- обезличенный id сессии/юзера (необяз.)
|
||
);
|
||
CREATE INDEX IF NOT EXISTS idx_feedback_error ON feedback(error_type);
|
||
CREATE INDEX IF NOT EXISTS idx_feedback_prompt ON feedback(prompt_version);
|
||
```
|
||
|
||
## 4. Что пишется по каждому типу замечания
|
||
|
||
| Действие юзера | scope | verdict | error_type | llm_value → correct_value |
|
||
|---|---|---|---|---|
|
||
| `[✓ всё верно]` на карточке | `document` | `correct` | — | — |
|
||
| `⚠` на строке: неверная цена | `row` | `error` | `wrong_price` | `{"price":68700}` → `{"price":68000}` |
|
||
| `⚠`: лишняя строка | `row` | `error` | `extra_row` | вся операция → `null` |
|
||
| `⚠`: должно быть UPDATE, а не ADD | `row` | `error` | `wrong_action` | `{"action":"ADD"}` → `{"action":"UPDATE"}` |
|
||
| `[➕ пропущена позиция]` | `missed` | `error` | `missed_row` | `null` → `{name,price,qty,sum,date}` |
|
||
| неверный режим ДС | `document` | `error` | `wrong_mode` | `{"mode":"partial"}` → `{"mode":"full_replace"}` |
|
||
|
||
Каждая запись самодостаточна: видно **что было** и **как надо** → готовая обучающая пара.
|
||
|
||
## 5. Обезличивание
|
||
|
||
- В `feedback` **не копируем** название клиента, № договора, ФИО, реквизиты.
|
||
- Храним только: `service_name` (тип услуги — «WAF Positive Technologies»), числа, тип ошибки, версию промпта.
|
||
- `contract_id/supplement_id` — это UUID (не имя), для внутренней трассировки. В **обучающий экспорт** агента эти id не включаются — только структурные поля.
|
||
- Для заказчика формулировка: *«в обезличенном виде, без названий компаний и персональных данных»* — соответствует фактической схеме.
|
||
|
||
## 6. Как агент читает и анализирует (потом)
|
||
|
||
Через уже существующий `/db/query?sql=…` (read-only) или напрямую psql. Примеры:
|
||
|
||
**Системные слабости промпта:**
|
||
```sql
|
||
SELECT error_type, field, count(*) AS n
|
||
FROM feedback WHERE verdict='error'
|
||
GROUP BY error_type, field ORDER BY n DESC;
|
||
```
|
||
|
||
**Качество по версии промпта (регрессия):**
|
||
```sql
|
||
SELECT prompt_version,
|
||
count(*) FILTER (WHERE verdict='correct') AS ok,
|
||
count(*) FILTER (WHERE verdict='error') AS err
|
||
FROM feedback GROUP BY prompt_version;
|
||
```
|
||
|
||
**Выгрузка обучающих пар (для правки промпта/few-shot):**
|
||
```sql
|
||
SELECT service_name, error_type, llm_value, correct_value
|
||
FROM feedback WHERE verdict='error' AND scope='row';
|
||
```
|
||
|
||
Дальше агент: смотрит агрегат → предлагает правку промпта/глоссария → прогоняет на накопленных парах → сравнивает метрику до/после. **Никакой авто-инъекции** правок в промпт — только осознанное batch-обновление.
|
||
|
||
## 7. Версия промпта — нюанс
|
||
|
||
Сейчас результат разбора **не штампуется** версией промпта. Варианты:
|
||
- (минимум, без правки основного кода) `/teach` пишет в `prompt_version` **текущую активную** версию промпта на момент замечания — приблизительно, с оговоркой;
|
||
- (правильно, отдельной задачей позже) при разборе сохранять `prompt_version` в результат — но это уже касается основного пайплайна, делать отдельно и по «делай».
|
||
|
||
Флажок: на старте берём активную версию, точность привязки уточним позже.
|
||
|
||
## 8. Этапы внедрения
|
||
|
||
1. `teach_schema.cfm` → создать таблицу `feedback`.
|
||
2. `teach.cfm` (чтение разобранных ДС + рендер таблицы с `⚠`) — без сохранения, только UI.
|
||
3. `teach_api.cfm` (параметризованный INSERT) + подключить JS-отправку.
|
||
4. Проверка end-to-end на одном реальном ДС (как твой `допник-1-XXX002`).
|
||
5. 2–3 контрольных SQL-запроса агента → убедиться, что данные читаемы и агрегируются.
|
||
6. (позже, отдельно) первый цикл анализа → правка промпта → регрессия.
|
||
|
||
## 9. Открытые вопросы к тебе
|
||
|
||
1. **`/teach` работает только по уже разобранным** документам (из БД) — или нужна и загрузка-разбор прямо на странице? (загрузка-разбор = переиспользование основного пайплайна, сложнее).
|
||
2. **Доступ к `/teach`** — нужен ли пароль/ограничение, или dev-стенд открыт?
|
||
3. **`service_name`** — оставляем как есть (тип услуги) или дополнительно маскируем?
|
||
4. **Кто reviewer** — нужно различать разных проверяющих или достаточно анонимной сессии?
|
||
|
||
---
|
||
|
||
Это план, код не трогал. Скажешь по каким пунктам «делай» (и ответишь на вопросы из §9) — начну с `teach_schema.cfm`.
|
||
|
||
---
|
||
|
||
# ЧАСТЬ 7: Ответ про промпт для Опуса
|
||
|
||
Текст для Опуса хороший — чёткие требования и правильные ограничения (не трогать пайплайн, без online-training, обезличенность). Но в нынешнем виде Опус ответит **обобщённо**, потому что в промпте нет контекста вашего стека и текущей схемы. По каждому из 5 вопросов это критично. Что я бы добавил, прежде чем отправлять:
|
||
|
||
## Чего не хватает Опусу для предметного ответа
|
||
|
||
1. **Стек и маршрутизация.** Lucee/CFML 6.0, файловая маршрутизация (`teach.cfm` → `/teach`), PostgreSQL `baza`. Без этого Опус не поймёт, как «не трогать пайплайн» технически (а ответ — просто новый файл, без правок существующих).
|
||
|
||
2. **Текущая схема — особенно для Q3.** У вас уже есть событийная модель:
|
||
- `spec_events(contract_id, supplement_id, seq, action, target_hash, new_values JSONB, comment, status)`
|
||
- `spec_current`, `contracts`, `supplements`.
|
||
|
||
Связь с исходной операцией = `(contract_id, supplement_id, event_seq)`, и это **уже отвечает на Q3**: ничего в пайплайне трогать не надо, фидбек просто ссылается на `seq` логически, без FK-constraint. Опус должен это знать, иначе предложит схему, не совпадающую с вашей.
|
||
|
||
3. **Форма результата (для Q1/Q2).** Таблица операций `Действие / Услуга / Цена / Кол-во / Сумма / Дата`, режимы `partial | full_replace`. Дайте Опусу реальный пример вывода (твой `допник-1-XXX002`).
|
||
|
||
4. **Ключевой факт для Q4.** Сейчас результат разбора **не штампуется версией промпта** — этого в промпте нет, а это определяет весь ответ на Q4. Без упоминания Опус посоветует «просто сохраняйте prompt_version», не зная, что её сейчас негде взять, кроме «текущей активной».
|
||
|
||
5. **Как агент потом читает БД (для Q1).** Есть read-only эндпоинт `/db/query?sql=…` в db.cfc. Это аргумент за «плоские колонки `error_type/field/prompt_version` + значения в JSONB» — чтобы агрегировалось обычным SQL.
|
||
|
||
## Мелочи по формулировкам
|
||
|
||
- Q5 стоит разнести: отдельно **PII** (имена/реквизиты/№ договора — не хранить) и отдельно **изоляция рабочих данных** (никаких `ALTER`/FK на боевые таблицы, только своя `feedback`). Сейчас они слиты в один вопрос.
|
||
- Добавь явно: *«предложи минимальный набор `error_type` как enum-значения»* — иначе Опус может уйти в свободный текст ошибок, который потом не агрегируется.
|
||
|
||
---
|
||
|
||
Хочешь — могу либо (а) переписать твой промпт для Опуса с этим контекстом, либо (б) сам ответить на все 5 вопросов по вашей реальной схеме (у меня он есть). Скажи, что полезнее.
|
||
|
||
---
|
||
|
||
# ЧАСТЬ 8: Финальный предметный ответ по 5 вопросам
|
||
|
||
Понял, отвечаю по существу — по вашей реальной схеме.
|
||
|
||
## 1. Минимальная схема `feedback` для MVP
|
||
|
||
Плоские колонки для агрегации + JSONB для значений:
|
||
|
||
```sql
|
||
CREATE TABLE IF NOT EXISTS feedback (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
created_at TIMESTAMPTZ DEFAULT now(),
|
||
|
||
-- связь с исходной операцией (логическая, без FK)
|
||
contract_id UUID,
|
||
supplement_id UUID,
|
||
event_seq INTEGER, -- NULL для missed/document
|
||
|
||
scope TEXT NOT NULL, -- 'row' | 'missed' | 'document'
|
||
verdict TEXT NOT NULL, -- 'correct' | 'error'
|
||
error_type TEXT, -- enum-значения, см. ниже
|
||
field TEXT, -- price|qty|sum|name|date_start|action|mode
|
||
|
||
service_name TEXT, -- наименование услуги (обезличено)
|
||
llm_value JSONB, -- что выдала система
|
||
correct_value JSONB, -- как правильно
|
||
|
||
prompt_version TEXT,
|
||
doc_mode TEXT, -- partial | full_replace
|
||
comment TEXT
|
||
);
|
||
CREATE INDEX IF NOT EXISTS idx_feedback_error ON feedback(error_type);
|
||
CREATE INDEX IF NOT EXISTS idx_feedback_prompt ON feedback(prompt_version);
|
||
```
|
||
|
||
Фиксированный набор `error_type` (enum как CHECK или просто документированный список):
|
||
`wrong_price, wrong_qty, wrong_name, wrong_date, extra_row, missed_row, wrong_action, wrong_mode`.
|
||
|
||
Всё. Ни истории версий записи, ни статусов модерации в MVP не нужно.
|
||
|
||
## 2. UI первой версии vs отложить
|
||
|
||
**В MVP:**
|
||
- колонка `⚠` у каждой строки таблицы операций → мини-форма: выбор `error_type` (радио) + опц. «правильное значение» + опц. комментарий;
|
||
- кнопка `[✓ всё верно]` на карточке ДС (дешёвый положительный сигнал, `scope=document, verdict=correct`);
|
||
- кнопка `[➕ пропущена позиция]` (`scope=missed`).
|
||
|
||
**Отложить:**
|
||
- инлайн-редактирование значений прямо в ячейке (пока хватает «правильное значение» в форме);
|
||
- дашборд статистики на самой странице (агрегация — задача агента через SQL, не UI);
|
||
- модерация/удаление чужих замечаний, роли, авторизация по пользователям;
|
||
- разбор-на-лету (загрузка нового документа) — работаем по уже разобранным из БД.
|
||
|
||
## 3. Связь с исходной операцией без вмешательства в pipeline
|
||
|
||
Ключ — **логическая ссылка `(contract_id, supplement_id, event_seq)`**, БЕЗ внешнего ключа на `spec_events`. У вас операции уже лежат в `spec_events(contract_id, supplement_id, seq, action, new_values…)` — `seq` стабилен в рамках договора. `feedback` просто хранит эти три значения как обычные поля.
|
||
|
||
Почему без FK:
|
||
- FK на `spec_events` = риск, что повторный разбор/перезапись событий уронит вставку фидбека или каскадом заденет данные — это и есть «вмешательство». Логическая ссылка развязывает таблицы полностью.
|
||
- `feedback` — отдельная таблица, ничего в боевых не меняем (никаких `ALTER`, никаких триггеров).
|
||
|
||
Если `seq` теоретически может переехать при reset/reparse — дополнительно дублируем в `llm_value` снимок операции (`action` + `new_values`), чтобы замечание оставалось интерпретируемым даже без живого `spec_events`. Этого достаточно.
|
||
|
||
## 4. prompt_version — хранить сразу, но честно
|
||
|
||
Хранить **да** — без неё нельзя мерить регрессию (стало лучше/хуже после правки промпта). Но: сейчас результат разбора версией промпта **не штампуется**, взять её из результата негде.
|
||
|
||
Поэтому для MVP, не трогая pipeline:
|
||
- `/teach` на момент сохранения замечания читает **текущую активную** версию промпта из БД (ту, что Lucee отдаёт как активную) и пишет в `prompt_version`.
|
||
- Это приближение: оно верно, если промпт не менялся между разбором и проверкой. Помечаем это как «версия на момент проверки», не «на момент разбора».
|
||
|
||
Правильная привязка (штамповать `prompt_version` в результат при разборе) — отдельная задача по основному пайплайну, делается позже и по явному решению, не в этом изолированном flow.
|
||
|
||
## 5. Что обязательно исключить
|
||
|
||
**PII / обезличивание — не хранить:**
|
||
- название/ИНН клиента, № договора, ФИО, подписантов, реквизиты, email/телефоны;
|
||
- оригинальный текст документа и байты файла.
|
||
|
||
Храним только тип услуги (`service_name`), числа, тип ошибки, версию промпта. `contract_id/supplement_id` — это UUID (не имена), для внутренней трассировки; в обучающий **экспорт** агента они не попадают.
|
||
|
||
**Изоляция рабочих данных — исключить из дизайна:**
|
||
- любые `ALTER` существующих таблиц, FK-constraint'ы на боевые таблицы, триггеры;
|
||
- запись в `spec_events / spec_current / contracts / supplements` — `/teach` только читает их, пишет исключительно в `feedback`;
|
||
- никакого generic-SQL write-эндпоинта на публичной странице — только параметризованный INSERT в `teach_api.cfm` (защита от инъекций).
|
||
|
||
---
|
||
|
||
Это ответ-проектирование, код не трогаю. Скажешь «делай» — начну с `teach_schema.cfm` (создание таблицы), затем `teach.cfm` и `teach_api.cfm`.
|