Цитата, которую модель не подделает
Дешёвая модель придумывала ID цитат и таймкоды на пустом месте. Помогла не модель поумнее, а то, что мы вообще перестали показывать ей ID.
В чате каждая цитата ассистента — это чип. Нажмите на него, и запись заиграет с нужного места. Чтобы это работало, за каждым чипом стоят настоящий 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
Часть проекта
Слушай Садху