CrewAI: подключение агентов к Claude и GPT через один ключ

CrewAI тянет к OpenAI через LiteLLM, но agent.llm можно переставить на любую модель гейтвея. Разбираем настройку и типичные ошибки на старте.

5 мин · · Обновлено · агенты

CrewAI собирает агентов в команду: у каждого своя роль, своя память и свой llm. По умолчанию фреймворк ходит к OpenAI через LiteLLM, но LLM-класс принимает base_url и api_key, так что вместо десяти отдельных провайдерских ключей можно использовать один fetch_... и переключать модели прямо в коде агента.

На практике почти вся боль с CrewAI на старте — не в логике агентов, а в том, что фреймворк собирает запрос через LiteLLM, а тот по умолчанию рассчитан на официальные адреса провайдеров. Как только вы уходите на сторонний совместимый гейтвей, всплывают три вещи: правильный префикс в имени модели, правильный base_url (с /v1 или без — зависит от протокола) и то, что повторные попытки LiteLLM при сетевых сбоях могут задвоить списание, если не подстраховаться идемпотентностью. Дальше — по порядку.

Базовая настройка

CrewAI использует нотацию LiteLLM: провайдер указывается префиксом в model, а фактический адрес берётся из base_url. Для OpenAI-совместимого режима это выглядит так:

bash
from crewai import Agent, Task, Crew, LLM

llm = LLM(
    model="openai/gpt-5.6-sol",
    base_url="https://api.zukko.pro/v1",
    api_key="fetch_ваш_ключ",
)

researcher = Agent(
    role="Researcher",
    goal="Собрать факты по теме и оформить кратким списком",
    backstory="Пишет по-русски, проверяет источники, не выдумывает цифры",
    llm=llm,
)

Префикс openai/ здесь не про модель, а про формат протокола: LiteLLM отправляет запрос на POST /v1/chat/completions, а какая модель за этим стоит — решает уже гейтвей по полю model. Поэтому тем же способом подключаются и Claude, и GLM:

bash
claude_llm = LLM(
    model="openai/claude-sonnet-5",
    base_url="https://api.zukko.pro/v1",
    api_key="fetch_ваш_ключ",
)

Если написать model="claude-sonnet-5" без префикса openai/, LiteLLM попытается угадать провайдера сам и полезет в нативный Anthropic SDK со своими дефолтными адресами — получится путаница с base_url. Префикс обязателен именно потому, что вы работаете через OpenAI-совместимый путь /v1/chat/completions, а не через /v1/messages.

Разные модели для разных агентов

В команде обычно не нужен один и тот же уровень модели для всех ролей. Дешёвая GLM для черновой работы, Opus 5.5 — для финальной проверки:

bash
drafter = Agent(
    role="Drafter",
    goal="Быстро накидать черновик по брифу",
    llm=LLM(model="openai/glm-5.3", base_url="https://api.zukko.pro/v1", api_key=key),
)

reviewer = Agent(
    role="Reviewer",
    goal="Проверить черновик на фактические ошибки",
    llm=LLM(model="openai/claude-opus-5-5", base_url="https://api.zukko.pro/v1", api_key=key),
)

crew = Crew(agents=[drafter, reviewer], tasks=[draft_task, review_task])
crew.kickoff()

Если у ключей заданы разные дневные лимиты (настраивается в кабинете на странице ключа), удобно выдать каждому агенту свой fetch_... — тогда дорогой Opus не съест лимит, рассчитанный на дешёвые черновики.

Агент с доступом к вебу

Ни у GPT, ни у Claude, ни у GLM в этом каталоге нет встроенного веб-поиска — за это отвечает отдельный POST /v1/web-search. В CrewAI его удобно завернуть в обычный tool и выдать агенту-исследователю:

bash
from crewai.tools import tool
import requests

@tool("web_search")
def web_search(query: str) -> str:
    """Ищет свежую информацию в интернете."""
    resp = requests.post(
        "https://api.zukko.pro/v1/web-search",
        headers={"Authorization": "Bearer fetch_ваш_ключ"},
        json={"query": query},
    )
    return resp.json()

researcher = Agent(
    role="Researcher",
    goal="Найти актуальные данные, которых нет в базовых знаниях модели",
    llm=llm,
    tools=[web_search],
)

Это отдельный вызов со своей тарификацией, не часть чат-запроса, поэтому его стоимость видна в истории списаний отдельной строкой. Для агента, который сначала ищет, а потом суммирует найденное через LLM, итоговая стоимость задачи складывается из двух разных строк в GET /v1/usage, и это стоит учитывать при прикидке бюджета на прогон всей команды.

Сколько стоил конкретный запуск

CrewAI даёт доступ к сырому ответу через llm.call() при ручных вызовах вне Agent, и в заголовках обычного (нестримингового) ответа гейтвея есть X-Zukko-Cost-Micros (стоимость в микродолларах, 1,000,000 = $1) и X-Fetch-Balance-Cents (остаток после списания — несмотря на название, тоже в микродолларах; точный баланс в центах отдаёт GET /v1/balance). У потоковых ответов этих заголовков нет. Если оборачивать LLM.call собственным логированием, эти два заголовка удобно писать в лог рядом с именем агента — так видно, какая роль в команде тратит больше остальных, без похода в кабинет за каждой цифрой.

Такое логирование полезно ещё и для отчётности: если команда выставляет клиентам счёт за использование агентов, именно собственный лог X-Zukko-Cost-Micros по ключу или агенту даёт стоимость самой дорогой роли отдельно от черновой. GET /v1/usage такой разбивки не даст: история там общая на весь аккаунт (все ключи вместе, фильтра по ключу нет), а баланс — один на аккаунт. Если нужно жёсткое разделение по клиентам, заведите под клиента отдельный аккаунт.

404 вместо ответа

Самая частая жалоба на форумах CrewAI — агент "молчит" или падает с ошибкой соединения. В девяти случаях из десяти это base_url без /v1: OpenAI-совместимый путь гейтвея — https://api.zukko.pro/v1, а не https://api.zukko.pro. Второй адрес — только для Anthropic-совместимых клиентов, которые сами добавляют путь /v1/messages, и LiteLLM в режиме openai/ этого не делает.

Модель не найдена

Если в коде осталась модель из старого туториала CrewAI, вида gpt-4 или claude-3-opus, вернётся:

bash
{"error": {"message": "The model 'gpt-4' does not exist", "code": "model_not_found"}}

В каталоге этих моделей нет — актуальный список без ключа:

bash
curl https://api.zukko.pro/v1/models

Ретраи и повторное списание

LiteLLM внутри CrewAI по умолчанию делает несколько повторных попыток при сетевых сбоях. Если сбой произошёл уже после того, как запрос доехал до модели, повтор с тем же телом может списать деньги дважды. Чтобы это исключить, нужен свой Idempotency-Key — но на каждый логический запрос, а не на весь LLM. Если передать его в конструктор LLM(..., extra_headers=...), он уйдёт во все вызовы агента, и второй же запрос с другим телом получит 422 idempotency_conflict. Для вызовов, которые вы делаете сами, задавайте ключ в конкретном вызове — один раз на запрос, без перегенерации при повторах:

bash
import uuid, litellm

idem_key = str(uuid.uuid4())  # один ключ на один логический запрос
resp = litellm.completion(
    model="openai/gpt-5.6-sol",
    api_base="https://api.zukko.pro/v1",
    api_key=key,
    messages=[{"role": "user", "content": prompt}],
    extra_headers={"Idempotency-Key": idem_key},
    num_retries=2,  # повторы LiteLLM уйдут с тем же ключом
)

Повтор с тем же ключом идемпотентности в течение 24 часов вернёт тот же ответ без повторного списания. Для вызовов, которые агенты CrewAI формируют сами, общий ключ в конструкторе LLM не задавайте.

Проверка до запуска команды

Прежде чем собирать полную Crew, стоит убедиться, что ключ и модель работают сами по себе:

bash
curl https://api.zukko.pro/v1/chat/completions \
  -H "Authorization: Bearer fetch_ваш_ключ" \
  -H "Content-Type: application/json" \
  -d '{"model":"gpt-5.6-sol","messages":[{"role":"user","content":"ping"}]}'

Если ответ пришёл, дальше проблема будет только в логике агентов, не в подключении. Баланс и историю списаний по каждому вызову видно в кабинете (GET /v1/balance, GET /v1/usage), а по $0.50 на тесты начисляют один раз за подписку на @zukkopro. Если разворачиваете CrewAI для клиентов и приводите новых пользователей на этот же гейтвей, по реферальной программе идёт 10% от их пополнений — для агентства, которое ставит несколько команд агентов на разные проекты, это заметная экономия на общем инструменте, а не разовая скидка.

zukko

Платите в 25×
меньше

за Claude Code & ChatGPT

Один ключ fetch_* и base URL api.zukko.pro — в Cursor, Claude Code, Codex и любом SDK.