Génération d'objets structurés (JSON) avec Vercel AI SDK et Zod - Partie 5 (Finale)
Dans les parties précédentes, nous avons vu comment le SDK IA de Vercel peut générer et diffuser du texte. Mais que faire si vous avez besoin que l'IA produise des données dans un format structuré spécifique, comme le JSON ?

Technologies Used
voir Part 4 ici
Ceci est crucial pour des tâches telles que :
- Extraction d'informations à partir d'un texte vers un schéma prédéfini.
- Génération des fichiers de configuration.
- Peupler des bases de données avec du contenu généré par IA.
- Alimenter des interfaces utilisateur qui attendent des structures de données spécifiques.
Demander manuellement à un LLM de produire du JSON valide puis l'analyser peut être source d'erreurs. Le SDK IA de Vercel offre une solution puissante : generateObject
. Cette fonction, souvent utilisée avec zod
pour la définition et la validation de schéma, garantit que la sortie du LLM respecte la structure souhaitée.
Prérequis
- Configuration d'un projet Next.js.
- Le SDK AI de Vercel et une bibliothèque fournisseur de LLM installés.
- Clé API OpenAI (ou équivalent pour un modèle qui prend bien en charge la sortie structurée/l'appel de fonction).
zod
installé :npm install zod
oupnpm add zod
.
Concept de base : generateObject
La fonction generateObject fonctionne en exploitant les capacités d'appel d'outils/fonctions des LLMs avancés.
- Vous définissez un schéma
zod
pour l'objet que vous souhaitez. - Vous fournissez une instruction à l'IA sur les données à générer.
generateObject
indique essentiellement au LLM de « appeler une fonction » dont les paramètres correspondent à votre schéma Zod.- Le LLM tente de remplir ces paramètres en fonction de votre invite.
- Le SDK IA de Vercel valide la sortie par rapport au schéma et renvoie l'objet analysé et typé.
Étape 1 : Définition d'un schéma Zod
Supposons que nous voulions extraire des informations de contact à partir d'un texte. Nous pouvons définir un schéma Zod pour cela :
// lib/schemas.ts (or directly in your API route)
import { z } from 'zod';
export const contactSchema = z.object({
name: z.string().optional().describe("Le nom complet de la personne."),
email: z.string().email().optional().describe("L'adresse e-mail."),
phone: z.string().optional().describe("Le numéro de téléphone."),
company: z.string().optional().describe("Le nom de l'entreprise."),
});
export type Contact = z.infer<typeof contactSchema>;
describe()
: Ajouter des descriptions à vos champs de schéma Zod peut considérablement aider le LLM à comprendre ce qu'il faut extraire pour chaque champ.
Étape 2 : Création de la route API pour la génération d'objets
Créer app/api/generate-object/route.ts
:
// app/api/generate-object/route.ts
import { openai } from '@ai-sdk/openai';
import { generateObject, GenerateObjectResult } from 'ai';
import { z } from 'zod';
export const runtime = 'edge'; // Peut aussi être 'nodejs' si nécessaire pour le modèle/fournisseur
export const maxDuration = 30;
// Définir le schéma directement ici ou l'importer
const contactSchema = z.object({
name: z.string().optional().describe("Le nom complet de la personne."),
email: z.string().email().optional().describe("L'adresse e-mail."),
phone: z.string().optional().describe("Le numéro de téléphone."),
company: z.string().optional().describe("Le nom de l'entreprise, si mentionné."),
});
export async function POST(req: Request) {
const { textInput } = await req.json();
if (!textInput) {
return new Response('Le texte saisi est requis', { status: 400 });
}
try {
const { object, usage, finishReason, ...rest } = await generateObject({
model: openai('gpt-4o'), // Un modèle qui prend en charge l'appel d'outils/fonctions est requis
schema: contactSchema,
prompt: `Extraire les informations de contact du texte suivant : "${textInput}"`,
// mode: 'tool', // Le mode par défaut est 'tool', peut aussi être 'json' pour les modèles prenant en charge le mode JSON directement
});
// `object` est maintenant un objet typé correspondant à votre schéma Zod
console.log('Objet généré :', object);
console.log('Utilisation :', usage);
console.log('Raison de la fin :', finishReason);
// Vous pouvez également accéder à d'autres parties du résultat si nécessaire, par exemple, rawResponse
// console.log('Résultat complet :', { object, usage, finishReason, ...rest });
return Response.json({ generatedContact: object, usage, finishReason });
} catch (error) {
console.error("Erreur lors de la génération de l'objet :", error);
let errorMessage = "Échec de la génération de l'objet.";
if (error instanceof Error) {
errorMessage = error.message;
}
// Vérifier s'il s'agit d'une APIError du SDK pour plus de détails
// if (error instanceof APIError) { ... }
return new Response(JSON.stringify({ error: errorMessage }), { status: 500, headers: { 'Content-Type': 'application/json' } });
}
}
Explication :
generateObject
: La fonction principale.modèle
: Une instance de LLM qui prend en charge l'appel d'outils (par exemple,gpt-3.5-turbo
,gpt-4o
,modèles Anthropic Claude
).schéma
: Votre schéma Zod.invite
: L'instruction donnée au modèle de langage. Elle doit guider l'IA pour extraire ou générer des données conformes au schéma.mode
: Peut être 'outil
' (utilise l'appel de fonction) ou 'json
' (utilise le mode JSON natif du modèle si disponible, comme avec les modèles OpenAI plus récents). 'outil
' est généralement plus robuste sur différents modèles.
- La fonction renvoie un objet contenant l'objet analysé
objet
, les statistiques d'utilisation
, laraison de la fin
, et plus encore. - Gestion des erreurs : Il est important d'encapsuler
generateObject
dans un bloc try-catch car des échecs de validation de schéma ou des erreurs LLM peuvent survenir.
Étape 3 : Construction de l'interface utilisateur pour la génération d'objets (côté client)
Créons une page simple app/object-gen/page.tsx
:
// app/object-gen/page.tsx
'use client';
import { FormEvent, useState } from 'react';
import { z } from 'zod'; // Importez Zod pour l'inférence de type si nécessaire
// Redéfinissez ou importez votre schéma pour la sécurité de type sur le client
const contactSchema = z.object({
name: z.string().optional(),
email: z.string().email().optional(),
phone: z.string().optional(),
company: z.string().optional(),
});
type Contact = z.infer<typeof contactSchema>;
interface GenerationResponse {
generatedContact?: Contact;
usage?: { promptTokens: number; completionTokens: number; totalTokens: number };
finishReason?: string;
error?: string;
}
export default function ObjectGenPage() {
const [textInput, setTextInput] = useState("John Doe travaille chez Acme Corp. Contactez-le à john.doe@example.com ou au (555) 123-4567.");
const [generatedObject, setGeneratedObject] = useState<Contact | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [apiResponse, setApiResponse] = useState<GenerationResponse | null>(null);
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
setIsLoading(true);
setError(null);
setGeneratedObject(null);
setApiResponse(null);
try {
const response = await fetch('/api/generate-object', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ textInput }),
});
const data: GenerationResponse = await response.json();
if (!response.ok) {
throw new Error(data.error || `L'API a répondu avec le statut ${response.status}`);
}
setGeneratedObject(data.generatedContact || null);
setApiResponse(data);
} catch (err) {
console.error(err);
setError(err instanceof Error ? err.message : 'Une erreur inconnue s'est produite.');
} finally {
setIsLoading(false);
}
};
return (
<div className="flex flex-col w-full max-w-xl py-12 mx-auto">
<h1 className="text-2xl font-bold mb-6">Génération d'objet structuré</h1>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="text-input" className="block text-sm font-medium text-gray-700">
Entrée de texte :
</label>
<textarea
id="text-input"
className="w-full p-2 border border-gray-300 rounded shadow-sm text-black"
rows={5}
value={textInput}
onChange={(e) => setTextInput(e.target.value)}
required
/>
</div>
<button
type="submit"
disabled={isLoading}
className="px-4 py-2 bg-purple-500 text-white rounded hover:bg-purple-600 disabled:bg-gray-300"
>
{isLoading ? 'Génération...' : 'Générer l'objet'}
</button>
</form>
{error && (
<div className="mt-6 p-4 bg-red-100 text-red-700 border border-red-300 rounded">
<strong>Erreur :</strong> {error}
</div>
)}
{generatedObject && (
<div className="mt-6 p-4 border border-gray-200 rounded bg-gray-50">
<h3 className="text-lg font-semibold mb-2">Objet généré :</h3>
<pre className="whitespace-pre-wrap text-sm text-gray-800">
{JSON.stringify(generatedObject, null, 2)}
</pre>
</div>
)}
{apiResponse && apiResponse.usage && (
<div className="mt-2 p-2 border-gray-200 text-xs text-gray-600">
<p>Utilisation : {apiResponse.usage.totalTokens} jetons ({apiResponse.usage.promptTokens} d'invite, {apiResponse.usage.completionTokens} de complétion). Fin : {apiResponse.finishReason}</p>
</div>
)}
</div>
);
}
Explication :
- Ce code côté client effectue une requête
fetch
standard à notre point de terminaison d'API. - Il affiche l'objet généré (si réussi) ou un message d'erreur.
- Il est de bonne pratique de montrer également les statistiques d'utilisation si elles sont pertinentes pour votre application.
Étape 4 : Test de la génération d'objet
Exécutez pnpm dev
et rendez-vous sur votre page de génération d'objets.
- Utilisez le texte par défaut : « John Doe travaille chez Acme Corp. Contactez-le à john.doe@example.com ou au (555) 123-4567. »
- Cliquez sur « Générer l'objet ». Vous devriez voir un objet JSON avec les champs
nom
,courriel
,téléphone
, etentreprise
remplis. - Essayez d'autres entrées :
- "Rendez-vous avec Jane Smith (jane@coolstartup.io) mardi prochain." (Il faut extraire le nom et l'e-mail).
- "Générez un cahier des charges pour une 'bouteille d'eau intelligente' avec des fonctionnalités : affichage de la température, rappels d'hydratation et auto-nettoyage. Prix cible 50 $." (Il vous faudrait un schéma Zod différent pour cela, par exemple,
productSpecSchema
).
Points clés
generateObject
combiné avec des schémas Zod est un moyen robuste d'obtenir une sortie JSON structurée à partir des LLM.- Cela réduit considérablement les risques de JSON mal formé ou de données qui ne correspondent pas à la structure attendue.
- Les descriptions claires des champs Zod (
.describe()
) aident le LLM à cartographier correctement les informations. - Gérez toujours les erreurs potentielles, car la génération de LLM n'est pas toujours parfaite, et la validation de schéma pourrait échouer.
- Cette fonctionnalité est incroyablement puissante pour intégrer l'IA dans les flux de travail et les interfaces utilisateur basés sur les données.
Conclusion de la série
Et c'est la fin de notre série d'introduction au SDK Vercel AI ! Nous avons couvert beaucoup de sujets :
- Construire un chatbot de base avec
useChat
etstreamText
. - L'amélioration avec l'utilisation d'outils (appel de fonction).
- Génération de complétions de texte avec
useCompletion
. - Plonger dans la génération de texte avancée et l'ingénierie des invites.
- Produire des objets structurés (JSON) avec
generateObject
et Zod.
Le SDK AI de Vercel offre une boîte à outils complète et conviviale pour les développeurs, permettant d'intégrer un large éventail de capacités d'intelligence artificielle dans vos applications JavaScript. Son accent sur le streaming, le support de première classe pour les frameworks modernes comme Next.js, et ses abstractions réfléchies en font un excellent choix pour construire la prochaine génération d'expériences alimentées par l'IA.
Le monde de l'IA évolue rapidement, tout comme le SDK IA de Vercel. Nous vous encourageons à explorer la documentation officielle, à expérimenter avec différents fournisseurs de LLM et à continuer d'apprendre. Bonne construction !