Azure AI FoundryのLLMトレース機能(プレビュー)を試す
はじめに
Azure AI FoundryのLLMのトレース機能(プレビュー)を試します。
LLMのトレース方法
LLMトレースというとLangsmithは有名でOSSだどLangfuseもあります。
ただ、LangsmithはクラウドServiceなのでエンプラだと使いにくく、Langfuseもセルフホスティングするとk8sが必要などそれなりの苦労があります。トレースまでAzureで完結すると楽だな、と思うので試します。
今回試すこと
Azure AI FoundryではAzure Monitor(Application Insights)と連携してLLMのトレースができるのでそれを試します。ローカルからAI Foundryのモデルカタログからデプロイしたモデルを呼び出し、そのトレースをAI Foundry上で可視化します。
また、このトレース機能はOpenTelemetryを使っているので、多少OpenTelemetryの単語なども理解しつつコードを書いていきます。
事前準備
最初にAzure AI Foundryで新規プロジェクトを作ります。
Azure AI Foundryのプロジェクト作成
AzureポータルでAzure AI Foundryの画面を開いてCreate
すべてサービス>Azure AI Foundry
Project or Hub
ProjectとHubの2択が出るのでHubを選びます。HubはProjectプロジェクトで利用する計算リソース、ストレージ、ネットワーク設定、セキュリティポリシーなどを一元的に管理するためのものなので、先に作成が必要です。(作成していないとProject作成の中で作成が必要になります。)
Azure AIハブ作成(基本)
リージョンはEast US2を選択します。AI系のサービスは最新の機能だと一部のリージョンでしか提供されていないことがよくあります。East US2は比較的最新の機能が提供されていることが多いので、East US2を選択します。
「ストレージ」など他のタブで求められる設定はすべてデフォルトのままでOKです。
デプロイが終わると以下のように関連リソースが作成されます。
Azure AI Hubのリソースグループ
いろいろリソースができているので確認します。
- Azure AI ServicesはAzure OpenAIや音声認識などのAIモデルやサービスを提供します。
- ストレージアカウントはプロジェクトで使用するデータや成果物(フロー、評価結果など)の保存
- キーコンテナー(Azure Key Vault)接続文字列やAPIキーなどの保存用途です
次に作成したHubからProjectを作成します。
AI HUb Top(Azure)
Hubは先ほど作成したものを選択します。他はデフォルトでOKです。
Azure AI Project作成(基本)
デプロイできたら作成したProjectの画面からLaunch Studio
をクリックしてAI Foundryを開きます。
AI Project TOP(Azure)
Azure AI Foundryの画面が開きます。
Azure AI Foundry(Top)
ここで表示されるAPIキーは後で利用するのでメモしておきます。(AZURE_INFERENCE_CREDENTIAL
)
また、プロジェクト接続文字列もメモしておきます。(PROJECT_CONNECTION_STRING
)
Azure AI推論エンドポイント
また、Azure AI推論モデル推論エンドポイントの値もメモしておきます。(AZURE_INFERENCE_ENDPOINT
)
※最初に開いているAzure OpenAI Service エンドポイント
とは違うので注意してください。
AIモデルのデプロイ
次に呼び出すAIモデルをデプロイします。モデルカタログ
からデプロイしたいモデルを選択します。
モデルのデプロイにはマネージドコンピューティング
とサーバレスAPI
の2つのオプションがあります。
- マネージドコンピューティング
- 自分でモデルをデプロイするVM(インスタンス)を用意する方法です。インスタンスで課金されます。
- サーバレスAPI
- サーバレスでモデルの呼び出しで従量課金されます。
少し試すだけなのでデプロイモデルでサーバレスAPI
のフィルタをかけます。
モデルカタログ
OpenAI系のモデルやAzureOpenAIでも使えますし、せっかくなので今回はDeeepSeek-V3
を選択します。もちろんgpt-4oなどOpenAI系のモデルでも構いません。
DeepSeekのデプロイ
デプロイ
をクリックして、デプロイが完了するまで待ちます。デプロイが終わると以下のようにAPIエンドポイント
が表示されます。
DeepSeekデプロイ後画面
マイアセットからモデル+エンドポイントを見ると以下のようにサーバレスでデプロイされていることがわかります。
モデル+エンドポイント
Application Insightsの設定
Azure AI FoundryではApplication Insightsと連携してトレースができるので、Application Insightsの設定をします。
トレース
の画面を開き新規作成
をクリックします。
トレース設定
適当な名前を設定して作成します。
Application Insights作成
設定が完了すると以下のようにトレース画面が開きます。これで準備完了です。
トレース画面(初期状態)
コードの準備
環境は以下で試しています。
- windows11 wsl (ubuntu 22.04.5 LTS)
- Python 3.11.10
コード内ではDefaultAzureCredentialを使って認証しています。事前にAzure CLIをインストールしてログインしておいてください。
- リポジトリのクローン:
git clone https://github.com/Tomodo1773/azure-aifoundry-trace-sample
cd azure-aifoundry-trace-sample
- 依存関係のインストール:
uv` を利用して仮想環境を作成し、依存関係のインストールを行います。
uv sync
- 環境変数の設定:
.env.sample
ファイルをコピーして .env
ファイルを作成します。
cp .env.sample .env
.env
ファイルを開き、以下の環境変数を設定します。
-
PROJECT_CONNECTION_STRING
: Azure AI Studioプロジェクトの接続文字列。 -
AZURE_INFERENCE_ENDPOINT
: Azure AI InferenceエンドポイントのURL。 -
AZURE_INFERENCE_CREDENTIAL
: Azure AI InferenceエンドポイントのAPIキー。
.env.sampleにあるAZURE_TRACING_GEN_AI_CONTENT_RECORDING_ENABLED=true
はそのまま必要です。
Inference SDKの呼び出しをトレース
Azure Monitor OpenTelemetryディストリビューションでトレース
いよいよトレースを試します。
最初はInference SDKを使ってLLMを呼び出し、そのトレースをAI Foundry上で可視化します。
Inference SDKはAzure AI Foundryでデプロイしたモデルを呼び出すためのMS謹製のSDKです。Pythonで書かれたSDKで、Azure OpenAIやDeepSeekなどのモデルを呼び出すことができます。ちなみにInferenceは推論
の意味です。(インターフェイスではありません。私は最初読み間違えました。)
以下にLLMのトレースをAI Foundryで可視化するための方法が書いてあります。
トレースの実装としてはここに書いてあるconfigure_azure_monitorを使うのが一番簡単だと思いますのでまずはこれで実施します。
コードは以下のようになります。
import os
from azure.ai.inference import ChatCompletionsClient
from azure.ai.inference.models import SystemMessage, UserMessage
from azure.ai.projects import AIProjectClient
from azure.core.credentials import AzureKeyCredential
from azure.identity import DefaultAzureCredential
from azure.monitor.opentelemetry import configure_azure_monitor
from dotenv import load_dotenv
from opentelemetry.trace import get_tracer
load_dotenv()
tracer = get_tracer(__name__)
project_client = AIProjectClient.from_connection_string(
credential=DefaultAzureCredential(), conn_str=os.environ["PROJECT_CONNECTION_STRING"]
)
application_insights_connection_string = project_client.telemetry.get_connection_string()
if application_insights_connection_string:
configure_azure_monitor(connection_string=application_insights_connection_string)
client = ChatCompletionsClient(
endpoint=os.environ["AZURE_INFERENCE_ENDPOINT"],
credential=AzureKeyCredential(
os.environ["AZURE_INFERENCE_CREDENTIAL"],
),
model="DeepSeek-V3",
)
message = [
SystemMessage("You are a helpful assistant."),
UserMessage("Hello"),
]
with tracer.start_as_current_span("AIInference_AzureMonitor_Dist"):
response = client.complete(messages=message)
print("***Print Raw response***")
print(response)
print("***End of Raw response***")
トレース設定部分のコードの解説をします。
tracer
の設定をしたあと、PROJECT_CONNECTION_STRING
からapplication insightsの接続文字列を取得します。次にconfigure_azure_monitor
を呼び出して、接続文字列を渡します。これだけです。
tracer = get_tracer(__name__)
project_client = AIProjectClient.from_connection_string(
credential=DefaultAzureCredential(), conn_str=os.environ["PROJECT_CONNECTION_STRING"]
)
application_insights_connection_string = project_client.telemetry.get_connection_string()
if application_insights_connection_string:
configure_azure_monitor(connection_string=application_insights_connection_string)
ここではAzure Monitor OpenTelemetryディストリビューションのconfigure_azure_monitor
を使ってトレースの設定をしています。
Azure Monitor OpenTelemetryディストリビューションというのは素のOpenTelemetryにAzure Monitorにトレースを送るようなもろもろの実装をラップしたもので、これを使うと簡単にAzure Monitorにトレースを送ることができます。これによって今回のコードもトレースを簡単に実装できています。
Azure Monitor OpenTelemetryディストリビューションについては以下です。
OpenTelemetryのディストリビューション自体が気になる方は以下です。
このあとはLLM呼び出しの部分のコードですが、こちらはInference SDKをそのまま使っているだけなので割愛します。
client = ChatCompletionsClient(
endpoint=os.environ["AZURE_INFERENCE_ENDPOINT"],
credential=AzureKeyCredential(
os.environ["AZURE_INFERENCE_CREDENTIAL"],
),
model="DeepSeek-V3",
)
message = [
SystemMessage("You are a helpful assistant."),
UserMessage("Hello"),
]
コードを実行した後、Azure AI Foundryを確認すると、トレースが以下のように表示されます。
トレース結果(タイムライン)
トレース結果(詳細)
System,User,AssistantといったLLM特有の内容もきちんと分割されて見やすく表示されていますね。
トレース結果(システム)
ちなみに、このSystem, User, Assistantという表示は先ほど.env
でAZURE_TRACING_GEN_AI_CONTENT_RECORDING_ENABLED
をtrue
にしたことで表示されています。
ここをfalse
にすると以下のようにNo content available
と表示されLLMのコンテンツの記録が行われていないことがわかります。
トレース結果(No content available)
ちなみにタイトルに表示されているAIInference_AzureMonitor_Dist
は見やすくするために自分で設定しているものです。以下の部分で設定しています。
with tracer.start_as_current_span("AIInference_AzureMonitor_Dist"):
response = client.complete(messages=message)
ある程度OpenTelemetryの設定も自分でしてトレース
さて、今はAzure Monitor OpenTelemetryディストリビューションを用いてトレース実装しましたが、ラップされすぎていて何を設定しているかわからなかったので、次はOpenTelemetry的にどのようなことをやっているか理解するための実装をしてみます。
ここではAzure MonitorのMicrosoft OpenTelemetryエクスポーターを使います。これは先ほどのAzure Monitor OpenTelemetryディストリビューションの中で自動設定されていたものの1つです。
公式でも構成要件が簡単な場合はAzure Monitor OpenTelemetryディストリビューションを使うことが推奨されているというものです。今回は理解のためにこちらを使います。
コードは以下のようになります。
import os
from azure.ai.inference import ChatCompletionsClient
from azure.ai.inference.models import SystemMessage, UserMessage
from azure.ai.inference.tracing import AIInferenceInstrumentor
from azure.ai.projects import AIProjectClient
from azure.core.credentials import AzureKeyCredential
from azure.core.settings import settings
from azure.identity import DefaultAzureCredential
from azure.monitor.opentelemetry.exporter import AzureMonitorTraceExporter
from dotenv import load_dotenv
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter, SimpleSpanProcessor
load_dotenv()
project_client = AIProjectClient.from_connection_string(
credential=DefaultAzureCredential(), conn_str=os.environ["PROJECT_CONNECTION_STRING"]
)
application_insights_connection_string = project_client.telemetry.get_connection_string()
settings.tracing_implementation = "opentelemetry"
exporter = AzureMonitorTraceExporter.from_connection_string(application_insights_connection_string)
tracer_provider = TracerProvider()
trace.set_tracer_provider(tracer_provider)
span_processor = BatchSpanProcessor(exporter, schedule_delay_millis=60000)
tracer_provider.add_span_processor(span_processor)
tracer_provider.add_span_processor(SimpleSpanProcessor(ConsoleSpanExporter()))
tracer = trace.get_tracer(__name__)
AIInferenceInstrumentor().instrument()
client = ChatCompletionsClient(
endpoint=os.environ["AZURE_INFERENCE_ENDPOINT"],
credential=AzureKeyCredential(os.environ["AZURE_INFERENCE_CREDENTIAL"]),
model="DeepSeek-V3",
)
message = [
SystemMessage("You are a helpful assistant."),
UserMessage("Hello"),
]
with tracer.start_as_current_span("AIInference_AzureMonitor_Otel"):
response = client.complete(messages=message)
print("***Print Raw response***")
print(response)
print("***End of Raw response***")
Application Insightsの接続文字列を取得するまでは同じですが、そのあとのトレースの設定の箇所のコードが少し長くなりました。
settings.tracing_implementation = "opentelemetry"
exporter = AzureMonitorTraceExporter.from_connection_string(application_insights_connection_string)
tracer_provider = TracerProvider()
trace.set_tracer_provider(tracer_provider)
span_processor = BatchSpanProcessor(exporter, schedule_delay_millis=60000)
tracer_provider.add_span_processor(span_processor)
tracer_provider.add_span_processor(SimpleSpanProcessor(ConsoleSpanExporter()))
tracer = trace.get_tracer(__name__)
AIInferenceInstrumentor().instrument()
ここからはOpenTelemetryの各用語にも触れながら説明します。
まず、exporterの設定をしています。exporterはトレースを送信する先のことで、今回はApplication Insightsに送信するのでAzureMonitorTraceExporter
を使ってそこにApplication Insightsの接続文字列を渡しています。
次にトレースを管理するためのクラスとしてTraceProviderを初期化し、そこから今回のトレースを取得するためのクラスを取得しています。
spanは処理の1単位です。この処理に対してトレースとして開始・終了、名前、属性などいろいろな値が記録されます。そして、span_processorはこれをどう処理するかを決めています。今回はBatchSpanProcessorを使って、トレースをある程度まとめて送るという設定です。
また、併せてConsoleSpanExporterも使ってコンソールにもトレースが出力されるようにしています。これはデバッグ用ですね。
最後に、Instrumentorを設定しています。これは特定のライブラリやフレームワークを自動的にトレースするためのもので、今回はInference SDKをトレースするためにAIInferenceInstrumentor
を使っています。
先ほどはコンソール出力は行っていませんでしたが、それでもAzure Monitor OpenTelemetryディストリビューションでかなり簡略化されていたことがわかると思います。
このあたりの用語の解説については以下の記事でも詳しく書かれています。
このコードを実行すると以下のように今ソース出力されます。(長いので折りたたみました。また、print部分は除いています。)
コンソール上のトレース結果
{
"name": "POST",
"context": {
"trace_id": "0x8ff38cab2c2b941b9914bf1c4e2666d2",
"span_id": "0x73e7776551a38ccb",
"trace_state": "[]"
},
"kind": "SpanKind.CLIENT",
"parent_id": "0x01a27b829f0d1eba",
"start_time": "2025-04-14T07:10:45.381360Z",
"end_time": "2025-04-14T07:10:46.610225Z",
"status": {
"status_code": "UNSET"
},
"attributes": {
"component": "http",
"http.method": "POST",
"http.url": "https://aihubdemo7428995252.services.ai.azure.com/models/chat/completions?api-version=2024-05-01-preview",
"net.peer.name": "aihubdemo7428995252.services.ai.azure.com",
"user_agent.original": "azsdk-python-ai-inference/1.0.0b9 Python/3.11.10 (Linux-5.15.167.4-microsoft-standard-WSL2-x86_64-with-glibc2.35)",
"http.status_code": 200,
"az.client_request_id": "95956ede-18ff-11f0-8f15-2fb2fcb6a9e6"
},
"events": [],
"links": [],
"resource": {
"attributes": {
"telemetry.sdk.language": "python",
"telemetry.sdk.name": "opentelemetry",
"telemetry.sdk.version": "1.28.2",
"service.name": "unknown_service"
},
"schema_url": ""
}
}
{
"name": "chat",
"context": {
"trace_id": "0x8ff38cab2c2b941b9914bf1c4e2666d2",
"span_id": "0x01a27b829f0d1eba",
"trace_state": "[]"
},
"kind": "SpanKind.CLIENT",
"parent_id": "0x051adf4bdbb074ad",
"start_time": "2025-04-14T07:10:45.380251Z",
"end_time": "2025-04-14T07:10:46.613027Z",
"status": {
"status_code": "UNSET"
},
"attributes": {
"gen_ai.operation.name": "chat",
"gen_ai.system": "az.ai.inference",
"gen_ai.request.model": "chat",
"server.address": "aihubdemo7428995252.services.ai.azure.com",
"gen_ai.response.id": "55225e4a77274197be38e8fa122d2a0a",
"gen_ai.response.model": "DeepSeek-V3",
"gen_ai.usage.input_tokens": 10,
"gen_ai.usage.output_tokens": 12,
"gen_ai.response.finish_reasons": [
"stop"
]
},
"events": [
{
"name": "gen_ai.system.message",
"timestamp": "2025-04-14T07:10:45.380474Z",
"attributes": {
"gen_ai.system": "az.ai.inference",
"gen_ai.event.content": "{\"role\": \"system\", \"content\": \"You are a helpful assistant.\"}"
}
},
{
"name": "gen_ai.user.message",
"timestamp": "2025-04-14T07:10:45.380560Z",
"attributes": {
"gen_ai.system": "az.ai.inference",
"gen_ai.event.content": "{\"role\": \"user\", \"content\": \"Hello\"}"
}
},
{
"name": "gen_ai.choice",
"timestamp": "2025-04-14T07:10:46.612991Z",
"attributes": {
"gen_ai.system": "az.ai.inference",
"gen_ai.event.content": "{\"message\": {\"content\": \"Hello! How can I assist you today? \\ud83d\\ude0a\"}, \"finish_reason\": \"stop\", \"index\": 0}"
}
}
],
"links": [],
"resource": {
"attributes": {
"telemetry.sdk.language": "python",
"telemetry.sdk.name": "opentelemetry",
"telemetry.sdk.version": "1.28.2",
"service.name": "unknown_service"
},
"schema_url": ""
}
}
{
"name": "AIInference_AzureMonitor_Otel",
"context": {
"trace_id": "0x8ff38cab2c2b941b9914bf1c4e2666d2",
"span_id": "0x051adf4bdbb074ad",
"trace_state": "[]"
},
"kind": "SpanKind.INTERNAL",
"parent_id": null,
"start_time": "2025-04-14T07:10:45.380030Z",
"end_time": "2025-04-14T07:10:46.614012Z",
"status": {
"status_code": "UNSET"
},
"attributes": {},
"events": [],
"links": [],
"resource": {
"attributes": {
"telemetry.sdk.language": "python",
"telemetry.sdk.name": "opentelemetry",
"telemetry.sdk.version": "1.28.2",
"service.name": "unknown_service"
},
"schema_url": ""
}
}
AI foundry上だと以下のようになります。
トレース結果2 (タイムライン)
トレース結果2 (詳細)
Langchainの呼び出しをトレース
さて、最初はMS謹製のInference SDKを使ってトレースを実装しましたが、それでできるのは当たり前、ということで次はLangchainを使ってトレースを実装してみます。
実はLangchainを使ってAI Foundryにトレースする方法も公式が出してくれています。
これを参考にコードを書くと以下のようになります。
せっかくlangchainのトレースをするということで処理をchainにしたかったのでprompt templateを使ってchainを作成しています。
import os
from azure.ai.projects import AIProjectClient
from azure.identity import DefaultAzureCredential
from dotenv import load_dotenv
from langchain_azure_ai.callbacks.tracers import AzureAIInferenceTracer
from langchain_azure_ai.chat_models import AzureAIChatCompletionsModel
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
load_dotenv()
project_client = AIProjectClient.from_connection_string(
credential=DefaultAzureCredential(),
conn_str=os.environ["PROJECT_CONNECTION_STRING"],
)
application_insights_connection_string = project_client.telemetry.get_connection_string()
tracer = AzureAIInferenceTracer(
connection_string=application_insights_connection_string,
enable_content_recording=True,
)
model = AzureAIChatCompletionsModel(
endpoint=os.environ["AZURE_INFERENCE_ENDPOINT"],
credential=os.environ["AZURE_INFERENCE_CREDENTIAL"],
model_name="DeepSeek-V3",
client_kwargs={"logging_enable": True},
)
system_template = "Translate the following into {language}:"
prompt_template = ChatPromptTemplate.from_messages([("system", system_template), ("user", "{text}")])
parser = StrOutputParser()
chain = prompt_template | model | parser
chain_config = chain.with_config(callbacks=[tracer], run_name="Langchain_AzureAIInferenceTracer")
response = chain_config.invoke({"language": "italian", "text": "hi"})
print("***Print Raw response***")
print(response)
print("***End of Raw response***")
トレースの実装は意外とシンプルです。Application Insightsの接続文字列取得までは同じでそのあとはLangchainのAzureAI用のライブラリ(langchain_azure_ai
)のAzureAIInferenceTracer
を使ってトレースを実装しています。
tracer = AzureAIInferenceTracer(
connection_string=application_insights_connection_string,
enable_content_recording=True,
)
そして、最後にchainを定義する箇所でcallbacks
にトレースを渡しています。Langchain_AzureAIInferenceTracer
の部分はトレースを見やするくするために自分で設定しているものです。
chain_config = chain.with_config(callbacks=[tracer], run_name="Langchain_AzureAIInferenceTracer")
これを実行するとAI Foundry上では以下のように表示されます。
トレース結果3(タイムライン)
今回は
- prompt templateの変数部分を埋める(
ChatPromptTemplate
) - LLMを呼び出す(
AzureAIChatCompletionsModel
) - LLMの返答のみをテキストとして抽出する(
StrOutputParser
)
の処理をしているのでそれがそれぞれ表示されています。
トレース結果3(詳細)
ちなみに、langchain-azure-aiも内部では最初に使ったazure-monitor-opentelemetry
を使ってトレースを実装しています。
そのため、AzureAIChatCompletionsModel
の部分は最初に試したもので表示されていたような内容がまるっとそこに入っているような形になっています。
課題
うすうす気が付いている方もいると思いますが、azure-monitor-opentelemetry
を使ったトレースをしているときにPOST /models/chat/completions
が2重で取得されています。
POSTが2重で取得される
原因はよくわかりません。おそらくですが、azure-monitor-opentelemetry
はInference SDKだけでなくAzure関連でよく使われるSDKの処理全般をOpenteremetryでトレースするような実装になっています。その中でHTTPリクエストはデフォルトでトレースされるようになっており、InferenceSDKで取得される分とダブっている?のではないかと思います。(完全に予想なので間違っていたらすみません。)
このあたりも調査、改善できるといいなと思います。
さいごに
AI FoundryでLLMトレースを可視化する方法を試しました。軽く試した感じでは実装も容易で、トレース自体も割と見やすく表示されるので使いやすそうです。
今回はモデルカタログからデプロイしたモデルを使いましたが、以下のようにAzure関係ないモデルでもトレースはできるようです。次はこのあたりも試したいと思います。
References
- azure-sdk-for-python/sdk/ai/azure-ai-inference/samples/sample_chat_completions_with_azure_monitor_tracing.py at main · Azure/azure-sdk-for-python
- Azure AI Inference SDK でのトレース - Azure AI Foundry |マイクロソフト ラーン
- azure-sdk-for-python/sdk/ai/azure-ai-inference/README.md at main · Azure/azure-sdk-for-python
- langchain tracingのブログ
- Microsoft OpenTelemetry exporter for Azure Monitor | Microsoft Learn
- mindful-time/langchain-azure-ai-foundary-tracing
- Langchain Trace (Inference SDK)
- Develop application with LangChain and Azure AI Foundry - Azure AI Foundry | Microsoft Learn
- Python 用 Azure Core Tracing OpenTelemetry クライアント ライブラリ | Microsoft Learn
- Enable OpenTelemetry in Application Insights - Azure Monitor | Microsoft Learn
Discussion