DeepSeek Vision and Grok Imagine models are now live on CometAPI →
guide/Recherche CometAPI

Comment créer une application de chat d'IA multi-modèles avec Next.js et CometAPI

Créer une application de chat IA Next.js en streaming qui bascule entre les modèles GPT, Claude et Gemini via une seule clé CometAPI et un point de terminaison backend.

CometAPI
AnnaÉquipe de recherche sur les modèles IA et API
Mis à jour Aug 25, 2026 14 min de lecture
Comment créer une application de chat d'IA multi-modèles avec Next.js et CometAPI
Utiliser ce modèle

Passez le premier appel 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)

Pour un chatbot Next.js ayant besoin de plusieurs fournisseurs de modèles, le backend le plus utile est celui qui centralise l’authentification tout en laissant l’application choisir un modèle par requête. CometAPI fournit ce schéma : le serveur conserve une unique clé API et l’URL de base compatible OpenAI https://api.cometapi.com/v1, tandis que le champ model de la requête sélectionne un modèle de chat disponible (GPT, Claude, Gemini, ou autre).

Ce tutoriel crée un projet App Router opérationnel avec une liste blanche de modèles côté serveur, un Route Handler en streaming, un sélecteur de modèles côté navigateur, des variables d’environnement protégées, une interface de chat complète et des conseils de déploiement. La clé CometAPI ne quitte jamais le navigateur.

Qu’est-ce qu’une application de chat IA multi-modèles ?

Une architecture de chat IA multi-modèles place un backend contrôlé par l’application entre l’interface utilisateur et plusieurs fournisseurs de modèles. Dans ce projet, le navigateur envoie la conversation à /api/chat, tandis que le serveur garde la clé CometAPI privée, valide l’ID de modèle demandé et transmet la requête via une API compatible.

Comment fonctionne le changement de modèle dans Next.js ?

Changer de modèle est une décision de routage. Le navigateur envoie un ID de modèle approuvé avec la conversation à /api/chat. Le Route Handler ajoute l’identifiant CometAPI, appelle POST /v1/chat/completions et diffuse la réponse du modèle sélectionné au navigateur.

Le point de terminaison partagé ne rend pas tous les modèles identiques. Le style de sortie, le comportement des outils, les paramètres pris en charge, les limites de contexte et les tarifs peuvent différer. Conservez les IDs de modèles dans une liste blanche côté serveur et testez chaque route avec les mêmes prompts d’application avant de la rendre disponible aux utilisateurs.

Ce que vous allez construire

L’application finale comporte un seul backend Next.js et trois routes de chat sélectionnables :

Model IDRôle d’exemplePrix CometAPI
gemini-3.7-flashChat à fort volume, sensible au coût0,60 $ entrée / 3,00 $ sortie
claude-opus-5Raisonnement premium ciblé4 $ entrée / 20 $ sortie
gpt-5.6gpt-5.6 est la route CometAPI générique pour GPT-5.6 et correspond actuellement au palier Sol.3,2 $ entrée / 16 $ sortie dans le palier court contexte

Note sur les tarifs : Les prix ci‑dessous sont en USD par million de jetons, vérifiés le 21 août 2026, et peuvent évoluer. Vérifiez toujours les tarifs actuels sur les pages des modèles avant un usage en production. Le tableau est un exemple de routage, pas un classement de qualité. GPT-5.6 utilise un palier tarifaire supérieur au‑delà de 272 000 jetons. Voir les pages modèles liées Gemini 3.7 Flash, Claude Opus 5 et GPT-5.6 pour les tarifs datés utilisés ici.

CometAPI facture les modèles de façon transparente par rapport à l’API officielle de chaque fournisseur. Les modèles à tarification officielle unifiée — OpenAI, Claude, Gemini, et similaires — sont facturés au jeton avec un ratio de 0,8:1 par rapport au prix officiel, soit 20 % de réduction ; les modèles sans API officielle (MidJourney, Kling, Luma) sont facturés à l’appel selon les tarifs fixés par CometAPI, également réduits de 20 %. Voir le Guide de tarification CometAPI pour la formule de majoration, les tarifs par modèle et les unités de facturation.

Les trois routes couvrent différents compromis. Gemini 3.7 Flash est le bourreau de travail agentique efficace de Google, avec une entrée multimodale et une fenêtre de contexte d’1 048 576 jetons pour le chat à fort volume, le code et les flux de travail de connaissances. Claude Opus 5 est le modèle de pointe d’Anthropic pour le raisonnement, performant en analyse multi‑étapes, en code et en rédaction soignée longue à un prix premium. GPT-5.6 est le modèle polyvalent d’OpenAI, équilibrant raisonnement, utilisation d’outils et rédaction sur une large fenêtre de contexte pour le trafic de production quotidien.

Avant de commencer

Vous avez besoin de Node.js, npm, d’un compte CometAPI et d’une clé API côté serveur depuis le Quick Start CometAPI. Les trois IDs de modèles exacts ci‑dessus étaient disponibles dans le catalogue en ligne au 21 août 2026, non marqués comme à venir, et exposaient POST /v1/chat/completions.

Le tutoriel utilise ces paramètres partagés :

Étape 1 : Créer l’application Next.js

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

Aucun SDK de fournisseur de modèles n’est requis pour cette version. Le serveur utilise l’API fetch intégrée et retransmet le flux Server‑Sent Events amont.

Étape 2 : Conserver la clé CometAPI sur le serveur

Créez .env.local à la racine du projet :

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

Ne préfixez pas la clé avec NEXT_PUBLIC_. Next.js n’expose que les variables avec ce préfixe aux bundles du navigateur ; la clé API doit rester dans le Route Handler côté serveur.

Étape 3 : Définir la politique de modèles

Placez les IDs autorisés dans le backend, pas seulement dans la liste déroulante. Un utilisateur peut contourner les contrôles du navigateur et appeler votre route directement, donc le serveur doit rejeter les valeurs de modèle inconnues.

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

Le client utilisera les mêmes trois IDs pour son sélecteur, tandis que le serveur restera la source de vérité.

Étape 4 : Diffuser CometAPI via un Route Handler

Créez app/api/chat/route.ts. La route valide une charge utile de chat texte uniquement, appelle CometAPI avec stream: true et renvoie le flux d’événements amont sans exposer l’identifiant.

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",    },  });}

Le gestionnaire transmet le signal de déconnexion du navigateur en amont, de sorte que la fermeture de la requête peut arrêter une génération inutile. Il renvoie également une erreur assainie au client tout en conservant les détails de diagnostic dans les journaux serveur.

Étape 5 : Ajouter la sélection de modèle et l’analyse du flux

Créez un composant client dans app/page.tsx. Il envoie le modèle sélectionné avec la conversation, analyse chaque événement data: et ajoute le texte incrémental delta.content au dernier message de l’assistant.

Le navigateur n’appelle jamais directement CometAPI. Sa seule destination est votre route /api/chat, qui conserve la clé privée et applique la liste blanche.

Code principal du projet

Le Route Handler ci‑dessus constitue le fichier complet app/api/chat/route.ts. Ajoutez la page, la mise en page et la feuille de style suivantes pour terminer le projet exécutable.

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. */

Étape 6 : Exécuter et déployer l’application

npm run dev

Ouvrez http://localhost:3000, sélectionnez un modèle et envoyez un message. Pour un déploiement Node.js en production, ajoutez COMETAPI_API_KEY et COMETAPI_BASE_URL aux paramètres d’environnement côté serveur de l’hôte, puis exécutez :

npm run buildnpm run start

Utilisez un hébergement qui prend en charge les réponses en streaming. Une exportation statique ne peut pas exécuter le Route Handler /api/chat.

Tester la route de streaming

Avec le serveur de développement en cours d’exécution, appelez directement votre backend :

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."}    ]  }'

Une requête réussie renvoie des Server‑Sent Events. Les IDs et le texte exacts varient, mais le flux suit cette structure :

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

L’analyseur du navigateur lit chaque événement SSE, extrait choices[0].delta.content et ajoute le texte diffusé au message de l’assistant au fur et à mesure de l’arrivée des fragments.

Erreurs d’intégration courantes

SymptômeCauseCorrectif
Erreur d’authentification 401Clé côté serveur manquante ou invalideDéfinir COMETAPI_API_KEY ; ne pas l’exposer avec NEXT_PUBLIC_.
404 ou mauvaise routeL’URL de base ne contient pas /v1Utiliser https://api.cometapi.com/v1.
400 modèle non pris en chargeL’ID n’est pas dans la liste blancheUtiliser un ID de modèle texte exact et en ligne, et mettre à jour les sélecteurs.
La réponse arrive d’un coupL’hôte ou le proxy met en mémoire tampon le fluxDésactiver la transformation de réponse et utiliser un déploiement Node.js compatible streaming.

Préparer le backend de chat pour la production

  • Authentifiez vos propres utilisateurs. N’exposez pas une route publique qui dépense des crédits pour un trafic anonyme.
  • Limitez le débit par utilisateur et par IP. Bornes sur les flux concurrents, requêtes par minute, nombre et longueur des messages.
  • Conservez la liste blanche des modèles côté serveur. Le sélecteur du navigateur est une commodité, pas une frontière de sécurité.
  • Validez le catalogue en ligne lors du déploiement. Interrogez GET https://api.cometapi.com/api/models et échouez la release si un ID configuré est à venir, indisponible ou sans point de terminaison de complétion de chat.
  • Suivez le coût par route. Journalisez le modèle sélectionné, l’ID de requête, la latence, l’usage de jetons et l’ID utilisateur. Définissez des quotas de clé ou des plafonds de dépenses dans le tableau de bord CometAPI si nécessaire.
  • Gérez les déconnexions et délais d’expiration. Préservez request.signal, définissez un délai applicatif et arrêtez le travail quand le client part.
  • Ne masquez pas les erreurs de configuration avec un repli. Remontez les réponses 400 et 401. N’utilisez un autre modèle que pour un ensemble borné d’échecs réessayables et seulement lorsque le schéma de requête est compatible ; voir le Guide de repli de modèle CometAPI pour une chaîne de repli à deux niveaux (CometAPI primaire → modèle de repli CometAPI → fournisseur officiel).
  • Rédigez les journaux. Gardez les clés API, les prompts complets et les sorties sensibles des modèles hors des journaux d’erreurs de production.

Un seul backend Next.js, plusieurs choix de modèles

Le commutateur de modèle appartient à votre politique serveur, pas à des comptes de fournisseurs distincts. Un Route Handler Next.js peut conserver une clé CometAPI privée, accepter un ID de modèle approuvé par requête et diffuser le modèle sélectionné via un point de terminaison compatible OpenAI. Le frontend reste simple, tandis que le backend conserve le contrôle sur l’accès, la validation, l’observabilité et les coûts. Le même compte et la même clé permettent aussi d’accéder aux APIs natives image et vidéo de CometAPI — comme Flux pour la génération d’images et Kling pour la vidéo — de sorte que ce backend de chat peut s’étendre à des workflows multimodaux sans seconde intégration.

Utilisez le répertoire public des modèles CometAPI pour la découverte de modèles et GET https://api.cometapi.com/api/models pour la validation automatisée du routage.

Foire aux questions

Puis-je utiliser plusieurs modèles d’IA dans une seule application Next.js ?

Oui. Conservez une unique route côté serveur et passez un ID de modèle autorisé avec chaque requête. L’UI du navigateur peut proposer des choix de modèles, tandis que le serveur contrôle les IDs acceptés.

Comment basculer entre GPT, Claude et Gemini ?

Envoyez l’ID de modèle sélectionné dans le corps de la requête. Le serveur le valide contre une liste blanche et transmet la même charge utile de chat au modèle choisi via le point de terminaison compatible OpenAI.

Où dois-je stocker ma clé API CometAPI ?

Stockez‑la dans une variable d’environnement côté serveur telle que COMETAPI_API_KEY. Ne l’exposez jamais via une variable NEXT_PUBLIC_ ou du code côté client.

Une API compatible OpenAI rend‑elle tous les modèles interchangeables ?

Non. La forme de requête est portable, mais les modèles peuvent différer en paramètres pris en charge, limites de contexte, comportement des outils, style de sortie, latence et prix. Testez chaque modèle en liste blanche avec vos prompts de production.

Les Route Handlers Next.js peuvent‑ils diffuser des réponses IA ?

Oui. Un Route Handler peut renvoyer le flux d’événements SSE amont avec un type de contenu text/event-stream, à condition que la plateforme de déploiement et tout proxy en frontal ne tamponnent pas la réponse.

Puis‑je déployer cette application en site Next.js statique ?

Non. Une exportation statique ne peut pas exécuter le Route Handler /api/chat ni protéger la clé API. Utilisez un runtime serveur Node.js ou un autre compatible prenant en charge les réponses en streaming.

Continuer à apprendre

Reliez cet article à la décision suivante.

Voir tous les sujets
Publié le Aug 25, 2026
Dernière mise à jour Aug 25, 2026
0 vues
Revu pour la clarté, l'attribution des sources et la terminologie API actuelle.

Prêt à réduire vos coûts de développement IA de 20 % ?

Démarrez gratuitement en quelques minutes. Crédits d'essai offerts. Aucune carte bancaire requise.

En savoir plus