
こんにちは。クラウド事業部の遠見です。
- 本記事は、「さくらのAI Engine」をOSSのLLM ObservabilityツールであるLangfuseで可視化する検証を実施したエンジニアが、内容をできる限り客観的に共有することを目的に作成しました。
- 本記事内の見解は執筆者個人のものであり、所属組織を代表するものではありません。
前回の記事では、さくらのAI EngineとDatadogのLLM Observabilityを組み合わせた構成を検証しました。
今回は、OSSのLLM ObservabilityツールであるLangfuseをさくらのクラウド上にセルフホストし、同じ構成を再現してみました。
「完全にデータを自分のインフラ内で完結させたい」という構成を意識しています。
目次
- はじめに
- 今回の構成とゴール
- 事前準備
- LangfuseをTerraformで構築する
- 本番検証のWebアプリ作成
- 【検証】チャットUIから各モデルの出力を確認する
- 【検証】LangfuseダッシュボードでTracesを確認する
- 【検証】コスト登録(さくらのAI Engineの料金をLangfuseに反映する)
- DatadogとLangfuseの比較
- まとめ
- お知らせ
はじめに
SaaSとOSSの使い分け
前回の記事でDatadogのLLM Observabilityを試した際、「ここまで手軽に可視化できるのか」という驚きがありました。
一方で、実際の運用を考えると、以下のようなことも考えられます。
| 懸念 | 詳細 |
|---|---|
| トレースデータの外部送信 | プロンプトや応答内容に機密情報が含まれる可能性があるため、送信前後にデータのマスキング処理や、特定のタグ・属性の除外設定を検討する必要がある |
| コストへの影響 | 外部SaaSでは取り込み量に応じた従量課金となるため、トレース量が増加するとコストも比例して増大する |
外部SaaSを使用せず完全に自社インフラで完結させるには、自己ホスト型トレーシング基盤の構築が必要となります。
こうしたニーズに応えるのが、OSSのLLM Observabilityツールです。
今回はその代表格である「Langfuse」を選びました。
Langfuseとは
Langfuseは、OSSのLLM Observabilityプラットフォームです。
MITライセンスで公開されており、セルフホストで完全に自社インフラ内に閉じた運用が可能です。
主な機能は以下の通りです。
| 項目 | 内容 |
|---|---|
| Traces | LLMの入出力・レイテンシ・トークン数の記録 |
| Sessions | 会話単位のセッション管理 |
| Evaluations | モデル出力の品質評価 |
| Prompts | プロンプトのバージョン管理 |
外部SaaSと比較したとき、最大の差別化ポイントは「データが自分のサーバーから出ない」という点です。
今回の構成とゴール
本検証では、前回の構成で利用したチャットアプリを再利用し、Langfuseで挙動を可視化することを目指します。
| 分類 | 項目 | 内容 / バージョン |
|---|---|---|
| 構成概要 | 実行環境 | ローカルPC |
| LLM基盤 | さくらのAI Engine | |
| 監視基盤 | Langfuse v3 (さくらのクラウドでセルフホスト) | |
| 構成管理 | Terraform | |
| ローカル | OS | Windows 11 Home 25H2 |
| PowerShell | 7.5.5 | |
| Python | 3.12.4 | |
| uv | 0.11.7 | |
| Langfuse SDK | 2.60.10 | |
| クラウド | Terraform | 1.14.9 (GitHub Codespaces) |
| サーバーOS | Ubuntu 22.04 | |
| Langfuse | v3 (3.157.0) |
構成図

事前準備
さくらのAI Engineのアカウント作成・トークン取得
前回記事と同様の手順です。
さくらのクラウドAPIキー取得
Terraformがサーバーやディスクをさくらのクラウドに作成するために必要な権限を取得します。
[さくらのクラウド ホーム] > [APIキー] > [APIキーの作成]をクリックします。

[APIキーの作成]で以下を入力します。
さくらのクラウドでTerraformを動かすには、リソースの「作成」や「削除」ができる権限が必要です。
| 項目 | 設定内容 | 詳細・用途 |
|---|---|---|
| APIキー種別 | リソース操作APIキー | Terraformでの構築に必要 |
| APIキー名 | 任意 | 任意の値を入力 |
| アクセスレベル | 作成・削除 | インフラの構築・破棄を行うために必要 |
| サービスへのアクセス権 | すべて未チェック | デフォルト設定のままでOK |

入力が確認できたら、[作成]をクリックします。
[APIキーが作成されました]ダイアログが表示されたら、CSVをダウンロードするか、以下の2つをメモします。
- アクセストークン
- アクセストークンシークレット

オブジェクトストレージのアクセスキー作成
オブジェクトストレージのアクセスキーは、リソース操作APIキーとは別の管理画面から発行が必要です。
[さくらのクラウド ホーム] > [トップ] > [オブジェクトストレージ]をクリックします。

利用したいリージョンを選択して、[OK]をクリックします。

[アクセスキー]ダイアログが表示されたら、以下の2つをメモします。
- アクセスキーID
- シークレットアクセスキー

オブジェクトストレージのバケット作成
Terraformのステート保存用とLangfuseのイベント保存用に、それぞれ別のバケットを作成します。
| バケット | 用途 | 命名例 |
|---|---|---|
| Terraformステート保存用 | Terraform tfstate の保管 | tfstate-sakura-langfuse |
| Langfuseイベント保存用 | Langfuse v3 必須のBlob Store | langfuse-events-yourname |
[オブジェクトストレージ] > [バケット] > [バケットを追加]をクリックします。

[バケットを追加]ダイアログで、任意の名前(例:tfstate-sakura-langfuse)を入力し、[追加]をクリックします。

バケットが作成されます。

同様の手順で、もう一つのバケットを作成します。
2つのバケットが作成できればOKです。

LangfuseをTerraformで構築する
今回は、GitHub Codespacesを使ってTerraformでインフラを構築します。
まずGitHubにリポジトリを作成し、Terraformのコードを配置します。
コードの構成は以下の通りです。
sakura-langfuse ├─ main.tf ├─ variables.tf ├─ terraform.tfvars ├─ outputs.tf └─ scripts/ └─ setup.sh
コードは参考までに記載します。
他の環境で動作するかは検証していないため、あらかじめご了承ください。
【重要】
terraform.tfvarsには機密情報が含まれます。
GitHubなどで管理する場合は、誤って公開リポジトリにプッシュしないよう、必ず.gitignoreに追加されていることを確認してください。
main.tf
terraform { required_providers { sakuracloud = { source = "sacloud/sakuracloud" version = "~> 2.0" } } backend "s3" { bucket = "tfstate-sakura-langfuse" key = "langfuse/terraform.tfstate" region = "jp-north-1" endpoints = { s3 = "https://s3.isk01.sakurastorage.jp" } skip_credentials_validation = true skip_region_validation = true skip_requesting_account_id = true skip_metadata_api_check = true use_path_style = true } } provider "sakuracloud" { zone = "is1c" } data "sakuracloud_archive" "ubuntu" { os_type = "ubuntu2204" } resource "sakuracloud_disk" "langfuse_disk" { name = "${var.server_name}-disk" source_archive_id = data.sakuracloud_archive.ubuntu.id plan = "ssd" size = 20 } resource "sakuracloud_packet_filter" "filter" { name = "langfuse-filter" expression { protocol = "tcp" destination_port = "80" source_network = var.my_ip allow = true description = "Allow HTTP" } expression { protocol = "tcp" destination_port = "32768-61000" allow = true description = "Allow outbound return packets" } expression { protocol = "udp" destination_port = "32768-61000" allow = true description = "Allow outbound return packets UDP" } expression { protocol = "ip" allow = false description = "Deny ALL" } } resource "sakuracloud_server" "langfuse_server" { name = var.server_name core = 2 memory = 8 # v3はClickHouse・Redis追加のため8GBを推奨 disks = [sakuracloud_disk.langfuse_disk.id] network_interface { upstream = "shared" packet_filter_id = sakuracloud_packet_filter.filter.id } disk_edit_parameter { note { id = sakuracloud_note.langfuse_init.id } } } resource "sakuracloud_note" "langfuse_init" { name = "langfuse-deployment" content = templatefile("${path.module}/scripts/setup.sh", { db_password = var.db_password, langfuse_secret = var.langfuse_secret, salt = var.salt, encryption_key = var.encryption_key, clickhouse_password = var.clickhouse_password, redis_password = var.redis_password, s3_access_key_id = var.s3_access_key_id, s3_secret_access_key = var.s3_secret_access_key, s3_bucket_events = var.s3_bucket_events, s3_endpoint = var.s3_endpoint, }) }
variables.tf
variable "server_name" { default = "langfuse-server" } variable "db_password" { type = string sensitive = true } variable "langfuse_secret" { type = string sensitive = true } variable "salt" { type = string sensitive = true } variable "my_ip" { type = string sensitive = true } variable "encryption_key" { description = "32バイトのhex文字列。生成: openssl rand -hex 32" type = string sensitive = true } variable "clickhouse_password" { type = string sensitive = true } variable "redis_password" { type = string sensitive = true } variable "s3_access_key_id" { description = "さくらのオブジェクトストレージ アクセスキー" type = string sensitive = true } variable "s3_secret_access_key" { description = "さくらのオブジェクトストレージ シークレットキー" type = string sensitive = true } variable "s3_bucket_events" { description = "Langfuseイベント保存用バケット名" type = string } variable "s3_endpoint" { description = "さくらのオブジェクトストレージ エンドポイント" type = string default = "https://s3.isk01.sakurastorage.jp" }
terraform.tfvars
db_password = "" # 任意のパスワード langfuse_secret = "" # openssl rand -base64 32 salt = "" # openssl rand -base64 32 my_ip = "" # curl ifconfig.me で確認 encryption_key = "" # openssl rand -hex 32 clickhouse_password = "" # 任意のパスワード redis_password = "" # 任意のパスワード # さくらのオブジェクトストレージで発行 s3_access_key_id = "" # 取得したアクセスキーID s3_secret_access_key = "" # 取得したシークレットアクセスキー s3_bucket_events = "" # 作成したバケット名 # s3_endpoint はデフォルト値あり(石狩リージョンの場合は設定不要)
outputs.tf
output "langfuse_url" { value = "http://${sakuracloud_server.langfuse_server.ip_address}" }
scripts/setup.sh
#!/bin/bash
set -euo pipefail
curl -fsSL https://get.docker.com | sh
SERVER_IP=$(curl -s ifconfig.me)
mkdir -p /opt/langfuse
cat <<EOF > /opt/langfuse/docker-compose.yml
services:
postgres:
container_name: langfuse-postgres
image: postgres:16
restart: always
environment:
POSTGRES_USER: langfuse
POSTGRES_PASSWORD: ${db_password}
POSTGRES_DB: langfuse
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U langfuse"]
interval: 5s
timeout: 5s
retries: 10
clickhouse:
container_name: langfuse-clickhouse
image: clickhouse/clickhouse-server:24.3
user: "101:101"
restart: always
environment:
CLICKHOUSE_DB: default
CLICKHOUSE_USER: clickhouse
CLICKHOUSE_PASSWORD: ${clickhouse_password}
volumes:
- clickhouse_data:/var/lib/clickhouse
- clickhouse_logs:/var/log/clickhouse-server
healthcheck:
test: ["CMD-SHELL", "clickhouse-client --user clickhouse --password ${clickhouse_password} --query 'SELECT 1'"]
interval: 5s
timeout: 5s
retries: 10
redis:
container_name: langfuse-redis
image: redis:7-alpine
restart: always
command: redis-server --requirepass ${redis_password}
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "-a", "${redis_password}", "ping"]
interval: 5s
timeout: 5s
retries: 10
langfuse-web:
container_name: langfuse-web
image: langfuse/langfuse:3
restart: always
depends_on:
postgres:
condition: service_healthy
clickhouse:
condition: service_healthy
redis:
condition: service_healthy
ports:
- "80:3000"
environment:
DATABASE_URL: postgresql://langfuse:${db_password}@postgres:5432/langfuse
NEXTAUTH_URL: http://$SERVER_IP
NEXTAUTH_SECRET: ${langfuse_secret}
SALT: ${salt}
ENCRYPTION_KEY: ${encryption_key}
CLICKHOUSE_URL: http://clickhouse:8123
CLICKHOUSE_USER: clickhouse
CLICKHOUSE_PASSWORD: ${clickhouse_password}
CLICKHOUSE_MIGRATION_URL: clickhouse://clickhouse:9000
CLICKHOUSE_CLUSTER_ENABLED: "false"
REDIS_HOST: redis
REDIS_PORT: "6379"
REDIS_AUTH: ${redis_password}
LANGFUSE_S3_EVENT_UPLOAD_BUCKET: ${s3_bucket_events}
LANGFUSE_S3_EVENT_UPLOAD_REGION: jp-north-1
LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID: ${s3_access_key_id}
LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY: ${s3_secret_access_key}
LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT: ${s3_endpoint}
LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE: "true"
TELEMETRY_ENABLED: "true"
langfuse-worker:
container_name: langfuse-worker
image: langfuse/langfuse-worker:3
restart: always
depends_on:
postgres:
condition: service_healthy
clickhouse:
condition: service_healthy
redis:
condition: service_healthy
environment:
DATABASE_URL: postgresql://langfuse:${db_password}@postgres:5432/langfuse
SALT: ${salt}
ENCRYPTION_KEY: ${encryption_key}
CLICKHOUSE_URL: http://clickhouse:8123
CLICKHOUSE_USER: clickhouse
CLICKHOUSE_PASSWORD: ${clickhouse_password}
CLICKHOUSE_CLUSTER_ENABLED: "false"
REDIS_HOST: redis
REDIS_PORT: "6379"
REDIS_AUTH: ${redis_password}
LANGFUSE_S3_EVENT_UPLOAD_BUCKET: ${s3_bucket_events}
LANGFUSE_S3_EVENT_UPLOAD_REGION: jp-north-1
LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID: ${s3_access_key_id}
LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY: ${s3_secret_access_key}
LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT: ${s3_endpoint}
LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE: "true"
volumes:
postgres_data:
clickhouse_data:
clickhouse_logs:
redis_data:
EOF
cd /opt/langfuse && docker compose up -d
setup.shは、サーバー起動時に自動実行されるセットアップスクリプトです。
DockerとDocker Composeをインストールし、Langfuse v3の全コンポーネント(PostgreSQL、ClickHouse、Redis、langfuse-web、langfuse-worker)を起動します。
ポイントは CLICKHOUSE_CLUSTER_ENABLED: "false" の設定です。
デフォルトではClickHouseがクラスターモード(ReplicatedMergeTree)で接続しようとしますが、シングルノード構成では明示的にfalseを指定することで通常のMergeTreeが使われます。
この設定がないとマイグレーション時にエラーになります。
Terraform関連のコード配置は以上です。
ここからはGitHub Codespacesの環境設定になりますが、初期状態ではTerraformが入っていなかったため、.devcontainer/devcontainer.jsonを作成して、Terraformをインストールしました。
{ "name": "Terraform Environment", "features": { "ghcr.io/devcontainers/features/terraform:1": {} } }
最終的なディレクトリ構成は以下の通りです。
sakura-langfuse ├─ .devcontainer/ │ └─ devcontainer.json ├─ main.tf ├─ variables.tf ├─ terraform.tfvars ├─ outputs.tf └─ scripts/ └─ setup.sh
コードの配置が完了したら、Codespacesを起動します。
起動後、以下のコマンドでTerraformがインストールされているか確認します。
terraform --version
バージョンが表示されればOKです。
Langfuse v3のアーキテクチャについて
Langfuse v3の検証を行う前に、初めはv2で検証を行っていました。
v2は単一コンテナ+PostgreSQLのシンプルな構成でした。
ただ、v2はサポートが終了しているため、v3での検証に切り替えました。
...Langfuse v2 receives security updates until end of Q1 2025.
v3では、大幅にアーキテクチャが刷新されています。
| コンポーネント | v2 | v3 |
|---|---|---|
| Webコンテナ | ✅ | ✅ |
| Workerコンテナ | ❌ | ✅(新規追加) |
| PostgreSQL | ✅ | ✅ |
| ClickHouse | ❌ | ✅(新規追加) |
| Redis | ❌ | ✅(新規追加) |
| S3/Blob Store | ❌ | ✅(新規追加) |
v3では、トレースのイベントがまずS3に書き込まれ、WorkerコンテナがキューからピックアップしてClickHouseに格納するという「非同期パイプライン」になっています。
これにより、大量のトレースデータを高スループットで処理できます。
実際にバケット(例:langfuse-events-tomi)の中身を覗いてみると、以下のようにトレースやオブザベーションのデータがファイルとして格納されている様子が分かります。

デプロイ実行
terraform init terraform plan
terraform planの結果
@user ➜ /workspaces/sakura-langfuse (main) $ terraform plan
data.sakuracloud_archive.ubuntu: Reading...
data.sakuracloud_archive.ubuntu: Read complete after 1s [id=113702234083]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# sakuracloud_disk.langfuse_disk will be created
+ resource "sakuracloud_disk" "langfuse_disk" {
+ connector = "virtio"
+ encryption_algorithm = "none"
+ id = (known after apply)
+ name = "langfuse-server-disk"
+ plan = "ssd"
+ server_id = (known after apply)
+ size = 20
+ source_archive_id = "113702234083"
+ zone = (known after apply)
}
# sakuracloud_note.langfuse_init will be created
+ resource "sakuracloud_note" "langfuse_init" {
+ class = "shell"
+ content = (sensitive value)
+ description = (known after apply)
+ id = (known after apply)
+ name = "langfuse-deployment"
}
# sakuracloud_packet_filter.filter will be created
+ resource "sakuracloud_packet_filter" "filter" {
+ id = (known after apply)
+ name = "langfuse-filter"
+ zone = (known after apply)
+ expression {
+ allow = true
+ description = "Allow HTTP"
+ destination_port = "80"
+ protocol = "tcp"
+ source_network = (sensitive value)
}
+ expression {
+ allow = true
+ description = "Allow outbound return packets"
+ destination_port = "32768-61000"
+ protocol = "tcp"
}
+ expression {
+ allow = true
+ description = "Allow outbound return packets UDP"
+ destination_port = "32768-61000"
+ protocol = "udp"
}
+ expression {
+ allow = false
+ description = "Deny ALL"
+ protocol = "ip"
}
}
# sakuracloud_server.langfuse_server will be created
+ resource "sakuracloud_server" "langfuse_server" {
+ commitment = "standard"
+ core = 2
+ cpu_model = (known after apply)
+ disks = (known after apply)
+ dns_servers = (known after apply)
+ gateway = (known after apply)
+ gpu_model = (known after apply)
+ hostname = (known after apply)
+ id = (known after apply)
+ interface_driver = "virtio"
+ ip_address = (known after apply)
+ memory = 8
+ name = "langfuse-server"
+ netmask = (known after apply)
+ network_address = (known after apply)
+ private_host_name = (known after apply)
+ zone = (known after apply)
+ disk_edit_parameter {
+ note {
+ id = (known after apply)
}
}
+ network_interface {
+ mac_address = (known after apply)
+ packet_filter_id = (known after apply)
+ upstream = "shared"
+ user_ip_address = (known after apply)
}
}
Plan: 4 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ langfuse_url = (known after apply)
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.
terraform apply
terraform apply完了後、サーバーのIPアドレスが出力されます。
Outputs: langfuse_url = "http://xxx.xxx.xxx.xxx"
サーバー起動後、DockerコンテナのHealthCheckが通るまで1〜2分ほどかかります。
[さくらのクラウド] > [サーバー] > [該当のサーバーにチェック] > [詳細]から進捗を確認できます。

[コンソール]からプロンプト画面をクリックすると、プロンプト入力が可能になります。
以下の画面では、まだ起動が進行中のため待ちます。

Ubuntuのログイン画面が出たら、ubuntuと入力してEnterキーを押すことで、サーバーにログインできます。

コンテナの起動確認をしてみます。
sudo docker ps
全てのコンテナが Up 状態であればOKです。

これで、Codespaces の環境を削除してもインフラの状態を維持できるようになりました。
【補足】オブジェクトストレージに保存された
tfstate
terraform apply実行後、さくらのオブジェクトストレージを確認すると、設定通りtfstate-sakura-langfuseバケット内にlangfuse/terraform.tfstateが作成されていることが確認できます。

Langfuseの初期設定
terraform apply完了の画面に戻り、OutputされたURLにアクセスします。


Langfuseの画面が表示されます。
初回は誰もユーザーがいないため、Sign upします。

必要な情報を入力し、[Sign up]をクリックします。

サインアップ後、以下の順序で初期設定を行います。
1. Organization作成
[Organization] > [New Organization]をクリックします。

[Organization name]に任意の値を入れて、[Create]をクリックします。

[Organization Members]には、現在は初めに作ったユーザー以外はいないため、そのまま[Next]をクリックします。

2. Project作成
[Project name]に任意の値を入れて、[Create]をクリックします。

3. APIキー発行
[Project Settings] > [Create new API keys]をクリックします。

[Create API Keys]ダイアログで、[Note]に任意の値を入れて、[Create new keys]をクリックします。

表示された以下の3つの値をメモします。
- Public Key(
pk-lf-...) - Secret Key(
sk-lf-...) - Host URL(
http://サーバーのIPアドレス)

本番検証のWebアプリ作成
作業ディレクトリに移動し、uvでプロジェクトを初期化します。
cd sakura-langfuse-chat uv init
必要なパッケージを追加します。
Langfuse SDKはv2系を使います。
uv add fastapi uvicorn openai python-dotenv uv add "langfuse>=2.60.0,<3.0.0"
| パッケージ | 役割 | 今回の用途 |
|---|---|---|
fastapi |
WebAPIフレームワーク | /api/chat エンドポイントを作る |
uvicorn |
ASGIサーバー | FastAPIを実際に動かすサーバー |
openai |
OpenAI SDK | さくらのAI Engine(OpenAI互換)への接続 |
langfuse |
Langfuse トレースSDK | LLMのレイテンシ・トークン・エラーをLangfuseに送信 |
python-dotenv |
環境変数管理 | .env ファイルからAPIキーを読み込む |
ファイル構成は以下の通りです。
sakura-langfuse-chat/ ├─ .env ├─ .env.example ├─ app.py ├─ main.py ├─ pyproject.toml ├─ static/ │ └─ index.html └─ uv.lock
【重要】
.envには機密情報が含まれます。
GitHubなどで管理する場合は、誤って公開リポジトリにプッシュしないよう、必ず.gitignoreに追加されていることを確認してください。
環境変数の設定
.env.exampleをコピーして.envを作成します。
copy .env.example .env
.envに以下を設定します。
# さくらのAI Engine SAKURA_API_KEY=さくらのAI Engineのアカウントトークン # Langfuse(セルフホスト) LANGFUSE_PUBLIC_KEY=pk-lf-xxxxxxxx LANGFUSE_SECRET_KEY=sk-lf-xxxxxxxx LANGFUSE_HOST=http://サーバーのIPアドレス
Langfuseはセルフホストサーバーのホスト名を指定するだけです。
バックエンド(app.py)のポイント
Langfuseクライアントの初期化
from langfuse import Langfuse langfuse = Langfuse( public_key=os.getenv("LANGFUSE_PUBLIC_KEY"), secret_key=os.getenv("LANGFUSE_SECRET_KEY"), host=os.getenv("LANGFUSE_HOST"), )
ホスト名を指定するだけで動作します。
トレースとGenerationの記録
# トレース(会話セッション単位) trace = langfuse.trace( name="sakura-chat", session_id=req.session_id, input=req.message, ) # Generation(LLM呼び出し単位) generation = trace.generation( name="sakura-llm-call", model=req.model, input=[{"role": "user", "content": req.message}], )
LangfuseはTrace/Generationオブジェクトを明示的に扱うスタイルです。
LLM呼び出し後の記録
generation.end(
output=answer,
usage={
"input": response.usage.prompt_tokens,
"output": response.usage.completion_tokens,
"total": response.usage.total_tokens,
},
metadata={
"latency_ms": latency_ms,
"model_provider": "sakura-ai-engine",
},
)
trace.update(output=answer)
トークン数・レイテンシ・モデル名が全てLangfuseに記録されます。
サーバー起動・動作確認
uv run uvicorn app:app --host 0.0.0.0 --port 8000 --reload
ブラウザで http://localhost:8000 にアクセスし、チャット画面が表示されればOKです。
チャット画面のUIは前回と同じものを使用しています。

【検証】チャットUIから各モデルの出力を確認する
3つのモデルで「LLM Observabilityとは?」と質問します。
前回と同様なので、実際の動作については以下をご参照ください。
【検証】LangfuseダッシュボードでTracesを確認する
ブラウザでhttp://サーバーIPにアクセスし、作成したプロジェクトを選択します。
Tracesの確認
[Tracing] > [Traces]を開くと、送信されたトレースの一覧が表示されます。
各トレースをクリックすると以下のような詳細が確認できます。
| 項目 | 記録内容 |
|---|---|
| Input / Output | プロンプトと応答内容 |
| Model | 使用したモデル名(gpt-oss-120bなど) |
| Usage | 各種トークン数(Input / Output / Total) |
| Latency | レイテンシ(ms単位) |

[Tracing] > [Observations]では、LLM呼び出し単位の一覧が確認できます。
モデル名でフィルタリングすることで、モデルごとのトークン消費量・レイテンシを比較できます。
| モデル | 平均レイテンシ | トークン消費傾向 |
|---|---|---|
preview/Phi-4-mini-instruct-cpu |
約5分 | 少ない(途中で打ち切り) |
gpt-oss-120b |
約20秒 | 多い(長文出力) |
llm-jp-3.1-8x13b-instruct4 |
約4秒 | 中程度(簡潔な出力) |

各トレースをクリックすると、詳細が確認できます。

【検証】コスト登録(さくらのAI Engineの料金をLangfuseに反映する)
Langfuseでは、カスタムモデルのUnit Priceを手動登録することで、トークン消費量からコストを推計できます。
ただし、LangfuseのModel DefinitionはUSD建て固定のため、円建てのさくらのAI Engine料金をドル換算して登録する必要があります。
今回の換算式
Langfuse入力値 = 円料金 ÷ 150(円→ドル) ÷ 10(10Kトークン→1Kトークン)
以下、さくらのAI Engineの公式料金をもとにしています。
換算した入力値は以下の通りです。
gpt-oss-120b / llm-jp-3.1-8x13b-instruct4
| 項目 | 円 | Langfuse入力値(per 1K tokens) |
|---|---|---|
| Input | 0.15円/10,000トークン | 0.000001 |
| Output | 0.75円/10,000トークン | 0.000005 |
preview/Phi-4-mini-instruct-cpu
| 項目 | 円 | Langfuse入力値(per 1K tokens) |
|---|---|---|
| Input | 0.01円/10,000トークン | 0.0000000667 |
| Output | 0.03円/10,000トークン | 0.000000200 |
登録手順
[Settings] > [Models] > [Add Model]をクリックします。

[Create Model]で以下を入力し、[Submit]をクリックします。
| 項目 | 設定値(例:gpt-oss-120b) |
|---|---|
| Model Name | gpt-oss-120b |
| Match Pattern | (?i)^(gpt-oss-120b)$ |
| Input price per 1k tokens | 0.000001 |
| Output price per 1k tokens | 0.000005 |
| Tokenizer | None |


同様に llm-jp-3.1-8x13b-instruct4 と preview/Phi-4-mini-instruct-cpu も登録します。

設定後に新しいトレースを送ると、コストが反映されていることが確認できます。

DatadogとLangfuseの比較
今回の検証を通じて感じた両者の特徴をまとめます。
| 観点 | Datadog LLM Observability | Langfuse(セルフホスト) |
|---|---|---|
| セットアップの手間 | 低い(APIキーのみ) | 高い(サーバー構築が必要) |
| データの所在 | Datadogのクラウド(AP1なら国内) | 完全に自社インフラ内 |
| コスト | トレース量に応じた従量課金 | インフラコストのみ |
| 可視化の豊富さ | 高い(既製のダッシュボードが充実) | 中程度(カスタマイズ性は高い) |
| 導入の判断軸 | 既存のDatadog監視と統合したい場合 | データを外部に出せない場合・OSSで完結させたい場合 |
セットアップの手間やメンテナンスコストは、Langfuseの方が確実に大きいです。
しかし、カスタマイズ性やデータの所在などのコントロールの部分は柔軟な対応が可能です。
まとめ
さくらのAI EngineとLangfuseを組み合わせることで、トレースデータを含む全てのデータをさくらのクラウド内で完結させるLLM Observability基盤を構築できました。
Datadogとの最大の差は「データの所在」です。
プロンプトや応答内容には機密情報が含まれる場合があり、それを外部に送ることへのハードルが高い現場は少なくありません。
そのような場合、Langfuseのセルフホスト構成は有力な選択肢になります。
一方で、セットアップやメンテナンスの手間、ダッシュボードの完成度ではDatadogに軍配が上がります。
既存のDatadog監視基盤がある場合はDatadog、データを完全に自社管理したい場合はLangfuse、という使い分けが現実的な判断軸になると思います。
さくらのAI Engine + OSSツールという組み合わせで、「国内完結のAI監視基盤」を低コストで構築できるか
今回のこの検証が、皆さんのLLM運用基盤選定の参考になれば幸いです。
お知らせ
現在、弊社はさくらインターネットとパートナー契約を締結しています。 www.ap-com.co.jp
また、Datadog社ともパートナーシップを結んでおり、その知見を活かした運用支援に力を入れています。
www.ap-com.co.jp
一緒に働いていただける仲間も募集中です!
今年もまだまだ組織規模拡大中なので、ご興味持っていただけましたらぜひお声がけください。
www.ap-com.co.jp