Симбиоз: Как я научился не спорить с машиной, а направлять её

30.06.2026 09:00

Гнев и отрицание

Я помню этот момент предельно ясно. Два часа ночи. Вкладка IDE горит красным от синтаксических ошибок. Я только что попросил языковую модель написать слой кэширования для нашего шлюза, а она выдала мне монолитный кусок кода из эпохи раннего PHP 5, проигнорировав паттерн Репозиторий и жестко вбив SQL-запросы прямо в контроллер. Моя первая реакция? «Она тупая».

Это типичная защитная реакция инженера. Мы привыкли доверять детерминированным системам. Если компилятор прерывает сборку, мы точно знаем, что где-то пропустили точку с запятой или нарушили контракт типов. Но когда LLM генерирует чушь, мы охотно виним саму модель. Мы навешиваем удобный ярлык «галлюцинация», тем самым снимая с себя ответственность за результат.

Но правда оказалась гораздо жестче. Машина не тупая. Она — идеальное зеркало. Она кристально точно отражает тот контекст, который мы ей скармливаем. Если на входе мусор, на выходе генерируется элегантно скомпилированный, грамматически безупречный мусор. Мой конфликт с нейросетями перестал быть технологическим и стал методологическим. Я понял: проблема не в том, что модель забыла синтаксис PHP 8.4 или принципы SOLID. Проблема в том, что я, как Staff Engineer, ставил задачу так, будто отдаю приказ телепату. Я не передал интерфейсы, не ограничил область видимости, не зафиксировал жесткие правила проекта. Я предоставил катастрофически плохой контекст.

От шаманства к инженерии

Осознание проблемы формирует лишь половину решения. Вторая половина требует инструментов. Если промпт де-факто стал новым языком программирования, значит, его необходимо профилировать и отлаживать. В классической разработке мы используем Xdebug, собираем логи, строим графы распределенной трассировки. В мире работы с LLM большинство инженеров до сих пор работают вслепую: отправляют запрос, получают нерелевантный ответ, переписывают пару фраз в промпте и надеются на лучшее. Это шаманство, которому не место в production-системах.

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

В RouterAPI каждый запрос получает идентификатор в журнале — по нему можно восстановить полный контекст вызова. Когда генерация идет вразнос, я больше не переписываю промпт наугад. Я иду читать логи.

Анатомия промпта в логах RouterAPI

Как это выглядит на практике? Допустим, агент-ассистент снова выдает галлюцинацию вместо рабочего сервиса. Раньше я закрывал вкладку и писал код руками. Теперь я беру request_id и выполняю запрос к базе данных, куда журнал запросов RouterAPI заботливо сложил объекты запись в журнале запросов.

Я смотрю на метрики. Наш логгер извлекает критически важные данные из метаданные запроса:

  • gen_ai.request.model — какую именно модель мы фактически вызвали? Была ли это тяжелая claude-3-5-sonnet или роутер прозрачно переключил нас на более дешевую модель, которая не вытянула сложный архитектурный контекст?
  • gen_ai.usage.prompt_tokens и gen_ai.usage.completion_tokens — здесь скрываются самые жестокие откровения.

Один из показательных случаев произошел при попытке автоматического рефакторинга ядра биллинга (систему биллинга RouterAPI). Модель упорно игнорировала половину бизнес-ограничений. Я открыл трейс. Метрика prompt_tokens показывала 45 000. Сорок пять тысяч токенов входного контекста. Начав разматывать сохраненный payload, я обнаружил, что наш внутренний скрипт сборки контекста вместе с целевым классом прикрепил полный лог миграций базы данных за последний год и огромный дамп конфигураций инфраструктуры.

Системный промпт с четкими правилами оказался погребен под тысячами строк нерелевантного шума. Механизм внимания (attention) модели просто размылся. Машина не ошиблась. Это мы устроили DDoS-атаку на её контекстное окно.

Грязная работа с OTLP: Парсинг и нормализация

RouterAPI нормализует метаданные запросов из разных провайдеров в единый формат — без ручного разбора сырых логов каждого вендора. OTLP-данные прилетают в чудовищном зоопарке форматов, усугубляемом десятками провайдеров, скрывающихся за фасадом резервный провайдер. Один провайдер отдает метрику как intValue, другой — как int_value, третий засовывает число в stringValue.

Наш сервис методично перемалывает это великолепие. Мы проверяем doubleValue, boolValue, bytesValue, arrayValue, рекурсивно разворачиваем KV-списки и мапы в плоский массив. Затем метод нормализацию метаданных гарантирует, что в базу попадут только безопасные скаляры, защищая PDO от падений с ошибкой «Array to string conversion».

Зачем мы тратим такты процессора на нормализацию этих метаданных? Потому что самое важное скрыто именно там. Параметр резервный провайдер.native_finish_reason порой рассказывает куда больше, чем стандартный stop. Он может показать, что модель прервала генерацию из-за срабатывания внутренних фильтров безопасности (content policy), а не потому что логически завершила задачу. Без глубокой нормализации мы бы продолжали кричать на модель «почему метод оборван на середине?!», не понимая, что провайдер убил соединение из-за ложного срабатывания фильтра на безобидное слово в SQL-дампе.

Цена контекста и обратная связь

Еще один критический аспект интеграции — управление стоимостью ошибок. Метод корректировку биллинга извлекает cost_usd и связывает затраты с конкретными пользователями или автоматическими процессами через идентификацию пользователя. Это не просто логика возврата средств за сбои. Это жестокий финансовый профилировщик промптов.

Мы видим, какие процессы жгут токены впустую. Мы строим аналитику стоимости неэффективных абстракций. Если скрипт каждую ночь прогоняет 100 000 токенов контекста только для того, чтобы модель изменила две строчки кода, метрики немедленно подсвечивают эту аномалию. Финансовая прозрачность заставляет инженеров переходить от передачи «всего проекта целиком» к точечному, снайперскому внедрению исключительно нужных контрактов.

Смещение роли: От кодера к архитектору контекста

Этот инструментарий сломал мою старую ментальную модель. Я перестал воевать с машиной. Отношения перешли в фазу настоящего симбиоза.

Машина блестяще берет на себя рутину: генерацию DTO, бойлерплейта, имплементацию CRUD-репозиториев. Но она делает это превосходно только тогда, когда я проектирую для нее железобетонные рамки. Моя ежедневная работа сместилась с набора строк кода на архитектуру контекста. Я задаю границы.

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

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

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

Теги

Ещё по теме