vegman.dev
Все статьи

От кода к архитектуре ИИ-систем: что меняется в голове

2026-01-15статья

Вы запускаете тест второй раз подряд, не тронув ни строчки кода, - и он падает. Не из-за сети и не из-за случайного таймаута: ответ просто пришел другой. Те же факты, другие слова, другой порядок предложений. Первая реакция - искать баг. Бага нет. Сломалось не поведение программы, а ваше представление о том, что такое «правильный ответ».

Я пришла в ИИ-системы из обычной разработки: 19 лет в ИТ, путь от инженера-программиста до архитектора. Писать код я не разучилась, и почти все, что я знала про инженерию, осталось в силе. Но несколько рабочих привычек пришлось перебрать заново, и лучше сделать это осознанно, чем по итогам инцидента. К концу текста вы поймете, чем работа над ИИ-системой отличается от привычной разработки и какие четыре привычки ломаются первыми.

Два слова для начала. Языковая модель - программа, которая по тексту запроса порождает текст ответа: она предсказывает продолжение, а не вычисляет его по формуле. Токен - кусочек текста размером примерно со слово или часть слова; в токенах меряют и объем запроса, и счет от провайдера.

Первая перемена: результат перестает быть детерминированным

Обычная программа детерминирована: одинаковый вход дает одинаковый выход. На этом стоит весь привычный тест - вы сравниваете полученное с ожидаемым, и он либо проходит, либо нет.

С языковой моделью это не работает. Один и тот же запрос дает разные ответы, и «правильно» перестает быть точкой - становится диапазоном. Сравнивать ответ со строкой-эталоном бессмысленно: тест будет краснеть на верных ответах и зеленеть на неверных, если те случайно совпали формулировкой.

Отсюда первая смена привычки: проверять не текст ответа, а его свойства. Не «совпало ли слово в слово», а:

  • есть ли в ответе ссылка на источник
  • сходятся ли числа в ответе с числами в источнике
  • соблюден ли формат, который ждет следующий шаг системы.

Такие проверки переживают переформулировку. И они же становятся гейтом - местом, где ответ, не прошедший проверку, не уходит дальше, а отправляется на повтор.

flowchart LR
  A["Запрос пользователя"] --> B["Ответ модели"]
  B --> C{"Есть ссылка на источник?"}
  C -- нет --> F["Не отдавать, повторить запрос"]
  C -- да --> D{"Числа сходятся с источником?"}
  D -- нет --> F
  D -- да --> E{"Формат соблюден?"}
  E -- нет --> F
  E -- да --> G["Показать пользователю"]
  F --> B
Тест ИИ-системы проверяет не совпадение текста, а свойства ответа: ссылку, сходимость чисел с источником, формат.

Вторая перемена: качество живет в данных, а не в коде

Раньше, когда результат был плохим, я шла в алгоритм. В ИИ-системе я иду в данные - и почти всегда нахожу причину там.

Самый наглядный случай - поисковый ассистент: система, которая ищет ответ в ваших документах и отвечает по найденному, а не «из головы» модели. Почти все ошибки в нем чинились не кодом, а двумя вещами: нарезкой документов на фрагменты и разметкой этих фрагментов.

Звучит скучно, пока не увидишь механику. Документ режут на куски, потому что целиком он в запрос не влезает. Кусок находится по запросу верно - слова совпали, поиск отработал. А понимается неправильно: в нем нет контекста. Абзац с условием или сроком верен внутри своего раздела и вводит в заблуждение, когда вырван из него: непонятно, к чему условие относится и на кого распространяется. Модель получает фрагмент без этой рамки и честно отвечает по тому, что видит.

Поэтому граница нарезки - инженерное решение, а не технический пустяк, и к каждому фрагменту нужна разметка: откуда он, из какого раздела, к чему относится. Меняете нарезку - меняется качество ответов, хотя в коде вы не тронули ничего.

flowchart LR
  A["Очередь задач пуста"] --> B["Служебный опрос уходит в модель"]
  B --> C["Запрос: около 29 700 токенов"]
  C --> D["Ответ: 90 токенов, смысл: задач нет"]
  D --> E["Доля в счете за модели: 45 процентов"]
  A -.-> F["Тот же ответ обычным кодом: 0 токенов"]
Служебный опрос при пустой очереди: огромный запрос ради короткого ответа «задач нет» - и 45 процентов всех расходов на модели.

Третья перемена: у каждого вызова есть цена

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

Мой самый обидный пример: служебный опрос - периодический вопрос системы самой себе, есть ли работа в очереди. При пустой очереди он все равно уходил в модель с полным контекстом: около 29 700 токенов запроса ради ответа в 90 токенов, смысл которого - «задач нет». Такой вызов выглядит дешевым: он редко попадается на глаза и не тормозит пользователя. По итогу он съел 45 процентов всех расходов на модели.

Вывод простой: смотреть не на общую сумму счета, а на разрез по типам вызовов. Общая сумма ничего не подсказывает - она просто растет. Разрез показывает, что почти половина денег уходит на вопрос, ответ на который вы знаете и без модели: очередь пуста, и проверить это можно обычным кодом.

Четвертая перемена: человек становится частью системы

Привычная роль человека - пользователь: он снаружи, он нажимает кнопки. В ИИ-системе он становится звеном самой системы, наравне с очередью и хранилищем.

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

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

Что осталось прежним и оказалось важнее прочего

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

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

Без последнего пункта разбор превращается в спор о воспоминаниях. Историю со служебным опросом я нашла именно в журналах: без них у меня была бы только строчка «расходы выросли».

И один соблазн

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

Применить у себя

Что проверить на практике

  • Прогоните один и тот же запрос к модели пять раз подряд и сравните ответы. Это самый быстрый способ почувствовать разницу между «сломалось» и «так устроено».
  • Перепишите один свой тест со сравнения текста на проверку свойств: наличие ссылки, совпадение чисел с источником, валидность формата.
  • Возьмите документ, по которому у вас идет поиск, и посмотрите на найденный фрагмент отдельно от документа. Спросите себя: понятен ли он без остального текста? Если нет - вы нашли причину будущей ошибки.
  • Посчитайте расходы за неделю в разрезе по типам вызовов, а не одной суммой. Найдите самый частый вызов и посмотрите, сколько токенов он отправляет и сколько получает обратно.
  • Выпишите список действий вашей системы, у которых есть внешние последствия. Отметьте, какие из них сейчас происходят без подтверждения человека.

Как понять, что получилось

Критерий простой: когда система в следующий раз ответит неправильно, вы сможете назвать причину, не меняя модель.

Если вы можете открыть журнал, увидеть, какой фрагмент попал в запрос, и сказать «вот здесь нарезка разрубила смысловой кусок пополам» - привычки перестроились. Если единственное объяснение звучит как «модель сглючила, давайте возьмем другую» - еще нет, и дело не в модели.

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