@proveanything/smartlinks 1.3.16 → 1.3.17

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/docs/ai.md ADDED
@@ -0,0 +1,1565 @@
1
+ # SmartLinks AI
2
+
3
+ Complete guide to using AI capabilities in the SmartLinks SDK, including chat completions, RAG (Retrieval-Augmented Generation), and voice integration.
4
+
5
+ ---
6
+
7
+ ## Table of Contents
8
+
9
+ - [Overview](#overview)
10
+ - [Quick Start](#quick-start)
11
+ - [Authentication](#authentication)
12
+ - [Chat Completions](#chat-completions)
13
+ - [RAG: Product Assistants](#rag-product-assistants)
14
+ - [Voice Integration](#voice-integration)
15
+ - [Podcast Generation](#podcast-generation)
16
+ - [Type Definitions](#type-definitions)
17
+ - [API Reference](#api-reference)
18
+ - [Usage Examples](#usage-examples)
19
+ - [Error Handling](#error-handling)
20
+ - [Rate Limiting](#rate-limiting)
21
+ - [Best Practices](#best-practices)
22
+
23
+ ---
24
+
25
+ ## Overview
26
+
27
+ SmartLinks AI provides four main capabilities:
28
+
29
+ 1. **Chat Completions** - OpenAI-compatible text generation with streaming and tool calling
30
+ 2. **RAG (Retrieval-Augmented Generation)** - Document-grounded Q&A for product assistants
31
+ 3. **Voice Integration** - Voice-to-text and text-to-voice for hands-free interaction
32
+ 4. **Podcast Generation** - NotebookLM-style multi-voice conversational podcasts from documents
33
+
34
+ ### Key Features
35
+
36
+ - ✅ Full TypeScript support with type safety
37
+ - ✅ Streaming responses with async iterators
38
+ - ✅ Automatic rate limit handling
39
+ - ✅ Session management for conversations
40
+ - ✅ Voice input/output helpers
41
+ - ✅ Tool/function calling support
42
+ - ✅ Document indexing and retrieval
43
+ - ✅ Customizable assistant behavior
44
+
45
+ ---
46
+
47
+ ## Quick Start
48
+
49
+ ```typescript
50
+ import { initializeApi, ai } from '@proveanything/smartlinks';
51
+
52
+ // Initialize the SDK
53
+ initializeApi({
54
+ baseURL: 'https://smartlinks.app/api/v1',
55
+ apiKey: process.env.SMARTLINKS_API_KEY // Required for admin endpoints
56
+ });
57
+
58
+ // Simple chat completion
59
+ const response = await ai.chat.completions.create('my-collection', {
60
+ model: 'google/gemini-2.5-flash',
61
+ messages: [
62
+ { role: 'user', content: 'Hello!' }
63
+ ]
64
+ });
65
+
66
+ console.log(response.choices[0].message.content);
67
+ ```
68
+
69
+ ---
70
+
71
+ ## Authentication
72
+
73
+ ### Admin Endpoints
74
+
75
+ Admin endpoints require an API key passed during initialization:
76
+
77
+ ```typescript
78
+ initializeApi({
79
+ baseURL: 'https://smartlinks.app/api/v1',
80
+ apiKey: process.env.SMARTLINKS_API_KEY
81
+ });
82
+ ```
83
+
84
+ The SDK automatically includes the API key in the `Authorization: Bearer <token>` header.
85
+
86
+ ### Public Endpoints
87
+
88
+ Public endpoints don't require an API key but are rate-limited by `userId`:
89
+
90
+ ```typescript
91
+ // No API key needed
92
+ const response = await ai.publicApi.chat({
93
+ productId: 'coffee-maker',
94
+ userId: 'user-123',
95
+ message: 'How do I clean this?'
96
+ });
97
+ ```
98
+
99
+ ---
100
+
101
+ ## Chat Completions
102
+
103
+ OpenAI-compatible chat completions with streaming and tool calling support.
104
+
105
+ ### Basic Chat
106
+
107
+ ```typescript
108
+ const response = await ai.chat.completions.create('my-collection', {
109
+ model: 'google/gemini-2.5-flash',
110
+ messages: [
111
+ { role: 'system', content: 'You are a helpful assistant.' },
112
+ { role: 'user', content: 'What is the capital of France?' }
113
+ ]
114
+ });
115
+
116
+ console.log(response.choices[0].message.content);
117
+ // Output: "The capital of France is Paris."
118
+ ```
119
+
120
+ ### Streaming Chat
121
+
122
+ Stream responses in real-time for better UX:
123
+
124
+ ```typescript
125
+ const stream = await ai.chat.completions.create('my-collection', {
126
+ model: 'google/gemini-2.5-flash',
127
+ messages: [
128
+ { role: 'user', content: 'Write a short poem about coding' }
129
+ ],
130
+ stream: true
131
+ });
132
+
133
+ for await (const chunk of stream) {
134
+ const content = chunk.choices[0]?.delta?.content || '';
135
+ process.stdout.write(content);
136
+ }
137
+ ```
138
+
139
+ ### Tool/Function Calling
140
+
141
+ Define tools (functions) that the AI can call:
142
+
143
+ ```typescript
144
+ const tools = [
145
+ {
146
+ type: 'function',
147
+ function: {
148
+ name: 'get_weather',
149
+ description: 'Get the current weather for a location',
150
+ parameters: {
151
+ type: 'object',
152
+ properties: {
153
+ location: {
154
+ type: 'string',
155
+ description: 'City name'
156
+ },
157
+ unit: {
158
+ type: 'string',
159
+ enum: ['celsius', 'fahrenheit']
160
+ }
161
+ },
162
+ required: ['location']
163
+ }
164
+ }
165
+ }
166
+ ];
167
+
168
+ const response = await ai.chat.completions.create('my-collection', {
169
+ model: 'google/gemini-2.5-flash',
170
+ messages: [
171
+ { role: 'user', content: 'What\'s the weather in Paris?' }
172
+ ],
173
+ tools
174
+ });
175
+
176
+ const toolCall = response.choices[0].message.tool_calls?.[0];
177
+ if (toolCall) {
178
+ console.log('Function:', toolCall.function.name);
179
+ console.log('Arguments:', JSON.parse(toolCall.function.arguments));
180
+ // { location: "Paris", unit: "celsius" }
181
+ }
182
+ ```
183
+
184
+ ### Available Models
185
+
186
+ ```typescript
187
+ // List all available models
188
+ const models = await ai.models.list('my-collection');
189
+
190
+ models.data.forEach(model => {
191
+ console.log(`${model.name}`);
192
+ console.log(` Provider: ${model.provider}`);
193
+ console.log(` Context: ${model.contextWindow} tokens`);
194
+ console.log(` Pricing: $${model.pricing.input}/1M input tokens`);
195
+ });
196
+
197
+ // Get specific model info
198
+ const model = await ai.models.get('my-collection', 'google/gemini-2.5-flash');
199
+ console.log(model.capabilities); // ['text', 'vision', 'audio', 'code']
200
+ ```
201
+
202
+ **Recommended Models:**
203
+
204
+ | Model | Use Case | Speed | Cost |
205
+ |-------|----------|-------|------|
206
+ | `google/gemini-2.5-flash-lite` | Simple Q&A | Fastest | Lowest |
207
+ | `google/gemini-2.5-flash` | General purpose | Fast | Low |
208
+ | `google/gemini-2.5-pro` | Complex reasoning | Slower | Higher |
209
+
210
+ ---
211
+
212
+ ## RAG: Product Assistants
213
+
214
+ Create intelligent product assistants that answer questions based on product documentation.
215
+
216
+ ### Setup: Index Documents
217
+
218
+ First, index your product documentation:
219
+
220
+ ```typescript
221
+ // Index a product manual from URL
222
+ const result = await ai.rag.indexDocument('my-collection', {
223
+ productId: 'coffee-maker-deluxe',
224
+ documentUrl: 'https://example.com/manuals/coffee-maker.pdf',
225
+ chunkSize: 500, // Tokens per chunk
226
+ overlap: 50, // Token overlap between chunks
227
+ provider: 'openai' // Embedding provider
228
+ });
229
+
230
+ console.log(`Indexed ${result.chunks} chunks`);
231
+ console.log(`Dimensions: ${result.metadata.embeddingDimensions}`);
232
+
233
+ // Or index from text directly
234
+ await ai.rag.indexDocument('my-collection', {
235
+ productId: 'coffee-maker-deluxe',
236
+ text: 'Your product manual content here...',
237
+ metadata: {
238
+ source: 'manual',
239
+ version: '2.0'
240
+ }
241
+ });
242
+ ```
243
+
244
+ ### Configure Assistant
245
+
246
+ Customize the assistant's behavior:
247
+
248
+ ```typescript
249
+ await ai.rag.configureAssistant('my-collection', {
250
+ productId: 'coffee-maker-deluxe',
251
+ systemPrompt: 'You are a helpful coffee maker assistant. Be concise and friendly.',
252
+ model: 'google/gemini-2.5-flash',
253
+ temperature: 0.7,
254
+ maxTokensPerResponse: 500,
255
+ rateLimitPerUser: 20,
256
+ allowedTopics: ['usage', 'cleaning', 'troubleshooting'],
257
+ customInstructions: {
258
+ tone: 'friendly',
259
+ additionalRules: 'Always include safety warnings when relevant.'
260
+ }
261
+ });
262
+ ```
263
+
264
+ ### Public Chat
265
+
266
+ Users can chat with the product assistant without authentication:
267
+
268
+ ```typescript
269
+ // First question
270
+ const response = await ai.publicApiApi.chat('my-collection', {
271
+ productId: 'coffee-maker-deluxe',
272
+ userId: 'user-123',
273
+ message: 'How do I descale my coffee maker?'
274
+ });
275
+
276
+ console.log('Answer:', response.message);
277
+ console.log('Used', response.context.chunksUsed, 'document sections');
278
+ console.log('Top similarity:', response.context.topSimilarity);
279
+ ```
280
+
281
+ ### Conversation History
282
+
283
+ Maintain conversation context with sessions:
284
+
285
+ ```typescript
286
+ const sessionId = `session-${Date.now()}`;
287
+
288
+ // First question
289
+ const q1 = await ai.public.chat('my-collection', {
290
+ productId: 'coffee-maker-deluxe',
291
+ userId: 'user-123',
292
+ message: 'How do I clean it?',
293
+ sessionId
294
+ });
295
+
296
+ // Follow-up question (uses history)
297
+ const q2 = await ai.public.chat('my-collection', {
298
+ productId: 'coffee-maker-deluxe',
299
+ userId: 'user-123',
300
+ message: 'How often should I do that?',
301
+ sessionId
302
+ });
303
+
304
+ // Get full conversation history
305
+ const session = await ai.public.getSession('my-collection', sessionId);
306
+ console.log('Messages:', session.messages);
307
+ console.log('Total messages:', session.messageCount);
308
+
309
+ // Clear session when done
310
+ await ai.public.clearSession('my-collection', sessionId);
311
+ ```
312
+
313
+ ### Session Management
314
+
315
+ ```typescript
316
+ // Get session statistics (admin)
317
+ const stats = await ai.sessions.stats('my-collection');
318
+ console.log('Total sessions:', stats.totalSessions);
319
+ console.log('Active sessions:', stats.activeSessions);
320
+ console.log('Total messages:', stats.totalMessages);
321
+ console.log('Rate-limited users:', stats.rateLimitedUsers);
322
+ ```
323
+
324
+ ---
325
+
326
+ ## Voice Integration
327
+
328
+ Enable voice input and output for hands-free interaction.
329
+
330
+ ### Browser Voice Helpers
331
+
332
+ ```typescript
333
+ // Check if voice is supported
334
+ if (ai.voice.isSupported()) {
335
+ // Listen for voice input
336
+ const question = await ai.voice.listen('en-US');
337
+ console.log('User said:', question);
338
+
339
+ // Get answer from AI
340
+ const response = await ai.public.chat('my-collection', {
341
+ productId: 'coffee-maker-deluxe',
342
+ userId: 'user-123',
343
+ message: question
344
+ });
345
+
346
+ // Speak the answer
347
+ await ai.voice.speak(response.message, {
348
+ voice: 'alloy',
349
+ rate: 1.0
350
+ });
351
+ }
352
+ ```
353
+
354
+ ### Voice Assistant Class
355
+
356
+ Create a complete voice assistant:
357
+
358
+ ```typescript
359
+ class ProductVoiceAssistant {
360
+ private collectionId: string;
361
+ private productId: string;
362
+ private userId: string;
363
+ private sessionId: string;
364
+
365
+ constructor(config: {
366
+ collectionId: string;
367
+ productId: string;
368
+ userId: string;
369
+ }) {
370
+ this.collectionId = config.collectionId;
371
+ this.productId = config.productId;
372
+ this.userId = config.userId;
373
+ this.sessionId = `voice-${Date.now()}`;
374
+ }
375
+
376
+ async ask(): Promise<string> {
377
+ // Listen for question
378
+ console.log('Listening...');
379
+ const question = await ai.voice.listen();
380
+
381
+ // Get answer
382
+ console.log('Processing...');
383
+ const response = await ai.public.chat(this.collectionId, {
384
+ productId: this.productId,
385
+ userId: this.userId,
386
+ message: question,
387
+ sessionId: this.sessionId
388
+ });
389
+
390
+ // Speak answer
391
+ console.log('Speaking...');
392
+ await ai.voice.speak(response.message);
393
+
394
+ return response.message;
395
+ }
396
+
397
+ async getRemainingQuestions(): Promise<number> {
398
+ const status = await ai.publicApiApi.getRateLimit(this.collectionId, this.userId);
399
+ return status.remaining;
400
+ }
401
+ }
402
+
403
+ // Usage
404
+ const assistant = new ProductVoiceAssistant({
405
+ collectionId: 'my-collection',
406
+ productId: 'coffee-maker-deluxe',
407
+ userId: 'user-123'
408
+ });
409
+
410
+ await assistant.ask(); // Voice question → Voice answer
411
+ const remaining = await assistant.getRemainingQuestions();
412
+ console.log(`${remaining} questions remaining`);
413
+ ```
414
+
415
+ ### Gemini Live Integration
416
+
417
+ Generate ephemeral tokens for Gemini Live (multimodal voice):
418
+
419
+ ```typescript
420
+ // Generate token for voice session
421
+ const token = await ai.publicApi.getToken('my-collection', {
422
+ settings: {
423
+ ttl: 3600, // 1 hour
424
+ voice: 'alloy',
425
+ language: 'en-US'
426
+ }
427
+ });
428
+
429
+ console.log('Token:', token.token);
430
+ console.log('Expires at:', new Date(token.expiresAt));
431
+
432
+ // Use token with Gemini Live API
433
+ // (See Google's Gemini documentation)
434
+ ```
435
+
436
+ ---
437
+
438
+ ## Podcast Generation
439
+
440
+ Generate NotebookLM-style multi-voice conversational podcasts from product documentation.
441
+
442
+ ### Generate a Podcast
443
+
444
+ ```typescript
445
+ const podcast = await ai.podcast.generate('my-collection', {
446
+ productId: 'coffee-maker-deluxe',
447
+ duration: 5, // Target 5 minutes
448
+ style: 'casual', // 'casual' | 'professional' | 'educational' | 'entertaining'
449
+ voices: {
450
+ host1: 'nova', // Female voice
451
+ host2: 'onyx' // Male voice
452
+ },
453
+ includeAudio: true // Generate audio files
454
+ });
455
+
456
+ console.log('Podcast Title:', podcast.script.title);
457
+ console.log('Duration:', podcast.metadata.duration, 'seconds');
458
+ console.log('Download:', podcast.audio?.mixedUrl);
459
+ ```
460
+
461
+ ### Available Voices
462
+
463
+ | Voice | Gender | Personality | Best For |
464
+ |-------|--------|-------------|----------|
465
+ | `alloy` | Neutral | Balanced, neutral | Professional podcasts |
466
+ | `echo` | Male | Clear, authoritative | Expert/teacher role |
467
+ | `fable` | Neutral | Warm, storytelling | Narrative content |
468
+ | `onyx` | Male | Deep, engaging | Main host, discussions |
469
+ | `nova` | Female | Friendly, enthusiastic | Co-host, questions |
470
+ | `shimmer` | Female | Bright, energetic | Entertaining content |
471
+
472
+ **Recommended Combinations:**
473
+ - **Casual**: Nova + Onyx - Friendly and engaging
474
+ - **Professional**: Alloy + Echo - Authoritative and clear
475
+ - **Educational**: Fable + Echo - Teaching style
476
+ - **Entertaining**: Shimmer + Onyx - High energy
477
+
478
+ ### Access the Script
479
+
480
+ ```typescript
481
+ // View the generated script
482
+ podcast.script.segments.forEach((segment, i) => {
483
+ const speaker = segment.speaker === 'host1' ? 'Host 1' : 'Host 2';
484
+ console.log(`${speaker}: ${segment.text}`);
485
+ });
486
+ ```
487
+
488
+ ### Check Generation Status
489
+
490
+ For long-running podcast generation, poll for status:
491
+
492
+ ```typescript
493
+ // Start generation
494
+ const podcast = await ai.podcast.generate('my-collection', {
495
+ productId: 'coffee-maker-deluxe',
496
+ duration: 10,
497
+ includeAudio: true
498
+ });
499
+
500
+ // Poll for status
501
+ const checkStatus = async () => {
502
+ const status = await ai.podcast.getStatus('my-collection', podcast.podcastId);
503
+
504
+ console.log(`Status: ${status.status} (${status.progress}%)`);
505
+
506
+ if (status.status === 'completed' && status.result) {
507
+ console.log('Podcast ready!');
508
+ console.log('Listen:', status.result.audio?.mixedUrl);
509
+ return true;
510
+ } else if (status.status === 'failed') {
511
+ console.error('Generation failed:', status.error);
512
+ return true;
513
+ }
514
+
515
+ return false;
516
+ };
517
+
518
+ // Check every 5 seconds
519
+ const interval = setInterval(async () => {
520
+ const done = await checkStatus();
521
+ if (done) clearInterval(interval);
522
+ }, 5000);
523
+ ```
524
+
525
+ ### Text-to-Speech (TTS)
526
+
527
+ Generate custom audio from text:
528
+
529
+ ```typescript
530
+ const audioBlob = await ai.tts.generate('my-collection', {
531
+ text: 'Welcome to our podcast about coffee makers!',
532
+ voice: 'nova',
533
+ speed: 1.0,
534
+ format: 'mp3'
535
+ });
536
+
537
+ // Create audio URL for playback
538
+ const audioUrl = URL.createObjectURL(audioBlob);
539
+ ```
540
+
541
+ ---
542
+
543
+ ## Type Definitions
544
+
545
+ ### Core Types
546
+
547
+ ```typescript
548
+ /**
549
+ * Chat message with role and content
550
+ */
551
+ interface ChatMessage {
552
+ role: 'system' | 'user' | 'assistant' | 'function' | 'tool';
553
+ content: string | ContentPart[];
554
+ name?: string;
555
+ function_call?: FunctionCall;
556
+ tool_calls?: ToolCall[];
557
+ tool_call_id?: string;
558
+ }
559
+
560
+ /**
561
+ * Chat completion request
562
+ */
563
+ interface ChatCompletionRequest {
564
+ messages: ChatMessage[];
565
+ model?: string;
566
+ stream?: boolean;
567
+ tools?: ToolDefinition[];
568
+ tool_choice?: 'none' | 'auto' | 'required' | { type: 'function'; function: { name: string } };
569
+ temperature?: number; // 0-2, default: 0.7
570
+ max_tokens?: number;
571
+ top_p?: number;
572
+ frequency_penalty?: number;
573
+ presence_penalty?: number;
574
+ response_format?: { type: 'text' | 'json_object' };
575
+ user?: string;
576
+ }
577
+
578
+ /**
579
+ * Chat completion response
580
+ */
581
+ interface ChatCompletionResponse {
582
+ id: string;
583
+ object: 'chat.completion';
584
+ created: number;
585
+ model: string;
586
+ choices: ChatCompletionChoice[];
587
+ usage: {
588
+ prompt_tokens: number;
589
+ completion_tokens: number;
590
+ total_tokens: number;
591
+ };
592
+ }
593
+
594
+ /**
595
+ * Streaming chunk
596
+ */
597
+ interface ChatCompletionChunk {
598
+ id: string;
599
+ object: 'chat.completion.chunk';
600
+ created: number;
601
+ model: string;
602
+ choices: Array<{
603
+ index: number;
604
+ delta: Partial<ChatMessage>;
605
+ finish_reason: string | null;
606
+ }>;
607
+ }
608
+ ```
609
+
610
+ ### RAG Types
611
+
612
+ ```typescript
613
+ /**
614
+ * Index document request
615
+ */
616
+ interface IndexDocumentRequest {
617
+ productId: string;
618
+ text?: string; // Either text or documentUrl required
619
+ documentUrl?: string;
620
+ metadata?: Record<string, any>;
621
+ chunkSize?: number; // Default: 500
622
+ overlap?: number; // Default: 50
623
+ provider?: 'openai' | 'gemini';
624
+ }
625
+
626
+ /**
627
+ * Configure assistant request
628
+ */
629
+ interface ConfigureAssistantRequest {
630
+ productId: string;
631
+ systemPrompt?: string;
632
+ model?: string;
633
+ maxTokensPerResponse?: number;
634
+ temperature?: number;
635
+ rateLimitPerUser?: number;
636
+ allowedTopics?: string[];
637
+ customInstructions?: Record<string, any>;
638
+ }
639
+
640
+ /**
641
+ * Public chat request
642
+ */
643
+ interface PublicChatRequest {
644
+ productId: string;
645
+ userId: string;
646
+ message: string;
647
+ sessionId?: string;
648
+ stream?: boolean;
649
+ }
650
+
651
+ /**
652
+ * Public chat response
653
+ */
654
+ interface PublicChatResponse {
655
+ message: string;
656
+ sessionId: string;
657
+ usage: {
658
+ prompt_tokens: number;
659
+ completion_tokens: number;
660
+ total_tokens: number;
661
+ };
662
+ context?: {
663
+ chunksUsed: number;
664
+ topSimilarity: number;
665
+ };
666
+ }
667
+ ```
668
+
669
+ ### Podcast Types
670
+
671
+ ```typescript
672
+ /**
673
+ * Podcast generation request
674
+ */
675
+ interface GeneratePodcastRequest {
676
+ productId: string;
677
+ documentText?: string; // Optional if document already indexed
678
+ duration?: number; // Target duration in minutes (default: 10)
679
+ style?: 'casual' | 'professional' | 'educational' | 'entertaining';
680
+ voices?: {
681
+ host1?: string; // Voice name for first host
682
+ host2?: string; // Voice name for second host
683
+ };
684
+ includeAudio?: boolean; // Generate audio files (default: false)
685
+ language?: string; // Default: 'en-US'
686
+ customInstructions?: string;
687
+ }
688
+
689
+ /**
690
+ * Podcast script segment
691
+ */
692
+ interface PodcastSegment {
693
+ speaker: 'host1' | 'host2';
694
+ text: string;
695
+ timestamp?: number; // Start time in seconds
696
+ duration?: number; // Segment duration
697
+ }
698
+
699
+ /**
700
+ * Podcast script
701
+ */
702
+ interface PodcastScript {
703
+ title: string;
704
+ description: string;
705
+ segments: PodcastSegment[];
706
+ }
707
+
708
+ /**
709
+ * Podcast generation response
710
+ */
711
+ interface GeneratePodcastResponse {
712
+ success: boolean;
713
+ podcastId: string;
714
+ script: PodcastScript;
715
+ audio?: {
716
+ host1Url?: string; // URL to download host 1 audio
717
+ host2Url?: string; // URL to download host 2 audio
718
+ mixedUrl?: string; // URL to download mixed podcast
719
+ };
720
+ metadata: {
721
+ duration: number; // Actual duration in seconds
722
+ wordCount: number;
723
+ generatedAt: string;
724
+ };
725
+ }
726
+
727
+ /**
728
+ * Podcast status
729
+ */
730
+ interface PodcastStatus {
731
+ podcastId: string;
732
+ status: 'generating_script' | 'generating_audio' | 'mixing' | 'completed' | 'failed';
733
+ progress: number; // 0-100
734
+ estimatedTimeRemaining?: number; // Seconds
735
+ error?: string;
736
+ result?: GeneratePodcastResponse; // Available when completed
737
+ }
738
+
739
+ /**
740
+ * TTS request
741
+ */
742
+ interface TTSRequest {
743
+ text: string;
744
+ voice?: 'alloy' | 'echo' | 'fable' | 'onyx' | 'nova' | 'shimmer';
745
+ speed?: number; // 0.25 - 4.0, default: 1.0
746
+ format?: 'mp3' | 'opus' | 'aac' | 'flac'; // Default: mp3
747
+ }
748
+ ```
749
+
750
+ ### Error Types
751
+
752
+ ```typescript
753
+ /**
754
+ * API Error response
755
+ */
756
+ interface AIError {
757
+ error: {
758
+ message: string;
759
+ type: string;
760
+ code: string;
761
+ param?: string;
762
+ resetAt?: string; // ISO 8601 timestamp (for rate limits)
763
+ };
764
+ }
765
+
766
+ /**
767
+ * Custom error class
768
+ */
769
+ class SmartLinksAIError extends Error {
770
+ type: string;
771
+ code: string;
772
+ statusCode: number;
773
+ resetAt?: string;
774
+
775
+ isRateLimitError(): boolean;
776
+ isAuthError(): boolean;
777
+ isNotFoundError(): boolean;
778
+ }
779
+ ```
780
+
781
+ ---
782
+
783
+ ## API Reference
784
+
785
+ ### Admin Endpoints
786
+
787
+ #### `ai.chat.completions.create(collectionId, request)`
788
+
789
+ Create a chat completion (OpenAI-compatible).
790
+
791
+ **Parameters:**
792
+ - `collectionId` (string) - Collection ID
793
+ - `request` (ChatCompletionRequest) - Request parameters
794
+
795
+ **Returns:** `Promise<ChatCompletionResponse | AsyncIterable<ChatCompletionChunk>>`
796
+
797
+ **Example:**
798
+ ```typescript
799
+ const response = await ai.chat.completions.create('my-collection', {
800
+ model: 'google/gemini-2.5-flash',
801
+ messages: [{ role: 'user', content: 'Hello!' }]
802
+ });
803
+ ```
804
+
805
+ ---
806
+
807
+ #### `ai.models.list(collectionId)`
808
+
809
+ List available AI models.
810
+
811
+ **Returns:** `Promise<ModelList>`
812
+
813
+ ---
814
+
815
+ #### `ai.models.get(collectionId, modelId)`
816
+
817
+ Get specific model information.
818
+
819
+ **Parameters:**
820
+ - `modelId` (string) - Model identifier (e.g., 'google/gemini-2.5-flash')
821
+
822
+ **Returns:** `Promise<AIModel>`
823
+
824
+ ---
825
+
826
+ #### `ai.rag.indexDocument(collectionId, request)`
827
+
828
+ Index a document for RAG.
829
+
830
+ **Parameters:**
831
+ - `request` (IndexDocumentRequest) - Document and indexing parameters
832
+
833
+ **Returns:** `Promise<IndexDocumentResponse>`
834
+
835
+ ---
836
+
837
+ #### `ai.rag.configureAssistant(collectionId, request)`
838
+
839
+ Configure AI assistant behavior.
840
+
841
+ **Parameters:**
842
+ - `request` (ConfigureAssistantRequest) - Assistant configuration
843
+
844
+ **Returns:** `Promise<ConfigureAssistantResponse>`
845
+
846
+ ---
847
+
848
+ #### `ai.sessions.stats(collectionId)`
849
+
850
+ Get session statistics.
851
+
852
+ **Returns:** `Promise<SessionStatistics>`
853
+
854
+ ---
855
+
856
+ #### `ai.rateLimit.reset(collectionId, userId)`
857
+
858
+ Reset rate limit for a user.
859
+
860
+ **Returns:** `Promise<{ success: boolean; userId: string }>`
861
+
862
+ ---
863
+
864
+ #### `ai.podcast.generate(collectionId, request)`
865
+
866
+ Generate a NotebookLM-style conversational podcast.
867
+
868
+ **Parameters:**
869
+ - `request` (GeneratePodcastRequest) - Podcast generation parameters
870
+
871
+ **Returns:** `Promise<GeneratePodcastResponse>`
872
+
873
+ **Example:**
874
+ ```typescript
875
+ const podcast = await ai.podcast.generate('my-collection', {
876
+ productId: 'coffee-maker-deluxe',
877
+ duration: 5,
878
+ style: 'casual',
879
+ voices: { host1: 'nova', host2: 'onyx' },
880
+ includeAudio: true
881
+ });
882
+ ```
883
+
884
+ ---
885
+
886
+ #### `ai.podcast.getStatus(collectionId, podcastId)`
887
+
888
+ Get podcast generation status.
889
+
890
+ **Parameters:**
891
+ - `podcastId` (string) - Podcast identifier
892
+
893
+ **Returns:** `Promise<PodcastStatus>`
894
+
895
+ ---
896
+
897
+ #### `ai.tts.generate(collectionId, request)`
898
+
899
+ Generate text-to-speech audio.
900
+
901
+ **Parameters:**
902
+ - `request` (TTSRequest) - TTS parameters
903
+
904
+ **Returns:** `Promise<Blob>`
905
+
906
+ **Example:**
907
+ ```typescript
908
+ const audioBlob = await ai.tts.generate('my-collection', {
909
+ text: 'Welcome to our podcast!',
910
+ voice: 'nova',
911
+ speed: 1.0
912
+ });
913
+ ```
914
+
915
+ ---
916
+
917
+ ### Public Endpoints
918
+
919
+ #### `ai.publicApi.chat(collectionId, request)`
920
+
921
+ Chat with product assistant (no auth required).
922
+
923
+ **Parameters:**
924
+ - `request` (PublicChatRequest) - Chat parameters
925
+
926
+ **Returns:** `Promise<PublicChatResponse>`
927
+
928
+ **Rate Limited:** Yes (20 requests/hour per userId by default)
929
+
930
+ ---
931
+
932
+ #### `ai.publicApi.getSession(collectionId, sessionId)`
933
+
934
+ Get conversation history.
935
+
936
+ **Returns:** `Promise<Session>`
937
+
938
+ ---
939
+
940
+ #### `ai.publicApi.clearSession(collectionId, sessionId)`
941
+
942
+ Clear conversation history.
943
+
944
+ **Returns:** `Promise<{ success: boolean }>`
945
+
946
+ ---
947
+
948
+ #### `ai.publicApi.getRateLimit(collectionId, userId)`
949
+
950
+ Check rate limit status.
951
+
952
+ **Returns:** `Promise<RateLimitStatus>`
953
+
954
+ ---
955
+
956
+ #### `ai.publicApi.getToken(collectionId, request)`
957
+
958
+ Generate ephemeral token for Gemini Live.
959
+
960
+ **Returns:** `Promise<EphemeralTokenResponse>`
961
+
962
+ ---
963
+
964
+ ## Usage Examples
965
+
966
+ ### Example 1: Product FAQ Bot
967
+
968
+ ```typescript
969
+ async function createProductFAQ() {
970
+ const collectionId = 'my-collection';
971
+ const productId = 'coffee-maker-deluxe';
972
+
973
+ // 1. Index product documentation
974
+ await ai.rag.indexDocument(collectionId, {
975
+ productId,
976
+ documentUrl: 'https://example.com/manual.pdf'
977
+ });
978
+
979
+ // 2. Configure assistant
980
+ await ai.rag.configureAssistant(collectionId, {
981
+ productId,
982
+ systemPrompt: 'You are a coffee maker expert. Provide clear, step-by-step instructions.',
983
+ rateLimitPerUser: 30
984
+ });
985
+
986
+ // 3. Answer user questions
987
+ const answer = await ai.publicApi.chat(collectionId, {
988
+ productId,
989
+ userId: 'user-123',
990
+ message: 'How do I make espresso?'
991
+ });
992
+
993
+ console.log(answer.message);
994
+ }
995
+ ```
996
+
997
+ ### Example 2: Streaming Chatbot UI
998
+
999
+ ```typescript
1000
+ async function streamingChatbot(userMessage: string) {
1001
+ const stream = await ai.chat.completions.create('my-collection', {
1002
+ model: 'google/gemini-2.5-flash',
1003
+ messages: [
1004
+ { role: 'system', content: 'You are a helpful assistant.' },
1005
+ { role: 'user', content: userMessage }
1006
+ ],
1007
+ stream: true
1008
+ });
1009
+
1010
+ let fullResponse = '';
1011
+
1012
+ for await (const chunk of stream) {
1013
+ const content = chunk.choices[0]?.delta?.content || '';
1014
+ fullResponse += content;
1015
+
1016
+ // Update UI in real-time
1017
+ updateChatUI(content);
1018
+ }
1019
+
1020
+ return fullResponse;
1021
+ }
1022
+ ```
1023
+
1024
+ ### Example 3: Multi-Turn Conversation
1025
+
1026
+ ```typescript
1027
+ async function chatConversation() {
1028
+ const collectionId = 'my-collection';
1029
+ const sessionId = `chat-${Date.now()}`;
1030
+ const userId = 'user-123';
1031
+ const productId = 'coffee-maker-deluxe';
1032
+
1033
+ // Question 1
1034
+ const a1 = await ai.publicApi.chat(collectionId, {
1035
+ productId,
1036
+ userId,
1037
+ message: 'How do I clean the machine?',
1038
+ sessionId
1039
+ });
1040
+ console.log('A1:', a1.message);
1041
+
1042
+ // Question 2 (references previous context)
1043
+ const a2 = await ai.publicApi.chat(collectionId, {
1044
+ productId,
1045
+ userId,
1046
+ message: 'How often should I do that?',
1047
+ sessionId
1048
+ });
1049
+ console.log('A2:', a2.message);
1050
+
1051
+ // Get full history
1052
+ const session = await ai.publicApi.getSession(collectionId, sessionId);
1053
+ console.log('Full conversation:', session.messages);
1054
+ }
1055
+ ```
1056
+
1057
+ ### Example 4: React Hook for Product Assistant
1058
+
1059
+ ```typescript
1060
+ import { useState, useCallback } from 'react';
1061
+ import { ai } from '@proveanything/smartlinks';
1062
+
1063
+ export function useProductAssistant(
1064
+ collectionId: string,
1065
+ productId: string,
1066
+ userId: string
1067
+ ) {
1068
+ const [loading, setLoading] = useState(false);
1069
+ const [error, setError] = useState<string | null>(null);
1070
+ const [rateLimit, setRateLimit] = useState({ remaining: 20, limit: 20 });
1071
+
1072
+ const ask = useCallback(async (message: string) => {
1073
+ setLoading(true);
1074
+ setError(null);
1075
+
1076
+ try {
1077
+ const response = await ai.publicApi.chat(collectionId, {
1078
+ productId,
1079
+ userId,
1080
+ message
1081
+ });
1082
+
1083
+ setRateLimit(prev => ({
1084
+ ...prev,
1085
+ remaining: prev.remaining - 1
1086
+ }));
1087
+
1088
+ return response.message;
1089
+ } catch (err: any) {
1090
+ setError(err.message);
1091
+ throw err;
1092
+ } finally {
1093
+ setLoading(false);
1094
+ }
1095
+ }, [collectionId, productId, userId]);
1096
+
1097
+ return { ask, loading, error, rateLimit };
1098
+ }
1099
+
1100
+ // Usage in component
1101
+ function ProductHelp() {
1102
+ const { ask, loading, rateLimit } = useProductAssistant(
1103
+ 'my-collection',
1104
+ 'coffee-maker',
1105
+ 'user-123'
1106
+ );
1107
+ const [answer, setAnswer] = useState('');
1108
+
1109
+ const handleAsk = async () => {
1110
+ const response = await ask('How do I clean this?');
1111
+ setAnswer(response);
1112
+ };
1113
+
1114
+ return (
1115
+ <div>
1116
+ <button onClick={handleAsk} disabled={loading}>
1117
+ {loading ? 'Asking...' : 'Ask Question'}
1118
+ </button>
1119
+ {answer && <p>{answer}</p>}
1120
+ <p>{rateLimit.remaining} questions remaining</p>
1121
+ </div>
1122
+ );
1123
+ }
1124
+ ```
1125
+
1126
+ ### Example 5: Voice Q&A
1127
+
1128
+ ```typescript
1129
+ async function voiceQA() {
1130
+ if (!ai.voice.isSupported()) {
1131
+ console.error('Voice not supported in this browser');
1132
+ return;
1133
+ }
1134
+
1135
+ console.log('Speak your question...');
1136
+
1137
+ // Listen for voice input
1138
+ const question = await ai.voice.listen('en-US');
1139
+ console.log('You asked:', question);
1140
+
1141
+ // Get answer from AI
1142
+ const response = await ai.public.chat('my-collection', {
1143
+ productId: 'coffee-maker-deluxe',
1144
+ userId: 'user-123',
1145
+ message: question
1146
+ });
1147
+
1148
+ // Display answer
1149
+ console.log('Answer:', response.message);
1150
+
1151
+ // Speak answer
1152
+ await ai.voice.speak(response.message);
1153
+ }
1154
+ ```
1155
+
1156
+ ### Example 6: Generate Product Podcast
1157
+
1158
+ ```typescript
1159
+ async function generateProductPodcast() {
1160
+ // Generate a casual 5-minute podcast about the coffee maker
1161
+ const podcast = await ai.podcast.generate('my-collection', {
1162
+ productId: 'coffee-maker-deluxe',
1163
+ duration: 5, // minutes
1164
+ style: 'casual', // Conversational style
1165
+ voices: {
1166
+ host1: 'nova', // Female voice
1167
+ host2: 'onyx' // Male voice
1168
+ },
1169
+ includeAudio: true
1170
+ });
1171
+
1172
+ console.log('Podcast Title:', podcast.script.title);
1173
+ console.log('Duration:', podcast.metadata.duration, 'seconds');
1174
+
1175
+ // Display script
1176
+ console.log('\nScript:');
1177
+ podcast.script.segments.forEach((segment, i) => {
1178
+ const speaker = segment.speaker === 'host1' ? 'Host 1' : 'Host 2';
1179
+ console.log(`\n${speaker}: ${segment.text}`);
1180
+ });
1181
+
1182
+ // Download audio
1183
+ if (podcast.audio?.mixedUrl) {
1184
+ console.log('\nDownload podcast:', podcast.audio.mixedUrl);
1185
+ }
1186
+ }
1187
+ ```
1188
+
1189
+ ### Example 7: Podcast with Progress Tracking
1190
+
1191
+ ```typescript
1192
+ async function generatePodcastWithProgress() {
1193
+ // Start podcast generation
1194
+ const podcast = await ai.podcast.generate('my-collection', {
1195
+ productId: 'coffee-maker-deluxe',
1196
+ duration: 10,
1197
+ style: 'professional',
1198
+ includeAudio: true
1199
+ });
1200
+
1201
+ const podcastId = podcast.podcastId;
1202
+
1203
+ // Poll for status
1204
+ const checkStatus = async () => {
1205
+ const status = await ai.podcast.getStatus('my-collection', podcastId);
1206
+
1207
+ console.log(`Status: ${status.status} (${status.progress}%)`);
1208
+
1209
+ if (status.status === 'completed' && status.result) {
1210
+ console.log('Podcast ready!');
1211
+ console.log('Listen:', status.result.audio?.mixedUrl);
1212
+ return true;
1213
+ } else if (status.status === 'failed') {
1214
+ console.error('Generation failed:', status.error);
1215
+ return true;
1216
+ }
1217
+
1218
+ return false;
1219
+ };
1220
+
1221
+ // Check every 5 seconds
1222
+ const interval = setInterval(async () => {
1223
+ const done = await checkStatus();
1224
+ if (done) clearInterval(interval);
1225
+ }, 5000);
1226
+ }
1227
+ ```
1228
+
1229
+ ---
1230
+
1231
+ ## Error Handling
1232
+
1233
+ ### Error Codes
1234
+
1235
+ | Code | Type | HTTP Status | Description |
1236
+ |------|------|-------------|-------------|
1237
+ | `rate_limit_exceeded` | `rate_limit_error` | 429 | User exceeded rate limit |
1238
+ | `invalid_request` | `invalid_request_error` | 400 | Invalid parameters |
1239
+ | `authentication_error` | `authentication_error` | 401 | Invalid/missing API key |
1240
+ | `permission_denied` | `permission_error` | 403 | Insufficient permissions |
1241
+ | `not_found` | `not_found_error` | 404 | Resource not found |
1242
+ | `document_not_found` | `not_found_error` | 404 | Product document not indexed |
1243
+ | `server_error` | `server_error` | 500 | Internal server error |
1244
+ | `service_unavailable` | `server_error` | 503 | Service temporarily unavailable |
1245
+
1246
+ ### Error Handling Pattern
1247
+
1248
+ ```typescript
1249
+ import { SmartLinksAIError } from '@proveanything/smartlinks';
1250
+
1251
+ async function robustChat() {
1252
+ try {
1253
+ const response = await ai.publicApi.chat('my-collection', {
1254
+ productId: 'coffee-maker',
1255
+ userId: 'user-123',
1256
+ message: 'Help!'
1257
+ });
1258
+ return response.message;
1259
+ } catch (error) {
1260
+ if (error instanceof SmartLinksAIError) {
1261
+ switch (error.code) {
1262
+ case 'rate_limit_exceeded':
1263
+ console.error('Rate limit exceeded');
1264
+ console.log('Try again at:', new Date(error.resetAt!));
1265
+ break;
1266
+ case 'document_not_found':
1267
+ console.error('Product manual not indexed yet');
1268
+ break;
1269
+ case 'authentication_error':
1270
+ console.error('Invalid API key');
1271
+ break;
1272
+ case 'invalid_request':
1273
+ console.error('Invalid request:', error.message);
1274
+ break;
1275
+ default:
1276
+ console.error('API error:', error.message);
1277
+ }
1278
+ } else {
1279
+ console.error('Unexpected error:', error);
1280
+ }
1281
+ throw error;
1282
+ }
1283
+ }
1284
+ ```
1285
+
1286
+ ### Rate Limit Retry
1287
+
1288
+ ```typescript
1289
+ async function chatWithRetry(
1290
+ request: PublicChatRequest,
1291
+ maxRetries = 3
1292
+ ) {
1293
+ let retries = 0;
1294
+
1295
+ while (retries < maxRetries) {
1296
+ try {
1297
+ return await ai.publicApi.chat('my-collection', request);
1298
+ } catch (error) {
1299
+ if (error instanceof SmartLinksAIError && error.isRateLimitError()) {
1300
+ if (retries === maxRetries - 1) throw error;
1301
+
1302
+ const resetTime = new Date(error.resetAt!).getTime();
1303
+ const waitTime = resetTime - Date.now();
1304
+
1305
+ console.log(`Rate limited. Waiting ${waitTime}ms...`);
1306
+ await new Promise(resolve => setTimeout(resolve, waitTime));
1307
+
1308
+ retries++;
1309
+ } else {
1310
+ throw error;
1311
+ }
1312
+ }
1313
+ }
1314
+ }
1315
+ ```
1316
+
1317
+ ---
1318
+
1319
+ ## Rate Limiting
1320
+
1321
+ ### Rate Limit Overview
1322
+
1323
+ Public endpoints are rate-limited per `userId`:
1324
+
1325
+ | Endpoint Type | Default Limit | Window |
1326
+ |--------------|---------------|--------|
1327
+ | Public Chat | 20 requests | 1 hour |
1328
+ | Token Generation | 10 requests | 1 hour |
1329
+ | Admin Endpoints | Unlimited* | - |
1330
+
1331
+ *Admin endpoints use API key authentication and are not rate-limited by default.
1332
+
1333
+ ### Rate Limit Headers
1334
+
1335
+ All API responses include rate limit information:
1336
+
1337
+ ```
1338
+ X-RateLimit-Limit: 20
1339
+ X-RateLimit-Remaining: 15
1340
+ X-RateLimit-Reset: 1707300000000
1341
+ ```
1342
+
1343
+ ### Checking Rate Limit
1344
+
1345
+ ```typescript
1346
+ // Check rate limit status before making requests
1347
+ const status = await ai.publicApi.getRateLimit('my-collection', 'user-123');
1348
+
1349
+ console.log('Used:', status.used);
1350
+ console.log('Remaining:', status.remaining);
1351
+ console.log('Resets at:', new Date(status.resetAt));
1352
+
1353
+ if (status.remaining > 0) {
1354
+ // Safe to make request
1355
+ await ai.publicApi.chat(/* ... */);
1356
+ } else {
1357
+ // Show user when they can ask again
1358
+ console.log('Rate limit reached. Try again at:', status.resetAt);
1359
+ }
1360
+ ```
1361
+
1362
+ ### Resetting Rate Limits (Admin)
1363
+
1364
+ ```typescript
1365
+ // Reset rate limit for a specific user
1366
+ await ai.rateLimit.reset('my-collection', 'user-123');
1367
+ console.log('Rate limit reset for user-123');
1368
+ ```
1369
+
1370
+ ---
1371
+
1372
+ ## Best Practices
1373
+
1374
+ ### 1. Choose the Right Model
1375
+
1376
+ ```typescript
1377
+ // For simple Q&A (fast, cheap)
1378
+ await ai.chat.completions.create('my-collection', {
1379
+ model: 'google/gemini-2.5-flash-lite',
1380
+ messages: [...]
1381
+ });
1382
+
1383
+ // For general use (balanced)
1384
+ await ai.chat.completions.create('my-collection', {
1385
+ model: 'google/gemini-2.5-flash',
1386
+ messages: [...]
1387
+ });
1388
+
1389
+ // For complex reasoning (powerful)
1390
+ await ai.chat.completions.create('my-collection', {
1391
+ model: 'google/gemini-2.5-pro',
1392
+ messages: [...]
1393
+ });
1394
+ ```
1395
+
1396
+ ### 2. Use Streaming for Long Responses
1397
+
1398
+ Improve perceived performance with streaming:
1399
+
1400
+ ```typescript
1401
+ // Non-streaming: User waits for full response
1402
+ const response = await ai.chat.completions.create('my-collection', {
1403
+ messages: [...]
1404
+ });
1405
+
1406
+ // Streaming: User sees progress immediately
1407
+ const stream = await ai.chat.completions.create('my-collection', {
1408
+ stream: true,
1409
+ messages: [...]
1410
+ });
1411
+
1412
+ for await (const chunk of stream) {
1413
+ updateUI(chunk.choices[0]?.delta?.content);
1414
+ }
1415
+ ```
1416
+
1417
+ ### 3. Maintain Session Context
1418
+
1419
+ Keep conversations coherent with session IDs:
1420
+
1421
+ ```typescript
1422
+ // Generate unique session ID per conversation
1423
+ const sessionId = `user-${userId}-product-${productId}`;
1424
+
1425
+ // All questions in same conversation use same sessionId
1426
+ await ai.public.chat('my-collection', {
1427
+ sessionId,
1428
+ message: 'First question',
1429
+ ...
1430
+ });
1431
+
1432
+ await ai.public.chat('my-collection', {
1433
+ sessionId,
1434
+ message: 'Follow-up question',
1435
+ ...
1436
+ });
1437
+ ```
1438
+
1439
+ ### 4. Handle Rate Limits Gracefully
1440
+
1441
+ Show clear feedback to users:
1442
+
1443
+ ```typescript
1444
+ try {
1445
+ await ai.publicApi.chat('my-collection', {...});
1446
+ } catch (error) {
1447
+ if (error instanceof SmartLinksAIError && error.isRateLimitError()) {
1448
+ const resetTime = new Date(error.resetAt!);
1449
+ showNotification(
1450
+ `You've reached your question limit. ` +
1451
+ `Try again at ${resetTime.toLocaleTimeString()}`
1452
+ );
1453
+ }
1454
+ }
1455
+ ```
1456
+
1457
+ ### 5. Optimize Voice UX
1458
+
1459
+ Provide clear status updates:
1460
+
1461
+ ```typescript
1462
+ async function voiceAssistant() {
1463
+ try {
1464
+ showStatus('Listening...');
1465
+ const question = await ai.voice.listen();
1466
+
1467
+ showStatus('Processing...');
1468
+ const answer = await ai.public.chat('my-collection', {
1469
+ message: question,
1470
+ ...
1471
+ });
1472
+
1473
+ showStatus('Speaking...');
1474
+ await ai.voice.speak(answer.message);
1475
+
1476
+ showStatus('Ready');
1477
+ } catch (error) {
1478
+ showStatus('Error', error.message);
1479
+ }
1480
+ }
1481
+ ```
1482
+
1483
+ ### 6. Chunk Large Documents
1484
+
1485
+ For better RAG performance, chunk documents appropriately:
1486
+
1487
+ ```typescript
1488
+ // For technical manuals
1489
+ await ai.rag.indexDocument('my-collection', {
1490
+ productId: 'coffee-maker',
1491
+ documentUrl: '...',
1492
+ chunkSize: 500, // Smaller chunks for precise answers
1493
+ overlap: 50 // Overlap maintains context
1494
+ });
1495
+
1496
+ // For narrative content
1497
+ await ai.rag.indexDocument('my-collection', {
1498
+ productId: 'coffee-maker',
1499
+ documentUrl: '...',
1500
+ chunkSize: 1000, // Larger chunks for coherent responses
1501
+ overlap: 100
1502
+ });
1503
+ ```
1504
+
1505
+ ### 7. Use System Prompts Effectively
1506
+
1507
+ Provide clear instructions:
1508
+
1509
+ ```typescript
1510
+ await ai.chat.completions.create('my-collection', {
1511
+ messages: [
1512
+ {
1513
+ role: 'system',
1514
+ content: `You are a coffee maker expert assistant.
1515
+ - Be concise and clear
1516
+ - Use numbered lists for steps
1517
+ - Always mention safety precautions
1518
+ - If unsure, ask for clarification`
1519
+ },
1520
+ { role: 'user', content: 'How do I descale?' }
1521
+ ]
1522
+ });
1523
+ ```
1524
+
1525
+ ### 8. Monitor Usage and Costs
1526
+
1527
+ Track usage for cost management:
1528
+
1529
+ ```typescript
1530
+ const response = await ai.chat.completions.create('my-collection', {
1531
+ messages: [...]
1532
+ });
1533
+
1534
+ // Log token usage
1535
+ console.log('Usage:', response.usage);
1536
+ console.log('Prompt tokens:', response.usage.prompt_tokens);
1537
+ console.log('Completion tokens:', response.usage.completion_tokens);
1538
+ console.log('Total tokens:', response.usage.total_tokens);
1539
+
1540
+ // Calculate estimated cost
1541
+ const model = await ai.models.get('my-collection', response.model);
1542
+ const cost =
1543
+ (response.usage.prompt_tokens * model.pricing.input / 1_000_000) +
1544
+ (response.usage.completion_tokens * model.pricing.output / 1_000_000);
1545
+ console.log('Estimated cost: $', cost.toFixed(4));
1546
+ ```
1547
+
1548
+ ---
1549
+
1550
+ ## Related Documentation
1551
+
1552
+ - [API Summary](./API_SUMMARY.md) - Complete API reference
1553
+ - [Widgets](./widgets.md) - Embedding SmartLinks components
1554
+ - [Realtime](./realtime.md) - Realtime data updates
1555
+ - [iframe Responder](./iframe-responder.md) - iframe integration
1556
+
1557
+ ---
1558
+
1559
+ ## Support
1560
+
1561
+ For questions or issues:
1562
+
1563
+ - **Documentation:** https://smartlinks.app/docs
1564
+ - **GitHub:** https://github.com/Prove-Anything/smartlinks
1565
+ - **Email:** support@smartlinks.app