Почему AI неправильно понимает длинные тексты
Генерирую описание для 2.5-часового стрима. В заголовки глав пролез GitHub. Codex — нет. При том что Codex я активно демонстрировал треть видео.
Что случилось
У меня есть многопроходной пайплайн для генерации YouTube-метаданных. Четыре стадии: разбить транскрипт на куски по 5-10 минут, извлечь сущности из каждого куска параллельно, объединить и ранжировать, сгенерировать финальные заголовки и описание.
На стадии агрегации пайплайн ранжировал сущности по concentration_score. Формула примитивная — сколько раз упомянут, в скольких кусках появился.
Обработал стрим “Opus 4.6 и GPT 5.3”. Смотрю на результат:
- GitHub: 22 упоминания, 8 чанков, покрывает 38.6% времени видео
- Codex: 31 упоминание, 6 чанков, покрывает 29.8% времени
GitHub — в заголовках. Codex — нет.
Но подождите. GitHub я упоминал мимоходом — в списках инструментов, в контексте “закоммить на GitHub”. А Codex — запускал, демонстрировал, клонировал игры, тестировал подписки. Тридцать процентов видео.
Spread vs depth
Проблема оказалась фундаментальной. Метрика не различала “назвал в списке” и “демонстрировал 20 минут”.
GitHub: ■□■□■□■□■□■□■□■□■□■□■ (мелькает везде, но мимоходом)
Codex: □□□□□□□□□□■■■■■■□□□□□ (концентрированно и глубоко)
GitHub разбросан по 8 из 21 чанка — 38% видео. Но в каждом чанке это просто упоминание: “залить на GitHub”, “открыть репозиторий”. Одно слово в потоке.
Codex сконцентрирован в 6 чанках — 30% видео. Но в каждом я его активно использую: генерирую проекты, клонирую Vampire Survivors, тестирую лимиты.
Метрика видела spread. Не видела depth.
Похоже на проблему TF-IDF в информационном поиске. Наивная TF (term frequency) считает слова — и common words типа “the” доминируют. IDF (inverse document frequency) фильтрует шум, взвешивая по редкости. Мой concentration_score был наивной TF — считал mentions без учёта того, насколько глубоко entity вовлечён.
Пять фиксов
1. Новая формула
relevance_score = avg_role × time_share × total_mentions
Три множителя вместо двух. avg_role — средняя глубина вовлечённости (от 1 до 3). time_share — доля видео, где entity присутствует. total_mentions — суммарные упоминания.
По-моему, это ≈ TF-IDF для видео. avg_role × time_share фильтрует шум как IDF. total_mentions — это TF.
2. Роли на стадии извлечения
Раньше Stage 2 считал только количество упоминаний. Добавил роль — как именно entity используется в каждом куске:
| Role | Что значит | Пример |
|---|---|---|
| 3 | main_focus | Спикер демонстрирует Codex, строит проект |
| 2 | actively_used | Деплоит на Railway как инструмент |
| 1 | mentioned | “Можно залить на GitHub” в списке |
GitHub в большинстве чанков получал role=1. Codex — role=2 и role=3.
3. Activity-first заголовки
Раньше: берём самую популярную entity → ставим в заголовок.
Теперь: сначала описываем что спикер ДЕЛАЕТ → entity появляется только если она в фокусе.
BEFORE AFTER
───── ─────
"GitHub и инструменты" → "Регистрация на хакатон в боте"
"Сравнение моделей: Opus → "Тестирование Codex и подписки"
vs Codex"
4. Жёсткие пороги
Entity попадает в заголовок только если выполнены ВСЕ три условия:
title_eligible = (
time_share >= 0.10 # присутствует в ≥10% видео
and avg_role >= 2.0 # активно используется, не просто упоминается
and total_mentions >= 10 # достаточно упоминаний
)
GitHub: time_share=38.6% ✓, mentions=22 ✓, но avg_role=1.75 ✗. Не прошёл.
Codex: time_share=29.8% ✓, mentions=31 ✓, avg_role=2.33 ✓. Прошёл.
5. Детектор фабрикаций
Заголовки LLM любит приукрашивать. Поэтому добавил два валидатора.
Narrative fabrication detector — ловит паттерны выдуманных нарративов:
- “от X до Y” — journey, которого не было
- “батл” / “vs” — сравнение, которого не проводили
- “полный гайд” — маркетинговый язык
Keyword grounding — больше 50% значимых слов в заголовке должны встречаться в анализах чанков. Если слово не из транскрипта — заголовок галлюцинирует.
Когда агент сгенерировал “Сравнение моделей: Opus vs Codex” — валидатор поймал паттерн “vs” и зарубил. Заменили на “Тестирование Codex и подписки” — activity-first, grounded.
Что получилось
Данные из реального прогона:
Entity mentions chunks time_share avg_role score title?
───────────────────────────────────────────────────────────────────────
Claude Code 28 8 36.6% 2.50 25.62 ✓
Codex 31 6 29.8% 2.33 21.54 ✓
GitHub 22 8 38.6% 1.75 14.86 ✗
Agent Teams 16 3 14.4% 2.67 6.16 ✓
Railway 14 3 14.6% 2.67 5.44 ✓
Warp — — <10% 1.00 — ✗
Claude Code — основной инструмент стрима, score=25.62. Codex — второй по значимости, score=21.54. GitHub — третий по score, но avg_role=1.75 < 2.0, в заголовки не попал. Warp — периферийный, даже порог time_share не прошёл.
21 глава стрима. Заголовки соответствуют тому, что я делал, а не тому, что мимоходом называл.
Вывод
Метрика без глубины взаимодействия — мусор. Как наивная TF без IDF: common words доминируют, важное теряется.
avg_role × time_share × mentions решает ту же задачу для видео, что TF-IDF для текста. Глубина вовлечённости (role) и временное покрытие (time_share) фильтруют шум. Только entities с role ≥ 2.0 и time_share ≥ 10% попадают в заголовки.
GitHub корректно отфильтрован. Codex корректно в заголовках. Пайплайн перестал врать про содержание видео.
Подписаться на обновления — @sereja_tech