Перейти к содержанию

Инструменты и агенты

run_tool_loop выполняет весь цикл работы с инструментами: вызывает модель, запускает запрошенные функции из dispatch и повторяет процесс, пока модель не вернёт окончательный ответ без новых вызовов инструментов.

def get_weather(city: str) -> str:
    return f"В {city} сейчас 20°C"


tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Текущая погода в городе",
            "parameters": {
                "type": "object",
                "properties": {"city": {"type": "string"}},
                "required": ["city"],
            },
        },
    }
]

broker = llmbroker.Broker()
reply = llmbroker.run_tool_loop(
    broker,
    [{"role": "user", "content": "Какая погода в Москве?"}],
    tools=tools,
    dispatch={"get_weather": get_weather},
)
print(reply.text, "— ответила", reply.llm_name)

Функция возвращает результат последнего обращения к модели в том же формате, что и chat: текст, имя модели и данные usage для этого обращения. Каждое предыдущее обращение записывается в журнал отдельно. Поэтому usage в результате относится только к последнему обращению, а общие значения за весь цикл нужно суммировать по журналу.

Для AsyncBroker используйте асинхронную функцию await llmbroker.arun_tool_loop(...).

Число обращений ограничено параметром max_steps, равным 8 по умолчанию. Если на последнем шаге модель снова запрашивает инструмент, возникает llmbroker.ToolLoopLimitError. Увеличьте max_steps или обработайте исключение, если хотите использовать промежуточный результат.

Параметры вызовов брокера

Дополнительные параметры, включая operation=, trace_id= и wait=, передаются в каждый вызов chat внутри цикла. Задавайте их так же, как при отдельном вызове chat. Без operation= все обращения относятся к общей категории, поэтому оценки качества не смогут различать задачи:

reply = llmbroker.run_tool_loop(
    broker,
    messages,
    tools=tools,
    dispatch={"get_weather": get_weather},
    operation="weather-agent",
    trace_id=request_id,
)

Одинаковый trace_id объединяет в журнале все обращения одного цикла. При записи оценки по этому идентификатору будет выбран только последний успешный вызов. Чтобы оценить другой вызов из того же цикла, сохраните его call_id. Подробнее см. в разделе Оценка качества.

Конкретная модель

Вместо брокера цикл принимает и прямой клиент. Тогда каждое обращение идёт к одной выбранной модели:

broker = llmbroker.Broker(direct=["opus"])
reply = llmbroker.run_tool_loop(
    broker.direct("opus"),
    [{"role": "user", "content": "Какая погода в Москве?"}],
    tools=tools,
    dispatch={"get_weather": get_weather},
    params={"temperature": 0},
)
print(reply.text, reply.usage)

Результат — последний DirectResult с полями text, tool_calls и usage. В нём нет llm_name и call_id: модель выбрана вами, а прямой вызов не записывается в журнал. После ошибки цикл не переходит к другой модели, и исключение передаётся приложению. Дополнительные параметры передаются в метод chat клиента, поэтому здесь используются timeout= и params=, а не operation=, trace_id= или wait=.

Псевдониму из каталога не нужны параметры, чтобы участвовать в цикле: параметры, которые его модели требуются вместе с инструментами, берутся из каталога и отправляются в каждом раунде. Ваши params имеют над ними приоритет. См. Прямые вызовы модели.

Обработка ошибок инструментов

Исключения из функций в dispatch не передаются вызывающему коду. Если функция завершилась с ошибкой, модель получает сообщение Tool <имя> failed: <ошибка> и может повторить запрос с другими аргументами. Если запрошенной функции нет в dispatch, модель получает сообщение Unknown tool <имя>. Успешный результат функции также преобразуется с помощью str(), поэтому из инструмента следует возвращать текст или готовую строку JSON, а не произвольный объект.

Если функция сама преобразовала ошибку в обычный результат, цикл продолжится и потратит на это один шаг. Чтобы ошибка инструмента завершала весь запрос, обработайте её в функции и верните модели однозначный признак отказа либо реализуйте цикл самостоятельно. Метод chat(messages, tools=...) возвращает tool_calls, после чего приложение может выбрать дальнейшее поведение.

Области данных

Первым аргументом может быть любой объект с методом chat, принимающим tools=: брокер, прямой клиент или объект из broker.for_scope(user). С объектом из for_scope каждое обращение в цикле использует ключи этого пользователя и записывается в журнал с его областью. Подробнее см. в разделе Многопользовательские приложения.