@aeye/ai 0.4.0 → 0.7.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 +898 -876
- package/aeye-ai.md +237 -219
- package/dist/ai.js +1 -1
- package/dist/ai.js.map +1 -1
- package/dist/types.d.ts +25 -19
- package/dist/types.d.ts.map +1 -1
- package/package.json +67 -67
package/README.md
CHANGED
|
@@ -1,876 +1,898 @@
|
|
|
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 with automatic model selection, cost tracking, and extensible architecture.
|
|
6
|
-
|
|
7
|
-
```ts
|
|
8
|
-
import { AI } from '@aeye/ai';
|
|
9
|
-
import { OpenAIProvider } from '@aeye/openai';
|
|
10
|
-
import z from 'zod';
|
|
11
|
-
|
|
12
|
-
const openai = new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY! });
|
|
13
|
-
|
|
14
|
-
const ai = AI.with<MyContext>()
|
|
15
|
-
.providers({ openai })
|
|
16
|
-
.create({ /* defaultContext, providedContext, defaultWeights,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
ai.
|
|
21
|
-
ai.image.
|
|
22
|
-
ai.image.
|
|
23
|
-
ai.
|
|
24
|
-
ai.
|
|
25
|
-
ai.
|
|
26
|
-
ai.
|
|
27
|
-
ai.
|
|
28
|
-
ai.
|
|
29
|
-
ai.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
myPrompt.
|
|
39
|
-
myPrompt.get('
|
|
40
|
-
myPrompt.get('
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
- **
|
|
48
|
-
- **
|
|
49
|
-
- **
|
|
50
|
-
- **
|
|
51
|
-
- **
|
|
52
|
-
- **
|
|
53
|
-
- **
|
|
54
|
-
- **
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
- [
|
|
60
|
-
- [
|
|
61
|
-
- [
|
|
62
|
-
- [
|
|
63
|
-
|
|
64
|
-
- [
|
|
65
|
-
- [
|
|
66
|
-
- [
|
|
67
|
-
- [
|
|
68
|
-
- [
|
|
69
|
-
- [
|
|
70
|
-
- [
|
|
71
|
-
- [
|
|
72
|
-
- [
|
|
73
|
-
- [
|
|
74
|
-
|
|
75
|
-
- [
|
|
76
|
-
- [
|
|
77
|
-
- [Model
|
|
78
|
-
- [
|
|
79
|
-
- [
|
|
80
|
-
- [
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
npm install @aeye/
|
|
93
|
-
npm install @aeye/
|
|
94
|
-
npm install @aeye/
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
import {
|
|
102
|
-
import {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
AI -->
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
- **
|
|
150
|
-
- **
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
.
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
db,
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
{
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
.
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
{
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
{ role: '
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
console.log(response.
|
|
221
|
-
console.log(response.usage?.text?.
|
|
222
|
-
console.log(response.usage?.
|
|
223
|
-
console.log(response.
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
{ type: '
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
}
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
**`image.
|
|
287
|
-
|
|
288
|
-
```typescript
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
```
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
const response = await ai.
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
)
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
const
|
|
402
|
-
|
|
403
|
-
console.log(
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
}
|
|
483
|
-
```
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
```typescript
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
});
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
},
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
{
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
.
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
.
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
});
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
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 with automatic model selection, cost tracking, and extensible architecture.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { AI } from '@aeye/ai';
|
|
9
|
+
import { OpenAIProvider } from '@aeye/openai';
|
|
10
|
+
import z from 'zod';
|
|
11
|
+
|
|
12
|
+
const openai = new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY! });
|
|
13
|
+
|
|
14
|
+
const ai = AI.with<MyContext>()
|
|
15
|
+
.providers({ openai })
|
|
16
|
+
.create({ /* defaultContext, providedContext, defaultWeights, ... */ })
|
|
17
|
+
.withHooks({ /* beforeRequest, afterRequest, afterBatch, onError, ... */ });
|
|
18
|
+
|
|
19
|
+
// Low-level APIs
|
|
20
|
+
ai.chat.get(request, ctx?) // or .stream(request, ctx?)
|
|
21
|
+
ai.image.generate.get(request, ctx?) // or .stream(request, ctx?)
|
|
22
|
+
ai.image.edit.get(request, ctx?) // or .stream(request, ctx?)
|
|
23
|
+
ai.image.analyze.get(request, ctx?) // or .stream(request, ctx?)
|
|
24
|
+
ai.transcribe.get(request, ctx?) // or .stream(request, ctx?)
|
|
25
|
+
ai.speech.get(request, ctx?) // or .stream(request, ctx?)
|
|
26
|
+
ai.embed.get(request, ctx?)
|
|
27
|
+
ai.rerank.get(request, ctx?) // order documents by relevance
|
|
28
|
+
ai.decide.get(request, ctx?) // typed answers from decision models
|
|
29
|
+
ai.batch.submit(requests, ctx) // .get(job, ctx), .wait(job, ctx, options?)
|
|
30
|
+
ai.models.list() // .get(id), .search(criteria), .select(criteria), .refresh()
|
|
31
|
+
|
|
32
|
+
// Components bound to this AI instance
|
|
33
|
+
const myTool = ai.tool({ name, description, schema, call, ... });
|
|
34
|
+
const myPrompt = ai.prompt({ name, description, content, tools, schema, ... });
|
|
35
|
+
const myAgent = ai.agent({ name, description, refs, call });
|
|
36
|
+
|
|
37
|
+
myTool.run(input, ctx?)
|
|
38
|
+
myPrompt.run(input, ctx?) // streaming generator
|
|
39
|
+
myPrompt.get('result', input, ctx?) // awaitable result
|
|
40
|
+
myPrompt.get('stream', input, ctx?) // stream all events
|
|
41
|
+
myPrompt.get('streamContent', input, ctx?) // stream text only
|
|
42
|
+
myAgent.run(input, ctx?)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Features
|
|
46
|
+
|
|
47
|
+
- **Multi-Provider Support**: Single interface for OpenAI, OpenRouter, Replicate, AWS Bedrock, and custom providers
|
|
48
|
+
- **Intelligent Model Selection**: Automatic model selection based on capabilities, cost, speed, and quality
|
|
49
|
+
- **Type-Safe Context**: Strongly-typed context and metadata with compiler validation
|
|
50
|
+
- **Comprehensive APIs**: Chat, Image Generation/Analysis/Editing, Speech Synthesis, Transcription, Embeddings, Reranking, Decisions, Batches
|
|
51
|
+
- **Lifecycle Hooks**: Intercept and modify operations at every stage
|
|
52
|
+
- **Cost Tracking**: Automatic token usage and cost calculation
|
|
53
|
+
- **Streaming Support**: Full streaming support across all compatible capabilities
|
|
54
|
+
- **Model Registry**: Centralized model management with external sources (OpenRouter, etc.)
|
|
55
|
+
- **Extensible**: Custom providers, model handlers, and transformers
|
|
56
|
+
|
|
57
|
+
## Table of Contents
|
|
58
|
+
|
|
59
|
+
- [Installation](#installation)
|
|
60
|
+
- [Quick Start](#quick-start)
|
|
61
|
+
- [Architecture](#architecture)
|
|
62
|
+
- [Core Concepts](#core-concepts)
|
|
63
|
+
- [API Reference](#api-reference)
|
|
64
|
+
- [Chat API](#chat-api)
|
|
65
|
+
- [Image API](#image-api)
|
|
66
|
+
- [Speech API](#speech-api)
|
|
67
|
+
- [Transcribe API](#transcribe-api)
|
|
68
|
+
- [Embed API](#embed-api)
|
|
69
|
+
- [Rerank API](#rerank-api)
|
|
70
|
+
- [Decide API](#decide-api)
|
|
71
|
+
- [Batch API](#batch-api)
|
|
72
|
+
- [Models API](#models-api)
|
|
73
|
+
- [Tools, Prompts & Agents](#tools-prompts--agents)
|
|
74
|
+
- [Advanced Features](#advanced-features)
|
|
75
|
+
- [Custom Context & Metadata](#custom-context--metadata)
|
|
76
|
+
- [Lifecycle Hooks](#lifecycle-hooks)
|
|
77
|
+
- [Model Selection](#model-selection)
|
|
78
|
+
- [Model Sources](#model-sources)
|
|
79
|
+
- [Custom Providers](#custom-providers)
|
|
80
|
+
- [Extending AI Instances](#extending-ai-instances)
|
|
81
|
+
- [Examples](#examples)
|
|
82
|
+
|
|
83
|
+
## Installation
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
npm install @aeye/ai @aeye/core
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
You'll also need provider packages:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
npm install @aeye/openai openai # OpenAI
|
|
93
|
+
npm install @aeye/openrouter # OpenRouter
|
|
94
|
+
npm install @aeye/replicate replicate # Replicate
|
|
95
|
+
npm install @aeye/aws # AWS Bedrock
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Quick Start
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
import { AI } from '@aeye/ai';
|
|
102
|
+
import { OpenAIProvider } from '@aeye/openai';
|
|
103
|
+
import { OpenRouterProvider } from '@aeye/openrouter';
|
|
104
|
+
|
|
105
|
+
const openai = new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY! });
|
|
106
|
+
const openrouter = new OpenRouterProvider({ apiKey: process.env.OPENROUTER_API_KEY! });
|
|
107
|
+
|
|
108
|
+
const ai = AI.with()
|
|
109
|
+
.providers({ openai, openrouter })
|
|
110
|
+
.create();
|
|
111
|
+
|
|
112
|
+
// Simple chat completion
|
|
113
|
+
const response = await ai.chat.get({
|
|
114
|
+
messages: [{ role: 'user', content: 'What is TypeScript?' }]
|
|
115
|
+
});
|
|
116
|
+
console.log(response.content);
|
|
117
|
+
|
|
118
|
+
// Streaming
|
|
119
|
+
for await (const chunk of ai.chat.stream({
|
|
120
|
+
messages: [{ role: 'user', content: 'Write a poem' }]
|
|
121
|
+
})) {
|
|
122
|
+
if (chunk.content) {
|
|
123
|
+
process.stdout.write(chunk.content);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Architecture
|
|
129
|
+
|
|
130
|
+
```mermaid
|
|
131
|
+
graph TD
|
|
132
|
+
AI["<b>AI Class</b><br/>Context Management<br/>Model Registry<br/>Lifecycle Hooks"]
|
|
133
|
+
APIs["<b>APIs</b><br/>Chat · Image<br/>Speech · Embed"]
|
|
134
|
+
Registry["<b>Registry</b><br/>Models · Search · Select"]
|
|
135
|
+
Providers["<b>Providers</b><br/>OpenAI · OpenRouter<br/>Replicate · AWS · Custom"]
|
|
136
|
+
|
|
137
|
+
AI --> APIs
|
|
138
|
+
AI --> Registry
|
|
139
|
+
Registry --> Providers
|
|
140
|
+
APIs --> Providers
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Core Concepts
|
|
144
|
+
|
|
145
|
+
### Context
|
|
146
|
+
|
|
147
|
+
Context is data passed through your entire AI operation:
|
|
148
|
+
|
|
149
|
+
- **Default Context**: Static values provided at AI creation via `defaultContext`
|
|
150
|
+
- **Provided Context**: Async-loaded values (e.g., from a database) via `providedContext`
|
|
151
|
+
- **Required Context**: Values that must be supplied per-call
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
interface AppContext {
|
|
155
|
+
userId: string;
|
|
156
|
+
user?: User; // loaded by providedContext
|
|
157
|
+
db?: Database; // loaded by providedContext
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const ai = AI.with<AppContext>()
|
|
161
|
+
.providers({ openai })
|
|
162
|
+
.create({
|
|
163
|
+
providedContext: async (ctx) => ({
|
|
164
|
+
user: await db.users.findById(ctx.userId),
|
|
165
|
+
db,
|
|
166
|
+
}),
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// At call time only userId is required — the rest is loaded automatically
|
|
170
|
+
const response = await ai.chat.get(
|
|
171
|
+
{ messages: [{ role: 'user', content: 'Hi!' }] },
|
|
172
|
+
{ userId: 'user123' }
|
|
173
|
+
);
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Metadata
|
|
177
|
+
|
|
178
|
+
Metadata controls model selection and per-request configuration:
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
interface AppMetadata {
|
|
182
|
+
priority: 'cost' | 'speed' | 'quality';
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const ai = AI.with<{}, AppMetadata>()
|
|
186
|
+
.providers({ openai, openrouter })
|
|
187
|
+
.create({
|
|
188
|
+
defaultMetadata: { priority: 'cost' },
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// Override per request via context
|
|
192
|
+
const response = await ai.chat.get(
|
|
193
|
+
{ messages: [{ role: 'user', content: 'Hello' }] },
|
|
194
|
+
{
|
|
195
|
+
metadata: {
|
|
196
|
+
model: 'openai/gpt-4o', // pin a specific model
|
|
197
|
+
weights: { cost: 0.2, speed: 0.3, accuracy: 0.5 },
|
|
198
|
+
priority: 'quality',
|
|
199
|
+
},
|
|
200
|
+
}
|
|
201
|
+
);
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## API Reference
|
|
205
|
+
|
|
206
|
+
### Chat API
|
|
207
|
+
|
|
208
|
+
**`chat.get(request, ctx?)`** — Non-streaming chat completion.
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
const response = await ai.chat.get({
|
|
212
|
+
messages: [
|
|
213
|
+
{ role: 'system', content: 'You are a helpful assistant.' },
|
|
214
|
+
{ role: 'user', content: 'Hello!' },
|
|
215
|
+
],
|
|
216
|
+
temperature: 0.7,
|
|
217
|
+
maxTokens: 1024,
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
console.log(response.content);
|
|
221
|
+
console.log(response.usage?.text?.input, 'input tokens');
|
|
222
|
+
console.log(response.usage?.text?.output, 'output tokens');
|
|
223
|
+
console.log(response.usage?.cost, 'cost in dollars');
|
|
224
|
+
console.log(response.finishReason); // 'stop' | 'length' | 'tool_calls' | ...
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
**`chat.stream(request, ctx?)`** — Streaming chat completion.
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
for await (const chunk of ai.chat.stream({
|
|
231
|
+
messages: [{ role: 'user', content: 'Count to 10' }]
|
|
232
|
+
})) {
|
|
233
|
+
if (chunk.content) {
|
|
234
|
+
process.stdout.write(chunk.content);
|
|
235
|
+
}
|
|
236
|
+
if (chunk.finishReason) {
|
|
237
|
+
console.log('\nFinished:', chunk.finishReason);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
**Vision:**
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
const response = await ai.chat.get({
|
|
246
|
+
messages: [{
|
|
247
|
+
role: 'user',
|
|
248
|
+
content: [
|
|
249
|
+
{ type: 'text', content: 'What is in this image?' },
|
|
250
|
+
{ type: 'image', content: 'https://example.com/image.jpg' },
|
|
251
|
+
],
|
|
252
|
+
}],
|
|
253
|
+
});
|
|
254
|
+
// vision capability is detected automatically from the message content
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
**Reasoning:**
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
const response = await ai.chat.get({
|
|
261
|
+
messages: [{ role: 'user', content: 'Solve step by step: 15! / 13!' }],
|
|
262
|
+
reason: { effort: 'low' },
|
|
263
|
+
});
|
|
264
|
+
console.log(response.reasoning?.content);
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
The effort is clamped to the nearest one the model accepts (`clampReasoningEffort`), and reasoning asked for stays on: `none` is only sent when asked for or when nothing else is accepted. Keep the assistant message's `reasoning` in the conversation: each provider replays only the reasoning it produced (`Reasoning.provider`) or that names none, so a conversation can move between providers. See the [reasoning guide](../docs/guides/reasoning.md).
|
|
268
|
+
|
|
269
|
+
### Image API
|
|
270
|
+
|
|
271
|
+
**`image.generate.get(request, ctx?)`** — Generate images from a text prompt.
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
const response = await ai.image.generate.get({
|
|
275
|
+
prompt: 'A futuristic city at sunset',
|
|
276
|
+
n: 2,
|
|
277
|
+
size: '1024x1024',
|
|
278
|
+
quality: 'high',
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
for (const image of response.images) {
|
|
282
|
+
console.log(image.url ?? image.b64_json);
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
**`image.edit.get(request, ctx?)`** — Edit an image with inpainting.
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
import fs from 'fs';
|
|
290
|
+
|
|
291
|
+
const response = await ai.image.edit.get({
|
|
292
|
+
prompt: 'Add a sunset in the background',
|
|
293
|
+
image: fs.readFileSync('photo.png'),
|
|
294
|
+
mask: fs.readFileSync('mask.png'),
|
|
295
|
+
size: '1024x1024',
|
|
296
|
+
});
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
**`image.analyze.get(request, ctx?)`** — Analyze image content (uses a vision chat model).
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
const response = await ai.image.analyze.get({
|
|
303
|
+
prompt: 'Describe this image in detail',
|
|
304
|
+
images: ['https://example.com/photo.jpg'],
|
|
305
|
+
});
|
|
306
|
+
console.log(response.content);
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Speech API
|
|
310
|
+
|
|
311
|
+
**`speech.get(request, ctx?)`** — Text-to-speech synthesis.
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
import fs from 'fs';
|
|
315
|
+
import { Readable } from 'stream';
|
|
316
|
+
|
|
317
|
+
const response = await ai.speech.get({
|
|
318
|
+
text: 'Hello, this is a text-to-speech example.',
|
|
319
|
+
voice: 'alloy',
|
|
320
|
+
speed: 1.0,
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
// Pipe the audio stream to a file
|
|
324
|
+
const fileStream = fs.createWriteStream('output.mp3');
|
|
325
|
+
Readable.fromWeb(response.audio).pipe(fileStream);
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### Transcribe API
|
|
329
|
+
|
|
330
|
+
**`transcribe.get(request, ctx?)`** — Speech-to-text transcription.
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
import fs from 'fs';
|
|
334
|
+
|
|
335
|
+
const response = await ai.transcribe.get({
|
|
336
|
+
audio: fs.readFileSync('recording.mp3'),
|
|
337
|
+
language: 'en',
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
console.log(response.text);
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### Embed API
|
|
344
|
+
|
|
345
|
+
**`embed.get(request, ctx?)`** — Generate text embeddings.
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
const response = await ai.embed.get({
|
|
349
|
+
texts: [
|
|
350
|
+
'TypeScript is a typed superset of JavaScript',
|
|
351
|
+
'Python is a high-level programming language',
|
|
352
|
+
],
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
response.embeddings.forEach(({ embedding, index }) => {
|
|
356
|
+
console.log(`Text ${index}: ${embedding.length} dimensions`);
|
|
357
|
+
});
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### Rerank API
|
|
361
|
+
|
|
362
|
+
**`rerank.get(request, ctx?)`** — Order documents by relevance to a query. Needs a model with the `rerank` capability (OpenRouter: Cohere, Voyage, Qwen, Nemotron).
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
const documents = ['Paris is the capital of France.', 'Berlin is the capital of Germany.'];
|
|
366
|
+
const { results } = await ai.rerank.get({ query: 'capital of France', documents, topN: 1 });
|
|
367
|
+
|
|
368
|
+
console.log(documents[results[0].index], results[0].score);
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
### Decide API
|
|
372
|
+
|
|
373
|
+
**`decide.get(request, ctx?)`** — Typed answers about text or a conversation from a decision model (`decision` capability; OpenAI `gpt-6-luna`, OpenRouter System One models). Questions are `predicate` (probability), `choice` (one of a set), or `score` (ordered levels). Much faster and cheaper than structured output for classification and routing.
|
|
374
|
+
|
|
375
|
+
```typescript
|
|
376
|
+
const { answers } = await ai.decide.get({
|
|
377
|
+
input: 'I was charged twice for my order.',
|
|
378
|
+
questions: {
|
|
379
|
+
billing: { type: 'predicate', instructions: 'Is this a billing problem?' },
|
|
380
|
+
team: { type: 'choice', instructions: 'Which team handles it?', choices: { payments: 'Charges and refunds', web: 'Website bugs' } },
|
|
381
|
+
urgency: { type: 'score', instructions: 'How urgent is it?', levels: ['Can wait', 'This week', 'Now'] },
|
|
382
|
+
},
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
const team = answers.team;
|
|
386
|
+
if (team.type === 'choice') {
|
|
387
|
+
console.log(team.choice, team.confidence);
|
|
388
|
+
}
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### Batch API
|
|
392
|
+
|
|
393
|
+
**`batch.submit(requests, ctx)`** — Run chat requests as one provider batch (OpenAI, OpenRouter): about half the price, with results within a day. `batch.get(job, ctx)` checks it; `batch.wait(job, ctx, { interval?, signal? })` waits for it to finish. Results come back in request order, and a failed request carries `error`. `submit` runs the `onModelSelected` hook once and `beforeRequest` for each request at the batch price estimate (so a budget check can stop the batch), and a completed batch is reported once to `afterBatch` with its usage and cost.
|
|
394
|
+
|
|
395
|
+
```typescript
|
|
396
|
+
const job = await ai.batch.submit(
|
|
397
|
+
reviews.map(review => ({ messages: [{ role: 'user', content: `Classify: ${review}` }] })),
|
|
398
|
+
{},
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
const done = await ai.batch.wait(job, {});
|
|
402
|
+
for (const result of done.results ?? []) {
|
|
403
|
+
console.log(result.response?.content ?? result.error);
|
|
404
|
+
}
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### Models API
|
|
408
|
+
|
|
409
|
+
```typescript
|
|
410
|
+
// List all registered models
|
|
411
|
+
const models = ai.models.list();
|
|
412
|
+
|
|
413
|
+
// Get a specific model
|
|
414
|
+
const model = ai.models.get('gpt-4o');
|
|
415
|
+
if (model) {
|
|
416
|
+
console.log('Context window:', model.contextWindow);
|
|
417
|
+
console.log('Capabilities:', Array.from(model.capabilities).join(', '));
|
|
418
|
+
console.log('Input cost:', model.pricing.text?.input, 'per 1M tokens');
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Search with selection criteria
|
|
422
|
+
const results = ai.models.search({
|
|
423
|
+
capabilities: new Set(['chat', 'structured']),
|
|
424
|
+
weights: { cost: 0.6, speed: 0.4 },
|
|
425
|
+
providers: { allow: ['openai', 'openrouter'] },
|
|
426
|
+
contextWindow: { min: 100000 },
|
|
427
|
+
});
|
|
428
|
+
console.log('Best match:', results[0]?.model.id);
|
|
429
|
+
|
|
430
|
+
// Refresh model list from all sources
|
|
431
|
+
await ai.models.refresh();
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
## Tools, Prompts & Agents
|
|
435
|
+
|
|
436
|
+
Use `ai.tool()`, `ai.prompt()`, and `ai.agent()` to create components that are automatically wired to the AI instance (executor, streamer, and context are injected automatically).
|
|
437
|
+
|
|
438
|
+
### Tool
|
|
439
|
+
|
|
440
|
+
```typescript
|
|
441
|
+
import z from 'zod';
|
|
442
|
+
|
|
443
|
+
const searchKnowledge = ai.tool({
|
|
444
|
+
name: 'searchKnowledge',
|
|
445
|
+
description: 'Search the knowledge base for relevant information',
|
|
446
|
+
instructions: 'Use this to find information relevant to the query: "{{query}}"',
|
|
447
|
+
schema: z.object({
|
|
448
|
+
query: z.string().describe('Search query'),
|
|
449
|
+
limit: z.number().int().min(1).max(20).optional().describe('Max results (default: 5)'),
|
|
450
|
+
}),
|
|
451
|
+
call: async ({ query, limit = 5 }, _refs, ctx) => {
|
|
452
|
+
// ctx is the full AI context including your custom fields
|
|
453
|
+
return { results: await vectorSearch(query, limit) };
|
|
454
|
+
},
|
|
455
|
+
});
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
### Prompt
|
|
459
|
+
|
|
460
|
+
```typescript
|
|
461
|
+
const knowledgeAssistant = ai.prompt({
|
|
462
|
+
name: 'knowledgeAssistant',
|
|
463
|
+
description: 'Answers questions using the knowledge base',
|
|
464
|
+
content: `You are a helpful assistant. Use searchKnowledge to find information, then answer the question.
|
|
465
|
+
|
|
466
|
+
Question: {{question}}`,
|
|
467
|
+
input: (input: { question: string }) => ({ question: input.question }),
|
|
468
|
+
tools: [searchKnowledge],
|
|
469
|
+
schema: z.object({
|
|
470
|
+
answer: z.string(),
|
|
471
|
+
sources: z.array(z.string()),
|
|
472
|
+
}),
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
// Get structured result
|
|
476
|
+
const result = await knowledgeAssistant.get('result', { question: 'How does X work?' });
|
|
477
|
+
console.log(result?.answer);
|
|
478
|
+
|
|
479
|
+
// Stream text
|
|
480
|
+
for await (const text of knowledgeAssistant.get('streamContent', { question: 'How does X work?' })) {
|
|
481
|
+
process.stdout.write(text);
|
|
482
|
+
}
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
A prompt can `review` its valid output, for example with another prompt, and send feedback for another round (up to `reviewRetries`):
|
|
486
|
+
|
|
487
|
+
```typescript
|
|
488
|
+
const checked = ai.prompt({
|
|
489
|
+
name: 'checked',
|
|
490
|
+
description: 'Answers, then checks the answer cites a source',
|
|
491
|
+
content: 'Answer with a source: {{question}}',
|
|
492
|
+
input: (input: { question: string }) => input,
|
|
493
|
+
schema: z.object({ answer: z.string(), sources: z.array(z.string()) }),
|
|
494
|
+
review: ({ sources }) => sources.length ? { pass: true } : { feedback: 'Cite at least one source.' },
|
|
495
|
+
});
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
When a provider rejects a schema, the prompt steps down its schema fallbacks (non-strict, then JSON mode, then text) instead of failing. The chat API sends a `'json'` or `'prompt'` schema as JSON mode or prompt text, and picks a model with the `json` capability rather than `structured` for it. Set the steps AI-wide through the default context:
|
|
499
|
+
|
|
500
|
+
```typescript
|
|
501
|
+
import { SCHEMA_FALLBACKS, type SchemaFallback } from '@aeye/core';
|
|
502
|
+
|
|
503
|
+
const ai = AI.with<{ schemaFallbacks?: readonly SchemaFallback[] }>()
|
|
504
|
+
.providers({ openai })
|
|
505
|
+
.create({ defaultContext: { schemaFallbacks: SCHEMA_FALLBACKS.filter(step => step.name !== 'json') } });
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
Run without a conversation, a prompt stays the system message and adds a user turn (`userTurn` on the prompt or context, default `'Begin.'`), since providers need one.
|
|
509
|
+
|
|
510
|
+
The prompt's callbacks (`validate`, `review`, `onToolIntercept`, and the rest) receive the full AI context.
|
|
511
|
+
|
|
512
|
+
Tool errors and thrown prompt errors carry `kind: 'model' | 'tool'`, telling a model's mistake from a tool's own failure; see the core README.
|
|
513
|
+
|
|
514
|
+
### Agent
|
|
515
|
+
|
|
516
|
+
```typescript
|
|
517
|
+
const chatAgent = ai.agent({
|
|
518
|
+
name: 'chatAgent',
|
|
519
|
+
description: 'Chat with the user and analyze the response quality',
|
|
520
|
+
refs: [searchKnowledge, knowledgeAssistant] as const,
|
|
521
|
+
call: async (
|
|
522
|
+
input: { userId: string; question: string },
|
|
523
|
+
[search, assistant],
|
|
524
|
+
ctx
|
|
525
|
+
) => {
|
|
526
|
+
// Step 1: Search for relevant context
|
|
527
|
+
const { results } = await search.run({ query: input.question }, ctx);
|
|
528
|
+
|
|
529
|
+
// Step 2: Generate answer with knowledge context
|
|
530
|
+
const answer = await assistant.get(
|
|
531
|
+
'result',
|
|
532
|
+
{ question: input.question },
|
|
533
|
+
{ ...ctx, messages: [] }
|
|
534
|
+
);
|
|
535
|
+
|
|
536
|
+
return {
|
|
537
|
+
userId: input.userId,
|
|
538
|
+
question: input.question,
|
|
539
|
+
answer: answer?.answer,
|
|
540
|
+
sources: answer?.sources ?? [],
|
|
541
|
+
contextUsed: results.length,
|
|
542
|
+
};
|
|
543
|
+
},
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
const result = await chatAgent.run({ userId: 'user1', question: 'What is aeye?' });
|
|
547
|
+
console.log(result.answer);
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
## Advanced Features
|
|
551
|
+
|
|
552
|
+
### Custom Context & Metadata
|
|
553
|
+
|
|
554
|
+
```typescript
|
|
555
|
+
interface AppContext {
|
|
556
|
+
userId: string;
|
|
557
|
+
user?: User;
|
|
558
|
+
db?: Database;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
interface AppMetadata {
|
|
562
|
+
feature: 'chat' | 'analysis';
|
|
563
|
+
priority: 'low' | 'normal' | 'high';
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const ai = AI.with<AppContext, AppMetadata>()
|
|
567
|
+
.providers({ openai, openrouter })
|
|
568
|
+
.create({
|
|
569
|
+
providedContext: async (ctx) => {
|
|
570
|
+
const user = await db.users.findById(ctx.userId);
|
|
571
|
+
return { user, db };
|
|
572
|
+
},
|
|
573
|
+
defaultMetadata: {
|
|
574
|
+
priority: 'normal',
|
|
575
|
+
},
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
// At call time only userId is required
|
|
579
|
+
const response = await ai.chat.get(
|
|
580
|
+
{ messages: [{ role: 'user', content: 'Hello' }] },
|
|
581
|
+
{ userId: '123', metadata: { feature: 'chat' } }
|
|
582
|
+
);
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
### Lifecycle Hooks
|
|
586
|
+
|
|
587
|
+
```typescript
|
|
588
|
+
const ai = AI.with<AppContext>()
|
|
589
|
+
.providers({ openai })
|
|
590
|
+
.create({ /* ... */ })
|
|
591
|
+
.withHooks({
|
|
592
|
+
beforeModelSelection: async (ctx, request, metadata) => {
|
|
593
|
+
// Adjust weights based on context (return modified metadata)
|
|
594
|
+
if (ctx.user?.tier === 'free') {
|
|
595
|
+
return { ...metadata, weights: { cost: 1.0 } };
|
|
596
|
+
}
|
|
597
|
+
return metadata;
|
|
598
|
+
},
|
|
599
|
+
|
|
600
|
+
onModelSelected: async (ctx, request, selected) => {
|
|
601
|
+
console.log(`Selected: ${selected.model.id} (score: ${selected.score.toFixed(3)})`);
|
|
602
|
+
// Return a modified selected model to override, or undefined to accept
|
|
603
|
+
},
|
|
604
|
+
|
|
605
|
+
beforeRequest: async (ctx, request, selected, estimatedUsage, estimatedCost) => {
|
|
606
|
+
// Throw to cancel the request
|
|
607
|
+
if (ctx.user && estimatedCost > ctx.user.budgetRemaining) {
|
|
608
|
+
throw new Error('Insufficient budget');
|
|
609
|
+
}
|
|
610
|
+
console.log(
|
|
611
|
+
`[${ctx.userId}] ${selected.model.id} — ` +
|
|
612
|
+
`~${estimatedUsage.text?.input ?? 0} input tokens, ~$${estimatedCost.toFixed(5)}`
|
|
613
|
+
);
|
|
614
|
+
},
|
|
615
|
+
|
|
616
|
+
afterBatch: async (ctx, job, usage, cost) => {
|
|
617
|
+
// A completed batch, reported once with its total usage and cost
|
|
618
|
+
console.log(`[${ctx.userId}] batch ${job.id}: $${cost.toFixed(5)}`);
|
|
619
|
+
},
|
|
620
|
+
|
|
621
|
+
afterRequest: async (ctx, request, response, responseComplete, selected, usage, cost) => {
|
|
622
|
+
// Record actual usage
|
|
623
|
+
if (ctx.user) {
|
|
624
|
+
ctx.user.budgetRemaining -= cost;
|
|
625
|
+
ctx.user.totalSpent += cost;
|
|
626
|
+
await ctx.user.save();
|
|
627
|
+
}
|
|
628
|
+
console.log(
|
|
629
|
+
`[${ctx.userId}] ${usage.text?.input ?? 0} in / ${usage.text?.output ?? 0} out, ` +
|
|
630
|
+
`cost: $${cost.toFixed(5)}`
|
|
631
|
+
);
|
|
632
|
+
},
|
|
633
|
+
|
|
634
|
+
onError: (errorType, message, error, ctx) => {
|
|
635
|
+
console.error(`[AI ${errorType}] ${message}`, { error, userId: ctx?.userId });
|
|
636
|
+
},
|
|
637
|
+
});
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
### Model Selection
|
|
641
|
+
|
|
642
|
+
```typescript
|
|
643
|
+
// Cost-optimized
|
|
644
|
+
await ai.chat.get(
|
|
645
|
+
{ messages },
|
|
646
|
+
{ metadata: { weights: { cost: 0.9, speed: 0.05, accuracy: 0.05 } } }
|
|
647
|
+
);
|
|
648
|
+
|
|
649
|
+
// Quality-optimized with large context
|
|
650
|
+
await ai.chat.get(
|
|
651
|
+
{ messages },
|
|
652
|
+
{
|
|
653
|
+
metadata: {
|
|
654
|
+
weights: { cost: 0.1, speed: 0.4, accuracy: 0.5 },
|
|
655
|
+
contextWindow: { min: 128000 },
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
);
|
|
659
|
+
|
|
660
|
+
// Provider-specific
|
|
661
|
+
await ai.chat.get(
|
|
662
|
+
{ messages },
|
|
663
|
+
{ metadata: { providers: { allow: ['openai'], deny: ['replicate'] } } }
|
|
664
|
+
);
|
|
665
|
+
|
|
666
|
+
// Explicit model override
|
|
667
|
+
await ai.chat.get(
|
|
668
|
+
{ messages },
|
|
669
|
+
{ metadata: { model: 'openai/gpt-4o' } }
|
|
670
|
+
);
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
### Named Weight Profiles
|
|
674
|
+
|
|
675
|
+
```typescript
|
|
676
|
+
const ai = AI.with()
|
|
677
|
+
.providers({ openai, openrouter })
|
|
678
|
+
.create({
|
|
679
|
+
weightProfiles: {
|
|
680
|
+
costPriority: { cost: 0.9, speed: 0.1 },
|
|
681
|
+
balanced: { cost: 0.4, speed: 0.3, accuracy: 0.3 },
|
|
682
|
+
performance: { cost: 0.1, speed: 0.4, accuracy: 0.5 },
|
|
683
|
+
},
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
await ai.chat.get(
|
|
687
|
+
{ messages },
|
|
688
|
+
{ metadata: { weightProfile: 'balanced' } }
|
|
689
|
+
);
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
### Model Sources
|
|
693
|
+
|
|
694
|
+
```typescript
|
|
695
|
+
import { OpenRouterModelSource } from '@aeye/openrouter';
|
|
696
|
+
|
|
697
|
+
const source = new OpenRouterModelSource({ apiKey: process.env.OPENROUTER_API_KEY });
|
|
698
|
+
|
|
699
|
+
const ai = AI.with()
|
|
700
|
+
.providers({ openai, openrouter })
|
|
701
|
+
.create({
|
|
702
|
+
modelSources: [source],
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
// Fetch and register all models from sources
|
|
706
|
+
await ai.models.refresh();
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
### Model Overrides
|
|
710
|
+
|
|
711
|
+
```typescript
|
|
712
|
+
const ai = AI.with()
|
|
713
|
+
.providers({ openai })
|
|
714
|
+
.create({
|
|
715
|
+
modelOverrides: [
|
|
716
|
+
{
|
|
717
|
+
modelPattern: /gpt-4/,
|
|
718
|
+
overrides: {
|
|
719
|
+
pricing: { text: { input: 30, output: 60 } },
|
|
720
|
+
},
|
|
721
|
+
},
|
|
722
|
+
],
|
|
723
|
+
});
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
### Strict Mode
|
|
727
|
+
|
|
728
|
+
Tool and Prompt schemas can ride the LLM's grammar-constrained strict mode for guaranteed type-safe output. Mechanics — descriptors, dialects, schema rewriting — live in [`@aeye/core`](../core/README.md#strict-mode). What `@aeye/ai` adds: model selection respects the `strict` flag (selection filters to strict-capable models when `strict: true`, scores them higher when `strict: <number>`).
|
|
729
|
+
|
|
730
|
+
Splat the curated `strictSupport` overrides from `@aeye/models` so model selection knows which models actually support strict — without it, every model defaults to lenient even when the underlying API supports strict:
|
|
731
|
+
|
|
732
|
+
```typescript
|
|
733
|
+
import { models, strictSupport } from '@aeye/models';
|
|
734
|
+
|
|
735
|
+
const ai = AI.with()
|
|
736
|
+
.providers({ openai })
|
|
737
|
+
.create({
|
|
738
|
+
models,
|
|
739
|
+
modelOverrides: [...strictSupport],
|
|
740
|
+
});
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
`strictSupport` is a `ModelOverride[]` you can combine with your own overrides:
|
|
744
|
+
|
|
745
|
+
```typescript
|
|
746
|
+
modelOverrides: [
|
|
747
|
+
...strictSupport,
|
|
748
|
+
{ modelPattern: /gpt-4/, overrides: { tier: 'flagship' } },
|
|
749
|
+
],
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
### Custom Providers
|
|
753
|
+
|
|
754
|
+
Extend an existing provider or implement the `Provider<TConfig>` interface from `@aeye/core`:
|
|
755
|
+
|
|
756
|
+
```typescript
|
|
757
|
+
import { OpenAIProvider, OpenAIConfig } from '@aeye/openai';
|
|
758
|
+
import OpenAI from 'openai';
|
|
759
|
+
|
|
760
|
+
class MyProvider extends OpenAIProvider {
|
|
761
|
+
readonly name = 'my-provider';
|
|
762
|
+
|
|
763
|
+
protected createClient(config: OpenAIConfig) {
|
|
764
|
+
return new OpenAI({
|
|
765
|
+
apiKey: config.apiKey,
|
|
766
|
+
baseURL: 'https://my-api.example.com/v1',
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
### Extending AI Instances
|
|
773
|
+
|
|
774
|
+
Share providers and configuration between multiple AI instances:
|
|
775
|
+
|
|
776
|
+
```typescript
|
|
777
|
+
const baseAI = AI.with<BaseContext>()
|
|
778
|
+
.providers({ openai, openrouter })
|
|
779
|
+
.create({ /* base config */ });
|
|
780
|
+
|
|
781
|
+
// Extend with additional context for a specific feature
|
|
782
|
+
interface ChatFeatureContext extends BaseContext {
|
|
783
|
+
chatId: string;
|
|
784
|
+
chatHistory: Message[];
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
const chatAI = baseAI.extend<ChatFeatureContext>({
|
|
788
|
+
defaultContext: { chatHistory: [] },
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
const response = await chatAI.chat.get(
|
|
792
|
+
{ messages: [{ role: 'user', content: 'Hello' }] },
|
|
793
|
+
{ chatId: 'chat-123', chatHistory: [] }
|
|
794
|
+
);
|
|
795
|
+
```
|
|
796
|
+
|
|
797
|
+
## Examples
|
|
798
|
+
|
|
799
|
+
### Complete Application
|
|
800
|
+
|
|
801
|
+
```typescript
|
|
802
|
+
import { AI } from '@aeye/ai';
|
|
803
|
+
import { OpenAIProvider } from '@aeye/openai';
|
|
804
|
+
import { OpenRouterProvider } from '@aeye/openrouter';
|
|
805
|
+
import z from 'zod';
|
|
806
|
+
|
|
807
|
+
interface User {
|
|
808
|
+
id: string;
|
|
809
|
+
budgetRemaining: number;
|
|
810
|
+
totalSpent: number;
|
|
811
|
+
save: () => Promise<void>;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
interface AppContext {
|
|
815
|
+
userId: string;
|
|
816
|
+
user?: User;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
const openai = new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY! });
|
|
820
|
+
const openrouter = new OpenRouterProvider({ apiKey: process.env.OPENROUTER_API_KEY! });
|
|
821
|
+
|
|
822
|
+
export const ai = AI.with<AppContext>()
|
|
823
|
+
.providers({ openai, openrouter })
|
|
824
|
+
.create({
|
|
825
|
+
providedContext: async (ctx) => ({
|
|
826
|
+
user: await getUser(ctx.userId),
|
|
827
|
+
}),
|
|
828
|
+
defaultWeights: { cost: 0.5, speed: 0.3, accuracy: 0.2 },
|
|
829
|
+
})
|
|
830
|
+
.withHooks({
|
|
831
|
+
beforeRequest: async (ctx, _request, selected, estimatedUsage, estimatedCost) => {
|
|
832
|
+
if (ctx.user && estimatedCost > ctx.user.budgetRemaining) {
|
|
833
|
+
throw new Error(
|
|
834
|
+
`Estimated cost $${estimatedCost.toFixed(4)} exceeds budget ` +
|
|
835
|
+
`$${ctx.user.budgetRemaining.toFixed(4)}`
|
|
836
|
+
);
|
|
837
|
+
}
|
|
838
|
+
},
|
|
839
|
+
afterRequest: async (ctx, _request, _response, _complete, _selected, usage, cost) => {
|
|
840
|
+
if (ctx.user) {
|
|
841
|
+
ctx.user.budgetRemaining -= cost;
|
|
842
|
+
ctx.user.totalSpent += cost;
|
|
843
|
+
await ctx.user.save();
|
|
844
|
+
}
|
|
845
|
+
},
|
|
846
|
+
onError: (errorType, message, error, ctx) => {
|
|
847
|
+
console.error(`[AI ${errorType}] ${message}`, { userId: ctx?.userId, error });
|
|
848
|
+
},
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
// Define a tool
|
|
852
|
+
const webSearch = ai.tool({
|
|
853
|
+
name: 'webSearch',
|
|
854
|
+
description: 'Search the web for recent information',
|
|
855
|
+
schema: z.object({ query: z.string() }),
|
|
856
|
+
call: async ({ query }) => {
|
|
857
|
+
// Integrate with a search API
|
|
858
|
+
return { results: [`Result for: ${query}`] };
|
|
859
|
+
},
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
// Define a research prompt
|
|
863
|
+
const researcher = ai.prompt({
|
|
864
|
+
name: 'researcher',
|
|
865
|
+
description: 'Researches a topic and provides a structured summary',
|
|
866
|
+
content: `You are a research assistant. Research the following topic and provide a clear summary.
|
|
867
|
+
|
|
868
|
+
Topic: {{topic}}`,
|
|
869
|
+
input: (input: { topic: string }) => ({ topic: input.topic }),
|
|
870
|
+
tools: [webSearch],
|
|
871
|
+
schema: z.object({
|
|
872
|
+
summary: z.string(),
|
|
873
|
+
keyFindings: z.array(z.string()),
|
|
874
|
+
sources: z.array(z.string()),
|
|
875
|
+
}),
|
|
876
|
+
});
|
|
877
|
+
|
|
878
|
+
// Use the prompt
|
|
879
|
+
async function researchTopic(userId: string, topic: string) {
|
|
880
|
+
const result = await researcher.get('result', { topic }, { userId });
|
|
881
|
+
return result;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
const findings = await researchTopic('user123', 'TypeScript 5.0 features');
|
|
885
|
+
console.log(findings?.summary);
|
|
886
|
+
console.log(findings?.keyFindings);
|
|
887
|
+
```
|
|
888
|
+
|
|
889
|
+
## License
|
|
890
|
+
|
|
891
|
+
GPL-3.0 © [ClickerMonkey](https://github.com/ClickerMonkey)
|
|
892
|
+
|
|
893
|
+
## Links
|
|
894
|
+
|
|
895
|
+
- [Root README](../../README.md)
|
|
896
|
+
- [@aeye/core README](../core/README.md)
|
|
897
|
+
- [GitHub Repository](https://github.com/ClickerMonkey/aeye)
|
|
898
|
+
- [Issue Tracker](https://github.com/ClickerMonkey/aeye/issues)
|