使用直接 OTel 集成代理可观测性

学习如何通过 OpenTelemetry (OTLP/HTTP+JSON) 直接发送遥测数据,将代理可观察性集成到 Agent 365。 这种方法帮助无法使用 Microsoft OpenTelemetry 发行版的代理高效且安全地发送遥测数据。 开始前,阅读 Agent 365的可观测性概念 ,了解模型、认证流程以及数据出现在哪里。

Important

直接 OTel 路径是异常,而不是默认值。 只有在你已经有 OpenTelemetry 流水线、你的框架无法使用 Microsoft OpenTelemetry 发行版,或者你的代理是用该发行版尚不支持的语言(比如 Java)编写的情况下,才使用它。 对于其他人,建议的路径是 Microsoft OpenTelemetry Distro,它跨 Agent 365、Microsoft Foundry、Azure Monitor 等提供统一的可观测性 SDK。 已弃用的Agent 365可观测性SDK仍适用于现有集成,但不建议用于新集成。

先决条件

在任何遥测数据传输开始之前,请确保以下配置均已设置妥当。

谁 What
租户管理员 注册 Agent 365,并为您的代理应用授予任何所需的同意。 请参阅 Agent 365 入门。 如果没有符合条件的租户,即使响应中的 200 OK 表明这些跨度已被拒绝,数据引入仍可能返回 results。
租户管理员 分配 Microsoft 365 E7 或 Microsoft Agent 365 许可证给租户中的至少一位用户。 存在的 SKU 是不够的。 分配给用户会启动启用引入的Defender后端工作流。 如果未分配许可证,数据引入在拒绝这些跟踪跨度的同时可能会返回 200 OK。
租户管理员 当你的路线需要 Agent365.Observability.OtelWrite 时,授予租户同意。 通过 S2S 途径导出的注册代理实例不需要 Observability 同意。 委派路由导出和未注册的 S2S 身份则会这样做。 请参阅 授予代理访问 Microsoft 365 资源的权限。 未经必要同意,令牌会在没有角色或作用域的情况下被发放,请求返回 403。
开发团队 注册你的应用(标准的 Microsoft Entra 应用或蓝图),并完成 Agent 365 的蓝图派生代理实例注册。 参见 代理身份。
开发团队 只有在路由需要时才添加 Agent365.Observability.OtelWrite :应用角色用于未注册的S2S身份,以及委派范围用于OBO。 对于仍需委派导出权限的蓝图,请参见配置可继承权限。 与 Agent 365 入门支持团队协调,以启用该权限。

身份验证范例

所有四种方案都使用标准的 Microsoft Entra 令牌终结点:

领域 价值
令牌终结点 https://login.microsoftonline.com/{your-tenant-id}/oauth2/v2.0/token
资源(aud 在返回的令牌中) 9b975845-388f-4429-889e-eab1ef63949c (也接受 api://9b975845-388f-4429-889e-eab1ef63949c)
S2S 范围 9b975845-388f-4429-889e-eab1ef63949c/.default
OBO 范围 9b975845-388f-4429-889e-eab1ef63949c/Agent365.Observability.OtelWrite

以下示例为便于说明,展示了原始 HTTP 内容。 在生产环境中,可以使用 Microsoft。Identity.Web 或其他 MSAL 库,这些库负责令牌刷新和缓存。

我需要哪种食谱?

我的应用模型 我的 OAuth 流程 跳转到
标准Microsoft Entra应用注册 S2S(客户端凭证) S2S、标准Microsoft Entra应用
标准Microsoft Entra应用注册 OBO(委派) OBO,标准 Microsoft Entra 应用程序
从蓝图派生的代理身份 S2S(客户端凭证) S2S,源自蓝图的代理标识
从蓝图派生的代理身份 OBO /AI 团队成员 OBO,基于蓝图派生的代理身份标识

S2S,标准 Microsoft Entra 应用

向租户的令牌端点发送带有 grant_type=client_credentials 的 POST 请求。 使用客户端密码、证书(已签名的 JWT 断言)或托管标识或联合凭据对应用进行身份验证。

POST https://login.microsoftonline.com/{your-tenant-id}/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded

client_id={your-app-id}
&scope=9b975845-388f-4429-889e-eab1ef63949c%2F.default
&client_secret={secret}
&grant_type=client_credentials

返回的令牌包含appid/azp = {your-app-id}、roles包含Agent365.Observability.OtelWrite和 。aud = 9b975845-... 在 /observabilityService/.../traces 路由上使用它。 标准应用注册并非 Agent 365 注册的代理实例,因此令牌需要此应用角色。

对于基于证书的身份验证,请替换为 client_secret={secret}client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer&client_assertion={signed-jwt}。

S2S,蓝图衍生代理身份

代理标识本身没有认证信息。 代理标识蓝图持有凭据(托管标识 FIC、证书或客户端密码),并通过两步交换代表其子代理标识生成令牌。 有关详细信息,请参阅 自治应用 OAuth 流。

  1. 该蓝图进行身份验证并获取联合身份交换令牌 T1:

    • {blueprint-credential} 是蓝图配置中指定的 MSI 令牌、经证书签名的 JWT 或机密交换令牌断言,具体取决于蓝图配置。
    POST https://login.microsoftonline.com/{your-tenant-id}/oauth2/v2.0/token
    Content-Type: application/x-www-form-urlencoded
    
    client_id={blueprint-app-id}
    &scope=api%3A%2F%2FAzureADTokenExchange%2F.default
    &fmi_path={agent-identity-app-id}
    &client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer
    &client_assertion={blueprint-credential}
    &grant_type=client_credentials
    
  2. 代理身份用 T1 换取 Agent 365 可观测性资源令牌:

    POST https://login.microsoftonline.com/{your-tenant-id}/oauth2/v2.0/token
    Content-Type: application/x-www-form-urlencoded
    
    client_id={agent-identity-app-id}
    &scope=9b975845-388f-4429-889e-eab1ef63949c%2F.default
    &client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer
    &client_assertion={T1}
    &grant_type=client_credentials
    
    • 返回的标记有 appid = azp/{agent-identity-app-id},无 scp,且 aud = 9b975845-...。 对于已注册到 Agent 365 的代理实例,令牌不需要 Agent365.Observability.OtelWrite 角色。
    • 如果代理实例未在 Agent 365 中注册,服务会拒绝同一个无角色的仅应用令牌,并显示 403 insufficient_scope。 未注册身份需要 Agent365.Observability.OtelWrite应用角色。
    • 在 /observabilityService/.../traces 路由上使用此令牌。
    • URL {agentId} 对应的是 代理身份 appId,而不是蓝图 appId。

OBO,标准 Microsoft Entra 应用

从上游调用方(Bearer 或 PFAT)接收用户的传入令牌 Tc ,然后交换它:

POST https://login.microsoftonline.com/{your-tenant-id}/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded

client_id={your-app-id}
&scope=9b975845-388f-4429-889e-eab1ef63949c%2FAgent365.Observability.OtelWrite
&client_secret={secret}
&grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
&assertion={Tc}
&requested_token_use=on_behalf_of

对于证书身份验证,请将 client_secret={secret} 替换为与 S2S 中相同的 client_assertion_type + client_assertion 对。

返回的令牌包含appid/azp = {your-app-id}、scp包含Agent365.Observability.OtelWrite和 。aud = 9b975845-... 在 /observability/.../traces 路由上使用它。 同时返回刷新令牌;缓存并重复使用它,而不是在每次调用时重新运行交换。

OBO,基于蓝图衍生的代理身份(包括AI队友)

代理流有三个主要步骤。 有关详细信息,请参阅 代理 OAuth 流:代表流。

  1. 接收用户令牌 Tc。 对于 AI 协作者,此令牌表示该代理自身的用户帐户;否则,它表示人类调用方。

  2. 蓝图进行身份验证并获取 T1,与 S2S 蓝图派生的代理标识流相同。

  3. 代理身份交换 T1 和 Tc 以换取委派资源令牌:

    POST https://login.microsoftonline.com/{your-tenant-id}/oauth2/v2.0/token
    Content-Type: application/x-www-form-urlencoded
    
    client_id={agent-identity-app-id}
    &scope=9b975845-388f-4429-889e-eab1ef63949c%2FAgent365.Observability.OtelWrite
    &client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer
    &client_assertion={T1}
    &grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
    &assertion={Tc}
    &requested_token_use=on_behalf_of
    

返回的令牌具有appid/azp = {agent-identity-app-id},其中scp包含Agent365.Observability.OtelWrite,并表示该代理的用户。 在 /observability/.../traces 路由上使用它。 URL {agentId} 对应的是 代理身份 appId,而不是蓝图 appId。 同时返回刷新令牌;缓存并重复使用它。 委派路线需要委派范围和租户管理员同意。

返回的令牌中的必需声明

S2S 路由 (/observabilityService/...) - 仅限应用的令牌:

索赔 所需的值
aud 9b975845-388f-4429-889e-eab1ef63949c (或 api://9b975845-...)
roles 对于代理365注册代理实例,则不强制。 对于未注册的标识,内容中必须包含 Agent365.Observability.OtelWrite。
appid (v1) 或 azp (v2) 必须与 URL 相同 {agentId}
scp 必须不存在

委托路由(/observability/...) - 用户委托令牌(持有者令牌或 PFAT):

索赔 所需的值
aud 9b975845-388f-4429-889e-eab1ef63949c (或 api://9b975845-...)
scp 必须包含 Agent365.Observability.OtelWrite
appid / azp 必须与 URL 相同 {agentId}

委托路由同时接受 Bearer 和 MSAuth1.0 PFAT 令牌。 直接调用者应使用 Bearer。 如果你不知道你拥有哪一个,请使用 Bearer。

Endpoints

两种路径;请根据 您的服务 如何进行身份验证来选择,而不是根据用户正在执行什么操作:

POST https://agent365.svc.cloud.microsoft/observabilityService/tenants/{tenantId}/otlp/agents/{agentId}/traces?api-version=1   # S2S
POST https://agent365.svc.cloud.microsoft/observability/tenants/{tenantId}/otlp/agents/{agentId}/traces?api-version=1          # OBO

标题

Authorization: Bearer <token>      # or MSAuth1.0 ... for delegated PFAT
Content-Type: application/json

URL 参数

  • {tenantId} - 客户租户的 GUID。 服务器将该值视为权威值。 如果你设置的跨度为 microsoft.tenant.id,但与实际不匹配,服务器将拒绝该请求。
  • {agentId} - 调用应用程序的 appId (也是 OAuth client_id)。 对于从蓝图派生的身份,此值对应的是 代理标识 的 appId,而不是蓝图的 appId。 它必须与您的令牌的 appid 或 azp 声明匹配。
  • api-version=1 -必填。

检查租户资格

采用S2S认证模型的第三方集成可以在启用集成或发送遥测数据前,检查客户租户是否符合Agent 365可观测性资格。 这种检查可以帮助集成避免向目前不符合条件的租户发送遥测数据。

使用 S2S认证中描述的仅应用令牌。 令牌必须包含 Agent365.Observability.OtelWrite 应用角色,且其 tid 声明必须与请求 URL 中的 {tenantId} 匹配。

GET https://agent365.svc.cloud.microsoft/observabilityService/tenants/{tenantId}/eligibility?api-version=1
Authorization: Bearer <access-token>

成功的回复包含资格结果:

{
  "enabled": true
}

处理回复的步骤如下:

Status Meaning 客户端操作
200 OK、enabled: true 租户有资格获得可观测性。 集成可以发送遥测数据。
200 OK、enabled: false 该租户当前暂无资格使用可观测性功能。 不要发送遥测数据。 确认租户满足先 决条件,状态发生变化后再检查。 如果仍然发送遥测数据,摄取端点可以在拒绝这些 span 的同时返回 200 OK。
400 Bad Request {tenantId} 是空的或无效的。 在重试前先纠正租户编号。
401 Unauthorized 访问令牌缺失或无效。 获取Agent 365可观测性资源的有效令牌。
403 Forbidden 该令牌缺少所需的应用程序角色,或者其租户与 {tenantId} 不匹配。 请先更正权限、同意或租户不匹配问题,然后重试。
429 Too Many Requests 来电者已超过资格请求限制。 遵循 Retry-After,并采用退避和抖动进行重试。
503 Service Unavailable 资格无法确定。 该响应没有响应体。 遵循 Retry-After: 30 并重试。 不要把这个回答当作 enabled: false。

请求正文编码

请求体采用标准的 OTLP/HTTP+JSON 格式:一个 ExportTraceServiceRequest,包含 resourceSpans → scopeSpans → spans。 请记住以下详细信息:

  • 发送 traceId (16字节)和 spanId (8字节)为小写十六进制字符串。
  • startTimeUnixNano 和 endTimeUnixNano 是保持 Unix 时代纳秒的 字符串 。
  • kind 是整数OTLP枚举值(例如, 1 对于 INTERNAL)。 status.code是整数枚举(例如,1表示OK,2表示ERROR)。
  • 将所有属性值发送为 stringValue。

响应形状

回复 200 OK 表示365代理已处理请求。 它并不保证每个桥段都被引导到目的地。 检查 partialSuccess 和 results。

该 results 数组报告每个适用目的地的每个跨度的结果:

  • sent - 该跨度已被路由到目标位置。
  • rejected - 该跨度未布线。 reason 字段说明了原因。
  • not_routed - 该跨度的目的地未被选定。 reason 字段说明了原因。

例如,成功路由的 span 可返回如下内容:

{
  "partialSuccess": {
    "rejectedSpans": 0,
    "errorMessage": ""
  },
  "results": [
    {
      "spanId": "0123456789abcdef",
      "sinks": {
        "flashpoint": {
          "status": "sent"
        },
        "sentinel": {
          "status": "sent"
        },
        "esp": {
          "status": "sent"
        }
      }
    }
  ]
}

如果租户没有资格,该请求仍可返回 200 OK。 在这种情况下,results 表明这些区间已被拒绝:

{
  "partialSuccess": {
    "rejectedSpans": 0,
    "errorMessage": ""
  },
  "results": [
    {
      "spanId": "0123456789abcdef",
      "sinks": {
        "flashpoint": {
          "status": "rejected",
          "reason": "tenant_not_licensed"
        },
        "sentinel": {
          "status": "rejected",
          "reason": "tenant_not_licensed"
        },
        "esp": {
          "status": "rejected",
          "reason": "tenant_not_licensed"
        }
      }
    }
  ]
}

对于整个请求的路由决策,即使0显示每个跨度都被拒绝,results仍可保持partialSuccess.rejectedSpans。 不要仅凭 partialSuccess 或 HTTP 状态就认定已被摄入。 字段名称是网络上的 camelCase。 有关遥测数据可能不显示的其他原因,请参见 限制和丢弃条件。

最小可能的请求

最简单的端到端测试发送单个 invoke_agent 范围。 此 span 是进入 Microsoft Defender 的最小主体。

第 1 步。 获取 Bearer 令牌。 对于 S2S,请使用作用域为 9b975845-388f-4429-889e-eab1ef63949c/.default 的客户端凭据(完整指南请参阅 身份验证指南)。

步骤 2。 POST 单个跨度:

TOKEN="$(./get-token.sh)"
TENANT_ID="<customer-tenant-guid>"
AGENT_ID="<your-agent-app-id>"

curl -i -X POST \
  "https://agent365.svc.cloud.microsoft/observabilityService/tenants/${TENANT_ID}/otlp/agents/${AGENT_ID}/traces?api-version=1" \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/json" \
  --data @- <<EOF
{
  "resourceSpans": [{
    "scopeSpans": [{
      "scope": { "name": "my-instrumentation", "version": "1.0.0" },
      "spans": [{
        "traceId": "0102030405060708090a0b0c0d0e0f10",
        "spanId":  "1111111111111111",
        "parentSpanId": "",
        "name": "invoke_agent",
        "kind": 1,
        "startTimeUnixNano": "1736175600000000000",
        "endTimeUnixNano":   "1736175601500000000",
        "status": { "code": 1 },
        "attributes": [
          { "key": "gen_ai.operation.name", "value": { "stringValue": "invoke_agent" } },
          { "key": "gen_ai.agent.id",       "value": { "stringValue": "${AGENT_ID}" } },
          { "key": "gen_ai.agent.name",     "value": { "stringValue": "MyAgent" } },
          { "key": "microsoft.a365.agent.blueprint.id", "value": { "stringValue": "${AGENT_ID}" } },
          { "key": "gen_ai.conversation.id","value": { "stringValue": "conv-001" } },
          { "key": "microsoft.channel.name","value": { "stringValue": "web" } },
          { "key": "user.id",               "value": { "stringValue": "<entra-user-objectid>" } },
          { "key": "client.address",        "value": { "stringValue": "10.1.2.80" } },
          { "key": "server.address",        "value": { "stringValue": "myagent.example.com" } },
          { "key": "server.port",           "value": { "stringValue": "443" } },
          { "key": "gen_ai.input.messages", "value": { "stringValue": "[{\"role\":\"user\",\"content\":\"hi\"}]" } },
          { "key": "gen_ai.output.messages","value": { "stringValue": "[{\"role\":\"assistant\",\"content\":\"hello\"}]" } }
        ]
      }]
    }]
  }]
}
EOF

步骤 3。 期待200 OK,然后按照响应形态描述进行检查results。 确认相关目的地的状态为 sent。

步骤 4. 确认数据确实已落地。 200 OK不代表摄入;关于验证流程,请参见 “验证吞入 ”。 若要改为 POST 已保存的正文文件,请将 --data @- <<EOF ... EOF 替换为 --data @./otlp-request.json。

代理运行示例

Microsoft Teams上的用户询问“西雅图天气怎么样?” 你的智能体调用 GetWeather 函数,要求 LLM 格式化答案,然后作出回复。 该单次运行包含四个区段:

graph TD
    A["<b>invoke_agent</b> · spanId=A · parentSpanId=∅<br/><i>root - the run itself</i>"]
    B["<b>chat</b> · spanId=B · parentSpanId=A<br/><i>LLM picks the tool / formats reply</i>"]
    C["<b>execute_tool</b> · spanId=C · parentSpanId=A<br/><i>the GetWeather call</i>"]
    D["<b>output_messages</b> · spanId=D · parentSpanId=A<br/><i>final reply emitted to the user</i>"]
    A --> B
    A --> C
    A --> D

在每个跨度上设置的全运行范围属性:

Attribute 示例值
traceId 0102030405060708090a0b0c0d0e0f10
gen_ai.conversation.id 19:abc@thread.tacv2
microsoft.session.id session-1234
microsoft.channel.name msteams
gen_ai.agent.id <AGENT_APP_ID>
gen_ai.agent.name WeatherBot
microsoft.a365.agent.blueprint.id <BLUEPRINT_APP_ID>
user.id <entra-user-objectid>
client.address 10.1.2.80
server.address weatherbot.example.com
server.port 443

Important

这些整个运行范围内的属性不会自动传递。 你必须在每个 span 上设置 microsoft.channel.name、microsoft.session.id 和 gen_ai.conversation.id。

范围 A: invoke_agent (根)

{
  "traceId": "0102030405060708090a0b0c0d0e0f10",
  "spanId": "1111111111111111",
  "parentSpanId": "",
  "name": "invoke_agent",
  "kind": 1,
  "startTimeUnixNano": "1736175600000000000",
  "endTimeUnixNano":   "1736175601500000000",
  "status": { "code": 1 },
  "attributes": [
    { "key": "gen_ai.operation.name",   "value": { "stringValue": "invoke_agent" } },
    { "key": "gen_ai.execution.type",   "value": { "stringValue": "HumanToAgent" } },
    { "key": "gen_ai.input.messages",   "value": { "stringValue": "[{\"role\":\"user\",\"content\":\"What's the weather in Seattle?\"}]" } },
    { "key": "gen_ai.output.messages",  "value": { "stringValue": "[{\"role\":\"assistant\",\"content\":\"It's 65F and partly cloudy in Seattle.\"}]" } },
    { "key": "user.email",              "value": { "stringValue": "alice@contoso.com" } }
    /* plus all the run-wide attributes listed above */
  ]
}

区间 B:chat(LLM 调用)

{
  "traceId": "0102030405060708090a0b0c0d0e0f10",
  "spanId": "2222222222222222",
  "parentSpanId": "1111111111111111",
  "name": "chat",
  "kind": 1,
  "startTimeUnixNano": "1736175600200000000",
  "endTimeUnixNano":   "1736175600900000000",
  "status": { "code": 1 },
  "attributes": [
    { "key": "gen_ai.operation.name",      "value": { "stringValue": "chat" } },
    { "key": "gen_ai.request.model",       "value": { "stringValue": "gpt-4o" } },
    { "key": "gen_ai.provider.name",       "value": { "stringValue": "openai" } },
    { "key": "gen_ai.usage.input_tokens",  "value": { "stringValue": "42" } },
    { "key": "gen_ai.usage.output_tokens", "value": { "stringValue": "23" } }
    /* plus all the run-wide attributes */
  ]
}

范围 C: execute_tool

{
  "traceId": "0102030405060708090a0b0c0d0e0f10",
  "spanId": "3333333333333333",
  "parentSpanId": "1111111111111111",
  "name": "execute_tool",
  "kind": 1,
  "startTimeUnixNano": "1736175600950000000",
  "endTimeUnixNano":   "1736175601200000000",
  "status": { "code": 1 },
  "attributes": [
    { "key": "gen_ai.operation.name",      "value": { "stringValue": "execute_tool" } },
    { "key": "gen_ai.tool.name",           "value": { "stringValue": "GetWeather" } },
    { "key": "gen_ai.tool.type",           "value": { "stringValue": "function" } },
    { "key": "gen_ai.tool.call.id",        "value": { "stringValue": "call-001" } },
    { "key": "gen_ai.tool.call.arguments", "value": { "stringValue": "{\"location\":\"Seattle\"}" } },
    { "key": "gen_ai.tool.call.result",    "value": { "stringValue": "{\"tempF\":65,\"condition\":\"partly cloudy\"}" } }
    /* plus all the run-wide attributes */
  ]
}

范围 D: output_messages

{
  "traceId": "0102030405060708090a0b0c0d0e0f10",
  "spanId": "4444444444444444",
  "parentSpanId": "1111111111111111",
  "name": "output_messages",
  "kind": 1,
  "startTimeUnixNano": "1736175601400000000",
  "endTimeUnixNano":   "1736175601500000000",
  "status": { "code": 1 },
  "attributes": [
    { "key": "gen_ai.operation.name",  "value": { "stringValue": "output_messages" } },
    { "key": "gen_ai.output.messages", "value": { "stringValue": "[{\"role\":\"assistant\",\"content\":\"It's 65F and partly cloudy in Seattle.\"}]" } }
    /* plus all the run-wide attributes */
  ]
}

发送遥测数据

使用OTel SDK

大多数合作伙伴通过 OTel SDK 发送追踪数据,而不是使用自行编写的 HTTP 实现。 SDK 会为你处理批处理、重试和 OTLP/HTTP+JSON 编码。 设置导出程序终结点并注入 Authorization 标头。

导出程序终结点是路由 URL 本身,包括查询字符串:

https://agent365.svc.cloud.microsoft/observabilityService/tenants/{tenantId}/otlp/agents/{agentId}/traces?api-version=1

(对于委托路由,请使用 /observability/... 而不是 /observabilityService/...。)

Python

from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter

exporter = OTLPSpanExporter(
    endpoint="https://agent365.svc.cloud.microsoft/observabilityService/tenants/{tenantId}/otlp/agents/{agentId}/traces?api-version=1",
    headers={"Authorization": f"Bearer {token}"},
)

包: opentelemetry-exporter-otlp-proto-http.

Node.js/TypeScript

import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";

const exporter = new OTLPTraceExporter({
  url: "https://agent365.svc.cloud.microsoft/observabilityService/tenants/{tenantId}/otlp/agents/{agentId}/traces?api-version=1",
  headers: { Authorization: `Bearer ${token}` },
});

包: @opentelemetry/exporter-trace-otlp-http.

.NET

using OpenTelemetry.Exporter;

services.AddOpenTelemetry().WithTracing(b => b
    .AddOtlpExporter(o =>
    {
        o.Endpoint = new Uri("https://agent365.svc.cloud.microsoft/observabilityService/tenants/{tenantId}/otlp/agents/{agentId}/traces?api-version=1");
        o.Headers = $"Authorization=Bearer {token}";
        o.Protocol = OtlpExportProtocol.HttpJson;
    }));

包: OpenTelemetry.Exporter.OpenTelemetryProtocol.

手动 HTTP

如果不能或不想使用 OTel SDK,请自行生成 OTLP/HTTP+JSON 请求并发布它。 OpenTelemetry OTLP/HTTP+JSON 规范 定义了该体形状:

{
  "resourceSpans": [{
    "resource":  { "attributes": [ ... ] },          // optional
    "scopeSpans": [{
      "scope":  { "name": "<your-instrumentation>", "version": "1.0.0" },
      "spans":  [ <span>, <span>, ... ]
    }]
  }]
}

每个 <span> 都是一个对象,其必填字段为 traceId、spanId、name、kind、startTimeUnixNano、endTimeUnixNano、attributes,以及(对于非根跨度)parentSpanId。 关于编码规则(字符串编码时间、十六进制traceId / spanId、整数status.codekind / 、所有属性值,如stringValue),请参见端点和请求体编码。

要在每个跨度上设置的属性集在 消息协定中定义。 完整属性列表请参见 属性参考。 请参阅 Agent 运行示例,查看一个端到端的可运行示例,其中 Bearer 令牌位于请求头中,正文以内联方式提供。

可以在单个 POST 正文(首选 - 一个请求、一个跟踪)或多个 POST 中发送运行的所有跨度。 服务器根据 traceId + parentSpanId + gen_ai.conversation.id 重建该次运行,因此每个跨度都包含足够的信息,可进行双向关联。

消息协定

本节定义了可以生成哪些 span,以及每个 span 应包含哪些属性。 有关完整的逐项属性规范,请参阅 属性参考。

操作类型

你发送的每个 span 都必须带有设为以下四个值之一的 gen_ai.operation.name(不区分大小写)。 服务器会丢弃任何具有缺失值或无法识别的值的 span,并将其计入 partialSuccess.rejectedSpans。

gen_ai.operation.name Meaning 谷歌搜索量最高的常见陷阱
invoke_agent 调用代理。 代理运行的“root”。 这是运行记录显示在 Microsoft Defender 代理活动视图或 Microsoft 365 管理中心中所必需的。 没有它,遥测数据只会进入 Microsoft Defender 高级搜寻(CloudAppEvents)。
execute_tool 代理执行的工具/函数调用。 --
chat 一次 LLM 推理调用。 使用字面量 chat,而不是 inference。
output_messages 最终发出的输出信息。 --

跨层次结构并运行分组

Agent 365 根据标准 OTLP 跨度图(traceId, spanId, parentSpanId)以及 属性参考中的整个运行属性重建一次运行。

六个规则:

  1. 始终在每个非根跨度上设置 parentSpanId。 没有它,你就无法重建该运行过程的树形结构。
  2. 在一次运行中的每个跨度中重复使用相同的 traceId
  3. 在每个 span 上将 gen_ai.conversation.id 设置为相同的值。 该值是“本次运行中所有跨度”的主要连接键。 它 不 会自动传播。
  4. 在每个 span 上将 microsoft.channel.name 设置为相同的值。 缺少通道或对话信息的工具 span 只有在其父 span 位于同一 OTLP 请求中invoke_agent,才能从父 span 继承这些信息,因此请自行在每个 span 上设置它们。
  5. 如果存在逻辑会话,请在每个 span 上设置microsoft.session.id。
  6. 对于子代理位于单独请求中的代理间调用,请复用同一个 gen_ai.conversation.id,并使用 microsoft.a365.caller.agent.* 属性(请参阅 属性参考)来捕获调用方代理的上下文。

代理运行示例中的四跨树是规范形状。

常见运行形状

形状 要输出的跨度 备注
单代理聊天机器人 (无工具,无 LLM 范围) 仅限一个invoke_agent 设置整个运行的属性,以及 gen_ai.input.messages 和 gen_ai.output.messages。 与 最小可能的请求相同。
使用工具的代理 (最常见的) invoke_agent根 + chat、execute_tool、output_messages子项 所有子项共享根项的 traceId 和 parentSpanId = root.spanId 集合。 它们都具有整个运行范围内相同的属性。 有关完整示例,请参阅 代理运行示例 。
代理到代理 每个代理都会发出自己的invoke_agent 在两个代理之间复用同一个 gen_ai.conversation.id。 在目标的 invoke_agent 上,设置 gen_ai.execution.type = "Agent2Agent" 以及 microsoft.a365.caller.agent.* 属性(调用代理的 appId、名称、蓝图 appId、用户 ID 和电子邮件)。 如果调用代理没有 Entra 注册,请改用 microsoft.a365.caller.agent.platform.id 和 gen_ai.caller.agent.type。

制作上线检查清单

在部署到生产环境之前,请先逐项检查这份清单。

Category 检查
身份验证 你的 Entra 应用(或蓝图)已注册,你现在可以为其生成令牌。
身份验证 如果你在S2S路由中使用蓝图衍生的身份,代理实例会注册到 Agent 365 中。 注册实例不需要应用 Agent365.Observability.OtelWrite 角色。
身份验证 如果你在S2S路由中使用未注册身份,你的应用会被赋予应用 Agent365.Observability.OtelWrite 角色。
身份验证 如果你使用委派路径,你的应用会被赋予委派 Agent365.Observability.OtelWrite 范围。
身份验证 每个代理都有自己的 Entra appId ,如 {agentId} URL 中所示。 对于从蓝图派生的标识,该 appId 是代理标识的 appId,而不是蓝图的 appId。 如果代理没有 Entra 注册,请参阅 选取值。
身份验证 当路由需要应用角色或委托范围时,租户管理员为Agent365.Observability.OtelWrite授予同意。 未经必要同意,代币会在没有角色或范围的情况下发布,请求会被 403 拒绝。
许可 客户租户中至少有一个用户已分配 Microsoft 365 E7 或 Microsoft Agent 365 许可证(指已将许可证分配给用户,而不只是租户中存在该 SKU)。 在没有分配许可证的情况下,响应中的 results 表明这些 spans 已被拒绝。 请参阅 先决条件。
跨度 每个 Span 都会设置整个运行范围内的关键基础信息(Span 层级结构和运行分组)。
跨度 invoke_agent 跨度集合 gen_ai.input.messages 和 gen_ai.output.messages。
跨度 execute_tool跨度集gen_ai.tool.name、gen_ai.tool.type、gen_ai.tool.call.id、gen_ai.tool.call.arguments、gen_ai.tool.call.result。
跨度 chat跨度集gen_ai.request.model和gen_ai.provider.name(理想情况下gen_ai.usage.input_tokens / gen_ai.usage.output_tokens - 以字符串编码)。
跨度 所有非根跨度都设置 parentSpanId;一次运行中的所有跨度共享相同的 traceId。
负载 请求正文不超过 1 MB。
验证 你会在每次响应中检查 partialSuccess 和 results,并记录拒绝情况。
验证 你在首次运行时执行了 验证数据摄取 中的验证流程。

后续步骤