← Журнал
Jiva Studio

Цитата, которую модель не подделает

Дешёвая модель придумывала ID цитат и таймкоды на пустом месте. Помогла не модель поумнее, а то, что мы вообще перестали показывать ей ID.

Слушай СадхуSakhaEngineeringAI

В чате каждая цитата ассистента — это чип. Нажмите на него, и запись заиграет с нужного места. Чтобы это работало, за каждым чипом стоят настоящий ID лекции и настоящие таймкоды: с какой миллисекунды начать и на какой закончить.

Что ломалось

Модель всё придумывала. Первые две-три цитаты были настоящими. Дальше она начинала генерировать похожие по виду ID (track_iL7KjU5Lp4pZ), которых в каталоге не было. Иногда её заносило в BG_1972_03.05 — этот формат она усвоила из религиозных текстов ещё на предобучении. Таймкоды она тоже выдумывала: могла поставить @5000000-5060000 на лекцию длиной тридцать минут. Пользователь нажимал — и приложение писало: «не удалось загрузить аудио».

Почему? В результатах поиска модель видит паттерн: track_ + четырнадцать случайных символов + @ + два больших числа. Дешёвая модель (Gemini Flash Lite, по $0.10 за миллион токенов) по аналогии продолжает штамповать токены такой же формы. С каталогом это никак не связано. И ID, и числа — просто статистическая имитация.

Мы пробовали вызов инструментов, ограниченную генерацию, жёсткий промпт с MANDATORY/FORBIDDEN. На дешёвых моделях ничего толком не сработало.

Решение: сузить поверхность

Главное открытие: сузить поверхность для галлюцинаций. Не научить модель не врать, а лишить её такой возможности.

Раньше модель видела в результатах поиска полную запись из каталога:

{ "track_id": "track_OkPV…", "start_ms": 630560, "end_ms": 684400, "text": "…" }

— и писала [cite:track_OkPV…@630560-684400|the perfection of life], где половину придумывала.

Теперь модель видит только текст, пронумерованный:

[1] "Bhakti is the path of service…"
[2] "Devotional service purifies…"
[3] …

Ни ID, ни миллисекунд. Карту сервер держит у себя: каждая тройка (track_id, start_ms, end_ms) получает короткий порядковый номер — целочисленный псевдоним, начиная с единицы. Чтобы сослаться на заметку, модель пишет это число обратно как сноску — [^1]. На выходе сервер разворачивает её в настоящий [cite:track_OkPV…@630560-684400|the perfection of life] — прямо перед тем, как маркер уйдёт клиенту. В промпте сказано прямо:

To cite a note, write the exact same [^N] — copy the integer verbatim,
do not invent or increment.

Меняется всё принципиально. Модель физически не может выдумать ни ID, ни время: она работает с одним коротким числом, которое уже есть в контексте. Напишет [^7], где заметок всего пять, — число сразу отбрасывается. Свалиться в BG_1972_03.05 тоже не выйдет: в окне нет ни одного track_…, чтобы скопировать паттерн.

Бонус: уточняющие вопросы всё ещё работают

Эту карту псевдонимов мы держим и на фронтенде, прикреплённой к каждому сообщению. На следующем шаге клиент отправляет её обратно, сервер сворачивает прежние чипы в форму [^N], и модель видит прошлые цитаты в том же пронумерованном виде. Поэтому она понимает просьбу «расскажи подробнее про ту первую цитату». Без такой карты прежние ссылки развернулись бы в нечитаемое месиво из полных ID, и модель потеряла бы нить между новым вопросом и тем, что было сказано раньше.

Урок

Можно решить это в лоб — купить модель в тридцать раз дороже. А можно менять не модель, а то, что она видит. Второй путь дешевле в эксплуатации. А значит, доступнее для пользователей и проще масштабируется. Качество не обязано быть дорогим. Оно может быть результатом архитектурного решения, а не размера модели.


Что почитать: Структурированные выводы модели (документация OpenAI) · Vectara Hallucination Leaderboard


Часть проекта

Слушай Садху

Открыть проект