Intégrer l’observabilité de l’assistant via OTel directement

Apprenez à intégrer l’observabilité de l’agent avec l’Agent 365 en envoyant la télémétrie directement via OpenTelemetry (OTLP/HTTP+JSON). Cette approche aide les agents qui ne peuvent pas utiliser le SDK Agent 365 ou la distribution Microsoft OpenTelemetry à envoyer la télémétrie de manière efficace et sécurisée. Avant de commencer, lisez les concepts d’observabilité de l’Agent 365 pour comprendre le modèle, les flux d’authentification et l’emplacement de vos données.

Important

Le chemin OTel direct est l’exception, et non la valeur par défaut. Utilisez-le uniquement si vous disposez déjà d'un pipeline OpenTelemetry, que votre infrastructure ne peut pas utiliser le Kit de développement logiciel (SDK) Agent 365, ou que votre agent se trouve dans un langage que le Kit de développement logiciel (SDK) ne prend pas encore en charge (par exemple, Java). Pour tous les autres, l’approche recommandée est Microsoft OpenTelemetry Distro, qui fournit un SDK d’observabilité unifié pour Agent 365, Microsoft Foundry, Azure Monitor et plus encore. Le SDK Observability précédent continue de fonctionner sans changements majeurs incompatibles, mais il n’est plus recommandé pour les nouvelles intégrations ; des directives de migration destinées aux utilisateurs existants du SDK seront bientôt disponibles.

Prerequisites

Vérifiez que les configurations suivantes sont en place avant les flux de télémétrie.

Qui What
Administrateur du locataire Inscrivez-vous à Agent 365 et accordez votre consentement pour votre application agent. Voir Intégration à l’agent 365. En l’absence de locataire éligible, l’ingestion peut renvoyer 200 OK même si les results de la réponse indiquent que les spans ont été rejetés.
Administrateur du locataire Attribuez une licence Microsoft 365 E7 ou Microsoft Agent 365 à au moins un utilisateur du locataire. La référence SKU présente n’est pas suffisante. L’affectation à un utilisateur démarre le flux de travail principal Defender qui active l’ingestion. Sans licence attribuée, l’ingestion peut renvoyer 200 OK tout en rejetant les segments.
Administrateur du locataire Accordez le consentement du locataire. Consultez Autoriser les agents à accéder aux ressources Microsoft 365. Sans cela, les jetons sont émis sans le rôle/la portée, et les requêtes renvoient 403.
Votre équipe de développement Inscrivez votre application (application standard Microsoft Entra ou blueprint). Voir Identité de l’agent.
Votre équipe de développement Ajouter Agent365.Observability.OtelWrite dans Autorisations de l’API (rôle d’application pour S2S, portée pour les autorisations déléguées). Pour les blueprints, consultez Configurer les autorisations héritées. Coordonnez-vous avec l’équipe d’intégration Agent 365 pour activer l’autorisation.

Recettes d’authentification

Les quatre recettes utilisent le point de terminaison standard des jetons Microsoft Entra :

Champ Valeur
Point de terminaison de jeton https://login.microsoftonline.com/{your-tenant-id}/oauth2/v2.0/token
Ressource (aud dans le jeton retourné) 9b975845-388f-4429-889e-eab1ef63949c (accepte également api://9b975845-388f-4429-889e-eab1ef63949c)
Étendue S2S 9b975845-388f-4429-889e-eab1ef63949c/.default
Étendue OBO 9b975845-388f-4429-889e-eab1ef63949c/Agent365.Observability.OtelWrite

Les recettes suivantes montrent HTTP brut pour plus de clarté. En production, utilisez Microsoft. Identity.Web ou une autre bibliothèque MSAL, qui gère le rafraîchissement des jetons et la mise en cache.

Quelle recette ai-je besoin ?

Mon modèle d’application Mon flux OAuth Accéder à
Inscription d’application Microsoft Entra standard S2S (informations d’identification du client) S2S, application Microsoft Entra standard
Inscription d’application Microsoft Entra standard OBO (délégué) OBO, application standard Microsoft Entra
Identité de l’agent issue d’un modèle S2S (informations d’identification du client) Identité de l’agent dérivé de Blueprint S2S
Identité de l’agent issue d’un modèle OBO / Collègue d’IA OBO, identité d’agent dérivée de Blueprint

S2S, application standard Microsoft Entra

Envoyez une requête POST au point de terminaison du token du locataire avec grant_type=client_credentials. Authentifiez l’application à l’aide d’une clé secrète client, d’un certificat (assertion JWT signée) ou d’une identité managée ou d’informations d’identification fédérées.

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

Le jeton retourné a appid/azp = {your-app-id}, roles contenant Agent365.Observability.OtelWriteet .aud = 9b975845-... Utilisez-le sur l’itinéraire /observabilityService/.../traces.

Pour l’authentification basée sur un certificat, remplacez client_secret={secret} par client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer&client_assertion={signed-jwt}.

S2S, identité d’agent dérivée d’un plan

Les identités d’agent n’ont pas d'identifiants propres. Le blueprint d’identité de l’assistant contient les informations d’identification (identité managée FIC, certificat ou secret client) et émet des jetons pour le compte de (on behalf of) ses identités d’assistant enfants au moyen d’un échange en deux étapes. Pour plus d’informations, consultez le flux OAuth pour application autonome.

  1. Le blueprint s’authentifie et obtient un jeton d’échange d’identité fédérée T1 :

    • {blueprint-credential} est le jeton MSI du blueprint, le JWT signé par certificat ou l’assertion de jeton d’échange secret par configuration de blueprint.
    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. L’identité de l’assistant échange T1 contre le jeton de la ressource Agent 365 Observability :

    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
    
    • Le jeton retourné a appid/azp = {agent-identity-app-id}, roles contenant Agent365.Observability.OtelWriteet .aud = 9b975845-...
    • Utilisez ce jeton sur l’itinéraire /observabilityService/.../traces .
    • L’URL {agentId} correspond à l’appId d’identité de l’agent, et non à l’appId du plan directeur.

OBO, application Microsoft Entra standard

Recevez le jeton utilisateur entrant Tc de l’appelant en amont (Bearer ou PFAT), puis échangez-le :

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

Pour l’authentification de certificat, remplacez client_secret={secret} par la même client_assertion_type + client_assertion paire que dans S2S.

Le jeton retourné a appid/azp = {your-app-id}, scp contenant Agent365.Observability.OtelWriteet .aud = 9b975845-... Utilisez-le sur l’itinéraire /observability/.../traces. Un jeton d’actualisation est retourné en même temps ; mettez en cache et réutilisez-le au lieu de réexécuter l’échange sur chaque appel.

OBO, identité d’assistant issue du blueprint (y compris un coéquipier IA)

Le flux On-Behalf-Of comporte trois étapes principales. Pour plus d’informations, consultez Flux OAuth de l’assistant : Flux On-Behalf-Of.

  1. Recevez le jeton utilisateur Tc. Pour un coéquipier IA, ce jeton représente le propre compte d’utilisateur de l’agent ; sinon, il représente l’appelant humain.

  2. Le blueprint s’authentifie et obtient T1, identique au flux d’identité de l’agent dérivé du blueprint S2S.

  3. L’identité de l’assistant échange T1 et Tc contre un jeton de ressource délégué :

    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
    

Le jeton retourné a appid/azp = {agent-identity-app-id}, scp contenant Agent365.Observability.OtelWriteet représente l’utilisateur de l’agent. Utilisez-le sur l’itinéraire /observability/.../traces. L’URL {agentId} correspond à l’appId d’identité de l’agent, et non à l’appId du plan directeur. Un jeton d’actualisation est retourné en même temps ; mettez en cache et réutilisez-le.

Revendications requises sur le jeton retourné

Route S2S (/observabilityService/...) - jeton d’application uniquement :

Réclamation Valeur requise
aud 9b975845-388f-4429-889e-eab1ef63949c (ou api://9b975845-...)
roles Doit contenir Agent365.Observability.OtelWrite
appid (v1) ou azp (v2) Doit correspondre à l’URL {agentId}
scp Doit être absent

Itinéraire délégué (/observability/...) - jeton délégué utilisateur (Bearer ou PFAT):

Réclamation Valeur requise
aud 9b975845-388f-4429-889e-eab1ef63949c (ou api://9b975845-...)
scp Doit contenir Agent365.Observability.OtelWrite
appid / azp Doit correspondre à l’URL {agentId}

La route déléguée accepte à la fois les jetons Bearer et MSAuth1.0 PFAT. Les appelants directs doivent utiliser Bearer. Si vous ne savez pas lequel vous avez, utilisez Bearer.

Endpoints

Deux itinéraires ; choisissez la façon dont votre service s’authentifie, et non par ce que fait l’utilisateur :

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

En-têtes :

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

Paramètres URL

  • {tenantId} - le GUID du locataire client. Le serveur considère cette valeur comme faisant autorité. Si vos spans sont configurés microsoft.tenant.id et qu’ils ne correspondent pas, le serveur rejette la requête.
  • {agentId} - appId de l’application appelante (également OAuth client_id). Pour les identités dérivées du blueprint, cette valeur correspond à l’identité de l’agent appId, et non à l’appId du blueprint. Il doit correspondre à l’attribut appid ou azp de votre jeton.
  • api-version=1 -Obligatoire.

Vérifiez l’éligibilité du locataire

Les intégrations tierces intégrées utilisant le modèle d’authentification S2S peuvent vérifier si un locataire client est éligible à l’observabilité de l’Agent 365 avant d’activer une intégration ou d’envoyer une télémétrie. Cette vérification peut aider les intégrations à éviter d’envoyer des télémétries pour les locataires qui ne sont pas actuellement éligibles.

Utilisez le même jeton uniquement applicatif décrit dans l’authentification S2S. Le jeton doit contenir le rôle d’application Agent365.Observability.OtelWrite, et la revendication tid doit correspondre à {tenantId} dans l’URL de la requête.

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

Une réponse retenue contient le résultat d’éligibilité :

{
  "enabled": true
}

Gérez la réponse comme suit :

État Sens Action du client
200 OK, enabled: true Le locataire est éligible à l’observabilité. L’intégration peut envoyer de la télémétrie.
200 OK, enabled: false Le locataire n’est actuellement pas éligible à l’observabilité. N’envoyez pas de télémétrie. Vérifiez que le locataire remplit les prérequis, puis vérifiez à nouveau après que son statut change. Même si la télémétrie est envoyée, le point de terminaison d’ingestion peut renvoyer 200 OK tout en rejetant les spans.
400 Bad Request {tenantId} est vide ou invalide. Corrigez l’identifiant locataire avant de réessayer.
401 Unauthorized Le jeton d'accès est manquant ou non valide. Acquérez un jeton valide pour la ressource Agent 365 Observabilité.
403 Forbidden Le jeton n’a pas le rôle requis dans l’application, ou bien son tenant ne correspond {tenantId}pas à celui de l’application . Corrigez la permission, le consentement ou le décalage entre locataires avant de réessayer.
429 Too Many Requests L’appelant a dépassé la limite d’éligibilité demandée. Honorez Retry-After et réessayez avec du recul et du trac.
503 Service Unavailable L’éligibilité n’a pas pu être déterminée. La réponse n’a pas de contenu. Respectez Retry-After: 30 et réessayez. Ne traitez pas cette réponse comme enabled: false.

Encodage du corps de la requête

Le corps utilise le format standard OTLP/HTTP+JSON : un ExportTraceServiceRequest avec resourceSpansscopeSpansspans. Gardez à l’esprit les détails suivants :

  • Envoyer traceId (16 octets) et spanId (8 octets) en chaînes hexagonales minuscules.
  • startTimeUnixNano et endTimeUnixNano sont des chaînes qui contiennent des nanosecondes d’époque Unix.
  • kind est la valeur entière de l’enum OTLP (par exemple, 1 pour INTERNAL). status.code est l’enum entier (par exemple, 1 pour OK, 2 pour ERROR).
  • Envoyez toutes les valeurs d’attribut en tant que stringValue.

Forme de réponse

Une 200 OK réponse signifie que l’Agent 365 a traité la demande. Cela ne garantit pas que chaque travée a été acheminée vers une destination. Inspectez à la fois partialSuccess et results.

Le results tableau indique le résultat pour chaque segment à chaque destination applicable :

  • sent - la travée était acheminée vers la destination.
  • rejected - le span n’a pas été acheminé. Le reason domaine explique pourquoi.
  • not_routed - la destination n’a pas été sélectionnée pour le span. Le reason domaine explique pourquoi.

Par exemple, un span acheminé avec succès peut renvoyer :

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

Si le client n’est pas éligible, la requête peut quand même renvoyer 200 OK. Dans ce cas, results indique que les spans ont été rejetés :

{
  "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"
        }
      }
    }
  ]
}

Pour les décisions de routage de requête entière, partialSuccess.rejectedSpans peut rester 0 même lorsque results indique que chaque span a été rejeté. N’utilisez pas partialSuccess ni le seul code d’état HTTP comme preuve que l’ingestion a bien eu lieu. Les noms de champs utilisent le format camelCase dans les échanges réseau. Voir Limites et conditions de suppression pour connaître d’autres raisons pour lesquelles la télémétrie peut ne pas apparaître.

Demande la plus petite possible

Le test de bout en bout le plus simple envoie un unique invoke_agent span. Ce span est le plus petit élément qui arrive dans Microsoft Defender.

Étape 1. Obtenez un jeton Bearer. Pour S2S, utilisez les identifiants client avec la portée 9b975845-388f-4429-889e-eab1ef63949c/.default (voir Recettes d’authentification pour la procédure complète).

Étape 2. POST un seul segment :

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

Étape 3. Attendez-vous à 200 OK, puis inspectez results comme décrit dans la structure de réponse. Confirmez que les destinations concernées ont un statut de sent.

Étape 4. Vérifiez que les données ont réellement atterri. Un 200 OK n’est pas une preuve d’ingestion ; voir Vérification de l’ingestion pour le flux de vérification. Pour effectuer une requête POST avec un fichier de corps de requête enregistré à la place, remplacez --data @- <<EOF ... EOF par --data @./otlp-request.json.

Exemple d’exécution de l’agent

Un utilisateur sur Microsoft Teams demande « Quelle est la météo à Seattle ? ». Votre agent appelle une fonction GetWeather, demande à un LLM de mettre en forme la réponse, puis répond. Cette seule exécution comporte quatre spans :

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

Attributs à l’échelle de l’exécution définis sur chaque span :

Attribute Exemple de valeur
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

Ces attributs à l’échelle de l’exécution ne se propagent pas automatiquement. Vous devez régler gen_ai.conversation.id, microsoft.channel.name, et microsoft.session.id sur chaque travée.

Étendue A : invoke_agent (racine)

{
  "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 */
  ]
}

Span B : chat (appel 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 */
  ]
}

Étendue 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 */
  ]
}

Étendue 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 */
  ]
}

Envoyer des données de télémétrie

Utilisez un SDK OTel

La plupart des partenaires envoient des traces via un SDK OTel plutôt que via une implémentation HTTP codée à la main. Le Kit de développement logiciel (SDK) gère le traitement par lots, les nouvelles tentatives et l’encodage OTLP/HTTP+JSON pour vous. Définissez le point de terminaison de l’exportateur et injectez l’en-tête Authorization .

Le point de terminaison de l’exportateur est l’URL de routage elle-même, y compris la chaîne de requête :

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

(Utilisez /observability/... plutôt que /observabilityService/... pour l’itinéraire délégué.)

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

Paquet : 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}` },
});

Paquet : @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;
    }));

Paquet : OpenTelemetry.Exporter.OpenTelemetryProtocol.

HTTP manuel

Si vous ne pouvez pas ou ne souhaitez pas utiliser un SDK OTel, générez la requête OTLP/HTTP+JSON vous-même et postez-la. La spécification OTLP/HTTP+JSON d'OpenTelemetry définit la structure du corps :

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

Chaque <span> est un objet dont les champs obligatoires sont traceId, spanId, name, kind, startTimeUnixNano, endTimeUnixNano, attributes et, (pour les spans non racine) parentSpanId. Pour les règles d’encodage (temps encodés par chaîne, hexadécimal traceId / spanId, entierstatus.codekind / , toutes les valeurs d’attribut comme stringValue), voir Terminaux et encodage du corps de requête.

L’ensemble d’attributs à définir sur chaque étendue est défini dans les contrats de message. Pour la liste complète des attributs, voir Référence attribut. Reportez-vous à l’exemple d’exécution d’assistant pour obtenir un exemple fonctionnel complet avec le jeton du porteur dans l’en-tête et le corps inclus directement.

Vous pouvez envoyer tous les spans d’un run dans un seul corps de requête POST (de préférence : une requête, une trace) ou dans plusieurs requêtes POST. Le serveur reconstruit l’exécution à partir de traceId + parentSpanId + gen_ai.conversation.id, de sorte que chaque span contient suffisamment d’informations pour être corrélé dans les deux sens.

Contrats de messages

Cette section définit quels spans vous pouvez générer et quels attributs sont associés à chacun d’eux. Pour obtenir la spécification d’attribut par attribut complète, consultez la référence d’attribut.

Types d’opération

Chaque span que vous envoyez doit avoir gen_ai.operation.name défini sur l’une de ces quatre valeurs (sans distinction de casse). Le serveur supprime toute plage avec une valeur manquante ou non reconnue et la compte dans partialSuccess.rejectedSpans.

gen_ai.operation.name Sens Le piège le plus recherché sur Google
invoke_agent Appel d’un agent. La « racine » d’une exécution d’assistant. Obligatoire pour que l’exécution apparaisse dans les vues d’activité de l’agent de Microsoft Defender ou dans le centre d’administration Microsoft 365. Sans cela, la télémétrie n’est transmise qu’à la fonctionnalité de recherche avancée de Microsoft Defender (CloudAppEvents).
execute_tool Un appel d’outil / de fonction effectué par un agent. --
chat Un appel d’inférence LLM. Utilisez le littéral chat, NOT inference.
output_messages Un message de sortie final émis. --

Hiérarchie des spans et regroupement des exécutions

Agent 365 reconstruit une exécution à partir du graphe standard des spans OTLP (traceId, spanId, parentSpanId), ainsi que des attributs globaux de l’exécution issus de la référence des attributs.

Six règles :

  1. Toujours définir parentSpanId sur chaque étendue non racine. Sans cela, vous ne pouvez pas reconstruire la structure arborescente de la partie.
  2. Réutiliser le même traceId pour chaque plage d’un run.
  3. Définissez gen_ai.conversation.id sur chaque étendue avec la même valeur. Cette valeur est la clé de jointure principale pour « tous les spans de cette exécution ». Ce n’est pas propagé automatiquement.
  4. Définissez microsoft.channel.name sur chaque étendue avec la même valeur. Les spans d’outil qui n’ont pas de canal ni de conversation peuvent les hériter de leur parent invoke_agent le parent se trouve dans la même requête OTLP ; définissez-les donc vous-même sur chaque span.
  5. Définissez microsoft.session.id sur chaque span lorsque vous avez une session logique.
  6. Pour les appels d’agent à agent où l’agent enfant est dans une requête distincte, réutilisez le même gen_ai.conversation.id et utilisez les attributs microsoft.a365.caller.agent.* (voir Référence des attributs) pour capturer le contexte de l’agent appelant.

L’arborescence à quatre spans de l’exemple d’exécution de l’assistant est la forme canonique.

Types d’exécution courants

Forme Spans à émettre Notes
Chatbot à agent unique (aucun outil, aucune étendue LLM) Un invoke_agent seul Définir les attributs globaux de l’exécution ainsi que gen_ai.input.messages et gen_ai.output.messages. Identique à la demande la plus petite possible.
Agent avec outils (le plus courant) Racine invoke_agent avec les spans enfants chat, execute_tool, output_messages Tous les enfants partagent la racine traceId et définissent parentSpanId = root.spanId. Tous portent les mêmes attributs à l’échelle de l’exécution. Consultez l’exemple d’exécution de l’agent pour obtenir un exemple complet.
Agent à agent Chaque agent émet son propre invoke_agent Réutilisez le même gen_ai.conversation.id dans les deux agents. Sur le invoke_agent cible, définissez gen_ai.execution.type = "Agent2Agent" ainsi que les attributs microsoft.a365.caller.agent.* (l’appId de l’assistant appelant, son nom, l’appId du blueprint, l’ID utilisateur et l’adresse e-mail). Si l’agent appelant n’a pas d’inscription Entra, utilisez microsoft.a365.caller.agent.platform.id et gen_ai.caller.agent.type à la place.

Liste de contrôle pour l’intégration en production

Exécutez cette liste de contrôle avant d’aller en production.

Category Cocher
Authentification Votre application Entra (ou blueprint) est enregistrée et vous pouvez générer des jetons pour celle-ci.
Authentification Votre application se voit accorder Agent365.Observability.OtelWrite (rôle d’application pour S2S, portée pour les autorisations déléguées).
Authentification Chaque agent possède son propre ID d’application Entra appId, sous la forme de {agentId} dans l’URL. Pour les identités dérivées du blueprint, cet appId est l’identité de l’assistant, et non l’appId du blueprint. Si l’agent n’a pas d’enregistrement Entra, consultez Choix des valeurs.
Authentification Un administrateur locataire accorde son consentement pour Agent365.Observability.OtelWrite. Sans consentement, les jetons sont émis sans le rôle ou l'étendue et les demandes sont rejetées avec 403.
Gestion des licences Au moins un utilisateur du locataire client possède une licence Microsoft 365 E7 ou Microsoft Agent 365 attribuée (attribution réelle, et non simple présence du SKU dans le locataire). Sans licence assignée, les results de la réponse indiquent que les spans ont été rejetés. Voir Conditions préalables.
Spans Chaque span définit les éléments essentiels communs à l’ensemble du run (Hiérarchie des spans et regroupement des runs).
Spans Ensemble de spans invoke_agentgen_ai.input.messages et gen_ai.output.messages.
Spans Ensemble de spans execute_toolgen_ai.tool.name, , gen_ai.tool.typegen_ai.tool.call.id, gen_ai.tool.call.arguments, gen_ai.tool.call.result.
Spans chat définit les étendues gen_ai.request.model et gen_ai.provider.name (et idéalement gen_ai.usage.input_tokens / gen_ai.usage.output_tokens - encodés sous forme de chaîne).
Spans Tous les spans non racine définissent parentSpanId ; tous les spans d’une exécution partagent le même traceId.
Payload Le corps de la demande est ≤ 1 Mo.
Vérification Vous examinez à la fois partialSuccess et results pour chaque réponse et consignez les rejets.
Vérification Vous avez exécuté le flux de vérification dans Vérification de l’ingestion sur vos premières exécutions.

Étapes suivantes