本記事記述および実装内容は、多くの部分をAIで処理しており、人間チェックは甘いです
Foundryトレースの保存先を4設定で検証してみた
はじめに
Microsoft Foundry Agent Service の Prompt Agent を動かすと、トレース(プロンプト・ツール呼び出し・トークン・レイテンシ)が OpenTelemetry の GenAI セマンティクスで記録されます。ただし「どの設定をしたら、どのデータが、どこ(どのテーブル / どの画面)に入るのか」は意外と分かりにくいです。
そこで本記事では、関数ツールを1つ持つ Prompt Agent を用意し、トレース設定を 4 通りに変えて、保存先テーブルと Portal 画面の見え方の違いを実機で確認しました。
- 想定読者: Foundry Agent / Application Insights でエージェントを監視したい Azure エンジニア
- 読むと分かること: サーバサイド / クライアントサイドのトレースの違い、Application Insights のどのテーブルに入るか、Foundry Portal と Azure Portal「Agents (Preview)」での見え方の差
結論
- ✅ トレースは実質すべて
dependencies(=AppDependencies)テーブルに入る。requests/tracesはほぼ 0 件。サーバ/クライアントはcloud_RoleName(responsesapi/unknown_service)で見分ける。 - ✅ サーバサイド(App Insights を Foundry に接続)はコード変更ゼロで有効になり、プロンプト・応答の本文まで既定でキャプチャされる。
- ✅ クライアントサイド(
AIProjectInstrumentor)は本文を既定で伏せる(redact)。本文を記録するには環境変数の明示 opt-in が必要。 - ⚠️
configure_azure_monitorを呼ぶ「だけ」では GenAI span が出ない。エージェント観測 UI(Foundry Traces / Azure Agents (Preview))は gen_ai span に依存するため、ほぼ何も映らない。 - ⚠️ Foundry Portal に出るかどうかは「App Insights をプロジェクトに接続したか」だけで決まる(クライアント計装とは無関係)。
構成
App Insights を 2 つ用意し、保存先を厳密に切り分けました。
-
appi-server: Foundry プロジェクトに接続(サーバサイドトレース受信) -
appi-client: 未接続。クライアントが接続文字列で export
検証した4パターン
同じ Prompt Agent に「追跡番号の配送状況を教えて」と尋ね、クライアント側のトレース計装だけを変えて 4 通り実行しました。
| パターン | クライアント設定 |
|---|---|
| A: 1だけ | 計装なし(素の Responses 呼び出し) |
| B: 2だけ |
configure_azure_monitor のみ |
| C: 2+3だけ |
configure_azure_monitor + GenAI 環境変数 + AIProjectInstrumentor
|
| D: 全部 | C を接続済み App Insights に export |
設定2・3 の実体はこれだけです。
import os
# 設定3: GenAI トレースを有効化(instrument より前に必須)
os.environ["AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING"] = "true"
# 任意: クライアント側で本文も記録したい場合のみ opt-in
# os.environ["OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT"] = "true"
# 設定2: Azure Monitor へ export
from azure.monitor.opentelemetry import configure_azure_monitor
configure_azure_monitor(connection_string=conn_str)
# 設定3: GenAI 計装を有効化
from azure.ai.projects.telemetry import AIProjectInstrumentor
AIProjectInstrumentor().instrument()
① パターン別「保存先テーブル」の対応
ここが本記事の主役です。どの設定で、どのテーブルに、どんな span が入るかを実測でまとめました(App Insights は workspace-based)。
| パターン | 出力先 | テーブル(cloud_RoleName) |
主な span | 本文 |
|---|---|---|---|---|
| A 1だけ | server(接続) |
dependencies(responsesapi) |
invoke_agent, chat(モデル) |
込み(既定ON) |
| B 2だけ | client(未接続) |
dependencies(unknown_service) |
汎用のみ(get_package_status 等 InProc / HTTP)、gen_ai なし
|
— |
| C 2+3だけ | client(未接続) |
dependencies(unknown_service) |
gen_ai 有(invoke_agent, create_conversation)+ 汎用 |
redact(既定OFF) |
| D 全部 | server(接続) |
dependencies(responsesapi + unknown_service) |
server + client が相関 | 込み |
ポイント:
- どのパターンでも トレースは
dependenciesに集約。requests/tracesは実質 0、exceptionsは IMDS への無害な接続エラーのみ。 -
サーバ(
responsesapi) はchat(モデル呼び出し)span まで出る。クライアント gen_ai はinvoke_agent止まり(モデル内部はサーバ側)。
全部出ているDパターンの場合のログとその由来などです。
| target | type | name | 由来 | 備考 |
|---|---|---|---|---|
| unknown | AI | chat gpt-4o-mini-2024-07-18 | A(サーバトレース設定) | Chat本文テキストあり(ツール結果での最終回答) |
| unknown | AI | invoke_agent ftrace-v2-agent:1 | A(サーバトレース設定) | Chat本文テキストあり(ツール結果での最終回答) |
| invoke_agent ftrace-v2-agent | Microsoft.CognitiveServices | invoke_agent ftrace-v2-agent | C(AIProjectInstrumentor) | ツール結果での最終回答 |
| get_package_status | InProc | <ツール名> | B(configure_azure_monitor) | ツール呼出 |
| unknown | AI | chat gpt-4o-mini-2024-07-18 | A(サーバトレース設定) | Chat本文テキストあり(ツール呼出指示) |
| unknown | AI | invoke_agent ftrace-v2-agent:1 | A(サーバトレース設定) | Chat本文テキストあり(ツール呼出指示) |
| invoke_agent ftrace-v2-agent | Microsoft.CognitiveServices | invoke_agent ftrace-v2-agent | C(AIProjectInstrumentor) | ツール呼出指示 |
| 169.254.169.254:None | HTTP | GET /metadata/identity/oauth2/token | B(configure_azure_monitor) | |
| create_conversation | Microsoft.CognitiveServices | create_conversation | C(AIProjectInstrumentor) | |
| agent-tracing-scenario | InProc | agent-tracing-scenario | C(AIProjectInstrumentor) | |
| AIProjectClient.get_openai_client | InProc | AIProjectClient.get_openai_client | B(configure_azure_monitor) |
Azure Portal の Logs(リソース スコープ)で、サーバ側 App Insights を見ると responsesapi(server)と unknown_service(client)が同じ dependencies に同居しているのが分かります。
▲ サーバ側 App Insights の dependencies:responsesapi(=サーバサイド) と unknown_service(=クライアント) が同居。requests / traces は出ていない
テーブル名はスコープで変わる
Azure Portal の「App Insights リソース」スコープでは classic 名(dependencies / requests / traces / exceptions)。一方、Log Analytics ワークスペース スコープ(LogsQueryClient など)では *AppDependencies / AppRequests / ...** になります。同じデータの別名で、リソース スコープで Appを使うとFailed to resolve table expression` になります。
② UI での「見え方」の違い
同じデータでも、gen_ai span があるかどうかで UI の見え方が大きく変わります。
Foundry Portal: トレース詳細
接続済み(パターン A / D)では、Foundry Portal の Agent → Traces にトレースが並びます。詳細(Trajectories)を開くと、custom span を根に、クライアント invoke_agent → サーバ invoke_agent → chat が入れ子で相関し、関数ツール get_package_status の span も確認できます。
▲ トレース詳細:span が相関表示され、ツール呼び出し get_package_status も見える(10 spans / 2 chat calls)
「User view」に切り替えると、プロンプトとモデル応答の本文がそのまま表示されます。サーバサイドが本文を既定キャプチャしている証拠です(本記事の例は架空のテストデータです)。
▲ User view:プロンプト/応答の本文が表示される(サーバサイドは本文を既定保存)
プライバシー注意:サーバサイドトレース(App Insights 接続)を有効にすると、ユーザーのプロンプトとモデル応答の本文が既定で App Insights に保存されます。Log Analytics 読み取り権限を持つ全員が閲覧可能です。本番では RBAC・リテンション・データ最小化を必ず検討してください。
Foundry Portal に出る/出ないの分かれ目は「接続」だけ
ここは間違えやすいポイントです。Foundry Portal の Traces / Monitor に出るかどうかは、App Insights をプロジェクトに「接続」したか(設定1)だけで決まります。クライアント計装(設定2 / 3)とは無関係です。
- 接続している(A / D) → サーバサイドトレースが出て Foundry Portal に表示。
-
接続していない(B / C) → いくらクライアントで
configure_azure_monitor/AIProjectInstrumentorしても、Foundry Portal は空(クライアントのデータは指定した App Insights 側にだけ入る)。
これは「Foundry+App Insights を4セット用意し、各パターンで接続状態を変えた」独立環境で検証しました。未接続プロジェクトの Traces 画面は次のように「接続を促す案内」と「No runs or traces to display」が出るだけです。
▲ 未接続(パターンB / C)の Traces 画面:「Create or connect an App Insights resource to enable tracing」「No runs or traces to display」で空
よくある誤解:「クライアント側で計装したのに Foundry Portal に出ない」は正常です。Foundry Portal は接続済み App Insights のサーバサイドトレースを見ているため、接続していなければ何も出ません。Foundry Portal で見たいなら、まず App Insights をプロジェクトに接続してください。
Foundry Portal: エージェントの Monitor 画面
Foundry Portal の ビルド → エージェント → (エージェント選択)→ Monitor には、エージェント単位の運用ダッシュボードがあります。Estimated cost と Total token usage、Agent runs / token のチャートが並びます(Open in Azure Monitor で Azure 側へ遷移可)。
▲ Monitor → Overview:Estimated cost <$0.01 / Total token usage 2.3K、下部に Agent runs / token メトリクス
Tools サブタブには、エージェントに紐づくツール(今回の get_package_status)が並びます。
▲ Monitor → Tools:関数ツール function(=get_package_status)が登録されている
この Monitor も 接続済み(server側)App Insights のサーバサイドトレースを集計しています(Azure Portal「Agents (Preview)」と同じデータソース)。つまり 接続している A / D でのみ数値が出ます。クライアントのみ(B / C)ではここには出ません。
Azure Portal: Agents (Preview)
Azure Portal の App Insights には「調査 → Agents (Preview)」というエージェント運用ダッシュボードがあります。これも gen_ai span を集計します。
接続済み(A / D)のサーバ側では、エージェント実行回数・トークン消費・モデル別メトリクスがしっかり表示されます。
▲ サーバ側 Agents (Preview):エージェント実行 12 回、生成 AI エラー 0、モデル別メトリクスも表示
一方、クライアント側(パターン C)の App Insights を見ると、エージェント実行は出るがモデル/ツールは空になります。クライアント gen_ai span には chat(モデル)span が無いためです。そして gen_ai span を出さないパターン B は、実行回数にすら現れません。
▲ クライアント側 Agents (Preview):実行回数は出るが「モデル:クエリは結果を返しません」。パターン B は出てこない
Agents (Preview) は既定の時間範囲が「過去24時間」です。過去のデータを見るときは範囲を広げてください(本検証は「過去7日」)。
パターンD の注意:クライアント/サーバが入れ子で「重複」する
接続済み App Insights にクライアント export も向けるパターン D では、同じエージェント呼び出しが「サーバ層」と「クライアント層」の2つの span として 1 トレースに入れ子で記録されます(先のトレース詳細 portal-05 と同じ構造)。完全コピーではなく、同一操作を2層から観測したものが traceparent で1トレースに相関しています。
| トレース内の span | 層 | 見分け方 |
|---|---|---|
invoke_agent <agent>(親) |
クライアント | 版数サフィックス無し |
└ invoke_agent <agent>:1(子) |
サーバ | :1(版数)付き |
└ chat <model>
|
サーバ | モデル呼び出し(サーバのみ) |
トークンの二重計上に注意
クライアントの invoke_agent とサーバの chat は同じトークン値を持ちます(実測で完全一致)。Dの構成で全 gen_ai span を素朴に SUM すると約2倍になります。集計は cloud_RoleName で絞ってください(例:トークンはサーバ chat のみ)。
重複を避けたい本番では、クライアント側で SDK の自動計装を止めて、自前 span だけ残すのが有効です。
import os
# サーバ側が記録する invoke_agent / chat の再送を止める(自前 span だけ残る)
os.environ["AZURE_TRACING_GEN_AI_INSTRUMENT_RESPONSES_API"] = "false"
from azure.ai.projects.telemetry import AIProjectInstrumentor
AIProjectInstrumentor().instrument()
(A はサーバのみ、B / C はクライアントが別 App Insights なので、この重複は D だけで起きます。)
おまけ:D の dependencies は A + B + C の全レコードが合流する
D(接続ON + クライアント export を接続済み AI へ)の dependencies には、A(サーバ)+ B(クライアント汎用)+ C(クライアント gen_ai)の全レコードが 1 トレースに同居します。代表列(Name / type / cloud_RoleName / 本文の有無)と、何がトリガで、どの画面に使われるかを実測でまとめました。
| Name | type / role | トリガ | 本文/応答/Tool I/O | 由来 | 使われる画面 |
|---|---|---|---|---|---|
chat <model> |
AI / responsesapi | Foundry サーバが LLM 呼び出し時に自動記録 | ✅ 含む(system/user 本文+tool_call 引数+tool 結果+usage) |
A(設定1) | Traces 詳細・User view、Monitor / Agents(Preview) のモデル・トークン |
invoke_agent <agent>:1 |
AI / responsesapi | Foundry サーバがエージェント実行時に自動記録(:1=版数) |
✅ 本文/応答あり | A(設定1) | Traces、Agent runs |
invoke_agent <agent> |
CognitiveServices / unknown_service | クライアント計装(AIProjectInstrumentor) |
⚠️ **本文 redact(空)**+usage 有(二重計上注意) |
C(設定2+3) | Traces(入れ子の親)、Agent runs |
create_conversation |
CognitiveServices / unknown_service | クライアント計装 | ❌ なし | C(設定2+3) | Traces |
agent-tracing-scenario |
InProc / unknown_service | 自前のカスタム span | ❌ 属性のみ | B(設定2 / 自前) | Traces(ルート) |
get_package_status |
InProc / unknown_service |
@trace_function 付きローカル関数の実行 |
❌ 引数/戻り値も空 | B(設定2) | Traces(子 span) |
AIProjectClient.get_openai_client |
InProc / unknown_service | SDK のクライアント生成 | ❌ なし | B(設定2) | Traces |
GET /metadata/identity/oauth2/token |
HTTP / unknown_service |
azure-core の HTTP 自動計装(IMDS 認証) |
❌ なし(ノイズ) | B(設定2) | Traces / 通常ノイズ |
ポイント:
-
実テキスト(プロンプト/応答/Tool 入出力)を持つのはサーバ側
responsesapiのchat/invoke_agent:1だけ。例:chatのgen_ai.output.messagesに{"type":"tool_call","name":"get_package_status","arguments":{"tracking_id":"..."}}。 -
クライアント gen_ai は redact(構造のみ)、クライアント InProc/HTTP は内容ゼロ(ローカルツール実行
get_package_statusですら引数/戻り値は空)。 - つまり D = A の server span + B の汎用 client span + C の gen_ai client span の合流。由来は「設定1=サーバ / 設定2=汎用 client / 設定2+3=gen_ai client」で切り分けられます。
ハマりポイント
configure_azure_monitor を呼んだのに Agent のトレースが Portal に出ない
configure_azure_monitor(設定2)だけでは、HTTP / InProc の汎用 span しか出ず、invoke_agent などの GenAI span は生成されません。さらに App Insights を接続していなければ Foundry Portal には何も出ません(接続が前提)。
原因: GenAI 計装はプレビューのため明示 opt-in が必要。
解決策: AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING=true を設定したうえで AIProjectInstrumentor().instrument() を呼ぶ(=設定3)。これで初めて invoke_agent / create_conversation などの gen_ai span が出ます。
# NG: これだけでは gen_ai span が出ない
configure_azure_monitor(connection_string=conn_str)
# OK: GenAI 計装を opt-in してから instrument
import os
os.environ["AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING"] = "true"
from azure.ai.projects.telemetry import AIProjectInstrumentor
AIProjectInstrumentor().instrument()
本文キャプチャの有無を切り替えるには
- サーバサイド(接続): 既定で本文込み。
- クライアントサイド: 既定で redact。本文も記録したい場合のみ
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=trueを設定(開発環境限定推奨)。
まとめ
| 観点 | サーバサイド(設定1) | クライアントサイド(設定2+3) |
|---|---|---|
| 有効化 | App Insights 接続のみ(コード不要) |
configure_azure_monitor + GenAI env + AIProjectInstrumentor
|
cloud_RoleName |
responsesapi |
unknown_service |
| 粒度 |
invoke_agent + chat(モデル)まで |
invoke_agent まで |
| 本文キャプチャ | 既定 ON | 既定 OFF(redact) |
| Foundry Portal 表示 | される(接続が条件) | されない(接続しない限り) |
- トレースの保存先は実質
dependenciesの一択。サーバ/クライアントはcloud_RoleNameで切り分ける。 - エージェント観測 UI は gen_ai span 依存。「
configure_azure_monitorだけ」では機能しない。 - Foundry Portal の可視性は「接続(設定1)」だけで決まる。クライアント計装をいくらしても、未接続なら Foundry Portal は空。
- サーバサイドは本文を既定保存するので、プライバシー設計を忘れずに。
