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

Инструменты

Инструменты и выполнение

One Handler Per Tool

120 LOC4 инструментовTool 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

"Добавить инструмент — значит добавить один обработчик" -- цикл остаётся прежним; новые инструменты регистрируются в карте диспетчеризации.

Проблема

Только с bash агент делает всё через шелл. cat обрезает непредсказуемо, sed ломается на специальных символах, и каждый вызов bash — это неконтролируемая поверхность атаки. Специализированные инструменты вроде read_file и write_file позволяют применять песочницу путей на уровне инструментов.

Ключевая идея: добавление инструментов не требует изменения цикла.

Решение

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

Карта диспетчеризации — это словарь: {имя_инструмента: функция_обработчик}.
Один поиск заменяет любую цепочку if/elif.

Как Это Работает

  1. Каждый инструмент получает функцию-обработчик. Песочница путей предотвращает выход из рабочей директории.
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. Карта диспетчеризации связывает имена инструментов с обработчиками.
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. В цикле ищем обработчик по имени. Тело цикла само не меняется по сравнению с 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,
        })

Добавить инструмент = добавить обработчик + добавить схему. Цикл никогда не меняется.

Что Изменилось с s01

КомпонентДо (s01)После (s02)
Инструменты1 (только bash)4 (bash, read, write, edit)
ДиспетчеризацияЖёстко закодированный вызов bashСловарь TOOL_HANDLERS
Безопасность путейНетПесочница safe_path()
Цикл агентаБез измененийБез изменений

Попробуйте

python agents/s02_tool_use.py
  1. Прочитайте файл requirements.txt
  2. Создайте файл greet.py с функцией greet(name)
  3. Отредактируйте greet.py, добавив docstring к функции
  4. Прочитайте greet.py для проверки редактирования