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.
Files changed (286) hide show
  1. package/.turbo/turbo-build.log +1 -4
  2. package/CHANGELOG.md +68 -1
  3. package/README.md +397 -157
  4. package/dist/ai-promise.d.ts +50 -3
  5. package/dist/ai-promise.d.ts.map +1 -1
  6. package/dist/ai-promise.js +410 -51
  7. package/dist/ai-promise.js.map +1 -1
  8. package/dist/ai-schemas.d.ts +56 -0
  9. package/dist/ai-schemas.d.ts.map +1 -0
  10. package/dist/ai-schemas.js +53 -0
  11. package/dist/ai-schemas.js.map +1 -0
  12. package/dist/ai.d.ts +16 -242
  13. package/dist/ai.d.ts.map +1 -1
  14. package/dist/ai.js +54 -837
  15. package/dist/ai.js.map +1 -1
  16. package/dist/batch/anthropic.d.ts +6 -4
  17. package/dist/batch/anthropic.d.ts.map +1 -1
  18. package/dist/batch/anthropic.js +83 -145
  19. package/dist/batch/anthropic.js.map +1 -1
  20. package/dist/batch/bedrock.d.ts +8 -30
  21. package/dist/batch/bedrock.d.ts.map +1 -1
  22. package/dist/batch/bedrock.js +155 -338
  23. package/dist/batch/bedrock.js.map +1 -1
  24. package/dist/batch/cloudflare.d.ts +8 -20
  25. package/dist/batch/cloudflare.d.ts.map +1 -1
  26. package/dist/batch/cloudflare.js +68 -189
  27. package/dist/batch/cloudflare.js.map +1 -1
  28. package/dist/batch/google.d.ts +6 -20
  29. package/dist/batch/google.d.ts.map +1 -1
  30. package/dist/batch/google.js +70 -238
  31. package/dist/batch/google.js.map +1 -1
  32. package/dist/batch/index.d.ts +4 -1
  33. package/dist/batch/index.d.ts.map +1 -1
  34. package/dist/batch/index.js +4 -1
  35. package/dist/batch/index.js.map +1 -1
  36. package/dist/batch/memory.d.ts +1 -1
  37. package/dist/batch/memory.d.ts.map +1 -1
  38. package/dist/batch/memory.js +14 -10
  39. package/dist/batch/memory.js.map +1 -1
  40. package/dist/batch/openai.d.ts +11 -14
  41. package/dist/batch/openai.d.ts.map +1 -1
  42. package/dist/batch/openai.js +52 -156
  43. package/dist/batch/openai.js.map +1 -1
  44. package/dist/batch/provider.d.ts +111 -0
  45. package/dist/batch/provider.d.ts.map +1 -0
  46. package/dist/batch/provider.js +233 -0
  47. package/dist/batch/provider.js.map +1 -0
  48. package/dist/batch-map.d.ts.map +1 -1
  49. package/dist/batch-map.js +23 -17
  50. package/dist/batch-map.js.map +1 -1
  51. package/dist/batch-queue.d.ts +65 -0
  52. package/dist/batch-queue.d.ts.map +1 -1
  53. package/dist/batch-queue.js +169 -14
  54. package/dist/batch-queue.js.map +1 -1
  55. package/dist/budget.d.ts +272 -0
  56. package/dist/budget.d.ts.map +1 -0
  57. package/dist/budget.js +513 -0
  58. package/dist/budget.js.map +1 -0
  59. package/dist/cache.d.ts +295 -0
  60. package/dist/cache.d.ts.map +1 -0
  61. package/dist/cache.js +433 -0
  62. package/dist/cache.js.map +1 -0
  63. package/dist/context.d.ts +42 -8
  64. package/dist/context.d.ts.map +1 -1
  65. package/dist/context.js +64 -62
  66. package/dist/context.js.map +1 -1
  67. package/dist/digital-objects-registry.d.ts +229 -0
  68. package/dist/digital-objects-registry.d.ts.map +1 -0
  69. package/dist/digital-objects-registry.js +617 -0
  70. package/dist/digital-objects-registry.js.map +1 -0
  71. package/dist/embeddings.d.ts +2 -2
  72. package/dist/embeddings.d.ts.map +1 -1
  73. package/dist/errors.d.ts +22 -0
  74. package/dist/errors.d.ts.map +1 -0
  75. package/dist/errors.js +35 -0
  76. package/dist/errors.js.map +1 -0
  77. package/dist/eval/runner.d.ts +10 -1
  78. package/dist/eval/runner.d.ts.map +1 -1
  79. package/dist/eval/runner.js +41 -35
  80. package/dist/eval/runner.js.map +1 -1
  81. package/dist/eval-log/in-memory.d.ts +34 -0
  82. package/dist/eval-log/in-memory.d.ts.map +1 -0
  83. package/dist/eval-log/in-memory.js +84 -0
  84. package/dist/eval-log/in-memory.js.map +1 -0
  85. package/dist/eval-log/index.d.ts +29 -0
  86. package/dist/eval-log/index.d.ts.map +1 -0
  87. package/dist/eval-log/index.js +39 -0
  88. package/dist/eval-log/index.js.map +1 -0
  89. package/dist/eval-log/types.d.ts +101 -0
  90. package/dist/eval-log/types.d.ts.map +1 -0
  91. package/dist/eval-log/types.js +16 -0
  92. package/dist/eval-log/types.js.map +1 -0
  93. package/dist/function-registry.d.ts +116 -0
  94. package/dist/function-registry.d.ts.map +1 -0
  95. package/dist/function-registry.js +546 -0
  96. package/dist/function-registry.js.map +1 -0
  97. package/dist/generate.d.ts +9 -3
  98. package/dist/generate.d.ts.map +1 -1
  99. package/dist/generate.js +18 -22
  100. package/dist/generate.js.map +1 -1
  101. package/dist/index.d.ts +35 -20
  102. package/dist/index.d.ts.map +1 -1
  103. package/dist/index.js +89 -42
  104. package/dist/index.js.map +1 -1
  105. package/dist/logger.d.ts +118 -0
  106. package/dist/logger.d.ts.map +1 -0
  107. package/dist/logger.js +187 -0
  108. package/dist/logger.js.map +1 -0
  109. package/dist/middleware/budget.d.ts +84 -0
  110. package/dist/middleware/budget.d.ts.map +1 -0
  111. package/dist/middleware/budget.js +110 -0
  112. package/dist/middleware/budget.js.map +1 -0
  113. package/dist/middleware/cache.d.ts +103 -0
  114. package/dist/middleware/cache.d.ts.map +1 -0
  115. package/dist/middleware/cache.js +228 -0
  116. package/dist/middleware/cache.js.map +1 -0
  117. package/dist/middleware/embed-cache.d.ts +99 -0
  118. package/dist/middleware/embed-cache.d.ts.map +1 -0
  119. package/dist/middleware/embed-cache.js +128 -0
  120. package/dist/middleware/embed-cache.js.map +1 -0
  121. package/dist/middleware/index.d.ts +11 -0
  122. package/dist/middleware/index.d.ts.map +1 -0
  123. package/dist/middleware/index.js +11 -0
  124. package/dist/middleware/index.js.map +1 -0
  125. package/dist/middleware/trace.d.ts +103 -0
  126. package/dist/middleware/trace.d.ts.map +1 -0
  127. package/dist/middleware/trace.js +176 -0
  128. package/dist/middleware/trace.js.map +1 -0
  129. package/dist/primitives.d.ts +120 -1
  130. package/dist/primitives.d.ts.map +1 -1
  131. package/dist/primitives.js +398 -26
  132. package/dist/primitives.js.map +1 -1
  133. package/dist/retry.d.ts +368 -0
  134. package/dist/retry.d.ts.map +1 -0
  135. package/dist/retry.js +646 -0
  136. package/dist/retry.js.map +1 -0
  137. package/dist/schema.d.ts.map +1 -1
  138. package/dist/schema.js +2 -10
  139. package/dist/schema.js.map +1 -1
  140. package/dist/telemetry.d.ts +128 -0
  141. package/dist/telemetry.d.ts.map +1 -0
  142. package/dist/telemetry.js +285 -0
  143. package/dist/telemetry.js.map +1 -0
  144. package/dist/template.d.ts.map +1 -1
  145. package/dist/template.js +6 -1
  146. package/dist/template.js.map +1 -1
  147. package/dist/tool-orchestration.d.ts +453 -0
  148. package/dist/tool-orchestration.d.ts.map +1 -0
  149. package/dist/tool-orchestration.js +763 -0
  150. package/dist/tool-orchestration.js.map +1 -0
  151. package/dist/type-guards.d.ts +28 -0
  152. package/dist/type-guards.d.ts.map +1 -0
  153. package/dist/type-guards.js +29 -0
  154. package/dist/type-guards.js.map +1 -0
  155. package/dist/types.d.ts +135 -17
  156. package/dist/types.d.ts.map +1 -1
  157. package/dist/types.js +36 -1
  158. package/dist/types.js.map +1 -1
  159. package/dist/wrap-for-v3.d.ts +80 -0
  160. package/dist/wrap-for-v3.d.ts.map +1 -0
  161. package/dist/wrap-for-v3.js +89 -0
  162. package/dist/wrap-for-v3.js.map +1 -0
  163. package/examples/00-quickstart.ts +232 -0
  164. package/examples/01-rag-chatbot.ts +212 -0
  165. package/examples/02-multi-agent-research.ts +290 -0
  166. package/examples/03-email-classification.ts +379 -0
  167. package/examples/04-content-moderation.ts +400 -0
  168. package/examples/05-document-extraction.ts +455 -0
  169. package/examples/06-streaming-chat-nextjs.ts +437 -0
  170. package/examples/07-cloudflare-worker.ts +483 -0
  171. package/examples/08-batch-processing.ts +491 -0
  172. package/examples/09-budget-constrained.ts +527 -0
  173. package/examples/10-tool-orchestration.ts +565 -0
  174. package/examples/11-retry-resilience.ts +403 -0
  175. package/examples/12-caching-strategies.ts +422 -0
  176. package/examples/README.md +145 -0
  177. package/package.json +10 -6
  178. package/src/ai-promise.ts +528 -99
  179. package/src/ai-schemas.ts +122 -0
  180. package/src/ai.ts +69 -1153
  181. package/src/batch/anthropic.ts +96 -161
  182. package/src/batch/bedrock.ts +203 -454
  183. package/src/batch/cloudflare.ts +99 -282
  184. package/src/batch/google.ts +91 -297
  185. package/src/batch/index.ts +4 -1
  186. package/src/batch/memory.ts +15 -10
  187. package/src/batch/openai.ts +65 -193
  188. package/src/batch/provider.ts +336 -0
  189. package/src/batch-map.ts +29 -24
  190. package/src/batch-queue.ts +200 -11
  191. package/src/budget.ts +740 -0
  192. package/src/cache.ts +681 -0
  193. package/src/context.ts +122 -76
  194. package/src/digital-objects-registry.ts +750 -0
  195. package/src/errors.ts +37 -0
  196. package/src/eval/runner.ts +63 -38
  197. package/src/eval-log/in-memory.ts +90 -0
  198. package/src/eval-log/index.ts +46 -0
  199. package/src/eval-log/types.ts +110 -0
  200. package/src/function-registry.ts +671 -0
  201. package/src/generate.ts +33 -33
  202. package/src/index.ts +325 -49
  203. package/src/logger.ts +232 -0
  204. package/src/middleware/budget.ts +171 -0
  205. package/src/middleware/cache.ts +299 -0
  206. package/src/middleware/embed-cache.ts +195 -0
  207. package/src/middleware/index.ts +23 -0
  208. package/src/middleware/trace.ts +248 -0
  209. package/src/primitives.ts +589 -62
  210. package/src/retry.ts +902 -0
  211. package/src/schema.ts +8 -17
  212. package/src/telemetry.ts +403 -0
  213. package/src/template.ts +8 -4
  214. package/src/tool-orchestration.ts +1173 -0
  215. package/src/type-guards.ts +31 -0
  216. package/src/types.ts +164 -25
  217. package/src/wrap-for-v3.ts +105 -0
  218. package/test/ai-promise.test.ts +1080 -0
  219. package/test/ai-proxy.test.ts +1 -1
  220. package/test/backward-compat.test.ts +147 -0
  221. package/test/batch-autosubmit-errors.test.ts +610 -0
  222. package/test/batch-blog-posts.test.ts +87 -129
  223. package/test/budget-tracking.test.ts +800 -0
  224. package/test/cache.test.ts +712 -0
  225. package/test/context-isolation.test.ts +687 -0
  226. package/test/core-functions.test.ts +183 -579
  227. package/test/decide.test.ts +154 -322
  228. package/test/define.test.ts +211 -8
  229. package/test/digital-objects-registry.test.ts +760 -0
  230. package/test/embedding-cache-middleware.test.ts +140 -0
  231. package/test/evals/deterministic.eval.test.ts +376 -0
  232. package/test/generate-core.test.ts +140 -229
  233. package/test/implicit-batch.test.ts +22 -65
  234. package/test/json-parse-error-handling.test.ts +463 -0
  235. package/test/retry-policy-integration.test.ts +117 -0
  236. package/test/retry.test.ts +1016 -0
  237. package/test/schema.test.ts +55 -19
  238. package/test/streaming.test.ts +316 -0
  239. package/test/template.test.ts +1164 -0
  240. package/test/tool-orchestration.test.ts +1040 -0
  241. package/test/wrap-for-v3.test.ts +612 -0
  242. package/vitest.config.js +6 -0
  243. package/vitest.config.ts +20 -0
  244. package/dist/rpc/auth.d.ts +0 -69
  245. package/dist/rpc/auth.d.ts.map +0 -1
  246. package/dist/rpc/auth.js +0 -136
  247. package/dist/rpc/auth.js.map +0 -1
  248. package/dist/rpc/client.d.ts +0 -62
  249. package/dist/rpc/client.d.ts.map +0 -1
  250. package/dist/rpc/client.js +0 -103
  251. package/dist/rpc/client.js.map +0 -1
  252. package/dist/rpc/deferred.d.ts +0 -60
  253. package/dist/rpc/deferred.d.ts.map +0 -1
  254. package/dist/rpc/deferred.js +0 -96
  255. package/dist/rpc/deferred.js.map +0 -1
  256. package/dist/rpc/index.d.ts +0 -22
  257. package/dist/rpc/index.d.ts.map +0 -1
  258. package/dist/rpc/index.js +0 -38
  259. package/dist/rpc/index.js.map +0 -1
  260. package/dist/rpc/local.d.ts +0 -42
  261. package/dist/rpc/local.d.ts.map +0 -1
  262. package/dist/rpc/local.js +0 -50
  263. package/dist/rpc/local.js.map +0 -1
  264. package/dist/rpc/server.d.ts +0 -165
  265. package/dist/rpc/server.d.ts.map +0 -1
  266. package/dist/rpc/server.js +0 -405
  267. package/dist/rpc/server.js.map +0 -1
  268. package/dist/rpc/session.d.ts +0 -32
  269. package/dist/rpc/session.d.ts.map +0 -1
  270. package/dist/rpc/session.js +0 -43
  271. package/dist/rpc/session.js.map +0 -1
  272. package/dist/rpc/transport.d.ts +0 -306
  273. package/dist/rpc/transport.d.ts.map +0 -1
  274. package/dist/rpc/transport.js +0 -731
  275. package/dist/rpc/transport.js.map +0 -1
  276. package/src/batch/anthropic.js +0 -256
  277. package/src/batch/bedrock.js +0 -584
  278. package/src/batch/cloudflare.js +0 -287
  279. package/src/batch/google.js +0 -359
  280. package/src/batch/index.js +0 -30
  281. package/src/batch/memory.js +0 -187
  282. package/src/batch/openai.js +0 -402
  283. package/src/eval/index.js +0 -7
  284. package/src/eval/models.js +0 -119
  285. package/src/eval/runner.js +0 -147
  286. package/test/schema.test.js +0 -96
package/README.md CHANGED
@@ -1,254 +1,494 @@
1
1
  # ai-functions
2
2
 
3
- Call AI like you'd talk to a colleague. No prompts. No configuration. Just say what you need.
3
+ ![Stability: Stable](https://img.shields.io/badge/stability-stable-green)
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
- import { ai, list, is } from 'ai-functions'
31
+ // What you could be doing
32
+ import { list } from 'ai-functions'
7
33
 
8
- // Ask for anything - it reads like English
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
- pnpm add ai-functions
40
+ npm install ai-functions
18
41
  ```
19
42
 
20
- ## The Magic: Promise Pipelining
43
+ Set your API key:
21
44
 
22
- Chain operations naturally—no `await` needed until you actually need the result:
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
- // Destructure to get exactly what you need
26
- const { qualified, score, reason } = ai`qualify this lead: ${lead}`
54
+ import { ai, list, is, write } from 'ai-functions'
27
55
 
28
- // Chain functions together—dependencies resolve automatically
29
- const followUp = ai`write follow-up email based on: ${reason}`
30
- const subject = ai`subject line for: ${followUp}`
56
+ // Generate text
57
+ const poem = await write`a haiku about TypeScript`
31
58
 
32
- // Only await when you need the actual value
33
- if (await qualified) {
34
- await sendEmail({ to: lead.email, subject: await subject, body: await followUp })
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
- ## Real-World Examples
69
+ ### The `list` Primitive with `.map()`
39
70
 
40
- ### Lead Qualification
71
+ Process lists with automatic batching - one prompt generates items, then each item is processed in parallel:
41
72
 
42
73
  ```typescript
43
- const { score, qualified, reasoning } = ai`
44
- qualify ${lead} for our product
45
- considering: ${idealCustomerProfile}
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
- if (await qualified) {
49
- await assignToSales(lead)
50
- }
80
+ // Result: Array of { idea, viable, market } objects
51
81
  ```
52
82
 
53
- ### Content Marketing
83
+ ### Boolean Checks with `is`
54
84
 
55
85
  ```typescript
56
- // Generate topic ideas for your audience
57
- const topics = list`content ideas for ${persona} in ${industry}`
86
+ // Simple validation
87
+ const isColor = await is`"turquoise" is a color` // true
58
88
 
59
- // Evaluate each in parallel—single LLM call!
60
- const evaluated = await topics.map(topic => ({
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
- // Pick the best
67
- const winner = evaluated.find(t => t.potential && t.difficulty === 'easy')
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
- ### Sales Intelligence
97
+ ### Task Execution with `do`
71
98
 
72
99
  ```typescript
73
- const { pros, cons, objections } = lists`
74
- competitive analysis: ${ourProduct} vs ${competitor}
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
- ### Customer Success
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
- // Analyze customer health
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
- if (await churnRisk) {
93
- const outreach = ai`retention outreach for ${customer} addressing ${churnRisk}`
94
- await scheduleCall(customer, await outreach)
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
- ### Recruiting
129
+ Or use the `withBatch` helper:
99
130
 
100
131
  ```typescript
101
- const candidates = list`source ${role} candidates from ${jobBoards}`
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 shortlist = evaluated.filter(c => c.fit)
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
- ## API Reference
141
+ ### Retry & Circuit Breaker
113
142
 
114
- ### Generation
143
+ Built-in resilience for production workloads:
115
144
 
116
145
  ```typescript
117
- ai`anything you need` // flexible object/text
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
- ### Classification
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
- ```typescript
128
- is`${email} spam?` // boolean
129
- decide`which converts better?`(optionA, optionB) // pick best
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
- ### Code & Visuals
188
+ ### Caching
189
+
190
+ Avoid redundant API calls with intelligent caching:
133
191
 
134
192
  ```typescript
135
- code`email validation function` // generate code
136
- diagram`user flow for ${feature}` // mermaid diagrams
137
- slides`pitch deck for ${startup}` // presentations
138
- image`hero image for ${brand}` // image generation
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
- ### Research & Web
220
+ ### Budget Tracking
221
+
222
+ Monitor and limit spending:
142
223
 
143
224
  ```typescript
144
- research`${competitor} market position` // web research
145
- read`${url}` // url to markdown
146
- browse`${url}` // browser automation
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
- ### Human-in-the-Loop
255
+ ### Tool Orchestration
256
+
257
+ Build agentic loops with tool calling:
150
258
 
151
259
  ```typescript
152
- ask`what's the priority for ${feature}?` // free-form input
153
- approve`deploy ${version} to production?` // yes/no approval
154
- review`${document}` // detailed feedback
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
- ## The `lists` Function
287
+ ## Configuration
158
288
 
159
- Get exactly what you ask for through destructuring:
289
+ ### Global Configuration
160
290
 
161
291
  ```typescript
162
- // Just name what you want—the schema is inferred!
163
- const { pros, cons } = lists`pros and cons of ${decision}`
164
- const { strengths, weaknesses, opportunities, threats } = lists`SWOT for ${company}`
165
- const { mustHave, niceToHave, outOfScope } = lists`requirements for ${feature}`
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
- ## Batch Processing with `.map()`
169
-
170
- Process arrays in a single LLM call:
303
+ ### Scoped Configuration
171
304
 
172
305
  ```typescript
173
- const leads = await list`leads from ${campaign}`
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
- // Each field evaluated for each lead—all in ONE call
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
- // Filter and act
184
- qualified
185
- .filter(l => l.qualified)
186
- .forEach(l => createTask(l.nextStep))
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
- ## Typed Schemas with `AI()`
328
+ ## Schema-Based Functions
190
329
 
191
- For reusable, typed functions:
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
- qualifyLead: {
196
- score: 'Lead score 1-100 (number)',
197
- qualified: 'Whether to pursue (boolean)',
198
- reasoning: 'Explanation of score',
199
- nextSteps: ['Recommended actions'],
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
- analyzeCompetitor: {
203
- positioning: 'How they position themselves',
204
- strengths: ['Their advantages'],
205
- weaknesses: ['Their disadvantages'],
206
- battleCard: 'Key talking points for sales',
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 result = await ai.qualifyLead('Enterprise CTO interested in AI automation')
212
- result.score // number
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
- ## Schema Syntax
362
+ const brand = await ai.storyBrand('A developer tools startup')
363
+ // { hero: string, problem: { internal, external, philosophical }, ... }
364
+ ```
218
365
 
219
- | Syntax | Type | Example |
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
- ## Philosophy
368
+ ```typescript
369
+ import { define, defineFunction } from 'ai-functions'
229
370
 
230
- **Code should read like conversation.**
371
+ // Auto-define from name and example args
372
+ const planTrip = await define('planTrip', {
373
+ destination: 'Tokyo',
374
+ travelers: 2
375
+ })
231
376
 
232
- Compare:
233
- ```typescript
234
- // Traditional AI code
235
- const response = await openai.chat.completions.create({
236
- model: "gpt-4",
237
- messages: [{ role: "user", content: `Analyze this lead: ${JSON.stringify(lead)}` }],
238
- response_format: { type: "json_object" },
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
- const result = JSON.parse(response.choices[0].message.content)
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
- // ai-functions
245
- const { qualified, score, nextStep } = ai`analyze lead: ${lead}`
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
- The second version is what you'd say to a colleague. That's the goal.
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-database`](../ai-database) — AI-powered database operations
253
- - [`ai-providers`](../ai-providers) Model provider abstraction
254
- - [`language-models`](../language-models) Model definitions
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