Spanlens - LLM呼び出しとエージェントtraceを一か所で見られるオープンソース観測プラットフォーム
(spanlens.io)こんにちは。LLM呼び出しのロギング、コスト追跡、エージェントtraceを一か所で見られるオープンソース観測プラットフォーム Spanlens を作っています。
作った理由
個人のサイドプロジェクトでLLMを使っているうちに、二つのことがずっと気になっていました。
一つはコスト追跡です。
GPTベースのブラウザー拡張を作っていたとき、月末にOpenAIの請求書を受け取って初めて驚いたのが始まりでした。学生なので大きな金額ではありませんでしたが、総額は見えても、どの機能でいくらかかったのか、どのモデルが平均してどれくらいトークンを使っているのかは別途見えませんでした。そこで毎回コードに console.log を埋め込み、CSVに書き出してExcelで集計し、モデル別の単価を掛けて見積もりを出す作業を繰り返していました。
もう一つはエージェントのデバッグでした。
Spanlens に LangGraph 統合を付けながら自分で体験したことですが、複数のLLM呼び出しが混ざったtrace一つに30秒かかったとき、
- どのノードで時間がかかったのか
- なぜ同じツールを二回呼んだのか
- LangGraph state がどの時点で変わったのか
こうしたことを、ログを広げて手で追いかけなければなりませんでした。
この二つを減らしたくて Spanlens を作りました。
主な機能
- baseURL 一行統合
OpenAI/Anthropic/Gemini SDK の baseURL を https://api.spanlens.io/proxy/openai/v1 のように変えるだけで、リクエスト、レスポンス、トークン、コストが自動で記録されます。レスポンスはそのまま passthrough なので、ストリーミング、tool calling、JSON mode もすべて元と同じように動作します。
エージェントのように wrap 方式が必要な場合は、SDK で trace_id、span_id を注入して、親子関係も一緒に記録できるようにしました。
- エージェントtrace + LangGraph topology view
traceを時系列タイムラインとしてだけでなく、実際のグラフノード上に重ねて見られます。LangGraph で書いたエージェントなら、どのノードで時間がかかったのか、どのエッジが最も頻繁に回っているのかを一画面で確認できます。
- Critical Path 自動分析
traceの中で latency を最も多く消費した呼び出しチェーンを自動で表示します。「このtraceはなぜ遅かったのか」の答えにたどり着くまでのクリック数を減らそうとしました。
- Prompts A/B 統計比較
同じプロンプトの二つのバージョンを、latency、コスト、トークン使用量の基準で比較します。単純な平均差ではなく Welch t-test を適用し、標本分散まで見た差を示します。「単に平均が少し低い」ではなく、「有意な差である」と言えるように入れました。
- セルフホスティング
Dockerイメージで自前のサーバーに立てられます。SaaS版と同じコードが public リポジトリにそのままあります。コード全体は MIT ライセンスです。
どう実装したか
現在のパイプラインはだいたい次のような構成です。
- プロキシリクエストは Hono で受け、
AuthorizationヘッダーとX-Spanlens-*メタデータを分離し、provider key を AES-256-GCM で復号して、呼び出し直前にメモリ内でのみ使用します。 - ストリーミングレスポンスは
body.tee()で元のストリームをクライアントに即時返却し、コピーはバックグラウンドでパーサーがトークンとコストを計算します。 - ログは ClickHouse に非同期で投入します。
INSERT失敗時は Supabase フォールバックキューに保存し、cron が再試行する構造で、fire-and-forget ですがデータ消失は避けるようにしました。 - モデル価格はDBテーブルに置き、5分TTLの stale-while-revalidate でキャッシュします。Fallback 価格がコールドスタート時の安全網です。
最初は単純なプロキシでしたが、実際に使ってみると、より重要なのは raw ログではなく正規化されたtraceでした。「このtraceはなぜ遅かったのか」の答えを見つけるには、呼び出し順だけでなく、親子関係、並列呼び出し、Critical Path が必要で、LangGraph topology view や自動 Critical Path のような機能はそこから生まれました。
スタックは Next.js 14、Hono、Supabase Postgres、ClickHouse、すべて TypeScript の pnpm monorepo です。
まだ悩んでいる点
-
セルフホスティングを一行の
docker runでできるようにしたいのですが、そのためには Supabase Postgres も一緒に下ろす必要があります。今は managed Supabase 前提でdocker-composeが web、server、ClickHouse の3コンテナですが、Supabase までセルフホスティングの選択肢を作るべきか、それとも managed 前提を維持するべきか悩んでいます。 -
Prompts A/B 比較には Welch t-test の計算は入っていますが、p-value をそのまま見せるのが役立つのか、結論バッジ(有意/非有意)だけを見せるのがよいのか決めきれていません。標本が小さいとき(n<30)にどう警告を出すかもあわせて悩んでいます。
-
LangGraph topology view ではノード、エッジ、Critical Path は可視化していますが、state channel の変化はノイズが多すぎるため、あえて外しています。実際のデバッグでは state 変化の追跡がより必要なのか、今の水準が適切なのか意見を聞きたいです。
自分で感じた不便さから始めて、継続的に磨いているプロジェクトです。
[ Spanlens ]
Web: https://www.spanlens.io
GitHub: https://github.com/spanlens/Spanlens
セルフホスティングガイド: https://www.spanlens.io/docs/self-host
ダッシュボードUX、トレースの可視化、プロキシ統合の方式、セルフホスティング体験など、どんな観点でもフィードバックをいただけると本当にありがたいです。特に OpenAI/Anthropic/Gemini 以外で対応してほしい provider があれば、コメントで教えてください。
Webサイトにデモ版があるので、ぜひ見ていただけるとうれしいです。
現在のUIは英語で、近日中に日本語対応も追加予定です。
まだコメントはありません。