DeepSeek Vision and Grok Imagine models are now live on CometAPI →
guide/Исследования CometAPI

Как создать многомодельное чат-приложение на основе ИИ с использованием Next.js и CometAPI

Создайте стриминговое чат-приложение на Next.js на базе ИИ, которое переключается между моделями GPT, Claude и Gemini, используя единый ключ CometAPI и единый эндпоинт бэкенда.

CometAPI
AnnaКоманда исследователей AI-моделей и API
Обновлено Aug 26, 2026 12 мин. чтения
Как создать многомодельное чат-приложение на основе ИИ с использованием Next.js и CometAPI
Использовать этот подход

Сделайте первый вызов API.

from openai import OpenAI

client = OpenAI(
    api_key="YOUR_COMETAPI_KEY",
    base_url="https://api.cometapi.com/v1",
)

response = client.chat.completions.create(
    model="gpt-5-mini",
    messages=[{"role": "user", "content": "Build this workflow."}],
)

print(response.choices[0].message.content)

Для чат-бота Next.js, которому нужны несколько провайдеров моделей, наиболее полезный бэкенд — тот, который централизует аутентификацию, позволяя приложению выбирать модель на каждый запрос. CometAPI предоставляет такой паттерн: сервер хранит один API-ключ и OpenAI-совместимый базовый URL https://api.cometapi.com/v1, а поле запроса model выбирает доступную модель чата GPT, Claude, Gemini или другую.

Это руководство строит рабочий проект на App Router с серверным списком разрешённых моделей, потоковым Route Handler, селектором модели в браузере, защищёнными переменными окружения, полноценным интерфейсом чата и рекомендациями по деплою. Ключ CometAPI никогда не попадает в браузер.

Что такое мульти-модельное AI-чат-приложение?

Архитектура мульти-модельного AI-чата размещает один контролируемый приложением бэкенд между пользовательским интерфейсом и несколькими провайдерами моделей. В этом проекте браузер отправляет диалог на /api/chat, в то время как сервер хранит приватный credential CometAPI, валидирует запрошенный ID модели и пересылает запрос через совместимый API.

Как работает переключение моделей в Next.js?

Смена модели — это решение маршрутизации. Браузер отправляет одобренный ID модели вместе с диалогом на /api/chat. Route Handler добавляет credential CometAPI, вызывает POST /v1/chat/completions и стримит ответ выбранной модели обратно в браузер.

Общий эндпоинт не делает все модели идентичными. Стиль вывода, поведение инструментов, поддерживаемые параметры, лимиты контекста и цены могут различаться. Поддерживайте ID моделей в серверном списке разрешений и тестируйте каждый маршрут с одними и теми же прикладными промптами перед тем, как сделать его доступным пользователям.

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

Готовое приложение имеет один бэкенд Next.js и три выбираемых маршрута чата:

Идентификатор моделиПример ролиЦена CometAPI
gemini-3.7-flashЧувствительный к стоимости, высокообъёмный чат$0.60 вход / $3.00 выход
claude-opus-5Фокус на премиальном рассуждении$4 вход / $20 выход
gpt-5.6gpt-5.6 — это общий маршрут CometAPI для GPT-5.6 и в настоящее время соответствует tier Sol.$3.2 вход / $16 выход в tier с коротким контекстом

Примечание по ценам: Цены ниже указаны в USD за миллион токенов, были проверены 21 августа 2026 года и могут изменяться. Всегда проверяйте текущие ставки на страницах моделей перед продакшен-использованием. Таблица — это пример маршрутизации, а не рейтинг качества. GPT-5.6 использует более высокий ценовой tier при объёме свыше 272 000 токенов. См. связанные страницы моделей Gemini 3.7 Flash, Claude Opus 5 и GPT-5.6 для датированных ставок, использованных здесь.

CometAPI прозрачно тарифицирует модели относительно официальных API каждого провайдера. Модели с унифицированным официальным ценообразованием — OpenAI, Claude, Gemini и подобные — тарифицируются по токенам с коэффициентом 0.8:1 относительно официальной цены, то есть со скидкой 20%; модели без официальных API (MidJourney, Kling, Luma) тарифицируются поминутно/по вызову по установленным ставкам CometAPI, также со скидкой 20%. См. CometAPI Pricing Guide для формулы наценки, ставок по моделям и единиц биллинга.

Три маршрута покрывают разные компромиссы. Gemini 3.7 Flash — эффективный «агентный» рабочий инструмент Google, с мультимодальным вводом и окном контекста 1 048 576 токенов для высокообъёмных сценариев чата, кодирования и знаний. Claude Opus 5 — флагманская модель рассуждений от Anthropic, сильная в многошаговом анализе, коде и аккуратном длинноформатном письме по премиальной цене. GPT-5.6 — универсальная модель OpenAI, балансирующая рассуждения, работу с инструментами и черновики, с широким окном контекста для повседневного продакшен-трафика.

Прежде чем начать

Вам потребуются Node.js, npm, аккаунт CometAPI и серверный API-ключ из CometAPI Quick Start. Три точных ID моделей выше были доступны в живом каталоге 21 августа 2026 года, не были помечены как ожидаемые (upcoming) и предоставляли POST /v1/chat/completions.

В руководстве используются следующие общие настройки:

Шаг 1: Создайте приложение Next.js

npx create-next-app@latest multi-model-chat --ts --app --eslintcd multi-model-chatnpm run dev

Для этой версии SDK провайдера модели не требуется. Сервер использует встроенный fetch API и передаёт вверх по цепочке поток Server-Sent Events.

Шаг 2: Держите ключ CometAPI на сервере

Создайте .env.local в корне проекта:

COMETAPI_API_KEY=replace_with_your_cometapi_keyCOMETAPI_BASE_URL=https://api.cometapi.com/v1

Не добавляйте префикс NEXT_PUBLIC_ к ключу. Next.js публикует в браузерные бандлы только переменные с таким префиксом; API-ключ должен находиться в серверном Route Handler.

Шаг 3: Определите политику по моделям

Поместите разрешённые ID в бэкенд, а не только в выпадающий список. Пользователь может обойти элементы управления в браузере и обратиться к вашему маршруту напрямую, поэтому сервер должен отклонять неизвестные значения модели.

const ALLOWED_MODELS = [  "gemini-3.7-flash",  "claude-opus-5",  "gpt-5.6",] as const;​type ModelId = (typeof ALLOWED_MODELS)[number];

Клиент будет использовать те же три ID для селектора, при этом сервер остаётся источником истины.

Шаг 4: Стримьте CometAPI через Route Handler

Создайте app/api/chat/route.ts. Маршрут валидирует чат-пейлоад только с текстом, вызывает CometAPI с stream: true и возвращает поток событий от upstream, не раскрывая credential.

export const runtime = "nodejs";export const dynamic = "force-dynamic";​const ALLOWED_MODELS = [  "gemini-3.7-flash",  "claude-opus-5",  "gpt-5.6",] as const;​type ModelId = (typeof ALLOWED_MODELS)[number];type ChatMessage = {  role: "system" | "user" | "assistant";  content: string;};​function isModelId(value: unknown): value is ModelId {  return (    typeof value === "string" &&    (ALLOWED_MODELS as readonly string[]).includes(value)  );}​function isChatMessage(value: unknown): value is ChatMessage {  if (typeof value !== "object" || value === null) return false;​  const message = value as Record<string, unknown>;  return (    ["system", "user", "assistant"].includes(String(message.role)) &&    typeof message.content === "string" &&    message.content.length > 0 &&    message.content.length <= 20_000  );}​export async function POST(request: Request) {  const apiKey = process.env.COMETAPI_API_KEY;  const baseUrl = (    process.env.COMETAPI_BASE_URL || "https://api.cometapi.com/v1"  ).replace(/\/$/, "");​  if (!apiKey) {    return Response.json(      { error: "COMETAPI_API_KEY is not configured." },      { status: 500 },    );  }​  let payload: { model?: unknown; messages?: unknown };​  try {    payload = await request.json();  } catch {    return Response.json({ error: "Invalid JSON body." }, { status: 400 });  }​  if (!isModelId(payload.model)) {    return Response.json({ error: "Unsupported model ID." }, { status: 400 });  }​  if (    !Array.isArray(payload.messages) ||    payload.messages.length === 0 ||    payload.messages.length > 50 ||    !payload.messages.every(isChatMessage)  ) {    return Response.json(      { error: "messages must contain 1 to 50 valid text messages." },      { status: 400 },    );  }​  const upstream = await fetch(`${baseUrl}/chat/completions`, {    method: "POST",    headers: {      Authorization: `Bearer ${apiKey}`,      "Content-Type": "application/json",    },    body: JSON.stringify({      model: payload.model,      messages: payload.messages,      stream: true,    }),    cache: "no-store",    signal: request.signal,  });​  if (!upstream.ok) {    const requestId = upstream.headers.get("x-request-id");​    console.error("CometAPI request failed", {      status: upstream.status,      requestId,    });​    return Response.json(      {        error: "The selected model request failed.",        status: upstream.status,        requestId,      },      { status: upstream.status },    );  }​  if (!upstream.body) {    return Response.json(      { error: "The model returned no response body." },      { status: 502 },    );  }​  return new Response(upstream.body, {    status: 200,    headers: {      "Content-Type": "text/event-stream; charset=utf-8",      "Cache-Control": "no-cache, no-transform",    },  });}

Handler передаёт upstream сигнал отключения браузера, так что закрытие запроса может остановить ненужную генерацию. Он также возвращает клиенту санитизированную ошибку, сохраняя диагностические детали в серверных логах.

Шаг 5: Добавьте выбор модели и парсинг потока

Создайте клиентский компонент в app/page.tsx. Он отправляет выбранную модель вместе с диалогом, парсит каждое событие data: и добавляет инкрементальный текст delta.content к последнему сообщению ассистента.

Браузер никогда не обращается напрямую к CometAPI. Его единственный адрес — ваш собственный маршрут /api/chat, который хранит ключ в приватности и обеспечивает применение списка разрешений.

Основной код проекта

Route Handler выше — это полный файл app/api/chat/route.ts. Добавьте следующую страницу, layout и таблицу стилей, чтобы завершить исполняемый проект.

app/page.tsx

"use client";​import { FormEvent, useState } from "react";​const MODEL_OPTIONS = [  { id: "gemini-3.7-flash", label: "Gemini 3.7 Flash" },  { id: "claude-opus-5", label: "Claude Opus 5" },  { id: "gpt-5.6", label: "GPT-5.6" },] as const;​type Role = "user" | "assistant";type Message = { role: Role; content: string };​function textFromSseLine(line: string): string {  const trimmed = line.trim();  if (!trimmed.startsWith("data:")) return "";​  const data = trimmed.slice(5).trim();  if (!data || data === "[DONE]") return "";​  try {    const event = JSON.parse(data);    return event.choices?.[0]?.delta?.content ?? "";  } catch {    return "";  }}​export default function Home() {  const [model, setModel] = useState("gemini-3.7-flash");  const [messages, setMessages] = useState<Message[]>([]);  const [input, setInput] = useState("");  const [loading, setLoading] = useState(false);  const [error, setError] = useState("");​  function appendAssistantText(text: string) {    if (!text) return;​    setMessages((current) => {      const next = [...current];      const lastIndex = next.length - 1;​      if (lastIndex >= 0 && next[lastIndex].role === "assistant") {        next[lastIndex] = {          ...next[lastIndex],          content: next[lastIndex].content + text,        };      }​      return next;    });  }​  async function sendMessage(event: FormEvent<HTMLFormElement>) {    event.preventDefault();​    const content = input.trim();    if (!content || loading) return;​    const outgoing: Message[] = [...messages, { role: "user", content }];    setMessages([...outgoing, { role: "assistant", content: "" }]);    setInput("");    setError("");    setLoading(true);​    try {      const response = await fetch("/api/chat", {        method: "POST",        headers: { "Content-Type": "application/json" },        body: JSON.stringify({ model, messages: outgoing }),      });​      if (!response.ok) {        const body = await response.json().catch(() => ({}));        throw new Error(body.error || `Request failed with ${response.status}`);      }​      if (!response.body) throw new Error("Streaming is not available.");​      const reader = response.body.getReader();      const decoder = new TextDecoder();      let buffer = "";​      while (true) {        const { value, done } = await reader.read();        buffer += decoder.decode(value, { stream: !done });​        const lines = buffer.split("\n");        buffer = lines.pop() ?? "";​        for (const line of lines) {          appendAssistantText(textFromSseLine(line));        }​        if (done) {          appendAssistantText(textFromSseLine(buffer));          break;        }      }    } catch (requestError) {      setError(        requestError instanceof Error ? requestError.message : "Request failed.",      );    } finally {      setLoading(false);    }  }​  return (    <main className="shell">      <section className="chat">        <header>          <p className="eyebrow">Next.js + CometAPI</p>          <h1>Multi-model chat</h1>          <label>            Model            <select              value={model}              onChange={(event) => setModel(event.target.value)}              disabled={loading}            >              {MODEL_OPTIONS.map((option) => (                <option key={option.id} value={option.id}>                  {option.label}                </option>              ))}            </select>          </label>        </header>​        <div className="messages" aria-live="polite">          {messages.length === 0 ? (            <p className="empty">Choose a model and send a message.</p>          ) : (            messages.map((message, index) => (              <article className={message.role} key={`${message.role}-${index}`}>                <b>{message.role === "user" ? "You" : "Assistant"}</b>                <p>{message.content || "…"}</p>              </article>            ))          )}        </div>​        <form onSubmit={sendMessage}>          <textarea            value={input}            onChange={(event) => setInput(event.target.value)}            placeholder="Ask something…"            rows={3}            maxLength={20_000}          />          <button disabled={loading || !input.trim()} type="submit">            {loading ? "Streaming…" : "Send"}          </button>        </form>​        {error ? <p className="error">{error}</p> : null}      </section>    </main>  );}

app/layout.tsx

import type { Metadata } from "next";import "./globals.css";​export const metadata: Metadata = {  title: "Multi-Model Chat",  description: "A streaming Next.js chat app powered by CometAPI.",};​export default function RootLayout({  children,}: Readonly<{ children: React.ReactNode }>) {  return (    <html lang="en">      <body>{children}</body>    </html>  );}

app/globals.css

:root {  color-scheme: dark;  font-family: Arial, sans-serif;  background: #07111f;  color: #eef4ff;}* { box-sizing: border-box; }body { margin: 0; }button, select, textarea { font: inherit; }​/* Layout shell and chat card */.shell { min-height: 100vh; display: grid; place-items: center; padding: 32px 16px; }.chat { width: min(820px, 100%); background: #0d1b2e; border: 1px solid #223957; border-radius: 20px; padding: 24px; }​/* Message list and bubbles */.messages { min-height: 360px; display: grid; align-content: start; gap: 12px; margin: 24px 0; }.messages article { max-width: 85%; padding: 12px 14px; border-radius: 14px; white-space: pre-wrap; }.user { justify-self: end; background: #164f8f; }.assistant { justify-self: start; background: #182a42; }​/* Form controls, buttons, and error states follow the same dark theme. */

Шаг 6: Запустите и задеплойте приложение

npm run dev

Откройте http://localhost:3000, выберите модель и отправьте сообщение. Для продакшен-деплоя Node.js добавьте COMETAPI_API_KEY и COMETAPI_BASE_URL в серверные переменные окружения хоста, затем запустите:

npm run buildnpm run start

Используйте хостинг, поддерживающий потоковые ответы. Статический экспорт не может выполнить Route Handler /api/chat.

Протестируйте потоковый маршрут

При запущенном dev-сервере вызовите ваш бэкенд напрямую:

curl -N http://localhost:3000/api/chat \  -H "Content-Type: application/json" \  -d '{    "model": "gemini-3.7-flash",    "messages": [      {"role": "user", "content": "Explain model routing in two sentences."}    ]  }'

Успешный запрос возвращает Server-Sent Events. Точные ID и текст варьируются, но поток следует такой форме:

data: {"choices":[{"delta":{"content":"Model"}}]}​data: {"choices":[{"delta":{"content":" routing"}}]}​data: [DONE]

Парсер в браузере читает каждое SSE-событие, извлекает choices[0].delta.content и добавляет стриминговый текст к сообщению ассистента по мере поступления чанков.

Распространённые ошибки интеграции

СимптомПричинаИсправление
401 ошибка аутентификацииОтсутствует или недействительный серверный ключУстановите COMETAPI_API_KEY; не публикуйте его через NEXT_PUBLIC_.
404 или неверный маршрутВ базовом URL отсутствует /v1Используйте https://api.cometapi.com/v1.
400 неподдерживаемая модельID отсутствует в серверном списке разрешенийИспользуйте точный живой ID текстовой модели и обновите оба селектора.
Ответ приходит разомХост или прокси буферизует поток событийОтключите трансформацию ответа и используйте деплой Node.js с поддержкой стриминга.

Подготовьте чат-бэкенд к продакшену

  • Аутентифицируйте ваших собственных пользователей. Не публикуйте публичный маршрут, который тратит кредиты на анонимный трафик.
  • Ограничивайте частоту по пользователю и IP. Ограничьте параллельные потоки, запросы в минуту, количество и длину сообщений.
  • Держите список разрешённых моделей на сервере. Селектор в браузере — удобство, а не граница безопасности.
  • Валидируйте живой каталог при деплое. Запрашивайте GET https://api.cometapi.com/api/models и проваливайте релиз, если настроенный ID в статусе upcoming, недоступен или без эндпоинта chat-completions.
  • Отслеживайте стоимость по маршруту. Логируйте выбранную модель, ID запроса, латентность, использование токенов и ID пользователя. Настраивайте квоты ключа или лимиты расходов в панели CometAPI там, где уместно.
  • Обрабатывайте отключения и таймауты. Сохраняйте request.signal, задавайте таймаут приложения и останавливайте работу, когда клиент уходит.
  • Не скрывайте ошибки конфигурации с помощью фолбэка. Отдавайте 400 и 401. Используйте другую модель только для ограниченного набора повторяемых (retryable) сбоев и только если схемы запросов совместимы; см. CometAPI Model Fallback Guide для двухслойной цепочки фолбэков (CometAPI primary → CometAPI fallback model → официальный провайдер).
  • Редактируйте логи. Не включайте API-ключи, полные промпты и чувствительный вывод модели в продакшен-логи ошибок.

Один бэкенд Next.js, несколько вариантов моделей

Переключение модели должно находиться в вашей серверной политике, а не в отдельных аккаунтах провайдеров. Route Handler в Next.js может хранить один ключ CometAPI приватным, принимать одобренный ID модели на запрос и стримить выбранную модель через один OpenAI-совместимый эндпоинт. Фронтенд остаётся простым, а бэкенд сохраняет контроль над доступом, валидацией, наблюдаемостью и стоимостью. Тот же аккаунт и ключ также позволяют работать с нативными API изображений и видео CometAPI — например, Flux для генерации изображений и Kling для генерации видео — так что этот чат-бэкенд можно расширить до мультимодальных сценариев без второй интеграции.

Используйте публичный каталог моделей CometAPI для поиска моделей и GET https://api.cometapi.com/api/models для автоматизированной проверки маршрутизации.

Часто задаваемые вопросы

Могу ли я использовать несколько AI-моделей в одном приложении Next.js?

Да. Держите один серверный маршрут и передавайте с каждым запросом ID модели из списка разрешений. Браузерный UI может предлагать выбор моделей, в то время как сервер контролирует, какие ID принимаются.

Как переключаться между GPT, Claude и Gemini?

Отправляйте выбранный ID модели в теле запроса. Сервер валидирует его по списку разрешений и пересылает тот же чат-пейлоад на выбранную модель через OpenAI-совместимый эндпоинт.

Где хранить мой CometAPI API-ключ?

Храните его в серверной переменной окружения, например COMETAPI_API_KEY. Никогда не публикуйте его через переменную NEXT_PUBLIC_ или в клиентском коде.

Делает ли OpenAI-совместимый API все модели взаимозаменяемыми?

Нет. Форма запроса переносима, но модели могут различаться поддерживаемыми параметрами, лимитами контекста, поведением инструментов, стилем вывода, латентностью и ценой. Тестируйте каждую модель из списка разрешений с вашими продакшен-промптами.

Могут ли Route Handlers в Next.js стримить ответы ИИ?

Да. Route Handler может вернуть поток Server-Sent Events с типом контента text/event-stream, при условии, что платформа деплоя и любой прокси перед ним не буферизуют ответ.

Могу ли я задеплоить это приложение как статический сайт Next.js?

Нет. Статический экспорт не может выполнять серверный Route Handler /api/chat или защитить API-ключ. Используйте Node.js или другой совместимый серверный рантайм с поддержкой потоковых ответов.

Продолжить обучение

Свяжите эту статью со следующим решением.

Посмотреть все темы
Опубликовано Aug 25, 2026
Последнее обновление Aug 26, 2026
2 просмотров
Проверено на ясность, указание источников и актуальную терминологию API.

Готовы сократить затраты на AI-разработку на 20%?

Начните бесплатно за несколько минут. Пробные кредиты включены. Карта не нужна.

Читать далее