LangChain.js: подключение к OpenAI-совместимому API в Node.js

Как направить ChatOpenAI из LangChain.js на шлюз Zukko: одна настройка baseURL, потоковый вывод, вызов инструментов и проверка расхода.

4 мин · · разработка · API-ключи

Если вы пишете на LangChain.js и хотите звать через него не только модели OpenAI, отдельный пакет под каждого поставщика не нужен. Класс ChatOpenAI умеет работать с любым сервером, который понимает формат Chat Completions, а адрес сервера задаётся одним полем. Ниже разберём, как подключить LangChain.js к шлюзу Zukko в Node.js: от установки до потокового вывода и вызова инструментов.

Что понадобится

  • Node.js 18 или новее и менеджер пакетов npm.
  • Ключ вида fetch_...: создаётся в кабинете, полностью показывается один раз.
  • Положительный баланс. Пополнение от $1: СБП (платёжная страница добавляет комиссию 8,5% сверху), криптовалюта или карты MasterCard.

Установка пакетов:

bash
npm install @langchain/openai @langchain/core zod
export ZUKKO_API_KEY="fetch_ваш_ключ"

Пакет @langchain/openai содержит ChatOpenAI, @langchain/core нужен для сообщений и инструментов, zod описывает схемы аргументов.

LangChain.js: базовое подключение

В документации LangChain.js адрес сервера меняется через поле configuration: оно принимает те же параметры, что и официальный OpenAI SDK. Для Zukko адрес заканчивается на /v1, потому что это клиент семейства OpenAI-совместимых.

bash
import { ChatOpenAI } from "@langchain/openai";

const llm = new ChatOpenAI({
  model: "claude-sonnet-5-5",
  apiKey: process.env.ZUKKO_API_KEY,
  configuration: {
    baseURL: "https://api.zukko.pro/v1",
  },
});

const answer = await llm.invoke([
  { role: "system", content: "Отвечай коротко и по делу." },
  { role: "user", content: "Чем Promise.all отличается от Promise.allSettled?" },
]);
console.log(answer.content);

Сохраните код как chat.ts в проекте с ES-модулями ("type": "module" в package.json) и запустите привычным способом, например npx tsx chat.ts.

Если ваша версия пакета не принимает apiKey в конструкторе, задайте переменную окружения OPENAI_API_KEY с тем же значением. Для базового адреса тоже есть переменная окружения OPENAI_BASE_URL, которую читает нижележащий SDK. Но явный configuration.baseURL понятнее при чтении кода и не зависит от окружения сервера.

Модель можно менять на любую из каталога: gpt-6.1-sol, deepseek-v4.1-flash, glm-5.3-flash, claude-opus-5-5. Актуальный список и цены отдаёт GET https://api.zukko.pro/v1/models, он же показан на странице «Модели» в кабинете.

Стриминг

Потоковый вывод в LangChain.js не требует отдельных настроек: метод stream() возвращает асинхронный итератор.

bash
const stream = await llm.stream("Объясни замыкания в JavaScript в трёх предложениях");
for await (const chunk of stream) {
  process.stdout.write(String(chunk.content));
}

Шлюз в конце потока присылает служебный чанк с usage и пустым списком choices. LangChain это учитывает сам. Если же вы разбираете поток руками через официальный SDK, перед обращением к chunk.choices[0] проверяйте, что массив не пуст.

Инструменты (tool calling)

Чтобы модель могла вызывать ваш код, опишите функцию через tool и привяжите её методом bindTools.

bash
import { tool } from "@langchain/core/tools";
import { HumanMessage, ToolMessage } from "@langchain/core/messages";
import * as z from "zod";

const getWeather = tool(
  async ({ city }) => `В городе ${city} +12, небольшой дождь`,
  {
    name: "get_weather",
    description: "Возвращает текущую погоду в городе",
    schema: z.object({ city: z.string().describe("Название города") }),
  },
);

const withTools = llm.bindTools([getWeather]);

Здесь цикл написан вручную, чтобы было видно, что происходит: модель возвращает не текст, а запрос на вызов, вы выполняете функцию и отдаёте результат обратно. Когда понадобятся несколько шагов подряд, ту же логику берут на себя агентные обёртки LangChain; про них и про сам подход есть заметка о tool use. Как именно разные модели справляются с вызовами инструментов, зависит от модели, поэтому проверьте на своих задачах.

Сколько это стоит

Цены на 18 октября 2026, $ за 1M токенов. Для claude-sonnet-5-5: вход 0.4, выход 2. Допустим, агент делает три вызова модели по 2 000 токенов входа и 300 токенов выхода:

  • вход: 6 000 × 0.4 / 1 000 000 = $0.0024;
  • выход: 900 × 2 / 1 000 000 = $0.0018;
  • итого $0.0042 за прогон.

Важная оговорка про кэш. Для Claude скидку за кэш промптов шлюз применяет только в Anthropic-формате (/v1/messages). В OpenAI-формате, которым пользуется ChatOpenAI, весь вход идёт по цене входа. Если у вас длинный повторяющийся контекст и модель Claude, посмотрите сравнение форматов. У GPT-моделей чтение кэша видно в обоих форматах.

Без LangChain: официальный openai SDK

Если фреймворк вам не нужен, тот же адрес работает с обычным пакетом openai:

bash
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://api.zukko.pro/v1",
  apiKey: process.env.ZUKKO_API_KEY,
});

const r = await client.chat.completions.create({
  model: "gpt-6.1-sol",
  messages: [{ role: "user", content: "Привет" }],
});
console.log(r.choices[0].message.content);

Подробнее про Node.js SDK: полный пример. Общий разбор настройки LangChain на Python и JS есть в статье о подключении LangChain к шлюзу.

Типичные ошибки

  • 404 model_not_found: имя модели написано не так, как в /v1/models.
  • 401: ключ скопирован с пробелом или передан не в то поле.
  • 402: на балансе не хватает средств.
  • 403 model_not_allowed_for_key: для ключа в кабинете ограничен список моделей.
  • 429: исчерпан дневной или месячный лимит ключа либо ограничение у провайдера модели.
  • Запросы уходят не туда: проверьте, что baseURL внутри configuration, а не на верхнем уровне конструктора.

Если вы хотите безопасно пробовать эксперименты, заведите отдельный ключ с дневным лимитом в $ и списком разрешённых моделей.

Частые вопросы

Нужен ли отдельный пакет для Claude в LangChain.js?

Для вызова через Zukko нет: Claude доступен в OpenAI-совместимом формате, поэтому достаточно ChatOpenAI с нужным baseURL. Отдельный Anthropic-класс имеет смысл, если вам нужен именно Anthropic-формат и скидка за кэш; тогда базовый адрес без /v1.

Куда подставить baseURL, если я использую переменные окружения?

Можно задать OPENAI_BASE_URL=https://api.zukko.pro/v1 и OPENAI_API_KEY=fetch_.... Но явная настройка в configuration надёжнее, особенно в серверных окружениях, где переменные могут отличаться.

Работает ли это из России?

Для оплаты из России есть три способа: СБП, карты MasterCard и криптовалюта. Код приложения при этом не меняется.

Как не потратить лишнего во время отладки?

Создайте отдельный ключ с дневным лимитом, начните с дешёвой модели (deepseek-v4.1-flash или glm-5.3-flash) и следите за расходом в разделе «Использование» кабинета.

Чтобы попробовать, создайте ключ в кабинете, подставьте его в пример выше и запустите первый запрос.

zukko

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

за Claude Code & ChatGPT

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