Configurer Agent Observability
Si vous n’avez pas encore configuré Agent Observability :
Installer Agent Observability Python SDK:
pip install ddtrace>=4.3.0
Activer Agent Observability:
from ddtrace.llmobs import LLMObs
LLMObs.enable(
api_key="<YOUR_API_KEY>", # defaults to DD_API_KEY environment variable
app_key="<YOUR_APP_KEY>", # defaults to DD_APP_KEY environment variable
site="datadoghq.com", # defaults to DD_SITE environment variable
project_name="<YOUR_PROJECT>" # defaults to DD_LLMOBS_PROJECT_NAME environment variable, or "default-project" if the environment variable is not set
)
Vous devez fournir à la fois un api_key et app_key.
Corrélation de trace APM
Pour corréler vos spans d’expérience avec les APM Traces, exécutez Agent Observability via un Datadog Agent et maintenez agentless_enabled réglé sur False (la valeur par défaut). L’Agent transfère les données de trace à APM, ce qui permet la corrélation Expérience ↔ APM Trace.
LLMObs.enable(
api_key="<YOUR_API_KEY>",
app_key="<YOUR_APP_KEY>",
site="datadoghq.com",
agentless_enabled=False, # default — required for APM Trace correlation
project_name="<YOUR_PROJECT>",
)
Si vous exécutez sans Agent (par exemple, dans un notebook ou un environnement CI), vous pouvez définir agentless_enabled=True, mais les spans APM correspondants ne sont pas générés pour les spans d’expérience issus d’exécutions sans Agent.
Créer un projet
Les projets constituent la couche organisationnelle principale de LLM Experiments. Tous les jeux de données et toutes les expériences résident dans un projet.
Vous pouvez créer un projet manuellement dans la console Datadog, via l’API ou le SDK en spécifiant un nom de projet qui n’existe pas encore dans LLMObs.enable.
LLMObs.enable(
...
project_name="<YOUR_PROJECT>" # defaults to DD_LLMOBS_PROJECT_NAME environment variable, or "default-project" if the environment variable is not set
)
Créer un jeu de données
Un jeu de données est une collection d’entrées, de sorties attendues et de métadonnées qui représentent les scénarios sur lesquels vous souhaitez tester votre agent. Chaque jeu de données est associé à un projet.
- entrée (obligatoire) : représente toutes les informations auxquelles l’agent peut accéder lors d’une tâche.
- sortie attendue (facultatif) : également appelée vérité terrain, représente la réponse idéale que l’agent devrait fournir. Vous pouvez utiliser la sortie attendue pour stocker la sortie réelle de l’application, ainsi que tout résultat intermédiaire que vous souhaitez évaluer.
- métadonnées (facultatif) : contiennent toute information utile pour catégoriser l’enregistrement et l’utiliser pour une analyse ultérieure. Par exemple : sujets, tags, descriptions, notes.
Pour créer un jeu de données à partir d’un fichier CSV, utilisez LLMObs.create_dataset_from_csv() :
# Create dataset from CSV
dataset = LLMObs.create_dataset_from_csv(
csv_path="questions.csv",
dataset_name="capitals-of-the-world",
project_name="capitals-project", # Optional: defaults to the project name from LLMObs.enable
description="Geography quiz dataset", # Optional: Dataset description
input_data_columns=["question", "category"], # Columns to use as input
expected_output_columns=["answer"], # Optional: Columns to use as expected output
metadata_columns=["difficulty"], # Optional: Additional columns as metadata
csv_delimiter="," # Optional: Defaults to comma
)
# Example "questions.csv":
# question,category,answer,difficulty
# What is the capital of Japan?,geography,Tokyo,medium
# What is the capital of Brazil?,geography,Brasília,medium
Consultez Datasets pour plus d’informations sur les jeux de données, notamment : comment créer manuellement des jeux de données, comment récupérer et gérer des jeux de données, et comment Datadog conserve les versions des jeux de données.
Créer une expérience
Une expérience vous permet de tester systématiquement votre application LLM en exécutant votre agent sur un ensemble de scénarios issus de votre jeu de données et en mesurant les performances par rapport aux sorties attendues à l’aide d’évaluateurs. Vous pouvez ensuite comparer les performances de différentes configurations d’application, côte à côte.
task : définit le workflow principal que vous souhaitez évaluer. Cela peut aller d’un simple appel LLM à un flux plus complexe impliquant plusieurs appels LLM et étapes RAG. La tâche est exécutée séquentiellement sur tous les enregistrements du jeu de données.
évaluateur : une fonction, exécutée sur chaque enregistrement, qui mesure les performances du modèle ou de l’agent. Les évaluateurs vous permettent de comparer la sortie à la sortie attendue ou à l’entrée d’origine.
évaluateurs de résumé : fonctions facultatives exécutées sur toutes les données de l’expérience (entrées, sorties, résultats attendus, résultats des évaluations). Les évaluateurs de résumé vous permettent de calculer des métriques plus avancées, comme la précision, le rappel et l’exactitude, sur l’ensemble de votre jeu de données.
Pour créer une expérience :
1. Charger un jeu de données
from ddtrace.llmobs import LLMObs
from typing import Dict, Any, Optional, List
dataset = LLMObs.pull_dataset("capitals-of-the-world")
2. Définir une fonction de tâche qui traite un seul enregistrement de jeu de données
def task(input_data: Dict[str, Any], config: Optional[Dict[str, Any]] = None) -> str:
question = input_data["question"]
# Your LLM or processing logic here
return "Beijing" if "China" in question else "Unknown"
Une tâche peut accepter n’importe quel type non nul comme input_data (chaîne, nombre, booléen, objet, tableau). La sortie qui sera utilisée dans les évaluateurs peut être de n’importe quel type.
Cet exemple génère une chaîne de caractères, mais un dict peut être généré en sortie pour stocker toute information intermédiaire et effectuer des comparaisons dans les évaluateurs.
Optionnellement, votre fonction de tâche peut accepter un troisième paramètre metadata pour recevoir les métadonnées de l’enregistrement du jeu de données :
def task(input_data: Dict[str, Any], config: Optional[Dict[str, Any]] = None, metadata: Optional[Dict[str, Any]] = None) -> str:
difficulty = metadata.get("difficulty", "unknown") if metadata else "unknown"
question = input_data["question"]
return "Beijing" if "China" in question else "Unknown"
Vous pouvez tracer les différentes parties de votre task d’expérience (workflow, appels d’outils, etc.) en utilisant les mêmes décorateurs de traçage que ceux utilisés en production.
Si vous utilisez un framework pris en charge (OpenAI, Amazon Bedrock, etc.), Agent Observability trace et annote automatiquement les appels aux frameworks et bibliothèques LLM, vous offrant une observabilité prête à l’emploi pour les appels effectués par votre application LLM.
Utilisation des spans OpenTelemetry dans les expériences
Si votre application utilise l’instrumentation OpenTelemetry, vous pouvez créer des spans OTel dans votre task d’expérience. Avec DD_TRACE_OTEL_ENABLED=1, ddtrace agit comme le OpenTelemetry TracerProvider, de sorte que les spans OTel apparaissent automatiquement comme enfants du span d’expérience.
import json
from opentelemetry import trace
tracer = trace.get_tracer(__name__)
def task(input_data: Dict[str, Any], config: Optional[Dict[str, Any]] = None) -> str:
question = input_data["question"]
# OTel gen_ai span — automatically becomes a child of the experiment span
with tracer.start_as_current_span("my-llm-call") as span:
span.set_attribute("gen_ai.operation.name", "chat")
span.set_attribute("gen_ai.system", "openai")
span.set_attribute("gen_ai.request.model", "gpt-4o")
span.set_attribute("gen_ai.usage.input_tokens", 25)
span.set_attribute("gen_ai.usage.output_tokens", 8)
span.set_attribute(
"gen_ai.input.messages",
json.dumps([{"role": "user", "parts": [{"type": "text", "content": question}]}]),
)
result = call_my_llm(question)
span.set_attribute(
"gen_ai.output.messages",
json.dumps([{"role": "assistant", "parts": [{"type": "text", "content": result}]}]),
)
return result
Pour activer cela, définissez la variable d’environnement DD_TRACE_OTEL_ENABLED :
DD_TRACE_OTEL_ENABLED=1 python my_experiment.py
3. Définissez les évaluateurs
Les évaluateurs mesurent les performances de votre modèle ou agent sur chaque enregistrement. Vous pouvez définir des évaluateurs en utilisant deux approches :
- Basé sur des fonctions: Définissez une fonction qui reçoit
input_data, output_data et expected_output en tant qu’arguments distincts. Idéal pour des évaluateurs ponctuels avec une logique simple. - Basés sur des classes: Sous-classer
BaseEvaluator pour des évaluateurs réutilisables avec une configuration personnalisée. Les Évaluateurs basés sur des classes reçoivent un objet EvaluatorContext avec le contexte complet du span.
Pour des informations détaillées sur la création d’évaluateurs, y compris la référence complète du modèle de données et les meilleures pratiques, consultez le Evaluation Developer Guide.
Datadog prend en charge les types de retour des évaluateurs suivants :
- Booléen : renvoie true ou false
- score : renvoie une valeur numérique (flottante)
- catégoriel : renvoie une catégorie étiquetée (chaîne)
- json : renvoie des données structurées (dict)
Vous pouvez également renvoyer :
- Un
EvaluatorResult pour capturer des données d’évaluation plus riches, telles que reasoning, assessment ("pass" ou "fail"), metadata et tags. - A
MultiEvaluatorResult pour émettre plusieurs métriques nommées à partir d’un seul appel d’évaluateur. Pour plus de détails et d’exemples, consultez le Guide du développeur d’évaluation.
Évaluateurs basés sur des fonctions
def exact_match(input_data: Dict[str, Any], output_data: str, expected_output: str) -> bool:
return output_data == expected_output
def overlap(input_data: Dict[str, Any], output_data: str, expected_output: str) -> float:
expected_output_set = set(expected_output)
output_set = set(output_data)
intersection = len(output_set.intersection(expected_output_set))
union = len(output_set.union(expected_output_set))
return intersection / union
def fake_llm_as_a_judge(input_data: Dict[str, Any], output_data: str, expected_output: str) -> EvaluatorResult:
fake_llm_call = "excellent"
return EvaluatorResult(
value=fake_llm_call,
reasoning="the model explains itself",
assessment="pass", # or fail
tags={"task": "judge_llm_call"},
)
# Return multiple metrics from one evaluator call
from ddtrace.llmobs import MultiEvaluatorResult
def multi_metric_evaluator(input_data, output_data, expected_output):
correct = output_data == expected_output
return MultiEvaluatorResult(
{
"correct": EvaluatorResult(value=correct, assessment="pass" if correct else "fail"),
"length": len(str(output_data)),
}
)
# Emitted as: multi_metric_evaluator-correct, multi_metric_evaluator-length
Évaluateurs basés sur des classes
from ddtrace.llmobs import BaseEvaluator, EvaluatorContext, EvaluatorResult
class SemanticSimilarityEvaluator(BaseEvaluator):
def __init__(self, threshold: float = 0.8):
super().__init__(name="semantic_similarity")
self.threshold = threshold
def evaluate(self, context: EvaluatorContext) -> EvaluatorResult:
score = compute_similarity(context.output_data, context.expected_output)
return EvaluatorResult(
value=score,
reasoning=f"Similarity score: {score:.2f}",
assessment="pass" if score >= self.threshold else "fail",
)
4. (Facultatif) Définissez les évaluateurs de résumé
Les évaluateurs de résumé s’exécutent après que tous les évaluateurs au niveau de l’enregistrement ont terminé, et reçoivent les résultats agrégés pour calculer des statistiques au niveau du jeu de données, telles que des moyennes ou des taux de réussite. Tout comme les Évaluateurs au niveau de l’enregistrement, vous pouvez définir des évaluateurs de résumé sous forme de fonctions ou de classes.
Pour l’approche basée sur des classes utilisant BaseSummaryEvaluator, consultez le Evaluation Developer Guide.
Évaluateurs de résumé basés sur des fonctions
def num_exact_matches(inputs, outputs, expected_outputs, evaluators_results):
return evaluators_results["exact_match"].count(True)
Les fonctions d’évaluateurs de résumé peuvent accepter une liste de n’importe quel type non nul comme inputs (chaîne, nombre, booléen, objet, tableau) ; outputs et expected_outputs peuvent être des listes de n’importe quel type. evaluators_results est un dict de listes de résultats provenant des évaluateurs, indexé par le nom de la fonction d’évaluateur. Par exemple, dans l’extrait de code ci-dessus, l’évaluateur de résumé num_exact_matches utilise les résultats (une liste de Booleans) de l’évaluateur exact_match pour fournir un nombre de correspondances exactes.
Évaluateurs de résumé basés sur des classes
from ddtrace.llmobs import BaseSummaryEvaluator, SummaryEvaluatorContext
class AverageScoreEvaluator(BaseSummaryEvaluator):
def __init__(self, target_evaluator: str):
super().__init__(name="average_score")
self.target_evaluator = target_evaluator
def evaluate(self, context: SummaryEvaluatorContext):
scores = context.evaluation_results.get(self.target_evaluator, [])
if not scores:
return None
return sum(scores) / len(scores)
Datadog prend en charge les types de retour d’évaluateurs de résumé suivants :
- Booléen : renvoie true ou false
- score : renvoie une valeur numérique (flottante)
- catégoriel : renvoie une catégorie étiquetée (chaîne)
- json : renvoie des données structurées (dict)
5. Créez et exécutez l’expérience.
experiment = LLMObs.experiment(
name="capital-cities-test",
task=task,
dataset=dataset,
evaluators=[exact_match, overlap, fake_llm_as_a_judge],
summary_evaluators=[num_exact_matches], # optional
description="Testing capital cities knowledge",
config={
"model_name": "gpt-4",
"version": "1.0"
},
)
# Run the experiment
results = experiment.run() # Run on all dataset records
# Process results
for result in results.get("rows", []):
print(f"Record {result['idx']}")
print(f"Input: {result['input']}")
print(f"Output: {result['output']}")
print(f"Score: {result['evaluations']['evaluator']['value']}")
if result['error']['message']:
print(f"Error: {result['error']['message']}")
Pour augmenter la vitesse d’exécution de l’expérience, vous pouvez activer le traitement parallèle :
results = experiment.run(jobs=4)
Pour tester votre pipeline sur un sous-ensemble des données, utilisez :
results = experiment.run(sample_size=10)
Pour arrêter l’exécution de l’expérience si une erreur se produit, utilisez :
results = experiment.run(raise_errors=True)
6. Examinez les résultats de votre expérience dans Datadog.
print(f"View experiment: {experiment.url}")
Configurer Agent Observability
Si vous n’avez pas encore configuré Agent Observability :
Installez Agent Observability Node.js SDK:
Définissez la clé d’API, la clé d’application et le site Datadog :
export DD_API_KEY="<YOUR_API_KEY>"
export DD_APP_KEY="<YOUR_APP_KEY>"
export DD_SITE=<span class="js-region-param region-param" data-region-param="dd_site"></span>
Initialize the tracer with the ML application and Experiments project names:
const tracer = require('dd-trace').init({
llmobs: {
mlApp: 'capitals-app',
projectName: 'capitals-project'
}
})
Vous devez fournir les deux DD_API_KEY et DD_APP_KEY.
Créer un projet
Les projets contiennent des jeux de données et des expériences. Le SDK utilise le nom de projet Experiments configuré et
Le SDK utilise le nom de projet Experiments configuré et crée le projet s’il n’existe pas.
Définissez llmobs.projectName lors de l’initialisation du traceur. Vous pouvez également définir DD_LLMOBS_PROJECT_NAME. Si aucune des deux valeurs n’est
définies, le SDK utilise default-project.
Créez un jeu de données
Créez un jeu de données avec l’API tracer.llmobs.experiments :
const { experiments } = tracer.llmobs
const dataset = experiments.createDataset('capitals-of-the-world', {
description: 'Questions and expected capital cities',
records: [
{
inputData: { question: 'What is the capital of China?' },
expectedOutput: 'Beijing',
metadata: { difficulty: 'medium' }
},
{
inputData: { question: 'What is the capital of Japan?' },
expectedOutput: 'Tokyo',
metadata: { difficulty: 'medium' }
}
]
})
Le SDK envoie le jeu de données lorsque l’expérience démarre. Pour utiliser un jeu de données existant, appelez
await experiments.pullDataset('<DATASET_NAME>').
Créez une expérience
Une expérience exécute une tâche pour chaque enregistrement de jeu de données et évalue chaque résultat.
1. Définissez une tâche
La tâche reçoit l’entrée de l’enregistrement. Elle peut également recevoir la configuration de l’expérience et des métadonnées d’enregistrement facultatives.
function task (inputData) {
const { question } = inputData
// Add the LLM or agent call to evaluate.
return question.includes('China') ? 'Beijing' : 'Tokyo'
}
Les appels aux intégrations Node.js prises en charge au sein de la tâche
sont tracés en tant qu’enfants du span de l’expérience.
2. Définissez des évaluateurs
Un évaluateur reçoit l’entrée de l’enregistrement, la sortie de la tâche et la sortie attendue. Renvoyez un booléen, un nombre, une chaîne ou une valeur JSON
pour créer la métrique d’évaluation correspondante.
function exactMatch (_inputData, outputData, expectedOutput) {
return outputData === expectedOutput
}
3. Créez et exécutez l’expérience
async function runExperiment () {
const experiment = experiments.experiment({
name: 'capital-cities-test',
dataset,
task,
evaluators: {
exact_match: exactMatch
},
description: 'Testing capital cities knowledge',
config: {
modelName: 'gpt-4',
version: '1.0'
}
})
const result = await experiment.run()
console.log(`View experiment: ${result.url}`)
}
runExperiment().catch((error) => {
console.error(error)
process.exitCode = 1
})
Le résultat contient chaque enregistrement de sortie, score de l’évaluateur, erreur et une URL pour l’expérience dans Datadog.