2026-05-13 media-toolbox: детальный план агента для unresolved, статус обновлён

This commit is contained in:
Alexey Martemyanov
2026-05-13 13:36:19 +06:00
parent 8d436a0d3c
commit 26d171dd22
+210 -16
View File
@@ -55,21 +55,22 @@ TrueNAS (Taiga)
| Шаг | Статус | Заметки |
|-----|--------|---------|
| 1 — Исследование (Kodi lib на HTPC) | ✅ | Data: мультфильмы(229G) сериалы(116G) movies(673G) bbc(284G). kodi/movies/ — NFO+симлинки уже есть |
| 2 — Wake HTPC + prevent sleep | ✅ | sleep/suspend/hibernate masked (systemctl mask). loginctl linger=yes. Процессы выживают после SSH |
| 3 — Копирование медиа HTPC → Kraken | 🔄 | 4x rsync via systemd-run --user, активны. ~1.3TB total |
| 4 — Transmission на Кракене | 🔲 | уже есть контейнер в /home/kraken/media/ |
| 5 — Перенос torrent-базы с HTPC | ✅ | 7 торрентов Windows Transmission добавлены в Крaken Transmission (paused), пути обновить после rsync |
| 6 — MediaFilesScraper: изучить + настроить | ✅ | Полный Swift 6 SPM. media-pipeline CLI. Пропатчен для Linux (AVFoundation stub, Swift 6 existential fix) |
| 7 — Cross-compile Mac → Linux ARM64 | 🔲 | Swift toolchain для aarch64 |
| 8 — Docker-образ kodi-media-sync на Кракене | 🔲 | |
| 9 — Config: пути, Transmission, категории | 🔲 | |
| 10 — Dry run: проверка сортировки | 🔲 | |
| 11 — Jellyfin: настройка библиотек | 🔲 | уже есть контейнер |
| 12 — Transmission download-complete hook | 🔲 | |
| 13 — Hermes cron на Кракене | 🔲 | |
| 14 — Руtracker-скилл (поиск/закачка) | 🔲 | |
| 15 — Музыка: скачка + sync с TrueNAS | 🔲 | |
| 1 — Исследование (Kodi lib на HTPC) | ✅ | Data: мультфильмы(229G) сериалы(116G) movies(673G) bbc(284G) |
| 2 — Wake HTPC + prevent sleep | ✅ | sleep masked, linger=yes, rsync через systemd-run --user |
| 3 — Копирование медиа HTPC → Kraken | 🔄 | ~58GB/категорию скопировано, ~1.3TB всего, rsync активен |
| 4 — Transmission на Кракене | | 128 торрентов (7 старых + 121 новый), paused, категоризированы |
| 5 — Перенос torrent-базы | ✅ | 121 медиа-торрент импортирован из Windows Downloads |
| 6 — MediaFilesScraper: изучить + настроить | ✅ | Swift 6 SPM, пропатчен для Linux, OpenRouter вместо OpenAI |
| 7 — Docker build ARM64 | | Colima 24GB, `--memory=20g -j 1`, образ 78MB задеплоен |
| 8 — Конфиг на Кракене | | config.json, on-download-complete.sh на месте |
| 9 — Dry run | 🔄 | 1/6 resolved (Black Mirror S06E01 ✅), остальное ждёт rsync |
| 10 — OpenRouter ключ | ⚠️ | Нужно вставить sk-or-v1-... в config.json на Кракене |
| 11 — Агент для unresolved | 🔲 | см. шаг 16 ниже |
| 12 — Jellyfin: настройка библиотек | 🔲 | после rsync |
| 13 — Transmission download-complete hook | ✅ | установлен и прописан |
| 14 — Hermes cron (unresolved notify) | 🔲 | см. шаг 16 |
| 15 — Rutracker-скилл | 🔲 | |
| 16 — Музыка: sync с TrueNAS | 🔲 | |
---
@@ -549,7 +550,200 @@ cat /srv/.../media/movies/SomeMovie/SomeMovie.nfo
---
## Шаг 11 — Jellyfin: настройка библиотек
## Шаг 11 (новый) — Встроенный агент для unresolved файлов
### Концепция
Когда стандартный resolve-пайплайн (TMDB → KP → IMDb → WebSearch) не смог идентифицировать файл, вместо записи "unresolved" — запускается LLM-агент с инструментами. Агент работает как последний уровень: пробует поиск по-другому, подтверждает кандидата, либо честно записывает "не могу".
**Активация:** `media-pipeline agent --config /config/config.json [--path ...]`
Или автоматически после `resolve` если остались unresolved регионы.
---
### Архитектура агента
#### Промпт (системный)
```
You are a media file identifier. You help identify what a file is so it can be
categorized correctly in a media library.
You are given:
- File path and name
- If the file is NOT in a root media folder: full listing of its parent directory
(helps distinguish: single movie, series season pack, documentary collection, etc.)
- Any context already gathered (partial TMDB/KP results, failed queries)
Your job: identify the content and return a structured JSON result.
Tools available:
- search(query) → DuckDuckGo HTML results (titles, URLs, snippets)
- fetch(url) → raw page content (use for TMDB/IMDb/KP specific pages)
Files can be: movie, series, cartoon, documentary, music album, game, book,
software, or other. Support "uncategorizable" as last resort.
Always prefer TMDB ID when available. Think step by step. Use tools to verify.
```
#### Инструменты
```swift
// Поверх существующего DuckDuckGo HTML scraper (уже есть в IMDbAPI/WebSearchStrategy)
struct AgentSearchTool {
func search(_ query: String) async -> [SearchResult] // DDG HTML, top 5
func fetch(_ url: URL) async -> String // curl-like, обрезан до 8k символов
}
```
#### Выходной JSON (с валидацией)
```json
{
"status": "resolved" | "uncategorizable" | "needs_manual",
// Если resolved:
"media_type": "movie" | "series" | "cartoon" | "documentary" |
"music" | "game" | "book" | "software" | "other",
"title": "Название на языке оригинала",
"title_ru": "Русское название (если известно)",
"year": 2024,
// IDs (хотя бы один обязателен для resolved):
"tmdb_id": "12345",
"imdb_id": "tt1234567",
"kp_id": "12345",
// Для сериалов:
"season": 1,
"episode": 5, // если отдельный эпизод
"is_season_pack": true,
"episodes_count": 10,
// Метаданные:
"genres": ["драма", "триллер"],
"confidence": 0.9, // 0.0–1.0, агент сам оценивает
"notes": "...", // почему uncategorizable или что смущает
// Для группировки:
"is_sideband": false, // субтитры, NFO, постер — не самостоятельный контент
"parent_title": "...", // если sideband — к чему относится
}
```
**Валидация:** после каждого ответа LLM — JSON schema check. Если невалидный — повторный запрос с описанием ошибки (до 2 попыток исправить).
---
### Хранилище unresolved файлов
```
/srv/.../docker/media-pipeline/unresolved.json
```
```json
[
{
"path": "/media/downloads/complete/Знакомимся с кабанами",
"added_at": "2026-05-13T04:00:00Z",
"last_attempt": "2026-05-13T04:00:00Z",
"attempts": 1,
"agent_notes": "Не нашлось в TMDB/KP, агент тоже не смог — слишком общее название",
"status": "needs_manual"
}
]
```
---
### Force-resolve через CLI
```bash
# Разрезолвить конкретный файл вручную (из файла)
media-pipeline resolve-manual \
--config /config/config.json \
--path "/media/downloads/complete/Знакомимся с кабанами" \
--result '{"status":"resolved","media_type":"documentary","title":"Meet the Boars","tmdb_id":"12345",...}'
# Или из файла (удобно для скилла Кракена):
media-pipeline resolve-manual \
--config /config/config.json \
--from-file /tmp/manual-resolve.json
```
Что делает `resolve-manual`:
1. Берёт готовый JSON результат
2. Применяет (создаёт NFO, перемещает если нужно, обновляет Transmission пути)
3. Удаляет запись из `unresolved.json`
4. Пишет в лог
---
### Cron: почасовой обход + Hermes notify
**Скрипт-чекер** `/srv/.../docker/media-pipeline/check-unresolved.sh`:
```bash
#!/bin/bash
# Запускает agent только если есть новые unresolved файлы
UNRESOLVED=/srv/dev-disk-by-uuid-49e8f586-3839-4c5d-a1e1-58bfc3579ade/docker/media-pipeline/unresolved.json
# Нет файла — нечего делать
[ -f "$UNRESOLVED" ] || exit 0
# Считаем needs_manual записи
COUNT=$(jq '[.[] | select(.status == "needs_manual")] | length' "$UNRESOLVED" 2>/dev/null || echo 0)
# Нет needs_manual — тихо выходим (Hermes не получит пустое сообщение)
[ "$COUNT" -gt 0 ] || exit 0
echo "UNRESOLVED_COUNT=$COUNT"
# Вывод идёт в Hermes cron prompt как контекст
jq '[.[] | select(.status == "needs_manual") | {path, added_at, agent_notes}]' "$UNRESOLVED"
```
**Hermes cron** (каждый час, Кракен):
```
Schedule: 0 * * * *
Script: /srv/.../docker/media-pipeline/check-unresolved.sh
Prompt:
Скрипт вернул список нераспознанных медиафайлов (вывод выше).
Для каждого файла:
1. Посмотри на путь и agent_notes
2. Если можешь идентифицировать (поиск, здравый смысл) — вызови:
docker run --rm ... media-pipeline resolve-manual --path "..." --result '{...}'
3. Если не можешь — отправь уведомление в Telegram с описанием файла и попроси
меня разрезолвить вручную.
Не присылай сообщение если всё уже разрезолвлено или список пустой.
```
Скрипт запускается **до** обращения к модели — если пустой вывод, Hermes не получает запрос. Нет пустых сообщений.
---
### Реализация в media_files_db
**Новые файлы:**
```
Sources/MediaPipeline/Agent/
AgentCommand.swift ← CLICommand: media-pipeline agent
AgentSession.swift ← LLM loop: prompt → tools → validate → save
AgentTools.swift ← search() + fetch() wrappers
AgentResultSchema.swift ← AgentResult struct + JSON validation
UnresolvedStore.swift ← чтение/запись unresolved.json
Sources/MediaPipeline/ResolveManual/
ResolveManualCommand.swift ← media-pipeline resolve-manual
```
**Зависимости уже есть:**
- `ChatGPTAPI` — LLM клиент (переключён на OpenRouter)
- `IMDbAPI` / DDG HTML scraper — search tool
- `HTTPClient` — fetch tool
- Кеш — все LLM вызовы кешируются через `ChatGPTAPI.sendPrompt(cacheKey:)`
**Оценка объёма:** ~400-500 строк Swift, 2-3 дня работы.
---
Jellyfin уже работает на Кракене: http://192.168.1.14:8096