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

Прямые вызовы модели

Методы ask, chat и stream выбирают модель из пула и при ошибке переходят к следующей. Если нужно обратиться к одной конкретной модели, используйте broker.direct(...). Такой вызов выполняется напрямую, без выбора из пула и без автоматического перехода к другой модели.

Для прямого доступа модель нужно указать в параметре direct= при создании брокера. К моделям из пула обращаются только через ask, chat или stream. При попытке передать имя модели из пула в direct() возникнет PoolModelError.

Настройка и вызов

broker = llmbroker.Broker(direct=["opus"])
broker.direct("opus").ask("...")

"opus" — постоянный псевдоним из поддерживаемого каталога платных моделей. При выходе нового поколения Claude каталог может связать opus с новой версией, при этом код приложения менять не потребуется. Псевдоним не содержит номера версии и не переименовывается. Нужный API-ключ определяется провайдером, например ANTHROPIC_API_KEY или OPENAI_API_KEY.

Список моделей из direct= не записывается в реестр: его источником остаётся код приложения. Соответствие псевдонима конкретной версии проверяется примерно раз в сутки вместе с обновлением пула. Отдельная синхронизация или локальный файл для этого не нужны.

Если каталог недоступен во время проверки, брокер продолжает использовать уже выбранную версию и записывает предупреждение в журнал. Ранее найденное соответствие не заменяется более старым. Ошибка возникает только при первом разрешении псевдонима, например если в нём есть опечатка; сообщение перечислит доступные варианты.

Собственная конфигурация модели

Вместо псевдонима можно передать LLMConfig. Это подходит для модели на собственном сервере, корпоративного шлюза или версии, которую нельзя обновлять автоматически:

from llmbroker import LLMConfig

gateway = LLMConfig(
    name="frontier",
    model="claude-opus-4-8",
    base_url="https://api.anthropic.com/v1",  # любой API, совместимый с OpenAI
    api_key_ref="ANTHROPIC_API_KEY",
)
broker = llmbroker.Broker(direct=[gateway])
broker.direct(name="frontier").ask("...")

Такая конфигурация, включая версию модели, не изменяется при обновлении каталога.

Прямые модели не входят в пул

Модель из direct= никогда не участвует в выборе моделей, не используется как запасная и не учитывается в count() и snapshot(). Пул предназначен для взаимозаменяемых бесплатных моделей с общей логикой ограничений. Прямые модели и частные шлюзы управляются отдельно.

Собственную модель можно включить в пул, если добавить её в реестр самостоятельно. Автоматическое обновление такую запись не изменит. Реестр содержит только модели пула, поэтому добавление в реестр и объявление через direct= — два разных способа использования модели.

Как найти платную модель

Команда llmbroker list показывает поддерживаемые бесплатные и платные модели и ничего не изменяет. В строке direct сначала указан псевдоним для direct=, а затем идентификаторы провайдера и модели, base_url и api_key_ref:

$ llmbroker list
pool groq-gpt-oss-120b openai/gpt-oss-120b https://api.groq.com/openai/v1 GROQ_API_KEY
...
direct opus anthropic claude-opus-5 https://api.anthropic.com/v1 ANTHROPIC_API_KEY
direct sonnet anthropic claude-sonnet-5 https://api.anthropic.com/v1 ANTHROPIC_API_KEY

Чтение каталога из программы

Те же данные можно прочитать из программы без создания брокера. Сначала используется сохранённая локальная копия, а при её отсутствии — копия из установленного пакета. Доступ к сети для этих функций не требуется:

from llmbroker import curated_paid, curated_pool, curated_providers

for row in curated_paid():
    print(row.alias or "-", row.name, row.label)

for provider in curated_providers():
    print(provider.id, provider.base_url, provider.api_key_ref)

print(len(curated_pool().configs), "бесплатных моделей в поддерживаемом списке")

Через описание провайдера можно настроить и модель, которой ещё нет в каталоге, например недавно выпущенную версию. Создайте конфигурацию методом declare() и передайте её в direct=:

anthropic = next(p for p in curated_providers() if p.id == "anthropic")
broker = llmbroker.Broker(direct=[anthropic.declare("claude-opus-9-preview")])
broker.direct(name="anthropic-claude-opus-9-preview").ask("...")

declare() подставляет адрес API и имя ключа. Результатом становится полная конфигурация с фиксированной версией, которая не обновляется вместе с каталогом. Метод .declare() есть и у записей каталога; он также фиксирует текущий идентификатор модели. Чтобы автоматически переходить на новые версии, передайте в direct= строковый псевдоним.

Эти функции только читают данные. Обновление выполняет sync. Поведение при отсутствующем ключе описано в разделе Ошибки.

Различие между alias и name

Псевдонимы и полные имена обрабатываются отдельно. Параметр name= можно использовать для проверки версии: если указать anthropic-claude-opus-5, а псевдоним в каталоге уже соответствует другой версии, вызов завершится ошибкой, а не перейдёт на новую модель незаметно.

Конфигурации из direct= не сохраняются в реестре. Значения ключей также не сохраняются: во время вызова они читаются из переменных окружения или выбранного хранилища секретов.

Обновление модели, заданной псевдонимом

Соответствие псевдонима конкретной версии проверяется примерно раз в сутки вместе с обновлением пула. При переходе на новую версию в журнал записываются прежнее и новое значения:

direct=: opus: claude-opus-4-8 -> claude-opus-5

После обновления поле name содержит новую версию. Если изменился провайдер, в записи также будет новый api_key_ref. Перед следующим вызовом задайте соответствующую переменную окружения.

Полная конфигурация LLMConfig не обновляется, поскольку она не связана с записью каталога.

Асинхронные вызовы и потоковая выдача

async with llmbroker.AsyncBroker(direct=["opus"]) as broker:
    client = await broker.direct("opus")

    # потоковая выдача через асинхронный итератор
    async for delta in client.stream("Напиши хокку про брокеров"):
        print(delta, end="", flush=True)

    # либо весь ответ сразу
    result = await client.ask("Дай полный текст")
    print(result.text, result.usage)

Прямой вызов обращается к одной указанной модели. Брокер не выбирает модель из пула, не переходит к другой при ошибке и не записывает вызов в журнал. Потоковая выдача из пула описана в разделе Асинхронные вызовы.

Синхронно

Синхронный Broker также предоставляет direct(...) с методами stream(), ask() и chat(). Синхронному клиенту не нужен цикл событий, и каждый клиент открывает собственные соединения; закройте его через with или client.close(), когда запрос обработан.

with llmbroker.Broker(direct=["opus"]) as broker:
    with broker.direct("opus") as client:
        for delta in client.stream("Напиши хокку про брокеров"):
            print(delta, end="", flush=True)

        result = client.ask("Дай весь текст целиком")
        print(result.text)

Если закрыть поток раньше времени, закрывается соединение, в которое пишет провайдер, и генерация останавливается. Поток закрывает вызов close() у итератора, а также исчезновение последней ссылки на него — так происходит при выходе из цикла for delta in client.stream(...) через break. В синхронном веб-сервере тело ответа, читающее поток, закрывает его, когда сервер закрывает тело после отключения клиента.

Инструменты

chat(messages, tools=...) отправляет список сообщений вместе с описаниями инструментов и возвращает полный ответ модели с полями text, tool_calls и usage. Ответ, содержащий только вызовы инструментов, считается корректным: поле text в нём пустое. stream() инструменты не принимает.

client = broker.direct("opus")
result = client.chat(messages, tools=tools)
if result.tool_calls:
    ...  # выполнить запрошенные инструменты и снова вызвать chat

run_tool_loop и arun_tool_loop принимают прямой клиент вместо брокера и сами повторяют эти вызовы. Подробнее см. в разделе Инструменты и агенты.

Псевдоним из каталога принимает инструменты без дополнительных параметров. Некоторые модели принимают функции-инструменты в chat completions только с определённым параметром: например, моделям OpenAI за псевдонимами gpt, gpt-mini и gpt-fast нужен reasoning_effort="none". Это записано в строке каталога, и llmbroker добавляет такие параметры в каждый вызов chat с tools, и только в него; вызов без инструментов сохраняет настройки модели по умолчанию. Значения из params имеют приоритет по каждому ключу. Когда псевдоним переходит на другую версию, её параметры переходят вместе с ним.

Конфигурация, созданная через declare() у провайдера из каталога, таких параметров не содержит: каталог эту модель не проверял. Если они нужны, передайте tool_params= в LLMConfig или params= в каждом вызове.

Параметры запроса

При прямом вызове можно передать параметры, поддерживаемые выбранной моделью, например уровень рассуждения, температуру, ограничение числа токенов или seed. Словарь params добавляется в тело запроса без изменений. Синхронный и асинхронный клиенты принимают его в ask(), chat() и stream():

client = broker.direct("opus")
client.ask("...", params={"reasoning_effort": "low", "temperature": 0})

llmbroker не проверяет и не изменяет значения из params. Если провайдер не поддерживает параметр, его сообщение об ошибке передаётся приложению. Параметры API провайдера помещаются внутрь params, а параметры llmbroker, например messages= и timeout=, передаются отдельно.

Поля model, messages, stream, stream_options и tools формирует сам llmbroker. Если одно из них передано в params, возникает ValueError с именем поля. Это предотвращает замену выбранной модели и несовместимое изменение режима ответа. Поле tool_choice разрешено, поэтому params={"tool_choice": "required"} заменяет значение "auto", которое устанавливается вместе с tools.

params поддерживается только для прямых вызовов. Для пула доступны лишь параметры, которые можно одинаково применить ко всем моделям. См. раздел Запрос JSON по схеме.

Ошибки

Все исключения прямых вызовов наследуют LLMRequestError:

  • PoolModelError — указано имя модели из поддерживаемого пула. Используйте ask, chat или stream либо создайте собственную конфигурацию модели.
  • UnknownModelError — запись с таким именем или псевдонимом не найдена. Если значение существует в другой категории, это будет указано в сообщении. Та же ошибка возникает, если псевдонима из direct= нет в каталоге или каталог вообще не удалось прочитать; сообщение перечислит доступные псевдонимы. Ошибка касается только вызова direct() с этим псевдонимом: пул, остальные объявленные модели и snapshot() работают как обычно, а snapshot().direct_unresolved содержит такие псевдонимы и причину для каждого — опечатка видна без единого вызова.
  • MissingKeyError — не найден ключ из api_key_ref. Для прямого вызова это ошибка; модель пула без ключа просто не используется.
  • ProviderError — провайдер вернул ошибку. Поля .status и .detail содержат код HTTP и фрагмент ответа. Для отдельной обработки можно перехватывать AuthError (401 или 403) и RateLimitError (429 или 503, с полем .retry_after).
  • InvalidProviderResponseError — провайдер вернул HTTP 200, но тело не удалось разобрать как ответ модели либо в нём нет ни текста, ни вызовов инструментов. Поле .model содержит имя модели, а .detail — фрагмент ответа. При прямом вызове брокер не может перейти к другой модели, поэтому исключение передаётся приложению.
  • LLMTimeoutError — истекло время ожидания ответа.

Для потоковой выдачи из пула предусмотрены ещё два исключения: StreamInterruptedError и StreamReplacementError. При прямом вызове они не возникают. Подробнее см. в разделе Асинхронные вызовы и потоковая выдача.