Découvrez le Vercel AI SDK pour ajouter de l'IA à vos apps Next.js : streaming LLM, génération de texte, tool use et intégration avec OpenAI ou Anthropic.
Vercel AI SDK — architecture et avantages
Le Vercel AI SDK est une bibliothèque TypeScript open-source qui unifie l'accès à tous les grands LLMs (OpenAI, Anthropic, Google, Mistral, Ollama) derrière une API commune. Changer de modèle = changer une ligne.
| Fonction | Usage | Quand utiliser |
|---|---|---|
generateText() | Génération complète | Scripts, batch, API backend |
streamText() | Streaming token par token | Chatbots, UX temps réel |
generateObject() | JSON structuré validé par Zod | Extraction, classification |
streamObject() | Streaming d'objets JSON | Forms progressives, dashboards |
embed() | Vecteurs d'embeddings | RAG, recherche sémantique |
ai (core, framework-agnostic) + providers (@ai-sdk/openai, @ai-sdk/anthropic, etc.) + intégrations UI (ai/react, ai/vue, ai/svelte).
Installation et providers
# Core + providers
npm install ai @ai-sdk/openai @ai-sdk/anthropic @ai-sdk/mistral @ai-sdk/google
# Zod pour la validation des sorties structurées
npm install zod
# Variables d'environnement (.env.local)
OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...
MISTRAL_API_KEY=...
GOOGLE_GENERATIVE_AI_API_KEY=AIza...
generateText() et streamText()
import { generateText, streamText } from 'ai';
import { openai } from '@ai-sdk/openai';
import { anthropic } from '@ai-sdk/anthropic';
// generateText() — réponse complète d'un coup
const { text, usage, finishReason } = await generateText({
model: openai('gpt-4o'),
system: 'Tu es un expert Angular. Réponds en français avec des exemples TypeScript.',
messages: [
{ role: 'user', content: 'Comment utiliser les Signals dans Angular 18 ?' },
{ role: 'assistant', content: 'Les Signals sont...' },
{ role: 'user', content: 'Montre-moi un exemple avec computed()' },
],
maxTokens: 2048,
temperature: 0.7,
});
console.log(text);
console.log(`Tokens: ${usage.promptTokens} prompt + ${usage.completionTokens} completion`);
// streamText() — streaming token par token
const result = streamText({
model: anthropic('claude-sonnet-4-6'),
system: 'Tu es un assistant Angular expert.',
prompt: 'Explique les microfrontends en 5 points.',
});
// Itérer sur le stream
for await (const textPart of result.textStream) {
process.stdout.write(textPart); // Affichage progressif
}
// Ou récupérer le message final avec usage
const finalText = await result.text;
const usage = await result.usage;
generateObject() — sortie structurée Zod
generateObject() garantit que la réponse du LLM respecte un schéma TypeScript défini avec Zod — idéal pour l'extraction d'informations, la classification, et la génération de formulaires.
import { generateObject } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';
// Schéma de validation Zod
const ArticleSchema = z.object({
title: z.string().min(10).max(60).describe('Titre SEO optimisé'),
summary: z.string().max(160).describe('Meta description SEO'),
tags: z.array(z.string()).min(3).max(7),
difficulty: z.enum(['débutant', 'intermédiaire', 'avancé']),
sections: z.array(z.object({
heading: z.string(),
keyPoints: z.array(z.string()).min(2).max(5),
})).min(3),
estimatedReadTime: z.number().int().positive().describe('En minutes'),
});
// L'IA retourne un objet TypeScript typé et validé
const { object: article } = await generateObject({
model: openai('gpt-4o'),
schema: ArticleSchema,
prompt: 'Génère un plan pour un article sur Angular Signals en 2026.',
});
// TypeScript sait que article.title est string, article.difficulty est un enum, etc.
console.log(article.title);
console.log(article.sections[0].heading);
Streaming SSE avec Next.js
// app/api/chat/route.ts (Next.js App Router)
import { streamText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: anthropic('claude-sonnet-4-6'),
system: 'Tu es un expert web Angular. Réponds en français.',
messages,
maxTokens: 2048,
onFinish({ text, usage }) {
console.log(`Tokens utilisés: ${usage.totalTokens}`);
// Sauvegarder l'historique en BD si besoin
},
});
return result.toDataStreamResponse();
}
// components/Chat.tsx — hook useChat() côté client
'use client';
import { useChat } from 'ai/react';
export function ChatComponent() {
const { messages, input, handleInputChange, handleSubmit, isLoading, error } = useChat({
api: '/api/chat',
onError: (err) => console.error('Chat error:', err),
});
return (
<div className="chat-container">
<div className="messages">
{messages.map(m => (
<div key={m.id} className={`message ${m.role}`}>
{m.content}
</div>
))}
{isLoading && <div className="message assistant">...</div>}
</div>
{error && <p className="error">{error.message}</p>}
<form onSubmit={handleSubmit}>
<input value={input} onChange={handleInputChange} placeholder="Votre question..." />
<button type="submit" disabled={isLoading}>Envoyer</button>
</form>
</div>
);
}
Tools et AI Agents
import { generateText, tool, streamText } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';
// Définir les outils disponibles
const agentTools = {
searchDocs: tool({
description: 'Recherche dans la documentation Angular',
parameters: z.object({
query: z.string(),
version: z.string().optional().default('18'),
}),
execute: async ({ query, version }) => {
// Implémentation réelle : recherche dans un vector store
const results = await vectorStore.search(query);
return results.map(r => r.content).join('\n');
},
}),
runTypeScriptSnippet: tool({
description: 'Exécute un snippet TypeScript/Angular et retourne le résultat',
parameters: z.object({
code: z.string().describe('Code TypeScript à exécuter'),
}),
execute: async ({ code }) => {
// Sandbox d'exécution sécurisé
const result = await sandboxExec(code);
return { output: result.stdout, errors: result.stderr };
},
}),
};
// Agent autonome : le modèle choisit ses outils en boucle
const { text, steps } = await generateText({
model: openai('gpt-4o'),
tools: agentTools,
maxSteps: 5, // Limiter les itérations de l'agent
prompt: 'Comment configurer le routing lazy-loading dans Angular 18 ? Montre-moi avec un exemple fonctionnel.',
});
console.log('Réponse finale:', text);
console.log('Étapes:', steps.length); // Nombre d'appels d'outils
Vision — analyse d'images
import { generateText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
import * as fs from 'fs';
// Vision avec URL publique
const { text: urlAnalysis } = await generateText({
model: anthropic('claude-opus-4-6'),
messages: [{
role: 'user',
content: [
{ type: 'image', image: new URL('https://example.com/architecture.png') },
{ type: 'text', text: 'Analyse cette architecture. Quels patterns reconnais-tu ?' },
],
}],
});
// Vision avec fichier local (base64 automatique)
const imageBuffer = fs.readFileSync('./screenshot.png');
const { text: localAnalysis } = await generateText({
model: openai('gpt-4o'),
messages: [{
role: 'user',
content: [
{
type: 'image',
image: imageBuffer, // Buffer Node.js — converti automatiquement
mimeType: 'image/png',
},
{ type: 'text', text: 'Décris les problèmes UX visibles dans cette interface.' },
],
}],
});
Intégration Angular (sans Next.js)
Pour une application Angular pure, on appelle le SDK côté serveur (Express/Node) et on consomme le SSE côté client via Angular.
// === BACKEND Express ===
import { streamText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
app.post('/api/ai/stream', async (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
const result = streamText({
model: anthropic('claude-sonnet-4-6'),
messages: req.body.messages,
});
// Pipe le stream vers la réponse Express
const reader = result.textStream.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) { res.write('data: [DONE]\n\n'); break; }
res.write(`data: ${JSON.stringify({ text: value })}\n\n`);
}
res.end();
});
// === FRONTEND Angular : AI Service ===
import { Injectable, inject, signal } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable({ providedIn: 'root' })
export class AiStreamService {
readonly response = signal('');
readonly isStreaming = signal(false);
async streamChat(messages: ChatMessage[]) {
this.response.set('');
this.isStreaming.set(true);
const res = await fetch('/api/ai/stream', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ messages }),
});
const reader = res.body!.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const lines = decoder.decode(value).split('\n');
for (const line of lines) {
if (line.startsWith('data: ') && line !== 'data: [DONE]') {
const data = JSON.parse(line.slice(6));
this.response.update(r => r + data.text); // Signal mis à jour
}
}
}
this.isStreaming.set(false);
}
}
Tous les providers supportés
| Provider | Package | Modèles populaires |
|---|---|---|
| OpenAI | @ai-sdk/openai | gpt-4o, gpt-4o-mini, o1 |
| Anthropic | @ai-sdk/anthropic | claude-opus-4-6, claude-sonnet-4-6, haiku-4-5 |
@ai-sdk/google | gemini-2.0-flash, gemini-1.5-pro | |
| Mistral | @ai-sdk/mistral | mistral-large, mistral-small |
| Ollama (local) | ollama-ai-provider | llama3, qwen2.5, phi3 |
| Groq | @ai-sdk/groq | llama-3.3-70b, mixtral-8x7b |
| Cohere | @ai-sdk/cohere | command-r-plus |
Conclusion
Le Vercel AI SDK simplifie considérablement l'intégration LLM dans vos applications web. Points clés :
generateText()pour les scripts et batch —streamText()pour l'UX temps réelgenerateObject()+ Zod = extraction d'informations typée et validée- Tools +
maxSteps= agents autonomes en quelques lignes - Changer de provider = changer une ligne (OpenAI → Anthropic → Mistral)
- Angular : appeler le SDK côté Express/Node, consommer le SSE côté client avec
fetch()+ReadableStream