Fastify v6と@fastify/otel — GenAIエージェントの可観測性を構築する

Maru

@maru

Fastify v6와 @fastify/otel — GenAI 에이전트 관측 가능성 구축하기

Fastify v6と@fastify/otel — GenAIエージェントの可観測性を構築する

高性能なNode.jsバックエンドを構築する際、Fastifyは確かな選択肢ですが、複雑な生成AIエージェントを導入した瞬間、従来のHTTPリクエスト単位のモニタリングでは限界に達します。エージェントの多段階推論プロセスやツール実行フローを正確に把握するには、アプリケーションの深部まで追跡できる精緻な可観測性が必要です。Fastify v6の分離されたスコープアーキテクチャと公式OpenTelemetryプラグインを通じて、パフォーマンスオーバーヘッドなしで強力なエージェント追跡システムを構築する方法を見ていきましょう。

レガシーの終焉: @opentelemetry/instrumentation-fastifyから@fastify/otelへ

既存のNode.js環境でFastifyのモニタリングを担当していたコミュニティパッケージである@opentelemetry/instrumentation-fastifyが公式にサポートを終了しました。代わりにFastifyエコシステムが第一級市民として直接メンテナンスを行う公式プラグインである@fastify/otelへ完全に移行する必要があります。外部ツールを組み合わせていた一時的な手法から脱却し、フレームワークのコアと有機的に結合された観測標準に基づきアーキテクチャを再編すべき時です。

@fastify/otelはFastifyの内部ライフサイクルフックと緊密に統合して動作し、リクエスト単位のコンテキストを安定して維持します。開発者が非同期コンテキスト伝播メカニズムを複雑に調整する必要はなく、ルーターハンドラー内部で提供されるrequest.openTelemetry()メソッドを呼び出すだけで簡単にフローを制御できます。

このメソッドは、現在リクエストにマッピングされたルートspan、当該リクエストのtracer、そして有効化されたcontextを含むオブジェクトを返します。これにより、HTTPリクエストが入ってくる最初の入り口から、下位の非同期エージェントレイヤーのツール呼び出しおよび推論演算ステップまで、一つの分散トレースフローとして途切れなく精緻に接続可能です。

Fastify v6のスコーププラグインと非侵襲的なTracer伝播

Fastify v6は、モノレポ環境で頻発するグローバルな型汚染問題を解決するため、TypeScriptのグローバルモジュール補強構造を脱却しました。従来はプラグインが登録したデコレーターがグローバル名前空間に強制的に注入され、予期せぬ型競合を引き起こしていました。v6からは、デコレーター型をプラグインが登録されたスコープ内に限定する「登録スコープ方式」を推奨します。

このような分離された型制御はfastify-pluginパッケージのcreatePluginヘルパーを活用して柔軟に実装できます。プラグイン内部でのみ有効なデコレーターと型を定義することで、ルートに登録されたOTel Tracerインスタンスを、下位のLLMエージェントレイヤーまで結合を最小限に抑えた状態で安全に共有できるようになります。

最新のOpenTelemetry GenAIセマンティックコンベンションの核となる標準

OpenTelemetryの生成AI観測仕様は、現在独立したリポジトリであるopen-telemetry/semantic-conventions-genaiに完全に分離され、標準化の段階を踏んでいます。今回の改定の要点は、曖昧だったレガシー仕様を精緻化し、エージェントの動作フロー上で発生しうる機密個人情報(PII)の流出防止基準を一層強化した点です。

最も代表的な変更は、従来のLLMシステムの分類に使用されていたgen_ai.system属性が、プロバイダーを明確に識別できるgen_ai.provider.nameに置き換わったことです。また、元のプロンプトや完成されたテキストが加工なしに保存されてセキュリティ事故を誘発していた生の文字列属性は、廃止またはデフォルトで無効化されました。

その代わりに、対話コンテキストを構造化して安全に追跡できるよう、シリアライズされたJSON文字列ベースの統合属性が新しく導入されました。システムプロンプトはgen_ai.system_instructionsに指定し、ユーザーの質問とモデルの応答メタデータはそれぞれgen_ai.input.messagesgen_ai.output.messagesにJSON構造でシリアライズして注入する方式が推奨されます。

typescript
// OpenTelemetry GenAI 스팬 속성 할당
span.setAttributes({
  'gen_ai.provider.name': 'openai',
  'gen_ai.system_instructions': '안전한 보안 가이드를 준수합니다.',
  'gen_ai.input.messages': JSON.stringify([{ role: 'user', content: 'Fastify v6' }]),
  'gen_ai.output.messages': JSON.stringify([{ role: 'assistant', content: '안전하고 빠릅니다.' }])
});

こうした形式化された標準仕様を遵守することで、マルチエージェント内部での無分別なテキストデータ露出問題を未然に防ぎ、可視化ツールとの互換性を最大化できます。

Fastify v6環境における実践的なデコレーター統合の実装

Fastify v6の独立したスコープ構造と@fastify/otelプラグインを活用すれば、HTTPリクエストの入り口からエージェントの内部作業ステップまでスムーズにつながる追跡チェーンを実装できます。特に@fastify/otelはルーターハンドラー全体をアクティブなスパンコンテキストとしてラップするため、開発者が直接上位のコンテキストを渡さなくても、OpenTelemetry APIが自動的に親子関係を接続してくれます。

次は、Fastify v6と@opentelemetry/apiを組み合わせてエージェントのLLM呼び出しステップを追跡する、非侵襲的な実装例です。

typescript
import { type Span } from '@opentelemetry/api';
import type { FastifyPluginAsync } from 'fastify';

export const agentRoutes: FastifyPluginAsync = async (fastify) => {
  fastify.post('/v1/chat', async (request, reply) => {
    // 1. 요청 스코프의 OTel 컨텍스트 획득
    const otel = request.openTelemetry();
    
    if (!otel.enabled || !otel.instrumented) {
      return { response: '텔레메트리 비활성 상태' };
    }

    const { tracer } = otel;

    // 2. 상위 컨텍스트를 자동 상속받는 하위 활성 스팬 생성
    return tracer.startActiveSpan('llm.generate', async (span: Span) => {
      try {
        const userPrompt = (request.body as { prompt: string }).prompt;

        // 3. 최신 시맨틱 규격을 반영하여 제공자 정보와 메시지 설정 (JSON 직렬화)
        span.setAttributes({
          'gen_ai.provider.name': 'openai',
          'gen_ai.request.model': 'gpt-4o',
          'gen_ai.input.messages': JSON.stringify([
            { role: 'user', content: userPrompt }
          ])
        });

        // 실제 LLM 서비스 호출 과정 (예시)
        const mockResponse = `답변 결과: ${userPrompt}`;

        // 4. 출력 데이터 및 토큰 사용량 속성 기록
        span.setAttributes({
          'gen_ai.output.messages': JSON.stringify([
            { role: 'assistant', content: mockResponse }
          ]),
          'gen_ai.usage.input_tokens': 15,
          'gen_ai.usage.output_tokens': 30
        });

        return { response: mockResponse };
      } catch (error) {
        if (error instanceof Error) {
          span.recordException(error);
        }
        reply.code(500);
        return { error: '내부 에러 발생' };
      } finally {
        // 5. 작업 완료 후 반드시 스팬 명시적 종료
        span.end();
      }
    });
  });
};

この実装の要は、Fastify v6の強みであるスコープ分離をそのまま維持する点です。グローバル名前空間を汚染するデコレーター型マージの代わりに、ルートスコープ単位で型をカプセル化して管理できます。また、機密となりうるプロンプト入出力データを個別のイベントではなくシリアライズされたJSON文字列属性として収集することで、個人情報の外部露出可能性を構造的に最小化します。

パフォーマンス低下のない安全な観測エコシステムの維持

Fastify v6は、従来の複雑なスキーマコンパイル方式の代わりに、V8エンジンのネイティブなシリアライズ最適化を積極的に活用します。このような超高速フレームワークの性能上の利点を十分に享受するには、リアルタイムのトレーシング処理コストがアプリケーションのボトルネックにならないよう配慮する必要があります。本番環境では、トレースのサンプリング比率を合理的に制御し、重い生成AIペイロードがNode.jsイベントループをブロックしないよう、非同期バッチ方式でバックグラウンドから安全に出力するアーキテクチャ設計が不可欠です。


参考リンク