В новых приложениях на LangChain используйте ChatOpenRouter. Для старых проектов, которые пока нельзя обновить, оставьте ChatOpenAI с адресом https://openrouter.ai/api/v1. URL https://openrouter.ai/api нужен только в сценарии с Anthropic Agent SDK.
Короткий ответ: в новых проектах LangChain выбирайте нативную интеграцию
OpenRouter отвечает за доступ к моделям и маршрутизацию запросов, а LangChain — за абстракции чат-моделей, цепочек и агентов. Актуальная связка для Python — пакет langchain-openrouter и класс ChatOpenRouter. В JavaScript и TypeScript используются @langchain/openrouter и тот же ChatOpenRouter.
| Ваша ситуация | Подход | Главная причина |
|---|---|---|
| Новое приложение LangChain на Python | langchain-openrouter + ChatOpenRouter | Интеграция предоставляет маршрутизацию по провайдерам, метаданные, reasoning, инструменты и управление структурированным выводом |
| Новое приложение LangChain на JS/TS | @langchain/openrouter + ChatOpenRouter | Сохраняются нативные интерфейсы LangChain для сообщений, потоковой генерации и инструментов |
| Существующее старое приложение LangChain | ChatOpenAI + base_url="https://openrouter.ai/api/v1" | Минимальные изменения для совместимости |
Существующее приложение с init_chat_model | LangChain v1 и langchain-openrouter | Включает маршрут диспетчеризации через model_provider="openrouter" |
| Приложение на Claude/Anthropic Agent SDK | ANTHROPIC_BASE_URL="https://openrouter.ai/api" | Agent SDK использует совместимый с Anthropic маршрут |
| Приложение Next.js на Vercel AI SDK | @openrouter/ai-sdk-provider + createOpenRouter() | Используются streamText() и абстракции инструментов из AI SDK |
В анонсе официального пакета LangChain говорится, что нативная интеграция сохраняет специфичные для OpenRouter возможности, которые могут потеряться при использовании универсальной обертки ChatOpenAI. Речь идет о reasoning-контенте, структурированном выводе, метаданных маршрутизации и идентификации трассировок. Если эти поля вам не нужны, существующую совместимую конфигурацию необязательно срочно переписывать.
Сначала разберитесь с картой эндпоинтов, а уже потом выбирайте фреймворк
Разные SDK добавляют к базовому URL разные пути запросов. Совместимый с OpenAI маршрут — https://openrouter.ai/api/v1; именно это значение указано в руководстве OpenRouter для OpenAI SDK. В руководстве по Anthropic Agent SDK базовая переменная ANTHROPIC_BASE_URL вместо этого указывает на https://openrouter.ai/api.
| Клиент или SDK | Настраиваемый базовый URL | Типичный набор запросов |
|---|---|---|
| OpenAI Python/JS SDK | https://openrouter.ai/api/v1 | OpenAI Chat Completions |
Устаревший LangChain ChatOpenAI | https://openrouter.ai/api/v1 | Совместимые с OpenAI вызовы чата |
LangChain ChatOpenRouter | Обычно собственный базовый URL не нужен | Нативная интеграция с OpenRouter |
| Провайдер OpenRouter для Vercel AI SDK | Обычно собственный базовый URL не нужен | Пакет провайдера сам управляет эндпоинтом |
| Anthropic Agent SDK | https://openrouter.ai/api | Вызовы Agent SDK, совместимые с Anthropic |
Не добавляйте /chat/completions в базовый URL SDK, если он явно не требует полный адрес эндпоинта. Ошибка с продублированным путем вроде /v1/v1 означает, что нужно проверить, как SDK объединяет базовый URL с путем запроса.
Для старого клиента LangChain, работающего через OpenAI-совместимый API, минимальная рабочая конфигурация выглядит так:
import os
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="openai/gpt-4o",
api_key=os.environ["OPENROUTER_API_KEY"],
base_url="https://openrouter.ai/api/v1",
)
response = llm.invoke("Explain provider routing in one sentence.")
print(response.content)
Используйте API-ключ OpenRouter, а не ключ OpenAI. В Quickstart OpenRouter указана аутентификация через Bearer-токен. Заголовки HTTP-Referer и X-OpenRouter-Title нужны для атрибуции приложения и не заменяют Authorization.
LangChain + OpenRouter: актуальная настройка
Python: langchain-openrouter
Установите интеграцию, а ключ храните за пределами исходного файла:
pip install -U langchain-openrouter
export OPENROUTER_API_KEY="sk-or-..."
После этого создайте нативную чат-модель. В документации интеграции LangChain для Python этот конструктор поддерживает параметры model, temperature, max_tokens и max_retries:
from langchain_openrouter import ChatOpenRouter
model = ChatOpenRouter(
model="anthropic/claude-sonnet-4.5",
temperature=0,
max_tokens=1024,
max_retries=2,
)
response = model.invoke([
("system", "You are a concise technical assistant."),
("human", "What does OpenRouter add to a LangChain application?"),
])
print(response.content)
Названия моделей OpenRouter обычно записываются в формате provider/model: например, anthropic/claude-sonnet-4.5, openai/gpt-4o или deepseek/deepseek-r1. Перед развертыванием проверьте OpenRouter Models API: идентификаторы, алиасы, варианты, доступность и поддерживаемые параметры могут меняться.
JavaScript и TypeScript: @langchain/openrouter
Установите интеграцию и LangChain core:
npm install @langchain/openrouter @langchain/core
Конструктор с объектом параметров из документации LangChain для JavaScript удобно сопоставить с примером на Python:
import { ChatOpenRouter } from "@langchain/openrouter";
const model = new ChatOpenRouter({
model: "anthropic/claude-sonnet-4.5",
temperature: 0,
maxTokens: 1024,
});
const response = await model.invoke([
{ role: "system", content: "You are a concise technical assistant." },
{ role: "user", content: "Explain the provider/model format." },
]);
console.log(response.content);
В Python используется max_tokens, а в JavaScript-примере — maxTokens. Обе интеграции по умолчанию читают переменную OPENROUTER_API_KEY. Если модель есть в каталоге, но вызов из кода завершается ошибкой, проверьте точный slug, суффикс варианта и поддерживаемые параметры запроса.
init_chat_model и ловушка с версиями
Частая ошибка выглядит так:
ValueError: Unsupported model_provider='openrouter'.
В обсуждении на форуме LangChain этот случай связывают с установкой LangChain до версии v1 при использовании документации для v1. В качестве решения рекомендуется обновить сам фреймворк и партнерский пакет одновременно:
pip install -U "langchain>=1" langchain-openrouter
Затем используйте универсальный инициализатор:
import os
from langchain.chat_models import init_chat_model
os.environ["OPENROUTER_API_KEY"] = "sk-or-..."
model = init_chat_model(
"anthropic/claude-sonnet-4.5",
model_provider="openrouter",
)
print(model.invoke("Say hello in one sentence.").content)
Если проект нельзя перевести на LangChain v1, установите langchain-openrouter и создавайте ChatOpenRouter напрямую. Так вы обходите универсальный реестр провайдеров и явно фиксируете зависимость от OpenRouter.
Переход с ChatOpenAI на ChatOpenRouter
Изменений в коде немного, но сама граница интеграции меняется: вместо универсального адаптера OpenAI используется специализированный класс провайдера:
# Before: OpenAI-compatible workaround
from langchain_openai import ChatOpenAI
model = ChatOpenAI(
model="anthropic/claude-sonnet-4.5",
base_url="https://openrouter.ai/api/v1",
api_key=os.environ["OPENROUTER_API_KEY"],
)
# After: native OpenRouter integration
from langchain_openrouter import ChatOpenRouter
model = ChatOpenRouter(model="anthropic/claude-sonnet-4.5")
Сначала оставьте без изменений промпты и места вызова цепочек. Когда базовый нативный вызов заработает, добавляйте маршрутизацию провайдеров, reasoning, структурированный вывод и обработку метаданных.
Потоковая генерация, callbacks, инструменты и структурированный вывод в LangChain
model.stream() возвращает части сообщений — этого достаточно для простой обработки текста. stream_events(..., version="v3") предоставляет события жизненного цикла и лучше подходит для логирования через callbacks, обновления состояния интерфейса и трассировки. Оба интерфейса описаны в документации интеграции LangChain для Python.
model = ChatOpenRouter(model="openai/gpt-4o")
for event in model.stream_events(
"Explain why OpenRouter has provider fallbacks.",
version="v3",
):
if event["event"] == "on_chat_model_stream":
chunk = event["data"]["chunk"]
print(chunk.text, end="", flush=True)
Callback нужен, когда приложению важно получать уведомления о начале, токенах, завершении или ошибке. Если нужны только части ответа модели, используйте stream(). Информацию об использовании токенов и метаданные ответа берите из завершенного ответа или агрегированного результата потока, как показано в документации интеграции, а не из произвольного первого чанка.
Вызов инструментов и маршрутизация по провайдерам
OpenRouter описывает нормализованный сценарий вызова инструментов: модель предлагает вызов, приложение выполняет инструмент, после чего отправляет результат обратно модели. Метод ChatOpenRouter.bind_tools() принимает инструменты LangChain, функции Python, классы Pydantic и схемы в виде словарей.
from pydantic import BaseModel, Field
class GetWeather(BaseModel):
"""Get current weather for a city."""
location: str = Field(
description="City and state, for example San Francisco, CA"
)
model_with_tools = model.bind_tools(
[GetWeather],
strict=True,
)
result = model_with_tools.invoke("What is the weather in San Francisco?")
print(result.tool_calls)
Первый вызов возвращает только предложенные моделью вызовы инструментов. В полном ручном цикле для каждого вызова нужно создать ToolMessage, а затем снова обратиться к модели за финальным ответом:
from langchain_core.messages import HumanMessage, ToolMessage
from langchain_core.tools import tool
@tool
def get_weather(location: str) -> str:
"""Get the current weather in a location."""
return f"Sunny in {location}."
model_with_tools = model.bind_tools([get_weather])
messages = [HumanMessage("What is the weather in San Francisco?")]
ai_msg = model_with_tools.invoke(messages)
messages.append(ai_msg)
for call in ai_msg.tool_calls:
tool_output = get_weather.invoke(call["args"])
messages.append(
ToolMessage(
content=tool_output,
tool_call_id=call["id"],
)
)
final_msg = model_with_tools.invoke(messages)
print(final_msg.content)
Настройки маршрутизации провайдеров задаются в конфигурации модели OpenRouter, описанной в документации интеграции LangChain и руководстве OpenRouter по выбору провайдера:
routed_model = ChatOpenRouter(
model="anthropic/claude-sonnet-4.5",
openrouter_provider={
"order": ["Anthropic", "Google"],
"allow_fallbacks": True,
"require_parameters": True,
"data_collection": "deny",
},
)
order задает предпочтительный порядок. Параметр only ограничивает список доступных провайдеров, а ignore исключает отдельных провайдеров. При allow_fallbacks=True запрос может уйти за пределы указанного приоритета. require_parameters=True не позволяет отправлять запрос провайдерам, которые не поддерживают хотя бы один параметр из тела запроса. Эти настройки позволяют выбирать между доступностью и контролем над провайдером.
В старых клиентах ChatOpenAI специфичные для OpenRouter поля, возможно, придется передавать через extra_body или model_kwargs — это зависит от версии клиента. Один из пользователей задал в обсуждении LangChain/OpenRouter на Reddit вполне логичный вопрос:
«Разве OpenRouter не поддерживает спецификацию OpenAI API, чтобы клиент OpenAI работал?» — u/tuxedo0.
Отдельный пакет делает границу интеграции с OpenRouter явной: универсальной обертке OpenAI больше не нужно самостоятельно предоставлять каждое специфичное для провайдера поле.
Структурированный вывод: проверяйте эндпоинт, а не только название модели
Нативная интеграция предоставляет типизированный вывод через with_structured_output(). В документации LangChain для Python показан нативный метод JSON Schema:
from pydantic import BaseModel, Field
class Movie(BaseModel):
title: str
year: int
director: str
rating: float = Field(description="Rating from 0 to 10")
structured_model = ChatOpenRouter(
model="openai/gpt-5.5",
).with_structured_output(
Movie,
method="json_schema",
strict=True,
)
movie = structured_model.invoke("Give details about the movie Inception.")
print(movie)
В интеграции также описан режим function_calling; параметр strict не поддерживается вместе с json_mode. Поведение структурированного вывода OpenRouter может зависеть от конкретного сервинг-эндпоинта, поэтому одного названия модели недостаточно для гарантии. В руководстве по структурированному выводу OpenRouter объясняется, почему важны проверка возможностей провайдера, require_parameters и валидация на стороне клиента.
Vercel AI SDK + OpenRouter
Для приложения на Next.js или TypeScript, использующего AI SDK от Vercel, установите провайдер OpenRouter:
npm install @openrouter/ai-sdk-provider ai zod
В этом компактном примере показаны потоковая генерация текста и локальный инструмент на базе Zod. В руководстве OpenRouter для Vercel AI SDK используется та же схема с createOpenRouter() и openrouter("provider/model").
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
import { isStepCount, streamText, tool } from "ai";
import { z } from "zod";
const openrouter = createOpenRouter({
apiKey: process.env.OPENROUTER_API_KEY,
});
const result = streamText({
model: openrouter("openai/gpt-4o"),
tools: {
getWeather: tool({
description: "Get the current weather in a location",
inputSchema: z.object({
location: z.string().describe("City and state"),
}),
execute: async ({ location }) => ({
location,
temperature: 18,
unit: "celsius",
}),
}),
},
stopWhen: isStepCount(3),
prompt: "What is the weather in San Francisco?",
});
for await (const textPart of result.textStream) {
process.stdout.write(textPart);
}
textStream передает дельты текста. Если приложению нужны reasoning, вызовы инструментов, результаты инструментов, границы шагов или события завершения, используйте fullStream. Эти поверхности результата описаны в справочнике AI SDK по streamText(). Приведенный выше исполнитель погоды — заглушка, а не реальный вызов API.
В публичном issue провайдера OpenRouter сообщалось о заметной буферизации в одном сценарии многочастного вызова инструмента при использовании @openrouter/ai-sdk-provider 2.2.3, AI SDK 6.0.81, Node.js 24 и openai/gpt-5.2. В описании упоминалось отсутствующее событие tool-input-end. Считайте это проблемой конкретных версий, а не универсальным свойством интеграции. Если ошибка воспроизводится, в issue предлагается такой обходной путь:
import { createOpenAI } from "@ai-sdk/openai";
const openrouter = createOpenAI({
apiKey: process.env.OPENROUTER_API_KEY,
baseURL: "https://openrouter.ai/api/v1",
});
const model = openrouter.chat("openai/gpt-5.2");
Пакет @ai-sdk/openai нужно установить отдельно, а версии зависимостей — зафиксировать после проверки. Этот обходной путь меняет адаптер провайдера, поэтому проверьте, какие специфичные для OpenRouter параметры при этом остаются доступными.
Anthropic Agent SDK + OpenRouter
Anthropic Agent SDK — отдельная граница интеграции. В официальном руководстве OpenRouter для Agent SDK сказано, что SDK использует Claude Code в качестве среды выполнения и принимает такую конфигурацию окружения:
export ANTHROPIC_BASE_URL="https://openrouter.ai/api"
export ANTHROPIC_AUTH_TOKEN="$OPENROUTER_API_KEY"
export ANTHROPIC_API_KEY=""
Пустое значение ANTHROPIC_API_KEY задано намеренно. Ключ OpenRouter передается через ANTHROPIC_AUTH_TOKEN, а путь /api в этой конфигурации Agent SDK нельзя заменять на совместимый с OpenAI путь /api/v1.
TypeScript-пример из руководства выглядит так:
npm install @anthropic-ai/claude-agent-sdk
import { query } from "@anthropic-ai/claude-agent-sdk";
for await (const message of query({
prompt: "Find and explain the bug in auth.py.",
options: {
allowedTools: ["Read"],
},
})) {
if (message.type === "assistant") {
console.log(message.message.content);
}
}
OpenRouter предоставляет эндпоинт модели, а Agent SDK сохраняет цикл работы агента и среду выполнения инструментов. Проверяйте инструменты, потоковую генерацию, поведение контекста и специфичные параметры модели именно на том маршруте, который будет использоваться в продакшене: совместимость с нативными возможностями Anthropic не возникает автоматически.
401, 404 и ситуация «через curl работает, а через SDK — нет»
Прежде чем менять модель, определите, на каком уровне возникает ошибка. Для аутентификации, построения URL, поиска модели, фильтрации провайдеров и проверки параметров нужны разные решения.
| Симптом | Вероятный уровень | Что проверить в первую очередь |
|---|---|---|
401 Missing Authentication header | Заголовок или загрузка переменной окружения | Проверьте Authorization: Bearer ... и запрос к /api/v1/key |
401 Unauthorized при наличии ключа | Неверный, отозванный или пустой ключ | Убедитесь, что процесс видит OPENROUTER_API_KEY, и используйте ключ OpenRouter |
404 с некорректным путем | Сборка базового URL | Для OpenAI-совместимых клиентов нужен /api/v1, для Agent SDK — /api; проверьте дублирование /v1 |
404 Invalid model или model_not_found | Идентификатор модели | Запросите /api/v1/models и используйте возвращенный id или документированный алиас |
404 No allowed providers are available | Фильтры провайдеров или доступность эндпоинта | Проверьте only, ignore, max_price, require_parameters и настройки политики данных |
400 для top_k, min_p, инструментов или JSON Schema | Несовместимость возможностей | Проверьте supported_parameters и при маршрутизации требуйте совместимых провайдеров |
Через curl работает, а код фреймворка — нет | Путь SDK, регистр, сериализация или потерянные заголовки | Сравните итоговый URL, заголовок авторизации, ID модели и JSON-тело |
OpenRouter документирует GET /api/v1/key как аутентифицированную проверку API-ключа. Выполните ее до того, как начнете отлаживать LangChain:
curl -sS https://openrouter.ai/api/v1/key \
-H "Authorization: Bearer $OPENROUTER_API_KEY"
Затем проверьте обнаружение моделей:
curl -sS https://openrouter.ai/api/v1/models \
-H "Authorization: Bearer $OPENROUTER_API_KEY"
Через Models API можно отфильтровать модели, поддерживающие инструменты, еще до их подключения:
curl -sS \
"https://openrouter.ai/api/v1/models?supported_parameters=tools" \
-H "Authorization: Bearer $OPENROUTER_API_KEY"
Ошибка 404 также может означать, что модель существует, но после применения фильтров запроса не осталось подходящих провайдеров. Чтобы увидеть решения маршрутизатора, включите необязательный заголовок X-OpenRouter-Metadata: enabled. В документации Router Metadata описаны объект openrouter_metadata и поведение при ошибках.
В разработке сравнивайте сформированный запрос, предварительно скрыв ключ: базовый URL, итоговый путь, наличие Authorization, точный ID модели и специфичные поля тела запроса. Для отдельного разбора проблем с аутентификацией смотрите руководство AIReiter по ошибкам неверного API-ключа и 401/403.
Чек-лист миграции в продакшен
- Согласуйте поколения пакетов. Версии
langchain,langchain-coreиlangchain-openrouterили@langchain/openrouterдолжны быть совместимы. Не копируйте версии из старого руководства без проверки. - Используйте канонические ID моделей. Берите их из
/api/v1/modelsили каталога OpenRouter; считайте алиасы осознанным изменением поведения. - Определите границу fallback. Запасной провайдер повышает доступность. Запасная модель может изменить качество, задержку, возможности и стоимость. Сохраняйте финальное значение
model, которое вернул OpenRouter. - Требуйте нужные возможности. Для запросов с инструментами и структурированным выводом используйте фильтры возможностей провайдера. Поддержка инструментов не означает автоматическую поддержку строгой JSON Schema.
- Проверяйте результаты приложения. Перед передачей данных в следующий участок кода валидируйте аргументы инструментов и структурированные ответы с помощью Pydantic, Zod или другого валидатора.
- Логируйте полезные метаданные. Если это допускает политика конфиденциальности, сохраняйте ID запроса, модель, использование токенов, причину завершения и информацию о провайдере. В интеграции LangChain также документируются
session_idдля группировки запросов иtraceдля метаданных конкретного запроса. - Тестируйте оба режима потоковой генерации. Успешный запрос без потоковой передачи не гарантирует корректную работу чанков вызовов инструментов или потокового UI.
- Проверяйте ограничения провайдеров. Если используются
only,ignore, фильтры сбора данных или ограничение цены, протестируйте сценарий, в котором подходящих провайдеров не остается. - Храните учетные данные на сервере. Размещайте
OPENROUTER_API_KEYв серверном окружении или менеджере секретов — никогда в браузерном бандле или закоммиченном файле.env.
Надежный порядок действий такой: проверить ключ, проверить ID модели, выполнить простой текстовый вызов и только потом добавлять маршрутизацию, инструменты, структурированный вывод и многошаговую потоковую генерацию.