<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Observability on AI2CORE - AI技術ブログ</title>
    <link>https://www.ai2core.com/tags/observability/</link>
    <description>Recent content in Observability on AI2CORE - AI技術ブログ</description>
    <generator>Hugo -- 0.146.4</generator>
    <language>ja</language>
    <lastBuildDate>Tue, 03 Mar 2026 09:05:00 +0900</lastBuildDate>
    <atom:link href="https://www.ai2core.com/tags/observability/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>OpenTelemetry実践導入ガイド：ログ・メトリクス・トレース統合を90日で定着させる</title>
      <link>https://www.ai2core.com/posts/2026-03-03-opentelemetry-practical-observability-guide/</link>
      <pubDate>Tue, 03 Mar 2026 09:05:00 +0900</pubDate>
      <guid>https://www.ai2core.com/posts/2026-03-03-opentelemetry-practical-observability-guide/</guid>
      <description>OpenTelemetryを現場導入する際の設計、サンプリング、Collector構成、SLO連携、運用改善の具体手順を解説。</description>
      <content:encoded><![CDATA[<h1 id="opentelemetry実践導入ガイドログメトリクストレース統合を90日で定着させる">OpenTelemetry実践導入ガイド：ログ・メトリクス・トレース統合を90日で定着させる</h1>
<p>「監視は入れているのに障害原因の特定が遅い」。この状態は、たいていデータが足りないのではなく、<strong>データが分断されている</strong>ことが原因です。メトリクスは見える、ログは別画面、トレースは導入途中、という構成だと、オンコールは毎回同じ調査を手作業で繰り返すことになります。</p>
<p>OpenTelemetry（OTel）はこの分断を減らすための共通規格です。ただし、導入に失敗するチームも少なくありません。理由は単純で、「計測の追加」だけやって「運用設計」を後回しにするからです。</p>
<p>本記事では、OpenTelemetry を 90 日で現場定着させるための、実務寄りの導入手順を紹介します。</p>
<h2 id="1-まず決めるべき運用目標">1. まず決めるべき運用目標</h2>
<p>OTel を入れる前に、次の問いに答えます。</p>
<ul>
<li>どの障害をどれだけ早く見つけたいか</li>
<li>どのサービスの MTTR をどれだけ下げたいか</li>
<li>どのチームがトリアージ責任を持つか</li>
</ul>
<p>たとえば「API 5xx の原因調査を 60 分 → 15 分に短縮する」と明文化すると、必要な計測が決まります。逆に目標がないと、span を増やす作業が目的化して終わります。</p>
<h2 id="2-参照アーキテクチャ">2. 参照アーキテクチャ</h2>
<p>本番で扱いやすい最小構成は次です。</p>
<ol>
<li>アプリケーションに OTel SDK を導入</li>
<li>エージェント/サイドカー経由で OTel Collector に送信</li>
<li>Collector で加工・サンプリング・ルーティング</li>
<li>Prometheus / Loki / Tempo（または商用基盤）へ出力</li>
</ol>
<p>Collector を中継に置く理由は、アプリ側の再デプロイなしでルール変更できるからです。運用現場ではここが非常に効きます。</p>
<h2 id="3-サービス命名規則を最初に固定する">3. サービス命名規則を最初に固定する</h2>
<p>命名規則を後で直すと、ダッシュボードとアラートが壊れます。以下は最低限のルール例です。</p>
<ul>
<li><code>service.name</code>: <code>domain-service-env</code>（例: <code>billing-api-prod</code>）</li>
<li><code>deployment.environment</code>: <code>prod|stg|dev</code></li>
<li><code>service.version</code>: Git SHA または semver</li>
<li><code>cloud.region</code>: 実リージョン名</li>
</ul>
<p>この 4 つが揃うと、障害時に「どの環境・どのバージョン」が悪いか一気に絞れます。</p>
<h2 id="4-pythonサービス計測の実装例">4. Pythonサービス計測の実装例</h2>
<p>FastAPI を例に、最小導入手順を示します。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>pip install opentelemetry-distro <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>  opentelemetry-exporter-otlp <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>  opentelemetry-instrumentation-fastapi <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>  opentelemetry-instrumentation-requests
</span></span></code></pre></td></tr></table>
</div>
</div><p>起動時に auto-instrumentation を有効化します。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>export OTEL_SERVICE_NAME<span style="color:#f92672">=</span>billing-api-prod
</span></span><span style="display:flex;"><span>export OTEL_EXPORTER_OTLP_ENDPOINT<span style="color:#f92672">=</span>http://otel-collector:4318
</span></span><span style="display:flex;"><span>export OTEL_EXPORTER_OTLP_PROTOCOL<span style="color:#f92672">=</span>http/protobuf
</span></span><span style="display:flex;"><span>export OTEL_TRACES_SAMPLER<span style="color:#f92672">=</span>parentbased_traceidratio
</span></span><span style="display:flex;"><span>export OTEL_TRACES_SAMPLER_ARG<span style="color:#f92672">=</span>0.1
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>opentelemetry-instrument uvicorn app.main:app --host 0.0.0.0 --port <span style="color:#ae81ff">8080</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>次に、業務的に重要な処理へ custom span を追加します。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">from</span> opentelemetry <span style="color:#f92672">import</span> trace
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>tracer <span style="color:#f92672">=</span> trace<span style="color:#f92672">.</span>get_tracer(__name__)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">charge_customer</span>(order_id: str, customer_id: str, amount: int):
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">with</span> tracer<span style="color:#f92672">.</span>start_as_current_span(<span style="color:#e6db74">&#34;billing.charge&#34;</span>) <span style="color:#66d9ef">as</span> span:
</span></span><span style="display:flex;"><span>        span<span style="color:#f92672">.</span>set_attribute(<span style="color:#e6db74">&#34;order.id&#34;</span>, order_id)
</span></span><span style="display:flex;"><span>        span<span style="color:#f92672">.</span>set_attribute(<span style="color:#e6db74">&#34;customer.id&#34;</span>, customer_id)
</span></span><span style="display:flex;"><span>        span<span style="color:#f92672">.</span>set_attribute(<span style="color:#e6db74">&#34;payment.amount&#34;</span>, amount)
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># 決済処理</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>HTTP 自動計測だけでは業務上のボトルネックが見えません。注文IDや課金金額など、<strong>調査に必要な属性</strong>を入れることが重要です。</p>
<h2 id="5-collector設定の実践パターン">5. Collector設定の実践パターン</h2>
<p>Collector の典型構成は次です。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">30
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">31
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">32
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">33
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">34
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">35
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">36
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">37
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">receivers</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">otlp</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">protocols</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">grpc</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">http</span>:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">processors</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">memory_limiter</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">check_interval</span>: <span style="color:#ae81ff">1s</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">limit_mib</span>: <span style="color:#ae81ff">512</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">batch</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">send_batch_size</span>: <span style="color:#ae81ff">1024</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">timeout</span>: <span style="color:#ae81ff">5s</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">resource</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">attributes</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">key</span>: <span style="color:#ae81ff">deployment.environment</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">value</span>: <span style="color:#ae81ff">prod</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">action</span>: <span style="color:#ae81ff">upsert</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">exporters</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">otlp/tempo</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">endpoint</span>: <span style="color:#ae81ff">tempo:4317</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">tls</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">insecure</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">prometheusremotewrite</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">endpoint</span>: <span style="color:#ae81ff">http://mimir:9009/api/v1/push</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">service</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">pipelines</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">traces</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">receivers</span>: [<span style="color:#ae81ff">otlp]</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">processors</span>: [<span style="color:#ae81ff">memory_limiter, batch, resource]</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">exporters</span>: [<span style="color:#ae81ff">otlp/tempo]</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">metrics</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">receivers</span>: [<span style="color:#ae81ff">otlp]</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">processors</span>: [<span style="color:#ae81ff">memory_limiter, batch, resource]</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">exporters</span>: [<span style="color:#ae81ff">prometheusremotewrite]</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>ポイントは <code>memory_limiter</code> と <code>batch</code> を必ず入れることです。高負荷時の Collector 落ちを防げます。</p>
<h2 id="6-サンプリング戦略">6. サンプリング戦略</h2>
<p>全トレースを保存すると費用が跳ねます。まずは次の段階運用が現実的です。</p>
<ul>
<li>フェーズ1: 10% head sampling</li>
<li>フェーズ2: エラーは 100%、正常は 5%</li>
<li>フェーズ3: 高価値APIのみ 20%、その他 1%</li>
</ul>
<p>さらに Collector で tail sampling を使うと、異常リクエストを優先的に残せます。導入初期は head sampling だけでも十分ですが、障害解析を重視するなら tail sampling への移行を計画に含めます。</p>
<h2 id="7-ログとの相関を必ず作る">7. ログとの相関を必ず作る</h2>
<p>トレースだけ可視化しても、最終的にはログを見ます。ログに trace_id と span_id を埋め込み、画面から相互ジャンプできるようにします。</p>
<p>Python の標準 logging なら、フォーマッタに <code>trace_id</code> を追加するだけで改善します。アプリ基盤に共通 formatter を置いて強制するのが現実的です。</p>
<h2 id="8-sloアラート設計の接続">8. SLO/アラート設計の接続</h2>
<p>OTel 導入の価値は、可視化だけでなく SLO 運用に繋がる点です。例:</p>
<ul>
<li>SLI: <code>http.server.duration</code> の p95</li>
<li>SLI: <code>http.server.request.count</code> に対する 5xx 率</li>
<li>エラーバジェット消費率が閾値超過でページ</li>
</ul>
<p>ここで重要なのは、アラート本文に「関連トレースのリンク」を入れることです。オンコールが即座に原因調査へ入れます。</p>
<h2 id="9-90日導入ロードマップ">9. 90日導入ロードマップ</h2>
<h3 id="day-1-14-土台づくり">Day 1-14: 土台づくり</h3>
<ul>
<li>命名規則決定</li>
<li>Collector を冗長構成で配置</li>
<li>主要 2 サービスへ SDK 導入</li>
</ul>
<h3 id="day-15-45-運用化">Day 15-45: 運用化</h3>
<ul>
<li>主要 API の custom span 追加</li>
<li>ダッシュボード標準化</li>
<li>エラー系アラートにトレースリンクを追加</li>
</ul>
<h3 id="day-46-90-最適化">Day 46-90: 最適化</h3>
<ul>
<li>サンプリング改善（費用最適化）</li>
<li>ノイズアラート削減</li>
<li>週次レビューで MTTR 変化を追跡</li>
</ul>
<p>この 90 日で「導入しただけ」から「使って解決できる」状態に変わります。</p>
<h2 id="10-よくある失敗と対策">10. よくある失敗と対策</h2>
<h3 id="失敗1-span-を増やしすぎて遅くなる">失敗1: span を増やしすぎて遅くなる</h3>
<p>対策: 重要フローに限定し、属性も最小から始める。</p>
<h3 id="失敗2-pii-を属性に入れてしまう">失敗2: PII を属性に入れてしまう</h3>
<p>対策: Collector でマスキング、アプリ側で禁止 lint を導入。</p>
<h3 id="失敗3-ダッシュボードがチームごとにバラバラ">失敗3: ダッシュボードがチームごとにバラバラ</h3>
<p>対策: プラットフォーム側で共通テンプレートを配布し、必須指標を統一。</p>
<h2 id="11-導入チェックリスト">11. 導入チェックリスト</h2>
<ul>
<li><input disabled="" type="checkbox"> <code>service.name</code> / <code>deployment.environment</code> / <code>service.version</code> が揃っている</li>
<li><input disabled="" type="checkbox"> Collector に <code>memory_limiter</code> と <code>batch</code> が入っている</li>
<li><input disabled="" type="checkbox"> トレースとログが <code>trace_id</code> で相関できる</li>
<li><input disabled="" type="checkbox"> エラー率アラートからトレースへ遷移できる</li>
<li><input disabled="" type="checkbox"> サンプリング率と月次コストをレビューしている</li>
<li><input disabled="" type="checkbox"> 主要障害のポストモーテムで観測改善が反映される</li>
</ul>
<p>OpenTelemetry は魔法のツールではありません。しかし、計測規約と運用フローをセットで設計すると、障害対応の速度と質は確実に上がります。まずは 2 サービスで勝ち筋を作り、そこから全体展開するのが最短ルートです。</p>
]]></content:encoded>
      <category>Tech</category>
      <category>OpenTelemetry</category>
      <category>Observability</category>
      <category>SRE</category>
      <category>Grafana</category>
      <category>Prometheus</category>
    </item>
    <item>
      <title>LLM運用の可観測性を実装する：OpenTelemetryでつくるPrompt/Token/Latency監視の実践</title>
      <link>https://www.ai2core.com/posts/2026-02-27-llm-observability-opentelemetry/</link>
      <pubDate>Fri, 27 Feb 2026 09:00:00 +0900</pubDate>
      <guid>https://www.ai2core.com/posts/2026-02-27-llm-observability-opentelemetry/</guid>
      <description>OpenTelemetryを使ってLLMアプリのレイテンシ、トークン、品質劣化を追跡する実装手順を具体例付きで解説。</description>
      <content:encoded><![CDATA[<h1 id="llm運用の可観測性を実装するopentelemetryでつくるprompttokenlatency監視の実践">LLM運用の可観測性を実装する：OpenTelemetryでつくるPrompt/Token/Latency監視の実践</h1>
<p>LLMアプリは「動く」だけでは本番品質になりません。運用を始めると、次のような問題が必ず発生します。</p>
<ul>
<li>昨日まで 1.2 秒だった応答が突然 4 秒台になる</li>
<li>コストが月末に急増したが、どの機能が原因かわからない</li>
<li>回答品質が落ちたと言われるが、どのプロンプト変更が影響したか追えない</li>
<li>リトライ回数や外部API待ちの偏りが可視化されていない</li>
</ul>
<p>この課題を解く鍵が「可観測性（Observability）」です。本記事では OpenTelemetry を軸に、LLM アプリの監視をゼロから構築する実装を、実際に運用で使える粒度で説明します。</p>
<h2 id="なぜ-apm-だけでは-llm-を見切れないのか">なぜ APM だけでは LLM を見切れないのか</h2>
<p>従来の Web アプリ監視（CPU、HTTP レイテンシ、エラーレート）だけでは、LLM 特有の故障点が見えません。理由は、LLM の品質とコストが「入力テキスト」と「推論設定」に強く依存するためです。</p>
<p>少なくとも次の軸が必要です。</p>
<ol>
<li><strong>Prompt 可視化</strong>: システム/ユーザー/ツール呼び出しの構成</li>
<li><strong>Token 可視化</strong>: input/output token、モデル別単価、キャッシュヒット率</li>
<li><strong>推論経路可視化</strong>: retrieval → rerank → generation の各ステップ時間</li>
<li><strong>品質シグナル</strong>: hallucination 率、参照文書一致率、ユーザー評価</li>
</ol>
<p>つまり、HTTP 1 本のログでは不十分で、<strong>トレース単位で LLM 実行を分解</strong>する必要があります。</p>
<h2 id="アーキテクチャの全体像">アーキテクチャの全体像</h2>
<p>最初に、実装対象を次の構成とします。</p>
<ul>
<li>API: FastAPI</li>
<li>LLM: OpenAI / Azure OpenAI（抽象化）</li>
<li>RAG: pgvector + reranker</li>
<li>Observability: OpenTelemetry SDK + OTLP Exporter + Grafana Tempo/Loki/Prometheus</li>
</ul>
<p>処理フローは次の通りです。</p>
<ol>
<li>リクエスト受信時に <code>trace_id</code> を生成</li>
<li>Retrieval、Rerank、Generate をそれぞれ span 化</li>
<li>各 span に token、model、temperature、cache_hit を attribute として記録</li>
<li>失敗時は exception をイベントとして保存</li>
<li>レスポンス時にコスト推定を metrics として送信</li>
</ol>
<h2 id="ステップ1opentelemetryの初期設定">ステップ1：OpenTelemetryの初期設定</h2>
<p>まずは Python で最小セットを導入します。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>uv add opentelemetry-api opentelemetry-sdk opentelemetry-exporter-otlp opentelemetry-instrumentation-fastapi
</span></span></code></pre></td></tr></table>
</div>
</div><p>次に初期化コードを用意します。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#75715e"># observability.py</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> opentelemetry <span style="color:#f92672">import</span> trace, metrics
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> opentelemetry.sdk.trace <span style="color:#f92672">import</span> TracerProvider
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> opentelemetry.sdk.trace.export <span style="color:#f92672">import</span> BatchSpanProcessor
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> opentelemetry.exporter.otlp.proto.grpc.trace_exporter <span style="color:#f92672">import</span> OTLPSpanExporter
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> opentelemetry.sdk.resources <span style="color:#f92672">import</span> Resource
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>resource <span style="color:#f92672">=</span> Resource<span style="color:#f92672">.</span>create({
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;service.name&#34;</span>: <span style="color:#e6db74">&#34;tech-blog-autopilot-api&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;service.version&#34;</span>: <span style="color:#e6db74">&#34;1.3.0&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;deployment.environment&#34;</span>: <span style="color:#e6db74">&#34;production&#34;</span>,
</span></span><span style="display:flex;"><span>})
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>provider <span style="color:#f92672">=</span> TracerProvider(resource<span style="color:#f92672">=</span>resource)
</span></span><span style="display:flex;"><span>provider<span style="color:#f92672">.</span>add_span_processor(
</span></span><span style="display:flex;"><span>    BatchSpanProcessor(OTLPSpanExporter(endpoint<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;http://otel-collector:4317&#34;</span>, insecure<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>))
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>trace<span style="color:#f92672">.</span>set_tracer_provider(provider)
</span></span><span style="display:flex;"><span>tracer <span style="color:#f92672">=</span> trace<span style="color:#f92672">.</span>get_tracer(<span style="color:#e6db74">&#34;llm-pipeline&#34;</span>)
</span></span></code></pre></td></tr></table>
</div>
</div><p>ここで重要なのは、<strong>service.name を固定すること</strong>です。デプロイごとに揺れるとダッシュボードが分断され、比較分析ができません。</p>
<h2 id="ステップ2llm処理を-span-で分割する">ステップ2：LLM処理を span で分割する</h2>
<p>実運用では「遅い」の原因が retrieval なのか generation なのかで対応が変わります。そこで、処理を細かく span 化します。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">from</span> opentelemetry <span style="color:#f92672">import</span> trace
</span></span><span style="display:flex;"><span>tracer <span style="color:#f92672">=</span> trace<span style="color:#f92672">.</span>get_tracer(<span style="color:#e6db74">&#34;llm-pipeline&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">generate_answer</span>(query: str, user_id: str):
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">with</span> tracer<span style="color:#f92672">.</span>start_as_current_span(<span style="color:#e6db74">&#34;rag.pipeline&#34;</span>) <span style="color:#66d9ef">as</span> root:
</span></span><span style="display:flex;"><span>        root<span style="color:#f92672">.</span>set_attribute(<span style="color:#e6db74">&#34;user.id&#34;</span>, user_id)
</span></span><span style="display:flex;"><span>        root<span style="color:#f92672">.</span>set_attribute(<span style="color:#e6db74">&#34;feature&#34;</span>, <span style="color:#e6db74">&#34;support-chat&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">with</span> tracer<span style="color:#f92672">.</span>start_as_current_span(<span style="color:#e6db74">&#34;rag.retrieve&#34;</span>) <span style="color:#66d9ef">as</span> span_retrieve:
</span></span><span style="display:flex;"><span>            docs <span style="color:#f92672">=</span> retrieve_docs(query)
</span></span><span style="display:flex;"><span>            span_retrieve<span style="color:#f92672">.</span>set_attribute(<span style="color:#e6db74">&#34;retrieved.count&#34;</span>, len(docs))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">with</span> tracer<span style="color:#f92672">.</span>start_as_current_span(<span style="color:#e6db74">&#34;rag.rerank&#34;</span>) <span style="color:#66d9ef">as</span> span_rerank:
</span></span><span style="display:flex;"><span>            ranked <span style="color:#f92672">=</span> rerank_docs(query, docs)
</span></span><span style="display:flex;"><span>            span_rerank<span style="color:#f92672">.</span>set_attribute(<span style="color:#e6db74">&#34;rerank.top_k&#34;</span>, <span style="color:#ae81ff">5</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">with</span> tracer<span style="color:#f92672">.</span>start_as_current_span(<span style="color:#e6db74">&#34;llm.generate&#34;</span>) <span style="color:#66d9ef">as</span> span_gen:
</span></span><span style="display:flex;"><span>            response <span style="color:#f92672">=</span> call_llm(query, ranked)
</span></span><span style="display:flex;"><span>            span_gen<span style="color:#f92672">.</span>set_attribute(<span style="color:#e6db74">&#34;llm.model&#34;</span>, response<span style="color:#f92672">.</span>model)
</span></span><span style="display:flex;"><span>            span_gen<span style="color:#f92672">.</span>set_attribute(<span style="color:#e6db74">&#34;llm.input_tokens&#34;</span>, response<span style="color:#f92672">.</span>usage<span style="color:#f92672">.</span>input_tokens)
</span></span><span style="display:flex;"><span>            span_gen<span style="color:#f92672">.</span>set_attribute(<span style="color:#e6db74">&#34;llm.output_tokens&#34;</span>, response<span style="color:#f92672">.</span>usage<span style="color:#f92672">.</span>output_tokens)
</span></span><span style="display:flex;"><span>            span_gen<span style="color:#f92672">.</span>set_attribute(<span style="color:#e6db74">&#34;llm.temperature&#34;</span>, <span style="color:#ae81ff">0.2</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> response<span style="color:#f92672">.</span>text
</span></span></code></pre></td></tr></table>
</div>
</div><p>この分割で、「retrieval が中央値 70ms → 280ms に悪化」「特定モデルだけ output token が急増」など、運用判断に直結する情報が取得できます。</p>
<h2 id="ステップ3コストをメトリクス化する">ステップ3：コストをメトリクス化する</h2>
<p>運用現場で最も効くのは、<strong>推定コストをリアルタイムに可視化</strong>することです。モデル単価表をコードに持ち、1リクエストごとに計算して metrics に送ります。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">8
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>MODEL_PRICE <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;gpt-4.1-mini&#34;</span>: {<span style="color:#e6db74">&#34;in&#34;</span>: <span style="color:#ae81ff">0.0000003</span>, <span style="color:#e6db74">&#34;out&#34;</span>: <span style="color:#ae81ff">0.0000012</span>},
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;gpt-4.1&#34;</span>: {<span style="color:#e6db74">&#34;in&#34;</span>: <span style="color:#ae81ff">0.000003</span>, <span style="color:#e6db74">&#34;out&#34;</span>: <span style="color:#ae81ff">0.000012</span>},
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">estimate_cost</span>(model: str, in_tokens: int, out_tokens: int) <span style="color:#f92672">-&gt;</span> float:
</span></span><span style="display:flex;"><span>    p <span style="color:#f92672">=</span> MODEL_PRICE[model]
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> in_tokens <span style="color:#f92672">*</span> p[<span style="color:#e6db74">&#34;in&#34;</span>] <span style="color:#f92672">+</span> out_tokens <span style="color:#f92672">*</span> p[<span style="color:#e6db74">&#34;out&#34;</span>]
</span></span></code></pre></td></tr></table>
</div>
</div><p>推奨は次の3指標です。</p>
<ul>
<li><code>llm_cost_usd_total</code>（counter）</li>
<li><code>llm_tokens_input_total</code> / <code>llm_tokens_output_total</code>（counter）</li>
<li><code>llm_latency_ms</code>（histogram）</li>
</ul>
<p>これを feature、tenant、model のラベルで集計すると、予算統制が一気に楽になります。</p>
<h2 id="ステップ4品質低下を検知する仕組みを入れる">ステップ4：品質低下を検知する仕組みを入れる</h2>
<p>レイテンシとコストだけでは不十分です。品質監視を最低限でも導入します。</p>
<h3 id="4-1-自動評価ジョブ">4-1. 自動評価ジョブ</h3>
<p>夜間バッチで固定データセット（100問程度）を流し、次を記録します。</p>
<ul>
<li>正答率（正解文との semantic similarity）</li>
<li>出典一致率（回答が引用した文書IDの妥当性）</li>
<li>禁止事項違反率（PII、コンプラNG）</li>
</ul>
<h3 id="4-2-本番フィードバック">4-2. 本番フィードバック</h3>
<p>UI で 👍 / 👎 を取り、trace_id と紐づけます。こうすると「悪評の大半が temperature=0.9 の実験フラグ経由」など、根因分析が可能です。</p>
<h2 id="ステップ5運用で効くダッシュボードを作る">ステップ5：運用で効くダッシュボードを作る</h2>
<p>実際に使われるダッシュボードは、項目を欲張らない方が強いです。最初は次の 6 つに絞ってください。</p>
<ol>
<li>P50/P95 レイテンシ（全体 + モデル別）</li>
<li>リクエスト数とエラー率（HTTP + LLM例外）</li>
<li>日次コスト（全体 + feature別）</li>
<li>input/output token 推移</li>
<li>retrieval 件数と空振り率</li>
<li>ユーザー評価（👍率）</li>
</ol>
<p>特に P95 とコストは同一画面に置くのがポイントです。高速化で品質が落ちた、または品質改善でコストが跳ねた、というトレードオフが即時に見えます。</p>
<h2 id="よくある失敗と回避策">よくある失敗と回避策</h2>
<h3 id="失敗1prompt全文を生で保存して個人情報を漏らす">失敗1：Prompt全文を生で保存して個人情報を漏らす</h3>
<p>対策は、PII マスキングを export 前に必ず実行することです。メール、電話番号、住所は正規表現だけでなく、NER ベースで二重防御すると安全です。</p>
<h3 id="失敗2span属性の命名がバラバラ">失敗2：span属性の命名がバラバラ</h3>
<p><code>llm.input_tokens</code> と <code>input_token_count</code> が混在すると集計不能になります。命名規約をリポジトリに固定し、CI で lint してください。</p>
<h3 id="失敗3高カーディナリティ地獄">失敗3：高カーディナリティ地獄</h3>
<p><code>user_id</code> をそのままメトリクスラベルに入れると TSDB が破綻します。ユーザー軸は trace/log に置き、metrics は tenant や plan 程度に抑えます。</p>
<h2 id="導入ロードマップ2週間">導入ロードマップ（2週間）</h2>
<ul>
<li><strong>Day 1-2</strong>: FastAPI + LLM呼び出しに trace 埋め込み</li>
<li><strong>Day 3-4</strong>: token/cost メトリクス送信</li>
<li><strong>Day 5-6</strong>: Grafana ダッシュボード構築</li>
<li><strong>Day 7-9</strong>: しきい値アラート設計（P95、error、cost）</li>
<li><strong>Day 10-12</strong>: 品質評価バッチ導入</li>
<li><strong>Day 13-14</strong>: インシデント演習（意図的劣化を検知できるか）</li>
</ul>
<p>2週間で「見える化」は十分達成できます。完璧を目指すより、まず計測可能にすることが重要です。</p>
<h2 id="まとめ">まとめ</h2>
<p>LLM運用で本当に困るのは、失敗そのものではなく「失敗の理由が見えない」状態です。OpenTelemetry を使って retrieval、generation、token、cost、品質を一貫して観測できるようにすると、改善サイクルが回り始めます。</p>
<p>可観測性は守りではなく、開発速度を上げるための攻めの基盤です。まずは span を3つに分けるところから始めてください。それだけで、LLM運用の景色が大きく変わります。</p>
]]></content:encoded>
      <category>Tech</category>
      <category>LLM</category>
      <category>OpenTelemetry</category>
      <category>Observability</category>
      <category>Prompt Engineering</category>
    </item>
  </channel>
</rss>
