@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.
- package/README.md +1324 -0
- package/dist/ai.d.ts +204 -0
- package/dist/ai.d.ts.map +1 -0
- package/dist/ai.js +837 -0
- package/dist/ai.js.map +1 -0
- package/dist/apis/base.d.ts +181 -0
- package/dist/apis/base.d.ts.map +1 -0
- package/dist/apis/base.js +359 -0
- package/dist/apis/base.js.map +1 -0
- package/dist/apis/chat.d.ts +82 -0
- package/dist/apis/chat.d.ts.map +1 -0
- package/dist/apis/chat.js +214 -0
- package/dist/apis/chat.js.map +1 -0
- package/dist/apis/embed.d.ts +27 -0
- package/dist/apis/embed.d.ts.map +1 -0
- package/dist/apis/embed.js +70 -0
- package/dist/apis/embed.js.map +1 -0
- package/dist/apis/image.d.ts +77 -0
- package/dist/apis/image.d.ts.map +1 -0
- package/dist/apis/image.js +313 -0
- package/dist/apis/image.js.map +1 -0
- package/dist/apis/models.d.ts +108 -0
- package/dist/apis/models.d.ts.map +1 -0
- package/dist/apis/models.js +118 -0
- package/dist/apis/models.js.map +1 -0
- package/dist/apis/speech.d.ts +27 -0
- package/dist/apis/speech.d.ts.map +1 -0
- package/dist/apis/speech.js +79 -0
- package/dist/apis/speech.js.map +1 -0
- package/dist/apis/transcribe.d.ts +26 -0
- package/dist/apis/transcribe.d.ts.map +1 -0
- package/dist/apis/transcribe.js +103 -0
- package/dist/apis/transcribe.js.map +1 -0
- package/dist/common.d.ts +10 -0
- package/dist/common.d.ts.map +1 -0
- package/dist/common.js +17 -0
- package/dist/common.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/modelDetection.d.ts +31 -0
- package/dist/modelDetection.d.ts.map +1 -0
- package/dist/modelDetection.js +78 -0
- package/dist/modelDetection.js.map +1 -0
- package/dist/registry.d.ts +150 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +639 -0
- package/dist/registry.js.map +1 -0
- package/dist/types.d.ts +767 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- 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
|