Intelligence Artificielle angularforall.com

- Vercel AI SDK : intégrer l'IA dans une app web

Ia Vercel Ai-Sdk Llm Streaming Vercel-Ai-Sdk Next-Js React Tool-Use Openai Anthropic Ia-Generative Typescript Edge-Runtime
Vercel AI SDK : intégrer l'IA dans une app web

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èteScripts, batch, API backend
streamText()Streaming token par tokenChatbots, UX temps réel
generateObject()JSON structuré validé par ZodExtraction, classification
streamObject()Streaming d'objets JSONForms progressives, dashboards
embed()Vecteurs d'embeddingsRAG, recherche sémantique
Architecture : Le SDK est divisé en 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/openaigpt-4o, gpt-4o-mini, o1
Anthropic@ai-sdk/anthropicclaude-opus-4-6, claude-sonnet-4-6, haiku-4-5
Google@ai-sdk/googlegemini-2.0-flash, gemini-1.5-pro
Mistral@ai-sdk/mistralmistral-large, mistral-small
Ollama (local)ollama-ai-providerllama3, qwen2.5, phi3
Groq@ai-sdk/groqllama-3.3-70b, mixtral-8x7b
Cohere@ai-sdk/coherecommand-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éel
  • generateObject() + 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

Partager