Модель не виновата: разбираем 3 громких ИИ-инцидента, которые случились из-за отсутствия архитектуры. anthropic.. anthropic. Claude.. anthropic. Claude. llm.. anthropic. Claude. llm. Natural Language Processing.. anthropic. Claude. llm. Natural Language Processing. Production.. anthropic. Claude. llm. Natural Language Processing. Production. structured outputs.. anthropic. Claude. llm. Natural Language Processing. Production. structured outputs. Анализ и проектирование систем.. anthropic. Claude. llm. Natural Language Processing. Production. structured outputs. Анализ и проектирование систем. архитектура по.. anthropic. Claude. llm. Natural Language Processing. Production. structured outputs. Анализ и проектирование систем. архитектура по. валидация данных.. anthropic. Claude. llm. Natural Language Processing. Production. structured outputs. Анализ и проектирование систем. архитектура по. валидация данных. ии-агенты.. anthropic. Claude. llm. Natural Language Processing. Production. structured outputs. Анализ и проектирование систем. архитектура по. валидация данных. ии-агенты. искусственный интеллект.. anthropic. Claude. llm. Natural Language Processing. Production. structured outputs. Анализ и проектирование систем. архитектура по. валидация данных. ии-агенты. искусственный интеллект. Машинное обучение.. anthropic. Claude. llm. Natural Language Processing. Production. structured outputs. Анализ и проектирование систем. архитектура по. валидация данных. ии-агенты. искусственный интеллект. Машинное обучение. отказоустойчивость.. anthropic. Claude. llm. Natural Language Processing. Production. structured outputs. Анализ и проектирование систем. архитектура по. валидация данных. ии-агенты. искусственный интеллект. Машинное обучение. отказоустойчивость. Программирование.

Я проектирую системы на LLM, и после каждого громкого ИИ-инцидента мне прилетает одна и та же ссылка с одним и тем же вопросом: «а у нас такое может случиться?» Чтобы отвечать не на глазок, я завёл привычку разбирать каждый такой инцидент до конкретной технической причины. За последние месяцы таких разборов набралось три, и они удивили меня не сходством, а различием. Дыры, в совершенно разных местах: у одного проекта не проверялись источники, у другого, данные на границах системы, у третьего, права автономного агента. А вот причина одна: системы вокруг LLM строили люди, которые умеют писать промпты, но не умеют проектировать архитектуру.

Судите сами. Deloitte Australia возвращает правительству деньги за отчёт, в котором ИИ выдумал источники (Источник). В Миннесоте полиция четырьмя машинами блокирует на парковке журналиста, потому что сеть ИИ-камер несколько дней вела его как угонщика, из-за опечатки, сделанной за две тысячи миль от него (Источник). А основатель инди-SaaS просыпается и обнаруживает, что написанный моделью код ночью отменил все подписки его клиентов: месячная регулярная выручка обрушилась до $38, и то лишь потому, что уцелели две тестовые подписки (Источник).

Из этих разборов у меня сложились три идеи, через которые я теперь смотрю на любую LLM-систему. Первая: контроль должен жить в детерминированном коде, а не в промпте, промпт снижает вероятность ошибки, но не даёт гарантий. Вторая: у каждой техники есть область применимости, и вероятностные компоненты нельзя ставить на места, где нужна детерминированность. Третья: строгость контролей калибруется под цену ошибки, высокорисковые сценарии требуют других порогов и других процедур, чем рутинные. Три инцидента ниже, это три нарушения этих идей в дикой природе. Разберём каждый: что произошло, где сломалось и как закрывается. Решения покажу с кодом на Claude API, во-первых, потому, что чинить абстрактными рассуждениями скучно, а во-вторых, потому, что сам строю на Claude: из всех моделей, с которыми я работал в проде, у него самый продуманный инструментарий именно для архитектурных гарантий, citations, structured outputs, tool use спроектированы так, что правильную систему построить проще, чем неправильную.

Кейс 1. Deloitte: отчёт, который все проверили и никто не прочитал

Модель не виновата: разбираем 3 громких ИИ-инцидента, которые случились из-за отсутствия архитектуры - 1

Хроника: сноски, которых не было

Deloitte Australia готовила для правительства официальный отчёт и использовала при этом ИИ. Документ прошёл внутренние ревью, не одно, и был опубликован. А потом внешний исследователь, читавший его по своей академической надобности, начал проверять сноски. Часть цитат была приписана не тем авторам. Часть источников не существовала вовсе. Дальше всё развивалось по законам жанра: публикации в СМИ, вопросы от законодателей, исправленная версия документа и возврат части оплаты по контракту. Для фирмы, которая продаёт в том числе аудит и проверку чужих отчётов, сюжет вышел особенно неловкий.

Где сломалось: беглость перестала означать достоверность

Самое интересное в этой истории, не галлюцинации. То, что языковые модели выдумывают источники, известно каждому, кто провёл с ними больше недели. Интересно, что документ прошёл несколько слоёв проверки, и ни один из проверяющих не споткнулся. Ревьюеры читали текст и оценивали его связность, логику, убедительность. Но LLM генерирует именно связный, логичный и убедительный текст, в том числе тогда, когда врёт. Беглость изложения десятилетиями работала прокси-сигналом качества, и процесс ревью молча на неё полагался. С приходом генеративных моделей этот сигнал умер, а процессы этого не заметили.

Архитектурно здесь нарушен принцип groundedness: фактическое утверждение модели должно быть привязано к проверяемому источнику, и проверять привязку должен механизм, а не внимательность уставшего человека. «Мы попросили модель не выдумывать», не механизм. Модель, которую попросили не выдумывать, выдумывает реже. Реже, не значит никогда.

Схема 1. Гарантии даёт код-валидатор, а не LLM; человек разбирает только непрошедшее

Схема 1. Гарантии даёт код-валидатор, а не LLM; человек разбирает только непрошедшее

Как закрыть: citations плюс механическая проверка

Теперь, как это закрывается на Claude. Первый слой, citations: API умеет отвечать строго по переданным документам и к каждому утверждению возвращать структурированную ссылку на фрагмент, из которого оно взято.

import anthropic

client = anthropic.Anthropic()

response = client.messages.create(
   model="claude-sonnet-4-6",
   max_tokens=2048,
   messages=[{
     "role": "user",
     "content": [
       {
         "type": "document",
         "source": {
           "type": "text",
           "media_type": "text/plain",
           "data": report_source_text, # исходные данные исследования
         },
         "title": "Данные аудита, Q2",
         "citations": {"enabled": True}, # ключевая строка
        }
       {
         "type": "text",
         "text": "Составь раздел отчёта о выявленных рисках. "
         "Используй только предоставленный документ."
       },
    ],
 }],
)

Ответ приходит блоками, и у каждого блока естьcited_textс координатами фрагмента источника. Само по себе это ещё не гарантия, гарантией становится второй, детерминированный слой. Перед сборкой финального документа код проверяет каждый блок:

def validate_grounding(response) -> list[str]:
   """Блоки без подтверждённых цитат не попадают в документ."""
   rejected = []
   for block in response.content:
     if block.type != "text":
       continue
     citations = getattr(block, "citations", None)
     if not citations:
       rejected.append(block.text) # утверждение без источника
       continue
     for c in citations:
       # цитата обязана дословно присутствовать в источнике
       if c.cited_text not in source_documents[c.document_index]:
         rejected.append(block.text)
   return rejected

Утверждение без прошедшей проверку цитаты физически не попадает в финальный текст, оно уходит в список «требует ручной проверки». Ревьюер перестаёт искать иголку в стоге сена: система сама подсвечивает те несколько процентов текста, которые ничем не подкреплены, и человеческое внимание тратится только на них. У Deloitte такой список из пары десятков строк сэкономил бы контракт и заголовки в прессе.

Кейс 2. Flock: два потерянных символа и четыре полицейские машины

Модель не виновата: разбираем 3 громких ИИ-инцидента, которые случились из-за отсутствия архитектуры - 3

Хроника: «Вы вооружены? Выйдите из автомобиля!»

Эта история случилась в июле 2026-го, и её главный герой описал её сам, Джоэл Федер, автомобильный журналист The Drive, пятнадцать лет тестирующий машины. В то воскресенье он с женой поехал по магазинам на Range Rover за $155 тысяч из пресс-парка Jaguar Land Rover. На выезде с парковки их заблокировали четыре полицейские машины. «Вы вооружены? Выйдите из автомобиля!».

Выяснилось, что полиция Плимута несколько дней вела Федера по камерам: система Flock, общенациональная сеть ИИ-камер, распознающих номера, пометила его машину как краденую. Офицер даже показал журналисту фотографии его собственных перемещений в приложении. Час разбирательств, звонок в Jaguar Land Rover прямо с парковки, и картина сложилась.

В Калифорнии заявили о пропаже номерного знака 34 03 DTM. Но на номерах этого формата средние цифры напечатаны мелким шрифтом, и при вводе в базу их просто опустили, записали «34 DTM». Распознавание Flock эти мелкие цифры на def validate_grounding(response) -> list[str]: “””Блоки без подтверждённых цитат не попадают в документ.””” rejected = [] for block in response.content: if block.type != “text”: continue citations = getattr(block, “citations”, None) if not citations: rejected.append(block.text) # утверждение без источника continue for c in citations: # цитата обязана дословно присутствовать в источнике if c.cited_text not in source_documents[c.document_index]: rejected.append(block.text) return rejected в проезжающих машинах тоже не считывало. Результат: любая машина пресс-парка JLR с номером шаблона «34 ## DTM», а их по стране десятки, начала триггерить алерт о краже. Полиция сказала Федеру, что только в Миннесоте в ту неделю отслеживали ещё четыре такие машины; он просто оказался первым задержанным. Вишенка: исходный номер вообще не был украден, его потеряли на фотосессии.

И ещё одна деталь, важная для нашего разбора. У Flock есть официальная политика: алерт — это повод для проверки, офицер обязан вручную сверить номер до каких-либо действий. Политика существовала. На практике алерт стал основанием для многодневной слежки со «стингом» на парковке. Один из офицеров честно сказал журналисту: считайте, повезло, что это Плимут, в Миннеаполисе вас бы положили на землю под прицелом.

Схема 2, Failure Cascade (кейс Flock): каскад отказа от опечатки до задержания.

Схема 2, Failure Cascade (кейс Flock): каскад отказа от опечатки до задержания.

Где сломалось: три границы без единой проверки

Здесь нарушен принцип валидации на границах системы, причём трижды. Вероятностный вывод (распознавание с камеры) сравнивался с невалидированной записью (усечённый номер). Частичное совпадение обрабатывалось как точное. И результат сравнения напрямую запускал высокорисковое действие, а человеческая проверка, которая должна была стоять между ними, жила в документе, а не в системе.

Граница

Как было у Flock

Как должно быть

Ввод в базу

усечённый номер «34 DTM» сохранён без проверки

schema/pattern-валидация при записи: неполная запись не сохраняется

Матчинг

частичное совпадение обработано как точное

только exact match; неполные распознавания не участвуют

Действие

алерт стал основанием для слежки и задержания

human-in-the-loop как обязательная ветка кода, а не пункт политики

Как закрыть: строгая схема и human-in-the-loop, который нельзя пропустить

Паттерн универсальный: он касается любой системы, где модель извлекает структурированные данные, номера, идентификаторы, суммы, коды диагнозов. На Claude это закрывается через structured outputs: модель не пишет ответ свободным текстом, а обязана заполнить строгую схему.

schema = {
   "name": "register_plate",
   "description": "Регистрация распознанного номерного знака",
   "input_schema": {
     "type": "object",
     "properties": {
       "state": {"type": "string", "enum": US_STATES},
       "plate": {
         "type": "string",
         # полный формат, включая мелкие средние символы
         "pattern": "^[0-9]{2} [0-9]{2} [A-Z]{3}$"
       },
       "confidence": {"type": "number", "minimum": 0, "maximum": 1},
       "all_characters_legible": {"type": "boolean"},
     },
     "required": ["state", "plate", "confidence",
     "all_characters_legible"],
   },
}

response = client.messages.create(
   model="claude-sonnet-4-6",
   max_tokens=1024,
   tools=[plate_schema],
   tool_choice={"type": "tool", "name": "register_plate"},
   messages=[{"role": "user", "content": [
     {"type": "image", "source": {...}}, # кадр с камеры
     {"type": "text", "text": "Распознай номерной знак."},
   ]}],
)

Схема убивает сам класс ошибки из кейса. Запись «34 DTM» не проходит pattern , неполный номер не может быть сохранён в базу, ни оператором, ни моделью. Не разглядела средние цифры, обязана выставить all_characters_legible: false , и такая запись вообще не участвует в матчинге.

Дальше слой сравнения и действия, уже без всякого ИИ:

def process_recognition(result: dict) -> Action:
   # 1. Неполные распознавания не матчатся вообще
   if not result["all_characters_legible"]:
     return Action.LOG_ONLY

   # 2. Только точное совпадение, никаких подстрок
   match = stolen_plates_db.exact_lookup(
     state=result["state"], plate=result["plate"]
   )
   if match is None:
     return Action.NONE

   # 3. Высокорисковое действие — только через человека
   if result["confidence"] < 0.98:
     return Action.QUEUE_FOR_REVIEW
   return Action.ALERT_WITH_MANDATORY_HUMAN_VERIFICATION
Схема 3. Тот же процесс с барьерами: alert создаёт код, действие санкционирует человек.

Схема 3. Тот же процесс с барьерами: alert создаёт код, действие санкционирует человек.

Сравните с оригиналом. «Офицер должен проверить вручную» у Flock, это фраза в PDF с политиками. ALERT_WITH_MANDATORY_HUMAN_VERIFICATION , это ветка кода, которую нельзя пропустить. Между этими двумя состояниями и проходит граница между пожеланием и гарантией. У Федера на этой границе стояли четыре полицейские машины.

Кейс 3. $38 MRR: агент, которому дали ключи от кассы

Модель не виновата: разбираем 3 громких ИИ-инцидента, которые случились из-за отсутствия архитектуры - 6

Хроника: 7 секунд, пока все спали

Третья история громыхнула в X в те же июльские дни. Основатель инди-SaaS проснулся утром и увидел, что месячная выручка бизнеса упала на тысячи долларов, до $38. Паника, проверка Stripe: клиенты не отписывались. Код, написанный моделью GPT 5.6 Sol в агентном режиме, ночью отменил каждую активную подписку. На всё ушло 7 секунд. Технический виновник, cron-джоба, которая обработала пустую очередь удаления как валидную задачу «удалить всё». Уцелели две тестовые подписки.

Совпадение, которое сделало историю вирусной: буквально за день до этого известный в AI-сообществе разработчик Мэтт Шумер написал, что та же модель в агентном режиме случайно удалила почти все файлы на его Mac. Автор поста про Stripe сделал из этого вывод в духе «модель X доверять продакшену нельзя, модель Y, можно».

Вывод эмоционально понятный, и инженерно неверный в обе стороны. Но об этом чуть ниже, сначала про то, что здесь сломалось.

Где сломалось: права агента жили в промпте

Автономный код имел права на массовую необратимую операцию с деньгами и выполнил её без подтверждения, без лимитов, без dry-run, в три часа ночи. Если у агента и были ограничения, они жили в промпте, в слое пожеланий. Принцип минимальных привилегий требует противоположного: то, что агент не должен делать, должно быть тем, что он не может сделать. На уровне выданных инструментов и ключей, а не инструкций.

Схема 4. Три слоя авторизации агента; любой может остановить вызов, ограничивая масштаб.

Схема 4. Три слоя авторизации агента; любой может остановить вызов, ограничивая масштаб.

Как закрыть: три слоя между агентом и деньгами

На Claude агентная система с такими гарантиями строится в три слоя. Первый, узкие инструменты. Агент не получает «доступ к Stripe», он получает конкретные операции, и опасных среди них просто нет:

tools = [
   {
     "name": "get_subscription",
     "description": "Прочитать данные одной подписки",
     "input_schema": {...},
   },
   {
     "name": "cancel_subscription",
     "description": "Отменить ОДНУ подписку по явному ID",
     "input_schema": {
       "type": "object",
       "properties": {
         "subscription_id": {"type": "string"},
         "reason": {"type": "string"},
       },
       "required": ["subscription_id", "reason"],
     },
   },
   # инструмента cancel_all / bulk_cancel не существует
]

Второй слой, исполнение на вашей стороне. При tool use Claude не выполняет операции сам: он возвращает запрос на вызов, а вызывает ваш код. Именно здесь живут предохранители:

MAX_CANCELLATIONS_PER_HOUR = 3

def execute_tool(tool_name: str, tool_input: dict):
   if tool_name == "cancel_subscription":
     # предохранитель от массовых операций
     if cancellations_last_hour() >= MAX_CANCELLATIONS_PER_HOUR:
        alert_owner("Агент превысил лимит отмен — остановлен")
        raise CircuitBreakerTripped
     # необратимое действие — только с подтверждением человека
     approval = request_human_approval(
       action=f"Отменить подписку {tool_input['subscription_id']}",
       reason=tool_input["reason"],
       timeout_hours=24,
     )
     if not approval:
        return {"status": "rejected_by_owner"}
    stripe.Subscription.cancel(tool_input["subscription_id"])
     return {"status": "cancel

С таким кодом худший сценарий той ночи, три отменённые подписки, остановленный агент и уведомление владельцу. Не $38 MRR.

Третий слой, скоуп ключей. Stripe поддерживает restricted keys: агентному сервису выдаётся ключ read-only на подписки, операции записи ходят через отдельный сервис с подтверждением. Ключ, который физически не умеет отменять подписки, не отменит их никогда, ни из-за бага в cron-джобе, ни из-за галлюцинации, ни спросонья.

Отступление: «А вот модель Y так бы не сделала»

Теперь вернёмся к «модели Y доверять можно». Более аккуратная модель действительно реже инициирует катастрофу, частота инцидентов зависит от модели. Но их максимальный масштаб определяется контуром вокруг неё. В ту ночь масштаб был «весь MRR за 7 секунд», потому что контура не существовало. Поменяйте модель на самую осторожную на рынке, и вы уменьшите вероятность повторения, оставив цену повторения прежней.

Что общего

Сложим три истории рядом:

Deloitte

Flock

Stripe-агент

Что сломалось

выдуманные источники прошли все ревью

опечатка в базе → ложные совпадения по всей стране

код агента отменил все подписки за 7 секунд

Нарушенный принцип

groundedness: факты без привязки к источникам

валидация на границах: ввод, матчинг, действие

least privilege: права шире задачи

Где жил «контроль»

во внимательности ревьюеров

в PDF с политиками

в промпте и добрых намерениях

Цена инцидента

возврат денег, репутация, пресса

час задержания невиновного, расследования

минус весь MRR за одну ночь

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

Вернусь к трём идеям из начала статьи, теперь у каждой есть цена, измеренная чужими деньгами и нервами. Контроль в детерминированном коде, а не в промпте: у Deloitte и в кейсе со Stripe «контроль» был текстом, и текст не сработал. Каждый компонент на своём месте: у Flock вероятностное распознавание поставили туда, где системе нужна была детерминированная сверка. Строгость под цену ошибки: алерт, ведущий к задержанию человека, и алерт, двигающий тикет в очереди, не могут проходить через одинаковую проверку.

Отсюда простой тест, который я теперь применяю к любому требованию вида «система никогда не должна X»: где живёт это «никогда»? Если в промпте, в инструкции для персонала или в PDF с политиками, у вас пожелание. Если в JSONсхеме, в скоупе ключа, в ветке кода, которую невозможно обойти, у вас гарантия. Все три инцидента случились ровно в зазоре между первым и вторым.

Показательно, что и сами разработчики моделей это понимают. Посмотрите, во что превращается Anthropic: не в поставщика одной модели, а в целую экосистему для промышленного внедрения. Claude Code, Model Context Protocol как способ подключать инструменты, structured outputs и citations как встроенные механизмы гарантий, готовые паттерны агентных систем. Всё это устроено вокруг одной идеи: чтобы LLM можно было ответственно поставить в продакшен, вокруг неё нужна инженерная обвязка, и вендор эту обвязку выстраивает сознательно, а не оставляет каждую команду изобретать её заново.

Отсюда главный вывод для всех, кто внедряет LLM в промышленный проект. Ключевая инвестиция сейчас не в подписку на модель поумнее, а в архитектурную грамотность команды. Модель это только ядро. Работающий и безопасный продукт получается тогда, когда вокруг ядра выстроена правильная архитектура: валидация на границах, детерминированные гарантии вместо пожеланий в промпте, продуманные права и пороги под цену ошибки. Разбор чужих инцидентов, вроде трёх в этой статье, самый дешёвый способ этому научиться. Свой инцидент обходится куда дороже, и в комплекте к нему идут пресса и возврат денег заказчику.

Автор: Aiarchpro

Источник