Inicio / Artículos / Notas prácticas: Diseño del bucle de mejora del agente: Evaluación de nivel de producción

Notas prácticas: Diseño del bucle de mejora del agente: Evaluación de nivel de producción

Guía paso a paso para utilizar las notas prácticas: Diseño del bucle de mejora del agente: Evaluación de nivel de producción: contratos, verificaciones y espacios para código listo para usar para los equipos que implementan este patrón.

4955 palabras

Las notas siguientes reconstruyen un enfoque práctico relacionado con “Diseñando el ciclo de mejora del agente: pipelines de evaluación de nivel profesional”. Se da énfasis en los contratos, las verificaciones y los marcadores de código reutilizables, en lugar de en un enfoque motivacional.

Introducción

Al trabajar en la etapa de introducción, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación garantiza que los cambios posteriores en el código sean transparentes. Registre los tiempos y el costo de tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando se pasa de entornos de demostración a entornos compartidos. Haga una verificación después de los pasos costosos. La reanudación no debe volver a facturar la misma llamada al LLM cuando un operador intenta nuevamente un nodo posterior.

Índice

Al trabajar en la etapa del índice, anote primero el contrato: los datos necesarios, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Guarde la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de datos secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin tener que leer todo el sistema.

Fase 1: Fundamentos

Al trabajar en la etapa de fundamentos de Fase 1, anote primero el contrato: los datos necesarios, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Documente junto con ello el camino óptimo y el camino de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no de mejoras posteriores. Haga un punto de control después de los pasos costosos. El sistema de reanudación no debe volver a facturar la misma llamada al LLM cuando un operador vuelve a intentar un nodo posterior.

Configuración del entorno

Al trabajar en la fase de configuración del entorno, anote primero el contrato: los datos necesarios, la señal de éxito y qué ocurre en caso de un fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el fallo debe apuntar a una única responsabilidad y no a un proceso complicado. Haga una verificación después de los pasos costosos. La función de reanudación no debe volver a facturar la misma llamada al LLM cuando un operador intenta nuevamente un nodo posterior.

# 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)

Probemos el agente

Al trabajar en la etapa “Vamos a probar”, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación garantiza que los cambios posteriores en el código sean transparentes. Considere esta etapa como un contrato entre las entradas y los resultados validados. Asigne nombres a los artefactos, defina las comprobaciones de éxito y evite completaciones parciales silenciosas. Haga una verificación después de los pasos costosos. La función de reanudación no debe volver a facturar la misma llamada al LLM cuando un operador intenta nuevamente un nodo posterior.

# 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)

Al trabajar en la etapa “Vamos a probar”, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación garantiza que los cambios posteriores en el código sean transparentes. Mantenga la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin tener que leer todo el grafo.

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.

Fase 2: Recolección de registros

La etapa de recolección de registros de la Fase 2 funciona mejor cuando se trata como una superficie medible. Capture un registro de éxito, un caso de fallo y la nota de reversión antes de ampliar el alcance. Documente tanto el camino óptimo como el camino de recuperación juntos. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores. Mantenga el estado del grafo simple y tipado. Los bloques anidados ocultan qué nodo escribió qué campo e interrumpen la continuación después de las interrupciones.

Obtención de registros recientes desde la producción

Extraer rastros recientes de la etapa de desarrollo funciona mejor cuando se trata como una superficie medible. Capture un registro exitoso, un caso de fallo y la nota de reversión antes de ampliar el alcance. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el fallo debe apuntar a una única responsabilidad y no a un proceso complicado. Mantenga el estado del grafo simple y tipado. Los bloques anidados ocultan qué nodo escribió qué campo y provocan interrupciones en la continuación del proceso.

# 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

Recopilando los campos que necesitamos

La etapa de recopilación de campos funciona mejor cuando se trata como una superficie medible. Capture un registro ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Considere esta etapa como un contrato entre las entradas y las salidas validadas. Asigne nombres a los artefactos, defina verificaciones de éxito y rechace completaciones parciales silenciosas. Mantenga el estado del grafo plano y tipado. Los bloques anidados ocultan qué nodo escribió qué campo y provocan interrupciones en la continuación del proceso. La etapa de recopilación de campos funciona mejor cuando se trata como una superficie medible. Capture un registro ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Mantenga la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin tener que leer todo el grafo.

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

Fase 3: Enriquecimiento de huellas con puntuaciones

En la etapa de Enriquecimiento de huellas de la Fase 3, se deben definir las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Documente tanto la ruta óptima como la ruta de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores. Incluya la aprobación humana en aquellos casos en los que se gastan fondos o se modifican datos de producción. La conexión en tiempo de compilación no equivale a la completitud del proceso empresarial.

Capa 1: Verificación de corrección de herramientas basadas en código

En la etapa basada en código de Capa 1, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el error debe indicar una única responsabilidad y no un proceso complicado. Autentique en la pasarela de entrada y vuelva a autorizarlo en el plano de datos. Un token portador por sí solo no constituye un límite entre tenencias.

# 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

Capa 3: la revisión por el autor como cola de anotaciones

En la etapa de revisión por el autor de la Capa 3, se deben definir las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Considere esta etapa como un contrato entre las entradas y los resultados validados. Asigne nombres a los artefactos, defina verificaciones de éxito y rechace las completaciones parciales silenciosas. Incluya la aprobación humana en aquellos casos que impliquen gastos o cambios en datos de producción. La conexión en tiempo de compilación no equivale a la completitud del proceso empresarial. En la etapa de revisión por el autor de la Capa 3, se deben definir las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Mantenga la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de datos secretos y las banderas de funcionalidad deben estar en un lugar que los operadores puedan auditar sin tener que leer todo el contenido.

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

Combinar las tres capas

Al trabajar en la etapa de combinación de las tres capas, anote primero el contrato: entradas requeridas, señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Documente junto con ello el camino óptimo y el camino de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no de mejoras posteriores. Haga un punto de control después de los pasos costosos. Resume no debe volver a facturar la misma llamada al LLM cuando un operador vuelve a intentar un nodo posterior.

# 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
}

Fase 4: Descubrimiento de patrones

Al trabajar en la fase 4 de descubrimiento de patrones, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el fallo debe apuntar a una única responsabilidad y no a un proceso complicado. Haga una verificación después de los pasos costosos. La función de reanudación no debe volver a facturar la misma llamada al LLM cuando un operador intenta nuevamente un nodo posterior.

Filtrado de fallos

Al trabajar en la etapa de filtrado de fallos, primero escribe el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación garantiza que los cambios posteriores en el código sean transparentes. Considera esta etapa como un contrato entre las entradas y las salidas validadas. Nombra los artefactos, define las comprobaciones de éxito y rechaza las completaciones parciales silenciosas. Haz una verificación después de los pasos costosos. La función de reanudación no debe volver a facturar la misma llamada al LLM cuando un operador intenta nuevamente un nodo posterior.

# 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")

Al trabajar en la etapa de filtrado de fallos, primero escribe el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación garantiza que los cambios posteriores en el código sean transparentes. Mantén la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin tener que leer todo el grafo.

9 of 42 traces flagged as low scoring

Clasificación de los fallos en categorías

La fase de clasificación de los fallos en categorías funciona mejor cuando se trata como una superficie medible. Capture una transcripción clave, un caso de fallo y la nota de reversión antes de ampliar el alcance. Documente junto con ello el camino óptimo y el camino de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores. Mantenga el estado del grafo simple y tipado. Los bloques anidados ocultan qué nodo escribió qué campo e interrumpen la continuación después de las interrupciones.

# 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.
---

Fase 5: Conjuntos de pruebas sin conexión

La etapa de prueba sin conexión de Fase 5 funciona mejor cuando se trata como una superficie medible. Capture una transcripción ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Prefiera unidades pequeñas y probables sobre scripts extensos. Cuando un paso falla, el fallo debe apuntar a una única responsabilidad en lugar de a un proceso complicado. Mantenga el estado del gráfico simple y tipado. Los bloques anidados ocultan qué nodo escribió qué campo y provocan interrupciones en la continuación después de las interrupciones.

Creación del conjunto de datos

La etapa de creación del conjunto de datos funciona mejor cuando se trata como una superficie medible. Capture un registro ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Trate esta etapa como un contrato entre las entradas y las salidas validadas. Asigne nombres a los artefactos, defina verificaciones de éxito y rechace completaciones parciales silenciosas. Mantenga el estado del grafo plano y tipado. Los bloques anidados ocultan qué nodo escribió qué campo y provocan interrupciones en la continuación después de las interrupciones.

# 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 etapa de creación del conjunto de datos funciona mejor cuando se trata como una superficie medible. Capture un registro ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Mantenga la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin tener que leer todo el grafo.

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 función objetivo

En la fase de la función objetivo, se deben definir las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Se debe documentar tanto la ruta óptima como la ruta de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores. Se debe incluir la aprobación humana en aquellos casos en que se gastan fondos o se modifican datos de producción. La configuración en tiempo de compilación no equivale a la completitud del proceso empresarial.

# 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

Fase 6: Cerrar el ciclo

En la fase 6 de cierre, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido, sin tener que adivinar el estado oculto. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el error debe indicar una única responsabilidad y no un proceso complicado. Incluya la aprobación humana en aquellos casos que impliquen gastos o cambios en los datos de producción. La configuración en tiempo de compilación no equivale a la completitud del proceso empresarial.

Ejecución de la evaluación

En la fase de evaluación, defina las entradas, el responsable de cada paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido, sin tener que adivinar el estado oculto. Considere esta fase como un contrato entre las entradas y los resultados validados. Asigne nombres a los artefactos, defina verificaciones de éxito y rechace las completaciones parciales silenciosas. Incluya la aprobación humana en aquellos casos que impliquen gastos o cambios en los datos de producción. La configuración en tiempo de compilación no equivale a la completitud del proceso empresarial.

# 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,
)

Para la fase de evaluación, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido, sin tener que adivinar el estado oculto. Guarde la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de datos confidenciales y las banderas de funcionalidad deben encontrarse en un lugar donde los operadores puedan auditarlos sin necesidad de leer todo el sistema.

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

Envío de la corrección y reevaluación

Al trabajar en la solución y puesta en producción de un envío, anote primero el contrato: los datos requeridos, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Documente tanto el camino óptimo como el de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no de mejoras posteriores. Haga un punto de control después de los pasos costosos. Resume no debe volver a facturar la misma llamada al LLM cuando un operador vuelve a intentar un nodo posterior.

# 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

Antes y después

Al trabajar en la fase de antes y después, anote primero el contrato: los datos necesarios, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Prefiera unidades pequeñas y probables sobre scripts extensos. Cuando un paso falla, el fallo debe apuntar a una única responsabilidad y no a un proceso complicado. Haga una verificación después de los pasos costosos. La función de reanudación no debe volver a facturar la misma llamada al LLM cuando un operador intenta nuevamente un nodo posterior.

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

Evaluación y Pruebas

Al trabajar en la fase de Evaluación y Pruebas, anote primero el contrato: los datos de entrada requeridos, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación garantiza que los cambios posteriores en el código sean transparentes. Considere esta fase como un contrato entre las entradas y los resultados validados. Asigne nombres a los artefactos, defina las comprobaciones de éxito y evite completaciones parciales silenciosas. Haga puntos de control después de los pasos costosos. La función de reanudación no debe volver a facturar la misma llamada al LLM cuando un operador intenta nuevamente un nodo posterior. Al trabajar en la fase de Evaluación y Pruebas, anote primero el contrato: los datos de entrada requeridos, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación garantiza que los cambios posteriores en el código sean transparentes. Mantenga la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin tener que leer todo el sistema.

# 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
}

Cómo mejorarlo aún más

La etapa de “Cómo mejorarlo” funciona mejor cuando se trata como una superficie medible. Capture una transcripción ejemplar, un caso de fallo y la nota de reversión antes de ampliar el alcance. Documente tanto el camino óptimo como el de recuperación juntos. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores. Mantenga el estado del grafo simple y tipado; los bloques anidados ocultan qué nodo escribió qué campo y provocan interrupciones en la continuación del proceso.

Lista de verificación operativa

En la etapa de lista de verificación operativa, defina las entradas, el responsable de cada paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto.

Registre los tiempos de ejecución y el costo de tokens o consultas junto con los resultados funcionales. Ver la información sobre costos desde el principio evita facturas inesperadas cuando se pasa de entornos de demostración a entornos compartidos.

Coloque la aprobación humana en aquellos puntos donde se gastan fondos o se modifican datos de producción. La configuración en tiempo de compilación no equivale a una solución completa para el negocio.

Supervise los costos y la latencia junto con la calidad. Una respuesta ligeramente peor que cueste 10 veces menos podría ser la mejor opción para producción.

Fije las versiones de dependencias y registre el resumen de la imagen que ejecutó la demostración. La reproducibilidad es mejor que el conocimiento basado en prácticas internas.

Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando falla un paso, el error debe apuntar a una única responsabilidad y no a un proceso complicado.

Antes de promocionar la solución, congele las versiones, capture una transcripción de referencia para el camino crítico y confirme los pasos de reversión. Los entornos compartidos requieren límites de velocidad, verificaciones de tenencia y un responsable claro para la rotación de credenciales secretas. Prefiera una fiabilidad sencilla a demostraciones ingeniosas pero puntuales.

Nota para el lote febbeae44915: mantenga las claves del proveedor fuera del repositorio, establezca un límite para los tokens por sesión y almacene las transcripciones junto a los archivos de prueba para que los cambios posteriores en el modelo sigan siendo comparables.

Al trabajar en la etapa 0 de fortalecimiento, anote primero el contrato: entradas requeridas, señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación garantiza que los cambios posteriores en el código sean transparentes. Prefiera unidades pequeñas y probables a scripts extensos. Cuando falla un paso, el error debe indicar una única responsabilidad y no un proceso complicado.

Detalle de reforzamiento 0/961: mida el tiempo de ejecución, la clase de error y el consumo de tokens para esta nota, y luego decida si mantener el cambio basándose en un conjunto fijo de preguntas en lugar de en anécdotas.

La fase 1 de las notas de reforzamiento funciona mejor cuando se trata como una superficie medible. Capture una transcripción ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Registre los tiempos y el costo en tokens o consultas junto a los resultados funcionales. La visibilidad temprana de los costos evita facturas inesperadas cuando el proceso pasa de la demostración a entornos compartidos.

Detalle de reforzamiento 1/961: mida el tiempo de ejecución, la clase de error y el consumo de tokens para esta nota, y luego decida si mantener el cambio basándose en un conjunto fijo de preguntas en lugar de en anécdotas.

Para la fase 2 de las notas de fortalecimiento, defina los insumos, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Documente tanto la ruta óptima como la ruta de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras realizadas posteriormente.

Detalle de fortalecimiento 2/961: mida el tiempo de ejecución, la clase del error y el consumo de tokens para esta nota, y luego decida si mantener el cambio basándose en un conjunto fijo de preguntas en lugar de en anécdotas.

Al trabajar en la fase 3 de las notas de fortalecimiento, anote primero el contrato: los datos requeridos, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Trate esta fase como un contrato entre los datos de entrada y los resultados validados. Asigne nombres a los artefactos, defina las comprobaciones de éxito y rechace las completaciones parciales silenciosas.

Detalle de fortalecimiento 3/961: mida el tiempo de ejecución, la clase del error y el consumo de tokens para esta nota, y luego decida si mantener el cambio basándose en un conjunto fijo de preguntas en lugar de en anécdotas.

La fase 4 de las notas de fortalecimiento funciona mejor cuando se trata como una superficie medible. Capture una transcripción de referencia, un caso de fallo y la nota de reversión antes de ampliar el alcance. Mantenga la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de secretos y las banderas de funcionalidad deben estar en un lugar que los operadores puedan auditar sin tener que leer todo el sistema.

Detalle de fortalecimiento 4/961: mida el tiempo de ejecución, la clase de error y el consumo de tokens para esta nota, y luego decida si mantener el cambio basándose en un conjunto fijo de preguntas en lugar de en anécdotas.

Para la fase 5 de la nota de fortalecimiento, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Prefiera unidades pequeñas y verificables sobre scripts extensos. Cuando un paso falla, el fallo debe apuntar a una única responsabilidad en lugar de a un proceso complicado.

Detalle de fortalecimiento 5/961: mida el tiempo de ejecución, la clase de error y el consumo de tokens para esta nota, y luego decida si mantener el cambio basándose en un conjunto fijo de preguntas en lugar de en anécdotas.

Al trabajar en la fase 6 de las notas de fortalecimiento, anote primero el contrato: los datos necesarios, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Registre los tiempos y el costo de tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando el proceso pasa de la fase de demostración a entornos compartidos.

Detalle de fortalecimiento 6/961: mida el tiempo real empleado, la clase del error y el gasto en tokens para esta nota, y luego decida si mantener la modificación basándose en un conjunto fijo de preguntas en lugar de en observaciones anecdóticas.

La fase 7 de las notas de fortalecimiento funciona mejor cuando se trata como una superficie medible. Capture un registro ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Documente tanto el camino óptimo como el camino de recuperación. Los intentos repetidos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores.

Detalle de fortalecimiento 7/961: mida el tiempo de ejecución, la clase de error y el consumo de tokens para esta nota, y luego decida si mantener el cambio basándose en un conjunto fijo de preguntas en lugar de en anécdotas.

En la etapa 8 de la nota de fortalecimiento, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Trate esta etapa como un contrato entre las entradas y los resultados validados. Asigne nombres a los artefactos, defina verificaciones de éxito y rechace las completaciones parciales silenciosas.

Detalle de fortalecimiento 8/961: mida el tiempo de ejecución, la clase de error y el consumo de tokens para esta nota, y luego decida si mantener el cambio basándose en un conjunto fijo de preguntas en lugar de en anécdotas.

Al trabajar en la fase 9 de las notas de fortalecimiento, anote primero el contrato: los datos necesarios, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código.

Mantenga la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin tener que leer todo el sistema.

Detalle de fortalecimiento 9/961: mida el tiempo de ejecución, la clase del error y el consumo de tokens para esta nota, y luego decida si mantener el cambio basándose en un conjunto fijo de criterios en lugar de en observaciones anecdóticas.

La fase 10 de las notas de fortalecimiento funciona mejor cuando se trata como una superficie medible. Capture un registro ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance.

Prefiera unidades pequeñas y verificables sobre scripts extensos. Cuando un paso falla, el fallo debe apuntar a una sola responsabilidad y no a un proceso complicado.

Detalle de endurecimiento 10/961: mida el tiempo de ejecución, la clase de error y el consumo de tokens para esta nota, y luego decida si mantener el cambio basándose en un conjunto fijo de preguntas en lugar de en anécdotas.