Construire un Chatbot IA avec OpenAI Assistants API et Next.js

OpenAI Assistants API est fondamentalement différent du Chat Completions API. Au lieu de gérer vous-même l'historique des conversations, Assistants API vous offre des threads persistants, des outils intégrés comme la recherche de fichiers et l'interpréteur de code, et une exécution basée sur les runs qui gère tout automatiquement.
Dans ce tutoriel, vous construirez un chatbot IA complet avec Next.js qui exploite ces capacités — du streaming des réponses au téléchargement de documents que votre assistant peut consulter.
Pourquoi Assistants API ? Le Chat Completions API exige de gérer l'état de la conversation, d'implémenter des pipelines RAG et de construire des boucles d'exécution d'outils manuellement. Assistants API gère tout cela nativement — les threads persistent automatiquement, les fichiers sont indexés pour la recherche et le code s'exécute dans un environnement sandboxé.
Ce que vous apprendrez
À la fin de ce tutoriel, vous serez capable de :
- Créer et configurer un Assistant OpenAI avec des instructions personnalisées
- Gérer des threads de conversation persistants
- Streamer les réponses de l'assistant en temps réel
- Activer la recherche de fichiers pour que votre assistant puisse répondre à partir de documents téléchargés
- Activer l'interpréteur de code pour que votre assistant puisse écrire et exécuter du code Python
- Construire une interface de chat soignée avec Next.js et React
- Gérer les erreurs et implémenter les bonnes pratiques de production
Prérequis
Avant de commencer, assurez-vous d'avoir :
- Node.js 20+ installé (
node --version) - Des connaissances en TypeScript (generics, async/await)
- Une clé API OpenAI avec accès à Assistants API (obtenez-la sur platform.openai.com)
- Les fondamentaux de Next.js (App Router, Server Actions)
- Un éditeur de code — VS Code recommandé
Ce que vous allez construire
Un chatbot IA complet qui prend en charge :
- Conversations persistantes — les utilisateurs peuvent reprendre des discussions précédentes
- Streaming des réponses — les tokens apparaissent au fur et à mesure que l'assistant génère sa réponse
- Q&A sur documents — téléchargez des PDF et posez des questions sur leur contenu
- Exécution de code — l'assistant peut écrire et exécuter du code Python, retournant résultats et graphiques
- Threads multiples — gérez des contextes de conversation séparés
Étape 1 : Configuration du projet
Créez un nouveau projet Next.js et installez les dépendances requises :
npx create-next-app@latest openai-chatbot --typescript --tailwind --app --src-dir
cd openai-chatbotInstallez le SDK OpenAI :
npm install openaiCréez votre fichier d'environnement :
# .env.local
OPENAI_API_KEY=sk-proj-your-api-key-here
OPENAI_ASSISTANT_ID= # Nous le remplirons à l'étape 3Ajoutez les types de variables d'environnement dans src/env.d.ts :
declare namespace NodeJS {
interface ProcessEnv {
OPENAI_API_KEY: string;
OPENAI_ASSISTANT_ID: string;
}
}Étape 2 : Comprendre l'architecture d'Assistants API
Avant d'écrire du code, il est important de comprendre les concepts clés :
Assistant — Une entité IA configurée avec des instructions spécifiques, un modèle et des outils activés. Considérez-le comme un persona qui persiste à travers les conversations.
Thread — Une session de conversation. Les threads stockent l'historique complet des messages et sont persistants — ils survivent aux redémarrages du serveur et peuvent être repris à tout moment.
Message — Un seul message utilisateur ou assistant dans un thread. Les messages peuvent inclure du texte, des images et des pièces jointes.
Run — Une exécution de l'assistant sur un thread. Lorsque vous créez un run, l'assistant lit le thread, décide d'appeler des outils ou non, et génère une réponse.
Run Step — Une action granulaire dans un run (appel d'outil, création de message). Utile pour le débogage et l'affichage de la progression.
Le flux ressemble à ceci :
Créer Assistant → Créer Thread → Ajouter Message → Créer Run → Streamer Réponse
↑ |
└──────────────────────────┘
(la conversation continue)
Étape 3 : Création de l'Assistant
Vous pouvez créer un assistant via l'API ou le tableau de bord OpenAI. Faisons-le de manière programmatique pour que la configuration vive dans le code.
Créez src/lib/openai.ts :
import OpenAI from "openai";
export const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});Créez un script de configuration dans scripts/create-assistant.ts :
import OpenAI from "openai";
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
async function createAssistant() {
const assistant = await openai.beta.assistants.create({
name: "Noqta Assistant",
instructions: `You are a helpful, knowledgeable assistant. Follow these rules:
- Be concise but thorough
- Use code examples when explaining technical concepts
- When using file search results, cite the source document
- When asked to analyze data, use code interpreter to create visualizations
- Always respond in the same language the user writes in`,
model: "gpt-4o",
tools: [
{ type: "file_search" },
{ type: "code_interpreter" },
],
});
console.log("Assistant created:", assistant.id);
console.log("Add this to your .env.local:");
console.log(`OPENAI_ASSISTANT_ID=${assistant.id}`);
}
createAssistant();Exécutez-le :
npx tsx scripts/create-assistant.tsCopiez l'identifiant de l'assistant dans votre fichier .env.local.
Astuce : Vous pouvez également créer et gérer des assistants depuis le OpenAI Playground. Le tableau de bord vous offre une interface visuelle pour ajuster les instructions et tester les outils de manière interactive.
Étape 4 : Construction de l'API de gestion des threads
Les threads sont la colonne vertébrale d'Assistants API. Créez les routes API pour les gérer.
Créez src/app/api/threads/route.ts :
import { NextResponse } from "next/server";
import { openai } from "@/lib/openai";
export async function POST() {
try {
const thread = await openai.beta.threads.create();
return NextResponse.json({ threadId: thread.id });
} catch (error) {
return NextResponse.json(
{ error: "Failed to create thread" },
{ status: 500 }
);
}
}Ce endpoint crée un nouveau thread de conversation. Chaque thread reçoit un identifiant unique que vous stockerez côté client pour reprendre les conversations.
Étape 5 : Implémentation du streaming des réponses
C'est ici que la magie opère. Assistants API prend en charge le streaming via Server-Sent Events, ce qui donne à vos utilisateurs un effet de frappe en temps réel.
Créez src/app/api/chat/route.ts :
import { openai } from "@/lib/openai";
export async function POST(request: Request) {
const { threadId, message } = await request.json();
if (!threadId || !message) {
return new Response("Missing threadId or message", { status: 400 });
}
await openai.beta.threads.messages.create(threadId, {
role: "user",
content: message,
});
const stream = openai.beta.threads.runs.stream(threadId, {
assistant_id: process.env.OPENAI_ASSISTANT_ID,
});
const encoder = new TextEncoder();
const readable = new ReadableStream({
async start(controller) {
try {
for await (const event of stream) {
if (event.event === "thread.message.delta") {
const delta = event.data.delta;
if (delta.content) {
for (const block of delta.content) {
if (block.type === "text" && block.text?.value) {
controller.enqueue(
encoder.encode(`data: ${JSON.stringify({
type: "text",
content: block.text.value,
})}\n\n`)
);
}
}
}
}
if (event.event === "thread.run.completed") {
controller.enqueue(
encoder.encode(`data: ${JSON.stringify({ type: "done" })}\n\n`)
);
}
if (event.event === "thread.run.failed") {
controller.enqueue(
encoder.encode(`data: ${JSON.stringify({
type: "error",
content: "Run failed",
})}\n\n`)
);
}
}
} catch (err) {
controller.enqueue(
encoder.encode(`data: ${JSON.stringify({
type: "error",
content: "Stream error",
})}\n\n`)
);
} finally {
controller.close();
}
},
});
return new Response(readable, {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
},
});
}Cette route :
- Ajoute le message utilisateur au thread
- Crée un run en streaming contre l'assistant
- Envoie les deltas de texte au client sous forme de Server-Sent Events
- Signale la fin ou les erreurs
Étape 6 : Activation de la recherche de fichiers (RAG)
La recherche de fichiers permet à votre assistant de répondre aux questions à partir de documents téléchargés. L'API découpe, vectorise et indexe automatiquement vos fichiers — aucune base de données vectorielle externe nécessaire.
Créez src/app/api/files/route.ts :
import { NextResponse } from "next/server";
import { openai } from "@/lib/openai";
export async function POST(request: Request) {
const formData = await request.formData();
const file = formData.get("file") as File;
const threadId = formData.get("threadId") as string;
if (!file || !threadId) {
return NextResponse.json(
{ error: "Missing file or threadId" },
{ status: 400 }
);
}
const uploadedFile = await openai.files.create({
file,
purpose: "assistants",
});
await openai.beta.threads.messages.create(threadId, {
role: "user",
content: "I've uploaded a document for reference.",
attachments: [
{
file_id: uploadedFile.id,
tools: [{ type: "file_search" }],
},
],
});
return NextResponse.json({
fileId: uploadedFile.id,
fileName: file.name,
});
}Lorsqu'un utilisateur télécharge un fichier :
- Le fichier est envoyé au stockage OpenAI
- Il est attaché à un message dans le thread avec l'outil
file_search - L'assistant peut maintenant rechercher dans le contenu du document pour répondre aux questions
Types de fichiers supportés : PDF, DOCX, TXT, MD, JSON, CSV, HTML et plus encore. Chaque fichier peut atteindre 512 Mo. L'API gère automatiquement le découpage et la vectorisation — vous n'avez pas besoin de gérer un vector store manuellement pour les pièces jointes au niveau du thread.
Étape 7 : Activation de l'interpréteur de code
L'interpréteur de code permet à votre assistant d'écrire et d'exécuter du code Python dans un environnement sandboxé. C'est puissant pour l'analyse de données, les mathématiques, la génération de graphiques et le traitement de fichiers.
L'interpréteur de code est déjà activé dans la configuration de l'assistant à l'étape 3. Gérons maintenant la sortie dans la route de streaming.
Mettez à jour le gestionnaire de streaming dans src/app/api/chat/route.ts pour gérer les événements de l'interpréteur de code :
for await (const event of stream) {
if (event.event === "thread.message.delta") {
const delta = event.data.delta;
if (delta.content) {
for (const block of delta.content) {
if (block.type === "text" && block.text?.value) {
controller.enqueue(
encoder.encode(`data: ${JSON.stringify({
type: "text",
content: block.text.value,
})}\n\n`)
);
}
if (block.type === "image_file" && block.image_file) {
controller.enqueue(
encoder.encode(`data: ${JSON.stringify({
type: "image",
fileId: block.image_file.file_id,
})}\n\n`)
);
}
}
}
}
if (event.event === "thread.run.step.delta") {
const stepDelta = event.data.delta;
if (stepDelta.step_details?.type === "tool_calls") {
for (const toolCall of stepDelta.step_details.tool_calls ?? []) {
if (
toolCall.type === "code_interpreter" &&
toolCall.code_interpreter?.input
) {
controller.enqueue(
encoder.encode(`data: ${JSON.stringify({
type: "code",
content: toolCall.code_interpreter.input,
})}\n\n`)
);
}
}
}
}
if (event.event === "thread.run.completed") {
controller.enqueue(
encoder.encode(`data: ${JSON.stringify({ type: "done" })}\n\n`)
);
}
if (event.event === "thread.run.failed") {
controller.enqueue(
encoder.encode(`data: ${JSON.stringify({
type: "error",
content: "Run failed",
})}\n\n`)
);
}
}Le stream émet maintenant trois types de contenu :
- text — messages réguliers de l'assistant
- code — code Python exécuté par l'interpréteur de code
- image — graphiques ou visualisations générés (retournés en tant qu'identifiants de fichiers)
Pour afficher les images générées par l'interpréteur de code, ajoutez un endpoint pour récupérer les fichiers :
// src/app/api/files/[fileId]/route.ts
import { NextResponse } from "next/server";
import { openai } from "@/lib/openai";
export async function GET(
_request: Request,
{ params }: { params: Promise<{ fileId: string }> }
) {
const { fileId } = await params;
const response = await openai.files.content(fileId);
const buffer = Buffer.from(await response.arrayBuffer());
return new NextResponse(buffer, {
headers: {
"Content-Type": "image/png",
"Cache-Control": "public, max-age=3600",
},
});
}Étape 8 : Construction de l'interface de chat
Construisons maintenant le frontend. Créez un hook personnalisé pour gérer la connexion de streaming :
Créez src/hooks/use-assistant-chat.ts :
"use client";
import { useState, useCallback, useRef } from "react";
interface ChatMessage {
id: string;
role: "user" | "assistant";
content: string;
codeBlocks?: string[];
images?: string[];
}
export function useAssistantChat() {
const [messages, setMessages] = useState<ChatMessage[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [threadId, setThreadId] = useState<string | null>(null);
const abortRef = useRef<AbortController | null>(null);
const initThread = useCallback(async () => {
const res = await fetch("/api/threads", { method: "POST" });
const { threadId: id } = await res.json();
setThreadId(id);
return id;
}, []);
const sendMessage = useCallback(
async (content: string) => {
const currentThreadId = threadId ?? (await initThread());
setIsLoading(true);
const userMessage: ChatMessage = {
id: crypto.randomUUID(),
role: "user",
content,
};
const assistantMessage: ChatMessage = {
id: crypto.randomUUID(),
role: "assistant",
content: "",
codeBlocks: [],
images: [],
};
setMessages((prev) => [...prev, userMessage, assistantMessage]);
abortRef.current = new AbortController();
try {
const res = await fetch("/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
threadId: currentThreadId,
message: content,
}),
signal: abortRef.current.signal,
});
const reader = res.body?.getReader();
const decoder = new TextDecoder();
if (!reader) throw new Error("No reader available");
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n\n");
buffer = lines.pop() ?? "";
for (const line of lines) {
if (!line.startsWith("data: ")) continue;
const data = JSON.parse(line.slice(6));
if (data.type === "text") {
setMessages((prev) => {
const updated = [...prev];
const last = updated[updated.length - 1];
last.content += data.content;
return updated;
});
}
if (data.type === "code") {
setMessages((prev) => {
const updated = [...prev];
const last = updated[updated.length - 1];
last.codeBlocks = [
...(last.codeBlocks ?? []),
data.content,
];
return updated;
});
}
if (data.type === "image") {
setMessages((prev) => {
const updated = [...prev];
const last = updated[updated.length - 1];
last.images = [
...(last.images ?? []),
`/api/files/${data.fileId}`,
];
return updated;
});
}
if (data.type === "done") {
setIsLoading(false);
}
if (data.type === "error") {
setIsLoading(false);
}
}
}
} catch (err) {
if ((err as Error).name !== "AbortError") {
setIsLoading(false);
}
}
},
[threadId, initThread]
);
const stopGeneration = useCallback(() => {
abortRef.current?.abort();
setIsLoading(false);
}, []);
const resetChat = useCallback(() => {
setMessages([]);
setThreadId(null);
setIsLoading(false);
}, []);
return {
messages,
isLoading,
threadId,
sendMessage,
stopGeneration,
resetChat,
};
}Maintenant, construisez le composant de chat. Créez src/components/chat.tsx :
"use client";
import { useState, useRef, useEffect } from "react";
import { useAssistantChat } from "@/hooks/use-assistant-chat";
export function Chat() {
const { messages, isLoading, sendMessage, stopGeneration, resetChat } =
useAssistantChat();
const [input, setInput] = useState("");
const messagesEndRef = useRef<HTMLDivElement>(null);
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
}, [messages]);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!input.trim() || isLoading) return;
sendMessage(input.trim());
setInput("");
};
return (
<div className="flex flex-col h-screen max-w-3xl mx-auto">
<header className="flex items-center justify-between p-4 border-b">
<h1 className="text-lg font-semibold">Assistant IA</h1>
<button
onClick={resetChat}
className="text-sm text-gray-500 hover:text-gray-700"
>
Nouvelle conversation
</button>
</header>
<div className="flex-1 overflow-y-auto p-4 space-y-4">
{messages.length === 0 && (
<div className="text-center text-gray-400 mt-20">
<p className="text-xl mb-2">Démarrer une conversation</p>
<p className="text-sm">
Posez des questions, téléchargez des fichiers ou demandez une analyse de données.
</p>
</div>
)}
{messages.map((msg) => (
<div
key={msg.id}
className={`flex ${
msg.role === "user" ? "justify-end" : "justify-start"
}`}
>
<div
className={`max-w-[80%] rounded-2xl px-4 py-3 ${
msg.role === "user"
? "bg-blue-600 text-white"
: "bg-gray-100 text-gray-900"
}`}
>
<p className="whitespace-pre-wrap">{msg.content}</p>
{msg.codeBlocks?.map((code, i) => (
<pre
key={i}
className="mt-2 p-3 bg-gray-900 text-green-400 rounded-lg text-sm overflow-x-auto"
>
<code>{code}</code>
</pre>
))}
{msg.images?.map((src, i) => (
<img
key={i}
src={src}
alt="Graphique généré"
className="mt-2 rounded-lg max-w-full"
/>
))}
</div>
</div>
))}
{isLoading && messages[messages.length - 1]?.content === "" && (
<div className="flex justify-start">
<div className="bg-gray-100 rounded-2xl px-4 py-3">
<div className="flex space-x-1">
<span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" />
<span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce [animation-delay:0.1s]" />
<span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce [animation-delay:0.2s]" />
</div>
</div>
</div>
)}
<div ref={messagesEndRef} />
</div>
<form onSubmit={handleSubmit} className="p-4 border-t">
<div className="flex gap-2">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Tapez votre message..."
className="flex-1 rounded-xl border border-gray-300 px-4 py-3 focus:outline-none focus:ring-2 focus:ring-blue-500"
disabled={isLoading}
/>
{isLoading ? (
<button
type="button"
onClick={stopGeneration}
className="px-6 py-3 rounded-xl bg-red-500 text-white font-medium hover:bg-red-600 transition-colors"
>
Arrêter
</button>
) : (
<button
type="submit"
disabled={!input.trim()}
className="px-6 py-3 rounded-xl bg-blue-600 text-white font-medium hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
Envoyer
</button>
)}
</div>
</form>
</div>
);
}Enfin, ajoutez le chat à votre page. Mettez à jour src/app/page.tsx :
import { Chat } from "@/components/chat";
export default function Home() {
return <Chat />;
}Étape 9 : Ajout du téléchargement de fichiers à l'interface
Ajoutons un bouton de téléchargement de fichiers à l'interface de chat pour que les utilisateurs puissent joindre des documents.
Créez src/components/file-upload.tsx :
"use client";
import { useRef, useState } from "react";
interface FileUploadProps {
threadId: string | null;
onUploadComplete: (fileName: string) => void;
disabled?: boolean;
}
export function FileUpload({
threadId,
onUploadComplete,
disabled,
}: FileUploadProps) {
const fileRef = useRef<HTMLInputElement>(null);
const [uploading, setUploading] = useState(false);
const handleUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file || !threadId) return;
setUploading(true);
const formData = new FormData();
formData.append("file", file);
formData.append("threadId", threadId);
try {
const res = await fetch("/api/files", {
method: "POST",
body: formData,
});
if (res.ok) {
const { fileName } = await res.json();
onUploadComplete(fileName);
}
} finally {
setUploading(false);
if (fileRef.current) fileRef.current.value = "";
}
};
return (
<>
<input
ref={fileRef}
type="file"
onChange={handleUpload}
accept=".pdf,.docx,.txt,.md,.csv,.json"
className="hidden"
/>
<button
type="button"
onClick={() => fileRef.current?.click()}
disabled={disabled || uploading || !threadId}
className="p-3 rounded-xl border border-gray-300 hover:bg-gray-50 disabled:opacity-50 transition-colors"
title="Télécharger un fichier"
>
{uploading ? (
<svg
className="w-5 h-5 animate-spin text-gray-500"
viewBox="0 0 24 24"
fill="none"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
/>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
/>
</svg>
) : (
<svg
className="w-5 h-5 text-gray-500"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M18.375 12.739l-7.693 7.693a4.5 4.5 0 01-6.364-6.364l10.94-10.94A3 3 0 1119.5 7.372L8.552 18.32m.009-.01l-.01.01m5.699-9.941l-7.81 7.81a1.5 1.5 0 002.112 2.13"
/>
</svg>
)}
</button>
</>
);
}Étape 10 : Persistance des threads avec le stockage local
Pour permettre aux utilisateurs de reprendre des conversations après rechargement de la page, persistez les identifiants de threads dans le stockage local.
Mettez à jour src/hooks/use-assistant-chat.ts pour ajouter la persistance :
const STORAGE_KEY = "openai-chat-threads";
interface ThreadInfo {
id: string;
title: string;
createdAt: string;
}
function getStoredThreads(): ThreadInfo[] {
if (typeof window === "undefined") return [];
const stored = localStorage.getItem(STORAGE_KEY);
return stored ? JSON.parse(stored) : [];
}
function storeThread(thread: ThreadInfo) {
const threads = getStoredThreads();
threads.unshift(thread);
localStorage.setItem(STORAGE_KEY, JSON.stringify(threads.slice(0, 50)));
}Ajoutez la route API pour l'historique du thread. Créez src/app/api/threads/[threadId]/messages/route.ts :
import { NextResponse } from "next/server";
import { openai } from "@/lib/openai";
export async function GET(
_request: Request,
{ params }: { params: Promise<{ threadId: string }> }
) {
const { threadId } = await params;
try {
const messages = await openai.beta.threads.messages.list(threadId, {
order: "asc",
});
return NextResponse.json({ messages: messages.data });
} catch {
return NextResponse.json(
{ error: "Thread not found" },
{ status: 404 }
);
}
}Étape 11 : Considérations de production
Avant le déploiement, adressez ces points importants :
Limitation du débit
Protégez vos routes API contre les abus :
// src/lib/rate-limit.ts
const rateLimitMap = new Map<string, { count: number; resetTime: number }>();
export function rateLimit(
identifier: string,
maxRequests = 20,
windowMs = 60_000
): boolean {
const now = Date.now();
const entry = rateLimitMap.get(identifier);
if (!entry || now > entry.resetTime) {
rateLimitMap.set(identifier, { count: 1, resetTime: now + windowMs });
return true;
}
if (entry.count >= maxRequests) {
return false;
}
entry.count++;
return true;
}Gestion des coûts
Assistants API facture :
- Utilisation des tokens — tokens d'entrée et de sortie au tarif du modèle
- Stockage de fichiers — 0,20 $ par Go par jour
- Sessions de l'interpréteur de code — 0,03 $ par session
Pour contrôler les coûts :
- Définissez
max_prompt_tokensetmax_completion_tokenssur les runs - Nettoyez régulièrement les anciens threads et fichiers
- Surveillez l'utilisation via le tableau de bord OpenAI
const stream = openai.beta.threads.runs.stream(threadId, {
assistant_id: process.env.OPENAI_ASSISTANT_ID,
max_prompt_tokens: 50000,
max_completion_tokens: 4096,
});Test de l'implémentation
Lancez le serveur de développement :
npm run devTestez ces scénarios :
- Conversation basique — Envoyez un message et vérifiez que le streaming fonctionne
- Questions de suivi — Confirmez que l'assistant se souvient du contexte précédent dans le thread
- Téléchargement de fichier — Téléchargez un PDF et posez des questions sur son contenu
- Interpréteur de code — Demandez "Créez un diagramme en barres montrant la population des 5 plus grands pays"
- Récupération d'erreur — Déconnectez brièvement votre réseau et vérifiez que l'interface gère correctement
- Nouvelle conversation — Cliquez sur "Nouvelle conversation" et vérifiez qu'un nouveau thread est créé
Dépannage
Erreur "Assistant not found"
Vérifiez que votre OPENAI_ASSISTANT_ID dans .env.local correspond à un assistant dans votre compte OpenAI. Les assistants sont liés à l'organisation de la clé API.
Le streaming s'arrête en cours de réponse
Cela signifie généralement que le run a atteint une limite de tokens ou a expiré. Augmentez max_prompt_tokens ou vérifiez les temps d'exécution longs des outils.
La recherche de fichiers ne renvoie pas de résultats
Assurez-vous que le fichier a été téléchargé avec purpose: "assistants". Les fichiers téléchargés à d'autres fins ne sont pas indexés pour la recherche. Vérifiez également que le format de fichier est supporté.
L'interpréteur de code expire Les calculs complexes peuvent dépasser le timeout d'exécution. Décomposez les requêtes complexes en étapes plus petites ou demandez à l'assistant de simplifier son approche.
Erreur "Run already active"
Un seul run peut être actif par thread à la fois. Attendez que le run actuel se termine avant d'en démarrer un autre, ou annulez le run actif avec openai.beta.threads.runs.cancel(threadId, runId).
Prochaines étapes
Maintenant que vous avez un chatbot IA fonctionnel, envisagez ces améliorations :
- Authentification — Ajoutez l'authentification utilisateur avec NextAuth.js ou Clerk pour isoler les threads par utilisateur
- Vector stores — Créez des vector stores partagés pour des bases de connaissances à l'échelle de l'organisation
- Appel de fonctions — Ajoutez des fonctions personnalisées pour que l'assistant puisse interagir avec vos propres API
- Rendu Markdown — Utilisez
react-markdownavec coloration syntaxique pour les réponses formatées - Responsive mobile — Optimisez la mise en page du chat pour les petits écrans
- Analytique — Suivez l'utilisation des tokens, les temps de réponse et la satisfaction des utilisateurs
Conclusion
Vous avez construit un chatbot IA complet avec OpenAI Assistants API et Next.js. Assistants API élimine une grande partie du code répétitif lié à la construction d'applications IA — la gestion des threads, l'indexation des documents et l'exécution de code sont tous gérés par la plateforme.
L'avantage principal de cette approche est la persistance. Contrairement aux complétions de chat sans état, vos threads préservent l'historique complet des conversations, les fichiers attachés restent indexés et les utilisateurs peuvent reprendre là où ils se sont arrêtés. Cela rend Assistants API particulièrement adapté au support client, aux bases de connaissances internes et aux outils d'analyse de données.
Le code de ce tutoriel est une base solide. Étendez-le avec l'authentification, des outils personnalisés et une interface soignée pour créer un assistant IA adapté à votre cas d'utilisation spécifique.
Discutez de votre projet avec nous
Nous sommes ici pour vous aider avec vos besoins en développement Web. Planifiez un appel pour discuter de votre projet et comment nous pouvons vous aider.
Trouvons les meilleures solutions pour vos besoins.
Articles connexes

Creer un Podcast a partir d'un PDF avec Vercel AI SDK et LangChain
Apprenez a creer un podcast a partir d'un PDF en utilisant Vercel AI SDK, PDFLoader de LangChain, ElevenLabs et Next.js.

Fine-tuning GPT avec OpenAI, Next.js et Vercel AI SDK
Apprenez a fine-tuner GPT-4o en utilisant OpenAI, Next.js et Vercel AI SDK pour creer Shooketh, un bot IA inspire de Shakespeare.

Implémenter le RAG sur les PDFs avec File Search dans l'API Responses
Un guide complet sur l'exploitation de l'API Responses pour une génération augmentée par récupération (RAG) efficace sur les documents PDF.