genai · temario
4.4tema 4 de 5

AI Agents

Del prompt al sistema autónomo: herramientas, planificación y guardrails

Un agente es un LLM en un loop: recibe una tarea, elige una herramienta para avanzar, ejecuta esa herramienta, observa el resultado, y repite hasta completar la tarea o alcanzar un límite. La diferencia con un chatbot es el loop y las herramientas. El LLM no ejecuta código directamente — declara su intención de llamar una herramienta, el sistema la ejecuta, y le devuelve el resultado. El modelo decide qué herramienta usar y cuándo considerar la tarea terminada.

Las herramientas (tools) son funciones que el agente puede invocar: buscar en la web, ejecutar código Python, consultar una base de datos, llamar una API REST, leer un archivo. Cada tool tiene un nombre, una descripción en lenguaje natural (crítica — el modelo la usa para decidir cuándo usar la tool), y un schema de inputs. La calidad de las descripciones de las tools determina en gran medida qué tan bien planifica el agente.

La planificación es cómo el agente descompone una tarea compleja en pasos. Los frameworks modernos usan distintas estrategias: ReAct (Reason + Act) alterna entre un paso de razonamiento y un paso de acción; Plan-and-Execute genera un plan completo antes de ejecutar; Tree of Thought explora múltiples ramas en paralelo. Para la mayoría de casos de producción, ReAct es suficiente y más predecible que enfoques más complejos.

Los guardrails son las restricciones que evitan que el agente haga daño o entre en loops infinitos. Sin ellas, un agente mal diseñado puede: hacer llamadas infinitas (si no detecta que terminó), gastar presupuesto ilimitado de API, modificar datos que no debería, o confundirse con resultados de herramientas y generar hallucinations. Las guardrails básicas son: límite de iteraciones, timeout por llamada, lista blanca de herramientas permitidas, y verificación de que el resultado de la tool se inyectó correctamente antes del siguiente turno.

El manejo del resultado de las tools es el punto donde más fallan los agentes en desarrollo. Cuando el modelo llama una tool, el sistema debe ejecutarla y devolver el resultado en el formato correcto — típicamente un mensaje con role 'tool' que contiene el output. Si el sistema no maneja este paso, el modelo no recibe el resultado y puede inventarlo, llamar la misma tool en loop, o colgar indefinidamente esperando contexto que nunca llega.

Los agentes multi-herramienta necesitan un mecanismo de stop claro: una condición o herramienta especial que el modelo usa para señalar que terminó. Sin esta señal, el loop puede continuar aunque el objetivo ya se alcanzó. Una práctica común es incluir una tool ficticia llamada 'final_answer' o 'done' con un campo 'response' — cuando el modelo la llama, el loop termina y su input es la respuesta final.

structure.txt
# Minimal agent loop

tools = [search_tool, calculator_tool, final_answer_tool]
messages = [{'role': 'user', 'content': task}]

MAX_ITERATIONS = 10
for iteration in range(MAX_ITERATIONS):
    response = client.messages.create(
        model='claude-opus-4-5',
        max_tokens=2048,
        tools=tools,
        messages=messages
    )

    # Check stop condition FIRST
    if response.stop_reason == 'end_turn':
        final = extract_text(response)
        break

    # Find tool_use blocks
    tool_uses = [b for b in response.content if b.type == 'tool_use']
    if not tool_uses:
        raise AgentError('No tool call and no end_turn — unexpected state')

    # Append assistant message with tool calls
    messages.append({'role': 'assistant', 'content': response.content})

    # Execute each tool and append results
    tool_results = []
    for tool_use in tool_uses:
        result = execute_tool(tool_use.name, tool_use.input)
        tool_results.append({
            'type': 'tool_result',
            'tool_use_id': tool_use.id,   # must match — model checks this
            'content': str(result)
        })

    messages.append({'role': 'user', 'content': tool_results})

    # Handle final_answer tool
    for tu in tool_uses:
        if tu.name == 'final_answer':
            final = tu.input['response']
            break
    else:
        continue
    break
else:
    raise AgentError(f'Agent exceeded MAX_ITERATIONS ({MAX_ITERATIONS})')

Debugging lab

Detecta y corrige el error en el código.

0/5 tests passing0%
  1. 4.4.5.1

    # Agent loop without iteration limit while True: response = client.messages.create(model='claude-opus-4-5', tools=tools, messages=messages) # ... process response

  2. 4.4.5.2

    # Tool result without tool_use_id tool_results = [{'type': 'tool_result', 'content': str(result)}] messages.append({'role': 'user', 'content': tool_results})

  3. 4.4.5.3

    result = execute_tool(tool_use.name, tool_use.input) # no error handling tool_results.append({'type': 'tool_result', 'tool_use_id': tool_use.id, 'content': str(result)})

  4. 4.4.5.4

    # After processing all tool calls, no stop check for tool_use in tool_uses: if tool_use.name == 'search': result = search(tool_use.input['query']) # loop continues without checking for final_answer

  5. 4.4.5.5

    # Missing assistant message before tool_results tool_results = [{'type': 'tool_result', 'tool_use_id': tu.id, 'content': r} for tu, r in zip(tool_uses, results)] messages.append({'role': 'user', 'content': tool_results}) # no assistant msg appended first