@boostecom/provider 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +90 -0
- package/dist/index.cjs +2522 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +848 -0
- package/dist/index.d.ts +848 -0
- package/dist/index.js +2484 -0
- package/dist/index.js.map +1 -0
- package/docs/content/README.md +337 -0
- package/docs/content/agent-teams.mdx +324 -0
- package/docs/content/api.mdx +757 -0
- package/docs/content/best-practices.mdx +624 -0
- package/docs/content/examples.mdx +675 -0
- package/docs/content/guide.mdx +516 -0
- package/docs/content/index.mdx +99 -0
- package/docs/content/installation.mdx +246 -0
- package/docs/content/skills.mdx +548 -0
- package/docs/content/troubleshooting.mdx +588 -0
- package/docs/examples/README.md +499 -0
- package/docs/examples/abort-signal.ts +125 -0
- package/docs/examples/agent-teams.ts +122 -0
- package/docs/examples/basic-usage.ts +73 -0
- package/docs/examples/check-cli.ts +51 -0
- package/docs/examples/conversation-history.ts +69 -0
- package/docs/examples/custom-config.ts +90 -0
- package/docs/examples/generate-object-constraints.ts +209 -0
- package/docs/examples/generate-object.ts +211 -0
- package/docs/examples/hooks-callbacks.ts +63 -0
- package/docs/examples/images.ts +76 -0
- package/docs/examples/integration-test.ts +241 -0
- package/docs/examples/limitations.ts +150 -0
- package/docs/examples/logging-custom-logger.ts +99 -0
- package/docs/examples/logging-default.ts +55 -0
- package/docs/examples/logging-disabled.ts +74 -0
- package/docs/examples/logging-verbose.ts +64 -0
- package/docs/examples/long-running-tasks.ts +179 -0
- package/docs/examples/message-injection.ts +210 -0
- package/docs/examples/mid-stream-injection.ts +126 -0
- package/docs/examples/run-all-examples.sh +48 -0
- package/docs/examples/sdk-tools-callbacks.ts +49 -0
- package/docs/examples/skills-discovery.ts +144 -0
- package/docs/examples/skills-management.ts +140 -0
- package/docs/examples/stream-object.ts +80 -0
- package/docs/examples/streaming.ts +52 -0
- package/docs/examples/structured-output-repro.ts +227 -0
- package/docs/examples/tool-management.ts +215 -0
- package/docs/examples/tool-streaming.ts +132 -0
- package/docs/examples/zod4-compatibility-test.ts +290 -0
- package/docs/src/claude-code-language-model.test.ts +3883 -0
- package/docs/src/claude-code-language-model.ts +2586 -0
- package/docs/src/claude-code-provider.test.ts +97 -0
- package/docs/src/claude-code-provider.ts +179 -0
- package/docs/src/convert-to-claude-code-messages.images.test.ts +104 -0
- package/docs/src/convert-to-claude-code-messages.test.ts +193 -0
- package/docs/src/convert-to-claude-code-messages.ts +419 -0
- package/docs/src/errors.test.ts +213 -0
- package/docs/src/errors.ts +216 -0
- package/docs/src/index.test.ts +49 -0
- package/docs/src/index.ts +98 -0
- package/docs/src/logger.integration.test.ts +164 -0
- package/docs/src/logger.test.ts +184 -0
- package/docs/src/logger.ts +65 -0
- package/docs/src/map-claude-code-finish-reason.test.ts +120 -0
- package/docs/src/map-claude-code-finish-reason.ts +60 -0
- package/docs/src/mcp-helpers.test.ts +71 -0
- package/docs/src/mcp-helpers.ts +123 -0
- package/docs/src/message-injection.test.ts +460 -0
- package/docs/src/types.ts +447 -0
- package/docs/src/validation.test.ts +558 -0
- package/docs/src/validation.ts +360 -0
- package/package.json +124 -0
|
@@ -0,0 +1,624 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Best Practices
|
|
3
|
+
description: Conseils et bonnes pratiques pour utiliser le provider Claude Code efficacement
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Best Practices
|
|
7
|
+
|
|
8
|
+
Ce guide présente les meilleures pratiques pour utiliser le provider Claude Code de manière efficace, sécurisée et performante.
|
|
9
|
+
|
|
10
|
+
## Choix du modèle
|
|
11
|
+
|
|
12
|
+
### Adapter le modèle à la tâche
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
// ✅ Haiku pour tâches simples et rapides
|
|
16
|
+
const quickTask = claudeCode('haiku');
|
|
17
|
+
await generateText({
|
|
18
|
+
model: quickTask,
|
|
19
|
+
prompt: 'Résume ce texte en une phrase',
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// ✅ Sonnet pour usage général (recommandé)
|
|
23
|
+
const balanced = claudeCode('sonnet');
|
|
24
|
+
await generateText({
|
|
25
|
+
model: balanced,
|
|
26
|
+
prompt: 'Analyse ce code et suggère des améliorations',
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// ✅ Opus pour tâches complexes uniquement
|
|
30
|
+
const complex = claudeCode('opus');
|
|
31
|
+
await generateText({
|
|
32
|
+
model: complex,
|
|
33
|
+
prompt: 'Conçois l\'architecture complète d\'un système distribué...',
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Règle d'or** : Commencez par Haiku, passez à Sonnet si nécessaire, réservez Opus aux tâches vraiment complexes.
|
|
38
|
+
|
|
39
|
+
## Gestion du contexte
|
|
40
|
+
|
|
41
|
+
### Maintenir l'historique efficacement
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import type { CoreMessage } from 'ai';
|
|
45
|
+
|
|
46
|
+
// ✅ Bon : Historique complet mais raisonnable
|
|
47
|
+
const messages: CoreMessage[] = [];
|
|
48
|
+
|
|
49
|
+
function addMessage(role: 'user' | 'assistant', content: string) {
|
|
50
|
+
messages.push({ role, content });
|
|
51
|
+
|
|
52
|
+
// Limiter à 20 derniers messages pour éviter token overflow
|
|
53
|
+
if (messages.length > 20) {
|
|
54
|
+
messages.splice(0, messages.length - 20);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ❌ Mauvais : Historique illimité
|
|
59
|
+
const unlimitedHistory: CoreMessage[] = [];
|
|
60
|
+
// Peut causer des erreurs de limite de tokens
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Résumé périodique pour conversations longues
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
async function summarizeHistory(messages: CoreMessage[]) {
|
|
67
|
+
if (messages.length > 15) {
|
|
68
|
+
const { text } = await generateText({
|
|
69
|
+
model: claudeCode('haiku'), // Haiku suffisant pour résumé
|
|
70
|
+
prompt: `Résume cette conversation en quelques phrases clés:\n${
|
|
71
|
+
messages.map(m => `${m.role}: ${m.content}`).join('\n')
|
|
72
|
+
}`,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Remplacer l'historique par le résumé
|
|
76
|
+
return [
|
|
77
|
+
{ role: 'assistant', content: `Résumé: ${text}` },
|
|
78
|
+
...messages.slice(-5), // Garder les 5 derniers messages
|
|
79
|
+
];
|
|
80
|
+
}
|
|
81
|
+
return messages;
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Structured Outputs
|
|
86
|
+
|
|
87
|
+
### Schémas simples et robustes
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import { z } from 'zod';
|
|
91
|
+
|
|
92
|
+
// ✅ Bon : Schéma simple avec descriptions
|
|
93
|
+
const goodSchema = z.object({
|
|
94
|
+
nom: z.string().min(1).max(100),
|
|
95
|
+
age: z.number().int().min(0).max(150),
|
|
96
|
+
email: z.string().describe('Email (ex: user@example.com)'),
|
|
97
|
+
role: z.enum(['admin', 'user', 'guest']),
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// ❌ Mauvais : Contraintes complexes qui causent fallback
|
|
101
|
+
const badSchema = z.object({
|
|
102
|
+
email: z.string().email(), // format constraint
|
|
103
|
+
url: z.string().url(), // format constraint
|
|
104
|
+
password: z.string().regex(/^(?=.*[A-Z])(?=.*[0-9]).{8,}$/), // lookahead
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// ✅ Solution : Valider côté client
|
|
108
|
+
const simpleSchema = z.object({
|
|
109
|
+
email: z.string().describe('Email valide'),
|
|
110
|
+
url: z.string().describe('URL complète avec protocole'),
|
|
111
|
+
password: z.string().min(8).describe('8+ caractères, maj et chiffres'),
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const result = await generateObject({ model, schema: simpleSchema, prompt });
|
|
115
|
+
|
|
116
|
+
// Validation stricte côté client
|
|
117
|
+
const emailValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(result.object.email);
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Décomposition pour schémas complexes
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
// ❌ Mauvais : Un seul appel avec schéma énorme
|
|
124
|
+
const hugeSchema = z.object({
|
|
125
|
+
user: z.object({ /* 20 champs */ }),
|
|
126
|
+
company: z.object({ /* 20 champs */ }),
|
|
127
|
+
settings: z.object({ /* 20 champs */ }),
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// ✅ Bon : Plusieurs appels avec schémas ciblés
|
|
131
|
+
const userSchema = z.object({ /* 10 champs essentiels */ });
|
|
132
|
+
const companySchema = z.object({ /* 10 champs essentiels */ });
|
|
133
|
+
|
|
134
|
+
const user = await generateObject({ model, schema: userSchema, prompt: '...' });
|
|
135
|
+
const company = await generateObject({ model, schema: companySchema, prompt: '...' });
|
|
136
|
+
|
|
137
|
+
const complete = { user: user.object, company: company.object };
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Sécurité
|
|
141
|
+
|
|
142
|
+
### Contrôle des outils
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
// ✅ Bon : Liste blanche explicite
|
|
146
|
+
const safeModel = claudeCode('sonnet', {
|
|
147
|
+
allowedTools: ['Read', 'LS'], // Seulement lecture
|
|
148
|
+
disallowedTools: [
|
|
149
|
+
'Write',
|
|
150
|
+
'Edit',
|
|
151
|
+
'Bash(rm:*)', // Bloquer rm
|
|
152
|
+
'Bash(sudo:*)', // Bloquer sudo
|
|
153
|
+
],
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// ❌ Risqué : Tous les outils sans restriction
|
|
157
|
+
const riskyModel = claudeCode('sonnet', {
|
|
158
|
+
permissionMode: 'bypassPermissions',
|
|
159
|
+
allowDangerouslySkipPermissions: true,
|
|
160
|
+
});
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Validation des entrées utilisateur
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
import { z } from 'zod';
|
|
167
|
+
|
|
168
|
+
// ✅ Bon : Validation avant envoi
|
|
169
|
+
const inputSchema = z.object({
|
|
170
|
+
prompt: z.string().min(1).max(5000),
|
|
171
|
+
model: z.enum(['haiku', 'sonnet', 'opus']),
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
async function safeGenerate(userInput: unknown) {
|
|
175
|
+
// Valider l'entrée
|
|
176
|
+
const validated = inputSchema.safeParse(userInput);
|
|
177
|
+
|
|
178
|
+
if (!validated.success) {
|
|
179
|
+
throw new Error('Entrée invalide');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return await generateText({
|
|
183
|
+
model: claudeCode(validated.data.model),
|
|
184
|
+
prompt: validated.data.prompt,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ❌ Mauvais : Accepter les entrées sans validation
|
|
189
|
+
async function unsafeGenerate(userInput: any) {
|
|
190
|
+
return await generateText({
|
|
191
|
+
model: claudeCode(userInput.model), // Peut crasher
|
|
192
|
+
prompt: userInput.prompt, // Peut être malicieux
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Sandboxing en production
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
// ✅ Bon : Environnement isolé
|
|
201
|
+
const productionModel = claudeCode('sonnet', {
|
|
202
|
+
sandbox: { enabled: true },
|
|
203
|
+
cwd: '/safe/isolated/directory',
|
|
204
|
+
allowedTools: ['Read'], // Minimal
|
|
205
|
+
persistSession: false, // Pas de persistence
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// ❌ Risqué : Accès complet au système
|
|
209
|
+
const riskyModel = claudeCode('sonnet', {
|
|
210
|
+
cwd: '/',
|
|
211
|
+
allowedTools: ['Bash'], // Accès shell illimité
|
|
212
|
+
});
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Performance
|
|
216
|
+
|
|
217
|
+
### Streaming pour grandes réponses
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
// ✅ Bon : Streaming pour réponses longues
|
|
221
|
+
const result = streamText({
|
|
222
|
+
model: claudeCode('sonnet'),
|
|
223
|
+
prompt: 'Écris un article de 2000 mots...',
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
for await (const chunk of result.textStream) {
|
|
227
|
+
process.stdout.write(chunk); // Affichage progressif
|
|
228
|
+
// Chunk garbage collected après usage
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ❌ Mauvais : Attendre la réponse complète
|
|
232
|
+
const result = await generateText({
|
|
233
|
+
model: claudeCode('sonnet'),
|
|
234
|
+
prompt: 'Écris un article de 2000 mots...',
|
|
235
|
+
});
|
|
236
|
+
console.log(result.text); // Tout en mémoire
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Parallélisation des tâches indépendantes
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
// ✅ Bon : Exécution parallèle
|
|
243
|
+
const [summary, keywords, sentiment] = await Promise.all([
|
|
244
|
+
generateText({ model: claudeCode('haiku'), prompt: 'Résume: ...' }),
|
|
245
|
+
generateText({ model: claudeCode('haiku'), prompt: 'Mots-clés: ...' }),
|
|
246
|
+
generateText({ model: claudeCode('haiku'), prompt: 'Sentiment: ...' }),
|
|
247
|
+
]);
|
|
248
|
+
|
|
249
|
+
// ❌ Mauvais : Exécution séquentielle
|
|
250
|
+
const summary = await generateText({ model, prompt: 'Résume: ...' });
|
|
251
|
+
const keywords = await generateText({ model, prompt: 'Mots-clés: ...' });
|
|
252
|
+
const sentiment = await generateText({ model, prompt: 'Sentiment: ...' });
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Cache des résultats fréquents
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
import NodeCache from 'node-cache';
|
|
259
|
+
|
|
260
|
+
const cache = new NodeCache({ stdTTL: 3600 }); // 1 heure
|
|
261
|
+
|
|
262
|
+
async function cachedGenerate(prompt: string) {
|
|
263
|
+
// Vérifier le cache
|
|
264
|
+
const cached = cache.get(prompt);
|
|
265
|
+
if (cached) {
|
|
266
|
+
return cached;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Générer si pas en cache
|
|
270
|
+
const { text } = await generateText({
|
|
271
|
+
model: claudeCode('haiku'),
|
|
272
|
+
prompt,
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// Mettre en cache
|
|
276
|
+
cache.set(prompt, text);
|
|
277
|
+
return text;
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Gestion d'erreurs
|
|
282
|
+
|
|
283
|
+
### Pattern robuste avec retry
|
|
284
|
+
|
|
285
|
+
```typescript
|
|
286
|
+
async function generateWithRetry(
|
|
287
|
+
prompt: string,
|
|
288
|
+
maxRetries = 3
|
|
289
|
+
): Promise<string> {
|
|
290
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
291
|
+
try {
|
|
292
|
+
const { text } = await generateText({
|
|
293
|
+
model: claudeCode('sonnet'),
|
|
294
|
+
prompt,
|
|
295
|
+
});
|
|
296
|
+
return text;
|
|
297
|
+
} catch (error: any) {
|
|
298
|
+
// Dernière tentative
|
|
299
|
+
if (i === maxRetries - 1) throw error;
|
|
300
|
+
|
|
301
|
+
// Erreur réseau/temporaire
|
|
302
|
+
if (error.code === 'ECONNRESET' || error.code === 'ETIMEDOUT') {
|
|
303
|
+
console.log(`Tentative ${i + 1} échouée, retry...`);
|
|
304
|
+
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Erreur non recoverable
|
|
309
|
+
throw error;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
throw new Error('Max retries exceeded');
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Fallback sur modèle plus simple
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
async function generateWithFallback(prompt: string) {
|
|
320
|
+
try {
|
|
321
|
+
// Essayer Opus
|
|
322
|
+
return await generateText({
|
|
323
|
+
model: claudeCode('opus'),
|
|
324
|
+
prompt,
|
|
325
|
+
});
|
|
326
|
+
} catch (error: any) {
|
|
327
|
+
if (error.message?.includes('rate limit')) {
|
|
328
|
+
// Fallback sur Sonnet si rate limited
|
|
329
|
+
console.warn('Rate limited, fallback sur Sonnet');
|
|
330
|
+
return await generateText({
|
|
331
|
+
model: claudeCode('sonnet'),
|
|
332
|
+
prompt,
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
throw error;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### Timeout approprié
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
async function generateWithTimeout(prompt: string, timeoutMs = 60000) {
|
|
344
|
+
const controller = new AbortController();
|
|
345
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
346
|
+
|
|
347
|
+
try {
|
|
348
|
+
const result = await generateText({
|
|
349
|
+
model: claudeCode('sonnet'),
|
|
350
|
+
prompt,
|
|
351
|
+
abortSignal: controller.signal,
|
|
352
|
+
});
|
|
353
|
+
clearTimeout(timeoutId);
|
|
354
|
+
return result;
|
|
355
|
+
} catch (error: any) {
|
|
356
|
+
if (error.name === 'AbortError') {
|
|
357
|
+
throw new Error(`Timeout après ${timeoutMs}ms`);
|
|
358
|
+
}
|
|
359
|
+
throw error;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
## Logging et monitoring
|
|
365
|
+
|
|
366
|
+
### Logging structuré
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
import type { Logger } from '@boostecom/provider';
|
|
370
|
+
|
|
371
|
+
const structuredLogger: Logger = {
|
|
372
|
+
debug: (msg) => logger.debug({ component: 'claude-code', message: msg }),
|
|
373
|
+
info: (msg) => logger.info({ component: 'claude-code', message: msg }),
|
|
374
|
+
warn: (msg) => logger.warn({ component: 'claude-code', message: msg }),
|
|
375
|
+
error: (msg) => {
|
|
376
|
+
logger.error({ component: 'claude-code', message: msg });
|
|
377
|
+
// Alerter si critique
|
|
378
|
+
if (msg.includes('authentication')) {
|
|
379
|
+
sendAlert('Claude Code auth failed');
|
|
380
|
+
}
|
|
381
|
+
},
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
const model = claudeCode('sonnet', {
|
|
385
|
+
verbose: true,
|
|
386
|
+
logger: structuredLogger,
|
|
387
|
+
});
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### Monitoring des coûts
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
let totalCost = 0;
|
|
394
|
+
let totalTokens = 0;
|
|
395
|
+
|
|
396
|
+
async function monitoredGenerate(prompt: string) {
|
|
397
|
+
const result = await generateText({
|
|
398
|
+
model: claudeCode('sonnet'),
|
|
399
|
+
prompt,
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
// Tracker usage
|
|
403
|
+
totalTokens += result.usage.totalTokens;
|
|
404
|
+
|
|
405
|
+
// Estimer coût (exemple: $0.003/1K tokens)
|
|
406
|
+
const cost = (result.usage.totalTokens / 1000) * 0.003;
|
|
407
|
+
totalCost += cost;
|
|
408
|
+
|
|
409
|
+
// Logger
|
|
410
|
+
console.log({
|
|
411
|
+
tokens: result.usage.totalTokens,
|
|
412
|
+
cost: cost.toFixed(4),
|
|
413
|
+
totalCost: totalCost.toFixed(4),
|
|
414
|
+
totalTokens,
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
// Alerte si dépassement budget
|
|
418
|
+
if (totalCost > 10) {
|
|
419
|
+
throw new Error('Budget journalier dépassé');
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return result;
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### Tracking des performances
|
|
427
|
+
|
|
428
|
+
```typescript
|
|
429
|
+
async function trackedGenerate(prompt: string) {
|
|
430
|
+
const startTime = Date.now();
|
|
431
|
+
|
|
432
|
+
try {
|
|
433
|
+
const result = await generateText({
|
|
434
|
+
model: claudeCode('sonnet'),
|
|
435
|
+
prompt,
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
const duration = Date.now() - startTime;
|
|
439
|
+
|
|
440
|
+
// Envoyer métriques
|
|
441
|
+
metrics.record({
|
|
442
|
+
metric: 'claude_generate_duration',
|
|
443
|
+
value: duration,
|
|
444
|
+
tags: { model: 'sonnet', status: 'success' },
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
return result;
|
|
448
|
+
} catch (error: any) {
|
|
449
|
+
const duration = Date.now() - startTime;
|
|
450
|
+
|
|
451
|
+
metrics.record({
|
|
452
|
+
metric: 'claude_generate_duration',
|
|
453
|
+
value: duration,
|
|
454
|
+
tags: { model: 'sonnet', status: 'error' },
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
throw error;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
## Testing
|
|
463
|
+
|
|
464
|
+
### Tests unitaires avec mocking
|
|
465
|
+
|
|
466
|
+
```typescript
|
|
467
|
+
import { vi, describe, it, expect } from 'vitest';
|
|
468
|
+
|
|
469
|
+
// Mock du provider
|
|
470
|
+
vi.mock('@boostecom/provider', () => ({
|
|
471
|
+
claudeCode: vi.fn(() => ({
|
|
472
|
+
provider: 'claude-code',
|
|
473
|
+
modelId: 'sonnet',
|
|
474
|
+
})),
|
|
475
|
+
}));
|
|
476
|
+
|
|
477
|
+
describe('Service avec Claude', () => {
|
|
478
|
+
it('génère une réponse', async () => {
|
|
479
|
+
const mockGenerateText = vi.fn().mockResolvedValue({
|
|
480
|
+
text: 'Réponse mockée',
|
|
481
|
+
usage: { totalTokens: 100 },
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
// Votre logique de test
|
|
485
|
+
const result = await mockGenerateText({
|
|
486
|
+
model: claudeCode('sonnet'),
|
|
487
|
+
prompt: 'Test',
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
expect(result.text).toBe('Réponse mockée');
|
|
491
|
+
});
|
|
492
|
+
});
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
### Tests d'intégration
|
|
496
|
+
|
|
497
|
+
```typescript
|
|
498
|
+
import { describe, it, expect } from 'vitest';
|
|
499
|
+
import { generateText } from 'ai';
|
|
500
|
+
import { claudeCode } from '@boostecom/provider';
|
|
501
|
+
|
|
502
|
+
describe('Intégration Claude Code', () => {
|
|
503
|
+
it('génère du texte réel', async () => {
|
|
504
|
+
const { text } = await generateText({
|
|
505
|
+
model: claudeCode('haiku'), // Haiku pour coût/vitesse
|
|
506
|
+
prompt: 'Dis simplement "OK"',
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
expect(text).toBeTruthy();
|
|
510
|
+
expect(text.toLowerCase()).toContain('ok');
|
|
511
|
+
}, 30000); // Timeout 30s
|
|
512
|
+
|
|
513
|
+
it('génère un objet structuré', async () => {
|
|
514
|
+
const { object } = await generateObject({
|
|
515
|
+
model: claudeCode('haiku'),
|
|
516
|
+
schema: z.object({
|
|
517
|
+
nombre: z.number(),
|
|
518
|
+
}),
|
|
519
|
+
prompt: 'Génère un objet avec nombre = 42',
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
expect(object.nombre).toBe(42);
|
|
523
|
+
}, 30000);
|
|
524
|
+
});
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
## Production
|
|
528
|
+
|
|
529
|
+
### Configuration d'environnement
|
|
530
|
+
|
|
531
|
+
```typescript
|
|
532
|
+
// config.ts
|
|
533
|
+
const isProd = process.env.NODE_ENV === 'production';
|
|
534
|
+
|
|
535
|
+
export const claudeConfig = {
|
|
536
|
+
model: isProd ? 'sonnet' : 'haiku', // Haiku en dev
|
|
537
|
+
verbose: !isProd, // Logs seulement en dev
|
|
538
|
+
persistSession: !isProd, // Pas de persistence en prod
|
|
539
|
+
sandbox: isProd ? { enabled: true } : undefined,
|
|
540
|
+
maxTurns: isProd ? 5 : 10, // Limiter en prod
|
|
541
|
+
allowedTools: isProd
|
|
542
|
+
? ['Read', 'LS'] // Minimal en prod
|
|
543
|
+
: ['Read', 'Write', 'Bash'], // Plus permissif en dev
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
// Utilisation
|
|
547
|
+
const model = claudeCode('sonnet', claudeConfig);
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
### Rate limiting
|
|
551
|
+
|
|
552
|
+
```typescript
|
|
553
|
+
import Bottleneck from 'bottleneck';
|
|
554
|
+
|
|
555
|
+
// Limiter à 10 requêtes par minute
|
|
556
|
+
const limiter = new Bottleneck({
|
|
557
|
+
maxConcurrent: 1,
|
|
558
|
+
minTime: 6000, // 6s entre requêtes
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
async function rateLimitedGenerate(prompt: string) {
|
|
562
|
+
return limiter.schedule(async () => {
|
|
563
|
+
return await generateText({
|
|
564
|
+
model: claudeCode('sonnet'),
|
|
565
|
+
prompt,
|
|
566
|
+
});
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
### Health checks
|
|
572
|
+
|
|
573
|
+
```typescript
|
|
574
|
+
import express from 'express';
|
|
575
|
+
|
|
576
|
+
const app = express();
|
|
577
|
+
|
|
578
|
+
app.get('/health', async (req, res) => {
|
|
579
|
+
try {
|
|
580
|
+
// Test simple avec timeout court
|
|
581
|
+
const controller = new AbortController();
|
|
582
|
+
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
|
583
|
+
|
|
584
|
+
await generateText({
|
|
585
|
+
model: claudeCode('haiku'),
|
|
586
|
+
prompt: 'Dis OK',
|
|
587
|
+
abortSignal: controller.signal,
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
clearTimeout(timeoutId);
|
|
591
|
+
res.json({ status: 'healthy', claude: 'ok' });
|
|
592
|
+
} catch (error: any) {
|
|
593
|
+
res.status(503).json({
|
|
594
|
+
status: 'unhealthy',
|
|
595
|
+
claude: 'error',
|
|
596
|
+
message: error.message,
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
## Ressources
|
|
603
|
+
|
|
604
|
+
- [Guide d'utilisation](/docs/guide) - Patterns de base
|
|
605
|
+
- [API Reference](/docs/api) - Documentation complète
|
|
606
|
+
- [Exemples](/docs/examples) - Cas d'usage pratiques
|
|
607
|
+
- [Dépannage](/docs/troubleshooting) - Solutions aux problèmes
|
|
608
|
+
|
|
609
|
+
## Checklist finale
|
|
610
|
+
|
|
611
|
+
Avant de déployer en production :
|
|
612
|
+
|
|
613
|
+
- [ ] Modèle adapté à la tâche (Haiku pour simple, Sonnet pour général)
|
|
614
|
+
- [ ] Outils restreints au minimum nécessaire
|
|
615
|
+
- [ ] Validation des entrées utilisateur
|
|
616
|
+
- [ ] Gestion d'erreurs avec retry
|
|
617
|
+
- [ ] Timeouts appropriés configurés
|
|
618
|
+
- [ ] Logging structuré en place
|
|
619
|
+
- [ ] Monitoring des coûts activé
|
|
620
|
+
- [ ] Tests d'intégration passants
|
|
621
|
+
- [ ] Rate limiting configuré
|
|
622
|
+
- [ ] Health checks implémentés
|
|
623
|
+
- [ ] Secrets sécurisés (pas de hardcoding)
|
|
624
|
+
- [ ] Documentation à jour
|