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
@@ -0,0 +1,284 @@
1
+ /**
2
+ * Tests for embedding utilities
3
+ *
4
+ * Pure unit tests for utility functions that don't require AI API calls.
5
+ * Gateway-dependent tests are skipped if no gateway is configured.
6
+ */
7
+
8
+ import { describe, it, expect } from 'vitest'
9
+ import {
10
+ cosineSimilarity,
11
+ findSimilar,
12
+ pairwiseSimilarity,
13
+ clusterBySimilarity,
14
+ averageEmbeddings,
15
+ normalizeEmbedding,
16
+ } from '../src/index.js'
17
+
18
+ describe('cosineSimilarity', () => {
19
+ it('returns 1 for identical vectors', () => {
20
+ const a = [1, 0, 0]
21
+ const b = [1, 0, 0]
22
+ expect(cosineSimilarity(a, b)).toBeCloseTo(1)
23
+ })
24
+
25
+ it('returns 0 for orthogonal vectors', () => {
26
+ const a = [1, 0, 0]
27
+ const b = [0, 1, 0]
28
+ expect(cosineSimilarity(a, b)).toBeCloseTo(0)
29
+ })
30
+
31
+ it('returns -1 for opposite vectors', () => {
32
+ const a = [1, 0, 0]
33
+ const b = [-1, 0, 0]
34
+ expect(cosineSimilarity(a, b)).toBeCloseTo(-1)
35
+ })
36
+
37
+ it('handles non-normalized vectors', () => {
38
+ const a = [2, 0, 0]
39
+ const b = [5, 0, 0]
40
+ expect(cosineSimilarity(a, b)).toBeCloseTo(1)
41
+ })
42
+
43
+ it('works with higher dimensional vectors', () => {
44
+ const a = [1, 2, 3, 4, 5]
45
+ const b = [1, 2, 3, 4, 5]
46
+ expect(cosineSimilarity(a, b)).toBeCloseTo(1)
47
+ })
48
+ })
49
+
50
+ describe('findSimilar', () => {
51
+ const embeddings = [
52
+ [1, 0, 0],
53
+ [0.9, 0.1, 0],
54
+ [0, 1, 0],
55
+ [0, 0, 1],
56
+ [-1, 0, 0],
57
+ ]
58
+ const items = ['A', 'B', 'C', 'D', 'E']
59
+
60
+ it('finds most similar items', () => {
61
+ const query = [1, 0, 0]
62
+ const results = findSimilar(query, embeddings, items, { topK: 3 })
63
+
64
+ expect(results).toHaveLength(3)
65
+ expect(results[0].item).toBe('A') // Exact match
66
+ expect(results[0].score).toBeCloseTo(1)
67
+ expect(results[1].item).toBe('B') // Very similar
68
+ })
69
+
70
+ it('respects topK parameter', () => {
71
+ const query = [1, 0, 0]
72
+ const results = findSimilar(query, embeddings, items, { topK: 2 })
73
+
74
+ expect(results).toHaveLength(2)
75
+ })
76
+
77
+ it('filters by minScore', () => {
78
+ const query = [1, 0, 0]
79
+ const results = findSimilar(query, embeddings, items, { minScore: 0.5 })
80
+
81
+ // Only A and B should have score >= 0.5
82
+ expect(results.every(r => r.score >= 0.5)).toBe(true)
83
+ expect(results).toHaveLength(2)
84
+ })
85
+
86
+ it('returns index in results', () => {
87
+ const query = [0, 1, 0]
88
+ const results = findSimilar(query, embeddings, items, { topK: 1 })
89
+
90
+ expect(results[0].item).toBe('C')
91
+ expect(results[0].index).toBe(2)
92
+ })
93
+
94
+ it('handles empty embeddings', () => {
95
+ const query = [1, 0, 0]
96
+ const results = findSimilar(query, [], [], { topK: 5 })
97
+
98
+ expect(results).toHaveLength(0)
99
+ })
100
+ })
101
+
102
+ describe('pairwiseSimilarity', () => {
103
+ it('creates a symmetric matrix', () => {
104
+ const embeddings = [
105
+ [1, 0, 0],
106
+ [0, 1, 0],
107
+ [0, 0, 1],
108
+ ]
109
+
110
+ const matrix = pairwiseSimilarity(embeddings)
111
+
112
+ expect(matrix).toHaveLength(3)
113
+ expect(matrix[0]).toHaveLength(3)
114
+
115
+ // Check symmetry
116
+ for (let i = 0; i < 3; i++) {
117
+ for (let j = 0; j < 3; j++) {
118
+ expect(matrix[i][j]).toBeCloseTo(matrix[j][i])
119
+ }
120
+ }
121
+ })
122
+
123
+ it('has 1s on the diagonal', () => {
124
+ const embeddings = [
125
+ [1, 2, 3],
126
+ [4, 5, 6],
127
+ [7, 8, 9],
128
+ ]
129
+
130
+ const matrix = pairwiseSimilarity(embeddings)
131
+
132
+ for (let i = 0; i < 3; i++) {
133
+ expect(matrix[i][i]).toBeCloseTo(1)
134
+ }
135
+ })
136
+
137
+ it('correctly computes similarity for orthogonal vectors', () => {
138
+ const embeddings = [
139
+ [1, 0, 0],
140
+ [0, 1, 0],
141
+ ]
142
+
143
+ const matrix = pairwiseSimilarity(embeddings)
144
+
145
+ expect(matrix[0][1]).toBeCloseTo(0)
146
+ expect(matrix[1][0]).toBeCloseTo(0)
147
+ })
148
+ })
149
+
150
+ describe('clusterBySimilarity', () => {
151
+ it('groups similar items together', () => {
152
+ const embeddings = [
153
+ [1, 0, 0],
154
+ [0.95, 0.05, 0],
155
+ [0, 1, 0],
156
+ [0.05, 0.95, 0],
157
+ ]
158
+ const items = ['A', 'A-like', 'B', 'B-like']
159
+
160
+ const clusters = clusterBySimilarity(embeddings, items, { threshold: 0.9 })
161
+
162
+ // Should create 2 clusters
163
+ expect(clusters).toHaveLength(2)
164
+
165
+ // Find the cluster with A
166
+ const clusterA = clusters.find(c => c.includes('A'))
167
+ expect(clusterA).toContain('A-like')
168
+
169
+ // Find the cluster with B
170
+ const clusterB = clusters.find(c => c.includes('B'))
171
+ expect(clusterB).toContain('B-like')
172
+ })
173
+
174
+ it('creates single-item clusters for dissimilar items', () => {
175
+ const embeddings = [
176
+ [1, 0, 0],
177
+ [0, 1, 0],
178
+ [0, 0, 1],
179
+ ]
180
+ const items = ['X', 'Y', 'Z']
181
+
182
+ const clusters = clusterBySimilarity(embeddings, items, { threshold: 0.9 })
183
+
184
+ // Each item should be in its own cluster
185
+ expect(clusters).toHaveLength(3)
186
+ expect(clusters.every(c => c.length === 1)).toBe(true)
187
+ })
188
+
189
+ it('puts all items in one cluster at low threshold', () => {
190
+ const embeddings = [
191
+ [1, 0.1, 0.1],
192
+ [0.9, 0.2, 0.1],
193
+ [0.8, 0.3, 0.1],
194
+ ]
195
+ const items = ['A', 'B', 'C']
196
+
197
+ const clusters = clusterBySimilarity(embeddings, items, { threshold: 0.5 })
198
+
199
+ // All items similar enough to be in one cluster
200
+ expect(clusters).toHaveLength(1)
201
+ expect(clusters[0]).toHaveLength(3)
202
+ })
203
+ })
204
+
205
+ describe('averageEmbeddings', () => {
206
+ it('averages multiple embeddings', () => {
207
+ const embeddings = [
208
+ [1, 0, 0],
209
+ [0, 1, 0],
210
+ [0, 0, 1],
211
+ ]
212
+
213
+ const avg = averageEmbeddings(embeddings)
214
+
215
+ expect(avg).toHaveLength(3)
216
+ expect(avg[0]).toBeCloseTo(1 / 3)
217
+ expect(avg[1]).toBeCloseTo(1 / 3)
218
+ expect(avg[2]).toBeCloseTo(1 / 3)
219
+ })
220
+
221
+ it('returns empty array for empty input', () => {
222
+ const avg = averageEmbeddings([])
223
+ expect(avg).toEqual([])
224
+ })
225
+
226
+ it('returns same vector for single input', () => {
227
+ const embedding = [1, 2, 3]
228
+ const avg = averageEmbeddings([embedding])
229
+
230
+ expect(avg).toEqual([1, 2, 3])
231
+ })
232
+ })
233
+
234
+ describe('normalizeEmbedding', () => {
235
+ it('normalizes a vector to unit length', () => {
236
+ const embedding = [3, 4, 0] // length = 5
237
+ const normalized = normalizeEmbedding(embedding)
238
+
239
+ expect(normalized[0]).toBeCloseTo(0.6)
240
+ expect(normalized[1]).toBeCloseTo(0.8)
241
+ expect(normalized[2]).toBeCloseTo(0)
242
+
243
+ // Check magnitude is 1
244
+ const magnitude = Math.sqrt(
245
+ normalized.reduce((sum, val) => sum + val * val, 0)
246
+ )
247
+ expect(magnitude).toBeCloseTo(1)
248
+ })
249
+
250
+ it('handles already normalized vectors', () => {
251
+ const embedding = [1, 0, 0]
252
+ const normalized = normalizeEmbedding(embedding)
253
+
254
+ expect(normalized).toEqual([1, 0, 0])
255
+ })
256
+
257
+ it('handles zero vector', () => {
258
+ const embedding = [0, 0, 0]
259
+ const normalized = normalizeEmbedding(embedding)
260
+
261
+ expect(normalized).toEqual([0, 0, 0])
262
+ })
263
+ })
264
+
265
+ // Skip API-dependent tests if no gateway
266
+ const hasGateway = !!process.env.AI_GATEWAY_URL || !!process.env.CF_ACCOUNT_ID
267
+
268
+ describe.skipIf(!hasGateway)('embedText and embedTexts', () => {
269
+ // These tests would require actual API calls
270
+ // They're here as placeholders for when the gateway is available
271
+
272
+ it.skip('embeds a single text', async () => {
273
+ // const { embedText } = await import('../src/index.js')
274
+ // const { embedding } = await embedText('hello world')
275
+ // expect(Array.isArray(embedding)).toBe(true)
276
+ // expect(embedding.length).toBeGreaterThan(0)
277
+ })
278
+
279
+ it.skip('embeds multiple texts', async () => {
280
+ // const { embedTexts } = await import('../src/index.js')
281
+ // const { embeddings } = await embedTexts(['doc1', 'doc2', 'doc3'])
282
+ // expect(embeddings).toHaveLength(3)
283
+ })
284
+ })
@@ -0,0 +1,379 @@
1
+ /**
2
+ * defineFunction Eval Suite
3
+ *
4
+ * Tests the defineFunction API with real AI calls across multiple models.
5
+ * Ensures that defined functions work correctly with generative, agentic, and code types.
6
+ *
7
+ * Run with: pnpm test -- test/evals/define-function.eval.test.ts
8
+ * Run specific model: MODEL=sonnet pnpm test -- test/evals/define-function.eval.test.ts
9
+ */
10
+
11
+ import { describe, it, expect, beforeEach } from 'vitest'
12
+ import { define, defineFunction, functions } from '../../src/index.js'
13
+ import { EVAL_MODELS, type EvalModel, type ModelTier } from '../../src/eval/models.js'
14
+
15
+ // Configuration
16
+ const EVAL_TIERS: ModelTier[] = (process.env.EVAL_TIERS?.split(',') as ModelTier[]) || ['fast']
17
+ const SINGLE_MODEL = process.env.MODEL
18
+
19
+ // Get models to test
20
+ function getTestModels(): EvalModel[] {
21
+ if (SINGLE_MODEL) {
22
+ const model = EVAL_MODELS.find(m => m.id === SINGLE_MODEL || m.name.toLowerCase().includes(SINGLE_MODEL.toLowerCase()))
23
+ return model ? [model] : EVAL_MODELS.filter(m => m.tier === 'fast').slice(0, 1)
24
+ }
25
+ return EVAL_MODELS.filter(m => EVAL_TIERS.includes(m.tier))
26
+ }
27
+
28
+ // Skip if no API access
29
+ const hasAPI = !!process.env.AI_GATEWAY_URL || !!process.env.ANTHROPIC_API_KEY || !!process.env.OPENAI_API_KEY
30
+
31
+ // Test timeout for AI calls
32
+ const AI_TIMEOUT = 30000
33
+
34
+ describe.skipIf(!hasAPI)('defineFunction Evals', () => {
35
+ const models = getTestModels()
36
+
37
+ beforeEach(() => {
38
+ functions.clear()
39
+ })
40
+
41
+ // ==========================================================================
42
+ // Generative Functions
43
+ // ==========================================================================
44
+ describe('define.generative()', () => {
45
+ describe('string output functions', () => {
46
+ for (const model of models) {
47
+ describe(`[${model.name}]`, () => {
48
+ it('executes greeting function', async () => {
49
+ const greet = define.generative({
50
+ name: 'greet',
51
+ args: { name: 'Name to greet' },
52
+ output: 'string',
53
+ promptTemplate: 'Say hello to {{name}} in a friendly way',
54
+ model: model.id,
55
+ })
56
+
57
+ const result = await greet.call({ name: 'World' })
58
+ expect(typeof result).toBe('string')
59
+ expect((result as string).toLowerCase()).toMatch(/hello|hi|hey|greet/)
60
+ }, AI_TIMEOUT)
61
+
62
+ it('executes translation function', async () => {
63
+ const translate = define.generative({
64
+ name: 'translate',
65
+ args: {
66
+ text: 'Text to translate',
67
+ targetLang: 'Target language',
68
+ },
69
+ output: 'string',
70
+ promptTemplate: 'Translate "{{text}}" to {{targetLang}}',
71
+ model: model.id,
72
+ })
73
+
74
+ const result = await translate.call({ text: 'Hello', targetLang: 'Spanish' })
75
+ expect(typeof result).toBe('string')
76
+ expect((result as string).toLowerCase()).toMatch(/hola|buenos/)
77
+ }, AI_TIMEOUT)
78
+
79
+ it('executes summarization function', async () => {
80
+ const summarizeText = define.generative({
81
+ name: 'summarizeText',
82
+ args: { text: 'Text to summarize' },
83
+ output: 'string',
84
+ promptTemplate: 'Summarize the following in one sentence: {{text}}',
85
+ model: model.id,
86
+ })
87
+
88
+ const longText = 'TypeScript is a strongly typed programming language that builds on JavaScript, giving you better tooling at any scale. TypeScript adds additional syntax to JavaScript to support a tighter integration with your editor. Catch errors early in your editor.'
89
+
90
+ const result = await summarizeText.call({ text: longText })
91
+ expect(typeof result).toBe('string')
92
+ expect((result as string).length).toBeGreaterThan(10)
93
+ expect((result as string).length).toBeLessThan(longText.length)
94
+ }, AI_TIMEOUT)
95
+ })
96
+ }
97
+ })
98
+
99
+ describe('object output functions', () => {
100
+ for (const model of models) {
101
+ describe(`[${model.name}]`, () => {
102
+ it('executes sentiment analysis function', async () => {
103
+ const analyzeSentiment = define.generative({
104
+ name: 'analyzeSentiment',
105
+ args: { text: 'Text to analyze' },
106
+ output: 'object',
107
+ returnType: {
108
+ sentiment: 'positive | negative | neutral',
109
+ confidence: 'Confidence score 0-1 (number)',
110
+ reasoning: 'Brief explanation',
111
+ },
112
+ promptTemplate: 'Analyze the sentiment of: {{text}}',
113
+ model: model.id,
114
+ })
115
+
116
+ const result = await analyzeSentiment.call({ text: 'I absolutely love this product!' }) as {
117
+ sentiment: string
118
+ confidence: number
119
+ reasoning: string
120
+ }
121
+
122
+ expect(result).toBeDefined()
123
+ expect(['positive', 'negative', 'neutral']).toContain(result.sentiment)
124
+ expect(typeof result.confidence).toBe('number')
125
+ expect(result.confidence).toBeGreaterThanOrEqual(0)
126
+ expect(result.confidence).toBeLessThanOrEqual(1)
127
+ }, AI_TIMEOUT)
128
+
129
+ it('executes entity extraction function', async () => {
130
+ const extractEntities = define.generative({
131
+ name: 'extractEntities',
132
+ args: { text: 'Text to extract entities from' },
133
+ output: 'object',
134
+ returnType: {
135
+ people: ['Names of people mentioned'],
136
+ organizations: ['Names of organizations mentioned'],
137
+ locations: ['Location names mentioned'],
138
+ },
139
+ promptTemplate: 'Extract named entities from: {{text}}',
140
+ model: model.id,
141
+ })
142
+
143
+ const result = await extractEntities.call({
144
+ text: 'John Smith from Google met with Jane Doe at the New York office to discuss the project.',
145
+ }) as {
146
+ people: string[]
147
+ organizations: string[]
148
+ locations: string[]
149
+ }
150
+
151
+ expect(result).toBeDefined()
152
+ expect(Array.isArray(result.people)).toBe(true)
153
+ expect(Array.isArray(result.organizations)).toBe(true)
154
+ expect(Array.isArray(result.locations)).toBe(true)
155
+
156
+ // Should find at least some entities
157
+ const totalEntities = result.people.length + result.organizations.length + result.locations.length
158
+ expect(totalEntities).toBeGreaterThanOrEqual(2)
159
+ }, AI_TIMEOUT)
160
+
161
+ it('executes classification function', async () => {
162
+ const classifyTicket = define.generative({
163
+ name: 'classifyTicket',
164
+ args: { ticket: 'Support ticket text' },
165
+ output: 'object',
166
+ returnType: {
167
+ category: 'billing | technical | account | shipping',
168
+ priority: 'low | medium | high',
169
+ suggestedAction: 'Brief suggested action',
170
+ },
171
+ promptTemplate: 'Classify this support ticket: {{ticket}}',
172
+ model: model.id,
173
+ })
174
+
175
+ const result = await classifyTicket.call({
176
+ ticket: 'My credit card was charged twice for the same order and I need a refund immediately!',
177
+ }) as {
178
+ category: string
179
+ priority: string
180
+ suggestedAction: string
181
+ }
182
+
183
+ expect(result).toBeDefined()
184
+ expect(['billing', 'technical', 'account', 'shipping']).toContain(result.category)
185
+ expect(['low', 'medium', 'high']).toContain(result.priority)
186
+ expect(typeof result.suggestedAction).toBe('string')
187
+ }, AI_TIMEOUT)
188
+ })
189
+ }
190
+ })
191
+ })
192
+
193
+ // ==========================================================================
194
+ // Code Generation Functions
195
+ // ==========================================================================
196
+ describe('define.code()', () => {
197
+ for (const model of models) {
198
+ describe(`[${model.name}]`, () => {
199
+ it('generates TypeScript function', async () => {
200
+ const generateTs = define.code({
201
+ name: 'generateTypeScript',
202
+ args: { spec: 'Function specification' },
203
+ language: 'typescript',
204
+ model: model.id,
205
+ })
206
+
207
+ const result = await generateTs.call({
208
+ spec: 'A function that checks if a string is a valid URL',
209
+ })
210
+
211
+ expect(typeof result).toBe('string')
212
+ expect((result as string).length).toBeGreaterThan(20)
213
+ // Should contain TypeScript/JavaScript-like syntax
214
+ expect(result as string).toMatch(/function|const|=>|return/)
215
+ }, AI_TIMEOUT)
216
+
217
+ it('generates Python function', async () => {
218
+ const generatePy = define.code({
219
+ name: 'generatePython',
220
+ args: { spec: 'Function specification' },
221
+ language: 'python',
222
+ model: model.id,
223
+ })
224
+
225
+ const result = await generatePy.call({
226
+ spec: 'A function that calculates the factorial of a number',
227
+ })
228
+
229
+ expect(typeof result).toBe('string')
230
+ expect((result as string).length).toBeGreaterThan(20)
231
+ // Should contain Python-like syntax
232
+ expect(result as string).toMatch(/def|return|if|else/)
233
+ }, AI_TIMEOUT)
234
+
235
+ it('generates SQL query', async () => {
236
+ const generateSql = define.code({
237
+ name: 'generateSql',
238
+ args: { spec: 'Query specification' },
239
+ language: 'sql',
240
+ model: model.id,
241
+ })
242
+
243
+ const result = await generateSql.call({
244
+ spec: 'Select all users who signed up in the last 30 days',
245
+ })
246
+
247
+ expect(typeof result).toBe('string')
248
+ expect((result as string).toUpperCase()).toMatch(/SELECT|FROM|WHERE/)
249
+ }, AI_TIMEOUT)
250
+ })
251
+ }
252
+ })
253
+
254
+ // ==========================================================================
255
+ // Function Registration and Tool Generation
256
+ // ==========================================================================
257
+ describe('function registry and tools', () => {
258
+ it('registers functions in the registry', () => {
259
+ const fn = define.generative({
260
+ name: 'testRegistration',
261
+ args: { input: 'Test input' },
262
+ output: 'string',
263
+ })
264
+
265
+ expect(functions.has('testRegistration')).toBe(true)
266
+ expect(functions.get('testRegistration')).toBeDefined()
267
+ expect(functions.list()).toContain('testRegistration')
268
+ })
269
+
270
+ it('generates valid tool schema', () => {
271
+ const fn = define.generative({
272
+ name: 'processData',
273
+ description: 'Process input data and return results',
274
+ args: {
275
+ data: 'The data to process',
276
+ format: 'Output format (json | csv | xml)',
277
+ },
278
+ output: 'string',
279
+ })
280
+
281
+ const tool = fn.asTool()
282
+
283
+ expect(tool.name).toBe('processData')
284
+ expect(tool.description).toBe('Process input data and return results')
285
+ expect(tool.parameters.type).toBe('object')
286
+ expect(tool.parameters.properties).toHaveProperty('data')
287
+ expect(tool.parameters.properties).toHaveProperty('format')
288
+ expect(tool.parameters.required).toContain('data')
289
+ expect(tool.parameters.required).toContain('format')
290
+ })
291
+
292
+ it('clears all registered functions', () => {
293
+ define.generative({ name: 'fn1', args: {}, output: 'string' })
294
+ define.generative({ name: 'fn2', args: {}, output: 'string' })
295
+ define.generative({ name: 'fn3', args: {}, output: 'string' })
296
+
297
+ expect(functions.list().length).toBeGreaterThanOrEqual(3)
298
+
299
+ functions.clear()
300
+
301
+ expect(functions.list()).toEqual([])
302
+ })
303
+ })
304
+
305
+ // ==========================================================================
306
+ // Complex Multi-Step Functions
307
+ // ==========================================================================
308
+ describe('complex functions', () => {
309
+ for (const model of models) {
310
+ describe(`[${model.name}]`, () => {
311
+ it('executes multi-output analysis function', async () => {
312
+ const analyzeArticle = define.generative({
313
+ name: 'analyzeArticle',
314
+ args: { article: 'Article text to analyze' },
315
+ output: 'object',
316
+ returnType: {
317
+ title: 'Generated title for the article',
318
+ summary: 'Brief summary (2-3 sentences)',
319
+ keyPoints: ['Main points from the article'],
320
+ readingTime: 'Estimated reading time in minutes (number)',
321
+ },
322
+ promptTemplate: 'Analyze this article and provide structured output: {{article}}',
323
+ model: model.id,
324
+ })
325
+
326
+ const article = `
327
+ Machine learning has revolutionized the tech industry. Companies are using AI to automate tasks,
328
+ improve customer service, and make better predictions. From healthcare to finance, the applications
329
+ are endless. However, there are concerns about job displacement and ethical use of AI.
330
+ `
331
+
332
+ const result = await analyzeArticle.call({ article }) as {
333
+ title: string
334
+ summary: string
335
+ keyPoints: string[]
336
+ readingTime: number
337
+ }
338
+
339
+ expect(result).toBeDefined()
340
+ expect(typeof result.title).toBe('string')
341
+ expect(result.title.length).toBeGreaterThan(5)
342
+ expect(typeof result.summary).toBe('string')
343
+ expect(result.summary.length).toBeGreaterThan(20)
344
+ expect(Array.isArray(result.keyPoints)).toBe(true)
345
+ expect(result.keyPoints.length).toBeGreaterThanOrEqual(1)
346
+ expect(typeof result.readingTime).toBe('number')
347
+ }, AI_TIMEOUT)
348
+
349
+ it('executes math problem solver', async () => {
350
+ const solveMath = define.generative({
351
+ name: 'solveMath',
352
+ args: { problem: 'Math problem to solve' },
353
+ output: 'object',
354
+ returnType: {
355
+ answer: 'The numeric answer (number)',
356
+ steps: ['Step by step solution'],
357
+ formula: 'Mathematical formula used (if applicable)',
358
+ },
359
+ promptTemplate: 'Solve this math problem step by step: {{problem}}',
360
+ model: model.id,
361
+ })
362
+
363
+ const result = await solveMath.call({
364
+ problem: 'What is 15% of 200?',
365
+ }) as {
366
+ answer: number
367
+ steps: string[]
368
+ formula: string
369
+ }
370
+
371
+ expect(result).toBeDefined()
372
+ expect(typeof result.answer).toBe('number')
373
+ expect(Math.abs(result.answer - 30)).toBeLessThan(1) // Allow small floating point difference
374
+ expect(Array.isArray(result.steps)).toBe(true)
375
+ }, AI_TIMEOUT)
376
+ })
377
+ }
378
+ })
379
+ })