【2026年】LLMのオブザーバビリティのためのOpenTelemetry — 自己ホスト構成

Dev.to / 2026/4/17

💬 オピニオンDeveloper Stack & InfrastructureTools & Practical UsageModels & Research

要点

  • OpenTelemetryは分散トレーシング、メトリクス、ログの事実上の標準となっており、2025年〜2026年初頭にかけて生成AI向けのセマンティック規約(トークン使用量やモデルパラメータ等)が安定してきた。
  • LLMエージェントのワークフローでは、LLM呼び出しやベクターストアの取得など複数ステップを1本のトレース内のスパンとして扱えるため、トレースの連続性を実現できる。
  • LLM向けのセマンティック属性(例:input/output tokens)により、リクエスト単位でのトークンおよびコストの帰属を、別の課金連携をせずに追跡できる。
  • 自己ホスト構成では、OTel CollectorがOTLP/gRPCでエージェントからテレメトリを受け取り、トレースはTempo/Jaeger、メトリクスはPrometheus、可視化はGrafanaへ出力する形に集約できる。
  • エージェント数が5未満の小規模では、スパン単位やシート単位で課金されがちなマネージドよりも自己ホストの方が費用面で有利だという見立てが示されている。

私は小さなAI自動化の店を回しています——私一人、数体のエージェント、そして予算を食い潰さない形で可観測性を維持する必要のあるセルフホスト構成です。LLMパイプラインの計測を始めたとき、多くの可観測性ガイドが「マネージドプラットフォームを使う」前提になっていることに気づきました。しかし私と同じようにデータとインフラを自分で持ちたいなら、OpenTelemetryは堅実なベンダーニュートラルな土台を提供してくれます。

以下は、2026年にセルフホスト環境でLLMエージェントのトレースにOpenTelemetryを動かす中で学んだことです。

なぜLLMワークロードにOpenTelemetryなのか?

OpenTelemetry(OTel)は、分散トレーシング、メトリクス、ログにおける事実上の標準になりました。2025年を通じてエコシステムは大きく成熟し、生成AIのセマンティック規約——LLM呼び出し、トークン使用量、モデルパラメータを含む——が、2026年初頭に安定したものとして確立されました。

特にLLMワークロードに関して、OTelが重要な点をいくつか挙げます:

エージェントの各ステップをまたぐトレースの連続性。 エージェントがLLMを呼び出し、ベクトルストアから取得し、さらに別のLLMを呼び出す——その各ステップは、1つのトレース内のスパンになります。単発のAPI呼び出しだけでなく、チェーン全体が見えます。

トークンとコストの帰属。 gen_ai のセマンティック規約には、gen_ai.usage.input_tokensgen_ai.usage.output_tokens のような属性が含まれており、別の課金レイヤーを継ぎ足さずに、リクエストごとのコストを追跡できます。

ベンダーニュートラル。 OpenAIを呼ぼうが、Anthropicを呼ぼうが、vLLM経由でローカルモデルを使おうが、計測の形は同じです。可観測性のコードを書き直さずにプロバイダを差し替えられます。

セルフホストの構成

私の構成は控えめです——収集とストレージのレイヤーを動かす単一のVPSで、エージェントは別途デプロイしています。アーキテクチャは次の通りです:

[Your LLM Agents]
       |
       v
[OTel Collector]  ← receives traces via OTLP/gRPC
       |
       v
[Tempo / Jaeger]  ← trace storage
[Prometheus]      ← metrics storage
[Grafana]         ← visualization

self-hosted vs managed cost comparison(セルフホストとマネージドのコスト比較)を見たことがあるなら、5体未満のエージェントで運用している場合は経済性が有利になることを知っているはずです。マネージドプラットフォームはスパン単位、またはシート単位で課金されますが、小規模でもすぐに積み上がってしまいます。

OTel Collectorのセットアップ

Collectorは中核となるハブです。エージェントからテレメトリを受け取り、処理して、ストレージのバックエンドにエクスポートします。以下はLLMトレース向けの最小構成です:

# otel-collector-config.yaml
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  batch:
    timeout: 5s
    send_batch_size: 512
  attributes:
    actions:
      - key: deployment.environment
        value: production
        action: upsert

exporters:
  otlp/tempo:
    endpoint: tempo:4317
    tls:
      insecure: true
  prometheus:
    endpoint: 0.0.0.0:8889

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch, attributes]
      exporters: [otlp/tempo]
    metrics:
      receivers: [otlp]
      processors: [batch]
      exporters: [prometheus]

ここに特別な工夫はありません。バッチプロセッサが効率化を担い、トレースはTempoへ、メトリクスはPrometheusへエクスポートします。これを本番環境に入れるためのより踏み込んだ手順については、production deployment guide がDocker Composeの設定やヘルスチェックをカバーしています。

LLM呼び出しの計測

実際の計測は、使用している言語とSDKに依存します。ここでは、多くのエージェントコードが動いているPythonの例を示します。

まずはパッケージをインストールします:

pip install opentelemetry-api opentelemetry-sdk \
  opentelemetry-exporter-otlp-proto-grpc \
  opentelemetry-instrumentation-requests

次にトレーサをセットアップして、LLM呼び出しをラップします:

from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
# 初期化 provider = TracerProvider() exporter = OTLPSpanExporter(endpoint="http://your-collector:4317", insecure=True) provider.add_span_processor(BatchSpanProcessor(exporter)) trace.set_tracer_provider(provider) tracer = trace.get_tracer("llm-agent") def call_llm(prompt, model="claude-sonnet-4-20250514"): with tracer.start_as_current_span("llm.call") as span: span.set_attribute("gen_ai.system", "anthropic") span.set_attribute("gen_ai.request.model", model) span.set_attribute("gen_ai.request.max_tokens", 1024) response = your_llm_client.complete(prompt=prompt, model=model) span.set_attribute("gen_ai.usage.input_tokens", response.usage.input_tokens) span.set_attribute("gen_ai.usage.output_tokens", response.usage.output_tokens) span.set_attribute("gen_ai.response.model", response.model) return response.content

ポイントは、gen_ai.* のセマンティック・コンベンションを一貫して使うことです。これにより、どのモデルやプロバイダに接続していても、Grafana のダッシュボード、アラート、クエリは同じように動作します。

マルチステップのエージェント・ワークフローをトレースする

ここで本当に役に立つのが、エージェントのワークフロー全体をトレースすることです。各ツール呼び出し、取得(リトリーブ)のステップ、そして LLM の呼び出しはすべて子スパンになります:

def run_agent(task):
    with tracer.start_as_current_span("agent.run") as parent:
        parent.set_attribute("agent.task", task)

        # ステップ 1: コンテキストを取得
        with tracer.start_as_current_span("retrieval.vector_search"):
            context = search_vector_store(task)

        # ステップ 2: コンテキスト付きで LLM を呼び出す
        result = call_llm(f"Context: {context}
Task: {task}")# ステップ 3: もしかするとツールを呼び出す        if needs_tool_call(result):
            with tracer.start_as_current_span("tool.execute") as tool_span:
                tool_span.set_attribute("tool.name", "web_search")
                tool_result = execute_tool(result)
                result = call_llm(f"ツールの結果: {tool_result}
元のタスク: {task}")

        return result

Tempo 経由で Grafana でこれを表示すると、どこにどれだけの時間が費やされたのかが一目で分かるウォーターフォールトレースが得られます――それはベクター検索でしょうか? 最初の LLM 呼び出しですか? ツールの実行ですか? デバッグする際に、推測ではなく実際の挙動を確認できるようにするのが、この種の可視性です。

ダッシュボードで実際に見えるもの

すべてがつながると、セルフホスト型の観測性ダッシュボードで次のことが分かります。

  • エージェントの各ステップごとのレイテンシ分解 — どのスパンが遅いのか、ネットワークなのかモデル推論なのか
  • トークン使用量の推移 — API の予算を使い切る前に暴走するプロンプトを検知
  • モデル/プロバイダー別のエラー率 — 劣化したモデルエンドポイントを早期に見つける
  • トレース検索 — エージェントが暴走した“その”トレースを正確に見つける

少数のエージェントを運用する単独の担当者にとって、このレベルの可視性は、エージェントのワークフローを自信を持って出荷できるか、それとも毎回のデプロイで運に任せるかの違いです。

もろいところと正直な所感

2026 年時点でもまだいくつか面倒なことがあります。

LLM SDK の自動計測(auto-instrumentation)は穴だらけです。 OpenAI の Python SDK は今では OTel のサポートがしっかりしていますが、Anthropic のものはまだ実験段階です。おそらく手動でスパンを書くことになります。

トレース量には驚かされます。 エージェントがループする(リトライや複数ターンの会話)と、大量のスパンが生成されます。サンプリングは早めに設定しましょう。エラーのトレースは保持し、成功トレースの 10% をサンプルする単純なテイルベースのサンプラーがうまく機能します。

Grafana ダッシュボードの構築には時間がかかります。 gen_ai のセマンティック規約は比較的新しく、既成のダッシュボードが多くありません。パネル設定のために午後を一枠確保してください。

まとめ

LLM の観測性のための OpenTelemetry は万能薬ではありませんが、セルフホスト環境で使うための最も実用的な土台として私が見つけたものです。セマンティック規約はプロダクションで使えるほど成熟しており、Collector は非常に堅牢で、自前で Tempo + Grafana スタックを運用するコストはマネージドプラットフォームに払う金額の一部にすぎません。

少数のエージェントを動かして、それが何をしているのかを実際に理解したいなら、このスタックはセットアップにかける時間の価値があります。