可観測性認証のセットアップ

エージェント 365 エクスポーターでは、テレメトリをエクスポートするときに認証を行うためにトークン リゾルバーが必要です。 このガイドでは、Microsoft 365 エージェント SDK を使用して構築されたエージェントのセットアップについて説明します。エージェント 365 対応エージェントとカスタム エンジン エージェントの両方を、.NET、Python、および Node.jsにわたってカバーします。

ディストリビューションのインストール、一般的な構成、およびエージェント以外の SDK シナリオについては、「Microsoft OpenTelemetry Distroを参照してください。

Overview

エージェントの種類とトークンの取得方法に応じて、4 つの認証シナリオがあります。 トークンの取得には、On-Behalf-Of フロー (OBO) または Service-to-Service (S2S) を使用できます。 セットアップに一致するシナリオを選択します。

Scenario Description
OBO を使用したエージェント 365 対応 このディストリビューションに搭載されている AgenticTokenCache は、委任されたルート上でトークンの取得を自動的に処理します。 カスタム リゾルバーは不要です。 このルートには、委任された Agent365.Observability.OtelWrite アクセス許可と管理者の承認が必要ですが、a365 setup all ではブループリント エージェントに対してこれらの構成を行っていません。
S2S を使用したエージェント 365 対応 エージェントはgetAgenticApplicationToken + Microsoft認証ライブラリ(MSAL) agentic ID チェーンを使ってアプリ専用トークンを取得します。 カスタム TokenResolver とS2Sエンドポイントオプションが必要です。 登録エージェントのインスタンスはObservability権限や管理者の同意を必要としないため、 a365 setup allで設定したBlueprint Agentにはこの方法が推奨されます。
OBO を使用したカスタム エンジン エージェントは、Azure Bot OAuth を介して、可観測性 API 向けにスコープ指定されたユーザー トークンを取得します。 カスタム TokenResolver、Azure Bot OAuth 接続、管理者の同意を得た Agent365.Observability.OtelWrite アクセス許可の委任が必要です。
S2S を使用したカスタム エンジン エージェントは、クライアント資格情報を使用してアプリ専用トークンを取得します。 カスタム TokenResolverが必要です。 アプリの登録は、標準 (非エージェント) アプリである必要があります。 標準的なアプリ登録は登録済みエージェント インスタンスではないため、管理者の同意を得た Agent365.Observability.OtelWrite アプリケーション権限が必要です。

OBO を使用する Agent 365 の有効化

エージェント 365 対応エージェントは、エージェント 365 プラットフォームからエージェント ID (agenticAppId、 agenticUserId) を使用して要求を受信します。 OBO を使用することで、ディストリビューションに搭載されている AgenticTokenCache が、委譲されたルート上でトークンの取得を自動的に処理するため、カスタム トークン リゾルバーは不要です。

[前提条件]

  • Microsoft Entra アプリ登録:クライアントID、クライアントシークレット、テナントIDを持つサービスプリンシパル(アプリ登録)です。

  • 委任されたAPI権限: Agent365.Observability.OtelWrite を追加 (委任) し、管理者の同意を与えます。 a365 setup allコマンドはブループリントエージェントに対してこの権限を追加しません。 グローバル管理者は以下のコマンドを実行することで追加し、管理者の同意を付与できます:

    a365 setup permissions custom --resource-app-id 9b975845-388f-4429-889e-eab1ef63949c --scopes Agent365.Observability.OtelWrite
    

    Microsoft Entra 管理センターを含むその他のオプションについては、「権限を付与する」をご覧ください。

Tip

委任された Observability のアクセス許可および管理者の同意を回避するには、a365 setup all で設定されたブループリント エージェントに対して、S2S を使用して有効化された Agent 365 を使用してください。

セットアップ

各ターンで、エージェントはターン コンテキストを使用して RegisterObservability 関数を呼び出します。 組み込みキャッシュは、 AgenticUserAuthorization ハンドラーからユーザーの委任されたトークンを使用して OBO 交換を実行し、 Agent365.Observability.OtelWriteスコープのトークンを取得します。

パッケージ、構成、コード例などの完全なセットアップ手順については、 Agent Framework アプリを使用した Agentic トークン キャッシュに関するページを参照してください。

S2S を使用したエージェント 365 対応

エージェント 365 対応エージェントは、OBO の代わりに S2S (サービス間) 認証を使用することもできます。 このパスは a365 setup allで設定したブループリントエージェントに推奨されます。 エージェントは、2 段階のエージェント ID チェーンを介して、独自のサービス プリンシパル ID を使用してトークンを取得します。

  1. getAgenticApplicationToken(tenantId, agentId) を呼び出して、Federated Managed Identity (FMI) パスを使用したクライアント資格情報トークンを取得します。
  2. アプリのトークンを acquireTokenForClient、スコープを api://9b975845-388f-4429-889e-eab1ef63949c/.default として MSAL clientAssertion を呼び出します。

Note

フェデレーション マネージド ID (FMI) は、マネージド ID がフェデレーション ID 資格情報を介してワークロード ID フェデレーションに参加し、ID 間の信頼関係に基づいてトークン交換とシークレットレス認証を有効にするアーキテクチャです。

MicrosoftのOpenTelemetryディストリビューションはデフォルトで委任ルートを使用するため、カスタム TokenResolver を指定し、使用する言語に応じて、たとえば UseS2SEndpoint = true、useS2SEndpoint: true、a365_use_s2s_endpoint=True のように S2S エンドポイント オプションを設定する必要があります。

[前提条件]

  • エージェント365の登録:エージェントのインスタンスはエージェント365に登録されている必要があります。 a365 setup allコマンドはブループリントエージェントのインスタンスを登録します。 Microsoft Entraのアイデンティティを作成するだけではエージェントインスタンスは登録されません。
  • FMI 交換用のブループリントの資格情報: ブループリントのクライアント シークレットまたはマネージド IDを使用して、api://AzureADTokenExchange/.default の Federated Managed Identity (FMI) トークンを取得し、それをエージェント インスタンスの Observability トークンと交換します。
  • 識別値のマッチング: baggage 内のエージェント ID とテナント ID、およびエクスポート URL は、アプリのみのトークン内のエージェント インスタンス クライアント ID およびテナントと一致しなければなりません。

Note

Agent 365に登録されたエージェント インスタンスは、Agent365.Observability.OtelWrite ロールを持たないアプリ専用トークンを使用して、S2S ルート上でエクスポートを行うことができます。 Observabilityの許可や管理者の同意は必要ありません。 標準的なアプリ登録を含む未登録の ID については、依然として Agent365.Observability.OtelWrite アプリ ロールと管理者の承認が必要です。

手順 1: 環境の構成

次のコード例は、カスタム S2S トークン フローを有効にする前に、必要な接続、テナント、クライアント資格情報、監視エクスポーターの環境設定を設定する方法を示しています。

AgenticUserAuthorization ハンドラーは必要ありません。 S2S では、手動エージェント ID チェーン (get_agentic_application_token + MSAL acquire_token_for_client) を使用して、監視リソースをスコープとするトークンを取得します。

CONNECTIONSMAP__0__SERVICEURL=*
CONNECTIONSMAP__0__CONNECTION=SERVICE_CONNECTION

CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID=<your-client-id>
CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTSECRET=<your-client-secret>
CONNECTIONS__SERVICE_CONNECTION__SETTINGS__TENANTID=<your-tenant-id>

ENABLE_A365_OBSERVABILITY=true
ENABLE_A365_OBSERVABILITY_EXPORTER=true

手順 2: カスタム トークン リゾルバーを使用してディストリビューションを構成する

次の例では、エージェント 365 のエクスポートを有効にし、カスタム TokenResolver を登録して、エクスポーターが各エージェントとテナントの S2S トークンを取得できるようにする方法を示します。

from microsoft.opentelemetry import use_microsoft_opentelemetry

_token_cache: dict[str, str] = {}

def token_resolver(agent_id: str, tenant_id: str) -> str | None:
    return _token_cache.get(f"{agent_id}:{tenant_id}")

use_microsoft_opentelemetry(
    enable_a365=True,
    a365_token_resolver=token_resolver,
    a365_use_s2s_endpoint=True,
    a365_enable_observability_exporter=True,
)

手順 3: S2S トークンを取得してキャッシュする

各受信メッセージで、エージェント ID チェーンを介して S2S トークンを取得し、リゾルバー用にキャッシュします。

import asyncio
from msal import ConfidentialClientApplication
from microsoft.opentelemetry.a365.core import BaggageBuilder, InvokeAgentScope, InvokeAgentScopeDetails, Request

OBSERVABILITY_S2S_SCOPE = "api://9b975845-388f-4429-889e-eab1ef63949c/.default"

async def get_agentic_s2s_token(connection, tenant_id: str, agent_id: str) -> str:
    # Step 1: Get agentic application token (client_credentials + fmi_path)
    app_token = await connection.get_agentic_application_token(tenant_id, agent_id)
    if not app_token:
        raise ValueError(f"Failed to get agentic app token for agent {agent_id}")

    # Step 2: Exchange for observability-scoped token
    cca = ConfidentialClientApplication(
        client_id=agent_id,
        authority=f"https://login.microsoftonline.com/{tenant_id}",
        client_credential={"client_assertion": app_token},
    )
    result = await asyncio.to_thread(
        lambda: cca.acquire_token_for_client(scopes=[OBSERVABILITY_S2S_SCOPE])
    )
    if not result or "access_token" not in result:
        raise ValueError(f"Token acquisition failed: {result}")
    return result["access_token"]

# In your message handler : use SDK helpers to get agent/tenant from the activity:
@AGENT_APP.activity("message")
async def on_message(context: TurnContext, _state: TurnState):
    # get_agentic_instance_id reads from recipient (SDK convention)
    agent_id = context.activity.get_agentic_instance_id()
    tenant_id = context.activity.get_agentic_tenant_id()

    # Acquire S2S token and cache BEFORE creating spans
    connection = CONNECTION_MANAGER.get_connection("SERVICE_CONNECTION")
    token = await get_agentic_s2s_token(connection, tenant_id, agent_id)
    _token_cache[f"{agent_id}:{tenant_id}"] = token

    # Wrap spans in BaggageBuilder so the exporter can resolve the token
    request = Request(content=user_message, session_id=None)
    with BaggageBuilder().tenant_id(tenant_id).agent_id(agent_id).build():
        invoke_scope = InvokeAgentScope.start(request, InvokeAgentScopeDetails(), agent_details)
        with invoke_scope:
            invoke_scope.record_input_messages([user_message])
            invoke_scope.record_output_messages([response])

Important

S2S には、手動の 2 段階フロー (get_agentic_application_token + MSAL acquire_token_for_client) が必要です。 AgenticUserAuthorization.get_token()は、可観測性リソース5a807f24-.../.defaultではなく、api://9b975845-.../.default (Bot Framework) をスコープとするトークンを返します。S2S エンドポイントは、401 InvalidAudienceで拒否します。

  • context.activity.get_agentic_instance_id()とget_agentic_tenant_id()を使用して、アクティビティからエージェントとテナントを読み取ります (SDK 規則に従ってrecipientから読み取ります)。
  • スパンを作成する 前に 、S2S トークンを取得してキャッシュします。 ハンドラーが終了する前に、エクスポーターの BatchSpanProcessor がフラッシュされる可能性があります。トークンがまだキャッシュされていない場合、エクスポートは失敗します。
  • トークンを解決するエージェントとテナントをエクスポーターが認識できるように、すべての A365 スコープを BaggageBuilder でラップします。 バゲージがない場合、「テナント/エージェントの ID を持つスパンが見つかりませんでした」というメッセージと共に、スパンは通知なしに破棄されます。

Important

リゾルバーが最終的なObservabilityトークンを返してキャッシュする前に:

  • トークンがアプリ専用であることを検証してください。 idtyp=app を受け入れます。 idtypが存在しない場合は、空でないrolesクレーム、またはsubに等しい空でないoidクレームを持つトークンのみを受け入れます。 scp というクレームを持つトークン、または別の idtyp という値を持つトークンを拒否します。
  • audが9b975845-388f-4429-889e-eab1ef63949cまたはapi://9b975845-388f-4429-889e-eab1ef63949cであり、トークンの有効期限が切れていないことを確認してください。
  • 中間ブループリントのアサーション、ブループリント トークン、ユーザー トークン、または OBO トークンを Observability に送信してはなりません。
  • 最後のトークンをキャッシュして、期限切れ前にリフレッシュしてください。 エクスポーターは各エクスポートバッチで、各テナント ID とエージェント ID ごとにリゾルバーを 1 回呼び出します。

ディストリビューションを使用した完全な実装については、Node.js、Python、.NETのAgent 365サンプルを参照してください。

OBO を使用したカスタム エンジン

カスタム エンジン エージェントは、エージェント ID チェーンではなく、Azure Bot OAuth 接続で標準アプリ登録を使用します。 OBO を使用すると、エージェントは、Bot Framework トークン サービスによって既に A365 監視 API にスコープが設定されている、Azure Bot OAuth を介してユーザー トークンを取得します。 1 つの getToken 呼び出しまたは GetTurnTokenAsync 呼び出しで正しいスコープのトークンが返されるため、 exchangeTokenは必要ありません。

[前提条件]

Microsoft Entra アプリ登録と委任された API のアクセス許可。 Agent365.Observability.OtelWrite (委任済み) を追加し、管理者の承認を付与してください。

Important

トークン キャッシュ内のagentIdは、カスタム エンジン エージェントには存在しないアクティビティのではなく、アプリ登録のagenticAppId一致する必要があります。 エクスポート URL には agentIdが含まれており、一致しない場合は HTTP 403 が発生します。

手順 1: 環境とアプリの構成

次の例では、サービス接続の値、テナントとクライアントの設定、必要な承認マッピングなど、アプリとランタイム環境を構成する方法を示します。

# .env
CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID=<your-client-id>
CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTSECRET=<your-client-secret>
CONNECTIONS__SERVICE_CONNECTION__SETTINGS__TENANTID=<your-tenant-id>

CONNECTIONSMAP__0__CONNECTION=SERVICE_CONNECTION
CONNECTIONSMAP__0__SERVICEURL=*

# Auth handler config : TYPE is required, name is uppercased by load_configuration_from_env
AGENTAPPLICATION__USERAUTHORIZATION__HANDLERS__OBOCONNECTIONPROFILE__TYPE=UserAuthorization
AGENTAPPLICATION__USERAUTHORIZATION__HANDLERS__OBOCONNECTIONPROFILE__SETTINGS__AZUREBOTOAUTHCONNECTIONNAME=oboConnectionProfile
AGENTAPPLICATION__USERAUTHORIZATION__HANDLERS__OBOCONNECTIONPROFILE__SETTINGS__SCOPES=api://9b975845-388f-4429-889e-eab1ef63949c/Agent365.Observability.OtelWrite

ENABLE_A365_OBSERVABILITY=true
ENABLE_A365_OBSERVABILITY_EXPORTER=true

Important

load_configuration_from_env すべての環境変数のキーを大文字に変換します。 ハンドラー名は OBOCONNECTIONPROFILE となり、auth_handlers と get_token() の呼び出しでは、この大文字小文字を正確に区別して参照する必要があります。 TYPEが見つからないと、実行時にAuth handler ... not recognized or not configuredが発生します。

手順 2: OBO のディストリビューションを構成する

次の例では、エージェント 365 のエクスポートを有効にし、OBO エンドポイントでエクスポーターを保持し、エクスポート中に委任されたトークンを返すカスタム TokenResolver を登録する方法を示します。

from microsoft.opentelemetry import use_microsoft_opentelemetry

_token_cache: dict[str, str] = {}

def token_resolver(agent_id: str, tenant_id: str) -> str | None:
    return _token_cache.get(f"{agent_id}:{tenant_id}")

environ["ENABLE_A365_OBSERVABILITY_EXPORTER"] = "true"

use_microsoft_opentelemetry(
    enable_a365=True,
    a365_token_resolver=token_resolver,
    a365_use_s2s_endpoint=False,  # OBO uses /observability endpoint
    a365_enable_observability_exporter=True,
)

Note

OBO モードでは、jwt_authorization_middlewareaiohttpでApplicationが必要です (Bot Framework からの受信 JWT (JSON Web トークン) を検証します)。 S2S/エミュレーター パスには、このミドルウェアを含めることはできません。

from microsoft_agents.hosting.aiohttp import jwt_authorization_middleware
app = Application(middlewares=[jwt_authorization_middleware])

手順 3: OBO トークンを取得する

次の例では、構成された Azure Bot OAuth 接続から委任された OBO トークンを要求し、それをアプリ クライアントとエクスポーターのテナントによってキャッシュする方法を示します。

from microsoft_agents.hosting.core import (
    AgentApplication, Authorization, MemoryStorage, TurnContext, TurnState,
)
from microsoft_agents.activity import load_configuration_from_env
from microsoft_agents.authentication.msal import MsalConnectionManager
from microsoft_agents.hosting.aiohttp import CloudAdapter

# Auth handlers are loaded from .env via load_configuration_from_env (see Environment config above)
agents_sdk_config = load_configuration_from_env(environ)

STORAGE = MemoryStorage()
CONNECTION_MANAGER = MsalConnectionManager(**agents_sdk_config)
ADAPTER = CloudAdapter(connection_manager=CONNECTION_MANAGER)
AUTHORIZATION = Authorization(STORAGE, CONNECTION_MANAGER, **agents_sdk_config)

AGENT_APP = AgentApplication[TurnState](
    storage=STORAGE, adapter=ADAPTER, authorization=AUTHORIZATION, **agents_sdk_config,
)

CLIENT_ID = environ.get("CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID", "")
TENANT_ID = environ.get("CONNECTIONS__SERVICE_CONNECTION__SETTINGS__TENANTID", "")

# Message handler : get_token returns a token already scoped to the observability API.
# The Azure Bot Token Service performs the OBO exchange internally based on the
# OAuth connection's configured scope. No manual MSAL exchange_token call is needed.
@AGENT_APP.activity("message", auth_handlers=["OBOCONNECTIONPROFILE"])
async def on_message(context: TurnContext, _state: TurnState):
    token_response = await AGENT_APP.auth.get_token(context, "OBOCONNECTIONPROFILE")
    # token_response.token has aud=<a365-observability-app-id>,
    # scp=Agent365.Observability.OtelWrite
    _token_cache[f"{CLIENT_ID}:{TENANT_ID}"] = token_response.token

Important

Azure ポータルの前提条件: という名前の Azure Bot OAuth 接続の oboConnectionProfile を api://9b975845-388f-4429-889e-eab1ef63949c/Agent365.Observability.OtelWrite に設定します。 この設定がないと、トークンのスコープはボット自身の対象ユーザー (api://botid-...) になり、HTTP 401 InvalidAudienceでエクスポートが失敗します。

Note

AGENT_APP.auth.get_token() は適切なスコープのトークンを直接返すため、exchange_token() の呼び出しは不要です。 Bot Framework トークン サービスは、OAuth 接続スコープが A365 監視リソースを対象とする場合に OBO 交換を処理します。

S2S を使用したカスタム エンジン

カスタムエンジンエージェントはS2S(クライアント認証情報)を使ってサービス接続認証情報を使ってアプリ専用トークンを取得することができます。 この方法では、標準の MSAL クライアント資格情報を使用します。エージェント型 ID チェーンは必要ありません。

[前提条件]

  • Microsoft Entraアプリの登録:アプリの登録はカスタムエンジン(標準)でなければなりません。 エージェント 365 対応アプリの登録では、監視リソース (client_credentials) にプレーンなAADSTS82001を使用することはできません。
  • アプリケーション権限: Agent365.Observability.OtelWrite を追加(アプリケーション、委任ではなく)、管理者の同意を付与します。 標準のアプリ登録は、Agent 365 に登録されたエージェント インスタンスではないため、S2S ルート上でこのアプリ ロールが必要です。

Important

キャッシュに使用する agentId は、ServiceConnection の ClientId と一致している必要があります。 エクスポート URL が /observabilityService/tenants/{tenantId}/otlp/agents/{agentId}/traces : 不一致により HTTP 403 が発生します。

手順 1: 環境とアプリの構成

次の例では、サービス接続の値、テナントとクライアントの設定、必要な承認マッピングなど、アプリとランタイム環境を構成する方法を示します。

# .env
CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID=<your-client-id>
CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTSECRET=<your-client-secret>
CONNECTIONS__SERVICE_CONNECTION__SETTINGS__TENANTID=<your-tenant-id>

CONNECTIONSMAP__0__CONNECTION=SERVICE_CONNECTION
CONNECTIONSMAP__0__SERVICEURL=*

ENABLE_A365_OBSERVABILITY=true
ENABLE_A365_OBSERVABILITY_EXPORTER=true

手順 2: S2S のディストリビューションを構成する

次の例では、エージェント 365 のエクスポートを有効にし、エクスポーターを S2S エンドポイントに設定し、エクスポート中にトークン検索用のカスタム TokenResolver を登録する方法を示します。

from microsoft.opentelemetry import use_microsoft_opentelemetry

_token_cache: dict[str, str] = {}

def token_resolver(agent_id: str, tenant_id: str) -> str | None:
    return _token_cache.get(f"{agent_id}:{tenant_id}")

use_microsoft_opentelemetry(
    enable_a365=True,
    a365_token_resolver=token_resolver,
    a365_use_s2s_endpoint=True,  # S2S uses /observabilityService endpoint
    a365_enable_observability_exporter=True,
)

手順 3: S2S トークンを取得する

次の例では、サービス接続資格情報を使用して監視リソースのアプリ専用アクセス トークンを要求し、それをエクスポーターのエージェントとテナントによってキャッシュする方法を示します。

# Force agentId to ServiceConnection ClientId (custom engine agents have no agenticAppId)
agent_id = os.environ.get("CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID")
tenant_id = os.environ.get("CONNECTIONS__SERVICE_CONNECTION__SETTINGS__TENANTID")

connection = CONNECTION_MANAGER.get_connection("SERVICE_CONNECTION")
token = await connection.get_access_token(
    resource_url="https://login.microsoftonline.com",
    scopes=["api://9b975845-388f-4429-889e-eab1ef63949c/.default"],
)
_token_cache[f"{agent_id}:{tenant_id}"] = token

手順 4: スパン エクスポートの手荷物を設定する

Agent365のエクスポーターは、スパンのコンテキストでbaggage(テナントIDおよびエージェントID)を必要とします。 このバグがなければ、エクスポーターはスパンを削除し、メッセージ No spans with tenant/agent identity found. をログに記録します。

from microsoft.opentelemetry.a365.core import BaggageBuilder, InvokeAgentScope

# Baggage must wrap the span as a context manager
with BaggageBuilder().tenant_id(tenant_id).agent_id(agent_id).build():
    invoke_scope = InvokeAgentScope.start(request, InvokeAgentScopeDetails(), agent_details)
    with invoke_scope:
        invoke_scope.record_input_messages([user_message])
        invoke_scope.record_output_messages([response])