ai-functions
Version:
Core AI primitives for building intelligent applications
259 lines (220 loc) • 7.32 kB
text/typescript
/**
* In-Memory Batch Adapter for Testing
*
* Simulates batch processing locally for testing purposes.
* Executes requests immediately (or with configurable delay).
*
* @packageDocumentation
*/
import {
registerBatchAdapter,
type BatchAdapter,
type BatchItem,
type BatchJob,
type BatchQueueOptions,
type BatchResult,
type BatchSubmitResult,
} from './provider.js'
import { generateObject, generateText } from '../generate.js'
// ============================================================================
// Types
// ============================================================================
interface MemoryBatch {
id: string
items: BatchItem[]
options: BatchQueueOptions
status: 'pending' | 'in_progress' | 'completed' | 'failed'
results: BatchResult[]
createdAt: Date
completedAt?: Date
}
// ============================================================================
// Storage
// ============================================================================
const batches = new Map<string, MemoryBatch>()
let batchCounter = 0
/**
* Options for the memory adapter
*/
export interface MemoryAdapterOptions {
/** Simulate processing delay in ms */
delay?: number
/** Mock handler for custom processing */
handler?: (item: BatchItem) => Promise<unknown>
/** Simulate failure rate (0-1) */
failureRate?: number
}
let memoryOptions: MemoryAdapterOptions = {}
/**
* Configure the memory adapter
*/
export function configureMemoryAdapter(options: MemoryAdapterOptions): void {
memoryOptions = options
}
/**
* Clear all stored batches (for testing)
*/
export function clearBatches(): void {
batches.clear()
batchCounter = 0
}
/**
* Get all stored batches (for testing)
*/
export function getBatches(): Map<string, MemoryBatch> {
return batches
}
// ============================================================================
// Memory Batch Adapter
// ============================================================================
const memoryAdapter: BatchAdapter = {
async submit(items: BatchItem[], options: BatchQueueOptions): Promise<BatchSubmitResult> {
const batchId = `batch_memory_${++batchCounter}`
const batch: MemoryBatch = {
id: batchId,
items: [...items],
options,
status: 'pending',
results: [],
createdAt: new Date(),
}
batches.set(batchId, batch)
// Start processing asynchronously
const completion = processMemoryBatch(batch)
const job: BatchJob = {
id: batchId,
provider: 'openai', // Simulating OpenAI
status: 'pending',
totalItems: items.length,
completedItems: 0,
failedItems: 0,
createdAt: batch.createdAt,
...(options.webhookUrl !== undefined && { webhookUrl: options.webhookUrl }),
}
return { job, completion }
},
async getStatus(batchId: string): Promise<BatchJob> {
const batch = batches.get(batchId)
if (!batch) {
throw new Error(`Batch not found: ${batchId}`)
}
const completedItems = batch.results.filter((r) => r.status === 'completed').length
const failedItems = batch.results.filter((r) => r.status === 'failed').length
return {
id: batch.id,
provider: 'openai',
status:
batch.status === 'completed'
? 'completed'
: batch.status === 'failed'
? 'failed'
: 'in_progress',
totalItems: batch.items.length,
completedItems,
failedItems,
createdAt: batch.createdAt,
...(batch.completedAt && { completedAt: batch.completedAt }),
}
},
async cancel(batchId: string): Promise<void> {
const batch = batches.get(batchId)
if (!batch) {
throw new Error(`Batch not found: ${batchId}`)
}
batch.status = 'failed'
},
async getResults(batchId: string): Promise<BatchResult[]> {
const batch = batches.get(batchId)
if (!batch) {
throw new Error(`Batch not found: ${batchId}`)
}
return batch.results
},
async waitForCompletion(batchId: string): Promise<BatchResult[]> {
const batch = batches.get(batchId)
if (!batch) {
throw new Error(`Batch not found: ${batchId}`)
}
// Wait for completion
while (batch.status !== 'completed' && batch.status !== 'failed') {
await new Promise((resolve) => setTimeout(resolve, 100))
}
return batch.results
},
}
// ============================================================================
// Processing
// ============================================================================
async function processMemoryBatch(batch: MemoryBatch): Promise<BatchResult[]> {
batch.status = 'in_progress'
// Optional delay
if (memoryOptions.delay) {
await new Promise((resolve) => setTimeout(resolve, memoryOptions.delay))
}
const results: BatchResult[] = []
for (const item of batch.items) {
try {
// Simulate failure
if (memoryOptions.failureRate && Math.random() < memoryOptions.failureRate) {
throw new Error('Simulated failure')
}
let result: unknown
if (memoryOptions.handler) {
// Use custom handler
result = await memoryOptions.handler(item)
} else if (item.schema) {
// Generate structured output
const response = await generateObject({
model: batch.options.model || 'sonnet',
schema: item.schema,
prompt: item.prompt,
...(item.options?.system !== undefined && { system: item.options.system }),
...(item.options?.temperature !== undefined && { temperature: item.options.temperature }),
...(item.options?.maxTokens !== undefined && { maxTokens: item.options.maxTokens }),
})
result = response.object
} else {
// Generate text
const response = await generateText({
model: batch.options.model || 'sonnet',
prompt: item.prompt,
...(item.options?.system !== undefined && { system: item.options.system }),
...(item.options?.temperature !== undefined && { temperature: item.options.temperature }),
...(item.options?.maxTokens !== undefined && { maxTokens: item.options.maxTokens }),
})
result = response.text
}
results.push({
id: item.id,
customId: item.id,
status: 'completed',
result,
usage: {
promptTokens: 100,
completionTokens: 200,
totalTokens: 300,
},
})
} catch (error) {
results.push({
id: item.id,
customId: item.id,
status: 'failed',
error: error instanceof Error ? error.message : 'Unknown error',
})
}
}
batch.results = results
batch.status = results.every((r) => r.status === 'completed') ? 'completed' : 'failed'
batch.completedAt = new Date()
return results
}
// ============================================================================
// Register Adapter
// ============================================================================
registerBatchAdapter('openai', memoryAdapter)
registerBatchAdapter('anthropic', memoryAdapter)
registerBatchAdapter('google', memoryAdapter)
registerBatchAdapter('bedrock', memoryAdapter)
registerBatchAdapter('cloudflare', memoryAdapter)
export { memoryAdapter }