Настройка аутентификации наблюдаемости

Экспортер Agent 365 требует токен-резолвера для аутентификации при экспорте телеметрии. Это руководство охватывает настройку агентов, построенных с Пакет SDK агентов Microsoft 365, охватывая как агентов с поддержкой Agent 365, так и кастомных движков по .NET, Python и Node.js.

Для установки дистрибутивов, общей конфигурации и сценариев не-агентных SDK см. Microsoft OpenTelemetry Distro.

Обзор

Существует четыре сценария аутентификации, в зависимости от типа вашего агента и способа приобретения токенов. Для получения токенов можно использовать On-Behalf-Of flow (OBO) или Service-to-Service (S2S). Выберите сценарий, который соответствует вашей схеме:

Сценарий Описание
Агент 365 с поддержкой OBO Встроенный AgenticTokenCache дистрибутив автоматически управляет получением токенов. Кастомный резолвер не требуется. Это рекомендованный подход для агентов с поддержкой Agent 365.
Агент 365 с поддержкой S2S Агент получает токен с помощью цепочки идентификации агента (getAgenticApplicationToken + Microsoft Authentication Libraries (MSAL)). Требуется кастомный TokenResolver. Используйте этот подход, когда OBO недоступен или нужны токены только для приложения.
Кастомный движок с использованием OBO Агент получает пользовательский токен через Azure Bot OAuth, ограниченный на API наблюдаемости. Требуется кастомный TokenResolver и Azure Bot OAuth.
Пользовательский движок с использованием S2S Агент приобретает токен только для приложения, используя учетные данные клиента. Требуется кастомный TokenResolver. Регистрация приложения должна быть стандартным (не агентским) приложением.

Агент 365 с поддержкой OBO

Агенты с поддержкой Agent 365 получают запросы с агентной идентичностью (agenticAppId, agenticUserId) от платформы Agent 365. В OBO встроенный AgenticTokenCache дистрибутив автоматически обрабатывает получение токенов: не требуется пользовательский резолвер токенов.

Необходимые условия

  • Регистрация приложения Entra : принципал сервиса (регистрация приложения) с идентификатором клиента, секретом клиента и идентификатором арендатора
  • Делегированные права API : Add Agent365.Observability.OtelWrite (Delegated), предоставление согласия администратора. Подробные шаги смотрите в разделе «Предоставление разрешения».

Настройка

В каждом ходу ваш агент вызывает RegisterObservability функцию с контекстом хода. Встроенный кэш использует делегированный пользователям токен от AgenticUserAuthorization обработчика для проведения OBO-обмена, приобретая токен с Agent365.Observability.OtelWriteобластью действия .

Полные инструкции по установке, включая пакеты, конфигурацию и примеры кода, см. Кэш агентных токенов с приложениями Agent Framework.

Агент 365 с поддержкой S2S

Агенты с поддержкой агента 365 также могут использовать аутентификацию S2S (service-to-service) вместо OBO. Агент приобретает токен, используя собственную принципальную идентичность сервиса через двухступенчатую агентную цепочку идентичности:

  1. getAgenticApplicationToken(tenantId, agentId) : учетные данные клиента + путь Федеративной управляемой идентичности (FMI)
  2. MSAL acquireTokenForClient с токеном приложения как clientAssertion и областью применения api://9b975845-388f-4429-889e-eab1ef63949c/.default

Примечание.

Федеративная управляемая идентичность (FMI) — это архитектура, в которой управляемая идентичность участвует в федерации рабочей нагрузки с помощью федеративных идентификаторов, обеспечивая обмен токенами и бессекретную аутентификацию на основе доверительных отношений между идентичностями.

Вы должны предоставить кастомный TokenResolver и набор UseS2SEndpoint = true.

Необходимые условия

  • Регистрация приложения Entra : принципал сервиса (регистрация приложения) с идентификатором клиента, секретом клиента и идентификатором арендатора

  • Разрешения API приложений: Add Agent365.Observability.OtelWrite (Application), предоставление согласия администратора

  • Agent365.Observability.OtelWrite роль приложения : Принципал сервиса агента должен иметь OtelWrite роль, назначенную на ресурсе наблюдаемости Agent365. Используйте CLI Agent 365:

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

    Примечание.

    Распространение роли может занять несколько минут. Ожидаются начальные ошибки 401 или 403 от экспортной конечной точки в этот период.

Шаг 1: Настройка среды

Следующие примеры кода показывают, как настроить необходимые настройки соединения, арендатора, учетных данных клиента и среды экспортера наблюдаемости перед включением пользовательского потока токена S2S.

Куратор не AgenticUserAuthorization нужен. S2S использует ручную агентную цепочку идентификации (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: Настройте дистрибутив с помощью кастомного резолвера токенов

Следующие примеры показывают, как включить экспорт Agent 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

В каждом входящем сообщении получайте токен 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])

Это важно

Для S2S требуется ручной двухступенчатый поток (get_agentic_application_token + MSAL acquire_token_for_client). AgenticUserAuthorization.get_token() возвращает токен с областью действия ( 5a807f24-.../.default Bot Framework), а не ресурс api://9b975845-.../.default наблюдаемости: конечная точка S2S отклоняет его с помощью 401 InvalidAudience.

  • Используйте context.activity.get_agentic_instance_id() и get_agentic_tenant_id() чтобы читать агента и арендатора из активности (читается по recipient SDK-конвенции).
  • Заберите и кэшируйте токен S2S перед созданием спайпов. У экспортера BatchSpanProcessor может промыться до завершения обработчика: если токен ещё не кэширован, экспорт не проходит.
  • Оберните все области BaggageBuilder A365, чтобы экспортер знал, для какого агента и арендатора разрешать токены. Без багажа спанты тихо сбрасывают с надписью «Ни одного варианта с идентификацией арендатора/агента не найдено».

Кастомный движок с использованием OBO

Пользовательские агенты движка используют стандартные регистрации приложений с соединениями Azure Bot OAuth, а не в цепочке агентной идентичности. Используя OBO, агент получает пользовательский токен через Azure Bot OAuth, который уже связан с API наблюдаемости A365 через Bot Framework Token Service. Одиночный getToken или GetTurnTokenAsync звонок возвращает правильно обозначенный токен, так что вам не нужен exchangeToken.

Необходимые условия

Регистрация приложения Entra с делегированными правами API. Добавить Agent365.Observability.OtelWrite (делегировано) и дать согласие администратора

Это важно

agentId Кэш в токене должен совпадать с идентификатором клиента регистрации приложения — а не с agenticAppIdID активности, которого нет для кастомных агентов движка. 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

Это важно

load_configuration_from_env заглавные буквы — все ключи переменных среды. Имя обработчика появляется OBOCONNECTIONPROFILE , и вы должны ссылаться на него именно с этим корпусом и auth_handlersget_token() вызывает. Отсутствующие TYPE причины Auth handler ... not recognized or not configured во время выполнения.

Шаг 2: Настройте дистрибутив для OBO

Следующие примеры показывают, как включить экспорт из Agent 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,
)

Примечание.

Режим OBO требует jwt_authorization_middleware на aiohttpApplication (валидирует входящий JWT (JSON Web Token) из Bot Framework). Путь S2S/эмулятора не должен включать этот промежуточный ПО.

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

Шаг 3: Приобрести токен OBO

Следующие примеры показывают, как запросить делегированный OBO-токен у настроенного OAuth соединения Azure Bot, а затем кэшировать его по клиенту приложения и арендатору для экспортера.

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

Это важно

портал Azure предварительное условие: Соединение Azure Bot OAuth с названием oboConnectionProfile должно иметь Scopes настроенное на api://9b975845-388f-4429-889e-eab1ef63949c/Agent365.Observability.OtelWrite. Без этой настройки токен распределяется на собственную аудиторию бота (api://botid-...), и экспорт с помощью HTTP 401 InvalidAudienceзаканчивается неудачей.

Примечание.

AGENT_APP.auth.get_token() возвращает токен с правильным диапазоном напрямую — вызов не exchange_token() требуется. Служба токенов Bot Framework обрабатывает обмен OBO, когда область соединения OAuth нацелена на ресурс наблюдаемости A365.

Пользовательский движок с использованием S2S

Пользовательские агенты движка могут использовать S2S (учетные данные клиента) для получения токена только для приложения, используя учетные данные подключения к сервису. Этот метод использует стандартные учетные данные клиента MSAL — не требуется цепочка идентификации агентов.

Необходимые условия

  • Azure регистрация приложения AD : Должно быть приложение custom engine (стандартное). Регистрации приложений с поддержкой агента 365 не могут использовать plain client_credentials для ресурса наблюдаемости (AADSTS82001).
  • Права заявки : Add Agent365.Observability.OtelWrite (Application, not Delegated) и предоставление согласия администратора.

Это важно

Для agentIdкэширования должны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

Следующие примеры показывают, как включить экспорт Agent 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: Настройте багаж для экспорта spanов

Экспортер Agent365 требует, чтобы багаж (идентификатор арендатора и идентификатор агента) был установлен в контексте span. Без него экспортер бесшумно сбрасывает суспензии с сообщением 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])