ai-functions 2.1.1 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -4
- package/CHANGELOG.md +68 -1
- package/README.md +397 -157
- package/dist/ai-promise.d.ts +50 -3
- package/dist/ai-promise.d.ts.map +1 -1
- package/dist/ai-promise.js +410 -51
- package/dist/ai-promise.js.map +1 -1
- package/dist/ai-schemas.d.ts +56 -0
- package/dist/ai-schemas.d.ts.map +1 -0
- package/dist/ai-schemas.js +53 -0
- package/dist/ai-schemas.js.map +1 -0
- package/dist/ai.d.ts +16 -242
- package/dist/ai.d.ts.map +1 -1
- package/dist/ai.js +54 -837
- package/dist/ai.js.map +1 -1
- package/dist/batch/anthropic.d.ts +6 -4
- package/dist/batch/anthropic.d.ts.map +1 -1
- package/dist/batch/anthropic.js +83 -145
- package/dist/batch/anthropic.js.map +1 -1
- package/dist/batch/bedrock.d.ts +8 -30
- package/dist/batch/bedrock.d.ts.map +1 -1
- package/dist/batch/bedrock.js +155 -338
- package/dist/batch/bedrock.js.map +1 -1
- package/dist/batch/cloudflare.d.ts +8 -20
- package/dist/batch/cloudflare.d.ts.map +1 -1
- package/dist/batch/cloudflare.js +68 -189
- package/dist/batch/cloudflare.js.map +1 -1
- package/dist/batch/google.d.ts +6 -20
- package/dist/batch/google.d.ts.map +1 -1
- package/dist/batch/google.js +70 -238
- package/dist/batch/google.js.map +1 -1
- package/dist/batch/index.d.ts +4 -1
- package/dist/batch/index.d.ts.map +1 -1
- package/dist/batch/index.js +4 -1
- package/dist/batch/index.js.map +1 -1
- package/dist/batch/memory.d.ts +1 -1
- package/dist/batch/memory.d.ts.map +1 -1
- package/dist/batch/memory.js +14 -10
- package/dist/batch/memory.js.map +1 -1
- package/dist/batch/openai.d.ts +11 -14
- package/dist/batch/openai.d.ts.map +1 -1
- package/dist/batch/openai.js +52 -156
- package/dist/batch/openai.js.map +1 -1
- package/dist/batch/provider.d.ts +111 -0
- package/dist/batch/provider.d.ts.map +1 -0
- package/dist/batch/provider.js +233 -0
- package/dist/batch/provider.js.map +1 -0
- package/dist/batch-map.d.ts.map +1 -1
- package/dist/batch-map.js +23 -17
- package/dist/batch-map.js.map +1 -1
- package/dist/batch-queue.d.ts +65 -0
- package/dist/batch-queue.d.ts.map +1 -1
- package/dist/batch-queue.js +169 -14
- package/dist/batch-queue.js.map +1 -1
- package/dist/budget.d.ts +272 -0
- package/dist/budget.d.ts.map +1 -0
- package/dist/budget.js +513 -0
- package/dist/budget.js.map +1 -0
- package/dist/cache.d.ts +295 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +433 -0
- package/dist/cache.js.map +1 -0
- package/dist/context.d.ts +42 -8
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +64 -62
- package/dist/context.js.map +1 -1
- package/dist/digital-objects-registry.d.ts +229 -0
- package/dist/digital-objects-registry.d.ts.map +1 -0
- package/dist/digital-objects-registry.js +617 -0
- package/dist/digital-objects-registry.js.map +1 -0
- package/dist/embeddings.d.ts +2 -2
- package/dist/embeddings.d.ts.map +1 -1
- package/dist/errors.d.ts +22 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +35 -0
- package/dist/errors.js.map +1 -0
- package/dist/eval/runner.d.ts +10 -1
- package/dist/eval/runner.d.ts.map +1 -1
- package/dist/eval/runner.js +41 -35
- package/dist/eval/runner.js.map +1 -1
- package/dist/eval-log/in-memory.d.ts +34 -0
- package/dist/eval-log/in-memory.d.ts.map +1 -0
- package/dist/eval-log/in-memory.js +84 -0
- package/dist/eval-log/in-memory.js.map +1 -0
- package/dist/eval-log/index.d.ts +29 -0
- package/dist/eval-log/index.d.ts.map +1 -0
- package/dist/eval-log/index.js +39 -0
- package/dist/eval-log/index.js.map +1 -0
- package/dist/eval-log/types.d.ts +101 -0
- package/dist/eval-log/types.d.ts.map +1 -0
- package/dist/eval-log/types.js +16 -0
- package/dist/eval-log/types.js.map +1 -0
- package/dist/function-registry.d.ts +116 -0
- package/dist/function-registry.d.ts.map +1 -0
- package/dist/function-registry.js +546 -0
- package/dist/function-registry.js.map +1 -0
- package/dist/generate.d.ts +9 -3
- package/dist/generate.d.ts.map +1 -1
- package/dist/generate.js +18 -22
- package/dist/generate.js.map +1 -1
- package/dist/index.d.ts +35 -20
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +89 -42
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +118 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +187 -0
- package/dist/logger.js.map +1 -0
- package/dist/middleware/budget.d.ts +84 -0
- package/dist/middleware/budget.d.ts.map +1 -0
- package/dist/middleware/budget.js +110 -0
- package/dist/middleware/budget.js.map +1 -0
- package/dist/middleware/cache.d.ts +103 -0
- package/dist/middleware/cache.d.ts.map +1 -0
- package/dist/middleware/cache.js +228 -0
- package/dist/middleware/cache.js.map +1 -0
- package/dist/middleware/embed-cache.d.ts +99 -0
- package/dist/middleware/embed-cache.d.ts.map +1 -0
- package/dist/middleware/embed-cache.js +128 -0
- package/dist/middleware/embed-cache.js.map +1 -0
- package/dist/middleware/index.d.ts +11 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +11 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/trace.d.ts +103 -0
- package/dist/middleware/trace.d.ts.map +1 -0
- package/dist/middleware/trace.js +176 -0
- package/dist/middleware/trace.js.map +1 -0
- package/dist/primitives.d.ts +120 -1
- package/dist/primitives.d.ts.map +1 -1
- package/dist/primitives.js +398 -26
- package/dist/primitives.js.map +1 -1
- package/dist/retry.d.ts +368 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +646 -0
- package/dist/retry.js.map +1 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +2 -10
- package/dist/schema.js.map +1 -1
- package/dist/telemetry.d.ts +128 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +285 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/template.d.ts.map +1 -1
- package/dist/template.js +6 -1
- package/dist/template.js.map +1 -1
- package/dist/tool-orchestration.d.ts +453 -0
- package/dist/tool-orchestration.d.ts.map +1 -0
- package/dist/tool-orchestration.js +763 -0
- package/dist/tool-orchestration.js.map +1 -0
- package/dist/type-guards.d.ts +28 -0
- package/dist/type-guards.d.ts.map +1 -0
- package/dist/type-guards.js +29 -0
- package/dist/type-guards.js.map +1 -0
- package/dist/types.d.ts +135 -17
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +36 -1
- package/dist/types.js.map +1 -1
- package/dist/wrap-for-v3.d.ts +80 -0
- package/dist/wrap-for-v3.d.ts.map +1 -0
- package/dist/wrap-for-v3.js +89 -0
- package/dist/wrap-for-v3.js.map +1 -0
- package/examples/00-quickstart.ts +232 -0
- package/examples/01-rag-chatbot.ts +212 -0
- package/examples/02-multi-agent-research.ts +290 -0
- package/examples/03-email-classification.ts +379 -0
- package/examples/04-content-moderation.ts +400 -0
- package/examples/05-document-extraction.ts +455 -0
- package/examples/06-streaming-chat-nextjs.ts +437 -0
- package/examples/07-cloudflare-worker.ts +483 -0
- package/examples/08-batch-processing.ts +491 -0
- package/examples/09-budget-constrained.ts +527 -0
- package/examples/10-tool-orchestration.ts +565 -0
- package/examples/11-retry-resilience.ts +403 -0
- package/examples/12-caching-strategies.ts +422 -0
- package/examples/README.md +145 -0
- package/package.json +10 -6
- package/src/ai-promise.ts +528 -99
- package/src/ai-schemas.ts +122 -0
- package/src/ai.ts +69 -1153
- package/src/batch/anthropic.ts +96 -161
- package/src/batch/bedrock.ts +203 -454
- package/src/batch/cloudflare.ts +99 -282
- package/src/batch/google.ts +91 -297
- package/src/batch/index.ts +4 -1
- package/src/batch/memory.ts +15 -10
- package/src/batch/openai.ts +65 -193
- package/src/batch/provider.ts +336 -0
- package/src/batch-map.ts +29 -24
- package/src/batch-queue.ts +200 -11
- package/src/budget.ts +740 -0
- package/src/cache.ts +681 -0
- package/src/context.ts +122 -76
- package/src/digital-objects-registry.ts +750 -0
- package/src/errors.ts +37 -0
- package/src/eval/runner.ts +63 -38
- package/src/eval-log/in-memory.ts +90 -0
- package/src/eval-log/index.ts +46 -0
- package/src/eval-log/types.ts +110 -0
- package/src/function-registry.ts +671 -0
- package/src/generate.ts +33 -33
- package/src/index.ts +325 -49
- package/src/logger.ts +232 -0
- package/src/middleware/budget.ts +171 -0
- package/src/middleware/cache.ts +299 -0
- package/src/middleware/embed-cache.ts +195 -0
- package/src/middleware/index.ts +23 -0
- package/src/middleware/trace.ts +248 -0
- package/src/primitives.ts +589 -62
- package/src/retry.ts +902 -0
- package/src/schema.ts +8 -17
- package/src/telemetry.ts +403 -0
- package/src/template.ts +8 -4
- package/src/tool-orchestration.ts +1173 -0
- package/src/type-guards.ts +31 -0
- package/src/types.ts +164 -25
- package/src/wrap-for-v3.ts +105 -0
- package/test/ai-promise.test.ts +1080 -0
- package/test/ai-proxy.test.ts +1 -1
- package/test/backward-compat.test.ts +147 -0
- package/test/batch-autosubmit-errors.test.ts +610 -0
- package/test/batch-blog-posts.test.ts +87 -129
- package/test/budget-tracking.test.ts +800 -0
- package/test/cache.test.ts +712 -0
- package/test/context-isolation.test.ts +687 -0
- package/test/core-functions.test.ts +183 -579
- package/test/decide.test.ts +154 -322
- package/test/define.test.ts +211 -8
- package/test/digital-objects-registry.test.ts +760 -0
- package/test/embedding-cache-middleware.test.ts +140 -0
- package/test/evals/deterministic.eval.test.ts +376 -0
- package/test/generate-core.test.ts +140 -229
- package/test/implicit-batch.test.ts +22 -65
- package/test/json-parse-error-handling.test.ts +463 -0
- package/test/retry-policy-integration.test.ts +117 -0
- package/test/retry.test.ts +1016 -0
- package/test/schema.test.ts +55 -19
- package/test/streaming.test.ts +316 -0
- package/test/template.test.ts +1164 -0
- package/test/tool-orchestration.test.ts +1040 -0
- package/test/wrap-for-v3.test.ts +612 -0
- package/vitest.config.js +6 -0
- package/vitest.config.ts +20 -0
- package/dist/rpc/auth.d.ts +0 -69
- package/dist/rpc/auth.d.ts.map +0 -1
- package/dist/rpc/auth.js +0 -136
- package/dist/rpc/auth.js.map +0 -1
- package/dist/rpc/client.d.ts +0 -62
- package/dist/rpc/client.d.ts.map +0 -1
- package/dist/rpc/client.js +0 -103
- package/dist/rpc/client.js.map +0 -1
- package/dist/rpc/deferred.d.ts +0 -60
- package/dist/rpc/deferred.d.ts.map +0 -1
- package/dist/rpc/deferred.js +0 -96
- package/dist/rpc/deferred.js.map +0 -1
- package/dist/rpc/index.d.ts +0 -22
- package/dist/rpc/index.d.ts.map +0 -1
- package/dist/rpc/index.js +0 -38
- package/dist/rpc/index.js.map +0 -1
- package/dist/rpc/local.d.ts +0 -42
- package/dist/rpc/local.d.ts.map +0 -1
- package/dist/rpc/local.js +0 -50
- package/dist/rpc/local.js.map +0 -1
- package/dist/rpc/server.d.ts +0 -165
- package/dist/rpc/server.d.ts.map +0 -1
- package/dist/rpc/server.js +0 -405
- package/dist/rpc/server.js.map +0 -1
- package/dist/rpc/session.d.ts +0 -32
- package/dist/rpc/session.d.ts.map +0 -1
- package/dist/rpc/session.js +0 -43
- package/dist/rpc/session.js.map +0 -1
- package/dist/rpc/transport.d.ts +0 -306
- package/dist/rpc/transport.d.ts.map +0 -1
- package/dist/rpc/transport.js +0 -731
- package/dist/rpc/transport.js.map +0 -1
- package/src/batch/anthropic.js +0 -256
- package/src/batch/bedrock.js +0 -584
- package/src/batch/cloudflare.js +0 -287
- package/src/batch/google.js +0 -359
- package/src/batch/index.js +0 -30
- package/src/batch/memory.js +0 -187
- package/src/batch/openai.js +0 -402
- package/src/eval/index.js +0 -7
- package/src/eval/models.js +0 -119
- package/src/eval/runner.js +0 -147
- package/test/schema.test.js +0 -96
package/README.md
CHANGED
|
@@ -1,254 +1,494 @@
|
|
|
1
1
|
# ai-functions
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
**Calling AI models shouldn't require 50 lines of boilerplate.**
|
|
6
|
+
|
|
7
|
+
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.
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
// What you're doing now
|
|
11
|
+
import Anthropic from '@anthropic-ai/sdk'
|
|
12
|
+
const client = new Anthropic()
|
|
13
|
+
try {
|
|
14
|
+
const response = await client.messages.create({
|
|
15
|
+
model: 'claude-sonnet-4-20250514',
|
|
16
|
+
max_tokens: 1024,
|
|
17
|
+
messages: [{ role: 'user', content: 'List 5 startup ideas' }],
|
|
18
|
+
})
|
|
19
|
+
const text = response.content[0].type === 'text'
|
|
20
|
+
? response.content[0].text
|
|
21
|
+
: ''
|
|
22
|
+
const ideas = JSON.parse(text) // Pray it's valid JSON
|
|
23
|
+
} catch (e) {
|
|
24
|
+
if (e.status === 429) { /* rate limit logic */ }
|
|
25
|
+
if (e.status === 500) { /* retry logic */ }
|
|
26
|
+
// ... 30 more lines
|
|
27
|
+
}
|
|
28
|
+
```
|
|
4
29
|
|
|
5
30
|
```typescript
|
|
6
|
-
|
|
31
|
+
// What you could be doing
|
|
32
|
+
import { list } from 'ai-functions'
|
|
7
33
|
|
|
8
|
-
|
|
9
|
-
const qualified = is`${lead} a good fit for our enterprise plan?`
|
|
10
|
-
const ideas = list`blog posts that would resonate with ${persona}`
|
|
11
|
-
const { summary, nextSteps } = ai`analyze this sales call: ${transcript}`
|
|
34
|
+
const ideas = await list`5 startup ideas`
|
|
12
35
|
```
|
|
13
36
|
|
|
14
37
|
## Installation
|
|
15
38
|
|
|
16
39
|
```bash
|
|
17
|
-
|
|
40
|
+
npm install ai-functions
|
|
18
41
|
```
|
|
19
42
|
|
|
20
|
-
|
|
43
|
+
Set your API key:
|
|
21
44
|
|
|
22
|
-
|
|
45
|
+
```bash
|
|
46
|
+
export ANTHROPIC_API_KEY=sk-... # or OPENAI_API_KEY
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Quick Start
|
|
50
|
+
|
|
51
|
+
### Template Literals for Natural AI Calls
|
|
23
52
|
|
|
24
53
|
```typescript
|
|
25
|
-
|
|
26
|
-
const { qualified, score, reason } = ai`qualify this lead: ${lead}`
|
|
54
|
+
import { ai, list, is, write } from 'ai-functions'
|
|
27
55
|
|
|
28
|
-
//
|
|
29
|
-
const
|
|
30
|
-
const subject = ai`subject line for: ${followUp}`
|
|
56
|
+
// Generate text
|
|
57
|
+
const poem = await write`a haiku about TypeScript`
|
|
31
58
|
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
59
|
+
// Generate lists
|
|
60
|
+
const ideas = await list`10 startup ideas in healthcare`
|
|
61
|
+
|
|
62
|
+
// Yes/no decisions
|
|
63
|
+
const isValid = await is`"john@example" is a valid email`
|
|
64
|
+
|
|
65
|
+
// Structured objects with auto-inferred schema
|
|
66
|
+
const { title, summary, tags } = await ai`analyze this article: ${articleText}`
|
|
36
67
|
```
|
|
37
68
|
|
|
38
|
-
|
|
69
|
+
### The `list` Primitive with `.map()`
|
|
39
70
|
|
|
40
|
-
|
|
71
|
+
Process lists with automatic batching - one prompt generates items, then each item is processed in parallel:
|
|
41
72
|
|
|
42
73
|
```typescript
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
`
|
|
74
|
+
const ideas = await list`5 startup ideas`.map(idea => ({
|
|
75
|
+
idea,
|
|
76
|
+
viable: is`${idea} is technically feasible`,
|
|
77
|
+
market: ai`estimate market size for ${idea}`,
|
|
78
|
+
}))
|
|
47
79
|
|
|
48
|
-
|
|
49
|
-
await assignToSales(lead)
|
|
50
|
-
}
|
|
80
|
+
// Result: Array of { idea, viable, market } objects
|
|
51
81
|
```
|
|
52
82
|
|
|
53
|
-
###
|
|
83
|
+
### Boolean Checks with `is`
|
|
54
84
|
|
|
55
85
|
```typescript
|
|
56
|
-
//
|
|
57
|
-
const
|
|
86
|
+
// Simple validation
|
|
87
|
+
const isColor = await is`"turquoise" is a color` // true
|
|
58
88
|
|
|
59
|
-
//
|
|
60
|
-
const
|
|
61
|
-
topic,
|
|
62
|
-
potential: is`${topic} would drive signups?`,
|
|
63
|
-
difficulty: ai`content difficulty for: ${topic}`,
|
|
64
|
-
}))
|
|
89
|
+
// Content moderation
|
|
90
|
+
const isSafe = await is`${userContent} is safe for work`
|
|
65
91
|
|
|
66
|
-
//
|
|
67
|
-
const
|
|
92
|
+
// Quality checks
|
|
93
|
+
const { conclusion } = await ai`write about ${topic}`
|
|
94
|
+
const isWellArgumented = await is`${conclusion} is well-argued`
|
|
68
95
|
```
|
|
69
96
|
|
|
70
|
-
###
|
|
97
|
+
### Task Execution with `do`
|
|
71
98
|
|
|
72
99
|
```typescript
|
|
73
|
-
const {
|
|
74
|
-
|
|
75
|
-
`
|
|
76
|
-
|
|
77
|
-
const battleCard = ai`
|
|
78
|
-
sales battlecard addressing: ${objections}
|
|
79
|
-
highlighting: ${pros}
|
|
80
|
-
`
|
|
100
|
+
const { summary, actions } = await do`send welcome email to ${user.email}`
|
|
101
|
+
// Returns: { summary: "...", actions: ["Created email", "Sent via SMTP", ...] }
|
|
81
102
|
```
|
|
82
103
|
|
|
83
|
-
|
|
104
|
+
## Features
|
|
105
|
+
|
|
106
|
+
### Batch Processing (50% Cost Savings)
|
|
107
|
+
|
|
108
|
+
Process large workloads at half the cost using provider batch APIs:
|
|
84
109
|
|
|
85
110
|
```typescript
|
|
86
|
-
|
|
87
|
-
const { healthy, churnRisk, opportunities } = ai`
|
|
88
|
-
analyze customer health for ${customer}
|
|
89
|
-
based on: ${usageData}
|
|
90
|
-
`
|
|
111
|
+
import { createBatch, write } from 'ai-functions'
|
|
91
112
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
113
|
+
// Create a batch queue
|
|
114
|
+
const batch = createBatch({ provider: 'openai' })
|
|
115
|
+
|
|
116
|
+
// Add items (deferred, not executed)
|
|
117
|
+
const posts = titles.map(title =>
|
|
118
|
+
batch.add(`Write a blog post about ${title}`)
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
// Submit for batch processing
|
|
122
|
+
const { job } = await batch.submit()
|
|
123
|
+
console.log(job.id) // batch_abc123
|
|
124
|
+
|
|
125
|
+
// Wait for results (up to 24hr turnaround)
|
|
126
|
+
const results = await batch.wait()
|
|
96
127
|
```
|
|
97
128
|
|
|
98
|
-
|
|
129
|
+
Or use the `withBatch` helper:
|
|
99
130
|
|
|
100
131
|
```typescript
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const evaluated = await candidates.map(candidate => ({
|
|
104
|
-
candidate,
|
|
105
|
-
fit: is`${candidate} matches ${requirements}?`,
|
|
106
|
-
summary: ai`one-line summary of ${candidate}`,
|
|
107
|
-
}))
|
|
132
|
+
import { withBatch } from 'ai-functions'
|
|
108
133
|
|
|
109
|
-
const
|
|
134
|
+
const results = await withBatch(async (batch) => {
|
|
135
|
+
return ['TypeScript', 'React', 'Next.js'].map(topic =>
|
|
136
|
+
batch.add(`Write tutorial about ${topic}`)
|
|
137
|
+
)
|
|
138
|
+
})
|
|
110
139
|
```
|
|
111
140
|
|
|
112
|
-
|
|
141
|
+
### Retry & Circuit Breaker
|
|
113
142
|
|
|
114
|
-
|
|
143
|
+
Built-in resilience for production workloads:
|
|
115
144
|
|
|
116
145
|
```typescript
|
|
117
|
-
|
|
118
|
-
write`blog post about ${topic}` // long-form content
|
|
119
|
-
summarize`${document}` // condense to key points
|
|
120
|
-
list`ideas for ${topic}` // array of items
|
|
121
|
-
lists`pros and cons of ${topic}` // named lists
|
|
122
|
-
extract`emails from ${text}` // structured extraction
|
|
123
|
-
```
|
|
146
|
+
import { withRetry, RetryPolicy, CircuitBreaker, FallbackChain } from 'ai-functions'
|
|
124
147
|
|
|
125
|
-
|
|
148
|
+
// Simple retry wrapper
|
|
149
|
+
const reliableAI = withRetry(myAIFunction, {
|
|
150
|
+
maxRetries: 3,
|
|
151
|
+
baseDelay: 1000,
|
|
152
|
+
jitter: 0.2, // Prevent thundering herd
|
|
153
|
+
})
|
|
126
154
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
155
|
+
// Advanced retry policy
|
|
156
|
+
const policy = new RetryPolicy({
|
|
157
|
+
maxRetries: 5,
|
|
158
|
+
baseDelay: 1000,
|
|
159
|
+
maxDelay: 30000,
|
|
160
|
+
jitterStrategy: 'decorrelated',
|
|
161
|
+
respectRetryAfter: true, // Honor rate limit headers
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
await policy.execute(async () => {
|
|
165
|
+
return await ai`generate content`
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
// Circuit breaker for fail-fast
|
|
169
|
+
const breaker = new CircuitBreaker({
|
|
170
|
+
failureThreshold: 5,
|
|
171
|
+
resetTimeout: 30000,
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
await breaker.execute(async () => {
|
|
175
|
+
return await ai`generate content`
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
// Model fallback chain
|
|
179
|
+
const fallback = new FallbackChain([
|
|
180
|
+
{ name: 'claude-sonnet', execute: () => generateWithClaude(prompt) },
|
|
181
|
+
{ name: 'gpt-4o', execute: () => generateWithGPT(prompt) },
|
|
182
|
+
{ name: 'gemini-pro', execute: () => generateWithGemini(prompt) },
|
|
183
|
+
])
|
|
184
|
+
|
|
185
|
+
const result = await fallback.execute()
|
|
130
186
|
```
|
|
131
187
|
|
|
132
|
-
###
|
|
188
|
+
### Caching
|
|
189
|
+
|
|
190
|
+
Avoid redundant API calls with intelligent caching:
|
|
133
191
|
|
|
134
192
|
```typescript
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
193
|
+
import { GenerationCache, EmbeddingCache, withCache, MemoryCache } from 'ai-functions'
|
|
194
|
+
|
|
195
|
+
// Generation cache
|
|
196
|
+
const cache = new GenerationCache({
|
|
197
|
+
defaultTTL: 3600000, // 1 hour
|
|
198
|
+
maxSize: 1000, // LRU eviction
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
// Check cache first
|
|
202
|
+
const cached = await cache.get({ prompt, model: 'sonnet' })
|
|
203
|
+
if (!cached) {
|
|
204
|
+
const result = await ai`${prompt}`
|
|
205
|
+
await cache.set({ prompt, model: 'sonnet' }, result)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Embedding cache with batch support
|
|
209
|
+
const embedCache = new EmbeddingCache()
|
|
210
|
+
const { hits, misses } = await embedCache.getMany(texts, { model: 'text-embedding-3-small' })
|
|
211
|
+
|
|
212
|
+
// Wrap any function with caching
|
|
213
|
+
const cachedGenerate = withCache(
|
|
214
|
+
new MemoryCache(),
|
|
215
|
+
generateContent,
|
|
216
|
+
{ keyFn: (prompt) => prompt, ttl: 3600000 }
|
|
217
|
+
)
|
|
139
218
|
```
|
|
140
219
|
|
|
141
|
-
###
|
|
220
|
+
### Budget Tracking
|
|
221
|
+
|
|
222
|
+
Monitor and limit spending:
|
|
142
223
|
|
|
143
224
|
```typescript
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
225
|
+
import { BudgetTracker, withBudget, BudgetExceededError } from 'ai-functions'
|
|
226
|
+
|
|
227
|
+
// Create a budget tracker
|
|
228
|
+
const tracker = new BudgetTracker({
|
|
229
|
+
maxTokens: 100000,
|
|
230
|
+
maxCost: 10.00, // USD
|
|
231
|
+
alertThresholds: [0.5, 0.8, 0.95],
|
|
232
|
+
onAlert: (alert) => {
|
|
233
|
+
console.log(`Budget ${alert.type} at ${alert.threshold * 100}%`)
|
|
234
|
+
},
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
// Record usage
|
|
238
|
+
tracker.recordUsage({
|
|
239
|
+
inputTokens: 1500,
|
|
240
|
+
outputTokens: 500,
|
|
241
|
+
model: 'claude-sonnet-4-20250514',
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
// Check remaining budget
|
|
245
|
+
console.log(tracker.getRemainingBudget())
|
|
246
|
+
// { tokens: 98000, cost: 9.95 }
|
|
247
|
+
|
|
248
|
+
// Use with automatic tracking
|
|
249
|
+
const result = await withBudget({ maxCost: 5.00 }, async (tracker) => {
|
|
250
|
+
// All AI calls within this scope are tracked
|
|
251
|
+
return await ai`generate content`
|
|
252
|
+
})
|
|
147
253
|
```
|
|
148
254
|
|
|
149
|
-
###
|
|
255
|
+
### Tool Orchestration
|
|
256
|
+
|
|
257
|
+
Build agentic loops with tool calling:
|
|
150
258
|
|
|
151
259
|
```typescript
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
260
|
+
import { AgenticLoop, createTool, createToolset } from 'ai-functions'
|
|
261
|
+
|
|
262
|
+
// Define tools
|
|
263
|
+
const searchTool = createTool({
|
|
264
|
+
name: 'search',
|
|
265
|
+
description: 'Search the web',
|
|
266
|
+
parameters: { query: 'Search query' },
|
|
267
|
+
handler: async ({ query }) => await searchWeb(query),
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
const calculatorTool = createTool({
|
|
271
|
+
name: 'calculate',
|
|
272
|
+
description: 'Perform calculations',
|
|
273
|
+
parameters: { expression: 'Math expression' },
|
|
274
|
+
handler: async ({ expression }) => eval(expression),
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
// Create an agentic loop
|
|
278
|
+
const loop = new AgenticLoop({
|
|
279
|
+
model: 'claude-sonnet-4-20250514',
|
|
280
|
+
tools: [searchTool, calculatorTool],
|
|
281
|
+
maxIterations: 10,
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
const result = await loop.run('What is the population of Tokyo times 2?')
|
|
155
285
|
```
|
|
156
286
|
|
|
157
|
-
##
|
|
287
|
+
## Configuration
|
|
158
288
|
|
|
159
|
-
|
|
289
|
+
### Global Configuration
|
|
160
290
|
|
|
161
291
|
```typescript
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
292
|
+
import { configure } from 'ai-functions'
|
|
293
|
+
|
|
294
|
+
configure({
|
|
295
|
+
model: 'claude-sonnet-4-20250514',
|
|
296
|
+
provider: 'anthropic',
|
|
297
|
+
batchMode: 'auto', // 'auto' | 'immediate' | 'flex' | 'deferred'
|
|
298
|
+
flexThreshold: 5, // Use flex for 5+ items
|
|
299
|
+
batchThreshold: 500, // Use batch API for 500+ items
|
|
300
|
+
})
|
|
166
301
|
```
|
|
167
302
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
Process arrays in a single LLM call:
|
|
303
|
+
### Scoped Configuration
|
|
171
304
|
|
|
172
305
|
```typescript
|
|
173
|
-
|
|
306
|
+
import { withContext } from 'ai-functions'
|
|
307
|
+
|
|
308
|
+
const results = await withContext(
|
|
309
|
+
{ provider: 'openai', model: 'gpt-4o', batchMode: 'deferred' },
|
|
310
|
+
async () => {
|
|
311
|
+
const titles = await list`10 blog titles`
|
|
312
|
+
return titles.map(title => write`blog post: ${title}`)
|
|
313
|
+
}
|
|
314
|
+
)
|
|
315
|
+
```
|
|
174
316
|
|
|
175
|
-
|
|
176
|
-
const qualified = await leads.map(lead => ({
|
|
177
|
-
lead,
|
|
178
|
-
score: ai`score 1-100: ${lead}`,
|
|
179
|
-
qualified: is`${lead} matches ${icp}?`,
|
|
180
|
-
nextStep: ai`recommended action for ${lead}`,
|
|
181
|
-
}))
|
|
317
|
+
### Environment Variables
|
|
182
318
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
319
|
+
```bash
|
|
320
|
+
AI_MODEL=claude-sonnet-4-20250514
|
|
321
|
+
AI_PROVIDER=anthropic
|
|
322
|
+
AI_BATCH_MODE=auto
|
|
323
|
+
AI_FLEX_THRESHOLD=5
|
|
324
|
+
AI_BATCH_THRESHOLD=500
|
|
325
|
+
AI_BATCH_WEBHOOK_URL=https://api.example.com/webhook
|
|
187
326
|
```
|
|
188
327
|
|
|
189
|
-
##
|
|
328
|
+
## Schema-Based Functions
|
|
190
329
|
|
|
191
|
-
|
|
330
|
+
Create typed AI functions with simple schemas:
|
|
192
331
|
|
|
193
332
|
```typescript
|
|
333
|
+
import { AI } from 'ai-functions'
|
|
334
|
+
|
|
194
335
|
const ai = AI({
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
336
|
+
recipe: {
|
|
337
|
+
name: 'Recipe name',
|
|
338
|
+
servings: 'Number of servings (number)',
|
|
339
|
+
ingredients: ['List of ingredients'],
|
|
340
|
+
steps: ['Cooking steps'],
|
|
341
|
+
prepTime: 'Prep time in minutes (number)',
|
|
200
342
|
},
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
343
|
+
storyBrand: {
|
|
344
|
+
hero: 'Who is the customer?',
|
|
345
|
+
problem: {
|
|
346
|
+
internal: 'Internal struggle',
|
|
347
|
+
external: 'External challenge',
|
|
348
|
+
philosophical: 'Why is this wrong?',
|
|
349
|
+
},
|
|
350
|
+
guide: 'Who helps them?',
|
|
351
|
+
plan: ['Steps to success'],
|
|
352
|
+
callToAction: 'What should they do?',
|
|
353
|
+
success: 'What success looks like',
|
|
354
|
+
failure: 'What failure looks like',
|
|
207
355
|
},
|
|
208
356
|
})
|
|
209
357
|
|
|
210
|
-
// Fully typed
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
result.qualified // boolean
|
|
214
|
-
result.nextSteps // string[]
|
|
215
|
-
```
|
|
358
|
+
// Fully typed results
|
|
359
|
+
const recipe = await ai.recipe('Italian pasta for 4 people')
|
|
360
|
+
// { name: string, servings: number, ingredients: string[], ... }
|
|
216
361
|
|
|
217
|
-
|
|
362
|
+
const brand = await ai.storyBrand('A developer tools startup')
|
|
363
|
+
// { hero: string, problem: { internal, external, philosophical }, ... }
|
|
364
|
+
```
|
|
218
365
|
|
|
219
|
-
|
|
220
|
-
|--------|------|---------|
|
|
221
|
-
| `'description'` | string | `name: 'Company name'` |
|
|
222
|
-
| `'desc (number)'` | number | `score: 'Score 1-100 (number)'` |
|
|
223
|
-
| `'desc (boolean)'` | boolean | `qualified: 'Pursue? (boolean)'` |
|
|
224
|
-
| `'opt1 \| opt2'` | enum | `priority: 'high \| medium \| low'` |
|
|
225
|
-
| `['description']` | array | `steps: ['Action items']` |
|
|
226
|
-
| `{ nested }` | object | `contact: { name, email }` |
|
|
366
|
+
## Define Custom Functions
|
|
227
367
|
|
|
228
|
-
|
|
368
|
+
```typescript
|
|
369
|
+
import { define, defineFunction } from 'ai-functions'
|
|
229
370
|
|
|
230
|
-
|
|
371
|
+
// Auto-define from name and example args
|
|
372
|
+
const planTrip = await define('planTrip', {
|
|
373
|
+
destination: 'Tokyo',
|
|
374
|
+
travelers: 2
|
|
375
|
+
})
|
|
231
376
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
377
|
+
// Or define explicitly with full control
|
|
378
|
+
const summarize = defineFunction({
|
|
379
|
+
type: 'generative',
|
|
380
|
+
name: 'summarize',
|
|
381
|
+
args: { text: 'Text to summarize', maxLength: 'Max words (number)' },
|
|
382
|
+
output: 'string',
|
|
383
|
+
promptTemplate: 'Summarize in {{maxLength}} words: {{text}}',
|
|
239
384
|
})
|
|
240
|
-
|
|
385
|
+
|
|
386
|
+
// Use the defined functions
|
|
387
|
+
const trip = await planTrip.call({ destination: 'Paris', travelers: 4 })
|
|
388
|
+
const summary = await summarize.call({ text: longArticle, maxLength: 100 })
|
|
241
389
|
```
|
|
242
390
|
|
|
391
|
+
### The four Function kinds
|
|
392
|
+
|
|
393
|
+
`defineFunction` accepts a discriminated union (`FunctionDefinition`). Each `type` has a distinct execution contract:
|
|
394
|
+
|
|
395
|
+
| `type` | Meaning | At call time |
|
|
396
|
+
|--------|---------|--------------|
|
|
397
|
+
| `code` | **Deterministic handler** — a fetch/transform/rule | Runs your `handler` (or inline `code` body). **No model.** |
|
|
398
|
+
| `generative` | One-shot generation | `generateObject` / `generateText` |
|
|
399
|
+
| `agentic` | Plan-execute-observe tool loop | Iterative model + tool calls |
|
|
400
|
+
| `human` | Human/approval step | Generates channel UI, returns a pending result |
|
|
401
|
+
|
|
243
402
|
```typescript
|
|
244
|
-
//
|
|
245
|
-
const
|
|
403
|
+
// Code = deterministic. Supply a handler (or an inline `code` string).
|
|
404
|
+
const calculateTax = defineFunction({
|
|
405
|
+
type: 'code',
|
|
406
|
+
name: 'calculateTax',
|
|
407
|
+
args: { amount: 'Amount (number)', rate: 'Rate (number)' },
|
|
408
|
+
handler: ({ amount, rate }) => amount * rate, // runs every call, no LLM
|
|
409
|
+
})
|
|
410
|
+
await calculateTax.call({ amount: 100, rate: 0.2 }) // => 20
|
|
246
411
|
```
|
|
247
412
|
|
|
248
|
-
|
|
413
|
+
> **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.
|
|
414
|
+
>
|
|
415
|
+
> 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:
|
|
416
|
+
>
|
|
417
|
+
> ```typescript
|
|
418
|
+
> import { generateCode } from 'ai-functions'
|
|
419
|
+
> const src = await generateCode(
|
|
420
|
+
> { name: 'fib', args: { n: '(number)' }, language: 'typescript' },
|
|
421
|
+
> { n: 10 }
|
|
422
|
+
> ) // => string of generated source
|
|
423
|
+
> ```
|
|
424
|
+
>
|
|
425
|
+
> The `generate('code', prompt)` primitive is unchanged — it is a string-prompt code-authoring helper, distinct from the `FunctionDefinition` union.
|
|
426
|
+
|
|
427
|
+
## API Reference
|
|
428
|
+
|
|
429
|
+
### Core Primitives
|
|
430
|
+
|
|
431
|
+
| Function | Description | Returns |
|
|
432
|
+
|----------|-------------|---------|
|
|
433
|
+
| `ai` | General-purpose generation with dynamic schema | `Promise<T>` |
|
|
434
|
+
| `write` | Generate text content | `Promise<string>` |
|
|
435
|
+
| `list` | Generate a list of items | `Promise<string[]>` |
|
|
436
|
+
| `lists` | Generate multiple named lists | `Promise<Record<string, string[]>>` |
|
|
437
|
+
| `is` | Boolean yes/no checks | `Promise<boolean>` |
|
|
438
|
+
| `do` | Execute a task | `Promise<{ summary, actions }>` |
|
|
439
|
+
| `extract` | Extract structured data | `Promise<T[]>` |
|
|
440
|
+
| `summarize` | Summarize content | `Promise<string>` |
|
|
441
|
+
| `code` | Generate code | `Promise<string>` |
|
|
442
|
+
| `decide` | Choose between options | `(options) => Promise<T>` |
|
|
443
|
+
|
|
444
|
+
### Batch Processing
|
|
445
|
+
|
|
446
|
+
| Export | Description |
|
|
447
|
+
|--------|-------------|
|
|
448
|
+
| `createBatch()` | Create a batch queue for deferred execution |
|
|
449
|
+
| `withBatch()` | Execute operations in batch mode |
|
|
450
|
+
| `BatchQueue` | Class for managing batch jobs |
|
|
451
|
+
|
|
452
|
+
### Resilience
|
|
453
|
+
|
|
454
|
+
| Export | Description |
|
|
455
|
+
|--------|-------------|
|
|
456
|
+
| `withRetry()` | Wrap function with retry logic |
|
|
457
|
+
| `RetryPolicy` | Configurable retry policy |
|
|
458
|
+
| `CircuitBreaker` | Fail-fast circuit breaker |
|
|
459
|
+
| `FallbackChain` | Model failover chain |
|
|
460
|
+
|
|
461
|
+
### Caching
|
|
462
|
+
|
|
463
|
+
| Export | Description |
|
|
464
|
+
|--------|-------------|
|
|
465
|
+
| `MemoryCache` | In-memory LRU cache |
|
|
466
|
+
| `GenerationCache` | Cache for generation results |
|
|
467
|
+
| `EmbeddingCache` | Cache for embeddings |
|
|
468
|
+
| `withCache()` | Wrap function with caching |
|
|
469
|
+
|
|
470
|
+
### Budget & Tracking
|
|
471
|
+
|
|
472
|
+
| Export | Description |
|
|
473
|
+
|--------|-------------|
|
|
474
|
+
| `BudgetTracker` | Track token usage and costs |
|
|
475
|
+
| `withBudget()` | Execute with budget limits |
|
|
476
|
+
| `RequestContext` | Request tracing and isolation |
|
|
477
|
+
|
|
478
|
+
### Configuration
|
|
479
|
+
|
|
480
|
+
| Export | Description |
|
|
481
|
+
|--------|-------------|
|
|
482
|
+
| `configure()` | Set global defaults |
|
|
483
|
+
| `withContext()` | Scoped configuration |
|
|
484
|
+
| `getContext()` | Get current context |
|
|
249
485
|
|
|
250
486
|
## Related Packages
|
|
251
487
|
|
|
252
|
-
- [`ai-
|
|
253
|
-
- [`ai-providers`](../ai-providers)
|
|
254
|
-
- [`language-models`](../language-models)
|
|
488
|
+
- [`ai-core`](../ai-core) - Lightweight core primitives (no batch/budget/retry)
|
|
489
|
+
- [`ai-providers`](../ai-providers) - Provider integrations
|
|
490
|
+
- [`language-models`](../language-models) - Model aliases and configuration
|
|
491
|
+
|
|
492
|
+
## License
|
|
493
|
+
|
|
494
|
+
MIT
|