Crea un agente autónomo con Claude Sonnet 5: tool use, el loop agéntico y autoverificación — Cesar Ayala
← Todos los artículos

Crea un agente autónomo con Claude Sonnet 5: tool use, el loop agéntico y autoverificación

Claude Sonnet 5 (id claude-sonnet-5) es el Sonnet más agéntico hasta hoy: planea, usa herramientas y revisa su propia salida. Define tus tools como JSON Schema, corre un loop agéntico (de tool_use a tool_result hasta stop_reason end_turn), usa adaptive thinking con effort, agrega un paso de autoverificación y cachea el prefijo system+tools. Con el precio intro de $2/$10 hasta el 31 ago 2026, correr agentes reales sale barato.

Acaba de salir Claude Sonnet 5 — y viene hecho para agentes

Hoy, 30 de junio de 2026, Anthropic lanzó Claude Sonnet 5. Ya está vivo en todas las apps de Claude y en la Claude Platform/API, y según el anuncio es desde hoy el modelo por defecto en Free y Pro, además de estar disponible para Max, Team y Enterprise.

La frase oficial de Anthropic es directa: “the best combination of speed and intelligence” y “the most agentic Sonnet yet”. En corto: planea, usa herramientas (navegadores, terminales) y corre de forma autónoma a un nivel que hace unos meses exigía modelos mucho más grandes y caros. Es un salto sólido sobre Sonnet 4.6 en razonamiento, tool use, código y trabajo de conocimiento, con un rendimiento cercano a Opus 4.8 a un precio más bajo.

El id del modelo es claude-sonnet-5 — un id sin fecha, formato pinned-snapshot, que sirve tanto como id de API como de alias. No le agregues sufijo de fecha. En Bedrock es anthropic.claude-sonnet-5 y en Google Cloud claude-sonnet-5.

Pero este post no es cobertura de día de lanzamiento. Es un build. Vamos a armar un agente autónomo de verdad — que usa herramientas y revisa su propia salida — sobre la Messages API. Si todavía no tienes claro el concepto, lee primero qué es un agente de IA. Las fuentes oficiales: Anthropic — Introducing Claude Sonnet 5 y Models overview.

Por qué Sonnet 5 cambia las cuentas de los agentes

Mi opinión, etiquetada como tal: el titular para builders es COSTO × AGENCIA. Puedes correr agentes autónomos, que usan herramientas y se autoverifican, a precio de Sonnet.

Los números, como están al lanzamiento (confirma el precio vigente antes de presupuestar): el precio estándar es $3 por MTok de input y $15 por MTok de output. Hay un precio introductorio de $2 / $10 por MTok hasta el 31 de agosto de 2026, y después regresa a $3/$15. A precio estándar, eso es alrededor de 60% más barato por token que Opus 4.8 ($5/$25).

Ahora, seamos honestos con la brecha frente a Opus. En una evaluación de coding agéntico, Sonnet 5 anda en ~63.2% contra ~69.2% de Opus 4.8 y ~58.1% de Sonnet 4.6. Son cifras reportadas por Anthropic — revisa el anuncio para los nombres exactos de los evals. La lectura: Sonnet 5 cierra la mayor parte de la distancia a Opus 4.8 a una fracción del costo, pero Opus 4.8 sigue mandando en el coding agéntico más difícil.

Mi veredicto operativo: elige Sonnet 5 para cargas agénticas a escala; Opus 4.8 cuando necesites lo absolutamente top; y Haiku 4.5 ($1/$5) para las llamadas simples más baratas y rápidas. Sonnet 5 es el punto dulce de la gama de valor, no la frontera absoluta.

Specs que importan para agentes: contexto de 1M de tokens, 128k de output máximo (hasta 300k en la Batch API con el beta header output-300k-2026-03-24), knowledge cutoff confiable de enero de 2026 y latencia “Fast”. Para más fundamentos, /es/agentes-de-ia/.

Cómo funciona de verdad el loop agéntico

Antes de tocar código, quitemos la magia. El contrato es este: llega una tarea del usuario → Sonnet 5 planea → emite un bloque tool_use (con un nombre de herramienta, un input y un id único de tool_use) → tu código corre la herramienta real → tú agregas un bloque tool_result que referencia ese mismo tool_use_id → mandas todo de regreso → y el loop se repite.

La señal que vigilas es stop_reason. Mientras valga "tool_use", sigues iterando. Cuando cambie a "end_turn", el modelo ya tiene su respuesta final y te detienes.

Detalle clave de seguridad y control: el modelo nunca corre tus herramientas, solo las pide. Tú eres el runtime. Esa separación es toda la historia de control — tú decides qué se ejecuta, con qué permisos y con qué validación.

La diferencia con un prompt de una sola pasada: ahí obtienes una respuesta y ya. En el loop agéntico el modelo planea → usa herramientas → verifica → termina. Eso cuesta varias llamadas, y justo por eso el precio bajo de Sonnet 5 importa: hace que correr el loop multi-paso repetidamente salga barato. Y como esta superficie trae adaptive thinking, el razonamiento se intercala con la planeación entre llamadas a herramientas.

Tarea del usuarioLlega la petición; defines tools[] + system
Sonnet 5 planea y emite tool_usestop_reason = tool_use, con tool_use_id único
Tu código corre la herramientaTú eres el runtime; el modelo nunca ejecuta
Devuelves tool_resultMismo tool_use_id; lo agregas a messages
Loop hasta end_turnstop_reason = end_turn → respuesta final

Por qué la jugada cambia con el precio

Prompt de una pasada

  • Una sola llamada, una respuesta
  • Sin herramientas reales ni verificación
  • Barato pero limitado a lo que ya sabe el modelo
  • Bien para Q&A simple

Loop agéntico

  • Planea → usa herramientas → verifica → termina
  • Varias llamadas por tarea (más tokens)
  • Acciones reales: datos, búsquedas, escrituras
  • Viable a escala por el intro de $2/$10 (al lanzamiento)

Define tus herramientas (JSON Schema con descripciones prescriptivas)

Cada herramienta necesita tres cosas: un name, una descripción prescriptiva (arranca con “Llama a esta herramienta cuando…” — la descripción es tu volante de dirección) y un input_schema en JSON Schema con propiedades tipadas y campos required.

La calidad de la descripción importa más que el schema. Dile al modelo la situación exacta que debe disparar la llamada y para qué no usarla.

import anthropic

client = anthropic.Anthropic()

# Las tools y el system se mantienen ESTABLES entre turnos.
# Más adelante cacheamos este prefijo para abaratar el loop.
tools = [
    {
        "name": "get_exchange_rate",
        "description": (
            "Llama a esta herramienta cuando el usuario pida convertir "
            "un monto entre dos monedas o pregunte el tipo de cambio actual. "
            "NO la uses para precios históricos ni para criptomonedas."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "from_currency": {
                    "type": "string",
                    "description": "Código ISO 4217 de origen, ej. 'USD'.",
                },
                "to_currency": {
                    "type": "string",
                    "description": "Código ISO 4217 destino, ej. 'MXN'.",
                },
                "amount": {
                    "type": "number",
                    "description": "Monto a convertir en la moneda de origen.",
                },
            },
            "required": ["from_currency", "to_currency", "amount"],
        },
    },
    {
        "name": "lookup_invoice",
        "description": (
            "Llama a esta herramienta cuando el usuario referencie una "
            "factura por folio para consultar su estatus o total. "
            "NO la uses para crear ni modificar facturas."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "folio": {
                    "type": "string",
                    "description": "Folio de la factura, ej. 'F-2026-0042'.",
                }
            },
            "required": ["folio"],
        },
    },
]

Si quieres que estas herramientas hablen con sistemas reales (tu base de datos, tu ERP, un proveedor de pagos), el camino limpio es MCP: conecta tu agente a tus datos vía MCP. Y fija el modelo explícito: model="claude-sonnet-5".

Escribe el loop: entra tool_use, sale tool_result

Aquí está el corazón. El loop manual completo, ejecutable, con el contrato de ids al frente: el tool_use_id de tu tool_result tiene que coincidir con el id del bloque tool_use que emitió el modelo, o la API lo rechaza.

import json


def run_tool(name, tool_input):
    # Despacha por nombre a tu handler real.
    # Hazlos IDEMPOTENTES: un re-plan o reintento puede llamar dos veces.
    if name == "get_exchange_rate":
        # ...llamada real a tu fuente de tipo de cambio...
        return {"rate": 17.05, "result": tool_input["amount"] * 17.05}
    if name == "lookup_invoice":
        return {"folio": tool_input["folio"], "status": "pagada", "total": 4200.0}
    return {"error": f"herramienta desconocida: {name}"}


messages = [
    {"role": "user", "content": "¿Cuánto son 250 USD en MXN hoy?"}
]

MAX_ITERS = 8  # guarda anti-loop-infinito
for _ in range(MAX_ITERS):
    response = client.messages.create(
        model="claude-sonnet-5",
        max_tokens=2048,
        system="Eres un agente operativo. Verifica tu resultado antes de terminar.",
        tools=tools,
        messages=messages,
        thinking={"type": "adaptive"},          # adaptive only; NO budget_tokens
        output_config={"effort": "medium"},      # default es "high"; bájalo si es simple
        # SIN temperature / top_p / top_k: no se usan en esta superficie.
    )

    # Maneja pause_turn: el modelo pausó una operación larga; reanuda.
    if response.stop_reason == "pause_turn":
        messages.append({"role": "assistant", "content": response.content})
        continue

    # Para el resto de finales, agrega el turno del assistant
    # (incluye los bloques tool_use). La rama pause_turn ya hizo su append y salió.
    messages.append({"role": "assistant", "content": response.content})

    if response.stop_reason != "tool_use":
        # end_turn (u otro final): el agente terminó.
        final_text = "".join(
            b.text for b in response.content if b.type == "text"
        )
        print(final_text)
        break

    # Ejecuta cada tool_use y construye los tool_result con el id que coincide.
    tool_results = []
    for block in response.content:
        if block.type == "tool_use":
            output = run_tool(block.name, block.input)
            tool_results.append({
                "type": "tool_result",
                "tool_use_id": block.id,                       # <- DEBE coincidir
                "content": json.dumps(output, ensure_ascii=False),  # JSON válido
            })

    # El turno del usuario lleva los tool_result; el orden y los ids deben cuadrar.
    # (content también acepta una lista de bloques de contenido, no solo string.)
    messages.append({"role": "user", "content": tool_results})
else:
    print("Se alcanzó MAX_ITERS sin end_turn — revisa el agente.")

Tres cosas que no debes saltarte. Una: la guarda de max-iterations, para que un agente que se porta mal no itere para siempre. Dos: idempotencia en los handlers con efectos secundarios — usa dedup keys o check-before-write, porque tus herramientas pueden llamarse más veces de las que esperas (reintentos, re-planes). Tres: effort arranca en "high" por defecto en la Claude API y en Claude Code; bájalo a medium/low para despacho de herramientas más barato y rápido cuando la tarea es simple. Si vienes armando esto desde cero, mi guía base es cómo construir un agente de IA con Python.

  1. Define tus toolsname + descripción 'llama a esta cuando…' + input_schema (JSON Schema)
  2. Escribe el loopmessages.create con tools[], thinking adaptive, effort explícito
  3. Ejecuta y devuelvecorre cada tool_use; tool_result con tool_use_id que coincide
  4. Deja que se autoverifiqueque revise su salida contra los criterios de éxito antes de cerrar
  5. Detente en end_turnloop mientras stop_reason sea tool_use; guarda de max-iters

Deja que revise su propio trabajo (autoverificación)

Los partners de acceso temprano reportan que Sonnet 5 termina tareas donde Sonnets anteriores se quedaban cortos, y que revisa su propia salida sin que se lo pidas. Hagamos de eso una feature, no suerte.

Agrega un paso explícito de autoverificación: antes de aceptar end_turn, haz que el modelo (o una herramienta dedicada) revalide su resultado contra los criterios de éxito de la tarea y o confirme o vuelva a iterar para corregir.

Hay dos patrones. Uno: una instrucción en el system prompt que le ordene verificar antes de terminar (es lo que puse arriba). Dos: una herramienta verify_result que el agente debe llamar y que regresa pass/fail con razones.

verify_tool = {
    "name": "verify_result",
    "description": (
        "Llama a esta herramienta SIEMPRE antes de dar tu respuesta final. "
        "Revisa tu resultado contra los criterios de éxito de la tarea. "
        "Devuelve pass/fail con las razones."
    ),
    "input_schema": {
        "type": "object",
        "properties": {
            "claim": {"type": "string", "description": "El resultado a verificar."},
            "criteria_met": {"type": "boolean"},
            "reasons": {"type": "string"},
        },
        "required": ["claim", "criteria_met", "reasons"],
    },
}

Y como doble seguro, usa structured outputs con output_config.format para que la respuesta final regrese en un schema que valides en tu código, no solo en la cabeza del modelo. effort y format viven como llaves hermanas dentro de output_config:

response = client.messages.create(
    model="claude-sonnet-5",
    max_tokens=1024,
    messages=messages,
    output_config={
        "effort": "medium",
        "format": {
            "type": "json_schema",
            "schema": {
                "type": "object",
                "properties": {
                    "amount_mxn": {"type": "number"},
                    "rate_used": {"type": "number"},
                    "verified": {"type": "boolean"},
                },
                "required": ["amount_mxn", "rate_used", "verified"],
            },
        },
    },
)

La advertencia honesta: la autoverificación reduce errores, no los elimina. Mantén tus propias aserciones sobre cualquier acción con efectos secundarios.

El tool runner del SDK vs el loop manual — cuál usar

Hay dos caminos y conviene elegir a propósito.

El tool runner del SDK es el camino fácil: registras tus funciones de herramienta y el SDK maneja el loop tool_usetool_result por ti. Menos código, más rápido para salir a producción. Es un helper en beta: en Python usas client.beta.messages.tool_runner junto con el decorador @beta_tool (el equivalente en el SDK de TypeScript se llama toolRunner).

El loop manual es para control: lógica custom de reintentos/idempotencia, observabilidad y logging en cada llamada a herramienta, aprobaciones human-in-the-loop, o despacho no estándar.

Mi take: arranca con el tool runner del SDK para prototipar, y baja al loop manual cuando necesites instrumentar o poner compuertas en el loop. De cualquier forma el contrato de abajo es idéntico — stop_reason, tool_use_id que coincide, end_turn — así que lo que aprendiste aquí se transfiere.

La palanca de costo aplica a ambos: prompt caching con cache_control sobre el prefijo estable de system + tools. El array tools[] y el system prompt se repiten en cada turno del loop; cachéalos para no pagar input completo en cada iteración. En corridas multi-paso largas el ahorro es real, además del intro de $2/$10. Tengo una guía dedicada: cachea el prefijo system+tools para bajar costos.

Modeloclaude-sonnet-5 (id sin fecha, sin sufijo)
Toolstools[] con input_schema + 'llama a esta cuando…'
Loopitera mientras stop_reason sea tool_use
Thinkingadaptive + effort explícito (default high)
Verificaciónpaso de autoverificación antes de end_turn
Costocachea el prefijo system+tools; intro $2/$10 al lanzamiento

Migrar un agente que ya tienes en Sonnet 4.6

Si ya corres un agente, la migración es corta:

  • Cambia el string del modelo a claude-sonnet-5.
  • Si usabas extended thinking con budget_tokens, cámbiate a adaptive thinking: thinking={"type": "adaptive"}. budget_tokens no está soportado.
  • Re-afina effort: Sonnet 5 trae "high" por defecto, que puede costar más o correr más lento de lo que quieres para despacho simple de herramientas. Ponlo explícito.
  • Quita cualquier temperature/top_p/top_k: no se usan en esta superficie.
  • Confirma los breaking changes exactos contra la guía de migración de Anthropic antes de ir a producción — no confíes en esta lista a ciegas.

Preguntas frecuentes

¿Cuál es el id exacto del modelo? claude-sonnet-5 — pinned snapshot sin fecha, usado como id de API y como alias. Sin sufijo de fecha.

¿Cuánto cuesta? Estándar $3/$15 por MTok; intro $2/$10 hasta el 31 de agosto de 2026, luego regresa a $3/$15. Confirma el precio vigente al momento, ya que esto está recién salido.

¿Puedo fijar un budget de thinking? No — solo adaptive thinking (thinking={"type": "adaptive"}). Controlas la profundidad con output_config.effort, no con budget_tokens.

¿Es tan bueno como Opus 4.8? Cerca en tareas agénticas (~63.2% vs ~69.2% en un eval de coding agéntico — cifras reportadas por Anthropic; revisa el anuncio para los nombres exactos), pero Opus 4.8 sigue liderando en el coding agéntico más difícil. Sonnet 5 es la gama de valor.

¿Cómo evito que el loop corra para siempre? Pon una guarda de max-iterations y detente en stop_reason == "end_turn".

¿Qué tan grande es el contexto / output? Contexto de 1M de tokens y 128k de output máximo (hasta 300k en la Batch API con el beta header output-300k-2026-03-24).

A producción

La receta entera de un jalón: modelo claude-sonnet-5, tools[] con JSON Schema y descripciones “llama a esta cuando…”, loop sobre stop_reason igual a tool_use, adaptive thinking más effort, un paso de autoverificación, y cachea el prefijo system + tools.

Sonnet 5 hace que correr agentes autónomos que se autorrevisan salga barato — especialmente durante la ventana intro de $2/$10 hasta el 31 de agosto de 2026. Confirma siempre los ids de modelo, los precios y los campos de API contra la documentación de precios de Anthropic antes de mandar a producción. Esto está recién salido y se mueve rápido.