Konfiguracja uwierzytelniania obserwowalności

Eksporter Agent 365 wymaga modułu rozwiązywania tokenów do uwierzytelniania podczas eksportu telemetrii. Niniejszy przewodnik obejmuje konfigurację dla agentów zbudowanych przy użyciu Zestaw SDK agentów usługi Microsoft 365, w tym zarówno agentów z obsługą Agent 365, jak i agentów z niestandardowym silnikiem na platformach .NET, Python i Node.js.

W przypadku instalacji, ogólnej konfiguracji oraz scenariuszy niewymagających Agent SDK, zobacz Microsoft OpenTelemetry Distro.

Omówienie

Istnieją cztery scenariusze uwierzytelniania, w zależności od typu agenta i sposobu pozyskiwania tokenów. Pozyskiwanie tokenów może wykorzystywać przepływ On-Behalf-Of (OBO) lub Service-to-Service (S2S). Wybierz scenariusz pasujący do Twojej konfiguracji:

Scenariusz Podpis
Agent 365 z obsługą OBO Wbudowany komponent AgenticTokenCache dystrybucji automatycznie obsługuje pozyskiwanie tokenów. Nie jest wymagany niestandardowy resolver. To jest zalecane podejście dla agentów z obsługą Agent 365.
Agent 365 z obsługą S2S Agent uzyskuje token, korzystając z łańcucha tożsamości agenta (getAgenticApplicationToken + Microsoft Authentication Libraries (MSAL)). Wymaga niestandardowego TokenResolver. Stosuj to podejście, gdy OBO nie jest dostępne lub potrzebujesz tokenów aplikacyjnych.
Niestandardowy silnik wykorzystujący OBO Agent uzyskuje token użytkownika za pośrednictwem Azure Bot OAuth, przeznaczony dla API obserwowalności. Wymaga niestandardowego TokenResolver oraz połączenia Azure Bot OAuth.
Niestandardowy silnik wykorzystujący S2S Agent pozyskuje token aplikacyjny, używając danych uwierzytelniających klienta. Wymaga niestandardowego TokenResolver. Rejestracja aplikacji musi być standardowa (non-agentic).

Agent 365 z obsługą OBO

Agenci zintegrowani z Agent 365 otrzymują żądania z tożsamością agenticzną (agenticAppId, agenticUserId) z platformy Agent 365. W OBO wbudowany AgenticTokenCache dystrybucji automatycznie obsługuje pozyskiwanie tokenów: nie jest potrzebny niestandardowy resolver tokenów.

Wymagania wstępne

  • Rejestracja aplikacji Entra : Service principal (rejestracja aplikacji) z Client ID, Client Secret i Tenant ID
  • Delegowane uprawnienia API : Dodaj Agent365.Observability.OtelWrite (delegowane), uzyskaj zgodę administratora. Szczegółowe kroki można znaleźć w artykule Udziel zgody.

Ustawienia

W każdej turze agent wywołuje funkcję RegisterObservability z kontekstem tury. Wbudowana pamięć podręczna wykorzystuje delegowany token użytkownika z handlera AgenticUserAuthorization do przeprowadzenia wymiany OBO, uzyskując token o zakresie Agent365.Observability.OtelWrite.

Pełne instrukcje dotyczące konfiguracji, w tym pakiety, konfigurację i przykłady kodu, zobacz w Agentic token cache with Agent Framework apps.

Agent 365 z obsługą S2S

Agenci Agent 365 mogą również używać uwierzytelniania S2S (service-to-service) zamiast OBO. Agent uzyskuje token, korzystając ze swojej tożsamości usługi (service principal) poprzez dwustopniowy agentowy łańcuch tożsamości:

  1. getAgenticApplicationToken(tenantId, agentId) : poświadczenia klienta + ścieżka Federated Managed Identity (FMI)
  2. MSAL acquireTokenForClient z tokenem aplikacji jako clientAssertion i zakresem api://9b975845-388f-4429-889e-eab1ef63949c/.default

Notatka

Federated Managed Identity (FMI) to architektura, w której zarządzana tożsamość uczestniczy w federacji tożsamości obciążenia za pomocą federowanych poświadczeń tożsamości, umożliwiając wymianę tokenów oraz uwierzytelnianie bez konieczności stosowania sekretów, opierając się na relacjach zaufania między tożsamościami.

Musisz podać niestandardową TokenResolver i ustawić UseS2SEndpoint = true.

Wymagania wstępne

  • Rejestracja aplikacji Entra : Service principal (rejestracja aplikacji) z Client ID, Client Secret i Tenant ID

  • Uprawnienia API aplikacji : Dodaj Agent365.Observability.OtelWrite (Aplikacja), udziel zgody administratora

  • Agent365.Observability.OtelWrite rola aplikacji: Podmiot usługowy agenta musi mieć przypisaną OtelWrite rolę na zasobie Agent365 Observability. Korzystanie z Agent 365 CLI:

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

    Notatka

    Propagacja roli może potrwać kilka minut. W tym okresie mogą wystąpić początkowe błędy 401 lub 403 z punktu końcowego eksportu.

Krok 1: Konfiguracja środowiska

Poniższe przykłady kodu pokazują, jak skonfigurować wymagane ustawienia środowiska połączenia, tenant, dane uwierzytelniające klienta oraz eksportera obserwacji przed włączeniem niestandardowego przepływu tokenów S2S.

Nie trzeba definiować AgenticUserAuthorization handlera. S2S wykorzystuje ręczny łańcuch tożsamości agentowej (get_agentic_application_token + MSAL acquire_token_for_client), aby uzyskać token z zakresem dla zasobu obserwowalności.

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

Krok 2: Skonfiguruj dystrybucję z niestandardowym resolverem tokenów

Poniższe przykłady pokazują, jak włączyć eksport i rejestrację niestandardowego TokenResolver przez Agent 365, aby eksporter mógł pobierać tokeny S2S dla każdego agenta i tenanta.

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,
)

Krok 3: Uzyskanie i buforowanie tokena S2S

Przy każdej przychodzącej wiadomości pobieraj token S2S przez łańcuch tożsamości agentycznej i buforuj go dla resolvera.

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])

Ważne

Ręczny dwuetapowy proces (get_agentic_application_token + MSAL acquire_token_for_client) jest wymagany w przypadku S2S. AgenticUserAuthorization.get_token() zwraca token z przypisanym zakresem do 5a807f24-.../.default (Bot Framework), a nie do zasobu obserwowalności api://9b975845-.../.default; punkt końcowy S2S odrzuca go kodem 401 InvalidAudience.

  • Wykorzystaj context.activity.get_agentic_instance_id() oraz get_agentic_tenant_id(), aby odczytać agenta i dzierżawcę z aktywności (odczyt z recipient zgodnie z konwencją SDK).
  • Uzyskaj i zbuforuj token S2S przed utworzeniem spanów. Bufor eksportera BatchSpanProcessor może zostać opróżniony zanim handler zakończy pracę: jeśli token nie został jeszcze zbuforowany, eksport się nie powiedzie.
  • Otocz BaggageBuilderwszystkie zakresy A365, aby eksporter wiedział, dla którego agenta i tenanta rozwiązywać tokeny. Bez baggage, spany są po cichu odrzucane z komunikatem: „Nie znaleziono spanów z tożsamością najemcy/agenta.”

Niestandardowy silnik wykorzystujący OBO

Agenci niestandardowego silnika używają standardowych rejestracji aplikacji z połączeniami Azure Bot OAuth, a nie łańcucha tożsamości agenta. Korzystając z OBO, agent otrzymuje token użytkownika przez Azure Bot OAuth, który ma już odpowiedni zakres do API obserwacyjnego A365, nadany przez Bot Framework Token Service. Pojedyncze wywołanie getToken lub GetTurnTokenAsync zwraca poprawnie zakreskowany token, więc nie potrzebujesz exchangeToken.

Wymagania wstępne

Rejestracja aplikacji Entra z delegowanymi uprawnieniami API. Dodaj Agent365.Observability.OtelWrite (delegowane) i uzyskaj zgodę administratora

Ważne

agentId w pamięci podręcznej musi być zgodny z „Client ID” rejestracji aplikacji – a nie agenticAppId aktywności, która nie istnieje dla agentów niestandardowych silników. URL eksportu zawiera agentId, a niedopasowanie powoduje błąd HTTP 403.

Krok 1: Konfiguracja środowiska i aplikacji

Poniższe przykłady pokazują, jak skonfigurować aplikację i środowisko uruchomieniowe, uwzględniając wartości połączeń z usługami, ustawienia dzierżawcy i klienta oraz wymagane mapowania autoryzacji.

# .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

Ważne

load_configuration_from_env konwertuje wszystkie klucze zmiennych środowiskowych na wielkie litery. Nazwa handlera przyjmuje wartość OBOCONNECTIONPROFILE i musisz odwołać się do niej, zachowując dokładnie taką pisownię, w wywołaniach auth_handlers i get_token(). Brak TYPE powoduje Auth handler ... not recognized or not configured podczas działania programu.

Krok 2: Skonfiguruj distro dla OBO

Poniższe przykłady pokazują, jak włączyć eksportowanie przez Agent 365, utrzymać eksportera na punkcie końcowym OBO oraz zarejestrować niestandardowy TokenResolver resolver, który zwraca tokeny delegowane podczas eksportu.

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,
)

Notatka

Tryb OBO wymaga obecności jwt_authorization_middleware na aiohttpApplication, który waliduje przychodzący token JWT (JSON Web Token) z Bot Framework. Ścieżka S2S/emulator nie powinna zawierać tego middleware.

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

Krok 3: Uzyskaj token OBO

Poniższe przykłady pokazują, jak uzyskać delegowany token OBO ze skonfigurowanego połączenia Azure Bot OAuth, a następnie przechowywać go w pamięci podręcznej według klienta aplikacji i dzierżawcy dla eksportera.

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

Ważne

Wymóg dotyczący Azure PortalWyPołączenie Azure Bot OAuth o nazwie oboConnectionProfile musi mieć swoje zakresy (Scopes) ustawione na api://9b975845-388f-4429-889e-eab1ef63949c/Agent365.Observability.OtelWrite. Bez tego ustawienia token jest przypisany do odbiorcy bota (api://botid-...) i eksport kończy się błędem HTTP 401 InvalidAudience.

Notatka

AGENT_APP.auth.get_token() zwraca token z odpowiednim zakresem bezpośrednio – nie jest potrzebne wywołanie exchange_token(). Usługa Bot Framework Token Service obsługuje wymianę OBO, gdy zakres połączenia OAuth celuje w zasób obserwacji A365.

Niestandardowy silnik wykorzystujący S2S

Agenci niestandardowego silnika mogą używać przepływu S2S (poświadczeń klienta) w celu uzyskania tokena aplikacji, korzystając z danych uwierzytelniających połączenia usługi. Ta metoda wykorzystuje standardowe poświadczenia klienta MSAL – nie jest wymagany łańcuch tożsamości agenta.

Wymagania wstępne

  • Rejestracja aplikacji Azure AD: Musi to być aplikacja silnika niestandardowego (standardowa). Rejestracje aplikacji z włączonym Agent 365 nie mogą używać zwykłego client_credentials dla zasobu obserwowalności (AADSTS82001).
  • Uprawnienia aplikacji: Dodaj Agent365.Observability.OtelWrite (aplikacyjne, nie delegowane) i zatwierdź zgodę administratora.

Ważne

agentId użyty podczas buforowania musi być ClientId ServiceConnection. URL eksportu to /observabilityService/tenants/{tenantId}/otlp/agents/{agentId}/traces : niedopasowanie powoduje błąd HTTP 403.

Krok 1: Konfiguracja środowiska i aplikacji

Poniższe przykłady pokazują, jak skonfigurować aplikację i środowisko uruchomieniowe, uwzględniając wartości połączeń z usługami, ustawienia dzierżawcy i klienta oraz wymagane mapowania autoryzacji.

# .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

Krok 2: Skonfiguruj distro dla S2S

Poniższe przykłady pokazują, jak włączyć eksportowanie Agent 365, ustawić eksporter na punkt końcowy S2S oraz zarejestrować niestandardowy TokenResolver do wyszukiwania tokenów podczas eksportu.

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,
)

Krok 3: Uzyskaj token S2S

Poniższe przykłady pokazują, jak uzyskać token dostępu aplikacji do zasobu obserwowalności, korzystając z poświadczeń połączenia serwisowego, a następnie przechowywać go w pamięci podręcznej dla agenta i dzierżawcy na potrzeby eksportera.

# 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

Krok 4: Ustaw baggage dla eksportu spanów

Eksporter Agent365 wymaga ustawienia baggage (tenant ID i agent ID) w kontekście spanu. Jeśli tego zabraknie, eksporter bez ostrzeżenia odrzuca span'y z wiadomością 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])