Appel de fonction OpenAI dans les routes API de Next.js
Appel de fonctions OpenAI dans les routes API de Next.js : connecter les outils aux gestionnaires serveur, rédiger des descriptions précises des outils, et exiger une autorisation pour les appels destructeurs.
Sources: Function Calling with Next.js and OpenAI: Letting AI Actually Do Things
Les complétions de chat simples ne peuvent générer que du texte. L’appel de fonctions — également appelé utilisation d’outils — permet à un modèle de demander des actions réelles dans une application, telles que rechercher une ligne, envoyer un e-mail ou mettre à jour un enregistrement, en fonction de la demande de l’utilisateur. La démarche présentée ci-dessous intègre ce modèle dans une route API Next.js avec OpenAI.
Le schéma de base
Déscrivez les outils au modèle. Le modèle choisit s’il doit en appeler un et quels arguments envoyer. Le code de l’application exécute le gestionnaire et renvoie un résultat que le modèle peut transformer en réponse.
// app/api/chat/route.js
import OpenAI from 'openai';
const openai = new OpenAI();
const tools = [
{
type: 'function',
function: {
name: 'get_order_status',
description: "Get the status of a customer's order",
parameters: {
type: 'object',
properties: {
orderId: { type: 'string', description: 'The order ID' },
},
required: ['orderId'],
},
},
},
];export async function POST(request) {
const { messages } = await request.json(); const response = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages,
tools,
}); const toolCall = response.choices[0].message.tool_calls?.[0]; if (toolCall) {
const args = JSON.parse(toolCall.function.arguments);
const result = await getOrderStatus(args.orderId); // your real function // Send the result back so the model can respond in natural language
const followUp = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [
...messages,
response.choices[0].message,
{ role: 'tool', tool_call_id: toolCall.id, content: JSON.stringify(result) },
],
}); return Response.json({ reply: followUp.choices[0].message.content });
} return Response.json({ reply: response.choices[0].message.content });
}
La fonction réelle derrière l’outil
async function getOrderStatus(orderId) {
const order = await db.order.findUnique({ where: { id: orderId } });
return order ? { status: order.status, eta: order.estimatedDelivery } : { error: 'Order not found' };
}
Ce gestionnaire est un code ordinaire du côté serveur. L’utilisation d’outils ne réalise pas de magie par elle-même ; elle offre simplement au modèle un moyen structuré de demander au serveur d’exécuter une fonction.
Gestion de plusieurs outils
const tools = [
{ type: 'function', function: { name: 'get_order_status', /* ... */ } },
{ type: 'function', function: { name: 'cancel_order', /* ... */ } },
{ type: 'function', function: { name: 'get_account_balance', /* ... */ } },
];
const toolHandlers = {
get_order_status: (args) => getOrderStatus(args.orderId),
cancel_order: (args) => cancelOrder(args.orderId),
get_account_balance: (args) => getAccountBalance(args.userId),
};// In your handler:
const handler = toolHandlers[toolCall.function.name];
const result = await handler(JSON.parse(toolCall.function.arguments));
Une carte de gestionnaires permet une adaptation plus propre qu’une chaîne croissante de if/else à mesure que la liste d’outils s’élargit.
Critique : ne permettez jamais au modèle d’exécuter des actions destructrices sans confirmation
async function cancelOrder(orderId, userId) {
// Always verify the action against the actual authenticated user server-side —
// never trust that the model's tool call alone represents authorized intent
const order = await db.order.findUnique({ where: { id: orderId } });
if (order.userId !== userId) throw new Error('Unauthorized');
await db.order.update({ where: { id: orderId }, data: { status: 'cancelled' } });
}
Le fait qu’un modèle décide d’appeler cancel_order ne prouve pas que l’utilisateur authentifié puisse annuler cette commande. Le gestionnaire doit vérifier à nouveau l’autorisation à chaque appel, de la même manière qu’un point d’entrée API normal le ferait.
Erreur courante : descriptions de fonctions vagues
// Too vague — model may call this incorrectly or miss when it should
{ name: 'update', description: 'Updates something' }
// Specific enough for the model to use reliably
{ name: 'update_shipping_address', description: "Updates the shipping address for the customer's most recent unshipped order" }
La qualité du choix des outils dépend de la précision des descriptions. Des noms ambigus et des descriptions floues entraînent des appels manqués ou erronés ; des noms spécifiques et des descriptions bien définies assurent un routage fiable.
Tenez les arguments des outils pour des entrées non fiables. Validez les identifiants, rejetez les champs inattendus, et conservez les effets secondaires au sein de transactions lorsque plusieurs tables sont modifiées. En enregistrant le nom de l’outil, la structure des arguments (pas les secrets) et le résultat de l’autorisation, il devient plus facile de reconstituer les incidents en production lorsque un modèle choisit à plusieurs reprises le mauvais outil.
En résumé
L’appel de fonctions transforme une interface de chat en un système capable d’agir, mais l’intention du modèle n’est que la moitié du système. Les gestionnaires serveur nécessitent toujours une autorisation au niveau de l’endpoint, et des descriptions précises des outils permettent de s’assurer que ces actions ciblent bien la tâche appropriée plutôt que de provoquer des erreurs. Instaurez des délais d’attente et des tentatives de réessai pour les itérations du modèle, afin qu’un boucle d’outil bloquée ne puisse épuiser le pool des gestionnaires de route.