はじめに
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
は各環境に応じて変更してください。
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: (ツール名)」として出力され、入出力結果についても記録されているのが確認できました。
コードの解説
ポイント1:Logfire.spanの呼び出しをフックし名称を書き換え
ここではOpenTelemetryが内部で使用しているwrapt
を利用しLogfire.span
関数の呼び出しをフックし、Logfireのテンプレート文字列の書き換え処理を行っています。
これはLogfireではメッセージとしてテンプレート形式をサポートしていますが、Application Insightsではサポートされていないため発生します。関数をラップしなかった場合のApplication Insightsでの表示は以下のキャプチャの通りで、ラップしない状態ではテンプレート文字列がそのまま表示され、エージェント名やツール名、モデル名が表示されず確認し辛くなっています。
(参考)ラップした場合のデメリットとその対策
Logfire.span
関数をラップした結果、以下のキャプチャの通り、全てユーザコードとして判定され、呼び出し元がspan_wrapper
として記録されます。
この対策として、①ユーザコードの判定ロジックをラップして対処する方法と、②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については一覧表示されるものの、互換性がないためか具体的な内容については以下のキャプチャの通り表示されませんでした。
終わりに
LLM ObservabilityについてOpenTelemetryやMLflowが取り組んでいるものの中々スタンダードと呼べるものが出てこず、OpenAI Agents SDKのTracingに記載されているTracing Processorを見ても独自に実装しているのが実情のようです。
今回AzureネイティブなサービスでOpenAI Agents SDKの可視化が可能か調査しましたが、Application Insightsを利用して何とか可視化出来るのが限界でした。
今後生成AIのエージェントやMCPが普及するにつれて、LLM Observabilityだけでなく分散Tracingの重要性も高まっていくと思いますので、引き続きウォッチしていきたいと思います。