ai-functions 0.2.19 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/.turbo/turbo-build.log +5 -0
  2. package/.turbo/turbo-test.log +105 -0
  3. package/README.md +232 -37
  4. package/TODO.md +138 -0
  5. package/dist/ai-promise.d.ts +219 -0
  6. package/dist/ai-promise.d.ts.map +1 -0
  7. package/dist/ai-promise.js +610 -0
  8. package/dist/ai-promise.js.map +1 -0
  9. package/dist/ai.d.ts +285 -0
  10. package/dist/ai.d.ts.map +1 -0
  11. package/dist/ai.js +842 -0
  12. package/dist/ai.js.map +1 -0
  13. package/dist/batch/anthropic.d.ts +23 -0
  14. package/dist/batch/anthropic.d.ts.map +1 -0
  15. package/dist/batch/anthropic.js +257 -0
  16. package/dist/batch/anthropic.js.map +1 -0
  17. package/dist/batch/bedrock.d.ts +64 -0
  18. package/dist/batch/bedrock.d.ts.map +1 -0
  19. package/dist/batch/bedrock.js +586 -0
  20. package/dist/batch/bedrock.js.map +1 -0
  21. package/dist/batch/cloudflare.d.ts +37 -0
  22. package/dist/batch/cloudflare.d.ts.map +1 -0
  23. package/dist/batch/cloudflare.js +289 -0
  24. package/dist/batch/cloudflare.js.map +1 -0
  25. package/dist/batch/google.d.ts +41 -0
  26. package/dist/batch/google.d.ts.map +1 -0
  27. package/dist/batch/google.js +360 -0
  28. package/dist/batch/google.js.map +1 -0
  29. package/dist/batch/index.d.ts +31 -0
  30. package/dist/batch/index.d.ts.map +1 -0
  31. package/dist/batch/index.js +31 -0
  32. package/dist/batch/index.js.map +1 -0
  33. package/dist/batch/memory.d.ts +44 -0
  34. package/dist/batch/memory.d.ts.map +1 -0
  35. package/dist/batch/memory.js +188 -0
  36. package/dist/batch/memory.js.map +1 -0
  37. package/dist/batch/openai.d.ts +37 -0
  38. package/dist/batch/openai.d.ts.map +1 -0
  39. package/dist/batch/openai.js +403 -0
  40. package/dist/batch/openai.js.map +1 -0
  41. package/dist/batch-map.d.ts +125 -0
  42. package/dist/batch-map.d.ts.map +1 -0
  43. package/dist/batch-map.js +406 -0
  44. package/dist/batch-map.js.map +1 -0
  45. package/dist/batch-queue.d.ts +273 -0
  46. package/dist/batch-queue.d.ts.map +1 -0
  47. package/dist/batch-queue.js +271 -0
  48. package/dist/batch-queue.js.map +1 -0
  49. package/dist/context.d.ts +133 -0
  50. package/dist/context.d.ts.map +1 -0
  51. package/dist/context.js +267 -0
  52. package/dist/context.js.map +1 -0
  53. package/dist/embeddings.d.ts +123 -0
  54. package/dist/embeddings.d.ts.map +1 -0
  55. package/dist/embeddings.js +170 -0
  56. package/dist/embeddings.js.map +1 -0
  57. package/dist/eval/index.d.ts +8 -0
  58. package/dist/eval/index.d.ts.map +1 -0
  59. package/dist/eval/index.js +8 -0
  60. package/dist/eval/index.js.map +1 -0
  61. package/dist/eval/models.d.ts +66 -0
  62. package/dist/eval/models.d.ts.map +1 -0
  63. package/dist/eval/models.js +120 -0
  64. package/dist/eval/models.js.map +1 -0
  65. package/dist/eval/runner.d.ts +64 -0
  66. package/dist/eval/runner.d.ts.map +1 -0
  67. package/dist/eval/runner.js +148 -0
  68. package/dist/eval/runner.js.map +1 -0
  69. package/dist/generate.d.ts +168 -0
  70. package/dist/generate.d.ts.map +1 -0
  71. package/dist/generate.js +174 -0
  72. package/dist/generate.js.map +1 -0
  73. package/dist/index.d.ts +30 -0
  74. package/dist/index.d.ts.map +1 -0
  75. package/dist/index.js +54 -0
  76. package/dist/index.js.map +1 -0
  77. package/dist/primitives.d.ts +292 -0
  78. package/dist/primitives.d.ts.map +1 -0
  79. package/dist/primitives.js +471 -0
  80. package/dist/primitives.js.map +1 -0
  81. package/dist/providers/cloudflare.d.ts +9 -0
  82. package/dist/providers/cloudflare.d.ts.map +1 -0
  83. package/dist/providers/cloudflare.js +9 -0
  84. package/dist/providers/cloudflare.js.map +1 -0
  85. package/dist/providers/index.d.ts +9 -0
  86. package/dist/providers/index.d.ts.map +1 -0
  87. package/dist/providers/index.js +9 -0
  88. package/dist/providers/index.js.map +1 -0
  89. package/dist/schema.d.ts +54 -0
  90. package/dist/schema.d.ts.map +1 -0
  91. package/dist/schema.js +109 -0
  92. package/dist/schema.js.map +1 -0
  93. package/dist/template.d.ts +73 -0
  94. package/dist/template.d.ts.map +1 -0
  95. package/dist/template.js +129 -0
  96. package/dist/template.js.map +1 -0
  97. package/dist/types.d.ts +481 -0
  98. package/dist/types.d.ts.map +1 -0
  99. package/dist/types.js +5 -0
  100. package/dist/types.js.map +1 -0
  101. package/evalite.config.ts +19 -0
  102. package/evals/README.md +212 -0
  103. package/evals/classification.eval.ts +108 -0
  104. package/evals/marketing.eval.ts +370 -0
  105. package/evals/math.eval.ts +94 -0
  106. package/evals/run-evals.ts +166 -0
  107. package/evals/structured-output.eval.ts +143 -0
  108. package/evals/writing.eval.ts +117 -0
  109. package/examples/batch-blog-posts.ts +160 -0
  110. package/package.json +59 -43
  111. package/src/ai-promise.ts +784 -0
  112. package/src/ai.ts +1183 -0
  113. package/src/batch/anthropic.ts +375 -0
  114. package/src/batch/bedrock.ts +801 -0
  115. package/src/batch/cloudflare.ts +421 -0
  116. package/src/batch/google.ts +491 -0
  117. package/src/batch/index.ts +31 -0
  118. package/src/batch/memory.ts +253 -0
  119. package/src/batch/openai.ts +557 -0
  120. package/src/batch-map.ts +534 -0
  121. package/src/batch-queue.ts +493 -0
  122. package/src/context.ts +332 -0
  123. package/src/embeddings.ts +244 -0
  124. package/src/eval/index.ts +8 -0
  125. package/src/eval/models.ts +158 -0
  126. package/src/eval/runner.ts +217 -0
  127. package/src/generate.ts +245 -0
  128. package/src/index.ts +154 -0
  129. package/src/primitives.ts +612 -0
  130. package/src/providers/cloudflare.ts +15 -0
  131. package/src/providers/index.ts +14 -0
  132. package/src/schema.ts +147 -0
  133. package/src/template.ts +209 -0
  134. package/src/types.ts +540 -0
  135. package/test/README.md +105 -0
  136. package/test/ai-proxy.test.ts +192 -0
  137. package/test/async-iterators.test.ts +327 -0
  138. package/test/batch-background.test.ts +482 -0
  139. package/test/batch-blog-posts.test.ts +387 -0
  140. package/test/blog-generation.test.ts +510 -0
  141. package/test/browse-read.test.ts +611 -0
  142. package/test/core-functions.test.ts +694 -0
  143. package/test/decide.test.ts +393 -0
  144. package/test/define.test.ts +274 -0
  145. package/test/e2e-bedrock-manual.ts +163 -0
  146. package/test/e2e-bedrock.test.ts +191 -0
  147. package/test/e2e-flex-gateway.ts +157 -0
  148. package/test/e2e-flex-manual.ts +183 -0
  149. package/test/e2e-flex.test.ts +209 -0
  150. package/test/e2e-google-manual.ts +178 -0
  151. package/test/e2e-google.test.ts +216 -0
  152. package/test/embeddings.test.ts +284 -0
  153. package/test/evals/define-function.eval.test.ts +379 -0
  154. package/test/evals/primitives.eval.test.ts +384 -0
  155. package/test/function-types.test.ts +492 -0
  156. package/test/generate-core.test.ts +319 -0
  157. package/test/generate.test.ts +163 -0
  158. package/test/implicit-batch.test.ts +422 -0
  159. package/test/schema.test.ts +109 -0
  160. package/test/tagged-templates.test.ts +302 -0
  161. package/tsconfig.json +8 -6
  162. package/vitest.config.ts +42 -0
  163. package/LICENSE +0 -21
  164. package/db/cache.ts +0 -6
  165. package/db/mongo.ts +0 -75
  166. package/dist/mjs/db/cache.d.ts +0 -1
  167. package/dist/mjs/db/cache.js +0 -5
  168. package/dist/mjs/db/mongo.d.ts +0 -31
  169. package/dist/mjs/db/mongo.js +0 -48
  170. package/dist/mjs/examples/data.d.ts +0 -1105
  171. package/dist/mjs/examples/data.js +0 -1105
  172. package/dist/mjs/functions/ai.d.ts +0 -20
  173. package/dist/mjs/functions/ai.js +0 -83
  174. package/dist/mjs/functions/ai.test.d.ts +0 -1
  175. package/dist/mjs/functions/ai.test.js +0 -29
  176. package/dist/mjs/functions/gpt.d.ts +0 -4
  177. package/dist/mjs/functions/gpt.js +0 -10
  178. package/dist/mjs/functions/list.d.ts +0 -7
  179. package/dist/mjs/functions/list.js +0 -72
  180. package/dist/mjs/index.d.ts +0 -3
  181. package/dist/mjs/index.js +0 -3
  182. package/dist/mjs/queue/kafka.d.ts +0 -0
  183. package/dist/mjs/queue/kafka.js +0 -1
  184. package/dist/mjs/queue/memory.d.ts +0 -0
  185. package/dist/mjs/queue/memory.js +0 -1
  186. package/dist/mjs/queue/mongo.d.ts +0 -30
  187. package/dist/mjs/queue/mongo.js +0 -42
  188. package/dist/mjs/streams/kafka.d.ts +0 -0
  189. package/dist/mjs/streams/kafka.js +0 -1
  190. package/dist/mjs/streams/memory.d.ts +0 -0
  191. package/dist/mjs/streams/memory.js +0 -1
  192. package/dist/mjs/streams/mongo.d.ts +0 -0
  193. package/dist/mjs/streams/mongo.js +0 -1
  194. package/dist/mjs/streams/types.d.ts +0 -0
  195. package/dist/mjs/streams/types.js +0 -1
  196. package/dist/mjs/types.d.ts +0 -11
  197. package/dist/mjs/types.js +0 -1
  198. package/dist/mjs/utils/completion.d.ts +0 -9
  199. package/dist/mjs/utils/completion.js +0 -20
  200. package/dist/mjs/utils/schema.d.ts +0 -10
  201. package/dist/mjs/utils/schema.js +0 -72
  202. package/dist/mjs/utils/schema.test.d.ts +0 -1
  203. package/dist/mjs/utils/schema.test.js +0 -60
  204. package/dist/mjs/utils/state.d.ts +0 -1
  205. package/dist/mjs/utils/state.js +0 -19
  206. package/examples/data.ts +0 -1105
  207. package/fixup +0 -11
  208. package/functions/ai.test.ts +0 -41
  209. package/functions/ai.ts +0 -115
  210. package/functions/gpt.ts +0 -12
  211. package/functions/list.ts +0 -84
  212. package/index.ts +0 -3
  213. package/queue/kafka.ts +0 -0
  214. package/queue/memory.ts +0 -0
  215. package/queue/mongo.ts +0 -88
  216. package/streams/kafka.ts +0 -0
  217. package/streams/memory.ts +0 -0
  218. package/streams/mongo.ts +0 -0
  219. package/streams/types.ts +0 -0
  220. package/tsconfig-backup.json +0 -105
  221. package/tsconfig-base.json +0 -26
  222. package/tsconfig-cjs.json +0 -8
  223. package/types.ts +0 -12
  224. package/utils/completion.ts +0 -28
  225. package/utils/schema.test.ts +0 -69
  226. package/utils/schema.ts +0 -74
  227. package/utils/state.ts +0 -23
package/src/context.ts ADDED
@@ -0,0 +1,332 @@
1
+ /**
2
+ * Execution Context for AI Functions
3
+ *
4
+ * Provides configuration without polluting function signatures.
5
+ * Settings flow from environment → global context → local context.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * // Set global defaults (from environment or initialization)
10
+ * configure({
11
+ * provider: 'anthropic',
12
+ * model: 'claude-sonnet-4-20250514',
13
+ * batchMode: 'auto', // 'auto' | 'immediate' | 'deferred'
14
+ * })
15
+ *
16
+ * // Or use execution context for specific operations
17
+ * await withContext({ provider: 'openai', model: 'gpt-4o' }, async () => {
18
+ * const titles = await list`10 blog titles`
19
+ * return titles.map(title => write`blog post: ${title}`)
20
+ * })
21
+ * ```
22
+ *
23
+ * @packageDocumentation
24
+ */
25
+
26
+ import type { FunctionOptions } from './template.js'
27
+ import type { BatchProvider } from './batch-queue.js'
28
+
29
+ // ============================================================================
30
+ // Types
31
+ // ============================================================================
32
+
33
+ /** Batch execution mode */
34
+ export type BatchMode =
35
+ | 'auto' // Smart selection: immediate < flexThreshold, flex < batchThreshold, batch above
36
+ | 'immediate' // Execute immediately (concurrent requests, full price)
37
+ | 'flex' // Use flex processing (faster than batch, ~50% discount, minutes)
38
+ | 'deferred' // Always use provider batch API (50% discount, up to 24hr)
39
+
40
+ /** Execution context configuration */
41
+ export interface ExecutionContext extends FunctionOptions {
42
+ /** Batch provider to use */
43
+ provider?: BatchProvider
44
+ /** Batch execution mode */
45
+ batchMode?: BatchMode
46
+ /** Minimum items to use flex processing (for 'auto' mode, default: 5) */
47
+ flexThreshold?: number
48
+ /** Minimum items to use batch API (for 'auto' mode, default: 500) */
49
+ batchThreshold?: number
50
+ /** Webhook URL for batch completion notifications */
51
+ webhookUrl?: string
52
+ /** Custom metadata for batch jobs */
53
+ metadata?: Record<string, unknown>
54
+ }
55
+
56
+ // ============================================================================
57
+ // Global Context
58
+ // ============================================================================
59
+
60
+ let globalContext: ExecutionContext = {}
61
+
62
+ /**
63
+ * Configure global defaults for AI functions
64
+ *
65
+ * @example
66
+ * ```ts
67
+ * configure({
68
+ * model: 'claude-sonnet-4-20250514',
69
+ * provider: 'anthropic',
70
+ * batchMode: 'auto',
71
+ * batchThreshold: 5,
72
+ * })
73
+ * ```
74
+ */
75
+ export function configure(context: ExecutionContext): void {
76
+ globalContext = { ...globalContext, ...context }
77
+ }
78
+
79
+ /**
80
+ * Get the current global context
81
+ */
82
+ export function getGlobalContext(): ExecutionContext {
83
+ return { ...globalContext }
84
+ }
85
+
86
+ /**
87
+ * Reset global context to defaults
88
+ */
89
+ export function resetContext(): void {
90
+ globalContext = {}
91
+ }
92
+
93
+ // ============================================================================
94
+ // Async Local Storage for Execution Context
95
+ // ============================================================================
96
+
97
+ // Use AsyncLocalStorage if available (Node.js), otherwise fallback to global
98
+ let asyncLocalStorage: {
99
+ getStore: () => ExecutionContext | undefined
100
+ run: <T>(store: ExecutionContext, callback: () => T) => T
101
+ } | null = null
102
+
103
+ // Lazy initialization of AsyncLocalStorage
104
+ let asyncLocalStorageInitialized = false
105
+
106
+ async function initAsyncLocalStorage(): Promise<void> {
107
+ if (asyncLocalStorageInitialized) return
108
+ asyncLocalStorageInitialized = true
109
+
110
+ try {
111
+ const { AsyncLocalStorage } = await import('async_hooks')
112
+ asyncLocalStorage = new AsyncLocalStorage<ExecutionContext>()
113
+ } catch {
114
+ // Not in Node.js environment, use global context only
115
+ }
116
+ }
117
+
118
+ // Initialize synchronously if possible (for Node.js environments)
119
+ if (typeof process !== 'undefined' && process.versions?.node) {
120
+ import('async_hooks')
121
+ .then(({ AsyncLocalStorage }) => {
122
+ asyncLocalStorage = new AsyncLocalStorage<ExecutionContext>()
123
+ asyncLocalStorageInitialized = true
124
+ })
125
+ .catch(() => {
126
+ asyncLocalStorageInitialized = true
127
+ })
128
+ }
129
+
130
+ /**
131
+ * Get the current execution context
132
+ * Merges: environment defaults → global context → local context
133
+ */
134
+ export function getContext(): ExecutionContext {
135
+ const envContext = getEnvContext()
136
+ const localContext = asyncLocalStorage?.getStore()
137
+
138
+ return {
139
+ ...envContext,
140
+ ...globalContext,
141
+ ...localContext,
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Run a function with a specific execution context
147
+ *
148
+ * @example
149
+ * ```ts
150
+ * const posts = await withContext({ provider: 'openai', batchMode: 'deferred' }, async () => {
151
+ * const titles = await list`10 blog titles`
152
+ * return titles.map(title => write`blog post: ${title}`)
153
+ * })
154
+ * ```
155
+ */
156
+ export function withContext<T>(
157
+ context: ExecutionContext,
158
+ fn: () => T | Promise<T>
159
+ ): T | Promise<T> {
160
+ const mergedContext = { ...getContext(), ...context }
161
+
162
+ if (asyncLocalStorage) {
163
+ return asyncLocalStorage.run(mergedContext, fn)
164
+ }
165
+
166
+ // Fallback: temporarily modify global context
167
+ const previousContext = globalContext
168
+ globalContext = mergedContext
169
+ try {
170
+ return fn()
171
+ } finally {
172
+ globalContext = previousContext
173
+ }
174
+ }
175
+
176
+ // ============================================================================
177
+ // Environment Defaults
178
+ // ============================================================================
179
+
180
+ function getEnvContext(): ExecutionContext {
181
+ if (typeof process === 'undefined') return {}
182
+
183
+ const context: ExecutionContext = {}
184
+
185
+ // Model defaults
186
+ if (process.env.AI_MODEL) {
187
+ context.model = process.env.AI_MODEL
188
+ }
189
+
190
+ // Provider defaults
191
+ if (process.env.AI_PROVIDER) {
192
+ context.provider = process.env.AI_PROVIDER as BatchProvider
193
+ } else if (process.env.ANTHROPIC_API_KEY && !process.env.OPENAI_API_KEY) {
194
+ context.provider = 'anthropic'
195
+ } else if (process.env.OPENAI_API_KEY) {
196
+ context.provider = 'openai'
197
+ } else if (process.env.CLOUDFLARE_API_TOKEN) {
198
+ context.provider = 'cloudflare'
199
+ } else if (process.env.AWS_ACCESS_KEY_ID) {
200
+ context.provider = 'bedrock'
201
+ }
202
+
203
+ // Batch mode
204
+ if (process.env.AI_BATCH_MODE) {
205
+ context.batchMode = process.env.AI_BATCH_MODE as BatchMode
206
+ }
207
+
208
+ // Flex threshold (when to start using flex processing)
209
+ if (process.env.AI_FLEX_THRESHOLD) {
210
+ context.flexThreshold = parseInt(process.env.AI_FLEX_THRESHOLD, 10)
211
+ }
212
+
213
+ // Batch threshold (when to switch from flex to full batch)
214
+ if (process.env.AI_BATCH_THRESHOLD) {
215
+ context.batchThreshold = parseInt(process.env.AI_BATCH_THRESHOLD, 10)
216
+ }
217
+
218
+ // Webhook URL
219
+ if (process.env.AI_BATCH_WEBHOOK_URL) {
220
+ context.webhookUrl = process.env.AI_BATCH_WEBHOOK_URL
221
+ }
222
+
223
+ return context
224
+ }
225
+
226
+ // ============================================================================
227
+ // Context Helpers
228
+ // ============================================================================
229
+
230
+ /**
231
+ * Get the effective model from context
232
+ */
233
+ export function getModel(): string {
234
+ const ctx = getContext()
235
+ return ctx.model || 'sonnet'
236
+ }
237
+
238
+ /**
239
+ * Get the effective provider from context
240
+ */
241
+ export function getProvider(): BatchProvider {
242
+ const ctx = getContext()
243
+ return ctx.provider || 'openai'
244
+ }
245
+
246
+ /**
247
+ * Get the effective batch mode from context
248
+ */
249
+ export function getBatchMode(): BatchMode {
250
+ const ctx = getContext()
251
+ return ctx.batchMode || 'auto'
252
+ }
253
+
254
+ /**
255
+ * Get the flex threshold from context (minimum items to use flex)
256
+ * Default: 5 items
257
+ */
258
+ export function getFlexThreshold(): number {
259
+ const ctx = getContext()
260
+ return ctx.flexThreshold || 5
261
+ }
262
+
263
+ /**
264
+ * Get the batch threshold from context (minimum items to use full batch)
265
+ * Default: 500 items
266
+ */
267
+ export function getBatchThreshold(): number {
268
+ const ctx = getContext()
269
+ return ctx.batchThreshold || 500
270
+ }
271
+
272
+ /** Execution tier for processing */
273
+ export type ExecutionTier = 'immediate' | 'flex' | 'batch'
274
+
275
+ /**
276
+ * Determine the execution tier for a given number of items
277
+ *
278
+ * Auto mode tiers:
279
+ * - immediate: < flexThreshold (default 5) - concurrent requests, full price
280
+ * - flex: flexThreshold to batchThreshold (5-500) - ~50% discount, minutes
281
+ * - batch: >= batchThreshold (500+) - 50% discount, up to 24hr
282
+ *
283
+ * @example
284
+ * ```ts
285
+ * getExecutionTier(3) // 'immediate' (< 5)
286
+ * getExecutionTier(50) // 'flex' (5-500)
287
+ * getExecutionTier(1000) // 'batch' (500+)
288
+ * ```
289
+ */
290
+ export function getExecutionTier(itemCount: number): ExecutionTier {
291
+ const mode = getBatchMode()
292
+
293
+ switch (mode) {
294
+ case 'immediate':
295
+ return 'immediate'
296
+ case 'flex':
297
+ return 'flex'
298
+ case 'deferred':
299
+ return 'batch'
300
+ case 'auto':
301
+ default: {
302
+ const flexThreshold = getFlexThreshold()
303
+ const batchThreshold = getBatchThreshold()
304
+
305
+ if (itemCount < flexThreshold) {
306
+ return 'immediate'
307
+ } else if (itemCount < batchThreshold) {
308
+ return 'flex'
309
+ } else {
310
+ return 'batch'
311
+ }
312
+ }
313
+ }
314
+ }
315
+
316
+ /**
317
+ * Check if we should use the batch API for a given number of items
318
+ * @deprecated Use getExecutionTier() instead for more granular control
319
+ */
320
+ export function shouldUseBatchAPI(itemCount: number): boolean {
321
+ const tier = getExecutionTier(itemCount)
322
+ return tier === 'flex' || tier === 'batch'
323
+ }
324
+
325
+ /**
326
+ * Check if flex processing is available for the current provider
327
+ * Only OpenAI and AWS Bedrock support flex processing currently
328
+ */
329
+ export function isFlexAvailable(): boolean {
330
+ const provider = getProvider()
331
+ return provider === 'openai' || provider === 'bedrock' || provider === 'google'
332
+ }
@@ -0,0 +1,244 @@
1
+ /**
2
+ * Embedding utilities from AI SDK
3
+ *
4
+ * Re-exports embed, embedMany, and cosineSimilarity from the Vercel AI SDK
5
+ * with additional convenience wrappers.
6
+ *
7
+ * Default model: Cloudflare Workers AI @cf/baai/bge-m3
8
+ *
9
+ * @packageDocumentation
10
+ */
11
+
12
+ // Re-export core embedding functions from AI SDK
13
+ export { embed, embedMany, cosineSimilarity } from 'ai'
14
+
15
+ // Re-export types
16
+ export type {
17
+ EmbeddingModel,
18
+ Embedding
19
+ } from 'ai'
20
+
21
+ // Re-export Cloudflare provider from ai-providers
22
+ export { cloudflare, cloudflareEmbedding, DEFAULT_CF_EMBEDDING_MODEL } from 'ai-providers/cloudflare'
23
+
24
+ import { embed as aiEmbed, embedMany as aiEmbedMany } from 'ai'
25
+ import { cloudflareEmbedding, DEFAULT_CF_EMBEDDING_MODEL } from 'ai-providers/cloudflare'
26
+
27
+ /**
28
+ * Get the default embedding model (Cloudflare @cf/baai/bge-m3)
29
+ */
30
+ export function getDefaultEmbeddingModel() {
31
+ return cloudflareEmbedding(DEFAULT_CF_EMBEDDING_MODEL)
32
+ }
33
+
34
+ /**
35
+ * Embed a single value using the default Cloudflare model
36
+ *
37
+ * @example
38
+ * ```ts
39
+ * import { embedText } from 'ai-functions'
40
+ *
41
+ * const { embedding } = await embedText('hello world')
42
+ * ```
43
+ */
44
+ export async function embedText(value: string) {
45
+ return aiEmbed({
46
+ model: getDefaultEmbeddingModel(),
47
+ value
48
+ })
49
+ }
50
+
51
+ /**
52
+ * Embed multiple values using the default Cloudflare model
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * import { embedTexts } from 'ai-functions'
57
+ *
58
+ * const { embeddings } = await embedTexts(['doc1', 'doc2', 'doc3'])
59
+ * ```
60
+ */
61
+ export async function embedTexts(values: string[]) {
62
+ return aiEmbedMany({
63
+ model: getDefaultEmbeddingModel(),
64
+ values
65
+ })
66
+ }
67
+
68
+ /**
69
+ * Result of an embed operation
70
+ */
71
+ export interface EmbedResult<T = string> {
72
+ /** The original input value */
73
+ value: T
74
+ /** The generated embedding vector */
75
+ embedding: number[]
76
+ /** Token usage */
77
+ usage: {
78
+ tokens: number
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Result of an embedMany operation
84
+ */
85
+ export interface EmbedManyResult<T = string> {
86
+ /** The original input values */
87
+ values: T[]
88
+ /** The generated embedding vectors */
89
+ embeddings: number[][]
90
+ /** Token usage */
91
+ usage: {
92
+ tokens: number
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Find the most similar items to a query embedding
98
+ *
99
+ * @example
100
+ * ```ts
101
+ * import { embed, embedMany, findSimilar } from 'ai-functions'
102
+ *
103
+ * const documents = ['doc1', 'doc2', 'doc3']
104
+ * const { embeddings } = await embedMany({ model, values: documents })
105
+ * const { embedding: queryEmbedding } = await embed({ model, value: 'search query' })
106
+ *
107
+ * const results = findSimilar(queryEmbedding, embeddings, documents, { topK: 2 })
108
+ * // [{ item: 'doc1', score: 0.95, index: 0 }, { item: 'doc2', score: 0.82, index: 1 }]
109
+ * ```
110
+ */
111
+ export function findSimilar<T>(
112
+ queryEmbedding: number[],
113
+ embeddings: number[][],
114
+ items: T[],
115
+ options: {
116
+ /** Number of results to return (default: 10) */
117
+ topK?: number
118
+ /** Minimum similarity score (default: 0) */
119
+ minScore?: number
120
+ } = {}
121
+ ): Array<{ item: T; score: number; index: number }> {
122
+ const { topK = 10, minScore = 0 } = options
123
+
124
+ // Import cosineSimilarity dynamically to avoid issues if ai isn't installed
125
+ const { cosineSimilarity } = require('ai')
126
+
127
+ const scored = embeddings
128
+ .map((embedding, index) => ({
129
+ item: items[index]!,
130
+ score: cosineSimilarity(queryEmbedding, embedding) as number,
131
+ index
132
+ }))
133
+ .filter(result => result.score >= minScore)
134
+ .sort((a, b) => b.score - a.score)
135
+ .slice(0, topK)
136
+
137
+ return scored
138
+ }
139
+
140
+ /**
141
+ * Calculate pairwise similarities between all embeddings
142
+ *
143
+ * @example
144
+ * ```ts
145
+ * const matrix = pairwiseSimilarity(embeddings)
146
+ * // matrix[i][j] = similarity between embeddings[i] and embeddings[j]
147
+ * ```
148
+ */
149
+ export function pairwiseSimilarity(embeddings: number[][]): number[][] {
150
+ const { cosineSimilarity } = require('ai')
151
+
152
+ const n = embeddings.length
153
+ const matrix: number[][] = Array(n).fill(null).map(() => Array(n).fill(0))
154
+
155
+ for (let i = 0; i < n; i++) {
156
+ matrix[i]![i] = 1 // Self-similarity is always 1
157
+ for (let j = i + 1; j < n; j++) {
158
+ const sim = cosineSimilarity(embeddings[i], embeddings[j])
159
+ matrix[i]![j] = sim
160
+ matrix[j]![i] = sim
161
+ }
162
+ }
163
+
164
+ return matrix
165
+ }
166
+
167
+ /**
168
+ * Cluster embeddings by similarity using a simple threshold-based approach
169
+ *
170
+ * @example
171
+ * ```ts
172
+ * const clusters = clusterBySimilarity(embeddings, items, { threshold: 0.8 })
173
+ * // [[item1, item2], [item3], [item4, item5, item6]]
174
+ * ```
175
+ */
176
+ export function clusterBySimilarity<T>(
177
+ embeddings: number[][],
178
+ items: T[],
179
+ options: {
180
+ /** Similarity threshold for clustering (default: 0.8) */
181
+ threshold?: number
182
+ } = {}
183
+ ): T[][] {
184
+ const { threshold = 0.8 } = options
185
+ const { cosineSimilarity } = require('ai')
186
+
187
+ const n = embeddings.length
188
+ const assigned = new Set<number>()
189
+ const clusters: T[][] = []
190
+
191
+ for (let i = 0; i < n; i++) {
192
+ if (assigned.has(i)) continue
193
+
194
+ const cluster: T[] = [items[i]!]
195
+ assigned.add(i)
196
+
197
+ for (let j = i + 1; j < n; j++) {
198
+ if (assigned.has(j)) continue
199
+
200
+ const sim = cosineSimilarity(embeddings[i], embeddings[j])
201
+ if (sim >= threshold) {
202
+ cluster.push(items[j]!)
203
+ assigned.add(j)
204
+ }
205
+ }
206
+
207
+ clusters.push(cluster)
208
+ }
209
+
210
+ return clusters
211
+ }
212
+
213
+ /**
214
+ * Average multiple embeddings into a single embedding
215
+ * Useful for creating document embeddings from chunk embeddings
216
+ */
217
+ export function averageEmbeddings(embeddings: number[][]): number[] {
218
+ if (embeddings.length === 0) return []
219
+
220
+ const dim = embeddings[0]!.length
221
+ const result = new Array(dim).fill(0)
222
+
223
+ for (const embedding of embeddings) {
224
+ for (let i = 0; i < dim; i++) {
225
+ result[i] += embedding[i]
226
+ }
227
+ }
228
+
229
+ const n = embeddings.length
230
+ for (let i = 0; i < dim; i++) {
231
+ result[i] /= n
232
+ }
233
+
234
+ return result
235
+ }
236
+
237
+ /**
238
+ * Normalize an embedding to unit length
239
+ */
240
+ export function normalizeEmbedding(embedding: number[]): number[] {
241
+ const magnitude = Math.sqrt(embedding.reduce((sum, val) => sum + val * val, 0))
242
+ if (magnitude === 0) return embedding
243
+ return embedding.map(val => val / magnitude)
244
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * AI Functions Eval Suite
3
+ *
4
+ * @packageDocumentation
5
+ */
6
+
7
+ export * from './models.js'
8
+ export * from './runner.js'