Prérequis
Avant de commencer ce tutoriel, assurez-vous d'avoir :
- Node.js 20 ou supérieur installé
- Des connaissances de base en React et Next.js App Router
- Un compte Together AI avec une clé API
- Une familiarité avec TypeScript
- Un éditeur de code (VS Code recommandé)
Ce que vous allez construire
Together AI vient de clôturer une levée de fonds Series C de 800 millions de dollars menée par Aramco Ventures, consolidant sa position de plateforme de référence pour l'inférence de modèles IA open-source. Avec un accès à plus de 200 modèles — dont Llama 3.3, Mistral, Qwen 2.5, DeepSeek et Falcon — Together AI offre aujourd'hui l'une des APIs multi-modèles les plus flexibles du marché.
Dans ce tutoriel, vous construirez une application de chat IA en streaming avec :
- Le streaming de tokens en temps réel via Server-Sent Events (SSE)
- La commutation entre des LLM open-source populaires
- Une interface de chat épurée et responsive avec Next.js 15 App Router
- Une intégration type-safe avec le SDK TypeScript officiel de Together AI
- Une gestion des erreurs et des états de chargement prête pour la production
Étape 1 : Créer un nouveau projet Next.js 15
Commencez par initialiser un projet Next.js 15 :
npx create-next-app@latest together-chat --typescript --tailwind --app --src-dir
cd together-chatLorsque vous y êtes invité, choisissez :
- TypeScript : Oui
- ESLint : Oui
- Tailwind CSS : Oui
- App Router : Oui
Étape 2 : Installer le SDK Together AI
Installez le SDK TypeScript officiel de Together AI :
npm install together-aiLe package together-ai est le SDK officiel maintenu par Together AI. Il encapsule l'API REST avec des types TypeScript complets, le support du streaming et des tentatives automatiques.
Étape 3 : Configurer les variables d'environnement
Créez un fichier .env.local à la racine de votre projet :
TOGETHER_API_KEY=your_api_key_hereObtenez votre clé API depuis le tableau de bord Together AI. La plateforme offre 25 dollars de crédits gratuits pour les nouveaux comptes — largement suffisant pour compléter ce tutoriel.
Ajoutez .env.local à votre .gitignore pour éviter de commettre vos secrets accidentellement :
echo ".env.local" >> .gitignoreÉtape 4 : Créer le client Together AI
Créez un module client partagé dans lib/together.ts :
import Together from 'together-ai';
export const together = new Together({
apiKey: process.env.TOGETHER_API_KEY,
});
export const AVAILABLE_MODELS = [
{
id: 'meta-llama/Llama-3.3-70B-Instruct-Turbo',
name: 'Llama 3.3 70B',
provider: 'Meta',
},
{
id: 'mistralai/Mixtral-8x7B-Instruct-v0.1',
name: 'Mixtral 8x7B',
provider: 'Mistral AI',
},
{
id: 'Qwen/Qwen2.5-72B-Instruct-Turbo',
name: 'Qwen 2.5 72B',
provider: 'Alibaba',
},
{
id: 'deepseek-ai/DeepSeek-V3',
name: 'DeepSeek V3',
provider: 'DeepSeek',
},
];Ce module exporte à la fois le client Together configuré et une liste sélectionnée de modèles open-source populaires disponibles sur la plateforme.
Étape 5 : Construire la route API en streaming
Créez la route API de chat dans app/api/chat/route.ts :
import { together } from '@/lib/together';
import { NextRequest } from 'next/server';
export const runtime = 'nodejs';
export const maxDuration = 60;
export async function POST(req: NextRequest) {
const { messages, model } = await req.json();
if (!messages || !Array.isArray(messages)) {
return new Response(JSON.stringify({ error: 'Invalid messages format' }), {
status: 400,
headers: { 'Content-Type': 'application/json' },
});
}
const selectedModel = model || 'meta-llama/Llama-3.3-70B-Instruct-Turbo';
const systemMessage = {
role: 'system' as const,
content: "Vous êtes un assistant IA utile. Soyez concis et précis dans vos réponses.",
};
const stream = await together.chat.completions.create({
messages: [systemMessage, ...messages],
model: selectedModel,
stream: true,
max_tokens: 2048,
temperature: 0.7,
});
const encoder = new TextEncoder();
const readable = new ReadableStream({
async start(controller) {
try {
for await (const chunk of stream) {
const content = chunk.choices[0]?.delta?.content || '';
if (content) {
controller.enqueue(encoder.encode(content));
}
}
} catch (err) {
controller.error(err);
} finally {
controller.close();
}
},
});
return new Response(readable, {
headers: {
'Content-Type': 'text/plain; charset=utf-8',
'Transfer-Encoding': 'chunked',
'X-Content-Type-Options': 'nosniff',
},
});
}Ce gestionnaire de route :
- Reçoit
messages(l'historique de conversation) etmodel(le LLM sélectionné) depuis le corps de la requête - Préfixe un message système pour guider le comportement de l'assistant
- Appelle
together.chat.completions.createavecstream: truepour activer le streaming de tokens - Itère sur le flux asynchrone et pousse chaque fragment de texte dans un
ReadableStream - Retourne le flux comme une réponse HTTP en texte brut
Le paramètre maxDuration = 60 garantit que la fonction serverless reste active assez longtemps pour les réponses longues.
Étape 6 : Créer les types de messages
Ajoutez un fichier de types partagé dans types/chat.ts :
export type Role = 'user' | 'assistant' | 'system';
export interface Message {
id: string;
role: Role;
content: string;
}Étape 7 : Construire le composant Chat
Créez le composant chat principal dans components/Chat.tsx :
'use client';
import { useState, useRef, useEffect } from 'react';
import { AVAILABLE_MODELS } from '@/lib/together';
import { Message } from '@/types/chat';
export default function Chat() {
const [messages, setMessages] = useState<Message[]>([]);
const [input, setInput] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [selectedModel, setSelectedModel] = useState(AVAILABLE_MODELS[0].id);
const bottomRef = useRef<HTMLDivElement>(null);
useEffect(() => {
bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]);
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
if (!input.trim() || isLoading) return;
const userMessage: Message = {
id: crypto.randomUUID(),
role: 'user',
content: input.trim(),
};
const updatedMessages = [...messages, userMessage];
setMessages(updatedMessages);
setInput('');
setIsLoading(true);
const assistantId = crypto.randomUUID();
setMessages(prev => [
...prev,
{ id: assistantId, role: 'assistant', content: '' },
]);
try {
const res = await fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
messages: updatedMessages.map(m => ({
role: m.role,
content: m.content,
})),
model: selectedModel,
}),
});
if (!res.ok) throw new Error('Failed to fetch response');
if (!res.body) throw new Error('No response body');
const reader = res.body.getReader();
const decoder = new TextDecoder();
while (true) {
const result = await reader.read();
if (result.done) break;
const text = decoder.decode(result.value, { stream: true });
setMessages(prev =>
prev.map(m =>
m.id === assistantId
? { ...m, content: m.content + text }
: m
)
);
}
} catch (err) {
setMessages(prev =>
prev.map(m =>
m.id === assistantId
? { ...m, content: "Erreur : impossible d'obtenir une réponse. Veuillez réessayer." }
: m
)
);
} finally {
setIsLoading(false);
}
}
const modelInfo = AVAILABLE_MODELS.find(m => m.id === selectedModel);
return (
<div className="flex flex-col h-screen max-w-3xl mx-auto px-4">
<div className="flex items-center justify-between py-4 border-b">
<h1 className="text-xl font-semibold">Together AI Chat</h1>
<select
value={selectedModel}
onChange={e => setSelectedModel(e.target.value)}
className="text-sm border rounded-lg px-3 py-1.5 bg-white dark:bg-zinc-900"
>
{AVAILABLE_MODELS.map(m => (
<option key={m.id} value={m.id}>
{m.name} ({m.provider})
</option>
))}
</select>
</div>
<div className="flex-1 overflow-y-auto py-4 space-y-4">
{messages.length === 0 && (
<div className="text-center text-zinc-400 mt-20">
<p className="text-lg font-medium">Démarrez une conversation</p>
<p className="text-sm mt-1">
Avec {modelInfo?.name} par {modelInfo?.provider}
</p>
</div>
)}
{messages.map(message => (
<div
key={message.id}
className={`flex ${message.role === 'user' ? 'justify-end' : 'justify-start'}`}
>
<div
className={`max-w-[80%] rounded-2xl px-4 py-2.5 text-sm ${
message.role === 'user'
? 'bg-blue-600 text-white'
: 'bg-zinc-100 dark:bg-zinc-800 text-zinc-900 dark:text-zinc-100'
}`}
>
{message.content || (
<span className="animate-pulse">Réflexion en cours...</span>
)}
</div>
</div>
))}
<div ref={bottomRef} />
</div>
<form onSubmit={handleSubmit} className="py-4 border-t">
<div className="flex gap-2">
<input
value={input}
onChange={e => setInput(e.target.value)}
placeholder="Tapez un message..."
className="flex-1 border rounded-xl px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-zinc-900"
disabled={isLoading}
/>
<button
type="submit"
disabled={isLoading || !input.trim()}
className="px-4 py-2.5 bg-blue-600 text-white rounded-xl text-sm font-medium disabled:opacity-50 hover:bg-blue-700 transition-colors"
>
Envoyer
</button>
</div>
</form>
</div>
);
}Patterns clés dans ce composant :
- UI optimiste — le message utilisateur apparaît immédiatement avant que l'API ne réponde
- Streaming —
ReadableStreamDefaultReaderlit les fragments dès leur arrivée et les ajoute au message de l'assistant en temps réel - Changement de modèle — un menu déroulant permet de basculer entre Llama, Mixtral, Qwen et DeepSeek à tout moment
- Défilement automatique — la vue de chat défile vers le dernier message à chaque nouveau token reçu
Étape 8 : Connecter la page
Mettez à jour app/page.tsx pour afficher le chat :
import Chat from '@/components/Chat';
export default function Home() {
return <Chat />;
}Étape 9 : Tester votre implémentation
Démarrez le serveur de développement :
npm run devOuvrez http://localhost:3000 dans votre navigateur. Vous devriez voir :
- Une interface de chat avec un sélecteur de modèle dans l'en-tête
- Un état vide vous invitant à démarrer une conversation
- Le streaming de texte en temps réel token par token lorsque l'IA répond
Testez ces scénarios pour valider le bon fonctionnement :
- Envoyez une question simple et vérifiez que le streaming s'affiche en temps réel
- Changez de modèle entre les messages
- Demandez un long extrait de code pour tester le comportement du tampon pendant un streaming soutenu
Étape 10 : Ajouter le suivi des usages (optionnel)
Le flux de réponse de Together AI inclut les données d'utilisation des tokens dans le dernier fragment. Capturez-les pour la surveillance et le contrôle des coûts :
let usage: { total_tokens: number; prompt_tokens: number; completion_tokens: number } | null = null;
for await (const chunk of stream) {
const content = chunk.choices[0]?.delta?.content || '';
if (content) {
controller.enqueue(encoder.encode(content));
}
if (chunk.usage) {
usage = chunk.usage;
}
}
if (usage) {
console.log(
`Tokens — prompt : ${usage.prompt_tokens}, ` +
`complétion : ${usage.completion_tokens}, ` +
`total : ${usage.total_tokens}`
);
}Utilisez ces données pour journaliser les coûts par requête, imposer des budgets de tokens par utilisateur ou alimenter un tableau de bord de monitoring.
Étape 11 : Déployer sur Vercel
Déployez sans aucune configuration supplémentaire :
npx vercelAjoutez votre variable d'environnement dans le tableau de bord Vercel sous Settings → Environment Variables, ou via CLI :
vercel env add TOGETHER_API_KEYL'infrastructure d'inférence mondiale de Together AI est optimisée pour une faible latence, ce qui en fait un excellent choix pour les plateformes serverless et Edge de Vercel.
Dépannage
Le streaming s'arrête en milieu de réponse
Augmentez maxDuration dans le gestionnaire de route. Définissez export const maxDuration = 60 pour prolonger le délai d'expiration serverless au-delà des 10 secondes par défaut.
Erreur TOGETHER_API_KEY is not defined
Assurez-vous que .env.local est à la racine du projet, puis redémarrez le serveur de développement. Next.js lit les variables d'environnement uniquement au démarrage.
Le streaming ne fonctionne pas en production
Confirmez que vous utilisez runtime = 'nodejs' dans le gestionnaire de route. Le runtime Edge a des limitations avec les flux de longue durée provenant de SDK tiers.
Prochaines étapes
Maintenant que votre application de chat en streaming fonctionne, envisagez ces extensions :
- Persistance des conversations — sauvegardez l'historique de chat dans Turso ou Supabase entre les sessions
- Appel d'outils — utilisez le support function calling de Together AI pour la recherche web ou l'exécution de code
- Pipeline RAG — combinez les embeddings de Together AI avec LanceDB pour des réponses contextualisées sur vos propres documents
- Fine-tuning — Together AI supporte le fine-tuning LoRA sur des datasets personnalisés via le même SDK
- Génération d'images — explorez les modèles basés sur FLUX de Together AI aux côtés des complétions de chat
Conclusion
Vous avez construit une application de chat IA multi-modèles en streaming, prête pour la production, avec le SDK TypeScript de Together AI et Next.js 15. L'API compatible OpenAI de la plateforme facilite la commutation entre plus de 200 modèles open-source, et le SDK officiel gère automatiquement le streaming, la sécurité des types et les tentatives.
Avec la levée de fonds de 800 millions de dollars soutenue par Aramco Ventures, Together AI est positionnée pour une expansion significative de son catalogue de modèles et de son infrastructure mondiale — ce qui en fait une excellente base pour construire des produits IA dès maintenant.