Langfuse
Langfuse — это инструмент для мониторинга LLM-приложений: логирование, трассировка, метрики и анализ качества ответов моделей. Langfuse позволяет отслеживать весь путь запроса: от prompt → до ответа модели → до финальной обработки.
Возможности Langfuse
- Трассировка запросов (traces & spans)
- Сравнение версий промптов
- Анализ качества моделей (scoring)
- Логирование пользовательских диалогов
- Отладка LLM-пайплайнов
- Мониторинг ошибок и задержек
Где применяется в Alem Plus
Langfuse полезен при работе:
- с LLM API (AlemLLM, Gemma3, Qwen3)
- при создании агентов
- при разработке чат-ботов с цепочками запросов
- при RAG-приложениях
- когда нужно отслеживать качество модели и поведение промптов
Шаг 1. Откройте Langfuse и нажмите «Получить доступ»
Шаг 2. Войдите в свой аккаунт в Gitlab
Шаг 3. Создание проекта
Шаг 4. Название проекта
Шаг 5. Получение API-key
- Откройте свой проект и нажмите на Settings
Шаг 6. Create API-key
Что вы получите:
- 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
После запуска проекта:
Откройте Traces: