Как связать AI-агентов разных вендоров в один пайплайн
Агент от OpenAI, агент на Claude и локальная модель на vLLM должны работать в одном процессе. Разбираем протоколы, брокеры и код, который заставит их говорить на одном языке.

Проблема, которую все замалчивают в демо
В маркетинговых роликах агент бронирует отель, пишет код и заказывает пиццу за один запрос. В продакшене у вас три разных SDK, два формата вызова функций и модель, которая отдаёт JSON с обрывающейся скобкой раз в сто вызовов. Оркестрация агентов разных вендоров — это не про «магию мультиагентности», а про интеграцию систем, которые проектировались независимо и не обязаны быть совместимыми.
Ниже — как соединить, например, планировщик на GPT (function calling через OpenAI API), исполнителя на Claude (tool use через Anthropic API) и локальную модель через OpenAI-совместимый эндпоинт vLLM так, чтобы они передавали друг другу задачи, а не падали на первом же несовпадении схем.
Три уровня, на которых агенты не совпадают
Прежде чем писать код, полезно понять, где именно ломается совместимость. Различия идут по трём слоям.
- Транспорт. У OpenAI и vLLM — формат
chat/completionsс полемtool_calls. У Anthropic — свой эндпоинтmessagesс блокамиtool_useиtool_result. Роли и структура сообщений отличаются. - Формат инструментов. Описание функции у OpenAI живёт в
tools[].function.parameters(JSON Schema). У Anthropic — вtools[].input_schema. Схема почти одинаковая, обёртка разная. - Семантика диалога. Как модель отдаёт «я хочу вызвать инструмент», как принимает результат, как обрабатывает несколько параллельных вызовов — здесь расхождения самые болезненные.
Оркестратор — это не самый умный агент в системе. Это самый скучный компонент: он не рассуждает, он переводит форматы и следит, чтобы никто не завис.
Два подхода к архитектуре
Общий протокол поверх вендоров
Вы описываете инструменты и сообщения в одном внутреннем формате, а на границе с каждым вендором конвертируете туда-обратно. Плюс — вендоры изолированы, замена одного не трогает остальных. Минус — вы поддерживаете адаптеры и рискуете потерять вендор-специфичные фичи (например, кэширование промптов Anthropic или строгий strict-режим схем у OpenAI).
Готовые фреймворки
LangGraph, CrewAI, AutoGen и Microsoft Semantic Kernel берут часть работы на себя. Но вы меняете один слой абстракции на другой: фреймворк тоже надо обновлять под изменения API вендоров, а отладка идёт через его внутренние объекты, а не через сырой HTTP.
| Подход | Контроль над трафиком | Порог входа | Когда брать |
|---|---|---|---|
| Свой оркестратор + адаптеры | Полный | Высокий | Прод с жёсткими требованиями к латентности и логам |
| LangGraph / AutoGen | Средний | Средний | Прототип и сложные графы состояний |
| Один вендор + его SDK | Полный | Низкий | Пока не нужна мультивендорность |
Минимальный оркестратор своими руками
Смысл — единый внутренний формат вызова инструмента и два адаптера. Определяем нейтральную структуру инструмента и переводим её под каждого вендора.
from dataclasses import dataclass
from typing import Callable, Any
@dataclass
class Tool:
name: str
description: str
parameters: dict # JSON Schema, общий формат
handler: Callable[[dict], Any]
# --- адаптер под OpenAI / vLLM ---
def to_openai(tools: list[Tool]) -> list[dict]:
return [{
"type": "function",
"function": {
"name": t.name,
"description": t.description,
"parameters": t.parameters,
},
} for t in tools]
# --- адаптер под Anthropic ---
def to_anthropic(tools: list[Tool]) -> list[dict]:
return [{
"name": t.name,
"description": t.description,
"input_schema": t.parameters,
} for t in tools]Обратите внимание: parameters и input_schema — это одна и та же JSON Schema. Разница только в имени ключа и уровне вложенности. Это и есть 80% работы адаптера.
Теперь диспетчеризация. Оба вендора возвращают запрос на вызов инструмента, но в разной обёртке. Приводим к общему виду.
@dataclass
class ToolCall:
id: str
name: str
args: dict
def parse_openai(resp) -> list[ToolCall]:
msg = resp.choices[0].message
calls = []
for tc in (msg.tool_calls or []):
calls.append(ToolCall(
id=tc.id,
name=tc.function.name,
args=json.loads(tc.function.arguments),
))
return calls
def parse_anthropic(resp) -> list[ToolCall]:
calls = []
for block in resp.content:
if block.type == "tool_use":
calls.append(ToolCall(
id=block.id,
name=block.name,
args=block.input,
))
return callsДальше цикл одинаков для любого вендора: получили ToolCall, нашли Tool по имени, выполнили handler(args), вернули результат в диалог тем же адаптером. Планировщик на GPT может сгенерировать вызов run_analysis, а исполнителем реально станет функция, которая под капотом дёргает Claude — потому что для оркестратора это просто ещё один handler.
MCP как попытка стандартизации
Model Context Protocol от Anthropic (представлен в конце 2024 года) описывает единый способ подключать инструменты и источники данных к моделям через отдельные серверы. Если ваши инструменты завёрнуты в MCP-сервер, любой MCP-совместимый клиент подключит их без переписывания адаптеров. Поддержка со стороны разных вендоров и клиентов на момент чтения статьи всё ещё расширяется — проверяйте актуальный список в документации протокола, прежде чем закладываться на него как на единственный слой интеграции.
Что ломается в проде
- Невалидный JSON в аргументах. Модель иногда возвращает битую структуру. Оборачивайте
json.loadsв try/except и возвращайте модели сообщение об ошибке вместо падения процесса. - Разные лимиты и таймауты. У вендоров свои rate limits и максимальные размеры контекста. Оркестратор должен ретраить с backoff и уметь деградировать на резервную модель.
- Параллельные вызовы. Одна модель отдаёт несколько
tool_callsсразу, другая — по одному за шаг. Пишите цикл так, чтобы он обрабатывал список любой длины. - Стоимость. Планировщик на дорогой модели, который на каждом шаге переспрашивает исполнителя, легко превращает копеечную задачу в ощутимый счёт. Логируйте токены по каждому агенту отдельно.
С чего начать
- Опишите инструменты в одном нейтральном формате (JSON Schema + handler).
- Напишите два адаптера: под OpenAI-совместимый API и под Anthropic. Проверьте на одном инструменте.
- Сделайте единый цикл диспетчеризации с обработкой ошибок парсинга и ретраями.
- Добавьте логирование токенов и латентности по каждому вендору, чтобы видеть цену каждого шага.
- Только после этого усложняйте: параллельные агенты, графы состояний, MCP.
Мультивендорная оркестрация окупается, когда у моделей реально разные сильные стороны: одна лучше планирует, другая дешевле на объёме, третья работает локально из-за требований к данным. Если такой причины нет — начните с одного вендора и его родного SDK, а слой абстракции добавьте, когда появится второй.
Prompt-инженер: Идеальные запросы для Midjourney, ChatGPT и других моделей.
Спросить за 15 ₽Источники: Anthropic — Model Context Protocol documentation, OpenAI API — Function calling guide
Частые вопросы
Обязательно ли использовать LangGraph или AutoGen?
Можно ли смешивать function calling OpenAI и tool use Anthropic в одном диалоге?
MCP уже можно ставить в продакшен?
Как не разориться на токенах при мультиагентной схеме?
Что делать, если модель возвращает невалидный JSON в аргументах инструмента?
vLLM совместим с кодом под OpenAI?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.