За прошлый год я запустил 5 сервисов с LLM под капотом. Каждый следующий сервис получался лучше предыдущего: мы оттачивали архитектуру, оптимизировали core микросервиса на FastAPI, быстрее выходили на MVP и ловили меньше багов.

Но довольно быстро стало понятно: LLM‑сервисы сложно интерпретировать. Для бизнес команды они выглядят как black box. Для инженеров — как набор плохо воспроизводимых состояний.

В этой статье я поделюсь практиками, которые:

  • упрощают интерпретацию поведения LLM;

  • делают работу сервиса прозрачной для Product Owners и SME;

  • ускоряют разработку и итерации без передеплоев.

Речь пойдёт про Langfuse — open‑source платформу для трейсинга, тестирования, отладки и версионирования промптов.

Почему именно Langfuse

Решение внедрять Langfuse в компанию было принято после анализа альтернатив: LangSmith, Arize Phoenix, Helicone и других.

Ключевые причины:

  • Полный контроль над данными (Self‑hosting и отсутствие жёсткой привязки к экосистеме LangChain);

  • Готовность к продакшену (Управление промптами, детальный cost tracking, нормальная работа с версиями);

  • Глубокий трейсинг (Поддержка сложных цепочек, агентов и workflow, а не просто логирование API‑вызовов).

Базовая инициализация Langfuse

Предположим, что базовые шаги вы уже сделали:

  • платформа развернута локально или используется Langfuse Cloud;

  • создан проект;

  • выпущены ключи доступа.

Для каждого проекта Langfuse предоставляет:

  • LANGFUSE_SECRET_KEY

  • LANGFUSE_PUBLIC_KEY

Инициализация клиента в коде может выглядеть так:

def get_llm_tracer() -> Langfuse:
    return Langfuse(
        secret_key=settings.langfuse_secret_key,
        public_key=settings.langfuse_public_key,
        host=settings.langfuse_host,
    )

Observation вместо россыпи спанов

В последних версиях SDK Langfuse унифицирован подход к трейсингу: теперь используется один метод, а тип наблюдения задаётся параметром as_type.

Для базового сценария достаточно обернуть вызов LLM в контекстный менеджер:

with llm_tracer.start_as_current_observation(
    name=span_name,
    as_type="generation",
    input=user_input,
    prompt=prompt_obj,
) as observation:
    response = llm_client.chat.completions.parse(
        model=settings.llm_model_base,
        messages=messages,
        temperature=settings.llm_temperature,
        response_format=response_format,
    )

    response_content = response.choices[0].message.parsed
    observation.update(output=response_content)

Этот паттерн одинаково хорошо работает для:

  • обычных LLM‑вызовов;

  • structured output;

  • function calling;

  • любых функций без LLM (как часть цепочки).

Важно: Langfuse совместим с OpenTelemetry (OTel), поэтому вы можете связать LLM‑трейсы с обычными backend‑трейсами и получить сквозную наблюдаемость всего запроса.

Prompt Management: когда PO и SME перестают быть наблюдателями

Одна из сильных сторон Langfuse — визуальная платформа. Помимо observability, в ней есть полноценный Prompt Management.

Для инженера это:

  • версионирование промптов;

  • метрики и статистика использования;

  • контроль изменений.

Для PO и SME — инструмент конфигурирования поведения сервиса без погружения в код.

Ключевой момент: изменение промпта не требует передеплоя микросервиса. Инстанс сервиса подтягивает актуальный промпт по имени и тегу прямо из Langfuse.

Если нужно добавить динамический контекст — используется метод compile.

Пример:

def _get_prompt(
    self,
    name: str,
    user_input: list[str],
    label: str = "production",
    *args,
    **kwargs,
) -> tuple:
    prompt = llm_tracer.get_prompt(name=name, label=label)
    messages = prompt.compile(user_input=user_input, **kwargs)
    return prompt, messages

Сам объект prompt лучше возвращать и передавать в observation — так Langfuse корректно считает количество использований каждого промпта. Это особенно важно, если вы используете Schema‑Guided Reasoning или сложные цепочки рассуждений.

Evaluation и метрики для недетерминированных систем

Метрики важны для любого сервиса. Для LLM — критически важны. Мы использовали арсенал Evaluation в Langfuse: он позволяет запускать сервис на датасете «вопрос‑ответ» и агрегировать результаты.

Пример теста:

def test_with_dataset(
    llm_tracer: Langfuse,
    *args,
):
    dataset = llm_tracer.get_dataset(settings.langfuse_dataset_name)

    for item in dataset.items:
        with item.run(
            run_name=datetime.now().isoformat(),
            run_description="Mud checks extraction",
        ) as root_span:
            # run your LLM workflow

            root_span.score_trace(
                name="Score your LLM workflow",
                value=accuracy,
            )

            # optional
            assert accuracy >= settings.accuracy_tolerance

Мы запускали это как отдельную CI‑джобу через команду pytest. Можно добавить thresholdы и жёсткие проверки, но на уровне MVP это не обязательно. Главное — регулярный прогон и накопление статистики.


Кастомные дашборды — вишенка на торте

И напоследок — приятный бонус. Langfuse позволяет настраивать собственные дашборды и виджеты внутри проекта:

  • выводить кастомные метрики;

  • отслеживать ключевые показатели;

  • собирать обзор состояния сервиса в одном месте.

Для LLM‑сервисов, где поведение не всегда детерминировано, это сильно снижает тревожность команды и упрощает принятие решений.