ai-functions
Version:
Core AI primitives for building intelligent applications
226 lines (193 loc) • 7.69 kB
text/typescript
/**
* Tests for the decide() function - LLM as Judge
*
* decide`criteria`(optionA, optionB) - Compare options and pick the best
*
* Tests require actual AI calls via the Cloudflare AI Gateway for real decisions.
*/
import { describe, it, expect } from 'vitest'
import { generateObject } from '../src/generate.js'
import { z } from 'zod'
// Skip tests if no gateway configured
const hasGateway = !!process.env.AI_GATEWAY_URL
// ============================================================================
// Real AI Decision Tests
// ============================================================================
describe.skipIf(!hasGateway)('decide() - LLM as Judge with Real AI', () => {
describe('basic comparison', () => {
it('compares two options and returns the better one', async () => {
const optionA = { name: 'Simple Solution', complexity: 'low', time: '1 day' }
const optionB = { name: 'Complex Solution', complexity: 'high', time: '1 week' }
const result = await generateObject({
model: 'haiku',
schema: z.object({
winner: z.enum(['A', 'B']).describe('Which option is faster to implement?'),
reasoning: z.string().describe('Brief explanation'),
}),
prompt: `Compare these options and pick the fastest to implement:
Option A: ${JSON.stringify(optionA)}
Option B: ${JSON.stringify(optionB)}`,
})
expect(['A', 'B']).toContain(result.object.winner)
// Simple solution should be faster
expect(result.object.winner).toBe('A')
expect(result.object.reasoning.length).toBeGreaterThan(0)
})
it('works with string options', async () => {
const result = await generateObject({
model: 'haiku',
schema: z.object({
winner: z.enum(['A', 'B']).describe('Which headline is better for developers?'),
}),
prompt: `Which headline is better for a developer audience?
A: "Simple Guide to TypeScript"
B: "TypeScript: Advanced Patterns for Enterprise"`,
})
expect(['A', 'B']).toContain(result.object.winner)
})
})
describe('multiple options comparison', () => {
it('compares multiple framework options', async () => {
const result = await generateObject({
model: 'haiku',
schema: z.object({
winner: z.enum(['React', 'Vue', 'Angular', 'Svelte']).describe('Best for enterprise'),
reasoning: z.string().describe('Brief explanation'),
}),
prompt: `Which frontend framework is best for large enterprise applications with many developers?
Options: React, Vue, Angular, Svelte`,
})
expect(['React', 'Vue', 'Angular', 'Svelte']).toContain(result.object.winner)
expect(result.object.reasoning.length).toBeGreaterThan(0)
})
})
describe('A/B testing use case', () => {
it('evaluates headline click-through potential', async () => {
const headlineA = 'Get Started with TypeScript Today'
const headlineB = 'TypeScript: The Complete Guide for 2025'
const result = await generateObject({
model: 'haiku',
schema: z.object({
winner: z
.enum(['A', 'B'])
.describe('Which headline would have higher click-through rate?'),
confidence: z.number().min(0).max(1).describe('Confidence score'),
}),
prompt: `For a developer audience, which headline would have higher click-through rate?
Headline A: "${headlineA}"
Headline B: "${headlineB}"`,
})
expect(['A', 'B']).toContain(result.object.winner)
expect(result.object.confidence).toBeGreaterThanOrEqual(0)
expect(result.object.confidence).toBeLessThanOrEqual(1)
})
})
describe('code comparison use case', () => {
it('compares code implementations', async () => {
const impl1 = `function isPrime(n) {
if (n <= 1) return false;
for (let i = 2; i <= Math.sqrt(n); i++) {
if (n % i === 0) return false;
}
return true;
}`
const impl2 = `function isPrime(n) {
if (n <= 1) return false;
if (n <= 3) return true;
if (n % 2 === 0 || n % 3 === 0) return false;
for (let i = 5; i * i <= n; i += 6) {
if (n % i === 0 || n % (i + 2) === 0) return false;
}
return true;
}`
const result = await generateObject({
model: 'haiku',
schema: z.object({
winner: z.enum(['A', 'B']).describe('Most performant implementation'),
reasoning: z.string().describe('Why this is more performant'),
}),
prompt: `Which isPrime implementation is more performant?
Implementation A:
${impl1}
Implementation B:
${impl2}`,
})
expect(['A', 'B']).toContain(result.object.winner)
// The second implementation with 6k optimization should be faster
expect(result.object.winner).toBe('B')
})
})
})
// ============================================================================
// Type Inference Pattern Tests (no AI needed)
// ============================================================================
describe('decide() pattern tests', () => {
it('documents the decide function signature', () => {
// decide`criteria`(option1, option2, ...) returns the winning option
// The return type matches the input option type
type DecideSignature<T> = (criteria: string, options: T[]) => Promise<T>
// Verify the type signature compiles
const signature: DecideSignature<{ name: string }> = async (_criteria, options) => options[0]
expect(typeof signature).toBe('function')
})
it('documents extended mode with reasoning', () => {
// Extended mode returns both the choice and reasoning
type DecideWithReasoning<T> = {
choice: T
reasoning: string
confidence: number
}
const result: DecideWithReasoning<string> = {
choice: 'Option A',
reasoning: 'Better for the use case',
confidence: 0.85,
}
expect(result).toHaveProperty('choice')
expect(result).toHaveProperty('reasoning')
expect(result).toHaveProperty('confidence')
})
})
// ============================================================================
// Edge Cases
// ============================================================================
describe.skipIf(!hasGateway)('decide() edge cases', () => {
it('handles identical options', async () => {
const result = await generateObject({
model: 'haiku',
schema: z.object({
winner: z.enum(['A', 'B']).describe('Which is better?'),
areSimilar: z.boolean().describe('Are the options essentially the same?'),
}),
prompt: `Which option is better?
Option A: "Hello World"
Option B: "Hello World"`,
})
expect(['A', 'B']).toContain(result.object.winner)
expect(result.object.areSimilar).toBe(true)
})
it('handles complex nested objects', async () => {
const architectureA = {
name: 'Microservices',
components: ['gateway', 'auth', 'users', 'products'],
database: 'distributed',
}
const architectureB = {
name: 'Monolith',
components: ['app'],
database: 'single',
}
const result = await generateObject({
model: 'haiku',
schema: z.object({
winner: z.enum(['Microservices', 'Monolith']).describe('Better for a 3-developer startup'),
reasoning: z.string(),
}),
prompt: `Which architecture is better for a startup with 3 developers?
Option 1: ${JSON.stringify(architectureA)}
Option 2: ${JSON.stringify(architectureB)}`,
})
expect(['Microservices', 'Monolith']).toContain(result.object.winner)
// Monolith is typically better for small teams
expect(result.object.winner).toBe('Monolith')
})
})