Я веду базу знаний по тайским визам и пишу справочный контент с помощью LLM. Ниша неприятная тем, что ошибка в цифре это не опечатка, а чей-то испорченный переезд: человек прочитает про «минимальный остаток 300 тысяч бат», принесёт консульству на 200 тысяч меньше, чем нужно, и получит отказ с невозвратным сбором. Поэтому за год у меня собрался пайплайн, в котором модель физически не может протащить выдуманное число в публикацию. Расскажу архитектуру: она не привязана ни к визам, ни к конкретной модели, и подойдёт любой нише, где факты дороже стиля медицина, налоги, финансы.
Почему «не выдумывай» в промпте не работает
Начну с наблюдения из практики. Визовые правила волатильны, а поисковая выдача по ним токсична: SEO-сайты штампуют «обновления», не ссылаясь ни на что. В моём ресёрче был показательный случай: по запросу об изменениях одной визы на 2026 год выдача возвращала несколько страниц одного и того же сайта, противоречащих друг другу одна писала про ужесточение финансовых требований, вторая про него не знала, и ни одна не ссылалась на официальный источник.
Теперь представьте, что на этом корпусе учат модель или, хуже, дают ей это в RAG. Модель не врёт в привычном смысле, она честно усредняет мусор. Инструкция «не выдумывай факты» тут бесполезна: модель и не считает, что выдумывает. Значит, запрет должен быть не в промпте, а в архитектуре.
Принцип 1: у чисел есть единственный источник истины, и это не модель
Центральная идея: в генерируемом тексте не бывает чисел «из головы». Все факты живут в базе записей примерно такой структуры:
{
"key": "visa.finance.min",
"value": 500000,
"display": "500,000 THB",
"updatedAt": "2026-07-17",
"source": "https://image.mfa.go.th/.../Checklist.pdf",
"attribution": "official",
"approvedBy": ["reviewer"],
"history": [
{"value": 400000, "updatedAt": "2024-05-01", "source": "..."}
]
}
Модель при генерации не «вспоминает» сумму, а подставляет запись по ключу. Гейт на выходе проверяет обратное соответствие: каждое число в тексте обязано матчиться на запись из базы. Число без записи блокировка, независимо от того, насколько правдоподобно оно выглядит. Особенно если правдоподобно: самые опасные галлюцинации те, что похожи на правду.
Приятный побочный эффект поле history. Когда сбор меняется, старое значение не затирается, а уезжает в историю, и из этого бесплатно рождается контент, которого нет у конкурентов: «сбор вырос с X до Y в таком-то месяце» это данные, которые нельзя нагуглить одной страницей.
Принцип 2: у каждого факта есть уровень доверия, и он не повышается молча
Вторая типовая ошибка LLM-контента тоньше галлюцинаций: смешение статусов. «Требуется страховка» это официальное требование, практика конкретного консульства или чей-то пересказ с форума? Для читателя разница критическая, а модель радостно нивелирует её до уверенного «требуется».
Поэтому каждая запись в базе несёт атрибуцию одного из трёх уровней: официальное требование (только со ссылкой на госисточник), практика (наблюдение с датой: «в последних кейсах консульство X запрашивало...») или внутренний стандарт («мы дополнительно просим...»). Гейт проверяет не только само число, но и уровень: если в тексте практика подана языком официального требования блокировка. Повышение уровня факта это отдельное осознанное действие человека с новым источником, а не решение генератора.
Принцип 3: fail-closed, или почему сомнение = отказ
Все проверки пайплайна работают по принципу fail-closed: не удалось однозначно подтвердить считается ошибкой. Это противоположность типовым «мягким» ревью, где сомнительное пропускают с пометкой.
Технически гейт это отдельный проход (и отдельный контекст!) от того, что писал текст. Проверяющая инстанция получает текст и базу фактов, а не черновики и рассуждения автора: у неё нет соблазна «понять, что имелось в виду». Проверяется три класса вещей: соответствие чисел базе, соответствие уровней атрибуции и стоп-лист.
Про стоп-лист стоит сказать отдельно. В каждой YMYL-нише есть формулировки, которые нельзя публиковать никогда (у меня это обещания «гарантированного одобрения» во всех вариациях). Наивная реализация регэкспы не работает: «завизируем без отказов» регэксп по слову «гарантия» не поймает. Поэтому стоп-лист матчится семантически, самой же моделью: «найди в тексте утверждения, эквивалентные по смыслу следующим запрещённым». LLM, которой нельзя доверить факты, отлично справляется с поиском перефразировок это ровно та задача, где она сильна.
Принцип 4: факты протухают, и это надо признать схемой
Визовый сбор, который я проверила в феврале, в августе уже нельзя считать проверенным. В базе это закреплено жёстко: запись старше 90 дней считается недействительной и не проходит гейт, пока её не переподтвердят по источнику. Чтобы не жить в вечном пожаре, есть очередь переверификации с буфером: всё, что старше 75 дней, попадает в план проверки заранее.
Из всех принципов этот самый противный в эксплуатации и самый важный. База фактов без TTL деградирует молча: через год она выглядит солидно и врёт в каждой третьей записи.
Принцип 5: мифы проверяются адверсариально
Отдельный контур работа с «общеизвестными фактами» ниши, которые кочуют по форумам. Схема: несколько независимых проверяющих агентов получают утверждение и задачу опровергнуть его по первоисточникам, не видя выводов друг друга. Дальше консенсус: подтверждение засчитывается, только если независимые проходы сошлись.
Живой пример: в моей нише все «знают», что деньги для визы должны «отлежаться» на счету 90 дней. Три независимых прохода по официальным чек-листам дали ноль подтверждений: правила такого не существует, это фольклор, выросший из практики отдельных консульств. В базу утверждение попало с атрибуцией «миф» и это тоже контент, причём самый цитируемый.
Человек в контуре: последняя миля не автоматизируется
Финальное «опубликовать» в пайплайне нажимает человек, и у записей есть поле approvedBy, которое заполняется только вручную. Это не дань бюрократии, а честность по поводу пределов автоматизации: гейт ловит несоответствия базе, но не ловит несоответствие базы реальности. Если я неправильно прочитала PDF консульства, все автоматические проверки радостно сойдутся на неправильном числе.
По этой же причине ведётся audit trail: у каждого факта видно, откуда он взялся, когда проверялся и кто одобрил. Когда через полгода находишь странную запись, история отвечает на вопрос «что мы вообще думали», не поднимая переписок.
Чего пайплайн не умеет (честный раздел)
Не ловит ошибки в первоисточнике: официальный сайт тоже бывает неправ или неоднозначен. Не решает проблему интерпретации: «выписка за 3 месяца» календарных или банковских? Тут спасает только практика и фиксация трактовок как отдельных записей. И не защищает от лени оператора: fail-closed гейт, который надоел и который начали «пропускать руками», хуже его отсутствия, потому что создаёт ложное чувство безопасности.
Итого
Скелет переносимый: единый источник истины для чисел с обязательными источниками и датами; уровни доверия как часть схемы данных; проверяющий проход, отделённый от генерирующего, с правилом «сомнение = блок»; TTL на каждый факт; адверсариальная проверка фольклора; человек на финальном гейте. Ничего из этого не требует конкретной модели или фреймворка это дисциплина данных, а не ML-магия.
Интересен опыт тех, кто решает похожую задачу в других нишах: как вы храните волатильные факты для генерации и что делаете с их протуханием? И отдельный вопрос к тем, кто пробовал semantic-стоп-листы: на каком проценте ложных срабатываний вы остановились как на приемлемом?