つれづれなる Agent OPS
LLMOps

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 project1 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 idSlack から Langfuse へ戻るリンク
trace nameどの処理か分かるようにする
timestampJST の前日分で絞る
status / level失敗 trace を拾う
input / output tokenstoken 急増を見る
costcost 異常を見る
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.varsLANGFUSE_SECRET_KEYSLACK_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 の責務を切り分けます。

DUO

Author

DUOps

LLMOps、Agent、MCP、Langfuse、Cloudflare 周辺の実装と運用を、個人で試しながら記録しています。

Xを見る

Related