How to Build an AI Agent
How to Build an AI Agent
s02

Herramientas

Herramientas y Ejecución

One Handler Per Tool

120 LOC4 herramientasTool dispatch map
The loop stays the same; new tools register into the dispatch map

s01 > [ s02 ] s03 > s04 > s05 > s06 | s07 > s08 > s09 > s10 > s11 > s12

"Agregar una herramienta significa agregar un gestor" -- el bucle permanece igual; las nuevas herramientas se registran en el mapa de distribución.

Problema

Con solo bash, el agente usa el shell para todo. cat trunca de manera impredecible, sed falla con caracteres especiales, y cada llamada bash es una superficie de seguridad sin restricciones. Herramientas dedicadas como read_file y write_file te permiten aplicar sandboxing de rutas a nivel de herramienta.

La idea clave: agregar herramientas no requiere modificar el bucle.

Solución

+--------+      +-------+      +------------------+
|  User  | ---> |  LLM  | ---> | Tool Dispatch    |
| prompt |      |       |      | {                |
+--------+      +---+---+      |   bash: run_bash |
                    ^           |   read: run_read |
                    |           |   write: run_wr  |
                    +-----------+   edit: run_edit |
                    tool_result | }                |
                                +------------------+

El mapa de distribución es un dict: {nombre_herramienta: función_gestor}.
Una sola búsqueda reemplaza cualquier cadena if/elif.

Cómo Funciona

  1. Cada herramienta obtiene una función handler. El sandboxing de ruta previene el escape del espacio de trabajo.
def safe_path(p: str) -> Path:
    path = (WORKDIR / p).resolve()
    if not path.is_relative_to(WORKDIR):
        raise ValueError(f"Path escapes workspace: {p}")
    return path

def run_read(path: str, limit: int = None) -> str:
    text = safe_path(path).read_text()
    lines = text.splitlines()
    if limit and limit < len(lines):
        lines = lines[:limit]
    return "\n".join(lines)[:50000]
  1. El mapa de distribución vincula nombres de herramientas a gestores.
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"]),
}
  1. En el bucle, busca el gestor por nombre. El cuerpo del bucle en sí mismo no cambia desde s01.
for block in response.content:
    if block.type == "tool_use":
        handler = TOOL_HANDLERS.get(block.name)
        output = handler(**block.input) if handler \
            else f"Unknown tool: {block.name}"
        results.append({
            "type": "tool_result",
            "tool_use_id": block.id,
            "content": output,
        })

Agregar una herramienta = agregar un gestor + agregar una entrada de esquema. El bucle nunca cambia.

Qué Cambió Respecto a s01

ComponenteAntes (s01)Después (s02)
Herramientas1 (solo bash)4 (bash, read, write, edit)
DistribuciónLlamada bash codificadaTOOL_HANDLERS dict
Seguridad rutaNonesafe_path() sandbox
Bucle agenteSin cambiosSin cambios

Pruébalo

python agents/s02_tool_use.py
  1. Leer el archivo requirements.txt
  2. Crear un archivo llamado greet.py con una función greet(name)
  3. Editar greet.py para agregar un docstring a la función
  4. Leer greet.py para verificar que la edición funcionó