- контракт с моделью переводится на адресацию файла номером строки списка, усечение промпта снимается, лимиты уходят в конфиг - раскладка показывает все файлы раздачи, покрытие плана блокирует авто только при непокрытом видеофайле
250 lines
19 KiB
Markdown
250 lines
19 KiB
Markdown
## MODIFIED Requirements
|
||
|
||
### Requirement: Разбор сигналов LLM в структурированный план
|
||
|
||
Система SHALL передавать LLM недоверенные сигналы (имя торрента, дерево файлов с
|
||
размерами, текстовый контекст и накопленные подсказки, пред-парс) и получать
|
||
структурированный план в схеме: `type` (`movie`|`series`), `title`,
|
||
`original_title`, `year`, `provider_hint`, `files[]` и `confidence`. Каждый
|
||
элемент `files[]` SHALL адресовать файл раздачи номером строки из напечатанного
|
||
системой списка файлов, а не копией пути, и нести `role`
|
||
(`main`|`episode`|`subtitle`|`extra`|`sample`|`ignore`) и, для сериала,
|
||
per-file `season`/`episode` (отдельного скалярного `season` быть SHALL NOT — так
|
||
выражаются мультисезонные паки и спецвыпуски). Путь файла (`src`) SHALL
|
||
подставлять сама система резолвом номера по своему списку. Ответ, где элемент
|
||
`files[]` несёт путь вместо номера, система SHALL принимать как запасной формат,
|
||
проверяя совпадение пути с реальным файлом торрента. Элемент, несущий и номер, и
|
||
путь, система SHALL резолвить по номеру; несовпадение присланного пути с
|
||
резолвом номера SHALL давать причину ухода в `review` (сигнал сдвига адресации),
|
||
а не молчаливое затирание.
|
||
|
||
Негодный элемент `files[]` — номер вне диапазона списка, повторная адресация уже
|
||
адресованного файла, отсутствие и номера, и пути — система SHALL отбрасывать с
|
||
причиной ухода в `review`, сохраняя остальной план; при повторной адресации в
|
||
плане SHALL оставаться первый элемент. Отбраковка отдельных элементов SHALL NOT
|
||
считаться ошибкой разбора и SHALL NOT порождать повторный запрос к модели. План,
|
||
в котором после отбраковки не осталось ни одного файла, система SHALL считать
|
||
неразобранным.
|
||
|
||
Порядок списка файлов SHALL быть свойством самого распознавания, а не
|
||
дисциплиной вызывающего: распознавание SHALL приводить полученный список к
|
||
детерминированному порядку само, печатать промпт и резолвить номера по одному и
|
||
тому же упорядоченному списку. Повторное распознавание той же раздачи SHALL
|
||
давать ту же нумерацию.
|
||
|
||
План MAY дополнительно нести опциональное скалярное поле `director` (режиссёр).
|
||
Это поле НЕ требуется от LLM и НЕ участвует в структурной валидации или гейте
|
||
авто-раскладки; его заполняют подтверждённый матч метабазы (авто) или закреплённый
|
||
в ревью выбранный источник (через override, см. `metadata-match`/`review`) как
|
||
недоверенное косметическое значение для вывода отображаемого имени. Как недоверенное
|
||
человекочитаемое поле, `director` SHALL NOT входить в plan-санитайзинг (он чистит
|
||
`title`/`original_title`/`provider_hint`); очистка режиссёра применяется при рендере
|
||
имени. Пустой `director` SHALL быть штатным (режиссёр неизвестен).
|
||
|
||
#### Scenario: План сериала с per-file нумерацией
|
||
|
||
- **GIVEN** сезон-пак из 10 видеофайлов
|
||
- **WHEN** LLM возвращает план
|
||
- **THEN** `type` = `series`, а каждый видеофайл несёт свои `season`/`episode`
|
||
|
||
#### Scenario: Номер файла резолвится в путь
|
||
|
||
- **GIVEN** список из 180 файлов, напечатанный в промпте
|
||
- **WHEN** модель возвращает элемент плана с номером 13
|
||
- **THEN** система подставляет в `src` путь тринадцатого файла своего списка
|
||
|
||
#### Scenario: Номер вне диапазона отбрасывает элемент, а не план
|
||
|
||
- **GIVEN** раздача из 180 файлов и ответ модели, где один элемент несёт номер 181,
|
||
а остальные 179 корректны
|
||
- **WHEN** план разбирается
|
||
- **THEN** негодный элемент отброшен, остальные 179 файлов остаются в плане
|
||
- **AND** задача уходит в `review` с причиной, называющей отброшенный элемент
|
||
- **AND** повторный запрос к модели не выполняется
|
||
|
||
#### Scenario: Повторная адресация одного файла
|
||
|
||
- **GIVEN** ответ модели, где два элемента адресуют один и тот же файл
|
||
- **WHEN** план разбирается
|
||
- **THEN** в плане остаётся первый элемент, второй отброшен
|
||
- **AND** задача уходит в `review` с причиной, называющей повторную адресацию
|
||
|
||
#### Scenario: Все элементы негодны — план не разобран
|
||
|
||
- **GIVEN** ответ модели, где ни один элемент `files[]` не адресует реальный файл
|
||
- **WHEN** план разбирается
|
||
- **THEN** план не принимается как валидный
|
||
|
||
#### Scenario: Путь вместо номера принимается как запасной формат
|
||
|
||
- **GIVEN** ответ модели, где элемент `files[]` несёт путь файла вместо номера
|
||
- **WHEN** план разбирается
|
||
- **THEN** разбор успешен, если путь совпадает с реальным файлом торрента
|
||
- **AND** correction-ретрай не выполняется
|
||
|
||
#### Scenario: Номер и путь одновременно — главенствует номер
|
||
|
||
- **GIVEN** элемент `files[]`, несущий и номер, и путь, которые указывают на разные файлы
|
||
- **WHEN** план разбирается
|
||
- **THEN** `src` берётся резолвом номера
|
||
- **AND** задача уходит в `review` с причиной о несовпадении присланного пути с резолвом
|
||
|
||
#### Scenario: Несуществующий src отклоняется
|
||
|
||
- **GIVEN** ответ LLM, где `files[].src` не совпадает ни с одним файлом торрента
|
||
- **WHEN** план разбирается
|
||
- **THEN** такой элемент отбрасывается с причиной, а посторонний путь в план не попадает
|
||
|
||
#### Scenario: Нумерация не зависит от вызывающего
|
||
|
||
- **GIVEN** два вызова распознавания одной раздачи, получившие список файлов в разном порядке
|
||
- **WHEN** собирается промпт
|
||
- **THEN** напечатанная нумерация в обоих вызовах одинакова
|
||
|
||
#### Scenario: Режиссёр не требуется от LLM и не влияет на гейт
|
||
|
||
- **GIVEN** ответ LLM без поля `director`
|
||
- **WHEN** план разбирается и оценивается
|
||
- **THEN** разбор успешен, `director` пуст
|
||
- **AND** отсутствие режиссёра не влияет на структурную валидацию и решение
|
||
auto/review
|
||
|
||
### Requirement: Провайдер LLM за абстракцией со структурированным выводом
|
||
|
||
Доступ к LLM SHALL быть за интерфейсом с выбором реализации по полю `[llm].type`
|
||
(первый тип — `openai-compat`). Система SHALL запрашивать JSON-режим
|
||
(`response_format: {"type":"json_object"}`), срезать ```-ограждения и
|
||
валидировать ответ в Go против схемы плана. При ошибке разбора система SHALL
|
||
ретраить до `[llm].max_retries`, передавая модели саму ошибку и схему. Если после
|
||
ретраев ответ не разобран, задача SHALL уходить в `review` (НЕ в `failed`) с
|
||
причиной «ответ LLM не разобран».
|
||
|
||
Повторный запрос SHALL NOT переприсылать список файлов раздачи: список назван в
|
||
первом сообщении диалога, и его номера сохраняют смысл на всех попытках. Размер
|
||
запроса SHALL NOT расти пропорционально числу попыток.
|
||
|
||
Признак завершения генерации (`finish_reason`) SHALL доходить до распознавания.
|
||
Ответ, оборванный по длине, система SHALL уводить в `review` с отдельной
|
||
причиной «ответ модели обрезан» и SHALL NOT повторять запрос ни тем же промптом,
|
||
ни его вариантом: обрыв означает, что ответ не поместился, а не что модель
|
||
ошиблась.
|
||
|
||
#### Scenario: Неразобранный ответ уходит в review
|
||
|
||
- **GIVEN** LLM, чей ответ не проходит валидацию схемы после всех ретраев
|
||
- **WHEN** завершается распознавание
|
||
- **THEN** задача переходит в `review` с причиной «ответ LLM не разобран»
|
||
- **AND** задача НЕ переходит в `failed`
|
||
|
||
#### Scenario: Повторная попытка не переприсылает список файлов
|
||
|
||
- **GIVEN** раздача из 180 файлов и ответ модели с ошибкой разбора
|
||
- **WHEN** выполняется correction-ретрай
|
||
- **THEN** список файлов раздачи повторно не печатается
|
||
- **AND** размер запроса второй попытки сопоставим с размером первой
|
||
|
||
#### Scenario: Обрыв по длине не ретраится
|
||
|
||
- **GIVEN** ответ модели с признаком обрыва генерации по длине
|
||
- **WHEN** завершается распознавание
|
||
- **THEN** задача переходит в `review` с причиной «ответ модели обрезан»
|
||
- **AND** повторных запросов к модели не выполняется
|
||
|
||
### Requirement: Модель уверенности и решение auto/review
|
||
|
||
Система SHALL раскладывать автоматически (без review) только при выполнении
|
||
ВСЕГО: (1) подтверждённый единичный сильный матч в базе (`metadata-match`) с
|
||
`provider_id`; (2) структурная валидация без предупреждений (фильм — ровно один
|
||
основной видеофайл; сериал — число серий бьётся с базой, нумерация S·E
|
||
консистентна); (3) согласованность пред-парса и LLM по типу/названию/году. Иначе
|
||
задача SHALL уходить в `review` с явной причиной. Самооценку LLM (`confidence`)
|
||
система SHALL учитывать лишь как вспомогательный сигнал, НЕ как единственный гейт.
|
||
|
||
Список причин распознавания SHALL быть неоднородным: кроме блокирующих причин он
|
||
MAY содержать информационные строки, которые показываются человеку и сохраняются
|
||
вместе с распознаванием, но авто-раскладку НЕ отменяют — такова сводка покрытия
|
||
плана (см. «Причины по нумерации серий и покрытию плана»). Поэтому решение
|
||
auto/review система SHALL считать по **блокирующим** причинам, а НЕ по длине
|
||
списка причин: непустой список сам по себе SHALL NOT означать `review`.
|
||
|
||
#### Scenario: Нет матча в базе — всегда review
|
||
|
||
- **GIVEN** план без подтверждённого матча в базе (база выключена или матча нет)
|
||
- **WHEN** принимается решение auto/review
|
||
- **THEN** задача уходит в `review`, авто-раскладка не делается
|
||
|
||
#### Scenario: Матч и чистая валидация — авто
|
||
|
||
- **GIVEN** подтверждённый единичный матч, чистая структурная валидация и
|
||
согласованность сигналов
|
||
- **WHEN** принимается решение
|
||
- **THEN** допускается авто-раскладка (при отсутствии `force_review`)
|
||
|
||
#### Scenario: Информационная причина авто не отменяет
|
||
|
||
- **GIVEN** подтверждённый матч и чистая валидация, но покрытие плана неполно
|
||
только за счёт файлов-спутников
|
||
- **WHEN** принимается решение auto/review
|
||
- **THEN** список причин непуст — в нём сводка покрытия
|
||
- **AND** авто-раскладка допускается
|
||
|
||
## ADDED Requirements
|
||
|
||
### Requirement: Полный список файлов раздачи в промпте
|
||
|
||
Промпт распознавания SHALL включать все файлы раздачи, а не фиксированную их
|
||
часть. Предельное число файлов и предельный размер ответа модели SHALL задаваться
|
||
настройками `[recognition].max_files` и `[recognition].max_tokens`; предел числа
|
||
файлов служит предохранителем от аномальной раздачи, а не рабочим ограничением, и
|
||
его значение по умолчанию SHALL соответствовать размеру промпта, который заведомо
|
||
принимает модель. Если список всё же усечён пределом, распознавание SHALL называть
|
||
усечение отдельной причиной ухода в `review`. Отказ модели, вызванный размером
|
||
запроса, распознавание SHALL называть причиной о размере запроса, а не текстом
|
||
провайдера.
|
||
|
||
#### Scenario: Раздача на сотни файлов показана модели целиком
|
||
|
||
- **GIVEN** раздача из 180 файлов и `max_files` = 500
|
||
- **WHEN** собирается промпт распознавания
|
||
- **THEN** в списке файлов промпта присутствуют все 180 файлов
|
||
|
||
#### Scenario: Усечение названо причиной
|
||
|
||
- **GIVEN** раздача, число файлов которой превышает `max_files`
|
||
- **WHEN** завершается распознавание
|
||
- **THEN** задача уходит в `review`, и среди причин названо усечение списка файлов
|
||
|
||
### Requirement: Причины по нумерации серий и покрытию плана компактны
|
||
|
||
Причины ухода в `review`, порождённые нумерацией серий, SHALL быть свёрнуты: на
|
||
сезон приходится не более одной причины, перечисляющей недостающие серии.
|
||
|
||
Распознавание SHALL называть покрытие плана — сколько файлов раздачи попало в
|
||
план из общего числа, — когда покрыты не все файлы. Блокировать авто-раскладку
|
||
SHALL только непокрытый видеофайл: файл, который модель не адресовала и который
|
||
по расширению является видео, означает потерянную серию или фильм. Непокрытые
|
||
файлы-спутники (субтитры, изображения, тексты, служебные файлы) SHALL
|
||
показываться в покрытии, но SHALL NOT входить в структурную валидацию и SHALL NOT
|
||
влиять на решение auto/review: модель вправе не перечислять то, что не
|
||
раскладывается.
|
||
|
||
#### Scenario: Пропуски сезона свёрнуты в одну причину
|
||
|
||
- **GIVEN** план сезона, где недостают серии E05, E07 и E11
|
||
- **WHEN** формируются причины ухода в review
|
||
- **THEN** этому сезону соответствует одна причина, перечисляющая недостающие серии
|
||
|
||
#### Scenario: Непокрытый видеофайл блокирует авто
|
||
|
||
- **GIVEN** раздача, где один видеофайл не адресован ни одним элементом плана
|
||
- **WHEN** принимается решение auto/review
|
||
- **THEN** задача уходит в `review` с причиной о непокрытом видеофайле
|
||
|
||
#### Scenario: Непокрытые файлы-спутники авто не блокируют
|
||
|
||
- **GIVEN** раздача, где не адресованы только `.nfo`, скриншоты и текстовый файл,
|
||
а все видеофайлы покрыты, матч подтверждён и валидация чиста
|
||
- **WHEN** принимается решение auto/review
|
||
- **THEN** авто-раскладка допускается
|
||
- **AND** покрытие плана показано человеку, но причиной ухода в review не является
|