eval-bootstrap

Par datadog-labs · agent-skills

Amorce des évaluateurs basés sur le SDK à partir de traces de production. À utiliser quand l'utilisateur dit « bootstrap evaluators », « generate evaluators », « create evals from traces », « eval bootstrap », « write evaluators », « build eval suite », ou souhaite générer du code BaseEvaluator/LLMJudge à partir de données de traces LLM de production. Fonctionne avec `ml_app` et un rapport RCA ou une hypothèse d'échec optionnels.

npx skills add https://github.com/datadog-labs/agent-skills --skill eval-bootstrap

Eval Bootstrap — Générer du Code Évaluateur à partir des Traces de Production

À partir d'un échantillon de traces LLM de production, analysez les patterns entrée/sortie et les dimensions de qualité, puis générez du code évaluateur prêt à l'emploi en utilisant le SDK Datadog Evals. La sortie est un fichier .py contenant des sous-classes BaseEvaluator et/ou des instances LLMJudge que l'utilisateur peut exécuter dans LLM Experiments.

Utilisation

/eval-bootstrap <ml_app> [--timeframe <window>] [--data-only]

Arguments: $ARGUMENTS

Entrées

Entrée Obligatoire Défaut Description
ml_app Oui Application ML pour limiter l'étendue des traces
timeframe Non now-7d Jusqu'où regarder en arrière
rca_report Non Taxonomie des défaillances issue de la skill eval-trace-rca, ou une hypothèse de défaillance en texte libre
--data-only Non off Émettre un fichier de spec JSON autonome au lieu du code SDK Python

Si ml_app est absent, demandez à l'utilisateur avant de continuer.

Outils Disponibles

Outil Objectif
search_llmobs_spans Trouver des spans par présence d'eval, tags, type de span, syntaxe de requête. Paginer avec cursor.
get_llmobs_span_details Métadonnées, évaluations (scores, labels, raisonnement) et map content_info montrant les champs disponibles + tailles.
get_llmobs_span_content Contenu réel pour un champ de span. Supporte JSONPath via paramètre path pour extraction ciblée.
get_llmobs_trace Hiérarchie complète de trace comme arbre de spans avec décompte de spans par type.
get_llmobs_agent_loop Timeline d'exécution d'agent chronologique (appels LLM, invocations d'outils, décisions).
list_llmobs_evals Lister tous les évaluateurs (OOTB + custom) configurés pour ml_app, avec statut enabled. Appeler une fois en Phase 0 pour mapper la couverture existante avant de proposer de nouveaux évaluateurs.
get_llmobs_eval_config Configuration complète (prompt, modèle, sortie structurée) pour un évaluateur custom/BYOP. Utiliser en Phase 0 pour comprendre ce qu'un eval custom mesure. Non supporté pour source=ootb — ignorer ces cas.

Patterns Clés de get_llmobs_span_content

Utilisez le paramètre path pour extraire des données ciblées sans récupérer les payloads complets :

Champ Path Ce que vous obtenez
messages $.messages[0] Prompt système (premier message, généralement rôle system)
messages $.messages[-1] Dernière réponse de l'assistant
messages (no path) Conversation complète incluant les appels d'outils
input / output I/O du span
documents Documents récupérés (apps RAG)
metadata Métadonnées custom (versions de prompt, feature flags, segments utilisateurs)

Comment Utiliser search_llmobs_spans

Les filtres supplémentaires se combinent avec un espace (AND) : @status:error @ml_app:my-app. Les paramètres dédiés (span_kind, root_spans_only, ml_app) fonctionnent aux côtés de query, mais query prend la priorité sur tags.

Pour trouver des spans avec un eval spécifique : @evaluations.custom.<eval_name>:* — vous ne pouvez interroger que la présence d'un eval, pas des résultats spécifiques.

Règles de Parallélisation

  1. get_llmobs_span_details: Grouper les span_ids par trace_id. Un appel par trace_id avec TOUS ses span_ids. Émettre TOUS les appels d'une page dans un seul message.
  2. get_llmobs_span_content: Chaque appel est indépendant — toujours émettre TOUS dans un seul message.
  3. get_llmobs_trace / get_llmobs_agent_loop: Paralléliser sur différentes traces dans un seul message.
  4. Parallélisme de pipeline: Commencer get_llmobs_span_details pour les résultats de la page 1 immédiatement — ne pas attendre de collecter toutes les pages.

Référence SDK Évaluateur

S'applique au mode sdk_code uniquement. En mode data_only, utilisez cette section comme contexte de domaine lors de l'écriture des prompts de rubrique — aucune classe SDK n'est émise.

Imports

# Classes principales
from ddtrace.llmobs._experiment import BaseEvaluator, EvaluatorContext, EvaluatorResult

# LLM-as-judge
from ddtrace.llmobs._evaluators.llm_judge import (
    LLMJudge,
    BooleanStructuredOutput,
    ScoreStructuredOutput,
    CategoricalStructuredOutput,
)

# Évaluateurs intégrés (utiliser uniquement si nécessaire)
from ddtrace.llmobs._evaluators.format import JSONEvaluator, LengthEvaluator
from ddtrace.llmobs._evaluators.string_matching import StringCheckEvaluator, RegexMatchEvaluator

N'importer que ce que le fichier généré utilise réellement.

EvaluatorContext (ce que evaluate() reçoit)

@dataclass(frozen=True)
class EvaluatorContext:
    input_data: dict[str, Any]          # Entrées de tâche (depuis record dataset, PAS depuis span)
    output_data: Any                     # Sortie de tâche (depuis retour de fonction tâche, PAS depuis span)
    expected_output: Optional[JSONType] = None  # Vérité de référence (si disponible)
    metadata: dict[str, Any] = {}        # Métadonnées supplémentaires
    span_id: Optional[str] = None        # ID de span LLMObs
    trace_id: Optional[str] = None       # ID de trace LLMObs

Important — données span vs données d'évaluateur: En explorant les traces de production, vous voyez l'I/O de span (p. ex. input.value, output.messages). Mais les évaluateurs s'exécutent dans des expériences hors ligne où input_data et output_data proviennent des records dataset et fonction tâche de l'utilisateur, pas des spans. Le schéma dataset est défini par l'utilisateur et peut ne pas correspondre à la structure du span. Écrivez les prompts d'évaluateur avec des placeholders génériques {{input_data}} / {{output_data}} et ajoutez des commentaires décrivant pour quelles données l'évaluateur a été conçu, afin que l'utilisateur puisse adapter la forme de son dataset.

EvaluatorResult (ce que evaluate() retourne)

EvaluatorResult(
    value=...,                    # Obligatoire. JSONType (str, int, float, bool, None, list, dict)
    reasoning="...",              # Optionnel. Chaîne d'explication
    assessment="pass" or "fail",  # Optionnel. Évaluation pass/fail
    metadata={...},              # Optionnel. Dict de métadonnées d'évaluation
    tags={...},                  # Optionnel. Dict de tags
)

LLMJudge — Évaluateur LLM-as-Judge

judge = LLMJudge(
    user_prompt="...",              # Obligatoire. Supporte {{template_vars}}
    system_prompt="...",            # Optionnel. NE supporte PAS les template vars
    structured_output=...,          # Optionnel. Sortie Boolean/Score/Categorical, ou dict pour schéma JSON custom
    provider="openai",              # "openai" | "anthropic" | "azure_openai" | "vertexai" | "bedrock"
    model="gpt-4o",                # Identifiant de modèle
    model_params={"temperature": 0.0},  # Optionnel. Passé à l'API LLM
    name="eval_name",              # Optionnel. Doit correspondre à ^[a-zA-Z0-9_-]+$
)

Variables de template dans user_prompt : {{input_data}}, {{output_data}}, {{expected_output}}, {{metadata.key}} — résolues depuis les champs EvaluatorContext via dot-path dans les dicts imbriqués.

Types de Sortie Structurée

Boolean — true/false avec pass/fail optionnel :

BooleanStructuredOutput(
    description="Whether the response is factually accurate",
    reasoning=True,                    # Inclure champ reasoning dans réponse LLM
    reasoning_description=None,        # Description custom optionnelle pour champ reasoning
    pass_when=True,                    # True → pass quand true, False → pass quand false, None → pas d'assessment
)

Score — numérique dans une plage avec seuils optionnels :

ScoreStructuredOutput(
    description="Helpfulness score",
    min_score=1,                       # Score minimum possible
    max_score=10,                      # Score maximum possible
    reasoning=True,
    reasoning_description=None,
    min_threshold=7,                   # Scores >= 7 passent (optionnel)
    max_threshold=None,                # Scores <= N passent (optionnel)
)

Categorical — sélectionner parmi catégories prédéfinies :

CategoricalStructuredOutput(
    categories={
        "correct": "The response correctly answers the question",
        "partially_correct": "The response is partially correct but missing key information",
        "incorrect": "The response is factually wrong or irrelevant",
    },
    reasoning=True,
    reasoning_description=None,
    pass_values=["correct"],           # Quelles catégories comptent comme pass (optionnel)
)

Schéma JSON custom — réponses structurées arbitraires pour evals multi-dimensionnels :

# Passer un dict brut comme structured_output — utilisé comme schéma JSON directement
structured_output={
    "type": "object",
    "properties": {
        "relevance": {"type": "boolean", "description": "Whether the response addresses the question"},
        "confidence": {"type": "number", "description": "Confidence score (0.0 to 1.0)"},
        "reasoning": {"type": "string", "description": "Explanation for the evaluation"},
    },
    "required": ["relevance", "confidence", "reasoning"],
    "additionalProperties": False,
}

Toujours écrire du schéma JSON standard — le SDK l'adapte par provider automatiquement (p. ex. Anthropic ne supporte pas minimum/maximum sur les champs number, donc le SDK déplace les contraintes de plage dans la description ; Vertex AI convertit const/anyOf en enum). Le dict JSON parsé complet devient la valeur eval ; une clé "reasoning" (si présente) est extraite automatiquement. Pas d'assessment pass/fail automatique.

Directives de Prompt LLMJudge

Le paramètre structured_output impose le format de réponse via schéma JSON. Ne prescrivez pas le format dans le prompt (pas de "Answer YES/NO", "Rate 1-10", etc.). À la place, décrivez les critères d'évaluation et laissez la sortie structurée gérer le format.

  • system_prompt: Définir le rôle du judge et le contexte de domaine de l'app. NE supporte PAS les template vars.
  • user_prompt: Présenter les données via {{input_data}} / {{output_data}}, puis décrire ce que bon vs. mauvais signifie pour cette dimension.

BaseEvaluator — Évaluateur Custom Basé sur du Code

Pour des vérifications déterministes qui n'ont pas besoin de jugement LLM :

class MyEvaluator(BaseEvaluator):
    def __init__(self, name=None, ...custom_params...):
        super().__init__(name=name)
        self._param = ...  # Stocker config comme attrs privés

    def evaluate(self, context: EvaluatorContext) -> EvaluatorResult:
        # Accès: context.input_data, context.output_data, context.expected_output, context.metadata
        # NE DOIT PAS modifier les attrs self (thread safety)
        passed = ...  # Votre logique ici
        return EvaluatorResult(
            value=passed,
            reasoning="...",
            assessment="pass" if passed else "fail",
        )

Évaluateurs Intégrés

# Valider syntaxe JSON + clés obligatoires optionnelles
JSONEvaluator(required_keys=["name", "age"], output_extractor=None, name=None)

# Valider longueur (caractères, mots, ou lignes)
LengthEvaluator(count_by="words", min_length=10, max_length=500, output_extractor=None, name=None)
# count_by: "characters" | "words" | "lines"

# Correspondance de chaîne
StringCheckEvaluator(operation="contains", expected="success", case_sensitive=False, name=None)
# operation: "eq" | "ne" | "contains" | "icontains"

# Correspondance regex
RegexMatchEvaluator(pattern=r"\d{4}-\d{2}-\d{2}", match_mode="search", name=None)
# match_mode: "search" | "match" | "fullmatch"

Matrice de Décision Type d'Évaluateur

Signal Type d'Évaluateur
La sortie doit être du JSON valide JSONEvaluator
La sortie doit correspondre à un pattern regex RegexMatchEvaluator
La sortie a des contraintes de longueur LengthEvaluator
La sortie doit contenir/ne pas contenir des chaînes spécifiques StringCheckEvaluator
Jugement de qualité sémantique (ton, exactitude, complétude) LLMJudge + BooleanStructuredOutput
Qualité notée sur une échelle LLMJudge + ScoreStructuredOutput
Classification en catégories LLMJudge + CategoricalStructuredOutput
Jugement multi-dimensionnel (évaluer plusieurs aspects à la fois) LLMJudge + schéma JSON custom dict
Logique de domaine complexe combinant plusieurs vérifications Sous-classe BaseEvaluator

Vérification Source

Si vous avez accès à dd-trace-py localement, vérifiez la surface API en lisant :

  • ddtrace/llmobs/_evaluators/llm_judge.py — classe LLMJudge, types de sortie structurée
  • ddtrace/llmobs/_experiment.py — BaseEvaluator, EvaluatorContext, EvaluatorResult
  • ddtrace/llmobs/_evaluators/format.py — JSONEvaluator, LengthEvaluator
  • ddtrace/llmobs/_evaluators/string_matching.py — StringCheckEvaluator, RegexMatchEvaluator

Workflow

Phase 0 : Résoudre Entrées & Mode d'Entrée

Détection de mode d'entrée:

Mode Signal Comportement
Cold Start Seul ml_app fourni (pas de RCA, pas d'hypothèse) Découverte ouverte complète — comprendre ce que fait l'app, identifier les dimensions de qualité worth measuring, proposer des evals pour la couverture
From RCA La conversation contient un rapport RCA ou l'utilisateur fournit une hypothèse de défaillance Passer la découverte ouverte — utiliser la taxonomie de défaillance existante comme cibles d'eval

Parser les arguments: Extraire ml_app (premier argument non-flag), --timeframe (défaut now-7d), et le flag --data-only. Définir output_mode = data_only si le flag est présent ; sinon output_mode = sdk_code.

Étapes de résolution:

  1. Si ml_app non fourni → demander à l'utilisateur.

  2. Auto-détecter le mode d'entrée :

    • Si la conversation contient un rapport RCA (chercher un en-tête "Failure Taxonomy", modes de défaillance structurés, ou ratings de sévérité) → from_rca. Extraire la taxonomie.
    • Si l'utilisateur fournit une hypothèse de défaillance en texte libre (p. ex. "le system prompt manque de grounding") → from_rca. Utiliser l'hypothèse comme cible d'eval initiale.
    • Sinon → cold_start.
  3. Si timeframe non fourni → défaut à now-7d.

  4. Mapper la couverture d'eval existantesauter si output_mode = data_only (il n'y a pas de projet Datadog eval pour vérifier la couverture contre) : Appeler list_llmobs_evals(ml_app=<ml_app>). Puis, pour chaque eval avec source=custom, appeler get_llmobs_eval_config pour inspecter son prompt et inférer quelle dimension de qualité il couvre. Émettre tous les appels de config dans un seul message (paralléliser). Ignorer les evals source=ootb — leurs noms sont auto-descriptifs.

    À la fin de cette étape vous avez une map de couverture complète : {eval_name → source, enabled, dimension}. Porter ceci dans la Phase 2 pour la déduplication.

  5. Détection contexte notebook: Scanner la conversation actuelle pour une URL de notebook Datadog qui a été produite par /eval-trace-rca (pattern : https://app.datadoghq.com/notebook/{numeric-id}). Si trouvée, la stocker comme rca_notebook_url et extraire l'ID numérique comme rca_notebook_id. Ceci est utilisé après la Phase 3 pour offrir l'ajout de la suite d'évaluateurs à ce notebook au lieu de créer un nouveau.


Phase 1 : Explorer les Traces & Identifier les Cibles d'Eval

Objectif: Échantillonner les traces de production, comprendre ce que fait l'app, et identifier les dimensions de qualité worth measuring.

Chemin Cold Start

  1. Échantillonner l'app: search_llmobs_spans(ml_app=<ml_app>, root_spans_only=true, limit=50, from=<timeframe>, query="@status:ok"). Filtrer par @status:ok — les spans d'erreur n'ont pas de sortie à évaluer.

  2. Profiler l'app et identifier les spans cibles d'évaluation: Appeler get_llmobs_span_details pour les span_ids groupés par trace_id. Inspecter content_info pour classifier :

    Signal Profil App
    content_info a messages App LLM/chat
    content_info a documents App RAG
    Spans incluent type agent App agent
    content_info a metadata A métadonnées custom

    Pour les apps agent/multi-étapes, appeler également get_llmobs_trace sur 2-3 traces pour voir la hiérarchie de spans complète. Comparer content_info entre le span root et ses sous-spans (particulièrement les sous-spans LLM). Le span root a généralement une vue de résumé (requête utilisateur → réponse finale), tandis que les sous-spans LLM ont l'image complète (system prompt, résultats d'appel d'outil, chaîne de raisonnement). Noter quel niveau de span a le signal le plus riche pour chaque dimension de qualité — ceci détermine le span cible d'évaluation pour chaque évaluateur.

  3. Extraire le contenu et identifier les cibles: Appeler get_llmobs_span_content pour les spans représentatifs. Récupérer les champs basés sur le profil d'app :

    Profil App Champs à Récupérer
    LLM/chat messages (path=$.messages[0] pour system prompt), output
    RAG documents, input, output
    Agent get_llmobs_agent_loop pour le span agent, puis messages pour détail
    Quelconque avec metadata metadata

    Émettre tous les appels dans un seul message. En lisant, noter les patterns de qualité : à quoi ressemble le « succès » ? Quelle variance existe entre les sorties ? Chaque dimension de qualité observée devient une cible d'eval, avec les traces que vous venez de lire comme preuve. Chercher aussi les signaux de sécurité — violations de scope, données sensibles dans les sorties, réponses hors-caractère — et proposer un évaluateur de sécurité si vous en trouvez.

Chemin From RCA

  1. Extraire la taxonomie de défaillance du rapport RCA. Chaque mode de défaillance avec sévérité High ou Medium devient une cible d'eval.
  2. Pour chaque cible : si la RCA inclut des trace IDs, les utiliser directement ; sinon chercher les traces correspondantes. Récupérer 2-3 traces par cible avec get_llmobs_span_content pour comprendre le pattern concret.

Phase 2 : Proposer la Suite d'Évaluateurs

Objectif: Présenter une proposition d'évaluateur concrète pour la confirmation de l'utilisateur.

Chaque évaluateur juge un seul point de données — il reçoit input_data et output_data pour un seul record, pas une trace complète ou batch. Concevoir les évaluateurs en conséquence.

Les évaluateurs générés ciblent les expériences hors ligne — les variables de template utilisent les champs EvaluatorContext ({{input_data}}, {{output_data}}). La forme réelle des données dépend du dataset de l'utilisateur et de la fonction tâche (voir note EvaluatorContext dans la Référence SDK).

Ordonner les propositions du signal le plus large au plus granulaire :

  1. Évaluateurs de résultat — Ce span a-t-il produit un bon résultat ?
    • Exemples : task_completion, answer_correctness, response_groundedness
  2. Évaluateurs de format — La sortie remplit-elle les requirements structurels ?
    • Exemples : valid_json_output, response_length, citation_format
  3. Évaluateurs de sécurité — La sortie reste-t-elle dans les limites appropriées ?
    • Exemples : no_pii_leakage, scope_adherence, no_hallucination

Déduplication Contre la Couverture Existante

En mode data_only: sauter cette section entièrement (la map de couverture n'a pas été construite en Phase 0). Procéder directement à la table de proposition.

Avant de construire la proposition, appliquer la map de couverture de la Phase 0 :

  1. Eval enabled (OOTB ou custom): NE PAS proposer un nouvel évaluateur pour la même dimension de qualité. Cette dimension est déjà couverte — la passer.

  2. Eval OOTB disabled: NE PAS proposer un nouvel évaluateur custom pour cette dimension. À la place, le surfacer dans une courte note dans la proposition et suggérer de l'activer via l'UI Datadog plutôt que de créer un doublon. Exemple :

    hallucination (ootb, disabled) — considérer l'activation dans l'UI Datadog (Evaluations → Configure) au lieu de créer un eval custom.

  3. Identification des lacunes: Ouvrir la proposition avec une ligne de résumé de couverture : "Couverture existante : N évaluateur(s) déjà configuré(s) ({names}). Proposition d'évaluateurs pour les dimensions non couvertes uniquement."

  4. Toutes les dimensions couvertes: Si la map de couverture compte pour toutes les dimensions de qualité identifiées, le surfacer explicitement et demander à l'utilisateur ce qu'il veut : (a) revoir/améliorer les prompts d'eval existants, (b) ajouter de la couverture pour des dimensions supplémentaires, ou (c) procéder quand même.

Pour chaque évaluateur proposé :

  • Nom: Doit correspondre à ^[a-zA-Z0-9_-]+$ (alphanumeric, underscore, hyphen uniquement)
  • Type: LLMJudge (Boolean/Score/Categorical/schéma JSON custom), built-in (JSONEvaluator, RegexMatchEvaluator, etc.), ou sous-classe BaseEvaluator
  • Ce qu'il mesure: Description plain-language de 1-2 phrases
  • Span cible: Données de quel span l'évaluateur a été conçu pour (p. ex. "root agent span", "sous-span LLM anthropic.request", "tous les spans llm"). Si l'I/O du span root est trop lossy pour la dimension de qualité (p. ex. les résultats d'appel d'outil ne sont pas visibles), le noter et spécifier quel sous-span a le signal.
  • Critères pass/fail: pass_when=True, min_threshold=7, pass_values=["correct"], ou "pas d'assessment automatique" pour schéma JSON custom
  • Variables de template: Lequel(s) de input_data, output_data, expected_output, metadata.* il utilise
  • Preuve: Au moins une trace où il aurait attrapé une défaillance (ou confirmé un comportement correct)

CHECKPOINT OBLIGATOIRE

Vous DEVEZ produire la proposition et attendre la confirmation de l'utilisateur avant de procéder.

## Suite d'Évaluateurs Proposée

**Profil app**: {LLM | RAG | Agent | Multi-agent}
**Mode d'entrée**: {cold_start | from_rca}

| # | Nom | Type | Mesure | Critères Pass |
|---|-----|------|--------|----------------|
| 1 | task_completion | LLMJudge (Boolean) | Si la tâche a été complétée | pass_when=True |
| 2 | ... | ... | ... | ... |

Pour chaque évaluateur :
- **{name}**: {ce qu'il mesure}
  - Span cible : {données de quel span il a été conçu pour}
  - Rationale : {quelle dimension de qualité il couvre et pourquoi}
  - Preuve : [Trace {id_short}](https://app.datadoghq.com/llm/traces?query=trace_id:{full_id})

Quels évaluateurs dois-je générer? (Accepter tous, en retirer certains, ou les renommer. En mode sdk_code vous pouvez aussi ajouter des évaluateurs custom ou changer provider/model.)

NE PAS procéder à la génération de code jusqu'à la confirmation de l'utilisateur.


Phase 3 : Générer la Sortie

Brancher sur output_mode:

  • sdk_codePhase 3A ci-dessous
  • data_only → sauter à Phase 3B

Phase 3A : Générer & Écrire le Code Évaluateur

Objectif: Générer le fichier .py final et l'écrire sur disque.

Pour chaque évaluateur confirmé, générer du code Python de qualité production suivant les patterns de Référence SDK ci-dessus.

Règles de Génération de Code

  1. Ancrer les prompts dans les traces: Les prompts système et utilisateur LLMJudge doivent référencer les patterns réellement observés dans les traces de production. Ne jamais écrire des prompts génériques comme "évaluer si la réponse est bonne" — les ancrer dans le domaine de l'app, les patterns de défaillance observés, et les critères de succès.

  2. Garder les variables de template génériques, ajouter des commentaires pour le contexte: Utiliser {{input_data}} et {{output_data}} comme placeholders top-level dans les prompts — NE PAS référencer des paths de span imbriqués comme {{input_data.messages[-1].content}}. Les données de l'évaluateur proviennent du dataset et de la fonction tâche de l'utilisateur, pas directement des spans. À la place, ajouter un commentaire au-dessus de chaque évaluateur décrivant pour quelles données il a été conçu et ce que l'utilisateur devrait adapter :

    # Conçu pour : input_data = requête utilisateur, output_data = texte de réponse assistant
    # Observé depuis : span agent root (input.value → output.value)
    # Si votre dataset utilise une structure différente, adapter les références de prompt ci-dessous.
  3. Utiliser le type d'évaluateur le plus étroit: Si une vérification peut être faite avec JSONEvaluator, RegexMatchEvaluator, StringCheckEvaluator, ou LengthEvaluator, NE PAS utiliser un LLMJudge. Les évaluateurs basés sur du code sont plus rapides, moins chers, et déterministes.

  4. Sous-classes BaseEvaluator:

    • Appeler super().__init__(name=name) dans __init__
    • Retourner EvaluatorResult depuis evaluate()
    • NE PAS modifier les attrs d'instance dans evaluate() (thread safety)
  5. Noms: Doivent correspondre à ^[a-zA-Z0-9_-]+$. Utiliser les noms snake_case descriptifs.

  6. Imports: Consolider en haut du fichier. N'importer que les classes réellement utilisées.

  7. Liste d'évaluateurs: Collecter tous les évaluateurs dans une liste evaluators en bas du fichier.

  8. Anonymiser PII: Retirer emails, noms, et données sensibles de tout contenu de trace inclus dans les prompts LLMJudge ou le commentaire en-tête.

Écrire le fichier

Écrire le code généré au chemin de sortie (suggérer ./evals/{ml_app}_evaluators.py si non spécifié), puis afficher un résumé :

## Évaluateurs Générés

Écrit {N} évaluateurs dans `{output_path}`:

| # | Nom | Type | Couvre |
|---|-----|------|--------|
| 1 | ... | ... | ... |

### Prochaines Étapes

1. **Revoir**: Vérifier que les prompts générés et les critères correspondent à vos attentes
2. **Tester hors ligne**: Utiliser `LLMObs.experiment(evaluators=evaluators)` pour batch-évaluer contre un dataset labelisé et vérifier les scores

Export notebook (après résumé)

Après affichage du résumé, offrir l'export notebook :

  • Si rca_notebook_url a été détectée en Phase 0:

    Un notebook RCA a été créé plus tôt dans cette session : {rca_notebook_url} Voulez-vous (a) ajouter le résumé de la suite d'évaluateurs à ce notebook, ou (b) créer un nouveau notebook autonome ?

    Si ajouter : appeler mcp__datadog-mcp-core__edit_datadog_notebook avec id={rca_notebook_id}, append_only=true, et la cellule de résumé de la suite d'évaluateurs (voir Contenu cellule notebook ci-dessous).

    Si nouveau : appeler mcp__datadog-mcp-core__create_datadog_notebook (voir ci-dessous).

  • Si pas de rca_notebook_url:

    Voulez-vous exporter ce résumé de la suite d'évaluateurs vers un notebook Datadog?

    Si oui : appeler mcp__datadog-mcp-core__create_datadog_notebook avec :

    • name: Eval Bootstrap: {ml_app} — YYYY-MM-DD
    • type: report
    • cells: cellule markdown unique avec le résumé de la suite d'évaluateurs
    • time: { "live_span": "1h" }

Après la création ou la mise à jour du notebook, afficher l'URL : Suite d'évaluateurs exportée vers notebook : <url>

Contenu cellule notebook — la cellule markdown devrait contenir :

## Eval Bootstrap: {ml_app}

**Généré**: YYYY-MM-DD | **Profil app**: {LLM | RAG | Agent | Multi-agent} | **Mode d'entrée**: {cold_start | from_rca}
**Code généré**: `{output_path}`

### Suite d'Évaluateurs

| # | Nom | Type | Mesure | Critères Pass |
|---|-----|------|--------|----------------|
| 1 | ... | ... | ... | ... |

### Preuve

{Pour chaque évaluateur : nom — description 1 ligne — [lien Trace]}

### Prochaines Étapes

1. Revoir les prompts générés dans `{output_path}`
2. Exécuter contre un dataset labelisé pour valider les scores
3. Déployer sur Datadog LLM Experiments

Phase 3B : Générer & Écrire la Spec JSON Eval

Objectif: Sérialiser la suite d'évaluateurs confirmée et les echantillons de trace représentatifs dans un seul fichier JSON autonome — zéro dépendances SDK.

Chemin de sortie: ./evals/{ml_app}_eval_spec.json

Schéma JSON

{
  "schema_version": "1",
  "generated_at": "<ISO 8601 UTC>",
  "generated_by": "eval-bootstrap",
  "app": {
    "ml_app": "<string>",
    "app_type": "LLM | RAG | Agent | Multi-agent",
    "trace_window": "<paramètre timeframe, p. ex. now-7d>",
    "trace_count": "<integer>"
  },
  "evaluators": [
    {
      "name": "snake_case_name",
      "category": "outcome | format | safety",
      "type": "llm_judge | code_check",
      "description": "<description plain-language 1-2 phrases>",
      "target_span": "<quel span : root, llm sub-span, etc.>",
      "scoring": {
        "scale": "boolean | score_1_10 | categorical",
        "categories": ["<seulement présent si scale=categorical>"],
        "pass_criteria": "<human-readable: true, >= 7, in [correct], etc.>"
      },
      "rubric": "<texte de prompt complet pour llm_judge; null pour code_check>",
      "implementation_hints": {
        "type_if_code_check": "json_valid | regex | contains | length_words | null",
        "pattern_if_code_check": "<chaîne de pattern ou null>",
        "notes": "<guidance d'implémentation framework-agnostique optionnel>"
      },
      "evidence": [
        {
          "trace_id": "<32-char hex>",
          "span_id": "<16-char hex>",
          "url": "https://app.datadoghq.com/llm/traces?query=trace_id:<trace_id>",
          "observation": "<pourquoi cette trace illustre l'évaluateur>"
        }
      ]
    }
  ],
  "sample_records": [
    {
      "trace_id": "<string>",
      "span_id": "<string>",
      "input": {},
      "output": "<string>",
      "suggested_labels": {
        "<evaluator_name>": "pass | fail | <score>"
      }
    }
  ]
}

Notes sur les Champs

  • evaluators[].type: "llm_judge" pour les évaluateurs sémantiques ; "code_check" pour les vérifications déterministes (regex, longueur, validité JSON, etc.).
  • evaluators[].rubric: Pour llm_judge — texte de prompt complet ancré dans les patterns de trace observés. Utiliser {{input}} et {{output}} comme placeholders génériques (pas {{input_data}} — c'est spécifique à ddeval). Pour code_check — null.
  • evaluators[].implementation_hints.notes: Guidance framework-agnostique optionnelle, p. ex. "Pour OpenAI Evals, utiliser rubric comme critère model-graded. Pour Braintrust, utiliser comme LLM scorer. Pour Promptfoo, utiliser comme assertion llm-rubric."
  • sample_records: 10–20 traces représentatives de la Phase 1. suggested_labels est la meilleure lecture de Claude desde l'inspection de trace — pas une vérité de référence. Le nom du champ communique ceci explicitement.
  • Règle PII: Retirer emails, noms, et données sensibles de tous les champs input, output, et evidence[].observation avant d'écrire (comme la Phase 3A).

Instructions d'Écriture

  1. Assembler l'objet JSON en mémoire suivant le schéma ci-dessus.
  2. Populer sample_records à partir des traces déjà récupérées en Phase 1. Récupérer des traces supplémentaires (jusqu'à 20 total) si moins de 10 ont été lues.
  3. Anonymiser PII dans tous les champs input, output, et evidence[].observation.
  4. Écrire le fichier avec indentation 2-espaces en utilisant l'outil Write.
  5. Afficher un résumé d'achèvement :
## Spec Eval Générée

Écrit `./evals/{ml_app}_eval_spec.json`:

- **{N} évaluateurs** ({outcome_count} outcome, {format_count} format, {safety_count} safety)
- **{M} sample records** avec suggested labels

| # | Nom | Catégorie | Type | Critères Pass |
|---|-----|----------|------|----------------|
| 1 | ... | ... | ... | ... |

### Prochaines Étapes

1. **Revoir**: Ouvrir `./evals/{ml_app}_eval_spec.json` et vérifier que les rubrics correspondent à vos attentes
2. **Implémenter**: Utiliser le champ `rubric` pour configurer les évaluateurs dans votre framework de choix :
   - OpenAI Evals : utiliser `rubric` comme critère model-graded
   - Braintrust : créer un LLM scorer avec le texte de rubric
   - Promptfoo : utiliser comme assertion `llm-rubric`
   - Code custom : appeler votre API LLM avec la rubric et parser la sortie structurée
3. **Labeliser**: `suggested_labels` sont les meilleures suppositions de Claude depuis l'inspection de trace — vérifier contre la vérité de référence avant utilisation comme données d'entraînement

Export notebook (après résumé)

Même logique que la Phase 3A — offrir l'ajout au notebook RCA si rca_notebook_url a été détectée, ou créer un nouveau notebook autonome. Utiliser le même format de cellule notebook que la Phase 3A, en remplaçant output_path par le chemin du fichier spec JSON.


Format de Sortie

Le fichier .py généré devrait suivre cette structure :

"""
Auto-generated evaluators for {ml_app}
Generated: {YYYY-MM-DD} by eval-bootstrap

App profile: {LLM | RAG | Agent | Multi-agent}

Quality dimensions covered:
  - {target_name}: {description}
    Evidence: https://app.datadoghq.com/llm/traces?query=trace_id:{full_id}
  ...

Usage:
    from ddtrace.llmobs import LLMObs

    experiment = LLMObs.experiment(
        name="my-experiment",
        task=my_task_fn,
        dataset=dataset,
        evaluators=evaluators,
    )
    experiment.run()
"""

{imports — seulement ce qui est utilisé}


# --- Outcome Evaluators ---

{evaluator code}


# --- Format Evaluators ---

{evaluator code}


# --- Safety Evaluators ---

{evaluator code}


# --- Evaluator Suite ---

evaluators = [
    {eval_1_variable_name},
    {eval_2_variable_name},
    ...
]

N'inclure que les commentaires de section (Outcome/Format/Safety) pour les catégories qui ont des évaluateurs.


Phase 3B : Générer & Écrire la Spec JSON Eval

Voir Phase 3B ci-dessus pour les instructions complètes.


Règles d'Exploitation

  • Couverture plutôt que précision: Proposer 4-6 évaluateurs couvrant les principales dimensions de qualité. Les utilisateurs peuvent toujours en retirer ; ils ne peuvent pas facilement ajouter ce qui n'a pas été proposé.
  • Ne pas sur-adapter: Écrire des critères qui se généralisent au-delà des traces spécifiques échantillonnées. Utiliser les exemples comme ancrage, pas comme seul critère.
  • Montrer votre travail: Chaque évaluateur proposé cite au moins une trace comme preuve avec un lien cliquable : [Trace {first_8}...](https://app.datadoghq.com/llm/traces?query=trace_id:{full_32_char_id}).
  • Fichier nouveau uniquement: Ne jamais modifier le code d'évaluateur existant ou les configurations d'expérience.
  • Honnête sur l'incertitude: Si moins de 5 traces soutiennent un évaluateur proposé, le marquer comme tentative.

Skills similaires