Tool calls: чтение кода и схем
Читай реальные сниппеты tool calling — JSON schema tool, диспетчер parallel-вызовов, цикл с timeout/обработкой ошибок и валидатор аргументов — и выбирай фикс с наибольшим рычагом.
Баги tool calling живут в схеме, диспетчере и цикле — не в модели. Читай каждый сниппет так, как читал бы на ревью, и выбирай фикс, который senior сделал бы первым.
Отработай петлю диагностики, которую ты запускаешь на каждом агенте: читай tool schema и код цикла, предскажи, как поведут себя модель и твой handler, и тянись к фиксу, который закрывает границу доверия или останавливает runaway.
Сниппет 1 — JSON schema tool
Когда видишь схему tool на ревью, первый вопрос не «выглядит ли это правильно?», а «что сделает модель, когда не знает id?» Слабая схема (JSON Schema, описывающая аргументы инструмента) — это приглашение к галлюцинации.
{
"name": "cancel_order",
"description": "Cancel an order.",
"input_schema": {
"type": "object",
"properties": {
"order_id": { "type": "string" },
"reason": { "type": "string" }
}
}
}
Мутирующий tool cancel_order выходит с этой схемой. В чём главная слабость и фикс с наибольшим рычагом?
Сниппет 2 — диспетчер parallel-вызовов
results = []
for block in response.tool_use_blocks: # их может быть несколько за ход
output = TOOLS[block.name](**block.input) # исполняем последовательно
results.append(tool_result(block.id, output))
send(messages + results)
Когда модель эмитит три независимых tool_use-блока за один ход, что этот диспетчер делает верно и что оставляет на столе?
Сниппет 3 — цикл без защиты
while True:
resp = model.create(messages=messages, tools=TOOLS)
if resp.stop_reason != "tool_use":
break
for b in resp.tool_use_blocks:
out = run_tool(b.name, b.input) # может бросить / зависнуть
messages.append(tool_result(b.id, out))
messages.append(resp.message)
Этот цикл работает в проде против client tools. Какие два дефекта укусят первыми при сбое и как их чинить?
Сниппет 4 — валидатор
def handle(block):
args = block.input
try:
validated = ToolArgs.model_validate(args) # pydantic: форма + типы
except ValidationError as e:
return tool_result(block.id, f"invalid arguments: {e}", is_error=True)
return tool_result(block.id, run(validated))
Этот handler валидирует по схеме через Pydantic и возвращает ошибки как tool_result. Для мутирующего tool против multi-tenant БД чего всё ещё не хватает?
Каждый баг tool calling читается в схеме, диспетчере, цикле или валидаторе. Слабая схема (нет required, нет enum, пустой description) даёт модели угадывать и не даёт тебе что валидировать. Последовательный диспетчер корректен, но теряет выигрыш по латентности от parallel-вызовов для независимых tools. while True без per-tool timeout — это runaway и зависание, ждущие случиться: ограничь итерации и тайм-боксь каждый tool. А валидация формы через Pydantic необходима, но недостаточна для мутирующего вызова: добавь проверку авторизации и существования и возвращай каждый отказ как tool_result, чтобы модель само-исправлялась. Теперь, когда встретишь while True в ревью или схему без required, ты знаешь, какой режим сбоя каждый из них открывает — и какой фикс его закрывает.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.