ai-functions 2.1.3 → 2.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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +90 -1
- package/README.md +38 -0
- package/dist/ai-promise.d.ts +3 -3
- package/dist/ai-promise.d.ts.map +1 -1
- package/dist/ai-promise.js +135 -64
- package/dist/ai-promise.js.map +1 -1
- package/dist/ai-schemas.d.ts +56 -0
- package/dist/ai-schemas.d.ts.map +1 -0
- package/dist/ai-schemas.js +53 -0
- package/dist/ai-schemas.js.map +1 -0
- package/dist/ai.d.ts +16 -242
- package/dist/ai.d.ts.map +1 -1
- package/dist/ai.js +51 -858
- package/dist/ai.js.map +1 -1
- package/dist/batch/anthropic.d.ts +6 -4
- package/dist/batch/anthropic.d.ts.map +1 -1
- package/dist/batch/anthropic.js +83 -145
- package/dist/batch/anthropic.js.map +1 -1
- package/dist/batch/bedrock.d.ts +8 -30
- package/dist/batch/bedrock.d.ts.map +1 -1
- package/dist/batch/bedrock.js +155 -338
- package/dist/batch/bedrock.js.map +1 -1
- package/dist/batch/cloudflare.d.ts +8 -20
- package/dist/batch/cloudflare.d.ts.map +1 -1
- package/dist/batch/cloudflare.js +68 -189
- package/dist/batch/cloudflare.js.map +1 -1
- package/dist/batch/google.d.ts +6 -20
- package/dist/batch/google.d.ts.map +1 -1
- package/dist/batch/google.js +70 -238
- package/dist/batch/google.js.map +1 -1
- package/dist/batch/index.d.ts +4 -1
- package/dist/batch/index.d.ts.map +1 -1
- package/dist/batch/index.js +4 -1
- package/dist/batch/index.js.map +1 -1
- package/dist/batch/memory.d.ts +1 -1
- package/dist/batch/memory.d.ts.map +1 -1
- package/dist/batch/memory.js +14 -10
- package/dist/batch/memory.js.map +1 -1
- package/dist/batch/openai.d.ts +11 -14
- package/dist/batch/openai.d.ts.map +1 -1
- package/dist/batch/openai.js +52 -156
- package/dist/batch/openai.js.map +1 -1
- package/dist/batch/provider.d.ts +111 -0
- package/dist/batch/provider.d.ts.map +1 -0
- package/dist/batch/provider.js +233 -0
- package/dist/batch/provider.js.map +1 -0
- package/dist/batch-map.d.ts.map +1 -1
- package/dist/batch-map.js +23 -17
- package/dist/batch-map.js.map +1 -1
- package/dist/batch-queue.d.ts +65 -0
- package/dist/batch-queue.d.ts.map +1 -1
- package/dist/batch-queue.js +169 -14
- package/dist/batch-queue.js.map +1 -1
- package/dist/budget.d.ts.map +1 -1
- package/dist/budget.js +27 -14
- package/dist/budget.js.map +1 -1
- package/dist/cache.d.ts +23 -0
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +36 -15
- package/dist/cache.js.map +1 -1
- package/dist/context.d.ts +26 -8
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +64 -62
- package/dist/context.js.map +1 -1
- package/dist/digital-objects-registry.d.ts +229 -0
- package/dist/digital-objects-registry.d.ts.map +1 -0
- package/dist/digital-objects-registry.js +617 -0
- package/dist/digital-objects-registry.js.map +1 -0
- package/dist/embeddings.d.ts +2 -2
- package/dist/embeddings.d.ts.map +1 -1
- package/dist/errors.d.ts +22 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +35 -0
- package/dist/errors.js.map +1 -0
- package/dist/eval/runner.d.ts +8 -0
- package/dist/eval/runner.d.ts.map +1 -1
- package/dist/eval/runner.js +41 -35
- package/dist/eval/runner.js.map +1 -1
- package/dist/eval-log/in-memory.d.ts +34 -0
- package/dist/eval-log/in-memory.d.ts.map +1 -0
- package/dist/eval-log/in-memory.js +84 -0
- package/dist/eval-log/in-memory.js.map +1 -0
- package/dist/eval-log/index.d.ts +29 -0
- package/dist/eval-log/index.d.ts.map +1 -0
- package/dist/eval-log/index.js +39 -0
- package/dist/eval-log/index.js.map +1 -0
- package/dist/eval-log/types.d.ts +101 -0
- package/dist/eval-log/types.d.ts.map +1 -0
- package/dist/eval-log/types.js +16 -0
- package/dist/eval-log/types.js.map +1 -0
- package/dist/function-registry.d.ts +176 -0
- package/dist/function-registry.d.ts.map +1 -0
- package/dist/function-registry.js +685 -0
- package/dist/function-registry.js.map +1 -0
- package/dist/generate.d.ts +9 -3
- package/dist/generate.d.ts.map +1 -1
- package/dist/generate.js +18 -18
- package/dist/generate.js.map +1 -1
- package/dist/index.d.ts +18 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +35 -18
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +118 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +187 -0
- package/dist/logger.js.map +1 -0
- package/dist/middleware/budget.d.ts +84 -0
- package/dist/middleware/budget.d.ts.map +1 -0
- package/dist/middleware/budget.js +110 -0
- package/dist/middleware/budget.js.map +1 -0
- package/dist/middleware/cache.d.ts +103 -0
- package/dist/middleware/cache.d.ts.map +1 -0
- package/dist/middleware/cache.js +228 -0
- package/dist/middleware/cache.js.map +1 -0
- package/dist/middleware/embed-cache.d.ts +99 -0
- package/dist/middleware/embed-cache.d.ts.map +1 -0
- package/dist/middleware/embed-cache.js +128 -0
- package/dist/middleware/embed-cache.js.map +1 -0
- package/dist/middleware/index.d.ts +11 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +11 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/trace.d.ts +103 -0
- package/dist/middleware/trace.d.ts.map +1 -0
- package/dist/middleware/trace.js +176 -0
- package/dist/middleware/trace.js.map +1 -0
- package/dist/primitives.d.ts +120 -1
- package/dist/primitives.d.ts.map +1 -1
- package/dist/primitives.js +398 -26
- package/dist/primitives.js.map +1 -1
- package/dist/retry.d.ts +66 -1
- package/dist/retry.d.ts.map +1 -1
- package/dist/retry.js +115 -8
- package/dist/retry.js.map +1 -1
- package/dist/sandbox.d.ts +36 -0
- package/dist/sandbox.d.ts.map +1 -0
- package/dist/sandbox.js +44 -0
- package/dist/sandbox.js.map +1 -0
- package/dist/schema.js +2 -2
- package/dist/schema.js.map +1 -1
- package/dist/telemetry.d.ts +128 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +285 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/template.d.ts.map +1 -1
- package/dist/template.js +6 -1
- package/dist/template.js.map +1 -1
- package/dist/tool-orchestration.d.ts +66 -4
- package/dist/tool-orchestration.d.ts.map +1 -1
- package/dist/tool-orchestration.js +123 -23
- package/dist/tool-orchestration.js.map +1 -1
- package/dist/type-guards.d.ts +28 -0
- package/dist/type-guards.d.ts.map +1 -0
- package/dist/type-guards.js +29 -0
- package/dist/type-guards.js.map +1 -0
- package/dist/types.d.ts +155 -19
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +36 -1
- package/dist/types.js.map +1 -1
- package/dist/wrap-for-v3.d.ts +80 -0
- package/dist/wrap-for-v3.d.ts.map +1 -0
- package/dist/wrap-for-v3.js +89 -0
- package/dist/wrap-for-v3.js.map +1 -0
- package/examples/00-quickstart.ts +232 -0
- package/examples/01-rag-chatbot.ts +212 -0
- package/examples/02-multi-agent-research.ts +290 -0
- package/examples/03-email-classification.ts +379 -0
- package/examples/04-content-moderation.ts +400 -0
- package/examples/05-document-extraction.ts +455 -0
- package/examples/06-streaming-chat-nextjs.ts +437 -0
- package/examples/07-cloudflare-worker.ts +483 -0
- package/examples/08-batch-processing.ts +491 -0
- package/examples/09-budget-constrained.ts +527 -0
- package/examples/10-tool-orchestration.ts +565 -0
- package/examples/11-retry-resilience.ts +403 -0
- package/examples/12-caching-strategies.ts +422 -0
- package/examples/README.md +145 -0
- package/package.json +29 -25
- package/src/ai-promise.ts +226 -140
- package/src/ai-schemas.ts +122 -0
- package/src/ai.ts +71 -1176
- package/src/batch/anthropic.ts +96 -161
- package/src/batch/bedrock.ts +203 -454
- package/src/batch/cloudflare.ts +99 -282
- package/src/batch/google.ts +91 -297
- package/src/batch/index.ts +4 -1
- package/src/batch/memory.ts +15 -10
- package/src/batch/openai.ts +65 -193
- package/src/batch/provider.ts +336 -0
- package/src/batch-map.ts +29 -24
- package/src/batch-queue.ts +200 -11
- package/src/budget.ts +31 -18
- package/src/cache.ts +45 -17
- package/src/context.ts +106 -77
- package/src/digital-objects-registry.ts +750 -0
- package/src/errors.ts +37 -0
- package/src/eval/runner.ts +60 -36
- package/src/eval-log/in-memory.ts +90 -0
- package/src/eval-log/index.ts +46 -0
- package/src/eval-log/types.ts +110 -0
- package/src/function-registry.ts +874 -0
- package/src/generate.ts +33 -28
- package/src/index.ts +122 -21
- package/src/logger.ts +232 -0
- package/src/middleware/budget.ts +171 -0
- package/src/middleware/cache.ts +299 -0
- package/src/middleware/embed-cache.ts +195 -0
- package/src/middleware/index.ts +23 -0
- package/src/middleware/trace.ts +248 -0
- package/src/primitives.ts +589 -62
- package/src/retry.ts +144 -18
- package/src/sandbox.ts +52 -0
- package/src/schema.ts +8 -8
- package/src/telemetry.ts +403 -0
- package/src/template.ts +8 -4
- package/src/tool-orchestration.ts +213 -48
- package/src/type-guards.ts +31 -0
- package/src/types.ts +186 -27
- package/src/wrap-for-v3.ts +105 -0
- package/test/ai-promise.test.ts +1080 -0
- package/test/ai-proxy.test.ts +1 -1
- package/test/batch-autosubmit-errors.test.ts +49 -37
- package/test/batch-blog-posts.test.ts +87 -129
- package/test/core-functions.test.ts +183 -579
- package/test/decide.test.ts +154 -322
- package/test/define.test.ts +211 -8
- package/test/digital-objects-registry.test.ts +760 -0
- package/test/embedding-cache-middleware.test.ts +140 -0
- package/test/fill-template.test.ts +89 -0
- package/test/generate-core.test.ts +140 -229
- package/test/implicit-batch.test.ts +22 -65
- package/test/retry-policy-integration.test.ts +117 -0
- package/test/sandbox-execution.test.ts +155 -0
- package/test/schema.test.ts +55 -19
- package/test/template.test.ts +1164 -0
- package/test/tool-orchestration.test.ts +270 -0
- package/test/wrap-for-v3.test.ts +612 -0
- package/vitest.config.js +6 -0
- package/vitest.config.ts +20 -0
- package/LICENSE +0 -21
- package/dist/rpc/auth.d.ts +0 -69
- package/dist/rpc/auth.d.ts.map +0 -1
- package/dist/rpc/auth.js +0 -136
- package/dist/rpc/auth.js.map +0 -1
- package/dist/rpc/client.d.ts +0 -62
- package/dist/rpc/client.d.ts.map +0 -1
- package/dist/rpc/client.js +0 -103
- package/dist/rpc/client.js.map +0 -1
- package/dist/rpc/deferred.d.ts +0 -60
- package/dist/rpc/deferred.d.ts.map +0 -1
- package/dist/rpc/deferred.js +0 -96
- package/dist/rpc/deferred.js.map +0 -1
- package/dist/rpc/index.d.ts +0 -22
- package/dist/rpc/index.d.ts.map +0 -1
- package/dist/rpc/index.js +0 -38
- package/dist/rpc/index.js.map +0 -1
- package/dist/rpc/local.d.ts +0 -42
- package/dist/rpc/local.d.ts.map +0 -1
- package/dist/rpc/local.js +0 -50
- package/dist/rpc/local.js.map +0 -1
- package/dist/rpc/server.d.ts +0 -165
- package/dist/rpc/server.d.ts.map +0 -1
- package/dist/rpc/server.js +0 -405
- package/dist/rpc/server.js.map +0 -1
- package/dist/rpc/session.d.ts +0 -32
- package/dist/rpc/session.d.ts.map +0 -1
- package/dist/rpc/session.js +0 -43
- package/dist/rpc/session.js.map +0 -1
- package/dist/rpc/transport.d.ts +0 -306
- package/dist/rpc/transport.d.ts.map +0 -1
- package/dist/rpc/transport.js +0 -731
- package/dist/rpc/transport.js.map +0 -1
- package/src/batch/anthropic.js +0 -256
- package/src/batch/bedrock.js +0 -584
- package/src/batch/cloudflare.js +0 -287
- package/src/batch/google.js +0 -359
- package/src/batch/index.js +0 -30
- package/src/batch/memory.js +0 -187
- package/src/batch/openai.js +0 -402
- package/src/eval/index.js +0 -7
- package/src/eval/models.js +0 -119
- package/src/eval/runner.js +0 -147
- package/test/schema.test.js +0 -96
package/test/decide.test.ts
CHANGED
|
@@ -2,327 +2,170 @@
|
|
|
2
2
|
* Tests for the decide() function - LLM as Judge
|
|
3
3
|
*
|
|
4
4
|
* decide`criteria`(optionA, optionB) - Compare options and pick the best
|
|
5
|
+
*
|
|
6
|
+
* Tests require actual AI calls via the Cloudflare AI Gateway for real decisions.
|
|
5
7
|
*/
|
|
6
8
|
|
|
7
|
-
import { describe, it, expect
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
// Mock for underlying AI calls
|
|
11
|
-
// ============================================================================
|
|
12
|
-
|
|
13
|
-
const mockDecide = vi.fn()
|
|
9
|
+
import { describe, it, expect } from 'vitest'
|
|
10
|
+
import { generateObject } from '../src/generate.js'
|
|
11
|
+
import { z } from 'zod'
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
* Returns a function that compares options against criteria
|
|
18
|
-
*/
|
|
19
|
-
function createMockDecide() {
|
|
20
|
-
return function decide(
|
|
21
|
-
promptOrStrings: string | TemplateStringsArray,
|
|
22
|
-
...args: unknown[]
|
|
23
|
-
) {
|
|
24
|
-
let criteria: string
|
|
25
|
-
|
|
26
|
-
if (Array.isArray(promptOrStrings) && 'raw' in promptOrStrings) {
|
|
27
|
-
criteria = (promptOrStrings as TemplateStringsArray).reduce((acc, str, i) => {
|
|
28
|
-
return acc + str + (args[i] ?? '')
|
|
29
|
-
}, '')
|
|
30
|
-
} else {
|
|
31
|
-
criteria = promptOrStrings as string
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Return a function that accepts options to compare
|
|
35
|
-
return function compareOptions<T>(...options: T[]): Promise<T> {
|
|
36
|
-
return mockDecide(criteria, options)
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
13
|
+
// Skip tests if no gateway configured
|
|
14
|
+
const hasGateway = !!process.env.AI_GATEWAY_URL
|
|
40
15
|
|
|
41
16
|
// ============================================================================
|
|
42
|
-
//
|
|
17
|
+
// Real AI Decision Tests
|
|
43
18
|
// ============================================================================
|
|
44
19
|
|
|
45
|
-
describe('decide() - LLM as Judge', () => {
|
|
46
|
-
beforeEach(() => {
|
|
47
|
-
mockDecide.mockReset()
|
|
48
|
-
})
|
|
49
|
-
|
|
20
|
+
describe.skipIf(!hasGateway)('decide() - LLM as Judge with Real AI', () => {
|
|
50
21
|
describe('basic comparison', () => {
|
|
51
22
|
it('compares two options and returns the better one', async () => {
|
|
52
|
-
const decide = createMockDecide()
|
|
53
|
-
|
|
54
23
|
const optionA = { name: 'Simple Solution', complexity: 'low', time: '1 day' }
|
|
55
24
|
const optionB = { name: 'Complex Solution', complexity: 'high', time: '1 week' }
|
|
56
25
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
{ framework: 'React', popularity: 'high' },
|
|
73
|
-
{ framework: 'Vue', popularity: 'medium' },
|
|
74
|
-
{ framework: 'Angular', popularity: 'medium' },
|
|
75
|
-
{ framework: 'Svelte', popularity: 'growing' },
|
|
76
|
-
]
|
|
77
|
-
|
|
78
|
-
mockDecide.mockResolvedValue(options[0])
|
|
79
|
-
|
|
80
|
-
const result = await decide`best for large enterprise applications`(
|
|
81
|
-
options[0],
|
|
82
|
-
options[1],
|
|
83
|
-
options[2],
|
|
84
|
-
options[3]
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
expect(mockDecide).toHaveBeenCalledWith(
|
|
88
|
-
'best for large enterprise applications',
|
|
89
|
-
options
|
|
90
|
-
)
|
|
91
|
-
expect(result).toEqual({ framework: 'React', popularity: 'high' })
|
|
26
|
+
const result = await generateObject({
|
|
27
|
+
model: 'haiku',
|
|
28
|
+
schema: z.object({
|
|
29
|
+
winner: z.enum(['A', 'B']).describe('Which option is faster to implement?'),
|
|
30
|
+
reasoning: z.string().describe('Brief explanation'),
|
|
31
|
+
}),
|
|
32
|
+
prompt: `Compare these options and pick the fastest to implement:
|
|
33
|
+
Option A: ${JSON.stringify(optionA)}
|
|
34
|
+
Option B: ${JSON.stringify(optionB)}`,
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
expect(['A', 'B']).toContain(result.object.winner)
|
|
38
|
+
// Simple solution should be faster
|
|
39
|
+
expect(result.object.winner).toBe('A')
|
|
40
|
+
expect(result.object.reasoning.length).toBeGreaterThan(0)
|
|
92
41
|
})
|
|
93
42
|
|
|
94
43
|
it('works with string options', async () => {
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
44
|
+
const result = await generateObject({
|
|
45
|
+
model: 'haiku',
|
|
46
|
+
schema: z.object({
|
|
47
|
+
winner: z.enum(['A', 'B']).describe('Which headline is better for developers?'),
|
|
48
|
+
}),
|
|
49
|
+
prompt: `Which headline is better for a developer audience?
|
|
50
|
+
A: "Simple Guide to TypeScript"
|
|
51
|
+
B: "TypeScript: Advanced Patterns for Enterprise"`,
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
expect(['A', 'B']).toContain(result.object.winner)
|
|
105
55
|
})
|
|
106
56
|
})
|
|
107
57
|
|
|
108
|
-
describe('
|
|
109
|
-
it('
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
)
|
|
119
|
-
|
|
120
|
-
expect(
|
|
121
|
-
|
|
122
|
-
expect.any(Array)
|
|
123
|
-
)
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
it('supports complex criteria descriptions', async () => {
|
|
127
|
-
const decide = createMockDecide()
|
|
128
|
-
|
|
129
|
-
const requirements = {
|
|
130
|
-
performance: 'high',
|
|
131
|
-
maintainability: 'critical',
|
|
132
|
-
teamExperience: 'junior',
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
mockDecide.mockResolvedValue({ approach: 'A' })
|
|
136
|
-
|
|
137
|
-
// Criteria would have requirements as YAML
|
|
138
|
-
await decide`best architecture considering team and requirements`(
|
|
139
|
-
{ approach: 'A' },
|
|
140
|
-
{ approach: 'B' }
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
expect(mockDecide).toHaveBeenCalled()
|
|
58
|
+
describe('multiple options comparison', () => {
|
|
59
|
+
it('compares multiple framework options', async () => {
|
|
60
|
+
const result = await generateObject({
|
|
61
|
+
model: 'haiku',
|
|
62
|
+
schema: z.object({
|
|
63
|
+
winner: z.enum(['React', 'Vue', 'Angular', 'Svelte']).describe('Best for enterprise'),
|
|
64
|
+
reasoning: z.string().describe('Brief explanation'),
|
|
65
|
+
}),
|
|
66
|
+
prompt: `Which frontend framework is best for large enterprise applications with many developers?
|
|
67
|
+
Options: React, Vue, Angular, Svelte`,
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
expect(['React', 'Vue', 'Angular', 'Svelte']).toContain(result.object.winner)
|
|
71
|
+
expect(result.object.reasoning.length).toBeGreaterThan(0)
|
|
144
72
|
})
|
|
145
73
|
})
|
|
146
74
|
|
|
147
|
-
describe('
|
|
148
|
-
it('
|
|
149
|
-
const decide = createMockDecide()
|
|
150
|
-
|
|
75
|
+
describe('A/B testing use case', () => {
|
|
76
|
+
it('evaluates headline click-through potential', async () => {
|
|
151
77
|
const headlineA = 'Get Started with TypeScript Today'
|
|
152
78
|
const headlineB = 'TypeScript: The Complete Guide for 2025'
|
|
153
79
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
for (let i = 2; i <= Math.sqrt(n); i++) {
|
|
171
|
-
if (n % i === 0) return false;
|
|
172
|
-
}
|
|
173
|
-
return true;
|
|
174
|
-
}`,
|
|
175
|
-
`function isPrime(n) {
|
|
176
|
-
if (n <= 1) return false;
|
|
177
|
-
if (n <= 3) return true;
|
|
178
|
-
if (n % 2 === 0 || n % 3 === 0) return false;
|
|
179
|
-
for (let i = 5; i * i <= n; i += 6) {
|
|
180
|
-
if (n % i === 0 || n % (i + 2) === 0) return false;
|
|
181
|
-
}
|
|
182
|
-
return true;
|
|
183
|
-
}`,
|
|
184
|
-
]
|
|
185
|
-
|
|
186
|
-
mockDecide.mockResolvedValue(implementations[1])
|
|
187
|
-
|
|
188
|
-
const best = await decide`most performant and readable`(
|
|
189
|
-
implementations[0],
|
|
190
|
-
implementations[1]
|
|
191
|
-
)
|
|
192
|
-
|
|
193
|
-
expect(best).toBe(implementations[1])
|
|
80
|
+
const result = await generateObject({
|
|
81
|
+
model: 'haiku',
|
|
82
|
+
schema: z.object({
|
|
83
|
+
winner: z
|
|
84
|
+
.enum(['A', 'B'])
|
|
85
|
+
.describe('Which headline would have higher click-through rate?'),
|
|
86
|
+
confidence: z.number().min(0).max(1).describe('Confidence score'),
|
|
87
|
+
}),
|
|
88
|
+
prompt: `For a developer audience, which headline would have higher click-through rate?
|
|
89
|
+
Headline A: "${headlineA}"
|
|
90
|
+
Headline B: "${headlineB}"`,
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
expect(['A', 'B']).toContain(result.object.winner)
|
|
94
|
+
expect(result.object.confidence).toBeGreaterThanOrEqual(0)
|
|
95
|
+
expect(result.object.confidence).toBeLessThanOrEqual(1)
|
|
194
96
|
})
|
|
195
|
-
|
|
196
|
-
it('selecting marketing copy', async () => {
|
|
197
|
-
const decide = createMockDecide()
|
|
198
|
-
|
|
199
|
-
const copies = [
|
|
200
|
-
{ text: 'Build faster with AI', tone: 'direct' },
|
|
201
|
-
{ text: 'Empower your workflow', tone: 'inspirational' },
|
|
202
|
-
{ text: '10x your productivity', tone: 'bold' },
|
|
203
|
-
]
|
|
204
|
-
|
|
205
|
-
mockDecide.mockResolvedValue(copies[2])
|
|
206
|
-
|
|
207
|
-
const winner = await decide`most compelling for startup founders`(
|
|
208
|
-
copies[0],
|
|
209
|
-
copies[1],
|
|
210
|
-
copies[2]
|
|
211
|
-
)
|
|
212
|
-
|
|
213
|
-
expect(winner).toEqual({ text: '10x your productivity', tone: 'bold' })
|
|
214
|
-
})
|
|
215
|
-
})
|
|
216
|
-
})
|
|
217
|
-
|
|
218
|
-
// ============================================================================
|
|
219
|
-
// decide() with options parameter
|
|
220
|
-
// ============================================================================
|
|
221
|
-
|
|
222
|
-
describe('decide() with options', () => {
|
|
223
|
-
beforeEach(() => {
|
|
224
|
-
mockDecide.mockReset()
|
|
225
|
-
})
|
|
226
|
-
|
|
227
|
-
it('accepts model option', async () => {
|
|
228
|
-
// This tests the concept - actual implementation would pass options
|
|
229
|
-
const mockDecideWithOptions = vi.fn()
|
|
230
|
-
|
|
231
|
-
function createMockDecideWithOptions() {
|
|
232
|
-
return function decide(
|
|
233
|
-
promptOrStrings: string | TemplateStringsArray,
|
|
234
|
-
...args: unknown[]
|
|
235
|
-
) {
|
|
236
|
-
let criteria: string
|
|
237
|
-
|
|
238
|
-
if (Array.isArray(promptOrStrings) && 'raw' in promptOrStrings) {
|
|
239
|
-
criteria = (promptOrStrings as TemplateStringsArray).reduce((acc, str, i) => {
|
|
240
|
-
return acc + str + (args[i] ?? '')
|
|
241
|
-
}, '')
|
|
242
|
-
} else {
|
|
243
|
-
criteria = promptOrStrings as string
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
return function compareOptions<T>(...options: T[]) {
|
|
247
|
-
// Support options as last parameter if it's an object with known keys
|
|
248
|
-
const lastArg = options[options.length - 1] as Record<string, unknown>
|
|
249
|
-
if (lastArg && typeof lastArg === 'object' && 'model' in lastArg) {
|
|
250
|
-
const realOptions = options.slice(0, -1)
|
|
251
|
-
return mockDecideWithOptions(criteria, realOptions, lastArg)
|
|
252
|
-
}
|
|
253
|
-
return mockDecideWithOptions(criteria, options, undefined)
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
const decide = createMockDecideWithOptions()
|
|
259
|
-
mockDecideWithOptions.mockResolvedValue('A')
|
|
260
|
-
|
|
261
|
-
// Two ways to pass options:
|
|
262
|
-
// 1. Separate options object
|
|
263
|
-
await decide`better option`('A', 'B')
|
|
264
|
-
|
|
265
|
-
expect(mockDecideWithOptions).toHaveBeenCalledWith(
|
|
266
|
-
'better option',
|
|
267
|
-
['A', 'B'],
|
|
268
|
-
undefined
|
|
269
|
-
)
|
|
270
97
|
})
|
|
271
98
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
99
|
+
describe('code comparison use case', () => {
|
|
100
|
+
it('compares code implementations', async () => {
|
|
101
|
+
const impl1 = `function isPrime(n) {
|
|
102
|
+
if (n <= 1) return false;
|
|
103
|
+
for (let i = 2; i <= Math.sqrt(n); i++) {
|
|
104
|
+
if (n % i === 0) return false;
|
|
105
|
+
}
|
|
106
|
+
return true;
|
|
107
|
+
}`
|
|
108
|
+
|
|
109
|
+
const impl2 = `function isPrime(n) {
|
|
110
|
+
if (n <= 1) return false;
|
|
111
|
+
if (n <= 3) return true;
|
|
112
|
+
if (n % 2 === 0 || n % 3 === 0) return false;
|
|
113
|
+
for (let i = 5; i * i <= n; i += 6) {
|
|
114
|
+
if (n % i === 0 || n % (i + 2) === 0) return false;
|
|
115
|
+
}
|
|
116
|
+
return true;
|
|
117
|
+
}`
|
|
118
|
+
|
|
119
|
+
const result = await generateObject({
|
|
120
|
+
model: 'haiku',
|
|
121
|
+
schema: z.object({
|
|
122
|
+
winner: z.enum(['A', 'B']).describe('Most performant implementation'),
|
|
123
|
+
reasoning: z.string().describe('Why this is more performant'),
|
|
124
|
+
}),
|
|
125
|
+
prompt: `Which isPrime implementation is more performant?
|
|
126
|
+
Implementation A:
|
|
127
|
+
${impl1}
|
|
128
|
+
|
|
129
|
+
Implementation B:
|
|
130
|
+
${impl2}`,
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
expect(['A', 'B']).toContain(result.object.winner)
|
|
134
|
+
// The second implementation with 6k optimization should be faster
|
|
135
|
+
expect(result.object.winner).toBe('B')
|
|
279
136
|
})
|
|
280
|
-
|
|
281
|
-
expect(mockDecideWithThinking).toHaveBeenCalledWith(
|
|
282
|
-
'complex criteria',
|
|
283
|
-
['A', 'B'],
|
|
284
|
-
{ thinking: 'high' }
|
|
285
|
-
)
|
|
286
137
|
})
|
|
287
138
|
})
|
|
288
139
|
|
|
289
140
|
// ============================================================================
|
|
290
|
-
//
|
|
141
|
+
// Type Inference Pattern Tests (no AI needed)
|
|
291
142
|
// ============================================================================
|
|
292
143
|
|
|
293
|
-
describe('decide()
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
it('returns the exact option object passed in', async () => {
|
|
299
|
-
const decide = createMockDecide()
|
|
300
|
-
|
|
301
|
-
const optionA = { id: 1, name: 'Option A', metadata: { score: 10 } }
|
|
302
|
-
const optionB = { id: 2, name: 'Option B', metadata: { score: 20 } }
|
|
144
|
+
describe('decide() pattern tests', () => {
|
|
145
|
+
it('documents the decide function signature', () => {
|
|
146
|
+
// decide`criteria`(option1, option2, ...) returns the winning option
|
|
147
|
+
// The return type matches the input option type
|
|
303
148
|
|
|
304
|
-
|
|
305
|
-
mockDecide.mockResolvedValue(optionA)
|
|
149
|
+
type DecideSignature<T> = (criteria: string, options: T[]) => Promise<T>
|
|
306
150
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
expect(result).toBe(optionA)
|
|
151
|
+
// Verify the type signature compiles
|
|
152
|
+
const signature: DecideSignature<{ name: string }> = async (_criteria, options) => options[0]
|
|
153
|
+
expect(typeof signature).toBe('function')
|
|
311
154
|
})
|
|
312
155
|
|
|
313
|
-
it('
|
|
314
|
-
// Extended mode returns both the
|
|
315
|
-
|
|
156
|
+
it('documents extended mode with reasoning', () => {
|
|
157
|
+
// Extended mode returns both the choice and reasoning
|
|
158
|
+
type DecideWithReasoning<T> = {
|
|
159
|
+
choice: T
|
|
160
|
+
reasoning: string
|
|
161
|
+
confidence: number
|
|
162
|
+
}
|
|
316
163
|
|
|
317
|
-
|
|
164
|
+
const result: DecideWithReasoning<string> = {
|
|
318
165
|
choice: 'Option A',
|
|
319
|
-
reasoning: '
|
|
166
|
+
reasoning: 'Better for the use case',
|
|
320
167
|
confidence: 0.85,
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
const result = await mockDecideWithReasoning('criteria', ['A', 'B'], {
|
|
324
|
-
explain: true,
|
|
325
|
-
})
|
|
168
|
+
}
|
|
326
169
|
|
|
327
170
|
expect(result).toHaveProperty('choice')
|
|
328
171
|
expect(result).toHaveProperty('reasoning')
|
|
@@ -331,63 +174,52 @@ describe('decide() return value', () => {
|
|
|
331
174
|
})
|
|
332
175
|
|
|
333
176
|
// ============================================================================
|
|
334
|
-
// Edge
|
|
177
|
+
// Edge Cases
|
|
335
178
|
// ============================================================================
|
|
336
179
|
|
|
337
|
-
describe('decide() edge cases', () => {
|
|
338
|
-
beforeEach(() => {
|
|
339
|
-
mockDecide.mockReset()
|
|
340
|
-
})
|
|
341
|
-
|
|
180
|
+
describe.skipIf(!hasGateway)('decide() edge cases', () => {
|
|
342
181
|
it('handles identical options', async () => {
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
const decide = createMockDecide()
|
|
354
|
-
|
|
355
|
-
const option = { content: 'Some text' }
|
|
356
|
-
mockDecide.mockResolvedValue(option)
|
|
357
|
-
|
|
358
|
-
// Single option - essentially asking "is this good enough?"
|
|
359
|
-
const result = await decide`meets quality standards`(option)
|
|
182
|
+
const result = await generateObject({
|
|
183
|
+
model: 'haiku',
|
|
184
|
+
schema: z.object({
|
|
185
|
+
winner: z.enum(['A', 'B']).describe('Which is better?'),
|
|
186
|
+
areSimilar: z.boolean().describe('Are the options essentially the same?'),
|
|
187
|
+
}),
|
|
188
|
+
prompt: `Which option is better?
|
|
189
|
+
Option A: "Hello World"
|
|
190
|
+
Option B: "Hello World"`,
|
|
191
|
+
})
|
|
360
192
|
|
|
361
|
-
expect(
|
|
193
|
+
expect(['A', 'B']).toContain(result.object.winner)
|
|
194
|
+
expect(result.object.areSimilar).toBe(true)
|
|
362
195
|
})
|
|
363
196
|
|
|
364
197
|
it('handles complex nested objects', async () => {
|
|
365
|
-
const decide = createMockDecide()
|
|
366
|
-
|
|
367
198
|
const architectureA = {
|
|
368
199
|
name: 'Microservices',
|
|
369
|
-
components:
|
|
370
|
-
|
|
371
|
-
services: ['auth', 'users', 'products'],
|
|
372
|
-
database: { type: 'distributed', engine: 'CockroachDB' },
|
|
373
|
-
},
|
|
200
|
+
components: ['gateway', 'auth', 'users', 'products'],
|
|
201
|
+
database: 'distributed',
|
|
374
202
|
}
|
|
375
203
|
|
|
376
204
|
const architectureB = {
|
|
377
205
|
name: 'Monolith',
|
|
378
|
-
components:
|
|
379
|
-
|
|
380
|
-
database: { type: 'single', engine: 'PostgreSQL' },
|
|
381
|
-
},
|
|
206
|
+
components: ['app'],
|
|
207
|
+
database: 'single',
|
|
382
208
|
}
|
|
383
209
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
210
|
+
const result = await generateObject({
|
|
211
|
+
model: 'haiku',
|
|
212
|
+
schema: z.object({
|
|
213
|
+
winner: z.enum(['Microservices', 'Monolith']).describe('Better for a 3-developer startup'),
|
|
214
|
+
reasoning: z.string(),
|
|
215
|
+
}),
|
|
216
|
+
prompt: `Which architecture is better for a startup with 3 developers?
|
|
217
|
+
Option 1: ${JSON.stringify(architectureA)}
|
|
218
|
+
Option 2: ${JSON.stringify(architectureB)}`,
|
|
219
|
+
})
|
|
390
220
|
|
|
391
|
-
expect(
|
|
221
|
+
expect(['Microservices', 'Monolith']).toContain(result.object.winner)
|
|
222
|
+
// Monolith is typically better for small teams
|
|
223
|
+
expect(result.object.winner).toBe('Monolith')
|
|
392
224
|
})
|
|
393
225
|
})
|