@aeye/ai 0.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.
Files changed (54) hide show
  1. package/README.md +1324 -0
  2. package/dist/ai.d.ts +204 -0
  3. package/dist/ai.d.ts.map +1 -0
  4. package/dist/ai.js +837 -0
  5. package/dist/ai.js.map +1 -0
  6. package/dist/apis/base.d.ts +181 -0
  7. package/dist/apis/base.d.ts.map +1 -0
  8. package/dist/apis/base.js +359 -0
  9. package/dist/apis/base.js.map +1 -0
  10. package/dist/apis/chat.d.ts +82 -0
  11. package/dist/apis/chat.d.ts.map +1 -0
  12. package/dist/apis/chat.js +214 -0
  13. package/dist/apis/chat.js.map +1 -0
  14. package/dist/apis/embed.d.ts +27 -0
  15. package/dist/apis/embed.d.ts.map +1 -0
  16. package/dist/apis/embed.js +70 -0
  17. package/dist/apis/embed.js.map +1 -0
  18. package/dist/apis/image.d.ts +77 -0
  19. package/dist/apis/image.d.ts.map +1 -0
  20. package/dist/apis/image.js +313 -0
  21. package/dist/apis/image.js.map +1 -0
  22. package/dist/apis/models.d.ts +108 -0
  23. package/dist/apis/models.d.ts.map +1 -0
  24. package/dist/apis/models.js +118 -0
  25. package/dist/apis/models.js.map +1 -0
  26. package/dist/apis/speech.d.ts +27 -0
  27. package/dist/apis/speech.d.ts.map +1 -0
  28. package/dist/apis/speech.js +79 -0
  29. package/dist/apis/speech.js.map +1 -0
  30. package/dist/apis/transcribe.d.ts +26 -0
  31. package/dist/apis/transcribe.d.ts.map +1 -0
  32. package/dist/apis/transcribe.js +103 -0
  33. package/dist/apis/transcribe.js.map +1 -0
  34. package/dist/common.d.ts +10 -0
  35. package/dist/common.d.ts.map +1 -0
  36. package/dist/common.js +17 -0
  37. package/dist/common.js.map +1 -0
  38. package/dist/index.d.ts +17 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +17 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/modelDetection.d.ts +31 -0
  43. package/dist/modelDetection.d.ts.map +1 -0
  44. package/dist/modelDetection.js +78 -0
  45. package/dist/modelDetection.js.map +1 -0
  46. package/dist/registry.d.ts +150 -0
  47. package/dist/registry.d.ts.map +1 -0
  48. package/dist/registry.js +639 -0
  49. package/dist/registry.js.map +1 -0
  50. package/dist/types.d.ts +767 -0
  51. package/dist/types.d.ts.map +1 -0
  52. package/dist/types.js +8 -0
  53. package/dist/types.js.map +1 -0
  54. package/package.json +53 -0
package/README.md ADDED
@@ -0,0 +1,1324 @@
1
+ # @aeye/ai
2
+
3
+ > **Multi-provider AI library with intelligent model selection, type-safe context management, and comprehensive hooks system.**
4
+
5
+ The `@aeye/ai` package is the main AI library built on `@aeye/core`, providing a unified interface for working with multiple AI providers (OpenAI, Anthropic, Google, etc.) with automatic model selection, cost tracking, and extensible architecture.
6
+
7
+ ```ts
8
+ const ai = AI.with<MyContext>()
9
+ .providers({ openai })
10
+ .create(/* options, hooks, etc */)
11
+
12
+ ai.chat.get(request, ctx?)
13
+ ai.chat.stream(request, ctx?)
14
+ ai.image.generate.get(request, ctx?)
15
+ ai.image.generate.stream(request, ctx?)
16
+ ai.image.edit.get(request, ctx?)
17
+ ai.image.edit.stream(request, ctx?)
18
+ ai.image.analyze.get(request, ctx?)
19
+ ai.image.analyze.stream(request, ctx?)
20
+ ai.transcribe.get(request, ctx?)
21
+ ai.transcribe.stream(request, ctx?)
22
+ ai.speech.get(request, ctx?)
23
+ ai.speech.stream(request, ctx?)
24
+ ai.embed.get(request, ctx?)
25
+ ai.embed.stream(request, ctx?)
26
+ ai.models.list() // get(id), search(criteria), select(criteria), refresh()
27
+ ai.providers.openai // Provider
28
+ ai.hooks // Hook events to implement BYOK
29
+ ai.components // all prompts/tools/agents created on this ai
30
+
31
+ // Define prompts, tools, agents
32
+ const chatPrompt = ai.prompt({
33
+ name: 'chat_prompt',
34
+ description: 'A helpful chat assistant that answers user questions',
35
+ content: `You are a helpful assistant. The user {{user}} has asked: {{question}}
36
+ Answer their question clearly and concisely. If you need more information, use the available tools.`,
37
+ input: (input: { user: string; question: string }) => input,
38
+ schema: z.object({ answer: z.string() }),
39
+ refs: [relevantInfo],
40
+ });
41
+
42
+ const analyzeConversation = ai.prompt({
43
+ name: 'analyze_conversation',
44
+ description: 'Analyzes a conversation to judge how accurately the agent performed the requests',
45
+ content: `You are a conversational analyst. Analyze the following conversation between user {{user}} and the assistant.
46
+ Messages: {{messages}}
47
+ Return a summary of how well the agent performed and a rating between 0 and 100.`,
48
+ input: (input: { user: string; messages: string }) => input,
49
+ schema: z.object({ summary: z.string(), rating: z.number() }),
50
+ });
51
+
52
+ // Tool
53
+ const relevantInfo = ai.tool({
54
+ name: 'relevant_info',
55
+ description: `Searches for info relevant to a user's question`,
56
+ instructions: 'Use the relevant_info tool to look up information to better answer questions',
57
+ schema: z.object({
58
+ query: z.string(),
59
+ }),
60
+ call: async ({ query }, refs, ctx) => {
61
+ return await DB.getRelevantInfo(query, 10);
62
+ }
63
+ });
64
+
65
+ // Agent
66
+ const chatAgent = ai.agent({
67
+ name: 'chat_agent',
68
+ description: 'An agent to chat with the user and analyze the conversation',
69
+ refs: [relevantInfo, chatPrompt, analyzeConversation],
70
+ call: async (input: { user: string; question: string }, refs, ctx) => {
71
+ // Get the chat response
72
+ const chatResponse = await refs[1].run({
73
+ user: input.user,
74
+ question: input.question
75
+ }, ctx);
76
+
77
+ // Analyze the conversation
78
+ const messages = JSON.stringify([
79
+ { role: 'user', content: input.question },
80
+ { role: 'assistant', content: chatResponse.answer }
81
+ ]);
82
+
83
+ const analysis = await refs[2].run({
84
+ user: input.user,
85
+ messages
86
+ }, ctx);
87
+
88
+ return {
89
+ answer: chatResponse.answer,
90
+ analysis: {
91
+ summary: analysis.summary,
92
+ rating: analysis.rating
93
+ }
94
+ };
95
+ },
96
+ });
97
+
98
+ // Use the agent
99
+ const result = await chatAgent.run(
100
+ { user: 'ClickerMonkey', question: 'What is TypeScript?' },
101
+ { messages: [] }
102
+ );
103
+ console.log(result.answer);
104
+ console.log(`Quality rating: ${result.analysis.rating}/100`);
105
+ ```
106
+
107
+ ## Features
108
+
109
+ - **Multi-Provider Support**: Single interface for OpenAI, Anthropic, Google, Replicate, and custom providers
110
+ - **Intelligent Model Selection**: Automatic model selection based on capabilities, cost, speed, and quality
111
+ - **Type-Safe Context**: Strongly-typed context and metadata with compiler validation
112
+ - **Comprehensive APIs**: Chat, Image Generation/Analysis, Speech Synthesis/Transcription, Embeddings
113
+ - **Lifecycle Hooks**: Intercept and modify operations at every stage
114
+ - **Cost Tracking**: Automatic token usage and cost calculation
115
+ - **Streaming Support**: Full streaming support across all compatible capabilities
116
+ - **Model Registry**: Centralized model management with external sources (OpenRouter, etc.)
117
+ - **Extensible**: Custom providers, model handlers, and transformers
118
+
119
+ ## Table of Contents
120
+
121
+ - [Installation](#installation)
122
+ - [Quick Start](#quick-start)
123
+ - [Architecture](#architecture)
124
+ - [Core Concepts](#core-concepts)
125
+ - [API Reference](#api-reference)
126
+ - [Chat API](#chat-api)
127
+ - [Image API](#image-api)
128
+ - [Speech API](#speech-api)
129
+ - [Transcribe API](#transcribe-api)
130
+ - [Embed API](#embed-api)
131
+ - [Models API](#models-api)
132
+ - [Advanced Features](#advanced-features)
133
+ - [Custom Context & Metadata](#custom-context--metadata)
134
+ - [Lifecycle Hooks](#lifecycle-hooks)
135
+ - [Model Selection](#model-selection)
136
+ - [Model Sources](#model-sources)
137
+ - [Custom Providers](#custom-providers)
138
+ - [TypeScript Guide](#typescript-guide)
139
+ - [Examples](#examples)
140
+
141
+ ## Installation
142
+
143
+ ```bash
144
+ npm install @aeye/ai @aeye/core
145
+ ```
146
+
147
+ You'll also need provider packages:
148
+
149
+ ```bash
150
+ npm install @aeye/openai @aeye/anthropic # etc.
151
+ ```
152
+
153
+ ## Quick Start
154
+
155
+ ```typescript
156
+ import { AI } from '@aeye/ai';
157
+ import { OpenAIProvider } from '@aeye/openai';
158
+ import { OpenRouterProvider } from '@aeye/openrouter';
159
+
160
+ // Create providers
161
+ const openai = new OpenAIProvider({ apiKey: '123' });
162
+ const openrouter = new OpenRouterProvider({ apiKey: 'abc' });
163
+
164
+ // Create an AI instance
165
+ const ai = AI.with()
166
+ .providers({ openai, openrouter })
167
+ .create({
168
+ defaultContext: {
169
+ apiVersion: 'v1'
170
+ }
171
+ });
172
+
173
+ // Simple chat completion
174
+ const response = await ai.chat.get([
175
+ { role: 'user', content: 'What is TypeScript?' }
176
+ ]);
177
+ console.log(response.content);
178
+
179
+ // Streaming
180
+ for await (const chunk of ai.chat.stream([
181
+ { role: 'user', content: 'Write a poem' }
182
+ ])) {
183
+ if (chunk.content) {
184
+ process.stdout.write(chunk.content);
185
+ }
186
+ }
187
+ ```
188
+
189
+ ## Architecture
190
+
191
+ The `@aeye/ai` library is structured around several key components:
192
+
193
+ ```
194
+ ┌─────────────────────────────────────────────────────────┐
195
+ │ AI Class │
196
+ │ - Context Management │
197
+ │ - Model Registry │
198
+ │ - Lifecycle Hooks │
199
+ └─────────────────┬───────────────────────────────────────┘
200
+ │
201
+ ┌────────┴─────────┐
202
+ │ │
203
+ ┌────▼────┐ ┌─────▼──────┐
204
+ │ APIs │ │ Registry │
205
+ │ │ │ │
206
+ │ • Chat │ │ • Models │
207
+ │ • Image │ │ • Search │
208
+ │ • Speech│ │ • Select │
209
+ │ • Embed │ └────┬───────┘
210
+ └────┬────┘ │
211
+ │ ┌──────▼──────┐
212
+ │ │ Providers │
213
+ │ │ │
214
+ │ │ • OpenAI │
215
+ │ │ • Anthropic │
216
+ │ │ • Google │
217
+ │ │ • Custom │
218
+ └─────────┴─────────────┘
219
+ ```
220
+
221
+ ### Key Components
222
+
223
+ 1. **AI Class**: Central orchestrator managing context, metadata, providers, and APIs
224
+ 2. **APIs**: Specialized interfaces for different capabilities (chat, images, speech, etc.)
225
+ 3. **Model Registry**: Centralized model management with selection logic
226
+ 4. **Providers**: Pluggable implementations for different AI services
227
+ 5. **Context System**: Type-safe context and metadata threading
228
+
229
+ ## Core Concepts
230
+
231
+ ### Context
232
+
233
+ Context is data passed through your entire AI operation. It's composed of:
234
+
235
+ - **Default Context**: Static values provided at AI creation
236
+ - **Provided Context**: Async-loaded values (e.g., from database)
237
+ - **Required Context**: Values that must be provided per-request
238
+
239
+ ```typescript
240
+ interface AppContext {
241
+ user: User;
242
+ db: Database;
243
+ apiVersion: string;
244
+ }
245
+
246
+ const ai = AI.with<AppContext>()
247
+ .providers({ openai })
248
+ .create({
249
+ defaultContext: {
250
+ apiVersion: 'v1'
251
+ },
252
+ providedContext: async (ctx) => ({
253
+ user: await getUser(ctx.userId),
254
+ db: database
255
+ })
256
+ });
257
+
258
+ // Usage: only provide what's not already available
259
+ await ai.chat.get(request, { userId: '123' });
260
+ ```
261
+
262
+ ### Metadata
263
+
264
+ Metadata controls model selection and operation configuration:
265
+
266
+ ```typescript
267
+ interface AppMetadata {
268
+ priority: 'cost' | 'speed' | 'quality';
269
+ }
270
+
271
+ const ai = AI.with<AppContext, AppMetadata>()
272
+ .providers({ openai, openrouter })
273
+ .create({
274
+ defaultMetadata: {
275
+ priority: 'balanced'
276
+ }
277
+ });
278
+
279
+ // Override per request
280
+ await ai.chat.get(request, {
281
+ metadata: {
282
+ model: 'gpt-4', // Specific model
283
+ required: ['chat', 'vision'], // Required capabilities
284
+ weights: { cost: 0.7, speed: 0.3 } // Selection weights
285
+ }
286
+ });
287
+ ```
288
+
289
+ ### Model Selection
290
+
291
+ Models are automatically selected based on:
292
+
293
+ 1. **Capabilities**: Required and optional features
294
+ 2. **Constraints**: Provider filters, context window, budget
295
+ 3. **Scoring**: Weighted evaluation of cost, speed, accuracy, context size
296
+
297
+ ```typescript
298
+ // Automatic selection for vision task
299
+ const response = await ai.chat.get(
300
+ [{
301
+ role: 'user',
302
+ content: [
303
+ { type: 'text', content: 'What is in this image?' },
304
+ { type: 'image', content: imageUrl }
305
+ ]
306
+ }],
307
+ {
308
+ metadata: {
309
+ required: ['chat', 'vision'],
310
+ weights: { cost: 0.5, speed: 0.5 }
311
+ }
312
+ }
313
+ );
314
+ ```
315
+
316
+ ## API Reference
317
+
318
+ ### Chat API
319
+
320
+ The Chat API provides conversational AI with automatic model selection.
321
+
322
+ #### Methods
323
+
324
+ **`chat.get(messages, context?)`**
325
+
326
+ Execute a non-streaming chat completion.
327
+
328
+ ```typescript
329
+ const response = await ai.chat.get([
330
+ { role: 'system', content: 'You are a helpful assistant.' },
331
+ { role: 'user', content: 'Hello!' }
332
+ ]);
333
+
334
+ console.log(response.content);
335
+ console.log(response.usage); // Token usage
336
+ console.log(response.finishReason);
337
+ ```
338
+
339
+ **`chat.stream(messages, context?)`**
340
+
341
+ Execute a streaming chat completion.
342
+
343
+ ```typescript
344
+ for await (const chunk of ai.chat.stream([
345
+ { role: 'user', content: 'Count to 10' }
346
+ ])) {
347
+ if (chunk.content) {
348
+ process.stdout.write(chunk.content);
349
+ }
350
+ if (chunk.finishReason) {
351
+ console.log('\nFinished:', chunk.finishReason);
352
+ }
353
+ }
354
+ ```
355
+
356
+ #### Request Format
357
+
358
+ ```typescript
359
+ interface Request {
360
+ messages: Message[];
361
+ maxTokens?: number;
362
+ temperature?: number;
363
+ topP?: number;
364
+ tools?: Tool[];
365
+ responseFormat?: { type: 'json_object' | 'text' };
366
+ // ... other options
367
+ }
368
+
369
+ type Message =
370
+ | { role: 'system' | 'user' | 'assistant', content: string }
371
+ | { role: 'user', content: ContentPart[] }; // Multimodal
372
+
373
+ type ContentPart =
374
+ | { type: 'text', content: string }
375
+ | { type: 'image', content: string }; // URL or base64
376
+ ```
377
+
378
+ #### Response Format
379
+
380
+ ```typescript
381
+ interface Response {
382
+ content: string;
383
+ finishReason: 'stop' | 'length' | 'tool_calls' | 'content_filter';
384
+ toolCalls?: ToolCall[];
385
+ refusal?: string;
386
+ reasoning?: string; // For reasoning models
387
+ usage: Usage;
388
+ }
389
+
390
+ interface Usage {
391
+ inputTokens: number;
392
+ outputTokens: number;
393
+ totalTokens: number;
394
+ cachedTokens?: number;
395
+ reasoningTokens?: number;
396
+ cost?: number; // Cost in dollars, if provided by the provider
397
+ }
398
+ ```
399
+
400
+ #### Examples
401
+
402
+ **Basic Conversation**
403
+
404
+ ```typescript
405
+ const messages = [
406
+ { role: 'system', content: 'You are a TypeScript expert.' },
407
+ { role: 'user', content: 'Explain generics' }
408
+ ];
409
+
410
+ const response = await ai.chat.get(messages);
411
+ console.log(response.content);
412
+ ```
413
+
414
+ **Vision Analysis**
415
+
416
+ ```typescript
417
+ const response = await ai.chat.get([
418
+ {
419
+ role: 'user',
420
+ content: [
421
+ { type: 'text', content: 'What is in this image?' },
422
+ { type: 'image', content: 'https://example.com/image.jpg' }
423
+ ]
424
+ }
425
+ ], {
426
+ metadata: {
427
+ required: ['chat', 'vision']
428
+ }
429
+ });
430
+ ```
431
+
432
+ **Function Calling**
433
+
434
+ ```typescript
435
+ const tools = [
436
+ {
437
+ name: 'get_weather',
438
+ description: 'Get weather for a location',
439
+ parameters: {
440
+ type: 'object',
441
+ properties: {
442
+ location: { type: 'string' }
443
+ }
444
+ }
445
+ }
446
+ ];
447
+
448
+ const response = await ai.chat.get(
449
+ [{ role: 'user', content: 'What is the weather in NYC?' }],
450
+ {
451
+ metadata: {
452
+ required: ['chat', 'tools']
453
+ }
454
+ }
455
+ );
456
+
457
+ if (response.toolCalls) {
458
+ for (const call of response.toolCalls) {
459
+ console.log(`Tool: ${call.function.name}`);
460
+ console.log(`Args: ${call.function.arguments}`);
461
+ }
462
+ }
463
+ ```
464
+
465
+ **Streaming with Token Counting**
466
+
467
+ ```typescript
468
+ let totalTokens = 0;
469
+
470
+ for await (const chunk of ai.chat.stream(messages)) {
471
+ if (chunk.content) {
472
+ process.stdout.write(chunk.content);
473
+ }
474
+ if (chunk.usage) {
475
+ totalTokens = chunk.usage.totalTokens;
476
+ }
477
+ }
478
+
479
+ console.log(`\nTotal tokens: ${totalTokens}`);
480
+ ```
481
+
482
+ ### Image API
483
+
484
+ The Image API provides image generation, editing, and analysis.
485
+
486
+ #### Sub-APIs
487
+
488
+ **`image.generate.get(request, context?)`**
489
+
490
+ Generate images from text prompts.
491
+
492
+ ```typescript
493
+ const response = await ai.image.generate.get({
494
+ prompt: 'A futuristic city at sunset',
495
+ n: 2,
496
+ size: '1024x1024',
497
+ quality: 'hd'
498
+ });
499
+
500
+ for (const image of response.images) {
501
+ console.log(image.url);
502
+ }
503
+ ```
504
+
505
+ **`image.generate.stream(request, context?)`**
506
+
507
+ Stream image generation progress.
508
+
509
+ ```typescript
510
+ for await (const chunk of ai.image.generate.stream({
511
+ prompt: 'A majestic mountain landscape'
512
+ })) {
513
+ if (chunk.progress) {
514
+ console.log(`Progress: ${chunk.progress}%`);
515
+ }
516
+ if (chunk.done && chunk.image) {
517
+ console.log('Image URL:', chunk.image.url);
518
+ }
519
+ }
520
+ ```
521
+
522
+ **`image.edit.get(request, context?)`**
523
+
524
+ Edit existing images with prompts.
525
+
526
+ ```typescript
527
+ const response = await ai.image.edit.get({
528
+ image: imageBuffer,
529
+ mask: maskBuffer,
530
+ prompt: 'Add a sunset in the background',
531
+ size: '1024x1024'
532
+ });
533
+ ```
534
+
535
+ **`image.analyze.get(request, context?)`**
536
+
537
+ Analyze images with vision models (uses chat models with vision capability).
538
+
539
+ ```typescript
540
+ const response = await ai.image.analyze.get({
541
+ prompt: 'Describe this image in detail',
542
+ images: ['https://example.com/photo.jpg'],
543
+ maxTokens: 500
544
+ });
545
+
546
+ console.log(response.content);
547
+ ```
548
+
549
+ #### Examples
550
+
551
+ **Batch Generation**
552
+
553
+ ```typescript
554
+ const response = await ai.image.generate.get({
555
+ prompt: 'Logo designs for a tech startup',
556
+ n: 4,
557
+ size: '512x512'
558
+ });
559
+
560
+ response.images.forEach((img, i) => {
561
+ console.log(`Design ${i + 1}: ${img.url}`);
562
+ });
563
+ ```
564
+
565
+ **Style Transfer**
566
+
567
+ ```typescript
568
+ const response = await ai.image.generate.get({
569
+ prompt: 'Van Gogh style starry night over modern city',
570
+ quality: 'hd',
571
+ style: 'vivid'
572
+ });
573
+ ```
574
+
575
+ ### Speech API
576
+
577
+ Text-to-speech synthesis.
578
+
579
+ **`speech.get(request, context?)`**
580
+
581
+ ```typescript
582
+ const response = await ai.speech.get({
583
+ text: 'Hello, this is a test of text to speech.',
584
+ voice: 'alloy',
585
+ speed: 1.0,
586
+ responseFormat: 'mp3'
587
+ });
588
+
589
+ // Save audio
590
+ fs.writeFileSync('output.mp3', response.audioBuffer);
591
+ ```
592
+
593
+ **`speech.stream(request, context?)`**
594
+
595
+ ```typescript
596
+ for await (const chunk of ai.speech.stream({
597
+ text: 'Streaming audio generation...',
598
+ voice: 'nova'
599
+ })) {
600
+ if (chunk.audioData) {
601
+ // Process audio chunks in real-time
602
+ audioStream.write(chunk.audioData);
603
+ }
604
+ }
605
+ ```
606
+
607
+ ### Transcribe API
608
+
609
+ Speech-to-text transcription.
610
+
611
+ **`transcribe.get(request, context?)`**
612
+
613
+ ```typescript
614
+ const audioBuffer = fs.readFileSync('recording.mp3');
615
+
616
+ const response = await ai.transcribe.get({
617
+ audio: audioBuffer,
618
+ language: 'en',
619
+ responseFormat: 'verbose_json',
620
+ timestampGranularities: ['word', 'segment']
621
+ });
622
+
623
+ console.log(response.text);
624
+ console.log('Words:', response.words);
625
+ console.log('Segments:', response.segments);
626
+ ```
627
+
628
+ ### Embed API
629
+
630
+ Generate text embeddings for semantic search and similarity.
631
+
632
+ **`embed.get(request, context?)`**
633
+
634
+ ```typescript
635
+ const response = await ai.embed.get({
636
+ texts: [
637
+ 'TypeScript is a typed superset of JavaScript',
638
+ 'Python is a high-level programming language',
639
+ 'Rust is a systems programming language'
640
+ ],
641
+ dimensions: 1536
642
+ });
643
+
644
+ // Get embeddings
645
+ response.embeddings.forEach(({ embedding, index }) => {
646
+ console.log(`Text ${index}: ${embedding.length} dimensions`);
647
+ });
648
+
649
+ // Calculate similarity
650
+ function cosineSimilarity(a: number[], b: number[]) {
651
+ const dot = a.reduce((sum, val, i) => sum + val * b[i], 0);
652
+ const magA = Math.sqrt(a.reduce((sum, val) => sum + val * val, 0));
653
+ const magB = Math.sqrt(b.reduce((sum, val) => sum + val * val, 0));
654
+ return dot / (magA * magB);
655
+ }
656
+
657
+ const sim = cosineSimilarity(
658
+ response.embeddings[0].embedding,
659
+ response.embeddings[1].embedding
660
+ );
661
+ console.log('Similarity:', sim);
662
+ ```
663
+
664
+ ### Models API
665
+
666
+ Explore and manage available models.
667
+
668
+ **`models.list()`**
669
+
670
+ ```typescript
671
+ const models = ai.models.list();
672
+ console.log(`Available models: ${models.length}`);
673
+
674
+ models.forEach(model => {
675
+ console.log(`${model.provider}/${model.id}`);
676
+ console.log(` Tier: ${model.tier}`);
677
+ console.log(` Capabilities: ${Array.from(model.capabilities).join(', ')}`);
678
+ console.log(` Cost: $${model.pricing.inputTokensPer1M}/1M input tokens`);
679
+ });
680
+ ```
681
+
682
+ **`models.get(id)`**
683
+
684
+ ```typescript
685
+ const model = ai.models.get('gpt-4');
686
+ if (model) {
687
+ console.log('Context window:', model.contextWindow);
688
+ console.log('Max output:', model.maxOutputTokens);
689
+ }
690
+ ```
691
+
692
+ **`models.search(criteria)`**
693
+
694
+ ```typescript
695
+ const results = ai.models.search({
696
+ required: ['chat', 'structured'],
697
+ optional: ['vision'],
698
+ weights: { cost: 0.6, speed: 0.4 },
699
+ providers: { allow: ['openai', 'anthropic'] },
700
+ minContextWindow: 100000
701
+ });
702
+
703
+ console.log('Best match:', results[0].model.id);
704
+ console.log('Score:', results[0].score);
705
+ ```
706
+
707
+ **`models.refresh()`**
708
+
709
+ ```typescript
710
+ console.log('Refreshing models...');
711
+ await ai.models.refresh();
712
+ console.log(`Now have ${ai.models.list().length} models`);
713
+ ```
714
+
715
+ ## Advanced Features
716
+
717
+ ### Custom Context & Metadata
718
+
719
+ Define your application-specific context and metadata types:
720
+
721
+ ```typescript
722
+ interface AppContext {
723
+ user: User;
724
+ organization: Organization;
725
+ db: Database;
726
+ requestId: string;
727
+ }
728
+
729
+ interface AppMetadata {
730
+ priority: 'low' | 'normal' | 'high';
731
+ category: 'support' | 'sales' | 'internal';
732
+ maxCost: number;
733
+ }
734
+
735
+ const ai = AI.with<AppContext, AppMetadata>()
736
+ .providers({ openai, anthropic })
737
+ .create({
738
+ defaultContext: {
739
+ requestId: uuid()
740
+ },
741
+ providedContext: async (ctx) => {
742
+ // Load from database
743
+ const user = await db.users.findById(ctx.userId);
744
+ const org = await db.orgs.findById(user.organizationId);
745
+ return { user, organization: org, db };
746
+ },
747
+ defaultMetadata: {
748
+ priority: 'normal',
749
+ maxCost: 0.10
750
+ }
751
+ });
752
+
753
+ // Use with minimal input
754
+ const response = await ai.chat.get(messages, {
755
+ userId: '123', // Rest is loaded automatically
756
+ metadata: {
757
+ priority: 'high',
758
+ category: 'support'
759
+ }
760
+ });
761
+ ```
762
+
763
+ ### Lifecycle Hooks
764
+
765
+ Intercept and modify AI operations at key points:
766
+
767
+ ```typescript
768
+ const ai = AI.with<AppContext, AppMetadata>()
769
+ .providers({ openai, anthropic })
770
+ .create({
771
+ // ... config
772
+ })
773
+ .withHooks({
774
+ beforeModelSelection: async (ctx, metadata) => {
775
+ // Adjust selection based on user tier
776
+ if (ctx.user.tier === 'free') {
777
+ return {
778
+ ...metadata,
779
+ weights: { cost: 1.0, speed: 0 } // Prioritize cost
780
+ };
781
+ }
782
+ return metadata;
783
+ },
784
+
785
+ onModelSelected: async (ctx, selected) => {
786
+ console.log(`Selected: ${selected.model.id}`);
787
+
788
+ // Override model for specific users
789
+ if (ctx.user.betaTester && selected.model.id === 'gpt-4') {
790
+ return {
791
+ ...selected,
792
+ model: ai.models.get('gpt-4-turbo')!
793
+ };
794
+ }
795
+ },
796
+
797
+ beforeRequest: async (ctx, request, selected, estimatedTokens, estimatedCost) => {
798
+ const estimatedCost = estimatedTokens / 1_000_000 *
799
+ (selected.model.pricing.inputTokensPer1M + selected.model.pricing.outputTokensPer1M) / 2;
800
+
801
+ // Check budget
802
+ if (estimatedCost > ctx.user.remainingBudget) {
803
+ throw new Error('Insufficient budget');
804
+ }
805
+
806
+ // Log request
807
+ await db.logs.create({
808
+ userId: ctx.user.id,
809
+ model: selected.model.id,
810
+ estimatedTokens,
811
+ estimatedCost,
812
+ timestamp: new Date()
813
+ });
814
+ },
815
+
816
+ afterRequest: async (ctx, request, response, responseComplete, selected, usage, cost) => {
817
+ // Track actual usage
818
+ await db.users.update(ctx.user.id, {
819
+ tokensUsed: usage.totalTokens,
820
+ costAccrued: cost,
821
+ remainingBudget: ctx.user.remainingBudget - cost
822
+ });
823
+
824
+ // Update model metrics
825
+ await db.modelMetrics.increment(selected.model.id, {
826
+ requestCount: 1,
827
+ successCount: 1,
828
+ totalTokens: usage.totalTokens,
829
+ totalCost: cost
830
+ });
831
+ },
832
+
833
+ onError: (type, message, error, ctx) => {
834
+ logger.error('AI Error', {
835
+ type,
836
+ message,
837
+ error,
838
+ userId: ctx?.user?.id,
839
+ stack: error?.stack
840
+ });
841
+
842
+ // Send to monitoring
843
+ monitoring.captureException(error, {
844
+ tags: { type, userId: ctx?.user?.id }
845
+ });
846
+ }
847
+ });
848
+ ```
849
+
850
+ ### Model Selection
851
+
852
+ Fine-tune model selection with weights and constraints:
853
+
854
+ ```typescript
855
+ // Cost-optimized
856
+ await ai.chat.get(messages, {
857
+ metadata: {
858
+ weights: {
859
+ cost: 0.9,
860
+ speed: 0.05,
861
+ accuracy: 0.05
862
+ }
863
+ }
864
+ });
865
+
866
+ // Performance-optimized
867
+ await ai.chat.get(messages, {
868
+ metadata: {
869
+ weights: {
870
+ cost: 0.1,
871
+ speed: 0.5,
872
+ accuracy: 0.4
873
+ },
874
+ minContextWindow: 128000
875
+ }
876
+ });
877
+
878
+ // Provider-specific
879
+ await ai.chat.get(messages, {
880
+ metadata: {
881
+ providers: {
882
+ allow: ['anthropic'], // Only use Anthropic models
883
+ deny: ['replicate'] // Exclude Replicate
884
+ }
885
+ }
886
+ });
887
+
888
+ // Budget-constrained
889
+ await ai.chat.get(messages, {
890
+ metadata: {
891
+ budget: {
892
+ maxCostPerRequest: 0.05,
893
+ maxCostPerMillionTokens: 10.0
894
+ }
895
+ }
896
+ });
897
+ ```
898
+
899
+ #### Selection Profiles
900
+
901
+ Use pre-configured profiles:
902
+
903
+ ```typescript
904
+ const ai = AI.with()
905
+ .providers({ openai, anthropic })
906
+ .create({
907
+ profiles: {
908
+ costPriority: { cost: 0.9, speed: 0.1 },
909
+ balanced: { cost: 0.5, speed: 0.3, accuracy: 0.2 },
910
+ performance: { cost: 0.1, speed: 0.5, accuracy: 0.4 }
911
+ }
912
+ });
913
+
914
+ // Reference profile in metadata
915
+ await ai.chat.get(messages, {
916
+ metadata: {
917
+ weights: config.profiles.costPriority
918
+ }
919
+ });
920
+ ```
921
+
922
+ ### Model Sources
923
+
924
+ Enrich model information from external registries:
925
+
926
+ ```typescript
927
+ import { openRouterSource } from '@aeye/openrouter';
928
+
929
+ const ai = AI.with()
930
+ .providers({ openai, anthropic, openrouter })
931
+ .create({
932
+ // Use OpenRouter as a model source to get pricing/capabilities
933
+ modelSources: [openRouterSource],
934
+
935
+ // Or configure it explicitly
936
+ fetchOpenRouterModels: {
937
+ enabled: true,
938
+ apiKey: process.env.OPENROUTER_API_KEY
939
+ }
940
+ });
941
+
942
+ // Models now have enriched information from OpenRouter
943
+ await ai.models.refresh();
944
+ const models = ai.models.list();
945
+ ```
946
+
947
+ ### Custom Providers
948
+
949
+ Create custom provider implementations:
950
+
951
+ ```typescript
952
+ import { Provider } from '@aeye/ai';
953
+
954
+ const customProvider: Provider<CustomConfig> = {
955
+ name: 'custom',
956
+ config: {
957
+ apiKey: process.env.CUSTOM_API_KEY,
958
+ baseURL: 'https://api.custom.ai'
959
+ },
960
+
961
+ async listModels() {
962
+ const response = await fetch(`${this.config.baseURL}/models`);
963
+ const data = await response.json();
964
+
965
+ return data.models.map(m => ({
966
+ id: m.id,
967
+ provider: 'custom',
968
+ name: m.name,
969
+ capabilities: new Set(['chat', 'streaming']),
970
+ tier: 'flagship',
971
+ pricing: {
972
+ inputTokensPer1M: m.pricing.input,
973
+ outputTokensPer1M: m.pricing.output
974
+ },
975
+ contextWindow: m.context_length
976
+ }));
977
+ },
978
+
979
+ async checkHealth() {
980
+ try {
981
+ await fetch(`${this.config.baseURL}/health`);
982
+ return true;
983
+ } catch {
984
+ return false;
985
+ }
986
+ },
987
+
988
+ createExecutor() {
989
+ return async (request, ctx, metadata) => {
990
+ const response = await fetch(`${this.config.baseURL}/chat`, {
991
+ method: 'POST',
992
+ headers: {
993
+ 'Authorization': `Bearer ${this.config.apiKey}`,
994
+ 'Content-Type': 'application/json'
995
+ },
996
+ body: JSON.stringify({
997
+ model: metadata.model,
998
+ messages: request.messages,
999
+ max_tokens: request.maxTokens,
1000
+ temperature: request.temperature
1001
+ })
1002
+ });
1003
+
1004
+ const data = await response.json();
1005
+
1006
+ return {
1007
+ content: data.choices[0].message.content,
1008
+ finishReason: data.choices[0].finish_reason,
1009
+ usage: {
1010
+ inputTokens: data.usage.prompt_tokens,
1011
+ outputTokens: data.usage.completion_tokens,
1012
+ totalTokens: data.usage.total_tokens
1013
+ }
1014
+ };
1015
+ };
1016
+ },
1017
+
1018
+ createStreamer() {
1019
+ return async function* (request, ctx, metadata) {
1020
+ const response = await fetch(`${this.config.baseURL}/chat/stream`, {
1021
+ // ... similar setup with stream: true
1022
+ });
1023
+
1024
+ const reader = response.body.getReader();
1025
+ const decoder = new TextDecoder();
1026
+
1027
+ while (true) {
1028
+ const { done, value } = await reader.read();
1029
+ if (done) break;
1030
+
1031
+ const chunk = decoder.decode(value);
1032
+ const lines = chunk.split('\n').filter(line => line.trim());
1033
+
1034
+ for (const line of lines) {
1035
+ if (line.startsWith('data: ')) {
1036
+ const data = JSON.parse(line.slice(6));
1037
+ yield {
1038
+ content: data.choices[0]?.delta?.content,
1039
+ finishReason: data.choices[0]?.finish_reason,
1040
+ usage: data.usage
1041
+ };
1042
+ }
1043
+ }
1044
+ }
1045
+ };
1046
+ }
1047
+ };
1048
+
1049
+ // Use custom provider
1050
+ const ai = AI.with()
1051
+ .providers({ custom: customProvider, openai })
1052
+ .create({});
1053
+ ```
1054
+
1055
+ ## TypeScript Guide
1056
+
1057
+ ### Type Safety
1058
+
1059
+ The library provides full TypeScript support with generic type inference:
1060
+
1061
+ ```typescript
1062
+ // Define your types
1063
+ interface MyContext {
1064
+ userId: string;
1065
+ sessionId: string;
1066
+ }
1067
+
1068
+ interface MyMetadata {
1069
+ priority: 'low' | 'high';
1070
+ }
1071
+
1072
+ // Create strongly-typed AI instance
1073
+ const ai = AI.with<MyContext, MyMetadata>()
1074
+ .providers({ openai })
1075
+ .create({
1076
+ defaultContext: {
1077
+ sessionId: 'default' // ✓ Valid
1078
+ // foo: 'bar' // ✗ TypeScript error
1079
+ }
1080
+ });
1081
+
1082
+ // Type-safe usage
1083
+ await ai.chat.get(messages, {
1084
+ userId: '123', // ✓ Required (not in default)
1085
+ sessionId: 'override', // ✓ Optional (in default)
1086
+ // invalidField: 'x' // ✗ TypeScript error
1087
+ metadata: {
1088
+ priority: 'high' // ✓ Valid enum value
1089
+ // priority: 'medium' // ✗ TypeScript error
1090
+ }
1091
+ });
1092
+ ```
1093
+
1094
+ ### Extending AI Instances
1095
+
1096
+ Create extended instances for specific use cases:
1097
+
1098
+ ```typescript
1099
+ // Base AI instance
1100
+ const baseAI = AI.with<BaseContext>()
1101
+ .providers({ openai, anthropic })
1102
+ .create({ /* ... */ });
1103
+
1104
+ // Extended for chat feature
1105
+ interface ChatContext extends BaseContext {
1106
+ chat: Chat;
1107
+ chatMessage: ChatMessage;
1108
+ }
1109
+
1110
+ const chatAI = baseAI.extend<ChatContext>({
1111
+ defaultContext: {
1112
+ // chat and chatMessage will be provided per-request
1113
+ },
1114
+ modelOverrides: [
1115
+ {
1116
+ modelPattern: /gpt/,
1117
+ overrides: {
1118
+ pricing: { /* custom pricing */ }
1119
+ }
1120
+ }
1121
+ ]
1122
+ });
1123
+
1124
+ // Use extended instance
1125
+ await chatAI.chat.get(messages, {
1126
+ chat,
1127
+ chatMessage
1128
+ });
1129
+ ```
1130
+
1131
+ ## Examples
1132
+
1133
+ ### Complete Application Example
1134
+
1135
+ ```typescript
1136
+ // types.ts
1137
+ export interface AppContext {
1138
+ user: User;
1139
+ organization: Organization;
1140
+ db: Database;
1141
+ cache: Redis;
1142
+ requestId: string;
1143
+ }
1144
+
1145
+ export interface AppMetadata {
1146
+ feature: 'chat' | 'summary' | 'analysis';
1147
+ priority: 'low' | 'normal' | 'high';
1148
+ maxCost: number;
1149
+ }
1150
+
1151
+ // ai.ts
1152
+ import { AI } from '@aeye/ai';
1153
+ import { openai } from '@aeye/openai';
1154
+ import { anthropic } from '@aeye/anthropic';
1155
+
1156
+ export const ai = AI.with<AppContext, AppMetadata>()
1157
+ .providers({ openai, anthropic })
1158
+ .create({
1159
+ defaultContext: {
1160
+ requestId: () => uuid(),
1161
+ cache: redis
1162
+ },
1163
+ providedContext: async (ctx) => {
1164
+ const user = await db.users.findById(ctx.userId);
1165
+ const org = await db.organizations.findById(user.organizationId);
1166
+ return { user, organization: org, db };
1167
+ },
1168
+ defaultMetadata: {
1169
+ priority: 'normal',
1170
+ maxCost: 0.10
1171
+ },
1172
+ profiles: {
1173
+ costPriority: { cost: 0.9, speed: 0.1 },
1174
+ balanced: { cost: 0.5, speed: 0.3, accuracy: 0.2 },
1175
+ performance: { cost: 0.1, speed: 0.5, accuracy: 0.4 }
1176
+ }
1177
+ })
1178
+ .withHooks({
1179
+ beforeRequest: async (ctx, request, selected, estimatedTokens, estimatedCost) => {
1180
+ const cost = estimateCost(tokens, selected.model);
1181
+ if (cost > ctx.user.remainingBudget) {
1182
+ throw new Error('Insufficient budget');
1183
+ }
1184
+ },
1185
+ afterRequest: async (ctx, request, response, responseComplete, selected, usage, cost) => {
1186
+ await trackUsage(ctx.user.id, usage, cost);
1187
+ },
1188
+ onError: (type, message, error, ctx) => {
1189
+ logger.error({ type, message, error, userId: ctx?.user?.id });
1190
+ }
1191
+ });
1192
+
1193
+ // features/chat.ts
1194
+ export async function handleChatMessage(
1195
+ userId: string,
1196
+ message: string
1197
+ ) {
1198
+ const response = await ai.chat.get(
1199
+ [
1200
+ { role: 'system', content: 'You are a helpful assistant.' },
1201
+ { role: 'user', content: message }
1202
+ ],
1203
+ {
1204
+ userId,
1205
+ metadata: {
1206
+ feature: 'chat',
1207
+ priority: 'normal'
1208
+ }
1209
+ }
1210
+ );
1211
+
1212
+ return response.content;
1213
+ }
1214
+
1215
+ // features/analysis.ts
1216
+ export async function analyzeDocument(
1217
+ userId: string,
1218
+ document: string
1219
+ ) {
1220
+ const response = await ai.chat.get(
1221
+ [
1222
+ { role: 'system', content: 'You are a document analysis expert.' },
1223
+ { role: 'user', content: `Analyze this document:\n\n${document}` }
1224
+ ],
1225
+ {
1226
+ userId,
1227
+ metadata: {
1228
+ feature: 'analysis',
1229
+ priority: 'high',
1230
+ required: ['chat'],
1231
+ weights: { accuracy: 0.7, cost: 0.3 }
1232
+ }
1233
+ }
1234
+ );
1235
+
1236
+ return response.content;
1237
+ }
1238
+ ```
1239
+
1240
+ ### Prompt Engineering with Context
1241
+
1242
+ ```typescript
1243
+ import { ai } from './ai';
1244
+
1245
+ const summarizer = ai.prompt({
1246
+ name: 'summarizer',
1247
+ description: 'Summarize documents',
1248
+
1249
+ input: async (params: { document: string }, ctx) => {
1250
+ // Access context in prompt construction
1251
+ const userPrefs = await ctx.db.getUserPreferences(ctx.user.id);
1252
+
1253
+ return [{
1254
+ role: 'system',
1255
+ content: `Summarize documents in ${userPrefs.language}.`
1256
+ }, {
1257
+ role: 'user',
1258
+ content: `Summarize:\n\n${params.document}`
1259
+ }];
1260
+ },
1261
+
1262
+ config: {
1263
+ maxTokens: 500,
1264
+ temperature: 0.3
1265
+ }
1266
+ });
1267
+
1268
+ // Use prompt
1269
+ const result = await summarizer.execute(
1270
+ { document: longText },
1271
+ { userId: '123' }
1272
+ );
1273
+ ```
1274
+
1275
+ ### Multi-Step Agent
1276
+
1277
+ ```typescript
1278
+ const researchAgent = ai.agent({
1279
+ name: 'researcher',
1280
+ description: 'Research topics using multiple AI calls',
1281
+
1282
+ call: async (input: { topic: string }, refs, ctx) => {
1283
+ // Step 1: Generate research questions
1284
+ const questions = await ai.chat.get([{
1285
+ role: 'user',
1286
+ content: `Generate 3 research questions about: ${input.topic}`
1287
+ }], ctx);
1288
+
1289
+ // Step 2: Answer each question
1290
+ const answers = [];
1291
+ for (const question of parseQuestions(questions.content)) {
1292
+ const answer = await ai.chat.get([{
1293
+ role: 'user',
1294
+ content: question
1295
+ }], ctx);
1296
+ answers.push({ question, answer: answer.content });
1297
+ }
1298
+
1299
+ // Step 3: Synthesize final report
1300
+ const report = await ai.chat.get([{
1301
+ role: 'user',
1302
+ content: `Create a research report from these Q&As:\n${JSON.stringify(answers, null, 2)}`
1303
+ }], ctx);
1304
+
1305
+ return report.content;
1306
+ }
1307
+ });
1308
+
1309
+ const report = await researchAgent.execute(
1310
+ { topic: 'Quantum Computing' },
1311
+ { userId: '123' }
1312
+ );
1313
+ ```
1314
+
1315
+ ## License
1316
+
1317
+ MIT
1318
+
1319
+ ---
1320
+
1321
+ For more information, visit:
1322
+ - GitHub: https://github.com/yourusername/aeye
1323
+ - Documentation: https://aeye.dev
1324
+ - Discord: https://discord.gg/aeye