0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

OpenAI Agents SDKのTrace情報をApplication Insightsへ送信・可視化する!(MCP実行例付き)

Last updated at Posted at 2025-04-10

はじめに

OpenAI Agents SDKを利用した際にTrace情報をAzureのApplication Insightsに送信する方法を調査したので共有します。
従来のChatCompletions APIでは、Application Insightsに送信する方法としてAzure Monitor OpenTelemetry Distro + OpenTelemetry OpenAI Instrumentationを組み合わせることで送信できたのですが、2025年4月9日時点ではResponses APIには対応しておらず送信できませんでした。
そこで今回、こちらのAzure AI Service Blogをベースに、Pydantic Logfire SDK + OpenTelemetry Collector経由で実装しました。なお元記事より下記の点を変更しています。

  • span名の修正をソースを変更するのではなく関数をフックする形で実現
  • MCP(Model Context Protocol)サーバーを実行した結果の記録例

OpenTelemetry Collectorの設定と実装例と実行結果

今回検証に利用した各OSSのバージョンは以下の通りです。

  • OpenAI Agents SDK: v0.0.7
  • Logfire: v3.12.0
  • OpenTelemetry Collector: v0.123.1

OpenTelemetry Collector のセットアップ

OpenAI Agents SDKのトレース情報を収集してApplication Insightsへ転送するためにOpenTelemetry Collectorを設定します。
ここでは下記のGitHubのリリースよりOpenTelemetry Collector Contribを実行ファイルをダウンロードしローカルで実行します。
なおContribリリース以外は、Azure Monitor Exporterをサポートしていないのでご注意ください。
元記事では、Dockerコンテナでの実行や、AKS、Azure Container Appsに関しても記載されていますので、各環境に応じて使い分けください。

Releases open-telemetry/opentelemetry-collector-releases

> ./otelcol-contrib --config=otel-collector-config.yaml

上記コマンドで指定した設定ファイルotel-collector-config.yamlは以下の通りで、Application Insightsにデータを転送する基本的な構成になっています。
Azure Monitor Exporterのconnection_stringは各環境に応じて変更してください。

otel-collector-config.yaml
receivers:
  otlp:
    protocols:
      http:
        endpoint: "0.0.0.0:4318"

exporters:
  azuremonitor:
    connection_string: "InstrumentationKey=your-instrumentation-key;IngestionEndpoint=https://your-region.in.applicationinsights.azure.com/"
    maxbatchsize: 100
    maxbatchinterval: 10s
  debug:
    verbosity: basic

service:
  pipelines:
    traces:
      receivers: [otlp]
      exporters: [azuremonitor, debug]

OpenAI Agents SDKとLogfireの実装例

OpenAI Agents SDKとLogfireの実装例は以下の通りで、OpenAI Agents SDKのサンプルと、元記事の公開コードを組み合わせたコードになっています。
また実装時のポイント2点については後述させて頂きます。

import asyncio
import os
import shutil

from openai import AsyncAzureOpenAI
from agents import Agent, Runner, set_default_openai_client, trace
from agents.mcp import MCPServer, MCPServerStdio
from azure.identity import DefaultAzureCredential, get_bearer_token_provider
from dotenv import load_dotenv
import logfire

from wrapt import wrap_function_wrapper
from logfire._internal.formatter import logfire_format
from logfire._internal.scrubbing import NOOP_SCRUBBER
from agents.tracing.processors import default_processor

load_dotenv()

# OpenTelemetry Collector経由でApplication Insightsに送信
os.environ["OTEL_EXPORTER_OTLP_TRACES_ENDPOINT"]= "http://127.0.0.1:4318/v1/traces"

# Spanの呼び出しをフックし、Span名称を書き換え -ポイント1
def span_wrapper(wrapped, instance, args, kwargs):
    kwargs["_span_name"] = logfire_format(args[0], kwargs, NOOP_SCRUBBER)
    return wrapped(*args, **kwargs)

wrap_function_wrapper(
    module="logfire._internal.main",
    name="Logfire.span",
    wrapper=span_wrapper,
)

# Logfireを初期化
logfire.configure(
    service_name='my-agent-service',
    send_to_logfire=False,
    distributed_tracing=True)
logfire.instrument_openai_agents()

# 背後でOpenAIにTrace情報を送信するProcessorが動作しているので停止する -ポイント2
default_processor().shutdown()

# Azure OpenAIクライアントを初期化
credential = DefaultAzureCredential()
deployment_name = os.getenv("AZURE_OPENAI_DEPLOYMENT")
openai_client = AsyncAzureOpenAI(
    api_version=os.getenv("AZURE_OPENAI_API_VERSION"),      # 2025-03-01-preview or later
    azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"),
    azure_deployment=os.getenv("AZURE_OPENAI_DEPLOYMENT"),
    azure_ad_token_provider=get_bearer_token_provider(credential, "https://cognitiveservices.azure.com/.default"),
)

# OpenAI Agents SDKにクライアントをセット
set_default_openai_client(openai_client)

async def run(mcp_server: MCPServer):
    agent = Agent(
        name="Assistant",
        instructions="Use the tools to read the filesystem and answer questions based on those files.",
        mcp_servers=[mcp_server],
        model=deployment_name
    )

    # List the files it can read
    message = "Read the files and list them."
    print(f"Running: {message}")
    result = await Runner.run(starting_agent=agent, input=message)
    print(result.final_output)

async def main():
    # トレースを開始
    with trace(workflow_name="OpenAI Agents Demo"):
        current_dir = os.path.dirname(os.path.abspath(__file__))
        samples_dir = os.path.join(current_dir, "sample_files")

        async with MCPServerStdio(
            name="Filesystem Server, via npx",
            params={
                "command": "npx",
                "args": ["-y", "@modelcontextprotocol/server-filesystem", samples_dir],
            },
        ) as server:
            await run(server)


if __name__ == "__main__":
    # npxがインストールされているか確認
    if not shutil.which("npx"):
        raise RuntimeError("npx is not installed. Please install it with `npm install -g npx`.")

    asyncio.run(main())

実行結果

Azure Application Insightsのトランザクションの検索からトレース情報を可視化した結果は以下の通りです。
エージェントの名称やMCPサーバー名、モデル名等がいつ、どのように呼び出されたか時系列で確認できます。
またMCPサーバーのどのツールを実行したかについては「Function: (ツール名)」として出力され、入出力結果についても記録されているのが確認できました。

Application Insightsでの記録結果

コードの解説

ポイント1:Logfire.spanの呼び出しをフックし名称を書き換え

ここではOpenTelemetryが内部で使用しているwraptを利用しLogfire.span関数の呼び出しをフックし、Logfireのテンプレート文字列の書き換え処理を行っています。
これはLogfireではメッセージとしてテンプレート形式をサポートしていますが、Application Insightsではサポートされていないため発生します。関数をラップしなかった場合のApplication Insightsでの表示は以下のキャプチャの通りで、ラップしない状態ではテンプレート文字列がそのまま表示され、エージェント名やツール名、モデル名が表示されず確認し辛くなっています。

関数をラップしなかった場合のApplication Insights

(参考)ラップした場合のデメリットとその対策

Logfire.span関数をラップした結果、以下のキャプチャの通り、全てユーザコードとして判定され、呼び出し元がspan_wrapperとして記録されます。

Span関数をラップした際の記録例

この対策として、①ユーザコードの判定ロジックをラップして対処する方法と、②Logfire.span関数をラップするロジックを別ファイルとして作成しLogfireのadd_non_user_code_prefixを用いてユーザコードの対象外とする方法の2つがあります。
ここでは前者のユーザコードの判定ロジックをラップして対処する方法を以下に示します。
こちらのコードをサンプルコードのwrap_function_wrapperの後に記載ください。
内容はシンプルで、呼び出し元がspan_wrapperの場合はユーザコードとしないという内容になっています。

def is_user_code_wrapper(wrapped, instance, args, kwargs):
    return not (wrapped(*args, **kwargs), args[0].co_name == "span_wrapper")

wrap_function_wrapper(
    module="logfire._internal.stack_info",
    name="is_user_code",
    wrapper=is_user_code_wrapper,
)

ポイント2:OpenAI Agents SDKのTrace情報送信スレッドの停止

OpenAI Agents SDKのv0.0.7では、Pythonのモジュールをインポートした時点で強制的にOpenAIへのTrace情報の送信スレッドが開始されてしまいます。
このためlogfire.instrument_openai_agents()にて、ログ記録モジュールをOpenAI Agents SDKからLogfireに切り替えたとしても、Trace情報送信処理が実行されてしまいエラーが発生します。
これを抑止するため、OpenAI Agents SDKのdefault_processor()よりBatchTraceProcessorインスタンスを取得、shutdownメソッドにより送信スレッドを停止し、エラーの発生を抑止しています。

※参考:Processors - OpenAI Agents SDK

[参考] Azure AI Foundry(旧Azure AI Studio)のトレース機能による可視化について

当初はAzure AI Foundry、またはAzure ML Studio上でTrace情報を収集・可視化できないか調査を開始しましたが、Azure ML Studio(Azure AI Foundryプロジェクト含)では、残念ながらMLflowのTrace APIをサポートしておらず、またAzure AI Foundry SDK (Azure AI Inference client library)は独自クライアントとなっておりOpenAI Agents SDKとの互換性がないという課題があり利用については断念しました。
またAzure AI Foundry上ではTraceについては一覧表示されるものの、互換性がないためか具体的な内容については以下のキャプチャの通り表示されませんでした。

Azure AI Foundry上の表示

終わりに

LLM ObservabilityについてOpenTelemetryやMLflowが取り組んでいるものの中々スタンダードと呼べるものが出てこず、OpenAI Agents SDKのTracingに記載されているTracing Processorを見ても独自に実装しているのが実情のようです。
今回AzureネイティブなサービスでOpenAI Agents SDKの可視化が可能か調査しましたが、Application Insightsを利用して何とか可視化出来るのが限界でした。
今後生成AIのエージェントやMCPが普及するにつれて、LLM Observabilityだけでなく分散Tracingの重要性も高まっていくと思いますので、引き続きウォッチしていきたいと思います。

参考記事

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?