ai-functions
Version:
Core AI primitives for building intelligent applications
495 lines (383 loc) • 13.5 kB
Markdown
# ai-functions

**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