Configuración de la autenticación de observabilidad

El exportador del Agente 365 requiere que un solucionador de tokens se autentique al exportar la telemetría. En esta guía se describe la configuración de los agentes creados con el SDK de agentes de Microsoft 365, que abarcan tanto agentes habilitados para agente 365 como agentes de motor personalizados en .NET, Python y Node.js.

Para la instalación de la distribución, la configuración general y los escenarios que no usan Agent SDK, consulte Microsoft OpenTelemetry Distro.

Overview

Hay cuatro escenarios de autenticación, en función del tipo de agente y de cómo adquiere tokens. La adquisición de tokens puede realizarse mediante el flujo On-Behalf-Of (OBO) o servicio a servicio (S2S). Elija el escenario que coincida con la configuración:

Scenario Description
Agente 365 habilitado mediante OBO El AgenticTokenCache integrado de la distribución gestiona la adquisición automática de tokens en la ruta delegada. No se necesita ningún solucionador personalizado. Esta ruta requiere el permiso delegado Agent365.Observability.OtelWrite y el consentimiento del administrador, que a365 setup all no configura para los agentes blueprint.
Agente 365 habilitado con S2S El agente adquiere un token solo para la aplicación utilizando la cadena de identidad agéntica (getAgenticApplicationToken + Microsoft Authentication Libraries (MSAL)). Requiere un TokenResolver personalizado y la opción de endpoint S2S. Una instancia de agente registrado no necesita un permiso de observabilidad ni consentimiento de administrador, por lo que este es el enfoque recomendado para agentes de plano técnico configurados con a365 setup all.
Motor personalizado con OBO El agente obtiene un token de usuario mediante Azure Bot OAuth, con alcance limitado a la API de observabilidad. Requiere una conexión personalizadaTokenResolver, una conexión OAuth de Azure Bot y el permiso delegado Agent365.Observability.OtelWrite con consentimiento del administrador.
Motor personalizado con S2S El agente adquiere un token de solo aplicación mediante credenciales de cliente. Requiere un TokenResolver personalizado. El registro de aplicaciones debe ser una aplicación estándar (no agentica). Los registros estándar de aplicaciones no son instancias de agente registrado, así que necesitan el Agent365.Observability.OtelWrite permiso de la aplicación con el consentimiento del administrador.

Agente 365 habilitado mediante OBO

Los agentes habilitados para el agente 365 reciben solicitudes con identidad agente (agenticAppId, agenticUserId) desde la plataforma del Agente 365. Al usar OBO, la distribución integrada AgenticTokenCache gestiona la adquisición automática de tokens en la ruta delegada, así que no necesitas un resolver de tokens personalizado.

Prerrequisitos

  • Registro de la app Microsoft Entra: Un principal de servicio (registro de la app) con ID de cliente, secreto del cliente e Id. de inquilino.

  • Permisos delegados de API: Añadir Agent365.Observability.OtelWrite (delegado) y conceder el consentimiento del administrador. El a365 setup all comando no añade este permiso para los agentes de blueprint. Un administrador global puede añadirlo y conceder el consentimiento del administrador ejecutando el siguiente comando:

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

    Para más opciones, incluido el Centro de administración Microsoft Entra, consulta Grant the permission.

Tip

Para evitar el permiso de observabilidad delegado y el consentimiento del administrador, utiliza Agent 365 habilitado con S2S para los agentes blueprint configurados con a365 setup all.

Configuración

En cada turno, el agente llama a la función RegisterObservability con el contexto del turno. La memoria caché integrada usa el token delegado del usuario del controlador AgenticUserAuthorization para realizar un intercambio OBO y adquirir un token con el ámbito de Agent365.Observability.OtelWrite.

Para obtener instrucciones de configuración completas, como paquetes, configuración y ejemplos de código, consulte Caché de tokens agente con aplicaciones de Agent Framework.

Agente 365 habilitado con S2S

Los agentes habilitados para Agent 365 también pueden usar la autenticación S2S (de servicio a servicio) en lugar de OBO. Este camino se recomienda para agentes de blueprint configurados con a365 setup all. El agente obtiene un token mediante su propia identidad del principal de servicio a través de una cadena de identidad agéntica de dos pasos:

  1. Llama getAgenticApplicationToken(tenantId, agentId) para obtener un token de credenciales de cliente con la ruta de Identidad Gestionada Federada (FMI).
  2. Llama a MSAL acquireTokenForClient con el token de la aplicación como clientAssertion y el ámbito api://9b975845-388f-4429-889e-eab1ef63949c/.default.

Note

La identidad administrada federada (FMI) es una arquitectura en la que una identidad administrada participa en la federación de identidades de carga de trabajo a través de credenciales de identidad federada, lo que permite el intercambio de tokens y la autenticación sin secretos en función de las relaciones de confianza entre identidades.

La distribución OpenTelemetry de Microsoft utiliza por defecto la ruta delegada, por lo que debes proporcionar un TokenResolver personalizado y establecer la opción del endpoint S2S para tu lenguaje, como UseS2SEndpoint = true, useS2SEndpoint: true o a365_use_s2s_endpoint=True.

Prerrequisitos

  • Registro de Agent 365: La instancia del agente debe estar registrada en Agent 365. El a365 setup all comando registra instancias de agentes blueprint. Crear una identidad Microsoft Entra por sí sola no registra una instancia de agente.
  • Credencial Blueprint para el intercambio FMI: Utiliza un secreto de cliente blueprint o una identidad gestionada para obtener el token Federated Managed Identity (FMI) para api://AzureADTokenExchange/.default, y luego cámbialo por el token de Observability de la instancia del agente.
  • Coincidencia de valores de identidad: El ID del agente y el ID del tenant en baggage y la URL de exportación deben coincidir con el ID de cliente de la instancia del agente y el tenant en el token solo de la app.

Note

Una instancia de agente registrada en Agent 365 puede exportar en la ruta S2S con un token solo de aplicación que Agent365.Observability.OtelWrite no tiene ningún papel. No necesita un permiso de observabilidad ni el consentimiento del administrador. Las identidades no registradas, incluidos los registros estándar de aplicaciones, siguen necesitando el Agent365.Observability.OtelWrite rol de la app y el consentimiento del administrador.

Paso 1: Configuración del entorno

En los ejemplos de código siguientes se muestra cómo establecer la conexión necesaria, el inquilino, las credenciales de cliente y la configuración del entorno de exportador de observabilidad antes de habilitar el flujo de token S2S personalizado.

No se necesita ningún controlador AgenticUserAuthorization. S2S usa la cadena manual de identidad agéntica (get_agentic_application_token + MSAL acquire_token_for_client) para obtener un token con alcance para el recurso de observabilidad.

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

Paso 2: Configurar la distribución con un resolvedor de tokens personalizado

Los siguientes ejemplos muestran cómo habilitar la exportación de Agent 365 y registrar un TokenResolver personalizado para que el exportador pueda recuperar tokens S2S para cada agente e inquilino.

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

Paso 3: Adquirir y almacenar en caché el token S2S

Con cada mensaje entrante, obtenga el token S2S a través de la cadena de identidad agéntica y almacénelo en caché para el resolvedor.

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

Importante

El flujo manual de dos pasos (get_agentic_application_token + MSAL acquire_token_for_client) es necesario para S2S. AgenticUserAuthorization.get_token() devuelve un token con ámbito de 5a807f24-.../.default (Bot Framework), no el recurso api://9b975845-.../.default de observabilidad : el punto de conexión S2S lo rechaza con 401 InvalidAudience.

  • Usa context.activity.get_agentic_instance_id() y get_agentic_tenant_id() para leer el agente y el inquilino a partir de la actividad (lee desde recipient según la convención del SDK).
  • Adquiera y almacene en caché el token S2S antes de crear intervalos. Puede que el BatchSpanProcessor del exportador se vacíe antes de que termine el controlador: si el token aún no está almacenado en caché, la exportación falla.
  • Envuelve todos los ámbitos de permisos de A365 en BaggageBuilder para que el exportador sepa para qué agente y tenant debe resolver los tokens. Sin equipaje, los intervalos se quitan silenciosamente con "No se encuentran intervalos con la identidad del inquilino o agente encontrado".

Importante

Antes de que tu resolver devuelva y almacene en caché el token final de Observabilidad:

  • Valida que el token sea solo para la aplicación. Acepta idtyp=app. Si idtyp está ausente, acepta solo un token que tenga un claim roles no vacío o un claim oid no vacío igual a sub. Rechaza tokens que tengan una scp declaración u otro idtyp valor.
  • Comprueba que aud sea 9b975845-388f-4429-889e-eab1ef63949c o api://9b975845-388f-4429-889e-eab1ef63949c, y que el token no esté caducado.
  • Nunca envíes la aserción intermedia de blueprint, un token de blueprint o un token de usuario u OBO a Observability.
  • Almacena en caché el token final y refrescalo antes de que caduque. El exportador llama al solucionador una vez para cada identidad de tenant y agente en cada lote de exportación.

Para implementaciones completas que usan la distro, consulte los ejemplos de Agent 365 para Node.js, Python y .NET.

Motor personalizado mediante OBO

Los agentes de motor personalizado usan registros de aplicaciones estándar con conexiones OAuth de Azure Bot, no la cadena de identidad agéntica. Mediante el uso de OBO, el agente obtiene un token de usuario a través de Azure Bot OAuth que ya está en el ámbito de la API de observabilidad de A365 mediante Bot Framework Token Service. Una sola llamada a getToken o GetTurnTokenAsync devuelve el token con el ámbito correcto, por lo que no necesitas exchangeToken.

Prerrequisitos

Registro de la app Microsoft Entra con permisos delegados de API. Añadir Agent365.Observability.OtelWrite (Delegado) y conceder el consentimiento de administrador.

Importante

El agentId de la caché de tokens debe coincidir con el identificador de cliente del registro de la aplicación, no con el agenticAppId de la actividad, que no existe para los agentes de motor personalizados. La URL de exportación incluye agentId, y una discrepancia provoca un error HTTP 403.

Paso 1: Entorno y configuración de la aplicación

Los ejemplos siguientes muestran cómo configurar tu aplicación y el entorno de ejecución, incluidos los valores de conexión del servicio, la configuración del tenant y del cliente, y las asignaciones de autorización necesarias.

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

Importante

load_configuration_from_env convierte todas las claves de las variables de entorno a mayúsculas. El nombre del manejador pasa a ser OBOCONNECTIONPROFILE y debe referirse a él respetando exactamente las mayúsculas y minúsculas en las llamadas a auth_handlers y get_token(). La ausencia de TYPE causa Auth handler ... not recognized or not configured en tiempo de ejecución.

Paso 2: Configurar la distribución para OBO

En los ejemplos siguientes se muestra cómo habilitar la exportación de Agent 365, mantener el exportador en el punto de conexión OBO y registrar un TokenResolver personalizado que devuelve tokens delegados durante la exportación.

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

El modo OBO requiere jwt_authorization_middleware en aiohttpApplication (valida el JWT entrante (JSON Web Token) de Bot Framework). La ruta de acceso del emulador o S2S no debe incluir este middleware.

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

Paso 3: Adquisición del token de OBO

En los ejemplos siguientes se muestra cómo solicitar un token de OBO delegado desde la conexión OAuth de bot configurada Azure y, a continuación, almacenarla en caché por cliente de aplicación e inquilino para el exportador.

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

Importante

Requisito previo del portal Azure: Establezca los Scopes de la conexión OAuth de Azure Bot denominada oboConnectionProfile en api://9b975845-388f-4429-889e-eab1ef63949c/Agent365.Observability.OtelWrite. Sin esta configuración, el token se limita a la audiencia propia del bot (api://botid-...) y se produce un error en la exportación con HTTP 401 InvalidAudience.

Note

AGENT_APP.auth.get_token() devuelve directamente el token con el ámbito correcto; no es necesario llamar a exchange_token(). Bot Framework Token Service gestiona el intercambio de OBO cuando el ámbito de la conexión de OAuth tiene como destino el recurso de observabilidad A365.

Motor personalizado con S2S

Los agentes de motores personalizados pueden usar S2S (credenciales de cliente) para obtener un token solo de aplicación utilizando las credenciales de conexión de servicio. Este método utiliza credenciales estándar de cliente MSAL: no necesita una cadena de identidad agentic.

Prerrequisitos

  • Registro de la aplicación Microsoft Entra: El registro de la aplicación debe ser un motor personalizado (estándar). Los registros de aplicaciones con Agent 365 habilitado no pueden usar solo client_credentials para el recurso de observabilidad (AADSTS82001).
  • Permisos de aplicación: Añadir Agent365.Observability.OtelWrite (Aplicación, no Delegada) y conceder el consentimiento del administrador. Los registros estándar de aplicaciones no son instancias de agente registradas en Agent 365, así que necesitan este rol de aplicación en la ruta S2S.

Importante

El agentId que uses para el almacenamiento en caché debe ser el de ServiceConnection ClientId La dirección URL de exportación es /observabilityService/tenants/{tenantId}/otlp/agents/{agentId}/traces : una falta de coincidencia provoca HTTP 403.

Paso 1: Entorno y configuración de la aplicación

Los ejemplos siguientes muestran cómo configurar tu aplicación y el entorno de ejecución, incluidos los valores de conexión del servicio, la configuración del tenant y del cliente, y las asignaciones de autorización necesarias.

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

Paso 2: Configurar la distribución para S2S

Los siguientes ejemplos muestran cómo habilitar la exportación de Agent 365, configurar el exportador para que use el punto de conexión S2S y registrar un TokenResolver personalizado para buscar tokens durante la exportación.

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

Paso 3: Adquisición del token de S2S

En los ejemplos siguientes se muestra cómo solicitar un token de acceso de solo aplicación para el recurso de observabilidad mediante las credenciales de conexión de servicio y, a continuación, almacenarlo en caché por agente e inquilino para el exportador.

# 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

Paso 4: Configurar baggage para la exportación de spans

El exportador Agent365 requiere baggage (ID del tenant e ID de agente) en el contexto de span. Sin este bagaje, el exportador suelta spans y registra el mensaje 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])