Ce que vous saurez faire à la fin
- Installer le SDK Anthropic TypeScript
- Créer un agent avec tool_use
- Valider les entrées avec Zod
- Streaming et prompt caching
- Déployer en production
Vue d’ensemble 1 — Installation
npm install @anthropic-ai/sdk@0.32.1 dotenv zod
npm install -D typescript @types/node tsx
Vue d’ensemble 2 — Premier appel
import Anthropic from "@anthropic-ai/sdk";
import "dotenv/config";
const client = new Anthropic();
const msg = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{ role: "user", content: "Plan SQL 5 modules" }],
});
const text = msg.content
.filter((b): b is Anthropic.TextBlock => b.type === "text")
.map(b => b.text).join("");
console.log(text);
Vue d’ensemble 3 — Agent avec tool_use
const tools: Anthropic.Tool[] = [{
name: "recherche_fichier",
description: "Cherche un fichier par pattern",
input_schema: {
type: "object",
properties: { pattern: { type: "string" } },
required: ["pattern"],
},
}];
async function agent(demande: string) {
const messages: Anthropic.MessageParam[] = [{ role: "user", content: demande }];
for (let tour = 0; tour < 10; tour++) {
const r = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 2048,
tools,
messages,
});
if (r.stop_reason === "end_turn") {
return r.content.filter((b: any) => b.type === "text").map((b: any) => b.text).join("");
}
messages.push({ role: "assistant", content: r.content });
const toolResults: any[] = [];
for (const b of r.content) {
if (b.type === "tool_use") {
const result = { files: ["src/index.ts"] };
toolResults.push({ type: "tool_result", tool_use_id: b.id, content: JSON.stringify(result) });
}
}
messages.push({ role: "user", content: toolResults });
}
throw new Error("trop de tours");
}
Vue d’ensemble 4 — Validation Zod
import { z } from "zod";
const SchemaRecherche = z.object({ pattern: z.string().min(1).max(100) });
function execRecherche(raw: unknown) {
const { pattern } = SchemaRecherche.parse(raw);
return { files: [] };
}
Vue d’ensemble 5 — Streaming Node
const stream = await client.messages.stream({
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{ role: "user", content: "200 mots sur Dakar" }],
});
for await (const chunk of stream) {
if (chunk.type === "content_block_delta" && chunk.delta.type === "text_delta") {
process.stdout.write(chunk.delta.text);
}
}
Vue d’ensemble 6 — Prompt caching
const r = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
system: [{
type: "text",
text: GROS_SYSTEM, // > 1024 tokens requis
cache_control: { type: "ephemeral" },
}],
messages: [{ role: "user", content: "Résume" }],
});
console.log("cache_read:", r.usage.cache_read_input_tokens);
Vue d’ensemble 7 — Retries
async function withRetry<T>(fn: () => Promise<T>, max = 3): Promise<T> {
for (let i = 0; i < max; i++) {
try { return await fn(); }
catch (e: any) {
if (e.status === 429 || e.status >= 500) {
await new Promise(r => setTimeout(r, 2 ** i * 1000));
continue;
}
throw e;
}
}
throw new Error("max retries");
}
Vue d’ensemble 8 — Config production
const client = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY!,
maxRetries: 3,
timeout: 60_000,
});
Vue d’ensemble 9 — Logging
import pino from "pino";
const log = pino();
log.info({
input: r.usage.input_tokens,
output: r.usage.output_tokens,
cache_read: r.usage.cache_read_input_tokens ?? 0,
}, "api_call");
Vue d’ensemble 10 — Checklist
✓ Clé API en env var
✓ Timeout et maxRetries configurés
✓ withRetry sur 429 et 5xx
✓ Prompt caching sur system stable
✓ Zod pour validation inputs
✓ Logging structuré des coûts
Pourquoi TypeScript pour un premier agent Claude
Vous avez teste l’API Anthropic via curl ou un notebook Python, et vous voulez maintenant un agent reutilisable, type, integrable dans un backend Node ou un projet Next.js. TypeScript est le bon choix : autocomplete sur les types Anthropic.Messages.MessageParam, detection des cles manquantes a la compilation, et integration directe dans une stack moderne. Que votre projet final tourne sur Render, Railway, un VPS Hetzner ou un serveur sur l’Almadies, le code reste identique.
Ce tutoriel construit un agent fonctionnel : recoit une question, appelle Claude, gere un outil simple, retourne la reponse formatee.
Etape 1 : initialiser le projet Node
Creez un dossier puis initialisez le projet avec les outils standard. Un terminal sous WSL Ubuntu, macOS ou Linux fonctionne identiquement.
mkdir mon-agent && cd mon-agent
npm init -y
npm install @anthropic-ai/sdk dotenv
npm install -D typescript tsx @types/node
Vous installez le SDK officiel Anthropic, dotenv pour la gestion de la cle API, TypeScript pour la compilation, et tsx pour executer du TypeScript directement sans build separe pendant le developpement.
Etape 2 : configurer TypeScript
Creez tsconfig.json avec une configuration moderne ESM, target ES2022, strict active.
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"outDir": "dist"
},
"include": ["src"]
}
Avec strict active, TypeScript refusera tout code ambigu. Sur un projet d’agent, c’est exactement ce qu’on veut : un appel API mal forme produit une erreur a la compilation, pas a l’execution en prod.
Etape 3 : stocker la cle API
Creez un fichier .env a la racine et ajoutez votre cle obtenue sur console.anthropic.com.
ANTHROPIC_API_KEY=sk-ant-...
Ajoutez .env dans .gitignore immediatement, avant tout commit. Une cle API exposee sur GitHub est revoquee automatiquement par Anthropic dans les minutes qui suivent, mais entretemps elle peut couter cher.
Etape 4 : premier appel a Claude
Creez src/agent.ts avec un appel minimal qui valide votre setup.
import Anthropic from "@anthropic-ai/sdk";
import "dotenv/config";
const client = new Anthropic();
const message = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [{ role: "user", content: "Bonjour, presente-toi en une phrase." }],
});
console.log(message.content);
Lancez avec npx tsx src/agent.ts. Le SDK lit ANTHROPIC_API_KEY automatiquement. Vous obtenez un tableau de blocs de contenu, le premier est de type text avec la reponse de Claude. Si vous voyez la presentation, votre stack fonctionne.
Etape 5 : extraire le texte de la reponse
Le champ content est un tableau de blocs typés. Pour un agent simple, on extrait juste le texte du premier bloc text.
const reponse = message.content
.filter((bloc): bloc is Anthropic.Messages.TextBlock => bloc.type === "text")
.map(bloc => bloc.text)
.join("\n");
console.log(reponse);
Le predicat de type avec is permet a TypeScript de comprendre que le bloc filtre est bien un TextBlock. Vous accedez ensuite a .text en toute securite, sans cast manuel.
Etape 6 : conversation multi-tours
Un agent ne traite jamais une question isolee. Maintenez un tableau messages que vous enrichissez a chaque echange.
const historique: Anthropic.Messages.MessageParam[] = [];
async function demander(question: string) {
historique.push({ role: "user", content: question });
const rep = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 2048,
system: "Tu es un assistant technique pour developpeurs ouest-africains.",
messages: historique,
});
const texte = rep.content.filter(b => b.type === "text").map(b => b.text).join("\n");
historique.push({ role: "assistant", content: texte });
return texte;
}
Le system prompt oriente le comportement. Le tableau historique conserve le contexte entre appels. Attention : chaque appel facture l’integralite du contexte, prevoyez un mecanisme de troncature au-dela de 50 tours.
Etape 7 : ajouter un outil simple
Les vrais agents appellent des outils. Definissez un outil qui retourne le taux de change FCFA-EUR.
const outils: Anthropic.Messages.Tool[] = [{
name: "taux_fcfa",
description: "Retourne le taux de change actuel entre FCFA et une devise.",
input_schema: {
type: "object",
properties: {
devise: { type: "string", description: "Code devise, ex EUR ou USD" }
},
required: ["devise"]
}
}];
Le schema input_schema decrit les parametres attendus. Claude saura ainsi quand et comment appeler l’outil, en remplissant correctement la devise demandee par l’utilisateur.
Etape 8 : la boucle d’execution d’outil
Quand Claude veut utiliser un outil, il retourne un bloc tool_use. Vous executez la fonction localement et renvoyez le resultat dans un nouveau message.
const rep = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 2048,
tools: outils,
messages: historique,
});
for (const bloc of rep.content) {
if (bloc.type === "tool_use" && bloc.name === "taux_fcfa") {
const input = bloc.input as { devise: string };
const taux = input.devise === "EUR" ? 655.957 : 600;
historique.push({ role: "assistant", content: rep.content });
historique.push({ role: "user", content: [{ type: "tool_result", tool_use_id: bloc.id, content: String(taux) }] });
}
}
Apres ce tour, rappelez l’API : Claude integre le resultat de l’outil et formule la reponse finale. C’est le pattern de base de tout agent.
Etape 9 : streaming pour l’experience utilisateur
Pour une interface chat, le streaming evite l’attente de la reponse complete. Le SDK expose stream qui emet des evenements text_delta au fil de la generation.
const stream = client.messages.stream({
model: "claude-opus-4-7",
max_tokens: 2048,
messages: historique,
});
for await (const event of stream) {
if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
process.stdout.write(event.delta.text);
}
}
L’utilisateur voit la reponse s’ecrire mot par mot. Sur une connexion ADSL ou 4G a Cotonou ou Lome, c’est la difference entre une UX fluide et un produit qui semble fige.
Etape 10 : activer le prompt caching
Pour les agents avec un long system prompt ou un historique repetitif, activez le cache pour reduire la facture de 90 pourcent sur les portions reutilisees.
system: [{
type: "text",
text: "Tu es un assistant technique tres detaille...",
cache_control: { type: "ephemeral" }
}]
Le cache reste valide 5 minutes. Sur un agent qui repond 60 fois par heure avec le meme system prompt, l’economie est massive. Mesurez l’impact via cache_read_input_tokens dans la reponse.
Etape 11 : passage en production
Compilez avec tsc et lancez le binaire compile sur votre serveur. Pour un endpoint HTTP, encapsulez l’agent dans Express ou Hono. Stockez l’historique des conversations dans Redis ou PostgreSQL : le tableau messages en RAM ne survit pas a un redeploiement.
Sur le même thème, voyez nos guides sur l’automatisation Excel et Motion App.
Etape 12 : monitorer les couts
Dans la console Anthropic, configurez une limite de depense mensuelle et activez les alertes. Logguez localement chaque appel avec input_tokens, output_tokens et le cout estime. Une fonction utilitaire qui calcule le cout par appel evite les mauvaises surprises en fin de mois quand l’agent part en boucle sur un cas non prevu.
Etape 13 : gerer les erreurs et les retries
Les appels API echouent parfois : timeout reseau au Plateau, rate limit Anthropic, erreur 529 surcharge. Le SDK officiel expose un mecanisme de retry mais une couche applicative reste utile pour journaliser et adapter le comportement metier.
async function appelerAvecRetry(messages: Anthropic.Messages.MessageParam[], tentative = 0): Promise<string> {
try {
const rep = await client.messages.create({ model: "claude-opus-4-7", max_tokens: 2048, messages });
return rep.content.filter(b => b.type === "text").map(b => b.text).join("");
} catch (e: any) {
if (tentative < 3 && (e.status === 529 || e.status === 429)) {
const attente = Math.pow(2, tentative) * 1000;
await new Promise(r => setTimeout(r, attente));
return appelerAvecRetry(messages, tentative + 1);
}
throw e;
}
}
Backoff exponentiel : 1 seconde, 2 secondes, 4 secondes. Au bout de trois tentatives infructueuses, l’erreur remonte et le code applicatif decide d’afficher un message d’attente a l’utilisateur ou de basculer vers un mode degrade.
Etape 14 : structurer le projet pour la maintenance
Au-dela d’un fichier unique, organisez le code en modules. src/client.ts expose l’instance Anthropic, src/outils/ regroupe les definitions et implementations d’outils, src/agent.ts contient la boucle conversationnelle, src/server.ts ouvre le HTTP. Cette separation facilite l’ajout d’outils sans toucher a l’agent et accelere les tests unitaires sur chaque module.
Ajoutez Vitest pour tester en isolation chaque outil avec des inputs synthetiques. Un agent en production sans tests automatises devient ingerable des que le nombre d’outils depasse cinq.
Etape 15 : deploiement et observabilite
Pour un VPS Hetzner ou un Render gratuit, packagez avec un Dockerfile minimal base sur node 22-alpine. Exposez un endpoint POST /chat et un endpoint GET /health. Sur Cloudflare Workers, le SDK fonctionne avec quelques adaptations sur le client fetch. Branchez Sentry ou Axiom pour capter les erreurs, et OpenTelemetry pour mesurer la latence par appel API et la repartition des couts par utilisateur.
Etape 16 : limites et bonnes pratiques de cle API
Ne distribuez jamais la cle ANTHROPIC_API_KEY cote client. Toute cle exposee dans une page web ou une app mobile est immediatement exploitable par un tiers. Le pattern correct est un backend qui detient la cle et expose un endpoint authentifie a vos utilisateurs. Mettez en place un rate limit applicatif (par exemple 30 requetes par utilisateur et par heure) et une verification du JWT a chaque appel pour eviter qu’un compte compromis ne fasse exploser la facture mensuelle.
Etape 17 : evolutions et choix de modele
Pour les agents simples sans long contexte, claude-sonnet-4-6 offre un excellent rapport cout-performance. Pour des taches qui demandent du raisonnement profond, claude-opus-4-7 reste le meilleur choix. Mesurez systematiquement la qualite des reponses sur un jeu de tests representatif avant de basculer un modele en production. Une difference de cout d’un facteur cinq merite quelques heures de comparaison rigoureuse.