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

エージェント 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 は、トークンの取得を自動的に処理します。 カスタム リゾルバーは不要です。 これは、エージェント 365 対応エージェントに推奨される方法です。
S2S を使用したエージェント 365 対応 エージェントは、エージェント ID チェーン (getAgenticApplicationToken + Microsoft 認証ライブラリ (MSAL)) を使用してトークンを取得します。 カスタム TokenResolverが必要です。 OBO が使用できない場合、またはアプリ専用トークンが必要な場合は、この方法を使用します。
OBO を使用したカスタム エンジン エージェントは、Azure Bot OAuth を介して、可観測性 API 向けにスコープ指定されたユーザー トークンを取得します。 カスタム TokenResolver とAzure Bot OAuth 接続が必要です。
S2S を使用したカスタム エンジン エージェントは、クライアント資格情報を使用してアプリ専用トークンを取得します。 カスタム TokenResolverが必要です。 アプリの登録は、標準 (非エージェント) アプリである必要があります。

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

エージェント 365 対応エージェントは、エージェント 365 プラットフォームからエージェント ID (agenticAppIdagenticUserId) を使用して要求を受信します。 OBO では、ディストリビューションの組み込みの AgenticTokenCache がトークンの取得を自動的に処理します。カスタム トークン リゾルバーは必要ありません。

前提条件

  • Entra アプリの登録 : クライアント ID、クライアント シークレット、テナント ID を持つサービス プリンシパル (アプリの登録)
  • 委任された API アクセス許可 : Agent365.Observability.OtelWrite の追加 (委任)、管理者の同意を付与します。 詳細な手順については、「 アクセス許可を付与する」を参照してください。

セットアップ

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

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

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

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

  1. getAgenticApplicationToken(tenantId, agentId) : クライアント資格情報 + フェデレーション マネージド ID (FMI) パス
  2. MSAL acquireTokenForClient。アプリ トークンを clientAssertion、スコープを api://9b975845-388f-4429-889e-eab1ef63949c/.default として使用する

Note

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

カスタム TokenResolver を指定し、 UseS2SEndpoint = trueを設定する必要があります。

前提条件

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

  • アプリケーション API のアクセス許可 : Agent365.Observability.OtelWrite (アプリケーション) の追加、管理者の同意の付与

  • Agent365.Observability.OtelWrite アプリ ロール : エージェントのサービス プリンシパルには、Agent365 Observability リソースに OtelWrite ロールが割り当てられている必要があります。 エージェント 365 CLI を使用します。

    a365 setup permissions bot --config-dir "<path-to-config-dir>"
    

    Note

    ロールの反映には数分かかる場合があります。 この期間中、エクスポート エンドポイントからの最初の 401 または 403 エラーが予想されます。

手順 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 を持つスパンが見つかりません」として通知なしに破棄されます。

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

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

前提条件

委任された API アクセス許可を使用した Entra アプリの登録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_middlewareaiohttpApplicationが必要です (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 portal prerequisite:oboConnectionProfile という名前の Azure Bot OAuth 接続では、Scopesapi://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 チェーンは必要ありません。

前提条件

  • Azure AD アプリの登録: カスタム エンジン(標準)アプリである必要があります。 エージェント 365 対応アプリの登録では、監視リソース (client_credentials) にプレーンなAADSTS82001を使用することはできません。
  • アプリケーションのアクセス許可 : Agent365.Observability.OtelWrite (委任されていないアプリケーション) を追加し、管理者の同意を付与します。

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 エクスポーターは、スパン コンテキストで手荷物 (テナント 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])