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

Tools

Tools & Ausführung

One Handler Per Tool

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

"Ein Tool hinzufügen bedeutet einen Handler hinzufügen" -- die Schleife bleibt gleich; neue Tools registrieren sich in der Dispatch-Map.

Problem

Mit nur bash shellt der Agent alles aus. cat schneidet unvorhersehbar ab, sed scheitert an Sonderzeichen, und jeder bash-Aufruf ist eine unbeschränkte Sicherheitsoberfläche. Dedizierte Tools wie read_file und write_file ermöglichen das Durchsetzen von Path-Sandboxing auf Tool-Ebene.

Der wichtige insight: Tools hinzufügen erfordert nicht, die Schleife zu ändern.

Lösung

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

Die Dispatch-Map ist ein Dict: {tool_name: handler_function}.
Ein Lookup ersetzt jede if/elif-Kette.

Wie es funktioniert

  1. Jedes Tool erhält eine Handler-Funktion. Path-Sandboxing verhindert Workspace-Escape.
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. Die Dispatch-Map verbindet Tool-Namen mit Handlern.
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. In der Schleife, schlagen Sie den Handler nach Namen nach. Der Schleifenkörper selbst ist unverändert von 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,
        })

Ein Tool hinzufügen = einen Handler hinzufügen + einen Schema-Eintrag hinzufügen. Die Schleife ändert sich nie.

Was sich von s01 geändert hat

KomponenteVorher (s01)Nachher (s02)
Tools1 (nur bash)4 (bash, read, write, edit)
DispatchHardcodierter bash-AufrufTOOL_HANDLERS dict
Path-SicherheitNonesafe_path() Sandbox
Agent-SchleifeUnverändertUnverändert

Ausprobieren

python agents/s02_tool_use.py
  1. Lesen Sie die Datei requirements.txt
  2. Erstellen Sie eine Datei namens greet.py mit einer greet(name) Funktion
  3. Bearbeiten Sie greet.py, um einen Docstring zur Funktion hinzuzufügen
  4. Lesen Sie greet.py, um zu überprüfen, ob die Bearbeitung funktioniert hat