UNPKG

ai-functions

Version:

Core AI primitives for building intelligent applications

484 lines (404 loc) 12.8 kB
/** * Edge Function Deployment Example (Cloudflare Workers) * * This example demonstrates deploying ai-functions on Cloudflare Workers. * It shows how to: * - Configure for edge runtime * - Handle request/response patterns * - Use Workers AI or external providers * - Implement rate limiting and caching * * Note: This file demonstrates the patterns. To actually deploy, you would: * 1. npm create cloudflare@latest * 2. Add ai-functions as a dependency * 3. Use this code in src/index.ts * * @example * ```bash * # Local development * npx wrangler dev * * # Deploy * npx wrangler deploy * ``` */ import { ai, write, list, is, configure, MemoryCache, withRetry, GenerationCache, } from '../src/index.js' // ============================================================================ // Types // ============================================================================ interface Env { AI_GATEWAY_URL?: string ANTHROPIC_API_KEY?: string OPENAI_API_KEY?: string KV_CACHE?: unknown // KVNamespace in actual Workers RATE_LIMIT_KV?: unknown } interface RequestBody { action: 'generate' | 'classify' | 'extract' | 'summarize' input: string options?: Record<string, unknown> } interface APIResponse<T = unknown> { success: boolean data?: T error?: string meta?: { requestId: string latencyMs: number cached: boolean } } // ============================================================================ // Worker Handler // ============================================================================ /** * Main worker handler (Cloudflare Workers format) * * In actual wrangler.toml: * ```toml * name = "ai-functions-worker" * main = "src/index.ts" * compatibility_date = "2024-01-01" * * [vars] * AI_GATEWAY_URL = "https://your-gateway.com" * * [[kv_namespaces]] * binding = "KV_CACHE" * id = "your-kv-namespace-id" * ``` */ async function handleRequest(request: Request, env: Env): Promise<Response> { const startTime = Date.now() const requestId = crypto.randomUUID() // Configure ai-functions configure({ model: 'sonnet', provider: 'anthropic', // In production, use env variables }) // CORS headers for API const corsHeaders = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type', } // Handle OPTIONS for CORS if (request.method === 'OPTIONS') { return new Response(null, { headers: corsHeaders }) } try { // Parse request const body = (await request.json()) as RequestBody // Process based on action let result: unknown let cached = false switch (body.action) { case 'generate': result = await handleGenerate(body.input, body.options) break case 'classify': result = await handleClassify(body.input, body.options) break case 'extract': result = await handleExtract(body.input, body.options) break case 'summarize': result = await handleSummarize(body.input, body.options) break default: throw new Error(`Unknown action: ${body.action}`) } const response: APIResponse = { success: true, data: result, meta: { requestId, latencyMs: Date.now() - startTime, cached, }, } return new Response(JSON.stringify(response), { headers: { 'Content-Type': 'application/json', ...corsHeaders, }, }) } catch (error) { const response: APIResponse = { success: false, error: (error as Error).message, meta: { requestId, latencyMs: Date.now() - startTime, cached: false, }, } return new Response(JSON.stringify(response), { status: 500, headers: { 'Content-Type': 'application/json', ...corsHeaders, }, }) } } // ============================================================================ // Action Handlers // ============================================================================ async function handleGenerate(input: string, options?: Record<string, unknown>): Promise<unknown> { const { type = 'text', schema } = options || {} if (type === 'text') { return write`${input}` } if (schema) { return ai`${input}` } return ai`${input}` } async function handleClassify(input: string, options?: Record<string, unknown>): Promise<unknown> { const { categories = ['positive', 'negative', 'neutral'] } = options || {} const { category, confidence, reasoning } = await ai`Classify this text into one of these categories: ${(categories as string[]).join(', ')} Text: "${input}" Provide: - category: the best matching category - confidence: confidence score 0-1 - reasoning: brief explanation` return { category, confidence, reasoning } } async function handleExtract(input: string, options?: Record<string, unknown>): Promise<unknown> { const { fields } = options || {} if (fields && Array.isArray(fields)) { const fieldDescriptions = (fields as string[]).map((f) => `- ${f}`).join('\n') return ai`Extract the following fields from this text: ${fieldDescriptions} Text: "${input}"` } // Default entity extraction const { entities, dates, amounts, names } = await ai`Extract key information from this text: "${input}" Provide: - entities: array of key entities mentioned - dates: array of dates found - amounts: array of monetary amounts or quantities - names: array of person/company names` return { entities, dates, amounts, names } } async function handleSummarize(input: string, options?: Record<string, unknown>): Promise<unknown> { const { maxLength = 100, style = 'concise' } = options || {} const summary = await write`Summarize this text in a ${style} style, maximum ${maxLength} words: "${input}"` return { summary, wordCount: summary.split(/\s+/).length } } // ============================================================================ // Middleware Utilities // ============================================================================ /** * Simple in-memory rate limiter (use KV in production) */ class RateLimiter { private requests = new Map<string, number[]>() private maxRequests: number private windowMs: number constructor(maxRequests = 100, windowMs = 60000) { this.maxRequests = maxRequests this.windowMs = windowMs } check(key: string): boolean { const now = Date.now() const windowStart = now - this.windowMs let timestamps = this.requests.get(key) || [] timestamps = timestamps.filter((t) => t > windowStart) if (timestamps.length >= this.maxRequests) { return false } timestamps.push(now) this.requests.set(key, timestamps) return true } } /** * Request caching utility */ class RequestCache { private cache: GenerationCache constructor() { this.cache = new GenerationCache({ defaultTTL: 3600000, // 1 hour maxSize: 1000, }) } async get(key: string): Promise<unknown | null> { return this.cache.get({ prompt: key, model: 'cache' }) } async set(key: string, value: unknown): Promise<void> { await this.cache.set({ prompt: key, model: 'cache' }, value) } } // ============================================================================ // Streaming Response Handler // ============================================================================ async function handleStreamingRequest(request: Request, env: Env): Promise<Response> { const { input } = (await request.json()) as { input: string } // Configure for streaming configure({ model: 'sonnet', provider: 'anthropic', }) const response = write`${input}` const stream = response.stream() // Create a TransformStream for SSE const { readable, writable } = new TransformStream() const writer = writable.getWriter() const encoder = new TextEncoder() // Stream in background ;(async () => { try { for await (const chunk of stream.textStream) { await writer.write(encoder.encode(`data: ${JSON.stringify({ text: chunk })}\n\n`)) } await writer.write(encoder.encode('data: [DONE]\n\n')) } finally { await writer.close() } })() return new Response(readable, { headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', Connection: 'keep-alive', }, }) } // ============================================================================ // Simulated Worker Execution // ============================================================================ async function simulateWorkerExecution(): Promise<void> { console.log('\n=== Simulating Cloudflare Worker Execution ===\n') const rateLimiter = new RateLimiter(10, 10000) // 10 requests per 10 seconds const cache = new RequestCache() // Simulate various API requests const testRequests = [ { action: 'generate' as const, input: 'Write a tagline for a coffee shop', }, { action: 'classify' as const, input: 'I absolutely love this product! Best purchase ever!', options: { categories: ['positive', 'negative', 'neutral'] }, }, { action: 'extract' as const, input: 'Contact John Smith at john@example.com or call 555-1234', options: { fields: ['name', 'email', 'phone'] }, }, { action: 'summarize' as const, input: 'The quick brown fox jumps over the lazy dog. This sentence contains every letter of the alphabet and is commonly used for testing purposes.', options: { maxLength: 20, style: 'brief' }, }, ] for (const body of testRequests) { console.log(`\n--- ${body.action.toUpperCase()} Request ---`) console.log(`Input: "${body.input.substring(0, 50)}..."`) // Check rate limit const clientIP = '127.0.0.1' if (!rateLimiter.check(clientIP)) { console.log('Rate limited!') continue } // Create mock request const request = new Request('https://worker.example.com/api', { method: 'POST', body: JSON.stringify(body), headers: { 'Content-Type': 'application/json' }, }) // Handle request const startTime = Date.now() const response = await handleRequest(request, {} as Env) const result = (await response.json()) as APIResponse console.log(`Response: ${response.status}`) console.log(`Latency: ${Date.now() - startTime}ms`) console.log(`Result:`, JSON.stringify(result.data, null, 2).substring(0, 200)) } } // ============================================================================ // Export Worker // ============================================================================ /** * In actual Cloudflare Worker (src/index.ts): * * ```ts * export default { * async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> { * // Check for streaming request * if (request.url.includes('/stream')) { * return handleStreamingRequest(request, env) * } * return handleRequest(request, env) * }, * } * ``` */ // ============================================================================ // Main Example // ============================================================================ async function main() { console.log('\n=== Cloudflare Worker Example ===\n') // Configure the AI provider (for local simulation) configure({ model: 'sonnet', provider: 'anthropic', }) // Show worker code structure console.log('Worker Code Structure:') console.log(` // wrangler.toml name = "ai-functions-api" main = "src/index.ts" compatibility_date = "2024-01-01" [vars] AI_MODEL = "sonnet" // src/index.ts import { ai, write, configure } from 'ai-functions' export default { async fetch(request, env, ctx) { configure({ model: env.AI_MODEL }) const { prompt } = await request.json() const response = await write\`\${prompt}\` return new Response(JSON.stringify({ response }), { headers: { 'Content-Type': 'application/json' } }) } } `) // Run simulation await simulateWorkerExecution() console.log('\n--- Deployment Instructions ---') console.log('1. npm create cloudflare@latest my-ai-worker') console.log('2. cd my-ai-worker && npm install ai-functions') console.log('3. Copy the handler code to src/index.ts') console.log('4. npx wrangler dev (for local testing)') console.log('5. npx wrangler deploy (for production)') } main() .then(() => { console.log('\n=== Example Complete ===\n') process.exit(0) }) .catch((error) => { console.error('\nError:', error.message) process.exit(1) })