Диспетчеризация вызова инструмента
Когда инструментов несколько, нужен код, который по имени вызова найдёт и запустит правильную функцию.
Диспетчер — функция, которая по имени инструмента из блока
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 без проверок