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

Strumenti

Strumenti & Esecuzione

One Handler Per Tool

120 LOC4 strumentiTool 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

"Aggiungere uno strumento significa aggiungere un handler" -- il loop rimane lo stesso; i nuovi strumenti si registrano nella mappa di dispatch.

Problema

Con solo bash, l'agente fa tutto attraverso la shell. cat tronca in modo imprevedibile, sed fallisce su caratteri speciali, e ogni chiamata bash è una superficie di sicurezza non vincolata. Strumenti dedicati come read_file e write_file permettono di applicare sandboxing dei percorsi a livello di strumento.

L'intuizione chiave: aggiungere strumenti non richiede di cambiare il loop.

Soluzione

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

La mappa di dispatch è un dizionario: {tool_name: handler_function}.
Una ricerca sostituisce qualsiasi catena if/elif.

Come Funziona

  1. Ogni strumento ha una funzione handler. Il sandboxing dei percorsi previene l'uscita dal workspace.
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. La mappa di dispatch collega i nomi degli strumenti agli handler.
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. Nel loop, cerca l'handler per nome. Il corpo del loop stesso è invariato da 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,
        })

Aggiungi uno strumento = aggiungi un handler + aggiungi una voce nello schema. Il loop non cambia mai.

Cosa è Cambiato da s01

ComponentePrima (s01)Dopo (s02)
Strumenti1 (solo bash)4 (bash, read, write, edit)
DispatchChiamata bash hardcodedDizionario TOOL_HANDLERS
Sicurezza percorsoNessunoSandbox safe_path()
Loop agenteInvariatoInvariato

Provalo

python agents/s02_tool_use.py
  1. Leggi il file requirements.txt
  2. Crea un file chiamato greet.py con una funzione greet(name)
  3. Modifica greet.py per aggiungere un docstring alla funzione
  4. Leggi greet.py per verificare che la modifica funzioni