Диспетчеризация вызова инструмента

Когда инструментов несколько, нужен код, который по имени вызова найдёт и запустит правильную функцию.

Диспетчер — функция, которая по имени инструмента из блока tool_use находит соответствующую Python-функцию в реестре и вызывает её с переданными аргументами.

Зачем диспетчер

Модель может вызвать любой из объявленных инструментов: get_weather, search_db, send_email. Писать if name == "get_weather": ... elif ... неудобно и не масштабируется. Чище — реестр «имя → функция» и единая точка диспетчеризации.

Диспетчер по имени (запускаемо)

Реестр сопоставляет имена функциям; диспетчер достаёт имя и аргументы из блока, находит функцию и вызывает её — чистый Python:

import json

# 1. Реальные функции
def get_weather(city, unit="celsius"):
    fake = {"Москва": 7, "Париж": 14, "Токио": 19}
    return {"city": city, "temp": fake.get(city, 0), "unit": unit}

def add(a, b):
    return {"result": a + b}

# 2. Реестр: имя инструмента -> функция
REGISTRY = {
    "get_weather": get_weather,
    "add": add,
}

# 3. Диспетчер: по блоку tool_use вызывает нужную функцию
def dispatch(tool_use):
    name = tool_use["name"]
    args = tool_use["input"]
    func = REGISTRY.get(name)
    if func is None:
        return {"error": f"неизвестный инструмент: {name}"}
    try:
        return func(**args)
    except TypeError as e:
        return {"error": f"неверные аргументы: {e}"}

# 4. Имитация того, что прислала модель
calls = [
    {"type": "tool_use", "id": "t1", "name": "get_weather", "input": {"city": "Париж"}},
    {"type": "tool_use", "id": "t2", "name": "add", "input": {"a": 2, "b": 3}},
    {"type": "tool_use", "id": "t3", "name": "translate", "input": {"text": "hi"}},
]

for call in calls:
    result = dispatch(call)
    print(f"{call['name']}({call['input']}) -> {json.dumps(result, ensure_ascii=False)}")

Вывод:

get_weather({'city': 'Париж'}) -> {"city": "Париж", "temp": 14, "unit": "celsius"}
add({'a': 2, 'b': 3}) -> {"result": 5}
translate({'text': 'hi'}) -> {"error": "неизвестный инструмент: translate"}

Что здесь важно

  • Реестр вместо ветвлений. Добавить инструмент — это добавить функцию и строку в REGISTRY.
  • Безопасная обработка неизвестного. Если модель назвала несуществующий инструмент, возвращаем ошибку, а не падаем.
  • Защита от плохих аргументов. Ловим TypeError, чтобы неверный набор полей не уронил приложение.
  • Валидация входа. Аргументы пришли от модели — перед опасными действиями (запись в БД, отправка письма) их обязательно проверяют.

Как это встраивается в цикл

Из ответа модели вы выбираете все блоки tool_use, прогоняете каждый через dispatch, формируете блоки tool_result (с тем же tool_use_id) и отправляете обратно — как в прошлом уроке.

Итог

  • Реестр «имя → функция» + диспетчер заменяют громоздкие if/elif.
  • Обрабатывайте неизвестные имена и неверные аргументы, не роняя приложение.
  • Аргументы от модели — недоверенный ввод: валидируйте перед опасными действиями.
Проверьте себя
1. Что делает диспетчер инструментов?
AГенерирует определения инструментов
BПо имени из блока tool_use находит нужную функцию в реестре и вызывает её с аргументами
CОтправляет запрос модели
DСчитает токены
2. Почему реестр «имя → функция» лучше цепочки if/elif по имени?
AОн работает быстрее в разы
BЕго проще расширять: добавить инструмент — это добавить функцию и запись в реестр
Cif/elif не поддерживается в Python
DРеестр не требует валидации
3. Как стоит обращаться с аргументами, которые модель передала в вызов инструмента?
AСчитать их полностью доверенными
BСчитать недоверенным вводом и валидировать перед опасными действиями
CИгнорировать и использовать значения по умолчанию
DПередавать напрямую в SQL без проверок
Поддержать проект