Lección 02 · Fundamentos

El bucle no cambia, solo hay más herramientas

«El bucle no cambió ni una línea; solo añadí cosas al array TOOLS.» — s02_tool_use.py

⏱ ~10 min · 📝 3 widgets interactivos · 🧑‍💻 Basado en shareAI-lab · s02_tool_use.py

¿Qué hay que tocar para añadir una herramienta?

El agent de s01 solo sabe usar bash. Para que también pueda read_file / write_file / edit_file, ¿qué cambiarías?

La respuesta intuitiva es «el loop». Equivocada. El loop no necesita cambiar ni una línea. Solo hacen falta tres cosas:

  1. Escribir una función handler en Python (run_read(path, limit)).
  2. Registrarla en el mapa TOOL_HANDLERS ("read_file": lambda **kw: run_read(...)).
  3. Añadir una declaración JSON schema al array TOOLS (le dice al modelo cómo se llama la herramienta y qué parámetros acepta).

Cuando el bucle detecta un bloque tool_use, busca la función en el dispatch map usando block.name, la ejecuta y mete el resultado en un tool_result. Exactamente el mismo camino que sigue bash.

# Dispatch map: name → handler lambda
TOOL_HANDLERS = {
    "bash":       lambda **kw: run_bash(kw["command"]),
    "read_file":  lambda **kw: run_read(kw["path"], kw.get("limit")),
    "write_file": lambda **kw: run_write(kw["path"], kw["content"]),
    "edit_file":  lambda **kw: run_edit(kw["path"], kw["old_text"], kw["new_text"]),
}

Cómo funciona el dispatch

El siguiente widget te permite seleccionar una solicitud tool_use y ver cómo se enruta hacia la función Python correspondiente. Fíjate: block.name es lo que determina el camino.

safe_path: una línea de defensa que no se puede omitir

Dar acceso al sistema de archivos al agent trae un riesgo de seguridad clásico: path traversal. El modelo debería operar dentro de /home/user/project/, pero podría enviar read_file("../../etc/passwd").

s02 tiene una función pequeña que resuelve esto:

def safe_path(p: str) -> Path:
    path = (WORKDIR / p).resolve()  # normaliza, resuelve .. y symlinks
    if not path.is_relative_to(WORKDIR):
        raise ValueError(f"Path escapes workspace: {p}")
    return path

La clave está en la combinación .resolve() + .is_relative_to(): primero resuelve a ruta absoluta, luego comprueba que siga dentro del sandbox. Sin la primera, foo/../../etc podría pasar; sin la segunda, no hay ninguna comprobación.

Detecta si una ruta es segura

Los siguientes 5 paths son parámetros que el modelo podría pasar a read_file. ¿Cuáles deja pasar safe_path y cuáles rechaza? Asume WORKDIR = /home/user/project.

No añadas herramientas peligrosas al agent

Añadir herramientas es fácil, pero recuerda: cada herramienta que añades amplía la superficie de acción del modelo. s02 ya tiene una lista negra para bash (rm -rf /, sudo, shutdown) y restricciones safe_path para write_file. Antes de añadir una herramienta en producción, hazte tres preguntas:

  • ¿Puede el modelo hacer algo irreversible con esta herramienta? (rm, enviar emails, git push)
  • ¿Puede filtrar información sensible? (variables de entorno, .ssh, cookies)
  • ¿Podría la salida de error ser interpretada como instrucciones? (prompt injection via tool output)

La lección s07 añadirá una «capa de permisos» para sacar estas decisiones del código y declararlas de forma explícita.

Interactivo

Widget 1 · Tool Dispatch · cómo se enruta tool_use al handler

Selecciona una solicitud tool_use y observa cómo el bucle usa block.name para encontrar la función, ejecutarla y meter el resultado en un bloque tool_result.

Solicitudes tool_use del modelo
Mapa TOOL_HANDLERS

        
Resultado · bloque tool_result
(haz clic en cualquier tool_use para ver el enrutamiento)
Interactivo

Widget 2 · Safe Path · detección de path escape en 5 rutas

Cada ruta pasará por safe_path(). Asumiendo WORKDIR = /home/user/project, decide si cada una es permitida o rechazada — pon a prueba tu intuición sobre path traversal.

Correctas 0 / 5
Interactivo

Widget 3 · Add a Tool · qué hay que cambiar para añadir glob

Supón que quieres añadir una herramienta glob(pattern) para buscar archivos por patrón. Rellena los huecos: son tres lugares, y ninguno se puede omitir.

Lugar 1 · escribir la función handler
def run_glob(pattern: str) -> str:
    import glob
    return "\n".join(glob.glob(pattern))
Lugar 2 · registrar en el dispatch map (elige la línea correcta)
Lugar 3 · añadir schema al array TOOLS (elige la opción correcta)