Langfuse朝ブリーフィングを実装して試す - 異常traceだけをSlackに流せるか
Langfuseのtrace、token、cost、latencyをCloudflare Workers Cronで集計し、見るべき異常だけをSlackへ流す実装検証。
前回の記事「Langfuseの異常だけをSlackに流す朝ブリーフィングを作りたい」では、Langfuse の trace を毎日見に行くのではなく、見るべき異常だけを Slack に流す構想を整理しました。
今回は、その実装編です。
結論から言うと、Cloudflare Workers Cron から Langfuse API を叩き、前日分の trace を集計して Slack に流すところまでは動きました。さらに、合成 trace を投入して token spike と latency 異常を検出し、翌朝の Cron 実行で実際に Slack 通知が届くところまで確認できました。
一方で、素直には進みませんでした。trace 一覧だけでは generation の token / cost が取れず、trace 詳細 API、さらに observation 詳細 API までたどる必要がありました。score も投入はできたものの、trace 詳細の scores には出てこなかったため、score API の扱いは残課題です。
この記事では、実際に作った構成、詰まった点、最終的に Slack に届いた通知、そして次に直すべき点をまとめます。
作ったもの
作ったのは、最小構成の Langfuse 朝ブリーフィングです。
Cloudflare Workers Cron
↓
Langfuse API から前日分の trace を取得
↓
trace 詳細 API / observation 詳細 API で usage と cost を補完
↓
異常候補を抽出
↓
Slack Incoming Webhook に投稿
実装場所は、既存ブログ repo 内の独立した Worker パッケージにしました。
llmops/apps/langfuse-morning-briefing-worker
最初から自動修正や GitHub Issue 化まではやりません。今回の目的は、毎朝 Langfuse を開かなくても、昨日見るべき trace があるかどうかを Slack で判断できる状態を作ることです。
実装対象
今回の前提は次の通りです。
| 項目 | 内容 |
|---|---|
| 対象 Langfuse project | 1 project 前提。ID は環境変数で指定 |
| 対象 trace | 全 trace から開始 |
| 通知先 | Slack Incoming Webhook |
| 実行頻度 | 毎朝 1 回 |
| 実行基盤 | Cloudflare Workers Cron Triggers |
| 保存先 | なし。初回は前日比を扱わず、絶対値しきい値だけ |
project ID、API key、Slack webhook URL はすべて環境変数または Workers secret に逃がしています。公開記事に値を貼ると事故るので、本文では具体値を書きません。
Worker は Cron だけでなく、検証用に POST /run でも手動実行できるようにしました。deploy 後に workers.dev URL が有効になったため、/run と診断用 endpoint は RUN_TOKEN の Bearer 認証で保護しています。
最初に見たかった指標
Langfuse から取りたかった情報は次の通りです。
| データ | 用途 |
|---|---|
| trace id | Slack から Langfuse へ戻るリンク |
| trace name | どの処理か分かるようにする |
| timestamp | JST の前日分で絞る |
| status / level | 失敗 trace を拾う |
| input / output tokens | token 急増を見る |
| cost | cost 異常を見る |
| latency | 遅い実行を見る |
| score | 評価が低い出力を見る |
抽出条件は、まず絶対値のしきい値にしました。
error: 1件以上
total tokens: 50,000 以上
cost: $1.00 以上
latency: 10秒以上
score: 0.5 未満
前日比も見たいところですが、前日比を見るには保存先が必要です。初回から Workers KV や D1 を入れると検証対象が増えるため、今回は「昨日の trace を取り、絶対値しきい値で異常候補を出す」ところに絞りました。
実装した構成
Cloudflare Cron は UTC なので、JST 07:30 に動かすために 22:30 UTC を指定しました。
[triggers]
crons = ["30 22 * * *"]
Slack 投稿は Incoming Webhook を使います。検証中に Discord 互換 Webhook も試しましたが、payload の key が違いました。
| 通知先 | payload |
|---|---|
| Slack Incoming Webhook | { "text": "message" } |
| Discord 互換 Webhook | { "content": "message" } |
この違いで一度 Cannot send an empty message に当たりました。Slack 本線の記事では text を使い、実装上は WEBHOOK_KIND=slack|discord で切り替えられるようにしています。
1つ目の詰まり:remote secretはローカルdevに入らない
最初のローカル検証では、Langfuse API が 401 になりました。
Invalid credentials. Confirm that you've configured the correct host.
原因は、Cloudflare に登録した remote secret がローカルの wrangler dev には自動で渡らないことでした。ローカル検証では .dev.vars に LANGFUSE_SECRET_KEY や SLACK_WEBHOOK_URL を置く必要があります。
切り分け用に /debug/langfuse endpoint を追加し、秘密値そのものは出さずに、base URL、request URL、key の有無、key prefix だけ確認できるようにしました。secret の値を出さずに疎通条件だけ確認できる endpoint は、こういう検証ではかなり役に立ちます。
2つ目の詰まり:trace一覧だけではtokenが取れない
最初は /api/public/traces の一覧から、token、cost、latency、score を全部拾える想定でした。
しかし実データを見ると、trace 一覧だけで generation の usage まで取れるとは限りませんでした。そこで合成 trace を投入する seed script を作り、Langfuse ingestion API に次のイベントを送るようにしました。
trace-create
generation-create
score-create
seed 自体は成功し、Langfuse 画面でも trace と generation は見えました。ただし、最初の Slack 通知では trace が 0 件のままでした。
ここでややこしかったのが timezone です。サンプル trace の timestamp は 2026-06-13T22:10:56Z でしたが、JST では 2026-06-14 07:10:56 です。つまり、朝に手動実行する場合は daysAgo=0 の対象であり、daysAgo=1 では拾えません。
Slack 本文に 対象範囲: from - to を出すようにしたのは、このズレを見落とさないためです。
3つ目の詰まり:observation詳細APIまで必要だった
GET /api/public/traces/{traceId} を呼ぶと、trace 詳細は取れました。しかし、最初に返ってきた observations は generation の詳細オブジェクトではなく、observation id の配列でした。
そのため、最終的には次の3段階で補完する形にしました。
/api/public/traces
↓
/api/public/traces/{traceId}
↓
/api/public/observations/{observationId}
observation 詳細 API まで呼ぶと、次のような値が取れました。
{
"type": "GENERATION",
"usageDetails": {
"input": 30000,
"output": 25000,
"total": 55000
},
"costDetails": {
"input": 0.0045,
"output": 0.015,
"total": 0.0195
},
"latency": 12.3
}
この時点で、token spike と latency 異常は Slack 通知に出せる状態になりました。
実際に届いたSlack通知
翌朝の Cron 実行後、Slack には次のような通知が届きました。
Langfuse Morning Briefing
対象日: 2026-06-14
対象範囲: 2026-06-13T15:00:00.000Z - 2026-06-14T15:00:00.000Z
traces: 8
errors: 0
total tokens: 440,000
estimated cost: $0.1560
見るべき異常:
1. sample-morning-briefing-token-spike - tokens 55,000, latency 12.3s
https://jp.cloud.langfuse.com/project/***redacted***/traces/***redacted***
2. sample-morning-briefing-token-spike - tokens 55,000, latency 12.3s
https://jp.cloud.langfuse.com/project/***redacted***/traces/***redacted***
3. sample-morning-briefing-token-spike - tokens 55,000, latency 12.3s
https://jp.cloud.langfuse.com/project/***redacted***/traces/***redacted***
4. sample-morning-briefing-token-spike - tokens 55,000, latency 12.3s
https://jp.cloud.langfuse.com/project/***redacted***/traces/***redacted***
5. sample-morning-briefing-token-spike - tokens 55,000, latency 12.3s
https://jp.cloud.langfuse.com/project/***redacted***/traces/***redacted***
取れなかった項目: score
検証用 seed を複数回打っていたため、sample trace が複数並んでいます。実運用では、Agent 名や prompt 名で絞る、同名 trace をまとめる、上位件数を調整するといった改善が必要です。
それでも、今回の目的である「毎朝 Langfuse を開かなくても、昨日見るべき異常候補が Slack に流れるか」は確認できました。
うまくいったこと
今回うまくいった点は次の通りです。
- Cloudflare Workers Cron で毎朝実行できた
- Slack Incoming Webhook へ投稿できた
- JST の前日範囲を UTC ISO に変換して集計できた
- trace 一覧、trace 詳細、observation 詳細をたどって token / cost / latency を取得できた
- token 55,000、latency 12.3秒の合成 trace を異常候補として検出できた
- Cron 実行後に実際の Slack 通知が届いた
RUN_TOKENによる手動実行 endpoint の保護も入れられた
個人的には、trace 詳細 API だけで終わらず observation 詳細 API まで見に行く必要があった点が一番の学びでした。Langfuse 画面では一体に見える情報でも、API では trace、observation、score の取得粒度が分かれています。
うまくいかなかったこと
一方で、詰まりもかなりありました。
- remote secret がローカル dev に渡らず、最初は 401 になった
- Discord 互換 Webhook を Slack と同じ payload で叩き、400 になった
- 本番
/runで未捕捉例外により Cloudflare のerror code: 1101が出た - trace 一覧だけでは token / cost / latency が揃わなかった
- trace 詳細の
observationsが詳細オブジェクトではなく id 配列だった score-createは成功したが、trace 詳細のscoresは空だった- pagination はまだ未実装で、trace が 100 件を超える日は取りこぼす可能性がある
特に score はまだ整理できていません。今回の Worker では score を missing field として Slack に出すだけにしています。品質スコア監視までやるなら、score 用 endpoint の確認、score 名ごとの集計、no data の扱いを別途設計する必要があります。
この実装で分かった設計上の論点
朝ブリーフィングは、アラート基盤そのものではありません。
今回作ったものは、あくまで「昨日の実行を朝にざっと見る入口」です。即時検知したい cost spike や latency 悪化であれば、評価窓を持つ monitor の方が向いています。実際、この記事を書いている途中で Langfuse 側に Monitors 機能が出てきたため、しきい値監視の一部は公式機能へ寄せられる可能性があります。
一方で、自前 Worker にはまだ役割があります。
- 複数の異常条件を1つの朝レポートにまとめる
- trace URL を優先度順に並べる
- 自分の運用に合わせたメッセージに整形する
- Langfuse 以外の情報と合わせて日次サマリーにする
つまり、公式 Monitors は「鳴らす」機能、自前 Worker は「読む」機能として分けるのがよさそうです。
残課題
公開時点で残っている課題は次の通りです。
| 課題 | 状態 |
|---|---|
| pagination | 未実装。limit=100 を超える日は取りこぼす可能性がある |
| score 取得 | 未完了。score-create 後も trace 詳細の scores は空だった |
| 前日比 | 未実装。KV や D1 など日次 summary の保存先が必要 |
| 通知の重複整理 | 未実装。同名 sample trace が複数並ぶため、実運用では集約したい |
| Monitors との役割分担 | 調査中。cost / latency の即時監視は Monitors に寄せる余地がある |
これらは未完成ですが、今回の記事の主題である「Langfuse の異常 trace を Slack の朝ブリーフィングとして流せるか」に対しては、検証完了と言ってよい状態です。
まとめ
Langfuse の trace を毎朝 Slack に流す朝ブリーフィングは、最小構成なら Cloudflare Workers Cron で実装できました。
ただし、Langfuse API のデータ取得は思ったより階層的です。trace 一覧だけで完結するのではなく、trace 詳細、observation 詳細までたどって初めて、generation の token、cost、latency が安定して取れました。
この仕組みは、毎日ダッシュボードを開く代わりに「昨日見るべき trace があるか」を Slack で判断する入口としては十分使えそうです。一方で、即時性のあるメトリクス監視や score 監視は、Langfuse Monitors など公式機能との役割分担を考えた方がよさそうです。
次は、Monitors を使ったコスト・レイテンシ監視と、この朝ブリーフィング Worker の責務を切り分けます。