Мы построили для сервиса интернет-бухгалтерии «Моё дело» систему, которая показывает, что на самом деле делают его AI-агенты, когда отвечают клиенту: что спросили у нейросети, во сколько обошёлся ответ и верный ли он. Всё работает на собственных серверах компании, данные клиентов не уходят наружу, а её инженеры переносят это решение на каждый новый AI-сервис.
LLM-агент, который отвечает на вопросы в ноутбуке у разработчика, выглядит законченным. Тот же агент в продакшене превращается в чёрный ящик. Не видно, какой промпт ушёл в модель, что вернул поиск по базе, сколько стоил один ответ и был ли он вообще верным. Для компании, которая уже запускает агентов, именно эта слепота и есть настоящая проблема, а сам агент тут вторичен.
«Моё дело» — это сервис интернет-бухгалтерии и бухгалтерского аутсорсинга, и агенты у них уже работали. Заказывали они не очередного агента, а слой под ними: возможность видеть, считать и проверять каждое обращение к модели. И делать это внутри собственной инфраструктуры, не отдавая данные наружу.
Мы отдали рабочий образец-эталон — небольшой агент, обвязанный полным продакшн-слоем: трейсинг каждого вызова, учёт стоимости и автоматическая оценка качества. Всё это поднято в их собственном Kubernetes. Дальше их команда берёт этот образец как шаблон и подключает к слою уже свои настоящие сервисы.
Зрелость в проде начинается там, где демо заканчивается: надо видеть каждое обращение к модели, понимать, во что оно обошлось, и уметь доказать, что ответ верный. Ничего из этого не появляется само, и почти каждая такая задача упёрлась в отдельное инженерное решение.
Самое важное решение в проекте мы сначала приняли неправильно.
Трейсинг записывает всю цепочку действий агента: какой промпт ушёл в модель, что она ответила, сколько токенов потратила, какие инструменты вызвала. Подключить его проще всего готовым SDK от Langfuse, той системы наблюдаемости, которую мы и разворачивали. Так и сделали в первой версии, и заработало с первого раза.
И мы это выкинули. Образец заказчик копирует на каждый свой сервис, поэтому любая привязка внутри него размножится вместе с шаблоном. Сцепи мы приложение с конкретной системой наблюдаемости, и этот вендор-лок переехал бы в каждый сервис: сменить Langfuse на Grafana или Datadog стало бы нельзя без переписывания кода во всех сервисах сразу.
Поэтому приложение отдаёт телеметрию в открытом стандарте OpenTelemetry и не тянет ничей SDK. Что куда направить, решает уже инфраструктура: между приложением и наблюдаемостью стоит отдельный сборщик, и именно он распределяет потоки. Как устроен этот путь целиком — в следующем разделе.
Полной независимости это не даёт: бэкендом для трейсов, оценок и разметки остаётся Langfuse, и сменить его — отдельная работа. Но вендор-нейтральным остаётся самое дорогое, те самые приложения, которых у компании со временем будут десятки и которые копируют чаще всего.
Идея простая. Каждое действие, которое агент совершает по ходу ответа, записывается отдельно. Записи стекаются в одну точку, там разделяются по назначению и собираются обратно в наглядную картину одного ответа — что агент делал, сколько это стоило и насколько ответ хорош. Вот этот путь по шагам.
gen_ai. и дополнительно отправляет в систему, заточенную под LLM.Раньше после ответа агента не оставалось ничего, кроме самого текста. Теперь весь этот путь виден по любому ответу: его можно открыть, разобрать по шагам, посчитать и сравнить с другими.
Контекст к записям приклеивается на уровне инфраструктуры, мимо бизнес-кода агента. Разработчику, который подключает к слою свой сервис, не нужно расставлять эти метки руками: они появляются в записях сами.
Чтобы стоимость было видно, обращения к моделям проходят через единый шлюз LiteLLM. Когда все вызовы идут через одну точку, учёт токенов и денег собирается там же, а не размазывается по сервисам, и квоты задаются централизованно. В контуре заказчика запросы при этом идут на их собственные модели, поднятые на их же железе.
Со стоимостью всплыла честная неприятность. Langfuse считает цену вызова по встроенному справочнику моделей, а собственных моделей заказчика в этом справочнике нет, поэтому стоимость показывается нулевой. Чтобы учёт денег заработал на своих моделях, цены нужно прописывать вручную. Такую мелочь видно только на боевом стеке с собственными моделями, а на чужих облачных моделях её просто не бывает, поэтому в чек-листах она обычно и не появляется.
Качество ответов оценивается автоматически и руками одновременно. На автоматической стороне в Langfuse работают LLM-судьи — отдельные модели-оценщики, которые смотрят на шаги агента и ставят оценки: полезность, краткость, токсичность, совпадение языка ответа с языком вопроса и релевантность найденного контекста. Релевантность контекста при этом меряется двумя способами, по текущему вопросу и по всей истории диалога, потому что это разные вещи.
Оценки ставятся на уровне отдельного шага, а не только на финальном ответе. Это важная деталь: когда ответ плохой, видно, где именно сломалось, в поиске или в генерации.
Рядом идёт ручная разметка. Люди оценивают ответы по своим шкалам, например верность ответа и его обоснованность найденными фрагментами, и для этого в Langfuse заведены очереди разметки. Весь набор оценщиков и шкал заказчик разворачивает у себя одним скриптом. Тонкость в том, что у Langfuse нет публичного API для настройки оценщиков, поэтому скрипт логинится как обычный пользователь и настраивает всё через внутренний интерфейс, а повторный запуск ничего не дублирует. Разовая ручная настройка превращается в повторяемый контур, который живёт в их кластере.
Весь стек — наблюдаемость, шлюз моделей, оркестрация фоновых задач, оценка — развёрнут в собственном Kubernetes заказчика. Трейсы оседают в их Langfuse и ClickHouse, запросы к моделям идут через их LiteLLM. В их боевом контуре модели работают на их же железе, и данные не выходят наружу. Демо мы гоняли на безобидных данных про настольные игры, поэтому для показа модель могла быть и внешней, но для домена, где за агентом стоят бухгалтерия и персональные данные клиентов, контур данных становится жёстким условием.
Из этого условия и выросло одно решение. Готовые сервисы фильтрации от внешних провайдеров отправляют запрос на проверку наружу, что здесь недопустимо. Поэтому защитный фильтр — гардрейл — мы собрали сами и показали как пример: вот платформа, к ней можно подключить готовый фильтр, а можно написать свой, вот пример своего. Он, в отличие от базовых, умеет смотреть на всю историю диалога, а не на одно последнее сообщение. Сам гардрейл в этом проекте был необязательной частью, мы добавили его к демонстрации бонусом.
Чтобы образ приложения не зависел от приватного доступа к внутренней библиотеке, эту библиотеку с автоматической инструментацией мы вложили прямо в репозиторий. Образец остаётся самодостаточным, и его не приходится собирать в обход закрытого контура.
За скучными словами «слой наблюдаемости» стоит конкретная новая возможность. Раньше агент в проде был чёрным ящиком; теперь инженеры заказчика видят каждое обращение к модели, его стоимость и его оценку качества и могут подключить следующий сервис, скопировав готовый образец, без привязки к вендору и не выводя данные из своего контура.
В этом и состоит экономика решения. Один работающий агент — это пока ещё демонстрация, и доверять ему в проде позволяет тот неприглядный слой под ним, который мы и собрали. Его строят один раз, а пользуются им на каждом следующем агенте, поэтому браться за него имеет смысл до того, как агентов в компании станет много.
Трейсинг каждого обращения к модели: дерево вызовов, токены, стоимость, привязка к пользователю и сессии. Контекст запроса дописывается в телеметрию отдельным обработчиком, чтобы бизнес-код ничего не знал о системе наблюдаемости
Единый шлюз к моделям, чтобы учёт токенов и стоимости жил в одной точке, а маршрутизация и квоты задавались централизованно. В их контуре запросы идут на их собственные модели
LLM-судьи оценивают каждый шаг агента (полезность, краткость, токсичность, язык, релевантность контекста), а не только финальный ответ. Рядом — очереди ручной разметки. Весь набор оценщиков разворачивается одним повторяемым скриптом
Загрузка документов в поиск обёрнута в Temporal-воркфлоу: ретраи и таймауты на каждый шаг, видимость статуса по документу, повтор сбойного шага без рестарта всего пайплайна
Демо-агент на pydantic-ai как эталон интеграции с платформой. Намеренно тривиальный по задаче — ценность в продакшн-обвязке вокруг него, которую заказчик копирует на свои сервисы
Самописный защитный фильтр как пример: к платформе можно подключить готовый или написать свой. Смотрит на всю историю диалога. Сторонние сервисы отклонены, потому что они выводят запрос из контура наружу
Ответим, подходит ли задача для AI-агентов, и если да, предложим конкретный план.
Заявка отправлена
Ответим в течение дня на указанный email.
или напишите напрямую — ilya@manaraga.ai