Notes pratiques : Conception du cycle d’amélioration de l’agent : Évaluation de niveau production
Guide pas à pas des notes pratiques : Conception du cycle d’amélioration de l’agent : Évaluation de niveau production : contrats, vérifications et emplacements pour du code à intégrer destinés aux équipes utilisant ce modèle.
Les notes suivantes reconstituent une approche pratique pour aborder le sujet « Architecting the Agent Improvement Loop: Production-Grade Eval Pipelines ». L’accent est mis sur les contrats, les vérifications et les placeholders de code interchangeables, plutôt que sur une présentation motivante.
Introduction
Lors de l’étape d’introduction, notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Enregistrez les temps d’exécution ainsi que le coût en tokens ou en requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite les factures inattendues lorsque le processus passe d’une démonstration à des environnements partagés. Créez un point de contrôle après les étapes coûteuses. Le redémarrage ne doit pas facturer à nouveau la même appel de LLM lorsque l’opérateur réessaie un nœud ultérieur.
Table des matières
Lors de l’étape de création du sommaire, notez d’abord les éléments requis : les données nécessaires, le signal de succès, ainsi que ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Conservez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans devoir lire l’ensemble du système. Créez des points de contrôle après les étapes coûteuses. Le mécanisme de reprise ne doit pas facturer à nouveau la même appel du LLM lorsque l’opérateur réessaie un nœud ultérieur.
Phase 1 : Fondations
Lors de la phase 1 de mise en place, notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Documentez ensemble le parcours normal et le parcours de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non traités font partie du produit, et non d’améliorations ultérieures. Créez un point de contrôle après les étapes coûteuses. Le système de reprise ne doit pas facturer à nouveau la même appel d’LLM lorsque l’opérateur tente à nouveau un nœud ultérieur.
Mise en place de l’environnement
Lors de l’étape de mise en place de l’environnement, notez d’abord les éléments essentiels : les entrées requises, le signal de succès, ainsi que ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Préférez des unités petites et testables plutôt que des scripts complexes. Lorsqu’une étape échoue, l’erreur doit indiquer une seule responsabilité et non un processus embrouillé. Créez des points de contrôle après les étapes coûteuses. Le système de reprise ne doit pas facturer à nouveau la même appel du LLM lorsque l’opérateur réessaie un nœud ultérieur.
# Install the libraries we need
pip install langgraph langchain langchain-openai langsmith
export LANGSMITH_TRACING=true
export LANGSMITH_API_KEY=your_langsmith_key
export LANGSMITH_PROJECT=agent-improvement-loop
export OPENAI_API_KEY=your_openai_key
# Step 1: Import the pieces we need
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langgraph.prebuilt import create_react_agent
# Step 2: Define two tools the agent can call
@tool
def get_account_info(account_id: str) -> str:
"""Look up basic information for an account by account_id."""
fake_accounts = {
"A100": "Account A100: plan=pro, status=active, email=ada@example.com",
"A200": "Account A200: plan=free, status=suspended, email=turing@example.com",
}
return fake_accounts.get(account_id, f"No account found with id {account_id}")
@tool
def get_recent_orders(account_id: str) -> str:
"""Return the three most recent orders for a given account_id."""
fake_orders = {
"A100": "Orders for A100: #9001 shipped, #9002 processing, #9003 returned",
"A200": "Orders for A200: no orders in the last 90 days",
}
return fake_orders.get(account_id, f"No orders for {account_id}")
# Step 3: Wire the tools into a ReAct agent
model = ChatOpenAI(model="gpt-4o-mini", temperature=0)
tools = [get_account_info, get_recent_orders]
agent = create_react_agent(model, tools)
Testons l’agent
Lorsque vous travaillez sur l’étape « Let’s test », notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de garantir l’intégrité des modifications ultérieures du code. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définez des vérifications de succès et refusez les terminations partielles silencieuses. Faites des points de contrôle après les étapes coûteuses. Le système de reprise ne doit pas facturer à nouveau la même appel du LLM lorsque l’opérateur réessaie un nœud ultérieur.
# Step 4: Invoke the agent with a simple query
result = agent.invoke({
"messages": [{"role": "user", "content": "What plan is account A100 on?"}]
})
for message in result["messages"]:
print(message.type, ":", message.content)
Lorsque vous travaillez sur l’étape « Let’s test », notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de garantir l’intégrité des modifications ultérieures du code. Gardez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans avoir à lire l’ensemble du graphe.
human : What plan is account A100 on?
ai :
tool : Account A100: plan=pro, status=active, email=ada@example.com
ai : Account A100 is on the pro plan.
Phase 2 : Collecte des traces
La phase de collecte des traces de la Phase 2 fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un transcript idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre. Documentez ensemble le parcours normal et le parcours de récupération. Les tentatives répétées, les contrôles humains et le traitement des messages non livrés font partie intégrante du produit, et non d’une mise en forme ultérieure. Maintenez l’état des graphes simple et typé. Les blocs imbriqués masquent le fait que tel nœud a écrit telle champ et perturbent la reprise après interruption.
Récupération des traces récentes depuis la production
Le retrait des traces récentes depuis l’étape fonctionne le mieux lorsqu’il est traité comme une surface mesurable. Capturez un transcript parfait, un cas d’échec et la note de rollback avant d’élargir le périmètre. Préférez des unités petites et testables aux scripts complexes. Lorsqu’une étape échoue, l’erreur doit indiquer une seule responsabilité plutôt qu’un processus embrouillé. Maintenez l’état du graphe plat et typé. Les blocs imbriqués cachent le fait que tel nœud a écrit tel champ et perturbent la reprise après interruption.
# Step 1: Create a LangSmith client
from langsmith import Client
from datetime import datetime, timedelta
client = Client()
# Step 2: List recent root runs from our project
recent_runs = list(client.list_runs(
project_name="agent-improvement-loop",
is_root=True,
start_time=datetime.utcnow() - timedelta(days=1),
))
print(f"Found {len(recent_runs)} root runs in the last 24 hours")
Found 42 root runs in the last 24 hours
Récupération des champs nécessaires
La phase de collecte des champs fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un exemplaire idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre. Considérez cette phase comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définites des vérifications de succès et refusez toute mise en œuvre partielle silencieuse. Maintenez l’état du graphe plat et typé. Les blocs imbriqués masquent le fait que tel nœud a écrit tel champ et perturbent la reprise après interruption. La phase de collecte des champs fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un exemplaire idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre. Gardez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les administrateurs peuvent auditer sans devoir lire l’ensemble du graphe.
# Step 3: Build a compact trace record from each run
def compact_trace(run):
return {
"run_id": str(run.id),
"inputs": run.inputs,
"outputs": run.outputs,
"error": run.error,
"latency_ms": (run.end_time - run.start_time).total_seconds() * 1000
if run.end_time else None,
"tool_calls": [
child.name for child in client.list_runs(
trace_id=run.trace_id, run_type="tool"
)
],
}
traces = [compact_trace(run) for run in recent_runs]
print(f"Collected {len(traces)} compact traces")
print("Example tool calls:", traces[0]["tool_calls"])
Collected 42 compact traces
Example tool calls: ['get_account_info']
# Step 4: Inspect the first trace end to end
import json
print(json.dumps(traces[0], indent=2, default=str))
{
"run_id": "b1d2e3f4-...",
"inputs": {"messages": [{"role": "user", "content": "What plan is account A100 on?"}]},
"outputs": {"messages": [{"role": "ai", "content": "Account A100 is on the pro plan."}]},
"error": null,
"latency_ms": 1423.51,
"tool_calls": ["get_account_info"]
}
Phase 3 : Enrichissement des traces avec des scores
Pour l’étape d’enrichissement des traces de la Phase 3, définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Documentez conjointement le parcours normal et les scénarios de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non livrés font partie intégrante du produit, et non d’améliorations ultérieures. Imposez une approbation humaine pour les actions qui entraînent des dépenses ou modifient des données de production. Une connexion au niveau du temps de compilation ne garantit pas la complétude fonctionnelle du produit.
Couche 1 : Vérification de la correction des outils basés sur du code
Pour l’étape basée sur le code de niveau 1, définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Préférez des unités petites et testables à des scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité plutôt qu’un processus embrouillé. Authentifiez au niveau du gateway et réautorisez au niveau du plan de données. Un token porteur seul ne constitue pas une frontière entre les tenants.
# Step 1: Define a rule that says which tool SHOULD be called for a given query
import re
def expected_tool_for_query(query: str) -> str:
q = query.lower()
if re.search(r"\b(order|shipment|shipped|return)\b", q):
return "get_recent_orders"
if re.search(r"\b(plan|account|status|email)\b", q):
return "get_account_info"
return "any"
def score_tool_correctness(trace):
query = trace["inputs"]["messages"][0]["content"]
expected = expected_tool_for_query(query)
actual = trace["tool_calls"][0] if trace["tool_calls"] else None
if expected == "any":
return 1.0
return 1.0 if actual == expected else 0.0
# Step 2: Score qualitative properties with an LLM judge
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
judge_model = ChatOpenAI(model="gpt-4o-mini", temperature=0)
judge_prompt = ChatPromptTemplate.from_messages([
("system", "You are an expert reviewer of AI agent responses. "
"Rate the response on a scale of 1 to 5 for helpfulness. "
"Respond with only a single integer, nothing else."),
("user", "User question: {question}\n\nAgent response: {response}\n\nScore:"),
])
def score_helpfulness(trace):
question = trace["inputs"]["messages"][0]["content"]
response = trace["outputs"]["messages"][-1]["content"]
chain = judge_prompt | judge_model
result = chain.invoke({"question": question, "response": response})
try:
return int(result.content.strip()) / 5.0
except ValueError:
return 0.0
Niveau 3 : la revue par l’auteur en tant que file d’annotation
Pour l’étape d’examen par l’auteur au niveau 3, définissez les entrées, le responsable de l’étape et les critères de fin avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Nommez les artefacts, définissez des vérifications de succès et refusez toute mise en œuvre partielle silencieuse. Faites approuver par un humain les actions qui entraînent des dépenses ou modifient des données de production. La connexion en temps de compilation ne correspond pas à une complétude opérationnelle. Pour l’étape d’examen par l’auteur au niveau 3, définissez les entrées, le responsable de l’étape et les critères de fin avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Gardez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans devoir lire l’intégralité du code.
aph.# Step 3: Model a human review signal
def score_human(trace, human_labels: dict) -> float | None:
run_id = trace["run_id"]
return human_labels.get(run_id)
human_labels = {
"b1d2e3f4-...": 1.0,
"c2e3f4g5-...": 0.0,
}
Combinaison des trois couches
Lors de l’étape de combinaison des trois couches, notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de maintenir l’honnêteté des modifications ultérieures du code. Documentez ensemble le parcours normal et le parcours de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non livrés font partie du produit, et non d’améliorations ultérieures. Créez un point de contrôle après les étapes coûteuses. Le système de reprise ne doit pas facturer à nouveau la même appel du LLM lorsque l’opérateur tente à nouveau un nœud ultérieur.
# Step 4: Run all three scorers and attach scores to each trace
def enrich(trace):
trace["scores"] = {
"tool_correctness": score_tool_correctness(trace),
"helpfulness": score_helpfulness(trace),
"human": score_human(trace, human_labels),
}
return trace
enriched = [enrich(t) for t in traces]
print(json.dumps(enriched[0]["scores"], indent=2))
{
"tool_correctness": 1.0,
"helpfulness": 0.8,
"human": null
}
Phase 4 : Découverte de motifs
Lors de la phase 4 de découverte des modèles, notez d’abord les exigences : entrées requises, signal de succès et conséquences en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Préférez des unités petites et testables aux scripts complexes. Lorsqu’une étape échoue, l’erreur doit indiquer une seule responsabilité plutôt qu’un processus embrouillé. Faites un point après les étapes coûteuses. Le système de reprise ne doit pas facturer à nouveau la même appel du LLM lorsque l’opérateur réessaie un nœud ultérieur.
Filtrage des échecs
Lors de la phase de filtrage des échecs, notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de garantir l’intégrité des modifications ultérieures du code. Considérez cette phase comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définez des vérifications de succès et refusez les terminations partielles silencieuses. Faites des points de contrôle après les étapes coûteuses. Le système de reprise ne doit pas facturer à nouveau la même appel du LLM lorsque l’opérateur réessaie un nœud ultérieur.
# Step 1: Keep only traces where at least one score is low
def is_low_scoring(trace):
scores = trace["scores"]
if scores["human"] is not None and scores["human"] < 0.5:
return True
if scores["tool_correctness"] < 0.5:
return True
if scores["helpfulness"] < 0.6:
return True
return False
failures = [t for t in enriched if is_low_scoring(t)]
print(f"{len(failures)} of {len(enriched)} traces flagged as low scoring")
Lors de la phase de filtrage des échecs, notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de garantir l’intégrité des modifications ultérieures du code. Gardez la configuration en dehors du code de l’application. Les fichiers d’environnement, les stocks de secrets et les flags fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans avoir à lire l’ensemble du graphe.
9 of 42 traces flagged as low scoring
Classement des échecs de clustering en catégories
La phase de classement des échecs de clustering en catégories fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un enregistrement exemplaire, un cas d’échec et la note de réversion avant d’élargir le périmètre. Documentez ensemble le parcours normal et le parcours de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non livrés font partie intégrante du produit, et non d’une mise en forme ultérieure. Maintenez l’état du graphe plat et typé. Les blocs imbriqués masquent le fait que tel nœud a écrit tel champ et perturbent la reprise après interruption.
# Step 2: Tag each failure with a category based on its scores
def categorize(trace):
scores = trace["scores"]
if scores["tool_correctness"] < 0.5:
return "wrong_tool"
if scores["helpfulness"] < 0.6 and scores["tool_correctness"] >= 0.5:
return "unhelpful_answer"
if scores["human"] is not None and scores["human"] < 0.5:
return "human_flagged"
return "other"
from collections import Counter
categories = Counter(categorize(t) for t in failures)
print(categories.most_common())
[('wrong_tool', 5), ('unhelpful_answer', 3), ('human_flagged', 1)]
# Step 3: Print the inputs and outputs for every wrong_tool failure
wrong_tool = [t for t in failures if categorize(t) == "wrong_tool"]
for trace in wrong_tool[:3]:
print("QUERY:", trace["inputs"]["messages"][0]["content"])
print("TOOLS CALLED:", trace["tool_calls"])
print("RESPONSE:", trace["outputs"]["messages"][-1]["content"])
print("---")
QUERY: Is my subscription active?
TOOLS CALLED: ['get_recent_orders']
RESPONSE: I could not find any recent orders to confirm your subscription status.
---
QUERY: Tell me about my subscription to your product
TOOLS CALLED: ['get_recent_orders']
RESPONSE: There are no recent orders for this account.
---
Phase 5 : Ensembles de tests hors ligne
La phase 5 de test hors ligne fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un transcript idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre. Préférez des unités petites et testables aux scripts complexes. Lorsqu’une étape échoue, l’échec doit pointer vers une seule responsabilité plutôt que vers un processus embrouillé. Maintenez l’état des graphes simple et typé. Les blocs imbriqués masquent le fait que tel nœud a écrit tel champ et perturbent la reprise après interruption.
Création du jeu de données
La phase de création du jeu de données fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un exemplaire idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre. Considérez cette phase comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définites des vérifications de succès et refusez toute mise à jour partielle silencieuse. Maintenez l’état du graphe plat et typé. Les blocs imbriqués masquent le fait que tel nœud a écrit telle champ et perturbent la reprise après interruption.
# Step 1: Create a LangSmith dataset for our agent
dataset_name = "agent-production-failures"
dataset = client.create_dataset(
dataset_name=dataset_name,
description="Production traces where the agent failed. Each example captures the user query and the expected tool call.",
)
print(f"Created dataset: {dataset.id}")
La phase de création du jeu de données fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un exemplaire idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre. Gardez la configuration en dehors du code de l’application. Les fichiers d’environnement, les stocks de secrets et les indicateurs fonctionnels doivent se trouver en un seul endroit que les opérateurs peuvent auditer sans avoir à lire l’ensemble du graphe.
Created dataset: 3f2a1b0c-...
# Step 2: Convert each failure trace into a dataset example
examples = []
for trace in failures:
query = trace["inputs"]["messages"][0]["content"]
expected_tool = expected_tool_for_query(query)
examples.append({
"inputs": {"question": query},
"outputs": {"expected_tool": expected_tool},
"metadata": {"category": categorize(trace), "source_run_id": trace["run_id"]},
})
client.create_examples(dataset_id=dataset.id, examples=examples)
print(f"Added {len(examples)} examples to {dataset_name}")
Added 9 examples to agent-production-failures
# Step 3: Write an evaluator that checks the agent's tool call against the expected tool
def tool_match_evaluator(inputs: dict, outputs: dict, reference_outputs: dict):
actual_messages = outputs.get("messages", [])
tool_called = None
for m in actual_messages:
if getattr(m, "tool_calls", None):
tool_called = m.tool_calls[0]["name"]
break
expected = reference_outputs["expected_tool"]
score = 1.0 if (expected == "any" or tool_called == expected) else 0.0
return {"key": "tool_match", "score": score}
La fonction cible
Pendant l’étape de la fonction cible, il convient de définir les entrées, le responsable de l’étape ainsi que les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu, sans avoir à deviner l’état caché. Documentez conjointement le parcours normal et les scénarios de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non traités font partie intégrante du produit, et non d’améliorations ultérieures. Imposez une approbation humaine pour les actions qui entraînent des dépenses ou modifient des données de production. Une connexion en temps de compilation ne garantit pas la complétude du processus métier.
# Step 4: Wrap our agent in a function that takes a LangSmith example and returns its output
def run_agent(inputs: dict) -> dict:
result = agent.invoke({
"messages": [{"role": "user", "content": inputs["question"]}]
})
return result
Phase 6 : Fermer le cycle
Pour la phase 6 de clôture, définissez les entrées, le responsable de l’étape et les critères d’achèvement avant de modifier du code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Préférez des unités petites et testables aux scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité plutôt qu’un processus embrouillé. Faites approuver par des humains les actions qui entraînent des dépenses ou modifient des données de production. La connexion en temps de compilation ne garantit pas la complétude du processus métier.
Exécution de l’évaluation
Pendant la phase d’évaluation, définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier du code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu, sans avoir à deviner l’état caché. Considérez cette phase comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définites des vérifications de succès et refusez toute finalisation partielle silencieuse. Faites approuver par un humain les cas où de l’argent est dépensé ou où des données de production sont modifiées. La connexion en temps de compilation ne revient pas à une finalisation complète du processus métier.
# Step 1: Run the evaluator across the dataset with the current agent
from langsmith.evaluation import evaluate
experiment_results = evaluate(
run_agent,
data=dataset_name,
evaluators=[tool_match_evaluator],
experiment_prefix="agent-v1-baseline",
max_concurrency=4,
)
Pendant la phase d’évaluation, il convient de définir les entrées, le responsable de l’étape ainsi que les critères d’arrêt avant de modifier du code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu, sans avoir à deviner l’état caché. La configuration doit être conservée en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans devoir lire l’ensemble du système.
View the evaluation results for experiment: 'agent-v1-baseline-...' at:
https://smith.langchain.com/o/.../datasets/.../compare?selectedSessions=...
9/9 runs | avg tool_match: 0.33
Déploiement de la correction et réévaluation
Lorsque vous travaillez sur la correction et le déploiement d’une fonctionnalité de livraison, notez d’abord les exigences du contrat : les données requises, le signal de succès, ainsi que ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de garantir l’honnêteté des modifications ultérieures du code. Documentez à la fois le parcours normal et les scénarios de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non livrés font partie intégrante du produit, et non d’améliorations apportées ultérieurement. Créez des points de contrôle après les étapes coûteuses. Le système de reprise ne doit pas facturer à nouveau la même appel d’LLM lorsque l’opérateur tente à nouveau un nœud ultérieur.
# Step 2: Update the tool docstring to teach the agent about subscriptions
@tool
def get_account_info_v2(account_id: str) -> str:
"""Look up basic information for an account, including plan, status,
subscription tier, and contact email. Use this tool for any question
about the account itself, including subscription status."""
fake_accounts = {
"A100": "Account A100: plan=pro, status=active, email=ada@example.com",
"A200": "Account A200: plan=free, status=suspended, email=turing@example.com",
}
return fake_accounts.get(account_id, f"No account found with id {account_id}")
tools_v2 = [get_account_info_v2, get_recent_orders]
agent_v2 = create_react_agent(model, tools_v2)
def run_agent_v2(inputs: dict) -> dict:
result = agent_v2.invoke({
"messages": [{"role": "user", "content": inputs["question"]}]
})
return result
# Step 3: Run the evaluator on the v2 agent
experiment_results_v2 = evaluate(
run_agent_v2,
data=dataset_name,
evaluators=[tool_match_evaluator],
experiment_prefix="agent-v2-docstring-fix",
max_concurrency=4,
)
View the evaluation results for experiment: 'agent-v2-docstring-fix-...' at:
https://smith.langchain.com/o/.../datasets/.../compare?selectedSessions=...
9/9 runs | avg tool_match: 0.89
Avant et après
Lors de l’étape « Avant et Après », notez d’abord les éléments requis pour le contrat : les données nécessaires, le signal de succès, ainsi que ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Préférez des unités petites et testables plutôt que des scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité et non un processus embrouillé. Faites des points de contrôle après les étapes coûteuses. Le système de reprise ne doit pas facturer à nouveau la même appel d’LLM lorsque l’opérateur réessaie un nœud ultérieur.
Before (v1): After (v2):
wrong_tool: 5 wrong_tool: 1
unhelpful: 3 unhelpful: 0
human: 1 human: 0
avg score: 0.33 avg score: 0.89
Évaluation et Tests
Lors de la phase d’évaluation et de test, écrivez d’abord le contrat : les entrées requises, le signal de succès, ainsi que ce qui se passe en cas d’échec partiel. Cette liste de contrôle garantit l’intégrité des modifications ultérieures du code. Considérez cette phase comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définites des vérifications de succès, et refusez les terminations partielles silencieuses. Faites des points de contrôle après les étapes coûteuses. Le système de reprise ne doit pas facturer à nouveau la même appel du LLM lorsque l’opérateur réessaie un nœud ultérieur. Lors de la phase d’évaluation et de test, écrivez d’abord le contrat : les entrées requises, le signal de succès, ainsi que ce qui se passe en cas d’échec partiel. Cette liste de contrôle garantit l’intégrité des modifications ultérieures du code. Conservez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans avoir à lire l’ensemble du système.
# Full loop driver
def run_improvement_loop():
# 1. Pull traces
runs = list(client.list_runs(
project_name="agent-improvement-loop",
is_root=True,
start_time=datetime.utcnow() - timedelta(days=1),
))
traces = [compact_trace(r) for r in runs]
# 2. Enrich with scores
enriched = [enrich(t) for t in traces]
# 3. Find failures and categorize them
failures = [t for t in enriched if is_low_scoring(t)]
categories = Counter(categorize(t) for t in failures)
# 4. Add failures to the dataset
new_examples = [{
"inputs": {"question": t["inputs"]["messages"][0]["content"]},
"outputs": {"expected_tool": expected_tool_for_query(
t["inputs"]["messages"][0]["content"])},
"metadata": {"category": categorize(t), "source_run_id": t["run_id"]},
} for t in failures]
if new_examples:
client.create_examples(dataset_id=dataset.id, examples=new_examples)
# 5. Re-evaluate the current agent against the full dataset
result = evaluate(
run_agent_v2,
data=dataset_name,
evaluators=[tool_match_evaluator],
experiment_prefix="daily-regression",
)
return {
"traces_collected": len(traces),
"failures_found": len(failures),
"by_category": dict(categories),
"examples_added": len(new_examples),
}
print(run_improvement_loop())
{
"traces_collected": 186,
"failures_found": 12,
"by_category": {"wrong_tool": 4, "unhelpful_answer": 6, "human_flagged": 2},
"examples_added": 12,
"regression_score": 0.87
}
Comment l’améliorer davantage
La phase « Comment l’améliorer » fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un transcript exemplaire, un cas d’échec et la note de rollback avant d’élargir le périmètre. Documentez ensemble le parcours normal et le parcours de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non livrés font partie intégrante du produit, et non d’une mise en forme ultérieure. Maintenez l’état des graphes simple et typé. Les blocs imbriqués masquent le fait que tel nœud a écrit telle champ et perturbent la reprise après interruption.
Liste de contrôle opérationnelle
Pour la phase de liste de contrôle opérationnelle, définissez les entrées, le responsable de l’étape et les critères de fin avant de modifier du code. Les opérateurs doivent pouvoir exécuter à nouveau l’étape à partir d’un point de contrôle connu, sans avoir à deviner l’état caché.
Enregistrez les temps d’exécution ainsi que le coût des jetons ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce du coût permet d’éviter des factures inattendues lorsque le processus passe de l’environnement de démonstration à des environnements partagés.
Assurez une approbation humaine pour les étapes qui engendrent des dépenses ou modifient des données de production. Une configuration en temps de compilation ne garantit pas une couverture complète des besoins métier.
Suivez le coût et la latence en même temps que la qualité. Une réponse légèrement moins bonne mais coûtant 10 fois moins peut être le meilleur compromis pour la production.
Fixez les versions des dépendances et enregistrez le résumé de l’image ayant été utilisée pour la démonstration. La reproductibilité vaut mieux que les connaissances propres à un groupe.
Préférez des unités petites et testables plutôt que des scripts complexes. Lorsqu’une étape échoue, l’erreur doit pointer vers une seule responsabilité et non vers un processus embrouillé.
Au préalable de promouvoir le stack, figez les versions, conservez une transcription « or » pour le chemin critique et confirmez les étapes de rollback. Les environnements partagés nécessitent des limites de débit, des vérifications de location ainsi qu’un responsable clair pour la rotation des secrets. Préférez une fiabilité banale à des démonstrations ingénieuses ponctuelles.
Note de lot pour febbeae44915 : gardez les clés du fournisseur hors du repo, fixez un plafond pour les tokens par session et stockez les transcriptions à côté des fichiers de configuration d’évaluation afin que les remplacements ultérieurs de modèles restent comparables.
Lorsque vous travaillez sur l’étape 0 des mesures de renforcement de sécurité, écrivez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Préférez des unités petites et testables à des scripts complexes. Lorsqu’une étape échoue, l’échec doit pointer vers une seule responsabilité plutôt que vers un processus embrouillé.
Détail de renforcement 0/961 : mesurez le temps d’exécution, la classe d’erreur et la consommation de tokens pour cette note, puis décidez si vous souhaitez conserver le changement en vous basant sur un ensemble de questions prédéfini plutôt que sur des anecdotes.
La première étape de la note de renforcement fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un enregistrement idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre. Notez les temps d’exécution ainsi que le coût en tokens ou en requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite les factures inattendues lorsque le processus passe de l’environnement de démonstration aux environnements partagés.
Détail de renforcement 1/961 : mesurez le temps d’exécution, la classe d’erreur et la consommation de tokens pour cette note, puis décidez si vous souhaitez conserver le changement en vous basant sur un ensemble de questions prédéfini plutôt que sur des anecdotes.
Pour la deuxième étape de l’amélioration de sécurité, définissez les entrées, le responsable de l’étape et les critères d’achèvement avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Documentez conjointement le parcours normal et le parcours de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non livrés font partie intégrante du produit, et non d’une mise en forme ultérieure.
Détail 2/961 de l’amélioration de sécurité : mesurez le temps d’exécution, la catégorie d’erreur et l’utilisation des tokens pour cette étape, puis décidez s’il convient de conserver la modification en vous basant sur un ensemble fixe de critères plutôt que sur des observations subjectives.
Lors de la réalisation de l’étape 3 des notes de renforcement, notez d’abord les conditions contractuelles : entrées requises, signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définez des vérifications de succès et refusez les terminaisons partielles silencieuses.
Détail de renforcement 3/961 : mesurez le temps d’exécution, la classe de l’erreur et la consommation de tokens pour cette note, puis décidez si vous souhaitez conserver la modification en vous basant sur un ensemble de questions prédéfini plutôt que sur des observations subjectives.
L’étape 4 des notes de renforcement fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un exemple idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre. Gardez la configuration en dehors du code de l’application. Les fichiers d’environnement, les stocks de secrets et les flags fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans devoir lire l’ensemble du système.
Détail de renforcement 4/961 : mesurer le temps d’exécution, la classe d’erreur et la consommation de tokens pour cette note, puis décider de conserver ou non le changement en se basant sur un ensemble de questions prédéfini plutôt que sur des anecdotes.
Pour la phase 5 de la note de renforcement, définir les entrées, le responsable de l’étape et les critères d’achèvement avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Préférer des unités petites et testables à des scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité plutôt qu’un processus embrouillé.
Détail de renforcement 5/961 : mesurer le temps d’exécution, la classe d’erreur et la consommation de tokens pour cette note, puis décider de conserver ou non le changement en se basant sur un ensemble de questions prédéfini plutôt que sur des anecdotes.
Lors de l’étape 6 des notes de renforcement, notez d’abord les éléments essentiels : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Enregistrez les temps d’exécution ainsi que le coût des jetons ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite les factures inattendues lorsque le processus passe de l’environnement de démonstration à des environnements partagés.
Détail 6/961 du renforcement : mesurez le temps d’exécution réel, la catégorie de l’erreur et la consommation de jetons pour cette note, puis décidez si vous souhaitez conserver la modification en vous basant sur un ensemble de critères prédéfinis plutôt que sur des observations subjectives.
L’étape 7 des notes de renforcement fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un exemple idéal de fonctionnement, un cas d’échec et la note de réversion avant d’élargir le périmètre. Documentez ensemble le parcours normal et le parcours de récupération. Les tentatives de répétition, les contrôles humains et la gestion des messages non livrés font partie intégrante du produit, et non d’améliorations ultérieures.
Détail de renforcement 7/961 : mesurer le temps d’exécution, la classe d’erreur et la consommation de tokens pour cette note, puis décider de conserver ou non le changement en se basant sur un ensemble de questions prédéfini plutôt que sur des anecdotes.
Pour l’étape 8 de la note de renforcement, définir les entrées, le responsable de l’étape et les critères de fin avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Considérer cette étape comme un contrat entre les entrées et les sorties validées. Donner des noms aux artefacts, définir des vérifications de succès et refuser toute complétion partielle silencieuse.
Détail de renforcement 8/961 : mesurer le temps d’exécution, la classe d’erreur et la consommation de tokens pour cette note, puis décider de conserver ou non le changement en se basant sur un ensemble de questions prédéfini plutôt que sur des anecdotes.
Lors de l’exécution de l’étape 9 des notes de renforcement, notez d’abord les éléments essentiels : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code.
Gardez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les administrateurs peuvent auditer sans devoir lire l’ensemble du système.
Détail de renforcement 9/961 : mesurez le temps d’exécution, la classe de l’erreur et l’utilisation des tokens pour cette note, puis décidez si vous souhaitez conserver la modification en vous basant sur un ensemble de critères prédéfinis plutôt que sur des observations subjectives.
L’étape 10 des notes de renforcement fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un exemple idéal de fonctionnement, un cas d’échec et une note de réversion avant d’élargir le périmètre des travaux.
Préférez des unités petites et testables plutôt que des scripts complexes. Lorsqu’une étape échoue, l’erreur doit permettre d’identifier une seule responsabilité plutôt qu’un processus embrouillé.
Détail de renforcement 10/961 : mesurer le temps d’exécution, la classe d’erreur et l’utilisation des tokens pour cette note, puis décider de conserver ou non le changement en se basant sur un ensemble fixe de questions plutôt que sur des anecdotes.