L'Assistants API d'OpenAI sera définitivement fermée le 26 août 2026. Si votre application en dépend — threads, runs, boucles de polling, objets assistant — vous devez migrer dès maintenant. Le remplaçant est la Responses API, une interface plus propre, plus rapide et plus capable qui condense l'intégralité du flux de travail Assistants en un seul appel de fonction.
Ce tutoriel vous guide à travers tout : utilisation de base, outils intégrés (recherche web et interpréteur de code), appel de fonctions personnalisées, streaming, gestion des conversations multi-tours, et un guide de migration complet. À la fin, votre application Next.js 15 disposera d'un backend d'agent IA prêt pour la production.
Prérequis
Avant de commencer, assurez-vous d'avoir :
- Node.js 20 ou supérieur installé
- Un compte OpenAI avec une clé API (sur platform.openai.com/api-keys)
- Un projet Next.js 15 (
npx create-next-app@latest my-agent --typescript) - Une familiarité avec TypeScript et les patterns async/await
Ce que vous allez construire
Un assistant de recherche IA multilingue dans Next.js 15 capable de :
- Répondre aux questions en s'appuyant sur une recherche web en temps réel
- Exécuter du code Python à la demande avec l'interpréteur de code
- Appeler des fonctions personnalisées pour récupérer des données structurées
- Streamer les réponses mot par mot vers le navigateur
- Maintenir l'historique de conversations multi-tours sans renvoyer le contexte complet
Qu'est-ce que la Responses API ?
La Responses API est l'interface unifiée d'OpenAI pour l'IA agentique, lancée début 2025 et désormais le chemin recommandé pour tous les nouveaux projets. Le modèle mental est simple :
- Vous envoyez des items d'entrée (messages, résultats d'outils)
- Vous recevez des items de sortie (texte, appels de fonctions, résultats d'outils, traces de raisonnement)
- L'API gère la boucle agentique — elle appelle les outils intégrés, exécute les appels de fonctions, et les enchaîne automatiquement au sein d'une seule requête
Pourquoi est-elle meilleure que l'Assistants API ?
L'Assistants API nécessitait d'orchestrer quatre objets distincts — Assistants, Threads, Messages, et Runs — avec une boucle de polling pour attendre la complétion. Un simple chatbot nécessitait six appels API. La Responses API condense tout cela en un seul appel responses.create(). L'utilisation du cache s'améliore de 40 à 80%, réduisant significativement les coûts. Vous débloquez également des capacités plus récentes : recherche approfondie, serveurs MCP distants, et computer use.
Étape 1 : Installer et configurer le SDK
Installez le dernier SDK OpenAI pour Node.js :
npm install openaiAjoutez votre clé API dans .env.local :
OPENAI_API_KEY=sk-proj-votre-clé-iciCréez un client OpenAI partagé dans lib/openai.ts :
import OpenAI from "openai";
export const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
maxRetries: 3,
});Définir maxRetries: 3 vous donne un backoff exponentiel automatique pour les erreurs transitoires — important en production.
Étape 2 : Votre première réponse
Créez une route API basique dans app/api/chat/route.ts :
import { openai } from "@/lib/openai";
import { NextResponse } from "next/server";
export async function POST(req: Request) {
const { message } = await req.json();
const response = await openai.responses.create({
model: "gpt-4.5",
instructions: "Tu es un assistant de recherche utile.",
input: message,
});
return NextResponse.json({ text: response.output_text });
}Trois paramètres clés :
model— le modèle à utiliser (gpt-4.5,gpt-4.5-mini,o3, etc.)instructions— remplace le message système ; définit la personnalité et le comportement de l'agentinput— le message de l'utilisateur (chaîne ou tableau d'items d'entrée)
response.output_text est une propriété de commodité qui extrait le premier contenu textuel de la réponse.
Étape 3 : Items d'entrée et de sortie
Pour les conversations multi-tours, passez un tableau d'items dans input :
const response = await openai.responses.create({
model: "gpt-4.5",
input: [
{ role: "user", content: "Quelle est la capitale de la Tunisie ?" },
{ role: "assistant", content: "La capitale de la Tunisie est Tunis." },
{ role: "user", content: "Quelle est sa population ?" },
],
});Le tableau response.output contient des items de sortie typés. Vérifiez toujours item.type avant d'accéder aux champs spécifiques au type :
for (const item of response.output) {
if (item.type === "message") {
for (const part of item.content) {
if (part.type === "output_text") {
console.log(part.text);
}
}
}
}Les types possibles d'items de sortie incluent message, function_call, function_call_output, reasoning, et web_search_call.
Étape 4 : Outil intégré — Recherche web
Activez la recherche web en temps réel en ajoutant { type: "web_search_preview" } au tableau tools. Le modèle décide de manière autonome quand une recherche est nécessaire :
const response = await openai.responses.create({
model: "gpt-4.5",
instructions: "Tu es un assistant de recherche. Cite toujours tes sources.",
input: "Quelles sont les principales fonctionnalités de la Responses API OpenAI sortie en 2026 ?",
tools: [{ type: "web_search_preview" }],
});
console.log(response.output_text);Les résultats de recherche incluent des annotations — des citations URL intégrées. Extrayez-les de la réponse :
for (const item of response.output) {
if (item.type === "message") {
for (const part of item.content) {
if (part.type === "output_text" && part.annotations) {
for (const ann of part.annotations) {
if (ann.type === "url_citation") {
console.log(`Source : ${ann.title} — ${ann.url}`);
}
}
}
}
}
}Vous obtenez des réponses fondées et citées sans aucune configuration d'API de recherche externe.
Étape 5 : Outil intégré — Interpréteur de code
L'interpréteur de code exécute Python dans un conteneur isolé. Utilisez-le pour les mathématiques, l'analyse de données, le traitement de fichiers et la génération de graphiques :
const response = await openai.responses.create({
model: "gpt-4.5",
instructions: "Tu es un analyste de données. Montre toujours ton travail avec du code.",
input: "Calcule l'intérêt composé sur 10 000 $ à 7% annuellement pendant 10 ans. Montre chaque année.",
tools: [
{
type: "code_interpreter",
container: { type: "auto" },
},
],
});
console.log(response.output_text);Le modèle écrit du Python, l'exécute dans le conteneur, et retourne les résultats dans la réponse. Les fichiers générés (graphiques, CSV) sont référencés par file_id dans la sortie et peuvent être téléchargés via l'API Files.
Note : l'interpréteur de code nécessite le runtime Node.js sur Vercel — pas le runtime Edge.
Étape 6 : Outils de fonctions personnalisées
Les fonctions personnalisées permettent au modèle d'appeler votre propre logique métier. Vous définissez le schéma ; le modèle décide quand l'invoquer.
Définir l'outil :
const getWeatherTool = {
type: "function" as const,
name: "get_weather",
description: "Obtenir les conditions météorologiques actuelles pour une ville.",
parameters: {
type: "object",
properties: {
city: { type: "string", description: "Nom de la ville, ex. Paris" },
unit: {
type: "string",
enum: ["celsius", "fahrenheit"],
description: "Unité de température",
},
},
required: ["city"],
additionalProperties: false,
},
strict: true,
};Définir strict: true active les sorties structurées pour les arguments de fonctions — le modèle retourne toujours du JSON valide correspondant à votre schéma.
Exécuter la boucle agentique :
import type OpenAI from "openai";
async function runAgent(userMessage: string): Promise<string> {
let input: string | OpenAI.Responses.InputItem[] = userMessage;
while (true) {
const response = await openai.responses.create({
model: "gpt-4.5",
input,
tools: [getWeatherTool],
});
const functionCall = response.output.find(
(item): item is OpenAI.Responses.FunctionCallItem =>
item.type === "function_call"
);
if (!functionCall) {
return response.output_text;
}
const args = JSON.parse(functionCall.arguments) as {
city: string;
unit?: string;
};
const weather = await fetchWeather(args.city, args.unit ?? "celsius");
// Ajouter le résultat de la fonction et reboucler
input = [
{ role: "user", content: userMessage },
...response.output,
{
type: "function_call_output" as const,
call_id: functionCall.call_id,
output: JSON.stringify(weather),
},
];
}
}
async function fetchWeather(city: string, unit: string) {
// Appelez une vraie API météo ici en production
return { city, temperature: 22, unit, condition: "Ensoleillé" };
}La boucle continue jusqu'à ce que le modèle retourne un message final sans demander d'autres appels d'outils.
Étape 7 : Streaming des réponses
Le streaming offre une bien meilleure expérience utilisateur pour les réponses longues. Utilisez openai.responses.stream() :
// app/api/chat/stream/route.ts
import { openai } from "@/lib/openai";
export const runtime = "nodejs";
export async function POST(req: Request) {
const { message } = await req.json();
const encoder = new TextEncoder();
const readable = new ReadableStream({
async start(controller) {
const stream = openai.responses.stream({
model: "gpt-4.5",
instructions: "Tu es un assistant utile.",
input: message,
tools: [{ type: "web_search_preview" }],
});
for await (const event of stream) {
if (
event.type === "response.output_text.delta" &&
event.delta
) {
controller.enqueue(encoder.encode(event.delta));
}
}
controller.close();
},
});
return new Response(readable, {
headers: {
"Content-Type": "text/plain; charset=utf-8",
"Cache-Control": "no-cache",
},
});
}Lire le stream côté client :
"use client";
import { useState } from "react";
export function ChatBox() {
const [output, setOutput] = useState("");
async function ask(message: string) {
setOutput("");
const res = await fetch("/api/chat/stream", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ message }),
});
const reader = res.body!.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
setOutput((prev) => prev + decoder.decode(value));
}
}
return (
<div className="p-4">
<button
className="px-4 py-2 bg-blue-600 text-white rounded"
onClick={() => ask("Quoi de neuf en IA cette semaine ?")}
>
Demander
</button>
<p className="mt-4 whitespace-pre-wrap">{output}</p>
</div>
);
}Étape 8 : Conversations multi-tours avec previous_response_id
Au lieu de renvoyer l'intégralité de l'historique de conversation à chaque tour, utilisez previous_response_id. OpenAI met en cache le contexte précédent côté serveur :
export async function POST(req: Request) {
const { message, previousResponseId } = await req.json();
const response = await openai.responses.create({
model: "gpt-4.5",
input: message,
previous_response_id: previousResponseId ?? undefined,
tools: [{ type: "web_search_preview" }],
});
return NextResponse.json({
text: response.output_text,
responseId: response.id,
});
}Votre client stocke responseId de chaque réponse et le passe en tant que previousResponseId à la requête suivante. C'est plus efficace que les tableaux d'historique manuels — et vous payez les tokens mis en cache à une fraction du prix des tokens d'entrée.
Étape 9 : Route Next.js 15 complète pour la production
En assemblant tous les patterns ci-dessus dans une route API complète :
// app/api/agent/route.ts
import { openai } from "@/lib/openai";
import { NextResponse } from "next/server";
import type OpenAI from "openai";
export const runtime = "nodejs";
const searchTool: OpenAI.Responses.Tool = { type: "web_search_preview" };
const codeTool: OpenAI.Responses.Tool = {
type: "code_interpreter",
container: { type: "auto" },
};
export async function POST(req: Request) {
const {
message,
previousResponseId,
enableSearch = true,
enableCode = false,
} = await req.json();
const tools: OpenAI.Responses.Tool[] = [
...(enableSearch ? [searchTool] : []),
...(enableCode ? [codeTool] : []),
];
try {
const response = await openai.responses.create({
model: "gpt-4.5",
instructions:
"Tu es un assistant de recherche expert pour les développeurs de la région MENA. " +
"Cite les sources quand tu utilises la recherche web. Montre du code quand on te demande d'analyser des données.",
input: message,
tools: tools.length > 0 ? tools : undefined,
previous_response_id: previousResponseId ?? undefined,
});
return NextResponse.json({
text: response.output_text,
responseId: response.id,
usage: {
inputTokens: response.usage?.input_tokens,
outputTokens: response.usage?.output_tokens,
cachedTokens: response.usage?.input_tokens_details?.cached_tokens,
},
});
} catch (error) {
if (error instanceof OpenAI.APIError) {
return NextResponse.json(
{ error: error.message },
{ status: error.status ?? 500 }
);
}
throw error;
}
}Étape 10 : Migration depuis l'Assistants API
Voici le mapping complet des concepts pour guider votre migration :
| Assistants API | Équivalent Responses API |
|---|---|
assistants.create() | Paramètre instructions dans responses.create() |
threads.create() | previous_response_id (léger) ou Conversations API (persistant) |
threads.messages.create() | Paramètre input |
threads.runs.create() | responses.create() |
| Boucle de polling des runs | Non nécessaire — synchrone par défaut |
threads.messages.list() | Tableau response.output |
| Outil file search | { type: "file_search", vector_store_ids: [...] } |
| Outil code interpreter | { type: "code_interpreter", container: { type: "auto" } } |
| Outils function | { type: "function", name, parameters } (même format de schéma) |
Avant (Assistants API — 6 appels API + polling) :
const assistant = await openai.beta.assistants.create({
name: "Research Bot",
instructions: "Tu aides à la recherche.",
tools: [{ type: "web_search" }],
model: "gpt-4-turbo",
});
const thread = await openai.beta.threads.create();
await openai.beta.threads.messages.create(thread.id, {
role: "user",
content: userMessage,
});
let run = await openai.beta.threads.runs.create(thread.id, {
assistant_id: assistant.id,
});
while (run.status !== "completed") {
await new Promise((r) => setTimeout(r, 1000));
run = await openai.beta.threads.runs.retrieve(thread.id, run.id);
}
const messages = await openai.beta.threads.messages.list(thread.id);
const text = messages.data[0].content[0].text.value;Après (Responses API — 1 seul appel API) :
const response = await openai.responses.create({
model: "gpt-4.5",
instructions: "Tu aides à la recherche.",
input: userMessage,
tools: [{ type: "web_search_preview" }],
});
const text = response.output_text;La différence est frappante — ce qui nécessitait 6 appels API et une boucle de polling est maintenant un seul await.
Dépannage
Erreur ERR_STREAM_WRITE_AFTER_END sur Edge runtime : l'interpréteur de code nécessite un environnement Node.js complet. Ajoutez export const runtime = "nodejs" à toute route qui l'utilise.
Boucles infinies d'appels d'outils : ajoutez un garde maxTurns à votre boucle while et retournez une erreur si dépassé. Une limite raisonnable est 10 tours.
Timeout de streaming sur Vercel : augmentez maxDuration de la fonction dans next.config.ts pour les routes utilisant la recherche approfondie ou l'interpréteur de code — elles peuvent fonctionner jusqu'à 30 secondes.
Erreurs de limite de débit (429) : le maxRetries: 3 dans le client OpenAI gère automatiquement les limites transitoires. Pour un volume élevé soutenu, demandez une augmentation de limite de débit depuis le tableau de bord de la plateforme OpenAI.
output_text est vide : cela se produit quand la sortie finale du modèle est un appel de fonction plutôt qu'un message. Vérifiez response.output pour les items de type function_call et complétez la boucle agentique.
Prochaines étapes
- Recherche de fichiers : téléchargez des PDFs dans un vector store avec
openai.vectorStores.create(), puis ajoutez{ type: "file_search", vector_store_ids: ["vs_..."] }pour activer la génération augmentée par récupération. - Recherche approfondie : utilisez
{ type: "deep_research" }pour des rapports de recherche multi-étapes. Ce sont des opérations asynchrones qui peuvent durer 5 à 30 minutes. - MCP distant : connectez-vous à n'importe quel serveur Model Context Protocol avec
{ type: "mcp", server_url: "...", headers: { Authorization: "Bearer ..." } }. - Computer use : construisez des agents d'automatisation de navigateur avec
{ type: "computer_use_preview" }pour les tâches nécessitant la navigation dans de vraies interfaces. - Ajoutez de l'observabilité : intégrez Langfuse ou LangSmith pour tracer chaque appel d'outil, mesurer la latence, et effectuer des évaluations sur les sorties de votre agent.
Conclusion
La Responses API d'OpenAI transforme un flux de travail Assistants en six appels et boucles de polling en un seul appel de fonction expressif. Avec la recherche web, l'interpréteur de code, les outils de fonctions, le streaming, et l'enchaînement de conversations via previous_response_id — tout disponible immédiatement — vous avez tout ce dont vous avez besoin pour construire des agents IA de niveau production en TypeScript. La fermeture le 26 août 2026 de l'Assistants API rend la migration urgente — et la bonne nouvelle est que la nouvelle API est genuinement meilleure dans toutes les dimensions : plus simple, plus rapide, moins chère, et plus capable.