UNPKG

ai-functions

Version:

Core AI primitives for building intelligent applications

495 lines (383 loc) 13.5 kB
# ai-functions ![Stability: Stable](https://img.shields.io/badge/stability-stable-green) **Calling AI models shouldn't require 50 lines of boilerplate.** You just want to get a response from Claude, GPT, or Gemini. Instead, you're drowning in SDK initialization, error handling, retry logic, JSON parsing, and type coercion. Every AI call becomes a small engineering project. ```typescript // What you're doing now import Anthropic from '@anthropic-ai/sdk' const client = new Anthropic() try { const response = await client.messages.create({ model: 'claude-sonnet-4-20250514', max_tokens: 1024, messages: [{ role: 'user', content: 'List 5 startup ideas' }], }) const text = response.content[0].type === 'text' ? response.content[0].text : '' const ideas = JSON.parse(text) // Pray it's valid JSON } catch (e) { if (e.status === 429) { /* rate limit logic */ } if (e.status === 500) { /* retry logic */ } // ... 30 more lines } ``` ```typescript // What you could be doing import { list } from 'ai-functions' const ideas = await list`5 startup ideas` ``` ## Installation ```bash npm install ai-functions ``` Set your API key: ```bash export ANTHROPIC_API_KEY=sk-... # or OPENAI_API_KEY ``` ## Quick Start ### Template Literals for Natural AI Calls ```typescript import { ai, list, is, write } from 'ai-functions' // Generate text const poem = await write`a haiku about TypeScript` // Generate lists const ideas = await list`10 startup ideas in healthcare` // Yes/no decisions const isValid = await is`"john@example" is a valid email` // Structured objects with auto-inferred schema const { title, summary, tags } = await ai`analyze this article: ${articleText}` ``` ### The `list` Primitive with `.map()` Process lists with automatic batching - one prompt generates items, then each item is processed in parallel: ```typescript const ideas = await list`5 startup ideas`.map(idea => ({ idea, viable: is`${idea} is technically feasible`, market: ai`estimate market size for ${idea}`, })) // Result: Array of { idea, viable, market } objects ``` ### Boolean Checks with `is` ```typescript // Simple validation const isColor = await is`"turquoise" is a color` // true // Content moderation const isSafe = await is`${userContent} is safe for work` // Quality checks const { conclusion } = await ai`write about ${topic}` const isWellArgumented = await is`${conclusion} is well-argued` ``` ### Task Execution with `do` ```typescript const { summary, actions } = await do`send welcome email to ${user.email}` // Returns: { summary: "...", actions: ["Created email", "Sent via SMTP", ...] } ``` ## Features ### Batch Processing (50% Cost Savings) Process large workloads at half the cost using provider batch APIs: ```typescript import { createBatch, write } from 'ai-functions' // Create a batch queue const batch = createBatch({ provider: 'openai' }) // Add items (deferred, not executed) const posts = titles.map(title => batch.add(`Write a blog post about ${title}`) ) // Submit for batch processing const { job } = await batch.submit() console.log(job.id) // batch_abc123 // Wait for results (up to 24hr turnaround) const results = await batch.wait() ``` Or use the `withBatch` helper: ```typescript import { withBatch } from 'ai-functions' const results = await withBatch(async (batch) => { return ['TypeScript', 'React', 'Next.js'].map(topic => batch.add(`Write tutorial about ${topic}`) ) }) ``` ### Retry & Circuit Breaker Built-in resilience for production workloads: ```typescript import { withRetry, RetryPolicy, CircuitBreaker, FallbackChain } from 'ai-functions' // Simple retry wrapper const reliableAI = withRetry(myAIFunction, { maxRetries: 3, baseDelay: 1000, jitter: 0.2, // Prevent thundering herd }) // Advanced retry policy const policy = new RetryPolicy({ maxRetries: 5, baseDelay: 1000, maxDelay: 30000, jitterStrategy: 'decorrelated', respectRetryAfter: true, // Honor rate limit headers }) await policy.execute(async () => { return await ai`generate content` }) // Circuit breaker for fail-fast const breaker = new CircuitBreaker({ failureThreshold: 5, resetTimeout: 30000, }) await breaker.execute(async () => { return await ai`generate content` }) // Model fallback chain const fallback = new FallbackChain([ { name: 'claude-sonnet', execute: () => generateWithClaude(prompt) }, { name: 'gpt-4o', execute: () => generateWithGPT(prompt) }, { name: 'gemini-pro', execute: () => generateWithGemini(prompt) }, ]) const result = await fallback.execute() ``` ### Caching Avoid redundant API calls with intelligent caching: ```typescript import { GenerationCache, EmbeddingCache, withCache, MemoryCache } from 'ai-functions' // Generation cache const cache = new GenerationCache({ defaultTTL: 3600000, // 1 hour maxSize: 1000, // LRU eviction }) // Check cache first const cached = await cache.get({ prompt, model: 'sonnet' }) if (!cached) { const result = await ai`${prompt}` await cache.set({ prompt, model: 'sonnet' }, result) } // Embedding cache with batch support const embedCache = new EmbeddingCache() const { hits, misses } = await embedCache.getMany(texts, { model: 'text-embedding-3-small' }) // Wrap any function with caching const cachedGenerate = withCache( new MemoryCache(), generateContent, { keyFn: (prompt) => prompt, ttl: 3600000 } ) ``` ### Budget Tracking Monitor and limit spending: ```typescript import { BudgetTracker, withBudget, BudgetExceededError } from 'ai-functions' // Create a budget tracker const tracker = new BudgetTracker({ maxTokens: 100000, maxCost: 10.00, // USD alertThresholds: [0.5, 0.8, 0.95], onAlert: (alert) => { console.log(`Budget ${alert.type} at ${alert.threshold * 100}%`) }, }) // Record usage tracker.recordUsage({ inputTokens: 1500, outputTokens: 500, model: 'claude-sonnet-4-20250514', }) // Check remaining budget console.log(tracker.getRemainingBudget()) // { tokens: 98000, cost: 9.95 } // Use with automatic tracking const result = await withBudget({ maxCost: 5.00 }, async (tracker) => { // All AI calls within this scope are tracked return await ai`generate content` }) ``` ### Tool Orchestration Build agentic loops with tool calling: ```typescript import { AgenticLoop, createTool, createToolset } from 'ai-functions' // Define tools const searchTool = createTool({ name: 'search', description: 'Search the web', parameters: { query: 'Search query' }, handler: async ({ query }) => await searchWeb(query), }) const calculatorTool = createTool({ name: 'calculate', description: 'Perform calculations', parameters: { expression: 'Math expression' }, handler: async ({ expression }) => eval(expression), }) // Create an agentic loop const loop = new AgenticLoop({ model: 'claude-sonnet-4-20250514', tools: [searchTool, calculatorTool], maxIterations: 10, }) const result = await loop.run('What is the population of Tokyo times 2?') ``` ## Configuration ### Global Configuration ```typescript import { configure } from 'ai-functions' configure({ model: 'claude-sonnet-4-20250514', provider: 'anthropic', batchMode: 'auto', // 'auto' | 'immediate' | 'flex' | 'deferred' flexThreshold: 5, // Use flex for 5+ items batchThreshold: 500, // Use batch API for 500+ items }) ``` ### Scoped Configuration ```typescript import { withContext } from 'ai-functions' const results = await withContext( { provider: 'openai', model: 'gpt-4o', batchMode: 'deferred' }, async () => { const titles = await list`10 blog titles` return titles.map(title => write`blog post: ${title}`) } ) ``` ### Environment Variables ```bash AI_MODEL=claude-sonnet-4-20250514 AI_PROVIDER=anthropic AI_BATCH_MODE=auto AI_FLEX_THRESHOLD=5 AI_BATCH_THRESHOLD=500 AI_BATCH_WEBHOOK_URL=https://api.example.com/webhook ``` ## Schema-Based Functions Create typed AI functions with simple schemas: ```typescript import { AI } from 'ai-functions' const ai = AI({ recipe: { name: 'Recipe name', servings: 'Number of servings (number)', ingredients: ['List of ingredients'], steps: ['Cooking steps'], prepTime: 'Prep time in minutes (number)', }, storyBrand: { hero: 'Who is the customer?', problem: { internal: 'Internal struggle', external: 'External challenge', philosophical: 'Why is this wrong?', }, guide: 'Who helps them?', plan: ['Steps to success'], callToAction: 'What should they do?', success: 'What success looks like', failure: 'What failure looks like', }, }) // Fully typed results const recipe = await ai.recipe('Italian pasta for 4 people') // { name: string, servings: number, ingredients: string[], ... } const brand = await ai.storyBrand('A developer tools startup') // { hero: string, problem: { internal, external, philosophical }, ... } ``` ## Define Custom Functions ```typescript import { define, defineFunction } from 'ai-functions' // Auto-define from name and example args const planTrip = await define('planTrip', { destination: 'Tokyo', travelers: 2 }) // Or define explicitly with full control const summarize = defineFunction({ type: 'generative', name: 'summarize', args: { text: 'Text to summarize', maxLength: 'Max words (number)' }, output: 'string', promptTemplate: 'Summarize in {{maxLength}} words: {{text}}', }) // Use the defined functions const trip = await planTrip.call({ destination: 'Paris', travelers: 4 }) const summary = await summarize.call({ text: longArticle, maxLength: 100 }) ``` ### The four Function kinds `defineFunction` accepts a discriminated union (`FunctionDefinition`). Each `type` has a distinct execution contract: | `type` | Meaning | At call time | |--------|---------|--------------| | `code` | **Deterministic handler** — a fetch/transform/rule | Runs your `handler` (or inline `code` body). **No model.** | | `generative` | One-shot generation | `generateObject` / `generateText` | | `agentic` | Plan-execute-observe tool loop | Iterative model + tool calls | | `human` | Human/approval step | Generates channel UI, returns a pending result | ```typescript // Code = deterministic. Supply a handler (or an inline `code` string). const calculateTax = defineFunction({ type: 'code', name: 'calculateTax', args: { amount: 'Amount (number)', rate: 'Rate (number)' }, handler: ({ amount, rate }) => amount * rate, // runs every call, no LLM }) await calculateTax.call({ amount: 100, rate: 0.2 }) // => 20 ``` > **Migration (breaking, as of this release):** previously `type: 'code'` LLM-**generated** code at call time. `Code` is now **deterministic** — it requires a `handler` or inline `code` body and never calls a model. A `code` function with neither now throws. > > If you relied on the old behavior (have a model *author* code), use the new explicit `generateCode()` export, or a `generative` function whose output is source text: > > ```typescript > import { generateCode } from 'ai-functions' > const src = await generateCode( > { name: 'fib', args: { n: '(number)' }, language: 'typescript' }, > { n: 10 } > ) // => string of generated source > ``` > > The `generate('code', prompt)` primitive is unchanged — it is a string-prompt code-authoring helper, distinct from the `FunctionDefinition` union. ## API Reference ### Core Primitives | Function | Description | Returns | |----------|-------------|---------| | `ai` | General-purpose generation with dynamic schema | `Promise<T>` | | `write` | Generate text content | `Promise<string>` | | `list` | Generate a list of items | `Promise<string[]>` | | `lists` | Generate multiple named lists | `Promise<Record<string, string[]>>` | | `is` | Boolean yes/no checks | `Promise<boolean>` | | `do` | Execute a task | `Promise<{ summary, actions }>` | | `extract` | Extract structured data | `Promise<T[]>` | | `summarize` | Summarize content | `Promise<string>` | | `code` | Generate code | `Promise<string>` | | `decide` | Choose between options | `(options) => Promise<T>` | ### Batch Processing | Export | Description | |--------|-------------| | `createBatch()` | Create a batch queue for deferred execution | | `withBatch()` | Execute operations in batch mode | | `BatchQueue` | Class for managing batch jobs | ### Resilience | Export | Description | |--------|-------------| | `withRetry()` | Wrap function with retry logic | | `RetryPolicy` | Configurable retry policy | | `CircuitBreaker` | Fail-fast circuit breaker | | `FallbackChain` | Model failover chain | ### Caching | Export | Description | |--------|-------------| | `MemoryCache` | In-memory LRU cache | | `GenerationCache` | Cache for generation results | | `EmbeddingCache` | Cache for embeddings | | `withCache()` | Wrap function with caching | ### Budget & Tracking | Export | Description | |--------|-------------| | `BudgetTracker` | Track token usage and costs | | `withBudget()` | Execute with budget limits | | `RequestContext` | Request tracing and isolation | ### Configuration | Export | Description | |--------|-------------| | `configure()` | Set global defaults | | `withContext()` | Scoped configuration | | `getContext()` | Get current context | ## Related Packages - [`ai-core`](../ai-core) - Lightweight core primitives (no batch/budget/retry) - [`ai-providers`](../ai-providers) - Provider integrations - [`language-models`](../language-models) - Model aliases and configuration ## License MIT