Skill : eval-session-classify
Étant donné un ID de session assistant Datadog, classifiez si l'intention de l'utilisateur a été satisfaite. Utilise les serveurs Datadog LLM Obs et MCP core.
Input
Un seul UUID session_id, par ex. a1b2c3d4-e5f6-7890-abcd-ef1234567890.
Ce que l'ensemble d'outils Datadog MCP LLM Obs vous donne
En partant d'un seul session_id, trois appels MCP reconstituent tout :
| Appel | Ce que vous obtenez |
|---|---|
search_llmobs_spans(session_id:<id>) |
trace_id, agent span_id, tous les tags de span : user_handle, user_id, org_id, product_area, message_id, compteurs iteration, stop_reason, matched_model_name, noms d'outils |
get_llmobs_span_details(agent_span_id) |
Tous les évaluations sur le span avec raisonnement complet (tous les juges qui ont été exécutés — intégrés, téléchargés par l'utilisateur ou externes), map content_info (affiche les champs de métadonnées disponibles : query_string, referrer_path, referrer_url, entities_json, user_info_json) |
get_llmobs_agent_loop(trace_id, agent_span_id) |
Conversation complète : prompt système, message utilisateur + ROUTE_CONTEXT (pages récentes avec scores de popularité), blocs de réflexion de l'assistant, tous les arguments d'appel d'outil + résultats, réponse finale |
L'outil agent loop reconstruit la conversation complète à partir
des spans LLM enfants, qui contiennent du contenu — les entrées/sorties nulles ne se trouvent que sur le
span agent lui-même, pas sur les spans anthropic.request sous-jacents.
Étape 1 — Obtenir l'identité de la trace et la structure des spans
search_llmobs_spans(
query = "session_id:<SESSION_ID>",
from = "<reasonable window, e.g. now-30d>",
to = "now",
limit = 50
)
À partir des résultats, extrayez :
trace_id(identique sur tous les spans)span_iddu span avecspan_kind=agentetname=assistant→ c'est le span agent- À partir des tags du span agent :
user_handle,user_id,org_id,product_area,message_id,start_ms - Noms des spans d'outil (toutes les entrées
span_kind=tool) → quels outils ont été appelés - Compteur d'itération à partir des tags
iterationsur les spansget_answer_from_model_step(0-indexé, donc max+1 = nombre total d'itérations) stop_reasonsur le span LLM de la dernière itération (end_turn= fin propre,tool_use= arrêt en cours d'outil)
Métadonnées clés disponibles à partir des tags seuls — avant de lire du contenu :
product_area→ le pod (workflow,rca,dashboard,monitor, etc.)user_handle→ email, nécessaire pour les requêtes RUMmatched_model_name→ quel modèle a servi la sessionmcp:true/falsesur les spans d'outil → si les outils étaient soutenus par MCPresponse_truncated:true/false→ si la réponse a été coupée
Si search_llmobs_spans ne retourne aucun résultat → arrêtez immédiatement, retournez l'erreur llmobs_not_found.
Étape 2 — Obtenir les évaluations et les champs de métadonnées
get_llmobs_span_details(
trace_id = "<TRACE_ID>",
span_ids = ["<AGENT_SPAN_ID>"],
from/to = <same window>
)
Évaluations
La map evaluations contient tous les verdicts de juges qui ont été exécutés contre ce span. Les évaluations en
LLM Observability sont simplement des résultats nommés clé-valeur attachés à un span — n'importe quel tiers peut les
télécharger. Il y a deux sources communes :
Juges exécutés par la plateforme (exécutés côté serveur par Datadog automatiquement sur des spans échantillonnés) :
apparaissent avec des noms comme tribunal_* ou prompt-injection. Chacun a une .value (l'étiquette ou le score) et généralement un .reasoning (explication en prose du juge LLM). Lisez toujours le raisonnement, pas juste la valeur — il contient souvent le signal le plus utile.
Évaluations téléchargées par l'utilisateur (exécutées en externe et renvoyées via le SDK ou l'API LLM Obs) :
apparaissent sous le nom que l'équipe a choisi. Elles suivent la même structure. Traitez-les avec le même poids — un juge défini par l'utilisateur pour par ex. groundedness ou helpfulness est aussi autoritaire qu'un juge de plateforme.
Comment lire la map d'évaluations :
- Itérez sur toutes les clés — ne supposez pas un ensemble fixe de noms de juges
- Pour chaque évaluation : notez
.value(le verdict),.reasoning(si présent), et.tags(qui peuvent porter des noms d'expériences, versions de juges, plan de facturation, etc.) - Les évaluations catégorielles ont une
.valuechaîne ; les évaluations de score ont une.valuenumérique - Une clé d'évaluation manquante signifie que le juge n'a pas été exécuté sur ce span (échantillonnage, pas défaillance)
Traitez toute évaluation présente comme l'ensemble autoritaire pour ce span.
Champs de métadonnées
De content_info.metadata :
query_string→ le texte brut que l'utilisateur a tapéreferrer_path→ la page Datadog sur laquelle l'utilisateur se trouvait quand il a ouvert l'assistantreferrer_url→ URL complèteentities_json→ toute entité Datadog que l'utilisateur a ajoutée en contexte (@asset)user_info_json→ infos de profil utilisateur
Ces champs de métadonnées sont disponibles via get_llmobs_span_content(field="metadata") si vous
en avez besoin des valeurs réelles. La map content_info confirme seulement qu'ils existent et leur type.
Bug connu : le paramètre span_ids de get_llmobs_span_details échoue régulièrement avec une erreur d'unmarshal de type Go, indépendamment de la façon dont le tableau est passé. C'est un bug connu de la plateforme affectant toutes les
sessions — ignorez silencieusement l'étape 2 et passez à l'étape 3. Ce n'est pas une erreur de classification.
Étape 3 — Lire la conversation complète
get_llmobs_agent_loop(
trace_id = "<TRACE_ID>",
span_id = "<AGENT_SPAN_ID>",
from/to = <same window>,
max_content_length = 3000 ← augmentez pour les résultats d'outil complets
)
Si cet appel retourne un 404 ou une erreur → arrêtez immédiatement, retournez l'erreur llmobs_content_expired.
Si la réponse contient <REDACTED_INPUT> ou <MASKED_STREAMING_RESPONSE> → arrêtez immédiatement,
retournez l'erreur llmobs_content_masked. Ne tentez pas de classifier à partir des noms d'outils ou de signaux structurels.
La réponse a deux structures parallèles :
iterations[] — une entrée par appel LLM dans l'ordre :
iteration(1-indexé)content— la sortie de l'assistant pour cette itération (bloc de réflexion JSON ou texte final)tool_calls[]— tableau de{name, arguments, result}pour tout appel d'outil effectuéinput_tokens,output_tokens,cache_read_input_tokens— économie de tokensstop_reasonimplicite selon qu'il y a d'autres itérations
timeline[] — liste de messages chronologique plate :
role: systemà l'itération 1 — le prompt système completrole: userà l'itération 1 — le message utilisateur +ROUTE_CONTEXTcomplet (pages récentes avec scores de popularité) + tout contexte personnalisérole: assistantà chaque itération — blocs de réflexion et appels d'outil- Réponse d'assistant finale en tant que dernière entrée
role: ""(le texte réel envoyé à l'utilisateur)
À partir du ROUTE_CONTEXT du message utilisateur, vous obtenez :
- Quelles pages l'utilisateur a visitées récemment et à quelle fréquence (score de popularité)
- La page actuelle sur laquelle il se trouvait quand il a envoyé le message
- Quels produits/fonctionnalités Datadog il utilisait avant la session
C'est du contexte comportemental pré-session intégré dans la trace LLM Obs — aucune requête RUM nécessaire pour comprendre ce que l'utilisateur faisait.
Étape 4 — Obtenir les signaux comportementaux RUM
Avec user_handle (de l'étape 1) et start_ms (début du span agent), définissez la fenêtre de temps :
- pre :
[start_ms − 30min, start_ms] - during :
[start_ms, start_ms + session_duration_ms] - post :
[start_ms + session_duration_ms, start_ms + session_duration_ms + 60min]
Exécutez deux requêtes RUM en parallèle via analyze_rum_events (basée sur SQL, ensemble rum).
Si l'une ou l'autre requête RUM retourne 0 lignes, interrogez une fenêtre plus large (30 derniers jours) pour vérifier si l'utilisateur a des données RUM web du tout. S'il a des données les autres jours mais pas le jour de la session
(écart RUM web), ou s'il n'a pas de données RUM du tout → arrêtez, retournez l'erreur rum_unavailable.
Ne revenez pas à une classification basée sur la trace seule.
Requête RUM A — Chronologie des vues de page
analyze_rum_events(
event_type = "view",
filter = "@usr.email:<user_handle>",
from = <pre_start>,
to = <post_end>,
sql_query = "SELECT timestamp, view_url, \"@view.time_spent\" FROM rum ORDER BY timestamp LIMIT 200",
extra_columns = [{"name": "@view.time_spent", "type": "int64"}]
)
@view.time_spent est en nanosecondes — divisez par 1e9 pour les secondes. Triez par ordre croissant pour l'arc de navigation.
Requête RUM B — Actions personnalisées uniquement
analyze_rum_events(
event_type = "action",
filter = "@action.type:custom @usr.email:<user_handle>",
from = <pre_start>,
to = <post_end>,
sql_query = "SELECT timestamp, \"@action.name\", view_url FROM rum ORDER BY timestamp LIMIT 200",
extra_columns = [{"name": "@action.name", "type": "string"}]
)
@action.type:custom filtre sur les événements instrumentés par les développeurs uniquement, excluant les clics et frappes collectés automatiquement.
Une fenêtre de session typique de 1,5h génère ~150–200 événements personnalisés — gérable en 1–2 appels.
De chaque ligne : @action.name, view_url, timestamp. Triez par ordre croissant.
Pagination : si is_truncated: true, rappelez avec start_at=<displayed_rows> et le même
LIMIT. start_at est un décalage de ligne dans le résultat SQL, pas un curseur. Utilisez max_tokens pour ajuster la taille de réponse.
Bruit RUM client externe : certaines sessions génèrent beaucoup plus de télémétrie de framework par chargement de page. Les pages de service APM peuvent émettre 200+ événements personnalisés en 2 minutes, épuisant le LIMIT avant d'atteindre l'heure de session. Quand cela se produit, utilisez les filtres SQL WHERE pour réduire le bruit. Chaque équipe agent aura son propre ensemble d'actions à filtrer ou cibler — les exemples ci-dessous sont illustratifs, pas prescriptifs.
Requête 1 — Navigation et actions non-assistant (bruit de framework filtré) :
analyze_rum_events(
event_type = "action",
filter = "@action.type:custom @usr.email:<user_handle>",
from = <pre_start>,
to = <post_end>,
sql_query = """
SELECT timestamp, "@action.name", view_url
FROM rum
WHERE "@action.name" NOT IN (
'dataviz.first_significant_render',
'perf.scroll.dashboard',
'perf.trafficTelemetry.initialLoad',
'getInitialContrastMode',
'DSM__root__Widget-Map--view',
'DataStreamsRelationGraph__ErrorBoundary--view',
'DataStreamsRelationGraphWrapper__ErrorBoundary--view',
'dsm-topology-map-fetch-finish',
'apm_autopilot_notification_stats',
'noEventInCache',
'useEventPlatformQuery without query',
'Experiments explicit fetch completed',
'discussions.discussionCountFetched',
'Feature Flags Provider'
)
ORDER BY timestamp LIMIT 200
""",
extra_columns = [{"name": "@action.name", "type": "string"}]
)
Requête 2 — Signaux spécifiques à l'assistant uniquement :
analyze_rum_events(
event_type = "action",
filter = "@action.type:custom @usr.email:<user_handle>",
from = <pre_start>,
to = <post_end>,
sql_query = """
SELECT timestamp, "@action.name", view_url
FROM rum
WHERE (
"@action.name" LIKE 'command-assistant%'
OR "@action.name" LIKE 'workbench%'
OR "@action.name" LIKE 'ai-experiences%'
OR "@action.name" = 'click on Bad response'
OR "@action.name" = 'click on Incorrect result'
OR "@action.name" = 'click on Submit'
OR "@action.name" = 'click on Reasoning'
OR "@action.name" = 'Rendered a Code block'
)
ORDER BY timestamp LIMIT 200
""",
extra_columns = [{"name": "@action.name", "type": "string"}]
)
Ces deux requêtes complémentaires — l'une filtrant le bruit connu, l'autre ciblant les signaux connus — réduisent généralement des milliers d'événements à la poignée qui importent pour la classification. L'instrumentation RUM de votre équipe agent différera ; adaptez les noms d'actions en conséquence.
Pour interpréter ce que les noms d'actions signifient pour votre agent, consultez le fichier de référence d'actions RUM spécifique à l'agent :
@rum-actions-bits-assistant.md
Chaque équipe d'agent LLM instrumente ses propres actions personnalisées. Si vous classifiez un agent différent, vous avez besoin du fichier de référence équivalent pour l'instrumentation RUM de cet agent. La logique de classification (ce qui constitue un signal positif vs négatif post-session) dépend entièrement des actions suivies et ce qu'elles signifient dans le contexte de ce produit.
Étape 5 — Classifiez
Avec la conversation (étape 3), les évaluations (étape 2), et les signaux RUM (étape 4), appliquez le schéma de classification suivant.
Intention de l'utilisateur (1 phrase)
À partir du message utilisateur dans la chronologie de la boucle agent. Qu'ont-ils voulu accomplir ?
Pod
Source principale : tag product_area de l'étape 1.
Confirmation secondaire : toute évaluation classificateur de pod/zone présente à l'étape 2 (par ex. tribunal_pod_classifier.value).
S'ils ne correspondent pas, notez la divergence — le tag reflète où l'utilisateur était dans l'interface,
l'évaluation reflète ce que la conversation portait réellement.
Ce que l'assistant a fait (2–4 puces)
À partir de la boucle agent : quels outils ont été appelés (avec arguments), quels docs/données ont été récupérés, ce que la réponse finale a dit.
Taxonomie des modes de défaillance
| Code | Signification |
|---|---|
wrong_answer |
Affirmation factuellement incorrecte (vérifiez les feature flags pour les revendications de limitation de plateforme) |
incomplete_answer |
Correct pour autant qu'il ait vu, mais a manqué des chemins importants |
broke_existing_state |
L'assistant a endommagé quelque chose que l'utilisateur avait |
excessive_turns |
Objectif réalisé mais a pris trop de round-trips |
context_loss |
L'assistant a oublié le contexte précédent ou répété les erreurs |
wrong_tool_use |
Outil appelé incorrect ou avec mauvais paramètres |
hallucination |
IDs, URLs ou faits inventés absents des résultats d'outil |
other: <describe> |
Verdict de satisfaction
yes / partial / no
À partir de la trace seule :
yes: la réponse finale répond directement à l'intention de l'utilisateur, aucun retour négatif, aucun signal d'abandonpartial: la réponse était partiellement correcte ou l'utilisateur s'est débloqué par l'effort continuno: retour négatif donné, utilisateur a abandonné, ou l'intention centrale est structurellement irréalisable avec la réponse donnée
Schéma de sortie d'erreur
Quand une source de données requise échoue, arrêtez la classification et émettez :
{
"session_id": "<id>",
"classification_with_rum": "error",
"error": "<error_code>: <detail>"
}
Codes d'erreur :
llmobs_not_found—search_llmobs_spansn'a retourné aucun span pour ce session_idllmobs_content_expired—get_llmobs_agent_loopa retourné 404 (trace passée la fenêtre de rétention)llmobs_content_masked— agent loop a retourné<REDACTED_INPUT>/<MASKED_STREAMING_RESPONSE>rum_unavailable— aucune donnée RUM web trouvée pour cet utilisateur à la date de la session ou environ
Schéma de sortie de classification
# Classification : <session_id>
## Métadonnées de session
- **Trace ID :** <trace_id>
- **Agent span ID :** <span_id>
- **Début :** <horodatage UTC>
- **Durée :** <secondes>s
- **Utilisateur :** <user_handle>
- **Zone de produit :** <valeur du tag>
- **Modèle :** <matched_model_name>
- **Itérations :** <N> (raison d'arrêt : <end_turn|tool_use>)
- **Outils appelés :** <noms d'outils, compteurs>
- **Évaluations :** <nom : valeur — "extrait de raisonnement"> pour chaque juge qui a été exécuté
- **Page de référence :** <referrer_path>
## Intention de l'utilisateur
Une phrase.
## Ce que l'assistant a fait
- Puce 1
- Puce 2
## Pourquoi l'utilisateur a donné un retour négatif
(ignorez si aucun retour)
## L'intention principale a-t-elle été satisfaite ?
**yes / partial / no** — justification d'une phrase.
## Mode de défaillance
- `code` : explication
## Signaux comportementaux RUM
### Contexte pré-session (à partir de ROUTE_CONTEXT dans la boucle agent)
Ce sur quoi l'utilisateur travaillait avant la session, à partir de la liste de pages avec scores de popularité.
### Actions du panneau d'assistant
| Heure | Action | Page |
|------|--------|------|
### Navigation post-session
| Heure | URL | Durée |
|------|-----|-------|
### Feature flags (à partir de l'événement RUM sur la page de session)
Listez tous les flags de zone de produit pertinents pour la classification.
### Verdict RUM
Une phrase : la preuve comportementale soutient-elle ou contredit-elle le verdict basé sur la trace seule ?
## Verdict de satisfaction révisé (avec RUM)
yes / partial / no
Notes sur les limitations
Ce que LLM Obs MCP donne que rien d'autre ne fait :
- Le
ROUTE_CONTEXTdans le message utilisateur est du contexte comportemental pré-session intégré dans la trace — l'assistant l'a reçu dans la conversation. Il liste les pages que l'utilisateur a visitées récemment avec scores de popularité. C'est plus précis que les vues de page RUM pour comprendre l'intention pré-session car c'est ce que l'assistant a réellement vu. - Les champs de raisonnement d'évaluation sont des explications complètes en prose, pas juste des étiquettes — lisez toujours
.reasoning, pas juste.value. Cela s'applique à tout juge, exécuté par la plateforme ou téléchargé par l'utilisateur. - L'économie de tokens par itération (
input_tokens,cache_read_input_tokens) révèle si le modèle travaillait avec un cache chaud (efficace en coûts, mais signifie aussi que le contexte de conversation était réutilisé à partir de sessions antérieures — pertinent pour l'analyse context_loss).
Ce que LLM Obs MCP NE donne PAS :
- Comportement post-session : que l'utilisateur a-t-il fait après avoir reçu la réponse ? RUM requis.
- Feature flags actifs sur le navigateur de l'utilisateur : RUM requis.
- Si l'utilisateur a continué avec d'autres sessions après avoir donné un retour négatif : RUM requis
(comptage des événements
ai-experiences.chat-submitaprèsstart_ms + duration). - Autres sessions concurrentes du même utilisateur dans la même fenêtre de temps : requiert un second appel
search_llmobs_spansfiltré paruser_handlesur une fenêtre plus large.