Представьте: вы ведете автомобиль, а на приборной панели нет ни спидометра, ни датчика топлива, ни каких-либо показателей вообще. Ни-че-го. В принципе ехать можно, но вы не знаете, какая у вас скорость, сколько бензина и есть ли с авто какие-то проблемы. Примерно так выглядит работа с LLM-приложением без инструментов мониторинга: система вроде бы и функционирует, но о ее внутреннем состоянии можно только догадываться.
Другое дело, когда у вас под рукой все необходимые показатели. Если вернуться к примеру с машиной, то по информации на приборной панели вы можете прогнозировать, когда лучше заправиться, и вовремя заметите возможные проблемы. Langfuse для LLM-приложений — это как раз такая приборная панель.
Меня зовут Александр Сесоров, я работаю инженером по тестированию в YADRO. Занимаюсь задачами автоматизации оценки производительности, точности и эффективности моделей на различных конфигурациях. Сегодня проведу краткий экскурс в инструментарий Langfuse и на примерах из практики покажу, как превратить оценку качества LLM-приложения из гадания в систематизированный и прозрачный сбор метрик на всех этапах.

Как все начиналось
В нашей команде была задача — улучшить внутренний сервис транскрибации аудиовстреч и последующей саммаризации текста. Для этого нужно было внедрить прозрачный сбор метрик качества саммаризации.
Мы постоянно тестировали гипотезы: пробовали разные LLM, крутили версии промптов и меняли параметры. Но не получали четкого ответа, стало ли лучше. Оценивать результаты «на глаз», читая сотни саммаризаций, — путь в никуда. Нам требовался инструмент, который позволил бы наглядно тестировать и сравнивать метрики для разных моделей и промптов, и желательно с полной трассировкой по всем компонентам сервиса.
Так мы и пришли к Langfuse. Но перед тем как перейти к обзору, давайте подсветим, какие вообще сложности могут возникнуть при оценке качества прил��жений.
Четыре главные проблемы при оценке качества LLM
Непрозрачность результатов. Не секрет, что поведение моделей часто напоминает «черный ящик». На первый взгляд их ответы выглядят связными и корректными, но при детальном рассмотрении внутри могут скрываться фактические ошибки и галлюцинации. Если у вас нет инструментов, чтобы заглянуть внутрь процесса генерации и сравнить ответ с исходными данными на каждом этапе, критичные неточности очень легко пропустить.
Субъективность оценки. Трудно формализовать, какой именно ответ модели считать «хорошим», если речь не идет о простой классификации. Оценка генерации текста всегда зависит от контекста. Что лучше — краткий сухой ответ или развернутый, но с «водой»? Стандартные метрики вроде BLEU или ROUGE здесь часто бессильны: они смотрят на пересечение слов, а не на смысл. Без четких критериев качества команда рискует погрязнуть в спорах о вкусовщине вместо работы над точностью.
Приведу простой пример. Модель попросили составить список всех фильмов, снятых Тарковским, и упорядочить их по дате выхода. Вот что получилось:

Выдача слева корректна, в нее даже вошла короткометражка, но есть ошибка с годом выхода одной из картин: «Зеркало» было снято в 1974 году, а не в 1975-м. Во втором случае (справа) модель потеряла короткометражку «Станция метро», но год выхода везде указан корректно. Какой из этих ответов в итоге лучше, зависит от контекста задачи.
Сложность отладки. Когда мы получаем неудовлетворительный результат от LLM, причина сбоя не всегда очевидна. Возможно, дело в некачественном промпте. А может, проблема возникла раньше — например, в RAG-системе, если поисковик (retriever) извлек из базы данных нерелевантный контекст, он же «мусор». Или же мы просто выбрали неудачные параметры генерации — temperature/top-p. В сложных пайплайнах, где компонентов много, найти истинный источник ошибки без трассировки — тот еще вызов.
Нестабильность. Со временем качество работы приложения может меняться, и не всегда в лучшую сторону. Это происходит после обновления самой модели провайдером, изменений в промптах или появления новых типов входных данных, к которым система не была готова. Без постоянного мониторинга мы рискуем не заметить момент деградации качества и н�� исправить ситуацию вовремя.
Основные трудности прояснили — Langfuse как раз призван решить часть из них.
Открытый исходный код и понятный интерфейс: обзор Langfuse

Langfuse состоит из двух основных компонентов — SDK для сбора телеметрии в коде и отдельно Web UI для анализа. В нем можно посмотреть пошаговый анализ работы каждого вызова LLM-приложения, пользовательский интерфейс (UI) интуитивно понятный. Интегрируется Langfuse уже в исходные пайплайны.
Если сравнивать с аналогами, одно из преимуществ Langfuse — полностью открытый исходный код. Его можно бесплатно развернуть на своих серверах, в своем контуре и при необходимости настроить под себя.
Под капотом Langfuse несколько ключевых компонентов. Основа всего — трейсы (traces).

Каждый трейс состоит из отдельных шагов — наблюдений (observation). В Langfuse их всего три типа:
-
генерация (generation) — вызов языковой модели;
-
спан (span) — любая другая функциональность вроде вызова API и обращения к базе данных;
-
события (events)— моментальное дебажное текстовое сообщение, как лог, чтобы зафиксировать что-то важное в определенный момент времени работы приложения.
Следующий ключевой компонент — метрики. Langfuse автоматически собирает базовые показатели Performance для каждой генерации. Это и время задержки, и расходы на вызов API модели, и объем токенов.
Но главное, мы можем добавлять собственные кастомные метрики. Поддерживаются:
-
числовые — например, оценка качества ответа условно по шкале от одного до десяти;
-
булевые, то есть прошел ли ответ проверку;
-
категориальные, текстовые оценки — например, тема разговора или текстовые градации точности ответа «полностью корректный», «частично корректный» и так далее.
Все эти метрики можно привязывать ко всему трейсу или отдельно к его шагам — генерации, спану, событию. Если у вас внешняя модель, также доступна настройка анализа стоимости вызовов.

Для системного улучшения приложений в Langfuse есть датасеты и эксперименты. Датасет — это коллекция тестовых примеров, они задаются через UI или код. Каждый пример — это входные данные и эталонный ответ для сравнения, но он есть не всегда. Эксперимент — это запуск определенной версии приложения, например с новым промптом или моделью на примерах из датасета. Langfuse записывает и логирует все результаты, чтобы на одном датасете можно было сравнить разные версии моделей и промптов и выбрать лучшую.

Еще в Langfuse есть инструментарий для создания шаблонов промптов прямо в UI. В них вы можете задать переменные, которые потом определите уже в коде, чтобы не делать множество промптов с плюс-минус одинаковыми входными данными. Между шаблонами легко переключаться, можно сравнивать версии и метрики для них.
Чтобы видеть картину в целом, в Langfuse предусмотрены дашборды. Тут сразу отмечу, что в версии, которая развернута сейчас в контуре компании YADRO, они не поддерживаются. Поэтому мы и перешли к использованию Superset, об этом расскажу ниже. Но в последних версиях дашборды уже добавлены, они кастомизируются, на них можно вывести ключевые показатели приложений, метрики качества и не только, поэтому сказать о них важно.

Как интегрировать в проекты
Чтобы создать новый проект, достаточно нажать кнопку New Project и задать название. Дальше мы получаем API-ключи и секретный публичный ключ, который будем использовать в пайплайне нашего приложения, чтобы логировать метрики.
Ключи, которые выдаются на этапе создания проекта, можно использовать для инициализации в Langfuse. Прямо в UI есть подсказка, как это сделать в чистом Python или отдельных библиотеках — Lang Chain, LLM Index и других. При необходимости можете создать дополнительные ключи. Пригодится, если нужно разграничить права доступа: ключ для администратора, только для чтения и так далее.
Дальше покажу, как мы размечаем код для логирования данных в Langfuse. В первую очередь создаем родительский трейс для всей операции с помощью функции Langfuse trace — он будет основным для всего пайплайна. Можно задать ему имя и юзера и любые полезные данные, которые хотим видеть в дашборде. Поддерживаются все типы данных, которые есть в Python.
trace = langfuse.trace(
name="summarization",
user_id="username",
metadata={"environment": "dev"}
)
Дальше мы оборачиваем отдельные шаги нашего пайплайна. Для транскрибации аудио, которая не является вызовом языковой модели, можно использовать функцию trace span и залогировать в нее входные данные. Залогировать можно и результат работы транскрибации, чтобы смотреть его прямо в Langfuse.
span = trace.span(
name="transcription",
input={"audio_path": path},
metadata={"some": "useful data"}
)
transcription = transcribe(audio_path=path)
span.end(output={"transcription": transcription})
Чтобы вызвать LLM, используем trace generation. Передаем все самое важное, касающееся генерации, — саму модель, промпт, а после выполнения логируем полученный ответ и данные об использованных токенах. Для разных подходов и библиотек применяются разные способы подсчета токенов, поэтому мой код тут только для примера:
generation = trace.generation(
name="summary-generation",
input=transcription,
model=model_name,
prompt=prompt
)
answer = generate_summary(transcription, model_name, prompt)
usage = {"promptTokens": 25, "completionTokens": 15, "totalTokens": 40}
generation.end(output=answer, usage=usage)
Добавить оценку так же просто, как и создать отдельные трейсы и шаги. С помощью функции langfuse score мы привязываем какую-либо метрику ко всему трейсу.
langfuse.score(
trace_id=trace.id,
name="faithfulness",
value=0.95,
comment="Reason from DeepEval"
)
Для конкретной генерации можно взять отдельно метрику с помощью generation score. В моем примере это bertscore:
generation.score(
name=“bertscore",
value=0.8,
data_type="NUMERIC"
)
Так же можно добавить оценку для любого шага, а потом в UI Langfuse посмотреть пошаговые оценки для каждого компонента пайплайна.
С датасетами мы тоже работаем через код. Создаем их в Langfuse, загружаем необходимые данные, указываем input и output, если они есть, — с ними можно будет сравнить ответ. Потом во время эксперимента получаем датасет и вызываем приложение для каждого элемента.
Загрузка локального датасета в Langfuse:
langfuse.create_dataset(name="dataset_name");
for item in local_items:
langfuse.create_dataset_item(
dataset_name="dataset_name",
input=item["input"],
expected_output=item["expected_output"]
)
Оборачивая запуск в RAM, мы автоматически связываем каждый прогон с соответствующим элементом. Затем в UI можно будет перейти во вкладку Datasets и для каждого элемента посмотреть, какие были запуски, метрики и так далее. Это сильно упрощает анализ.
Использование загруженного датасета в экспериментах:
dataset = langfuse.get_dataset("dataset_name")
for item in dataset.items:
with item.run(run_name="experiment_name") as root_span:
output = run_my_custom_llm_app(item.input, prompt)
root_span.score_trace(name="sentence_bertscore", value=0.7)
Напоследок ��ще пример с управлением промптами. В интерфейсе Langfuse мы можем создать шаблон с переменными. Тут у меня в фигурных скобках указан language:
«Твоя задача — строго на основании данных из стенограммы подготовить связное и подробное саммари на
{{language}}языке».
В коде мы запрашиваем нужный промпт по имени, можем выбрать определенную версию или найти по тегу — в моем примере это latest. Дальше компилируем, подставляя нужные значения в заданные переменные. Готово, можно брать и работать!
prompt = langfuse.get_prompt("summarization_demo", label="latest", version=19)
compiled_prompt = prompt.compile(language="русском")
Из Langfuse в Superset
Выше я уже писал, что с нашей версией мы не могли создавать кастомные дашборды. Решение было таким — интегрировать аналитику данных, которую собирали в Langfuse, в Superset. С PostgreSQL сделать это было просто.
Мы создали несколько дашбордов. Например, в Nightly попадают ежедневные замеры из триггера для отслеживания флуктуаций в метриках. Тут есть фильтрация по версиям моделей репозитория приложений саммаризации или промпта. На любой трейс из nightly-запуска можно перейти из Superset в Langfuse, для этого есть ссылки.

Другие замеры у нас логируются на экспериментальном дашборде. Тут доступны более подробные таблицы и графики для всех замеров. Мы сраниванием метрики для разных моделей, версий промптов и репозитория. После фильтрации наблюдений по какому-либо критерию Superset можно быстро перейти в сам Langfuse и посмотреть их пошаговое выполнение для каждого запуска.


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

Что в итоге
Так в нашей команде появился инструмент для удобного логирования и управления метриками с прозрачной трассировкой. Теперь мы можем быстро определить, что конкретно повлияло на ту или иную оценку. По нашему опыту Langfuse действительно эффективен для мониторинга работы приложений на основе LLM. С ним можно оценить и увидеть слабые места в компонентах всего пайплайна, а не его отдельной части. Но отмечу, что этот инструмент не подходит для оценок непосредственно LLM: для этого есть огромная масса бенчмарков разной направленности.
С экскурсом в Langfuse у меня на сегодня все. Если у вас есть вопросы или хотите подробнее узнать об интеграции в приложение, оставляйте комментарии или приходите в личку — помогу чем смогу. А пока спасибо за внимание!
Автор: Salexoid


