Выгорание от абстракций: Как мы запутались во фреймворках для AI

28.06.2026 21:00

Год назад казалось, что разработка AI-приложений навсегда изменилась. Вслед за релизом ChatGPT появились специализированные библиотеки, обещавшие превратить интеграцию языковых моделей в сборку простейшего конструктора. Фреймворки вроде LangChain, LlamaIndex и десятки их аналогов кричали с главных страниц GitHub: «Создайте свой RAG за пять строк кода!», «Запустите автономного агента за десять минут!». Мы поверили этим обещаниям. Мы щедро внедряли эти инструменты во все новые микросервисы, надеясь на кратное ускорение разработки. А спустя полгода обнаружили себя выгоревшими, злыми и безнадежно увязшими в отладке чужого переусложненного кода.

Эта статья — история о том, как безудержное стремление к быстрой разработке привело к архитектурному параличу, и почему безжалостный отказ от AI-фреймворков в пользу простых HTTP-запросов оказался единственным способом спасти проект и психическое здоровье команды.

Ловушка «пяти строк кода» и анатомия переусложнения

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

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

В традиционном коде это означало бы добавление одного условного оператора и одного нового ключа в словарь заголовков. В экосистеме толстых AI-фреймворков это превращается в многочасовую экспедицию в исходный код библиотеки. Выясняется, что нужный класс глубоко инкапсулирует логику запроса. Он не принимает системный промпт напрямую, а требует создания кастомного объекта PromptTemplate. Этот объект нужно прокинуть через фабрику, которая, в свою очередь, оборачивается в абстракцию вроде RunnableSequence.

Вы пишете обертку над оберткой, пытаясь обмануть фреймворк. Ваш код стремительно мутирует:

chain = (
 {"context": retriever, "question": RunnablePassthrough}
 | prompt_template
 | llm_model.bind(model_kwargs={"headers": {"X-Custom-Billing-Plan": user.plan}})
 | StrOutputParser
)

В документации это преподносится как элегантный декларативный подход. Но на практике это хрупкая конструкция, где малейший шаг в сторону приводит к краху.

Ад зависимостей и сломанная обратная совместимость

Масштаб катастрофы становится очевидным при попытках обновить зависимости. Экосистема AI-инструментов развивается настолько хаотично, что разработчики фреймворков не успевают стабилизировать API. То, что вчера было правильным подходом, сегодня объявляется устаревшим (deprecated).

Мы столкнулись с непрерывным рефакторингом ради рефакторинга. Пакеты бесконечно дробились: сначала всё было в одном модуле, потом появились langchain-core, затем выделили langchain-community, потом langchain-openai. Обычное обновление минорной версии библиотеки могло сломать импорты в десятках файлов.

Отдельной болью стали конфликты базовых библиотек. Когда часть системы использовала Pydantic версии 1, а новая версия фреймворка жестко требовала Pydantic версии 2, проекты просто переставали собираться. Вместо написания полезной бизнес-логики мы неделями решали головоломки с разрешением конфликтующих транзитивных зависимостей.

Слепая отладка и скрытые затраты

Самая разрушительная черта толстых AI-фреймворков — полная потеря контроля над состоянием и потоком данных. Языковые модели — это, по своей сути, внешние функции, принимающие текст и возвращающие текст. Они недетерминированы, склонны к галлюцинациям и часто работают медленно. Когда вы оборачиваете этот хрупкий сетевой процесс в пять слоев скрытой логики, отладка становится практически невыносимой.

Если запрос в продакшене зависает на тридцать секунд, где искать причину?

  • Это векторная база данных слишком долго выполняет поиск семантических совпадений?
  • Это фреймворк столкнулся с сетевой ошибкой и решил сделать три скрытых повторных запроса (retries)?
  • Это сама языковая модель отвечает медленно из-за высокой нагрузки?
  • Или же абстрактный «агент» внутри библиотеки ушел в бесконечный цикл размышлений (thought loop)?

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

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

Сброс иллюзий: LLM — это просто HTTP API

В какойто момент пришло кристально ясное понимание: мы стали жертвами маркетинга. Фреймворки заставили нас поверить, что интеграция с языковыми моделями — это фундаментально новая парадигма программирования, требующая собственного языка (DSL) и новых архитектурных паттернов.

Но если снять всю эту псевдоинтеллектуальную мишуру, работа с LLM — это самый обычный HTTP POST-запрос.

Пресловутый RAG (Retrieval-Augmented Generation) — это всего лишь выполнение SQL или векторного запроса, превращение результатов в массив строк и подстановка этих строк в текст сообщения. Автономные «агенты» — это банальный цикл while, который вызывает LLM, получает в ответ JSON с названием функции, вызывает эту функцию локально и передает результат обратно в модель.

Зачем нам импортировать сотни мегабайт стороннего кода, чтобы склеить две строки контекста и отправить сетевой запрос?

Умный шлюз вместо толстого клиента: Переход на RouterAPI

Мы приняли радикальное архитектурное решение: полностью удалить все специализированные AI-библиотеки из кодовой базы.

Однако отказ от фреймворков обнажил реальную проблему. Кто-то должен заниматься балансировкой нагрузки, переключением на резервные модели при падении OpenAI (fallback), отслеживанием метрик и учетом стоимости запросов. Раньше часть этих задач фреймворк пытался решать на стороне клиента (приложения).

Мы поменяли парадигму с «Умный клиент» на «Простой клиент / Умный шлюз». Всю сложную логику маршрутизации и биллинга мы вынесли на уровень инфраструктуры, интегрировав наш собственный шлюз RouterAPI (развернутый в рамках экосистемы).

Это решение стало глотком свежего воздуха. Теперь наш PHP или Node.js бэкенд вообще ничего не знает о том, как обрабатываются отказы провайдеров. Приложение просто формирует стандартный JSON-объект в формате OpenAI и отправляет его через обычный fetch или базовый HTTP-клиент:

const response = await fetch('https://api.RouterAPI.host/v1/chat/completions', {
 method: 'POST',
 headers: {
 'Authorization': `Bearer ${ROUTER_API_KEY}`,
 'Content-Type': 'application/json'
 },
 body: JSON.stringify({
 model: 'anthropic/claude-3-5-sonnet',
 messages: [
 { 
 role: 'system', 
 content: `Контекст пользователя: ${formatContext(userDocs)}` 
 },
 { 
 role: 'user', 
 content: userQuery 
 }
 ],
 temperature: 0.3
 })
});

Вся интеллектуальная работа происходит на сервере Gateway. Если оригинальный провайдер отвечает ошибкой 403 или 502, RouterAPI прозрачно перенаправляет запрос на резервного вендора (например, через резервные маршруты RouterAPI). Механизм система тарификации RouterAPI нормализует стоимость токенов в рублях, учитывая сложные правила ценообразования (например, минимальный порог в 10 рублей на миллион токенов), и сохраняет транзакцию для биллинга.

Наш продуктовый код очистился. Он снова стал простым, детерминированным и легко тестируемым. Работа с потоковыми ответами (streaming) перестала требовать изучения хитросплетений чужих асинхронных генераторов — мы просто читаем стандартный Server-Sent Events (SSE) поток.

Возвращение к инженерным основам

Эффект от удаления абстракций превзошел все ожидания. Мы вернули себе полный контроль над тем, какие данные уходят в сеть. Любая проблема отлаживается за минуты простым взглядом во вкладку Network или в стандартные логи приложения. Скорость онбординга новых разработчиков выросла многократно: джуниор-инженеру больше не нужно зубрить архитектуру LangChain, достаточно уметь работать с REST API.

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

Главный урок, который мы усвоили ценой выгорания: базовая операция работы с LLM — это простой обмен текстом по сети. Попытки обернуть эту примитивную операцию в многоуровневые классы создают отрицательную ценность. Используйте надежные инфраструктурные шлюзы вроде RouterAPI для решения задач маршрутизации и отказоустойчивости. А в самом приложении пишите прозрачный, явный код. Собирайте строки руками, делайте прямые HTTP-запросы. Ваша нервная система, ваши коллеги и ваш продакшен скажут вам огромное спасибо.

Теги

Ещё по теме