Примерно год назад наша команда загорелась идеей создать продукт, который позволил бы «поговорить с кодом». Под впечатлением от возможностей LLM, мы мечтали, что нейросеть возьмёт на себя рутину: анализ легаси, аудит систем, онбординг разработчиков.
Мы представляли идеальную картину: загружаем исходники, документацию, ТЗ — и на выходе получаем структурированный JSON с описанием архитектуры, связей, интеграций. Дополняет всё это умный чат, в котором можно спросить: «Как у нас реализованы выплаты по убыткам?» — и получить мгновенный ответ.
На старте всё казалось простым. LLM умеет читать код, есть фреймворки для аудита — осталось написать хороший промпт и пожинать плоды. Но реальность enterprise-разработки быстро разрушила иллюзии.
За несколько месяцев мы накопили 12 критических ошибок, которые едва не убили наш проект Code Scope. Расскажу о пяти самых показательных. Спойлер: в итоге 99% кода — это инженерия, и только 1% — тексты промптов.
Ошибка 1: Один запрос обо всём
Мы начали с амбициозного промпта: загружаем код, просим модель вернуть всё — описание методов, интеграции, эндпоинты, метрики — в одном большом JSON.
Сначала работало. Но как только промпт и схема выросли, начался хаос. Модель пропускала методы, то видела интеграции, то нет. Результаты перестали воспроизводиться.
Мы пытались улучшать промпт — добавляли ограничения, уточнения. Он разросся до тысяч токенов, стал нечитаемым и хрупким: меняешь одну часть — ломается другая.
Решение: разделили задачу. Вместо одного гигантского запроса — несколько узкоспециализированных. Отдельно — методы, отдельно — интеграции, отдельно — эндпоинты.
Результат: качество и стабильность резко выросли. Модель получила фокус. Да, вызовов стало больше, но они выполняются быстро и параллельно. Выигрыш по времени — минимальный, выигрыш по качеству — огромный.
Ошибка 2: «Я доверяю фактам от LLM + self-confidence»
Мы увидели, что LLM врёт, и решили добавить защиту: пусть модель возвращает не только факт, но и цитату из кода, и уровень уверенности (от 0 до 10).
Звучало разумно. Но на практике это оказалось ложным чувством контроля. Модель выдавала уверенность 10 даже при явной ошибке. Например, увидела переменную Kafka — и объявила, что система использует брокер сообщений.
Попытка использовать «LLM-as-a-Judge» — когда одна модель проверяет другую — тоже провалилась: дорого и ненадёжно.
Вывод: универсальной вероятностной проверки не существует. Нужен детерминированный слой валидации.
Сейчас мы собираем метаинформацию о системе: стек, фреймворки, зависимости. На их основе формируем маркеры. Например, для Spring Boot внешний вызов — это RestTemplate, Feign или Kafka Producer.
Когда LLM говорит «нашёл интеграцию», мы проверяем наличие маркеров. Теперь мы не утверждаем: «здесь есть интеграция». Мы говорим: «с высокой/средней/низкой уверенностью считаем, что она есть».
Ошибка 3: Формат — не главное
Когда мы разобрались с качеством, столкнулись с производительностью. В проекте тысячи вызовов к LLM. Некоторые запросы выполнялись по 10 минут.
Инфраструктура была в порядке. Проблема оказалась в формате: пробелы, переносы строк — это тоже токены. А латентность растёт линейно с их количеством.
Провели тест: один и тот же JSON — с отступами и без. Разница в выходных токенах — более 200. На тысячи вызовов это десятки часов ожидания и лишние расходы.
С тех пор у нас жёсткое правило: везде, где можно, используем компактный single-line JSON — в few-shot примерах, в схемах, в требованиях к ответу. Формат — это часть производительности.
Ошибка 4: Один универсальный тул «в базу»
Мы построили мультиагентский RAG: графовая база для связей, векторная — для семантики. Для запросов к графу написали агента, который генерирует Cypher-запросы.
На простых вопросах — работало отлично. На сложных — ад. Агент писал огромные запросы, которые не проходили валидацию или падали. Начинались перегенерации. Ответ можно было ждать десятки минут.
Мы улучшали промпты, но проблема осталась. Тогда проанализировали реальные сценарии: 80–90% запросов укладываются в несколько паттернов — «покажи контекст метода», «построй цепочку вызовов», «найди интеграции вокруг узла».
Решение: мы сами написали эти запросы, оттестировали и завернули в готовые тулы. Агенту больше не нужно генерировать сложный Cypher — он просто вызывает нужный инструмент.
Универсальная генерация осталась как fallback, но используется крайне редко. Количество перегенераций упало в разы, скорость ответа — выросла.
Ошибка 5: Observability «потом»
Система росла: векторное и графовое хранилища, куча инструментов, несколько агентов. Мы хотели быстрее добавить «магии» и отложить наблюдаемость на потом.
Но в ИИ-системах это не работает. Логика зависит от данных, промптов и вызовов. В один момент агент начал вести себя странно: то не вызывал нужный инструмент, то сообщал, что создал 11 объектов, хотя было 9. Поведение не воспроизводилось.
Мы начали копаться в логах — это превратилось в археологию. Тогда за час подняли Langfuse и включили трейсинг.
Первый же трейс показал проблему: LLM просила вызвать один инструмент, а в коде вызывался первый из списка MCP-сервера. Сам сервер отвечал 200 OK на любой запрос — даже при ошибке. Модель думала, что всё в порядке.
Баг был найден и исправлен за пару часов. Трейсинг дал прозрачность, без которой разработка ИИ-систем превращается в шаманство.
Итог: LLM — это всего лишь компонент
Сейчас, оглядываясь на восьмимесячный путь, понимаю главное: мечта «загрузи код — получи ответ» осталась мечтой. В нашем репозитории промпты — это 1% кода. Остальные 99% — инженерия: пайплайны, схемы, валидация, наблюдаемость, интеграции.
LLM — это вероятностный компонент. А ценность продукта — в инженерной обвязке, которая делает систему предсказуемой и надёжной.
Если вы только начинаете:
- Golden Dataset и тесты качества — чтобы понимать, что такое «хороший ответ».
- Structured Output везде, где можно. JSON-схемы — первый шаг к воспроизводимости.
- Не пишите промпты вручную. Пусть LLM сама сгенерирует промпт с примерами и учётом граничных случаев.
- Минимальная наблюдаемость с первого дня. Иначе будете копаться в логах вместо разработки.