Langfuse

Langfuse — это инструмент для мониторинга LLM-приложений: логирование, трассировка, метрики и анализ качества ответов моделей. Langfuse позволяет отслеживать весь путь запроса: от prompt → до ответа модели → до финальной обработки.


Возможности Langfuse

  • Трассировка запросов (traces & spans)
  • Сравнение версий промптов
  • Анализ качества моделей (scoring)
  • Логирование пользовательских диалогов
  • Отладка LLM-пайплайнов
  • Мониторинг ошибок и задержек

Где применяется в Alem Plus

Langfuse полезен при работе:

  • с LLM API (AlemLLM, Gemma3, Qwen3)
  • при создании агентов
  • при разработке чат-ботов с цепочками запросов
  • при RAG-приложениях
  • когда нужно отслеживать качество модели и поведение промптов

Шаг 1. Откройте Langfuse и нажмите «Получить доступ»

langfuse: карточка сервиса с кнопкой «Получить доступ»
Чтобы получить доступ, нажмите Получить доступ

Шаг 2. Войдите в свой аккаунт в Gitlab

langfuse: авторизация в Gitlab
Введите свои данные

Шаг 3. Создание проекта

langfuse: Создание проекта
Нажмите на New project чтобы создать проект

Шаг 4. Название проекта

langfuse: Название проекта
Введите любое название

Шаг 5. Получение API-key

  • Откройте свой проект и нажмите на Settings
langfuse: API-key
Нажмите на API keys

Шаг 6. Create API-key

langfuse: Create API-key
Нажмите на Create API keys. После получения сохраните

Что вы получите:

  • Secrete Key начинается на “sk-…”
  • Public Key начинается на “pk-…”
  • Host

Примеры использования в Вашем коде:

# pip install langfuse
import os
import requests
from langfuse import Langfuse, observe

langfuse = Langfuse(
    public_key="Ваш секретный API, который начинается на pk-...",
    secret_key="Ваш секретный API, который начинается на sk-...",
    base_url="https://a1-langfuse1.alem.ai",  # или твой self-host URL
)

ALEM_API_KEY ="Ваш ALEM_API_KEY"
ALEM_URL = "https://llm.alem.ai/v1/chat/completions"


@observe(as_type="generation")  # каждый вызов этой функции пойдёт в Langfuse
def alem_chat(prompt: str) -> str:
    payload = {
        "model": "alemllm",
        "messages": [{"role": "user", "content": prompt}],
    }

    resp = requests.post(
        ALEM_URL,
        headers={
            "Authorization": f"Bearer {ALEM_API_KEY}",
            "Content-Type": "application/json",
        },
        json=payload,
        timeout=60,
    )
    resp.raise_for_status()
    data = resp.json()
    return data["choices"][0]["message"]["content"]


if __name__ == "__main__":
    user_prompt = input("Введи вопрос для AlemLLM: ")

    answer = alem_chat(user_prompt)
    print("\nОтвет модели:\n", answer)

    # В конце короткого скрипта отправляем всё в Langfuse
    langfuse.flush()



// === AlemLLM ===
const ALEM_API_KEY = "Ваш ALEM_API_KEY";
const ALEM_URL = "https://llm.alem.ai/v1/chat/completions";

// === Langfuse ===
const LANGFUSE_PUBLIC_KEY = "Ваш секретный API, который начинается на pk-...";
const LANGFUSE_SECRET_KEY = "Ваш секретный API, который начинается на sk-...";
const LANGFUSE_BASE_URL = "https://a1-langfuse1.alem.ai";


async function alemChat(prompt) {
  const payload = {
    model: "alemllm",
    messages: [{ role: "user", content: prompt }],
  };

  const resp = await fetch(ALEM_URL, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${ALEM_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(payload),
  });

  if (!resp.ok) {
    const text = await resp.text();
    throw new Error(`AlemLLM error ${resp.status}: ${text}`);
  }

  const data = await resp.json();
  const answer = data.choices[0].message.content;

  await sendToLangfuse(prompt, answer);

  return answer;
}


async function sendToLangfuse(prompt, answer) {
  const now = new Date().toISOString(); // RFC3339
  const traceId = `trace_${crypto.randomUUID()}`;
  const genId = `gen_${crypto.randomUUID()}`;
  const eventId1 = `event_${crypto.randomUUID()}`;
  const eventId2 = `event_${crypto.randomUUID()}`;

  const body = {
    batch: [
      {
        id: eventId1,
        timestamp: now,
        type: "trace-create",
        body: {
          id: traceId,
          name: "alem_chat_js",
          userId: "demo-user-js",
          input: { prompt },
          startTime: now,
          tags: ["alemllm", "javascript"],
        },
      },
      {
        id: eventId2,
        timestamp: now,
        type: "generation-create",
        body: {
          id: genId,
          traceId: traceId,
          name: "alemllm",
          model: "alemllm",
          startTime: now,
          input: [{ role: "user", content: prompt }],
          output: [{ role: "assistant", content: answer }],
        },
      },
    ],
  };

  const ingestionUrl = `${LANGFUSE_BASE_URL.replace(/\/$/, "")}/api/public/ingestion`;

  const authHeader =
    "Basic " + btoa(`${LANGFUSE_PUBLIC_KEY}:${LANGFUSE_SECRET_KEY}`);

  const resp = await fetch(ingestionUrl, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: authHeader,
    },
    body: JSON.stringify(body),
  });

  if (!resp.ok) {
    const text = await resp.text();
    throw new Error(`Langfuse error ${resp.status}: ${text}`);
  }
}


async function main() {
  const prompt = "Самая длинная река в мире";
  const answer = await alemChat(prompt);
  console.log("Ответ модели:\n", answer);
}

main().catch((err) => {
  console.error("Ошибка:", err);
});


<?php

// 1. Настройки AlemLLM
$ALEM_API_KEY = "Ваш ALEM_API_KEY";
$ALEM_URL     = "https://llm.alem.ai/v1/chat/completions";

// 2. Настройки Langfuse
$LANGFUSE_PUBLIC_KEY = "Ваш секретный API, который начинается на pk-...";
$LANGFUSE_SECRET_KEY = "Ваш секретный API, который начинается на sk-...";
$LANGFUSE_BASE_URL   = "https://a1-langfuse1.alem.ai";

function alem_chat(string $prompt, string $ALEM_URL, string $ALEM_API_KEY): string
{
    $payload = [
        "model" => "alemllm",
        "messages" => [
            ["role" => "user", "content" => $prompt],
        ],
    ];

    $ch = curl_init($ALEM_URL);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST           => true,
        CURLOPT_HTTPHEADER     => [
            "Authorization: Bearer {$ALEM_API_KEY}",
            "Content-Type: application/json",
        ],
        CURLOPT_POSTFIELDS     => json_encode($payload, JSON_UNESCAPED_UNICODE),
        CURLOPT_TIMEOUT        => 60,
    ]);

    $response = curl_exec($ch);
    if ($response === false) {
        throw new Exception("Ошибка curl при вызове AlemLLM: " . curl_error($ch));
    }

    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($status < 200 || $status >= 300) {
        throw new Exception("Ошибка ответа AlemLLM, HTTP {$status}: {$response}");
    }

    $data = json_decode($response, true);
    if (!isset($data["choices"][0]["message"]["content"])) {
        throw new Exception("Неожиданный формат ответа AlemLLM: {$response}");
    }

    return $data["choices"][0]["message"]["content"];
}

// === Аналог декоратора @observe: вручную создаём trace + generation ===
function send_to_langfuse(
    string $prompt,
    string $answer,
    string $LANGFUSE_BASE_URL,
    string $publicKey,
    string $secretKey
): void {
    $now      = gmdate("Y-m-d\TH:i:s.v\Z");
    $traceId  = uniqid("trace_", true);
    $genId    = uniqid("gen_", true);
    $eventId1 = uniqid("event_", true);
    $eventId2 = uniqid("event_", true);

    $body = [
        "batch" => [
            [
                "id"        => $eventId1,
                "timestamp" => $now,
                "type"      => "trace-create",
                "body"      => [
                    "id"        => $traceId,
                    "name"      => "alem_chat_php",
                    "userId"    => "cli-user-php",
                    "input"     => ["prompt" => $prompt],
                    "startTime" => $now,
                    "tags"      => ["alemllm", "php"],
                ],
            ],
            [
                "id"        => $eventId2,
                "timestamp" => $now,
                "type"      => "generation-create",
                "body"      => [
                    "id"        => $genId,
                    "traceId"   => $traceId,
                    "name"      => "alemllm",
                    "model"     => "alemllm",
                    "startTime" => $now,
                    "input"     => [
                        ["role" => "user", "content" => $prompt],
                    ],
                    "output"    => [
                        ["role" => "assistant", "content" => $answer],
                    ],
                ],
            ],
        ],
    ];

    $url = rtrim($LANGFUSE_BASE_URL, "/") . "/api/public/ingestion";

    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST           => true,
        CURLOPT_HTTPHEADER     => [
            "Content-Type: application/json",
            "Authorization: Basic " . base64_encode($publicKey . ":" . $secretKey),
        ],
        CURLOPT_POSTFIELDS     => json_encode($body, JSON_UNESCAPED_UNICODE),
        CURLOPT_TIMEOUT        => 30,
    ]);

    $resp   = curl_exec($ch);
    if ($resp === false) {
        throw new Exception("Ошибка curl при отправке в Langfuse: " . curl_error($ch));
    }

    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($status < 200 || $status >= 300) {
        throw new Exception("Ошибка ответа Langfuse, HTTP {$status}: {$resp}");
    }
}

// === Точка входа — как в Python: берём вопрос, отвечаем, логируем ===

echo "Введи вопрос для AlemLLM: ";
$prompt = trim(fgets(STDIN));

try {
    $answer = alem_chat($prompt, $ALEM_URL, $ALEM_API_KEY);
    echo PHP_EOL . "Ответ модели:" . PHP_EOL . $answer . PHP_EOL;

    // «в конце короткого скрипта отправляем всё в Langfuse»
    send_to_langfuse($prompt, $answer, $LANGFUSE_BASE_URL, $LANGFUSE_PUBLIC_KEY, $LANGFUSE_SECRET_KEY);
    echo PHP_EOL . "Трейс отправлен в Langfuse." . PHP_EOL;

} catch (Exception $e) {
    fwrite(STDERR, "Ошибка: " . $e->getMessage() . PHP_EOL);
    exit(1);
}


Шаг 7. Проверка в Traces

После запуска проекта:

langfuse: запуск проекта
Запустите проект

Откройте Traces:

langfuse: traces
Traces