Диспетчер инструментов

Реализуем ядро механики: реестр инструментов и диспетчер, который по имени вызывает нужную функцию.

Диспетчер инструментов (tool dispatcher) — код, который принимает имя инструмента и аргументы, находит соответствующую функцию в реестре и вызывает её, возвращая результат агенту.

Идея

Модель говорит текстом: «вызови calculator с аргументом 2 * 15». Наш код должен превратить эту строку в реальный вызов функции calculator("2 * 15"). Связующее звено — диспетчер. Внутри он держит реестр: словарь «имя → функция».

Запускаемый диспетчер

Соберём два инструмента (калькулятор и поиск-заглушку) и диспетчер, который выбирает их по имени. Это и есть тот код, что в настоящем агенте стоит между моделью и внешним миром.

def calculator(expression):
    # простой и безопасный калькулятор: только цифры и операции
    allowed = set("0123456789+-*/(). ")
    if not set(expression) <= allowed:
        return "ошибка: недопустимые символы"
    return str(eval(expression))

def search(query):
    # заглушка поиска: маленькая «база знаний»
    db = {
        "столица франции": "Париж",
        "автор гамлета": "Уильям Шекспир",
    }
    return db.get(query.lower().strip(), "ничего не найдено")

# Реестр: имя инструмента -> функция
TOOLS = {"calculator": calculator, "search": search}

def dispatch(name, arg):
    if name not in TOOLS:
        return f"ошибка: инструмент '{name}' не найден"
    return TOOLS[name](arg)

print(dispatch("calculator", "2 * (10 + 5)"))
print(dispatch("search", "столица Франции"))
print(dispatch("search", "погода завтра"))
print(dispatch("translator", "hello"))

Вывод:

30
Париж
ничего не найдено
ошибка: инструмент 'translator' не найден

Разберём, что произошло:

  • dispatch("calculator", ...) нашёл функцию в реестре и вызвал её — получили точный результат.
  • search по известному запросу вернул факт, по неизвестному — «ничего не найдено».
  • Запрос несуществующего инструмента translator дал понятную ошибку, а не падение программы.

Почему так, а не «модель сама вызовет функцию»

Модель не выполняет код — она только просит. Диспетчер нужен, чтобы:

  • контролировать, какие функции вообще доступны (модель не вызовет то, чего нет в реестре);
  • в одном месте добавлять логирование, тайм-ауты, проверку прав;
  • аккуратно обрабатывать ошибки (следующий урок).

Расширяемость

Добавить инструмент — это добавить одну запись в реестр. Агент сразу сможет его использовать, если упомянуть в описании для модели. Реестр — естественная точка роста системы.

def word_count(text):
    return str(len(text.split()))

TOOLS = {"count": word_count}

def dispatch(name, arg):
    if name not in TOOLS:
        return f"ошибка: нет инструмента '{name}'"
    return TOOLS[name](arg)

print("инструментов в реестре:", len(TOOLS))
print(dispatch("count", "агент рассуждает и действует"))

Вывод:

инструментов в реестре: 1
4

Итог

  • Диспетчер связывает текстовую «просьбу» модели с реальным вызовом функции через реестр «имя → функция».
  • Он же — единая точка для контроля доступа, логирования и обработки ошибок.
  • Новый инструмент = новая запись в реестре; масштабировать агента просто.
Проверьте себя
1. Что хранит реестр инструментов в диспетчере?
AИсторию диалога
BСоответствие «имя инструмента → функция»
CРезультаты прошлых вызовов
DСхемы JSON для модели
2. Зачем нужен отдельный диспетчер, если можно вызвать функцию напрямую?
AТак быстрее работает eval
BМодель не выполняет код — она лишь называет инструмент; диспетчер контролирует доступ, логирует и ловит ошибки
CPython требует диспетчер для словарей
DЧтобы скрыть результат от модели
3. Что вернёт диспетчер при запросе инструмента, которого нет в реестре?
AАварийно завершит программу
BПонятное сообщение об ошибке, не падая
CВызовет первый попавшийся инструмент
DПустую строку без объяснения
Поддержать проект