可观测性身份验证设置

代理 365 导出程序要求在导出遥测时使用令牌解析程序进行身份验证。 本指南介绍如何设置使用 Microsoft 365 智能体 SDK 构建的代理,涵盖支持 Agent 365 的代理以及基于 .NET、Python 和 Node.js 的自定义引擎代理。

有关发行版安装、常规配置和非代理 SDK 方案,请参阅 Microsoft OpenTelemetry 发行版。

Overview

有四种身份验证方案,具体取决于代理类型及其获取令牌的方式。 令牌获取可使用代表流 (OBO) 或服务对服务 (S2S)。 选择与设置匹配的方案:

Scenario Description
通过 OBO 启用 Agent 365 发行版内置的 AgenticTokenCache 会自动处理委托路由上的令牌获取。 不需要自定义解析程序。 此路径需要 Agent365.Observability.OtelWrite 委托权限和管理员同意,而 a365 setup all 蓝图智能体未对此进行配置。
使用 S2S 启用 Agent 365 代理通过代理身份链(getAgenticApplicationToken + Microsoft 认证库(MSAL))获取仅限应用的令牌。 需要自定义 TokenResolver 和S2S端点选项。 注册代理实例不需要可观察性权限或管理员同意,因此这是蓝图代理 a365 setup all设置时推荐的方法。
使用 OBO 的自定义引擎 代理通过 Azure Bot OAuth 获取用户令牌,该令牌的范围限定为可观测性 API。 需要自定义TokenResolver、Azure Bot OAuth 连接,以及管理员同意的委托Agent365.Observability.OtelWrite权限。
使用 S2S 的自定义引擎 智能体使用客户端凭据获取仅限应用的令牌。 需要自定义 TokenResolver。 应用注册必须是标准(非代理)应用。 标准应用注册并非已注册的智能体实例,因此需要 Agent365.Observability.OtelWrite 应用程序权限及管理员同意。

通过 OBO 启用 Agent 365

启用 Agent 365 的智能体会从 Agent 365 平台接收带有智能体身份 (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 管理中心,请参见“授予权限”。

小窍门

若要避免使用委托的可观测性权限和管理员同意,请对已配置 a365 setup all 的蓝图智能体使用通过 S2S 启用 Agent 365。

Setup

在每一轮中,你的代理都会调用 RegisterObservability 函数,并传入当前轮次的上下文。 内置缓存使用来自 AgenticUserAuthorization 处理程序的用户委托令牌执行 OBO 交换,获取作用域为 Agent365.Observability.OtelWrite 的令牌。

有关完整的安装说明,包括包、配置和代码示例,请参阅 Agent Framework 应用的 Agentic 令牌缓存。

使用 S2S 启用 Agent 365

启用了 Agent 365 的代理还可以使用 S2S(服务间)身份验证,而不是 OBO。 对于使用 a365 setup all 配置的蓝图智能体,建议采用此路径。 智能体通过两步智能体身份链使用其自身的服务主体身份获取令牌:

  1. 呼叫 getAgenticApplicationToken(tenantId, agentId) 以获取带有联邦托管身份(FMI)路径的客户端凭证令牌。
  2. 用应用令牌api://9b975845-388f-4429-889e-eab1ef63949c/.default和范围acquireTokenForClient调用 MSALclientAssertion。

Note

联合托管标识(FMI)是一种体系结构,其中托管标识通过联合标识凭据参与工作负荷标识联合,基于标识之间的信任关系启用令牌交换和无机密身份验证。

Microsoft OpenTelemetry 发行版默认使用委派路由,因此你必须为你的语言提供自定义TokenResolver并设置 S2S 端点选项,例如 UseS2SEndpoint = true、 useS2SEndpoint: true或 a365_use_s2s_endpoint=True。

先决条件

  • Agent 365 注册:代理实例必须在 Agent 365 中注册。 该 a365 setup all 命令用于注册蓝图代理实例。 仅创建 Microsoft Entra 身份并不会注册代理实例。
  • FMI 交换的蓝图凭据:使用蓝图客户端密钥或托管身份获取 api://AzureADTokenExchange/.default 的联合托管身份 (FMI) 令牌,然后将其兑换为智能体实例的可观测性令牌。
  • 身份值匹配:baggage 中的代理 ID 和租户 ID 以及导出 URL 必须与应用专用令牌中的代理实例客户端 ID 和租户相匹配。

Note

已注册到 Agent 365 的智能体实例可通过 S2S 路径,使用不包含 Agent365.Observability.OtelWrite 角色的“仅应用”令牌进行导出。 它不需要 Observability 权限或管理员许可。 未注册身份(包括标准应用注册)仍然需要 Agent365.Observability.OtelWrite 应用角色和管理员同意。

步骤 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:使用自定义令牌解析程序配置发行版

以下示例演示如何启用代理 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])

Important

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() 从活动读取智能体和租户(根据 SDK 约定从 recipient 读取)。
  • 在创建范围 之前 获取和缓存 S2S 令牌。 导出器的 BatchSpanProcessor 可能会在处理程序完成之前刷新:如果令牌尚未缓存,导出就会失败。
  • 将所有 A365 作用域整合在 BaggageBuilder 中,以便导出器知道应为哪个智能体和租户解析令牌。 若未包含这些信息,跨度将被静默丢弃,并显示“未找到具有租户/智能体身份的跨度”。

Important

在解析器返回并缓存最终的 Observability 令牌之前:

  • 验证该令牌仅限应用程序。 同意 idtyp=app。 如果缺少 idtyp,则仅接受具有非空 roles 声明或非空 oid 声明(且该声明值等于 sub)的令牌。 拒绝具有 scp 声明或其他 idtyp 值的令牌。
  • 检查 aud 是否为 9b975845-388f-4429-889e-eab1ef63949c 或 api://9b975845-388f-4429-889e-eab1ef63949c,并且令牌没有过期。
  • 切勿将中间蓝图断言、蓝图令牌、用户或 OBO 令牌发送给 Observability。
  • 缓存最后一个令牌并在它过期前刷新。 导出器在每个导出批次中,针对每个租户和代理身份调用一次解析器。

有关使用该发行版的完整实现,请参见 Agent 365 的 Node.js、Python 和 .NET 示例。

基于 OBO 的自定义引擎

自定义引擎智能体使用带有 Azure Bot OAuth 连接的标准应用注册,而非智能体身份链。 通过 OBO,代理可通过 Azure Bot OAuth 获取用户令牌,该令牌的作用域已由 Bot Framework 令牌服务限定为 A365 可观测性 API。 单个 getToken 或 GetTurnTokenAsync 调用返回正确的作用域令牌,因此不需要 exchangeToken。

先决条件

Microsoft Entra 应用注册,使用委托 API 权限。 添加 Agent365.Observability.OtelWrite(委托)并授予管理员同意。

Important

令牌缓存中的 agentId 必须与应用注册中的 客户端 ID 匹配,而不是与活动的 agenticAppId 匹配;对于自定义引擎代理,这个 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 portal 先决条件:将名为 oboConnectionProfile 的 Azure Bot OAuth 连接的 Scopes 设置为 api://9b975845-388f-4429-889e-eab1ef63949c/Agent365.Observability.OtelWrite。 如果没有此设置,令牌的范围限定为机器人自己的受众(api://botid-...),导出失败并出现 HTTP 401 InvalidAudience。

Note

AGENT_APP.auth.get_token() 直接返回正确的作用域令牌——无需 exchange_token() 调用。 当 OAuth 连接范围面向 A365 可观测性资源时,Bot Framework 令牌服务将处理 OBO 交换。

基于 S2S 的自定义引擎

自定义引擎代理可以使用S2S(客户端凭证)通过服务连接凭证获取仅应用的令牌。 此方法使用标准的 MSAL 客户端凭据 — 无需智能体身份链。

先决条件

  • Microsoft Entra 应用注册:应用注册必须是自定义引擎(标准)应用。 启用 Agent 365 的应用注册不能在可观测性资源 (AADSTS82001)中使用纯 client_credentials。
  • 应用权限:添加 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])