omgkit 2.0.6 → 2.1.0
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/package.json +6 -3
- package/plugin/agents/architect.md +357 -43
- package/plugin/agents/code-reviewer.md +481 -22
- package/plugin/agents/debugger.md +397 -30
- package/plugin/agents/docs-manager.md +431 -23
- package/plugin/agents/fullstack-developer.md +395 -34
- package/plugin/agents/git-manager.md +438 -20
- package/plugin/agents/oracle.md +329 -53
- package/plugin/agents/planner.md +275 -32
- package/plugin/agents/researcher.md +343 -21
- package/plugin/agents/scout.md +423 -18
- package/plugin/agents/sprint-master.md +418 -48
- package/plugin/agents/tester.md +551 -26
- package/plugin/skills/backend/api-architecture/SKILL.md +857 -0
- package/plugin/skills/backend/caching-strategies/SKILL.md +755 -0
- package/plugin/skills/backend/event-driven-architecture/SKILL.md +753 -0
- package/plugin/skills/backend/real-time-systems/SKILL.md +635 -0
- package/plugin/skills/databases/database-optimization/SKILL.md +571 -0
- package/plugin/skills/devops/monorepo-management/SKILL.md +595 -0
- package/plugin/skills/devops/observability/SKILL.md +622 -0
- package/plugin/skills/devops/performance-profiling/SKILL.md +905 -0
- package/plugin/skills/frontend/advanced-ui-design/SKILL.md +426 -0
- package/plugin/skills/integrations/ai-integration/SKILL.md +730 -0
- package/plugin/skills/integrations/payment-integration/SKILL.md +735 -0
- package/plugin/skills/methodology/problem-solving/SKILL.md +355 -0
- package/plugin/skills/methodology/research-validation/SKILL.md +668 -0
- package/plugin/skills/methodology/sequential-thinking/SKILL.md +260 -0
- package/plugin/skills/mobile/mobile-development/SKILL.md +756 -0
- package/plugin/skills/security/security-hardening/SKILL.md +633 -0
- package/plugin/skills/tools/document-processing/SKILL.md +916 -0
- package/plugin/skills/tools/image-processing/SKILL.md +748 -0
- package/plugin/skills/tools/mcp-development/SKILL.md +883 -0
- package/plugin/skills/tools/media-processing/SKILL.md +831 -0
|
@@ -0,0 +1,730 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ai-integration
|
|
3
|
+
description: AI/ML model integration including vision, audio, embeddings, and RAG implementation patterns
|
|
4
|
+
category: integrations
|
|
5
|
+
triggers:
|
|
6
|
+
- ai integration
|
|
7
|
+
- ai ml
|
|
8
|
+
- embeddings
|
|
9
|
+
- rag
|
|
10
|
+
- vision api
|
|
11
|
+
- audio transcription
|
|
12
|
+
- openai
|
|
13
|
+
- anthropic
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# AI Integration
|
|
17
|
+
|
|
18
|
+
Enterprise **AI/ML model integration** patterns for vision, audio, embeddings, and RAG systems. This skill covers API integration, prompt engineering, and production deployment.
|
|
19
|
+
|
|
20
|
+
## Purpose
|
|
21
|
+
|
|
22
|
+
Integrate AI capabilities into applications effectively:
|
|
23
|
+
|
|
24
|
+
- Implement vision and image understanding
|
|
25
|
+
- Add audio transcription and processing
|
|
26
|
+
- Build semantic search with embeddings
|
|
27
|
+
- Create RAG (Retrieval Augmented Generation) systems
|
|
28
|
+
- Handle rate limiting and error recovery
|
|
29
|
+
- Optimize costs and latency
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
|
|
33
|
+
### 1. Vision API Integration
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
37
|
+
import OpenAI from 'openai';
|
|
38
|
+
|
|
39
|
+
const anthropic = new Anthropic();
|
|
40
|
+
const openai = new OpenAI();
|
|
41
|
+
|
|
42
|
+
// Analyze image with Claude
|
|
43
|
+
async function analyzeImageWithClaude(
|
|
44
|
+
imageUrl: string | Buffer,
|
|
45
|
+
prompt: string
|
|
46
|
+
): Promise<string> {
|
|
47
|
+
const imageSource = typeof imageUrl === 'string'
|
|
48
|
+
? { type: 'url' as const, url: imageUrl }
|
|
49
|
+
: {
|
|
50
|
+
type: 'base64' as const,
|
|
51
|
+
media_type: 'image/jpeg' as const,
|
|
52
|
+
data: imageUrl.toString('base64'),
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const response = await anthropic.messages.create({
|
|
56
|
+
model: 'claude-sonnet-4-20250514',
|
|
57
|
+
max_tokens: 1024,
|
|
58
|
+
messages: [{
|
|
59
|
+
role: 'user',
|
|
60
|
+
content: [
|
|
61
|
+
{
|
|
62
|
+
type: 'image',
|
|
63
|
+
source: imageSource,
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
type: 'text',
|
|
67
|
+
text: prompt,
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
}],
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return response.content[0].type === 'text'
|
|
74
|
+
? response.content[0].text
|
|
75
|
+
: '';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Extract structured data from image
|
|
79
|
+
interface ProductInfo {
|
|
80
|
+
name: string;
|
|
81
|
+
description: string;
|
|
82
|
+
price?: string;
|
|
83
|
+
category?: string;
|
|
84
|
+
features: string[];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function extractProductFromImage(imageBuffer: Buffer): Promise<ProductInfo> {
|
|
88
|
+
const prompt = `Analyze this product image and extract:
|
|
89
|
+
1. Product name
|
|
90
|
+
2. Description (2-3 sentences)
|
|
91
|
+
3. Price (if visible)
|
|
92
|
+
4. Category
|
|
93
|
+
5. Key features (list)
|
|
94
|
+
|
|
95
|
+
Return as JSON only, no explanation.`;
|
|
96
|
+
|
|
97
|
+
const response = await analyzeImageWithClaude(imageBuffer, prompt);
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
return JSON.parse(response);
|
|
101
|
+
} catch {
|
|
102
|
+
throw new Error('Failed to parse product information');
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// OCR with GPT-4 Vision
|
|
107
|
+
async function extractTextFromImage(imageUrl: string): Promise<string> {
|
|
108
|
+
const response = await openai.chat.completions.create({
|
|
109
|
+
model: 'gpt-4o',
|
|
110
|
+
messages: [{
|
|
111
|
+
role: 'user',
|
|
112
|
+
content: [
|
|
113
|
+
{
|
|
114
|
+
type: 'image_url',
|
|
115
|
+
image_url: { url: imageUrl, detail: 'high' },
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
type: 'text',
|
|
119
|
+
text: 'Extract all text from this image. Preserve the original formatting and structure as much as possible.',
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
}],
|
|
123
|
+
max_tokens: 4096,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
return response.choices[0].message.content || '';
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Batch image processing
|
|
130
|
+
async function batchAnalyzeImages(
|
|
131
|
+
images: Array<{ id: string; url: string }>,
|
|
132
|
+
prompt: string,
|
|
133
|
+
concurrency: number = 3
|
|
134
|
+
): Promise<Map<string, string>> {
|
|
135
|
+
const results = new Map<string, string>();
|
|
136
|
+
const queue = new PQueue({ concurrency });
|
|
137
|
+
|
|
138
|
+
await Promise.all(
|
|
139
|
+
images.map(image =>
|
|
140
|
+
queue.add(async () => {
|
|
141
|
+
try {
|
|
142
|
+
const result = await analyzeImageWithClaude(image.url, prompt);
|
|
143
|
+
results.set(image.id, result);
|
|
144
|
+
} catch (error) {
|
|
145
|
+
results.set(image.id, `Error: ${error.message}`);
|
|
146
|
+
}
|
|
147
|
+
})
|
|
148
|
+
)
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
return results;
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### 2. Audio Processing
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
import { Readable } from 'stream';
|
|
159
|
+
|
|
160
|
+
// Transcribe audio with Whisper
|
|
161
|
+
async function transcribeAudio(
|
|
162
|
+
audioFile: Buffer | string,
|
|
163
|
+
options: {
|
|
164
|
+
language?: string;
|
|
165
|
+
prompt?: string;
|
|
166
|
+
responseFormat?: 'json' | 'text' | 'srt' | 'vtt';
|
|
167
|
+
timestamps?: boolean;
|
|
168
|
+
} = {}
|
|
169
|
+
): Promise<TranscriptionResult> {
|
|
170
|
+
const {
|
|
171
|
+
language,
|
|
172
|
+
prompt,
|
|
173
|
+
responseFormat = 'json',
|
|
174
|
+
timestamps = false,
|
|
175
|
+
} = options;
|
|
176
|
+
|
|
177
|
+
const file = typeof audioFile === 'string'
|
|
178
|
+
? fs.createReadStream(audioFile)
|
|
179
|
+
: Readable.from(audioFile);
|
|
180
|
+
|
|
181
|
+
const response = await openai.audio.transcriptions.create({
|
|
182
|
+
file,
|
|
183
|
+
model: 'whisper-1',
|
|
184
|
+
language,
|
|
185
|
+
prompt,
|
|
186
|
+
response_format: timestamps ? 'verbose_json' : responseFormat,
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
if (timestamps && typeof response !== 'string') {
|
|
190
|
+
return {
|
|
191
|
+
text: response.text,
|
|
192
|
+
segments: response.segments?.map(seg => ({
|
|
193
|
+
start: seg.start,
|
|
194
|
+
end: seg.end,
|
|
195
|
+
text: seg.text,
|
|
196
|
+
})),
|
|
197
|
+
language: response.language,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return { text: typeof response === 'string' ? response : response.text };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Real-time transcription with streaming
|
|
205
|
+
async function* streamTranscription(
|
|
206
|
+
audioStream: ReadableStream
|
|
207
|
+
): AsyncGenerator<string> {
|
|
208
|
+
// For real-time, use Deepgram or AssemblyAI
|
|
209
|
+
const deepgram = new Deepgram(process.env.DEEPGRAM_API_KEY!);
|
|
210
|
+
|
|
211
|
+
const connection = await deepgram.transcription.live({
|
|
212
|
+
model: 'nova-2',
|
|
213
|
+
language: 'en',
|
|
214
|
+
smart_format: true,
|
|
215
|
+
interim_results: true,
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
connection.on('transcriptReceived', (message) => {
|
|
219
|
+
const transcript = message.channel?.alternatives?.[0]?.transcript;
|
|
220
|
+
if (transcript) {
|
|
221
|
+
yield transcript;
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// Pipe audio to connection
|
|
226
|
+
const reader = audioStream.getReader();
|
|
227
|
+
while (true) {
|
|
228
|
+
const { done, value } = await reader.read();
|
|
229
|
+
if (done) break;
|
|
230
|
+
connection.send(value);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
connection.close();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Generate speech from text
|
|
237
|
+
async function generateSpeech(
|
|
238
|
+
text: string,
|
|
239
|
+
options: {
|
|
240
|
+
voice?: 'alloy' | 'echo' | 'fable' | 'onyx' | 'nova' | 'shimmer';
|
|
241
|
+
model?: 'tts-1' | 'tts-1-hd';
|
|
242
|
+
speed?: number;
|
|
243
|
+
} = {}
|
|
244
|
+
): Promise<Buffer> {
|
|
245
|
+
const { voice = 'alloy', model = 'tts-1', speed = 1 } = options;
|
|
246
|
+
|
|
247
|
+
const response = await openai.audio.speech.create({
|
|
248
|
+
model,
|
|
249
|
+
voice,
|
|
250
|
+
input: text,
|
|
251
|
+
speed,
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
return Buffer.from(await response.arrayBuffer());
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### 3. Embeddings & Vector Search
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
import { Pinecone } from '@pinecone-database/pinecone';
|
|
262
|
+
|
|
263
|
+
const pinecone = new Pinecone();
|
|
264
|
+
|
|
265
|
+
// Generate embeddings
|
|
266
|
+
async function generateEmbeddings(
|
|
267
|
+
texts: string[],
|
|
268
|
+
model: string = 'text-embedding-3-small'
|
|
269
|
+
): Promise<number[][]> {
|
|
270
|
+
const response = await openai.embeddings.create({
|
|
271
|
+
model,
|
|
272
|
+
input: texts,
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
return response.data.map(d => d.embedding);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Index documents
|
|
279
|
+
interface Document {
|
|
280
|
+
id: string;
|
|
281
|
+
content: string;
|
|
282
|
+
metadata?: Record<string, any>;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async function indexDocuments(
|
|
286
|
+
documents: Document[],
|
|
287
|
+
indexName: string,
|
|
288
|
+
namespace: string = 'default'
|
|
289
|
+
): Promise<void> {
|
|
290
|
+
const index = pinecone.index(indexName);
|
|
291
|
+
|
|
292
|
+
// Process in batches
|
|
293
|
+
const batchSize = 100;
|
|
294
|
+
for (let i = 0; i < documents.length; i += batchSize) {
|
|
295
|
+
const batch = documents.slice(i, i + batchSize);
|
|
296
|
+
|
|
297
|
+
const embeddings = await generateEmbeddings(
|
|
298
|
+
batch.map(d => d.content)
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
const vectors = batch.map((doc, j) => ({
|
|
302
|
+
id: doc.id,
|
|
303
|
+
values: embeddings[j],
|
|
304
|
+
metadata: {
|
|
305
|
+
content: doc.content.substring(0, 1000), // Store truncated content
|
|
306
|
+
...doc.metadata,
|
|
307
|
+
},
|
|
308
|
+
}));
|
|
309
|
+
|
|
310
|
+
await index.namespace(namespace).upsert(vectors);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Semantic search
|
|
315
|
+
interface SearchResult {
|
|
316
|
+
id: string;
|
|
317
|
+
score: number;
|
|
318
|
+
content: string;
|
|
319
|
+
metadata?: Record<string, any>;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async function semanticSearch(
|
|
323
|
+
query: string,
|
|
324
|
+
indexName: string,
|
|
325
|
+
options: {
|
|
326
|
+
namespace?: string;
|
|
327
|
+
topK?: number;
|
|
328
|
+
filter?: Record<string, any>;
|
|
329
|
+
minScore?: number;
|
|
330
|
+
} = {}
|
|
331
|
+
): Promise<SearchResult[]> {
|
|
332
|
+
const {
|
|
333
|
+
namespace = 'default',
|
|
334
|
+
topK = 10,
|
|
335
|
+
filter,
|
|
336
|
+
minScore = 0.7,
|
|
337
|
+
} = options;
|
|
338
|
+
|
|
339
|
+
const [queryEmbedding] = await generateEmbeddings([query]);
|
|
340
|
+
|
|
341
|
+
const index = pinecone.index(indexName);
|
|
342
|
+
const results = await index.namespace(namespace).query({
|
|
343
|
+
vector: queryEmbedding,
|
|
344
|
+
topK,
|
|
345
|
+
filter,
|
|
346
|
+
includeMetadata: true,
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
return results.matches
|
|
350
|
+
?.filter(m => m.score && m.score >= minScore)
|
|
351
|
+
.map(match => ({
|
|
352
|
+
id: match.id,
|
|
353
|
+
score: match.score || 0,
|
|
354
|
+
content: match.metadata?.content as string || '',
|
|
355
|
+
metadata: match.metadata,
|
|
356
|
+
})) || [];
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### 4. RAG Implementation
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
interface RAGConfig {
|
|
364
|
+
indexName: string;
|
|
365
|
+
namespace?: string;
|
|
366
|
+
topK?: number;
|
|
367
|
+
model?: string;
|
|
368
|
+
systemPrompt?: string;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
class RAGSystem {
|
|
372
|
+
private config: RAGConfig;
|
|
373
|
+
|
|
374
|
+
constructor(config: RAGConfig) {
|
|
375
|
+
this.config = {
|
|
376
|
+
namespace: 'default',
|
|
377
|
+
topK: 5,
|
|
378
|
+
model: 'claude-sonnet-4-20250514',
|
|
379
|
+
systemPrompt: 'You are a helpful assistant. Answer based on the provided context.',
|
|
380
|
+
...config,
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
async query(question: string): Promise<RAGResponse> {
|
|
385
|
+
// Step 1: Retrieve relevant documents
|
|
386
|
+
const context = await semanticSearch(question, this.config.indexName, {
|
|
387
|
+
namespace: this.config.namespace,
|
|
388
|
+
topK: this.config.topK,
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
if (context.length === 0) {
|
|
392
|
+
return {
|
|
393
|
+
answer: "I couldn't find relevant information to answer your question.",
|
|
394
|
+
sources: [],
|
|
395
|
+
confidence: 0,
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Step 2: Build context string
|
|
400
|
+
const contextText = context
|
|
401
|
+
.map((doc, i) => `[${i + 1}] ${doc.content}`)
|
|
402
|
+
.join('\n\n');
|
|
403
|
+
|
|
404
|
+
// Step 3: Generate answer
|
|
405
|
+
const response = await anthropic.messages.create({
|
|
406
|
+
model: this.config.model!,
|
|
407
|
+
max_tokens: 2048,
|
|
408
|
+
system: `${this.config.systemPrompt}
|
|
409
|
+
|
|
410
|
+
Use the following context to answer the user's question. If the answer is not in the context, say so.
|
|
411
|
+
|
|
412
|
+
Context:
|
|
413
|
+
${contextText}`,
|
|
414
|
+
messages: [{
|
|
415
|
+
role: 'user',
|
|
416
|
+
content: question,
|
|
417
|
+
}],
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
const answer = response.content[0].type === 'text'
|
|
421
|
+
? response.content[0].text
|
|
422
|
+
: '';
|
|
423
|
+
|
|
424
|
+
return {
|
|
425
|
+
answer,
|
|
426
|
+
sources: context.map(c => ({
|
|
427
|
+
id: c.id,
|
|
428
|
+
content: c.content,
|
|
429
|
+
score: c.score,
|
|
430
|
+
})),
|
|
431
|
+
confidence: Math.max(...context.map(c => c.score)),
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Hybrid search (keyword + semantic)
|
|
436
|
+
async hybridQuery(
|
|
437
|
+
question: string,
|
|
438
|
+
keywords?: string[]
|
|
439
|
+
): Promise<RAGResponse> {
|
|
440
|
+
// Semantic search
|
|
441
|
+
const semanticResults = await semanticSearch(question, this.config.indexName, {
|
|
442
|
+
namespace: this.config.namespace,
|
|
443
|
+
topK: this.config.topK! * 2,
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
// Keyword filter (if provided)
|
|
447
|
+
let results = semanticResults;
|
|
448
|
+
if (keywords && keywords.length > 0) {
|
|
449
|
+
results = semanticResults.filter(r =>
|
|
450
|
+
keywords.some(k =>
|
|
451
|
+
r.content.toLowerCase().includes(k.toLowerCase())
|
|
452
|
+
)
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Rerank and take top K
|
|
457
|
+
const topResults = results.slice(0, this.config.topK);
|
|
458
|
+
|
|
459
|
+
// Generate answer using top results
|
|
460
|
+
return this.generateAnswer(question, topResults);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
private async generateAnswer(
|
|
464
|
+
question: string,
|
|
465
|
+
context: SearchResult[]
|
|
466
|
+
): Promise<RAGResponse> {
|
|
467
|
+
// ... same generation logic
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Usage
|
|
472
|
+
const rag = new RAGSystem({
|
|
473
|
+
indexName: 'knowledge-base',
|
|
474
|
+
topK: 5,
|
|
475
|
+
systemPrompt: 'You are a customer support agent. Be helpful and concise.',
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
const response = await rag.query('How do I reset my password?');
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
### 5. Structured Output
|
|
482
|
+
|
|
483
|
+
```typescript
|
|
484
|
+
import { z } from 'zod';
|
|
485
|
+
import { zodResponseFormat } from 'openai/helpers/zod';
|
|
486
|
+
|
|
487
|
+
// Define schema
|
|
488
|
+
const SentimentSchema = z.object({
|
|
489
|
+
sentiment: z.enum(['positive', 'negative', 'neutral']),
|
|
490
|
+
confidence: z.number().min(0).max(1),
|
|
491
|
+
topics: z.array(z.string()),
|
|
492
|
+
summary: z.string(),
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
type SentimentAnalysis = z.infer<typeof SentimentSchema>;
|
|
496
|
+
|
|
497
|
+
// Get structured output
|
|
498
|
+
async function analyzeSentiment(text: string): Promise<SentimentAnalysis> {
|
|
499
|
+
const response = await openai.beta.chat.completions.parse({
|
|
500
|
+
model: 'gpt-4o',
|
|
501
|
+
messages: [{
|
|
502
|
+
role: 'system',
|
|
503
|
+
content: 'Analyze the sentiment of the provided text.',
|
|
504
|
+
}, {
|
|
505
|
+
role: 'user',
|
|
506
|
+
content: text,
|
|
507
|
+
}],
|
|
508
|
+
response_format: zodResponseFormat(SentimentSchema, 'sentiment_analysis'),
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
return response.choices[0].message.parsed!;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// Claude tool use for structured output
|
|
515
|
+
async function extractEntities(text: string): Promise<{
|
|
516
|
+
people: string[];
|
|
517
|
+
organizations: string[];
|
|
518
|
+
locations: string[];
|
|
519
|
+
dates: string[];
|
|
520
|
+
}> {
|
|
521
|
+
const response = await anthropic.messages.create({
|
|
522
|
+
model: 'claude-sonnet-4-20250514',
|
|
523
|
+
max_tokens: 1024,
|
|
524
|
+
tools: [{
|
|
525
|
+
name: 'extract_entities',
|
|
526
|
+
description: 'Extract named entities from text',
|
|
527
|
+
input_schema: {
|
|
528
|
+
type: 'object',
|
|
529
|
+
properties: {
|
|
530
|
+
people: {
|
|
531
|
+
type: 'array',
|
|
532
|
+
items: { type: 'string' },
|
|
533
|
+
description: 'Names of people mentioned',
|
|
534
|
+
},
|
|
535
|
+
organizations: {
|
|
536
|
+
type: 'array',
|
|
537
|
+
items: { type: 'string' },
|
|
538
|
+
description: 'Organization names',
|
|
539
|
+
},
|
|
540
|
+
locations: {
|
|
541
|
+
type: 'array',
|
|
542
|
+
items: { type: 'string' },
|
|
543
|
+
description: 'Location names',
|
|
544
|
+
},
|
|
545
|
+
dates: {
|
|
546
|
+
type: 'array',
|
|
547
|
+
items: { type: 'string' },
|
|
548
|
+
description: 'Dates mentioned',
|
|
549
|
+
},
|
|
550
|
+
},
|
|
551
|
+
required: ['people', 'organizations', 'locations', 'dates'],
|
|
552
|
+
},
|
|
553
|
+
}],
|
|
554
|
+
tool_choice: { type: 'tool', name: 'extract_entities' },
|
|
555
|
+
messages: [{
|
|
556
|
+
role: 'user',
|
|
557
|
+
content: `Extract entities from: ${text}`,
|
|
558
|
+
}],
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
const toolUse = response.content.find(c => c.type === 'tool_use');
|
|
562
|
+
return toolUse?.input as any;
|
|
563
|
+
}
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
### 6. Production Patterns
|
|
567
|
+
|
|
568
|
+
```typescript
|
|
569
|
+
// Rate limiting and retry
|
|
570
|
+
import Bottleneck from 'bottleneck';
|
|
571
|
+
|
|
572
|
+
const limiter = new Bottleneck({
|
|
573
|
+
reservoir: 100, // Initial tokens
|
|
574
|
+
reservoirRefreshAmount: 100,
|
|
575
|
+
reservoirRefreshInterval: 60 * 1000, // Per minute
|
|
576
|
+
maxConcurrent: 10,
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
async function withRateLimit<T>(fn: () => Promise<T>): Promise<T> {
|
|
580
|
+
return limiter.schedule(fn);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// Retry with exponential backoff
|
|
584
|
+
async function withRetry<T>(
|
|
585
|
+
fn: () => Promise<T>,
|
|
586
|
+
maxRetries: number = 3,
|
|
587
|
+
baseDelay: number = 1000
|
|
588
|
+
): Promise<T> {
|
|
589
|
+
let lastError: Error;
|
|
590
|
+
|
|
591
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
592
|
+
try {
|
|
593
|
+
return await fn();
|
|
594
|
+
} catch (error) {
|
|
595
|
+
lastError = error as Error;
|
|
596
|
+
|
|
597
|
+
// Check if retryable
|
|
598
|
+
if (error.status === 429 || error.status >= 500) {
|
|
599
|
+
const delay = baseDelay * Math.pow(2, attempt);
|
|
600
|
+
await new Promise(r => setTimeout(r, delay));
|
|
601
|
+
continue;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
throw error;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
throw lastError!;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// Caching layer
|
|
612
|
+
const cache = new Map<string, { data: any; expires: number }>();
|
|
613
|
+
|
|
614
|
+
async function cachedEmbedding(
|
|
615
|
+
text: string,
|
|
616
|
+
ttl: number = 3600000 // 1 hour
|
|
617
|
+
): Promise<number[]> {
|
|
618
|
+
const key = `embedding:${hashString(text)}`;
|
|
619
|
+
const cached = cache.get(key);
|
|
620
|
+
|
|
621
|
+
if (cached && cached.expires > Date.now()) {
|
|
622
|
+
return cached.data;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
const [embedding] = await generateEmbeddings([text]);
|
|
626
|
+
cache.set(key, { data: embedding, expires: Date.now() + ttl });
|
|
627
|
+
|
|
628
|
+
return embedding;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// Cost tracking
|
|
632
|
+
class CostTracker {
|
|
633
|
+
private costs: Map<string, number> = new Map();
|
|
634
|
+
|
|
635
|
+
track(model: string, inputTokens: number, outputTokens: number): void {
|
|
636
|
+
const pricing = MODEL_PRICING[model] || { input: 0, output: 0 };
|
|
637
|
+
const cost = (inputTokens * pricing.input + outputTokens * pricing.output) / 1000;
|
|
638
|
+
|
|
639
|
+
const current = this.costs.get(model) || 0;
|
|
640
|
+
this.costs.set(model, current + cost);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
getReport(): Record<string, number> {
|
|
644
|
+
return Object.fromEntries(this.costs);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
getTotalCost(): number {
|
|
648
|
+
return Array.from(this.costs.values()).reduce((a, b) => a + b, 0);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
## Use Cases
|
|
654
|
+
|
|
655
|
+
### 1. Document Q&A System
|
|
656
|
+
|
|
657
|
+
```typescript
|
|
658
|
+
// Build document Q&A
|
|
659
|
+
async function buildDocumentQA(documents: string[]): Promise<RAGSystem> {
|
|
660
|
+
// Chunk documents
|
|
661
|
+
const chunks = documents.flatMap((doc, docIndex) =>
|
|
662
|
+
chunkText(doc, 500, 50).map((chunk, chunkIndex) => ({
|
|
663
|
+
id: `doc-${docIndex}-chunk-${chunkIndex}`,
|
|
664
|
+
content: chunk,
|
|
665
|
+
metadata: { documentIndex: docIndex },
|
|
666
|
+
}))
|
|
667
|
+
);
|
|
668
|
+
|
|
669
|
+
// Index chunks
|
|
670
|
+
await indexDocuments(chunks, 'document-qa');
|
|
671
|
+
|
|
672
|
+
// Return RAG system
|
|
673
|
+
return new RAGSystem({
|
|
674
|
+
indexName: 'document-qa',
|
|
675
|
+
topK: 5,
|
|
676
|
+
systemPrompt: 'Answer questions based on the provided documents.',
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
### 2. Content Moderation
|
|
682
|
+
|
|
683
|
+
```typescript
|
|
684
|
+
// Moderate content with AI
|
|
685
|
+
async function moderateContent(content: string): Promise<ModerationResult> {
|
|
686
|
+
const response = await openai.moderations.create({ input: content });
|
|
687
|
+
const result = response.results[0];
|
|
688
|
+
|
|
689
|
+
return {
|
|
690
|
+
flagged: result.flagged,
|
|
691
|
+
categories: Object.entries(result.categories)
|
|
692
|
+
.filter(([_, flagged]) => flagged)
|
|
693
|
+
.map(([category]) => category),
|
|
694
|
+
scores: result.category_scores,
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
## Best Practices
|
|
700
|
+
|
|
701
|
+
### Do's
|
|
702
|
+
|
|
703
|
+
- **Implement rate limiting** - Respect API limits
|
|
704
|
+
- **Cache embeddings** - Avoid redundant API calls
|
|
705
|
+
- **Handle errors gracefully** - Implement retry logic
|
|
706
|
+
- **Monitor costs** - Track token usage
|
|
707
|
+
- **Use streaming** - For better UX with long responses
|
|
708
|
+
- **Chunk appropriately** - Balance context vs. relevance
|
|
709
|
+
|
|
710
|
+
### Don'ts
|
|
711
|
+
|
|
712
|
+
- Don't expose API keys in frontend code
|
|
713
|
+
- Don't skip input validation
|
|
714
|
+
- Don't ignore rate limit errors
|
|
715
|
+
- Don't cache sensitive data inappropriately
|
|
716
|
+
- Don't use overly large context windows
|
|
717
|
+
- Don't forget fallback strategies
|
|
718
|
+
|
|
719
|
+
## Related Skills
|
|
720
|
+
|
|
721
|
+
- **api-architecture** - API design patterns
|
|
722
|
+
- **caching-strategies** - Caching for AI responses
|
|
723
|
+
- **backend-development** - Integration patterns
|
|
724
|
+
|
|
725
|
+
## Reference Resources
|
|
726
|
+
|
|
727
|
+
- [OpenAI API Reference](https://platform.openai.com/docs)
|
|
728
|
+
- [Anthropic API Reference](https://docs.anthropic.com/)
|
|
729
|
+
- [Pinecone Documentation](https://docs.pinecone.io/)
|
|
730
|
+
- [LangChain Documentation](https://js.langchain.com/)
|