ai-functions 2.1.3 → 2.4.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 -1
- package/CHANGELOG.md +90 -1
- package/README.md +38 -0
- package/dist/ai-promise.d.ts +3 -3
- package/dist/ai-promise.d.ts.map +1 -1
- package/dist/ai-promise.js +135 -64
- 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 +51 -858
- 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.map +1 -1
- package/dist/budget.js +27 -14
- package/dist/budget.js.map +1 -1
- package/dist/cache.d.ts +23 -0
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +36 -15
- package/dist/cache.js.map +1 -1
- package/dist/context.d.ts +26 -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 +8 -0
- 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 +176 -0
- package/dist/function-registry.d.ts.map +1 -0
- package/dist/function-registry.js +685 -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 -18
- package/dist/generate.js.map +1 -1
- package/dist/index.d.ts +18 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +35 -18
- 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 +66 -1
- package/dist/retry.d.ts.map +1 -1
- package/dist/retry.js +115 -8
- package/dist/retry.js.map +1 -1
- package/dist/sandbox.d.ts +36 -0
- package/dist/sandbox.d.ts.map +1 -0
- package/dist/sandbox.js +44 -0
- package/dist/sandbox.js.map +1 -0
- package/dist/schema.js +2 -2
- 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 +66 -4
- package/dist/tool-orchestration.d.ts.map +1 -1
- package/dist/tool-orchestration.js +123 -23
- package/dist/tool-orchestration.js.map +1 -1
- 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 +155 -19
- 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 +29 -25
- package/src/ai-promise.ts +226 -140
- package/src/ai-schemas.ts +122 -0
- package/src/ai.ts +71 -1176
- 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 +31 -18
- package/src/cache.ts +45 -17
- package/src/context.ts +106 -77
- package/src/digital-objects-registry.ts +750 -0
- package/src/errors.ts +37 -0
- package/src/eval/runner.ts +60 -36
- 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 +874 -0
- package/src/generate.ts +33 -28
- package/src/index.ts +122 -21
- 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 +144 -18
- package/src/sandbox.ts +52 -0
- package/src/schema.ts +8 -8
- package/src/telemetry.ts +403 -0
- package/src/template.ts +8 -4
- package/src/tool-orchestration.ts +213 -48
- package/src/type-guards.ts +31 -0
- package/src/types.ts +186 -27
- 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/batch-autosubmit-errors.test.ts +49 -37
- package/test/batch-blog-posts.test.ts +87 -129
- 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/fill-template.test.ts +89 -0
- package/test/generate-core.test.ts +140 -229
- package/test/implicit-batch.test.ts +22 -65
- package/test/retry-policy-integration.test.ts +117 -0
- package/test/sandbox-execution.test.ts +155 -0
- package/test/schema.test.ts +55 -19
- package/test/template.test.ts +1164 -0
- package/test/tool-orchestration.test.ts +270 -0
- package/test/wrap-for-v3.test.ts +612 -0
- package/vitest.config.js +6 -0
- package/vitest.config.ts +20 -0
- package/LICENSE +0 -21
- 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
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edge Function Deployment Example (Cloudflare Workers)
|
|
3
|
+
*
|
|
4
|
+
* This example demonstrates deploying ai-functions on Cloudflare Workers.
|
|
5
|
+
* It shows how to:
|
|
6
|
+
* - Configure for edge runtime
|
|
7
|
+
* - Handle request/response patterns
|
|
8
|
+
* - Use Workers AI or external providers
|
|
9
|
+
* - Implement rate limiting and caching
|
|
10
|
+
*
|
|
11
|
+
* Note: This file demonstrates the patterns. To actually deploy, you would:
|
|
12
|
+
* 1. npm create cloudflare@latest
|
|
13
|
+
* 2. Add ai-functions as a dependency
|
|
14
|
+
* 3. Use this code in src/index.ts
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```bash
|
|
18
|
+
* # Local development
|
|
19
|
+
* npx wrangler dev
|
|
20
|
+
*
|
|
21
|
+
* # Deploy
|
|
22
|
+
* npx wrangler deploy
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import {
|
|
27
|
+
ai,
|
|
28
|
+
write,
|
|
29
|
+
list,
|
|
30
|
+
is,
|
|
31
|
+
configure,
|
|
32
|
+
MemoryCache,
|
|
33
|
+
withRetry,
|
|
34
|
+
GenerationCache,
|
|
35
|
+
} from '../src/index.js'
|
|
36
|
+
|
|
37
|
+
// ============================================================================
|
|
38
|
+
// Types
|
|
39
|
+
// ============================================================================
|
|
40
|
+
|
|
41
|
+
interface Env {
|
|
42
|
+
AI_GATEWAY_URL?: string
|
|
43
|
+
ANTHROPIC_API_KEY?: string
|
|
44
|
+
OPENAI_API_KEY?: string
|
|
45
|
+
KV_CACHE?: unknown // KVNamespace in actual Workers
|
|
46
|
+
RATE_LIMIT_KV?: unknown
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface RequestBody {
|
|
50
|
+
action: 'generate' | 'classify' | 'extract' | 'summarize'
|
|
51
|
+
input: string
|
|
52
|
+
options?: Record<string, unknown>
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface APIResponse<T = unknown> {
|
|
56
|
+
success: boolean
|
|
57
|
+
data?: T
|
|
58
|
+
error?: string
|
|
59
|
+
meta?: {
|
|
60
|
+
requestId: string
|
|
61
|
+
latencyMs: number
|
|
62
|
+
cached: boolean
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ============================================================================
|
|
67
|
+
// Worker Handler
|
|
68
|
+
// ============================================================================
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Main worker handler (Cloudflare Workers format)
|
|
72
|
+
*
|
|
73
|
+
* In actual wrangler.toml:
|
|
74
|
+
* ```toml
|
|
75
|
+
* name = "ai-functions-worker"
|
|
76
|
+
* main = "src/index.ts"
|
|
77
|
+
* compatibility_date = "2024-01-01"
|
|
78
|
+
*
|
|
79
|
+
* [vars]
|
|
80
|
+
* AI_GATEWAY_URL = "https://your-gateway.com"
|
|
81
|
+
*
|
|
82
|
+
* [[kv_namespaces]]
|
|
83
|
+
* binding = "KV_CACHE"
|
|
84
|
+
* id = "your-kv-namespace-id"
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
async function handleRequest(request: Request, env: Env): Promise<Response> {
|
|
88
|
+
const startTime = Date.now()
|
|
89
|
+
const requestId = crypto.randomUUID()
|
|
90
|
+
|
|
91
|
+
// Configure ai-functions
|
|
92
|
+
configure({
|
|
93
|
+
model: 'sonnet',
|
|
94
|
+
provider: 'anthropic',
|
|
95
|
+
// In production, use env variables
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
// CORS headers for API
|
|
99
|
+
const corsHeaders = {
|
|
100
|
+
'Access-Control-Allow-Origin': '*',
|
|
101
|
+
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
|
102
|
+
'Access-Control-Allow-Headers': 'Content-Type',
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Handle OPTIONS for CORS
|
|
106
|
+
if (request.method === 'OPTIONS') {
|
|
107
|
+
return new Response(null, { headers: corsHeaders })
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
// Parse request
|
|
112
|
+
const body = (await request.json()) as RequestBody
|
|
113
|
+
|
|
114
|
+
// Process based on action
|
|
115
|
+
let result: unknown
|
|
116
|
+
let cached = false
|
|
117
|
+
|
|
118
|
+
switch (body.action) {
|
|
119
|
+
case 'generate':
|
|
120
|
+
result = await handleGenerate(body.input, body.options)
|
|
121
|
+
break
|
|
122
|
+
case 'classify':
|
|
123
|
+
result = await handleClassify(body.input, body.options)
|
|
124
|
+
break
|
|
125
|
+
case 'extract':
|
|
126
|
+
result = await handleExtract(body.input, body.options)
|
|
127
|
+
break
|
|
128
|
+
case 'summarize':
|
|
129
|
+
result = await handleSummarize(body.input, body.options)
|
|
130
|
+
break
|
|
131
|
+
default:
|
|
132
|
+
throw new Error(`Unknown action: ${body.action}`)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const response: APIResponse = {
|
|
136
|
+
success: true,
|
|
137
|
+
data: result,
|
|
138
|
+
meta: {
|
|
139
|
+
requestId,
|
|
140
|
+
latencyMs: Date.now() - startTime,
|
|
141
|
+
cached,
|
|
142
|
+
},
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return new Response(JSON.stringify(response), {
|
|
146
|
+
headers: {
|
|
147
|
+
'Content-Type': 'application/json',
|
|
148
|
+
...corsHeaders,
|
|
149
|
+
},
|
|
150
|
+
})
|
|
151
|
+
} catch (error) {
|
|
152
|
+
const response: APIResponse = {
|
|
153
|
+
success: false,
|
|
154
|
+
error: (error as Error).message,
|
|
155
|
+
meta: {
|
|
156
|
+
requestId,
|
|
157
|
+
latencyMs: Date.now() - startTime,
|
|
158
|
+
cached: false,
|
|
159
|
+
},
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return new Response(JSON.stringify(response), {
|
|
163
|
+
status: 500,
|
|
164
|
+
headers: {
|
|
165
|
+
'Content-Type': 'application/json',
|
|
166
|
+
...corsHeaders,
|
|
167
|
+
},
|
|
168
|
+
})
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ============================================================================
|
|
173
|
+
// Action Handlers
|
|
174
|
+
// ============================================================================
|
|
175
|
+
|
|
176
|
+
async function handleGenerate(input: string, options?: Record<string, unknown>): Promise<unknown> {
|
|
177
|
+
const { type = 'text', schema } = options || {}
|
|
178
|
+
|
|
179
|
+
if (type === 'text') {
|
|
180
|
+
return write`${input}`
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (schema) {
|
|
184
|
+
return ai`${input}`
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return ai`${input}`
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async function handleClassify(input: string, options?: Record<string, unknown>): Promise<unknown> {
|
|
191
|
+
const { categories = ['positive', 'negative', 'neutral'] } = options || {}
|
|
192
|
+
|
|
193
|
+
const { category, confidence, reasoning } =
|
|
194
|
+
await ai`Classify this text into one of these categories: ${(categories as string[]).join(', ')}
|
|
195
|
+
|
|
196
|
+
Text: "${input}"
|
|
197
|
+
|
|
198
|
+
Provide:
|
|
199
|
+
- category: the best matching category
|
|
200
|
+
- confidence: confidence score 0-1
|
|
201
|
+
- reasoning: brief explanation`
|
|
202
|
+
|
|
203
|
+
return { category, confidence, reasoning }
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async function handleExtract(input: string, options?: Record<string, unknown>): Promise<unknown> {
|
|
207
|
+
const { fields } = options || {}
|
|
208
|
+
|
|
209
|
+
if (fields && Array.isArray(fields)) {
|
|
210
|
+
const fieldDescriptions = (fields as string[]).map((f) => `- ${f}`).join('\n')
|
|
211
|
+
|
|
212
|
+
return ai`Extract the following fields from this text:
|
|
213
|
+
${fieldDescriptions}
|
|
214
|
+
|
|
215
|
+
Text: "${input}"`
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Default entity extraction
|
|
219
|
+
const { entities, dates, amounts, names } = await ai`Extract key information from this text:
|
|
220
|
+
|
|
221
|
+
"${input}"
|
|
222
|
+
|
|
223
|
+
Provide:
|
|
224
|
+
- entities: array of key entities mentioned
|
|
225
|
+
- dates: array of dates found
|
|
226
|
+
- amounts: array of monetary amounts or quantities
|
|
227
|
+
- names: array of person/company names`
|
|
228
|
+
|
|
229
|
+
return { entities, dates, amounts, names }
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async function handleSummarize(input: string, options?: Record<string, unknown>): Promise<unknown> {
|
|
233
|
+
const { maxLength = 100, style = 'concise' } = options || {}
|
|
234
|
+
|
|
235
|
+
const summary = await write`Summarize this text in a ${style} style, maximum ${maxLength} words:
|
|
236
|
+
|
|
237
|
+
"${input}"`
|
|
238
|
+
|
|
239
|
+
return { summary, wordCount: summary.split(/\s+/).length }
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ============================================================================
|
|
243
|
+
// Middleware Utilities
|
|
244
|
+
// ============================================================================
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Simple in-memory rate limiter (use KV in production)
|
|
248
|
+
*/
|
|
249
|
+
class RateLimiter {
|
|
250
|
+
private requests = new Map<string, number[]>()
|
|
251
|
+
private maxRequests: number
|
|
252
|
+
private windowMs: number
|
|
253
|
+
|
|
254
|
+
constructor(maxRequests = 100, windowMs = 60000) {
|
|
255
|
+
this.maxRequests = maxRequests
|
|
256
|
+
this.windowMs = windowMs
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
check(key: string): boolean {
|
|
260
|
+
const now = Date.now()
|
|
261
|
+
const windowStart = now - this.windowMs
|
|
262
|
+
|
|
263
|
+
let timestamps = this.requests.get(key) || []
|
|
264
|
+
timestamps = timestamps.filter((t) => t > windowStart)
|
|
265
|
+
|
|
266
|
+
if (timestamps.length >= this.maxRequests) {
|
|
267
|
+
return false
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
timestamps.push(now)
|
|
271
|
+
this.requests.set(key, timestamps)
|
|
272
|
+
return true
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Request caching utility
|
|
278
|
+
*/
|
|
279
|
+
class RequestCache {
|
|
280
|
+
private cache: GenerationCache
|
|
281
|
+
|
|
282
|
+
constructor() {
|
|
283
|
+
this.cache = new GenerationCache({
|
|
284
|
+
defaultTTL: 3600000, // 1 hour
|
|
285
|
+
maxSize: 1000,
|
|
286
|
+
})
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async get(key: string): Promise<unknown | null> {
|
|
290
|
+
return this.cache.get({ prompt: key, model: 'cache' })
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async set(key: string, value: unknown): Promise<void> {
|
|
294
|
+
await this.cache.set({ prompt: key, model: 'cache' }, value)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ============================================================================
|
|
299
|
+
// Streaming Response Handler
|
|
300
|
+
// ============================================================================
|
|
301
|
+
|
|
302
|
+
async function handleStreamingRequest(request: Request, env: Env): Promise<Response> {
|
|
303
|
+
const { input } = (await request.json()) as { input: string }
|
|
304
|
+
|
|
305
|
+
// Configure for streaming
|
|
306
|
+
configure({
|
|
307
|
+
model: 'sonnet',
|
|
308
|
+
provider: 'anthropic',
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
const response = write`${input}`
|
|
312
|
+
const stream = response.stream()
|
|
313
|
+
|
|
314
|
+
// Create a TransformStream for SSE
|
|
315
|
+
const { readable, writable } = new TransformStream()
|
|
316
|
+
const writer = writable.getWriter()
|
|
317
|
+
const encoder = new TextEncoder()
|
|
318
|
+
|
|
319
|
+
// Stream in background
|
|
320
|
+
;(async () => {
|
|
321
|
+
try {
|
|
322
|
+
for await (const chunk of stream.textStream) {
|
|
323
|
+
await writer.write(encoder.encode(`data: ${JSON.stringify({ text: chunk })}\n\n`))
|
|
324
|
+
}
|
|
325
|
+
await writer.write(encoder.encode('data: [DONE]\n\n'))
|
|
326
|
+
} finally {
|
|
327
|
+
await writer.close()
|
|
328
|
+
}
|
|
329
|
+
})()
|
|
330
|
+
|
|
331
|
+
return new Response(readable, {
|
|
332
|
+
headers: {
|
|
333
|
+
'Content-Type': 'text/event-stream',
|
|
334
|
+
'Cache-Control': 'no-cache',
|
|
335
|
+
Connection: 'keep-alive',
|
|
336
|
+
},
|
|
337
|
+
})
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// ============================================================================
|
|
341
|
+
// Simulated Worker Execution
|
|
342
|
+
// ============================================================================
|
|
343
|
+
|
|
344
|
+
async function simulateWorkerExecution(): Promise<void> {
|
|
345
|
+
console.log('\n=== Simulating Cloudflare Worker Execution ===\n')
|
|
346
|
+
|
|
347
|
+
const rateLimiter = new RateLimiter(10, 10000) // 10 requests per 10 seconds
|
|
348
|
+
const cache = new RequestCache()
|
|
349
|
+
|
|
350
|
+
// Simulate various API requests
|
|
351
|
+
const testRequests = [
|
|
352
|
+
{
|
|
353
|
+
action: 'generate' as const,
|
|
354
|
+
input: 'Write a tagline for a coffee shop',
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
action: 'classify' as const,
|
|
358
|
+
input: 'I absolutely love this product! Best purchase ever!',
|
|
359
|
+
options: { categories: ['positive', 'negative', 'neutral'] },
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
action: 'extract' as const,
|
|
363
|
+
input: 'Contact John Smith at john@example.com or call 555-1234',
|
|
364
|
+
options: { fields: ['name', 'email', 'phone'] },
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
action: 'summarize' as const,
|
|
368
|
+
input:
|
|
369
|
+
'The quick brown fox jumps over the lazy dog. This sentence contains every letter of the alphabet and is commonly used for testing purposes.',
|
|
370
|
+
options: { maxLength: 20, style: 'brief' },
|
|
371
|
+
},
|
|
372
|
+
]
|
|
373
|
+
|
|
374
|
+
for (const body of testRequests) {
|
|
375
|
+
console.log(`\n--- ${body.action.toUpperCase()} Request ---`)
|
|
376
|
+
console.log(`Input: "${body.input.substring(0, 50)}..."`)
|
|
377
|
+
|
|
378
|
+
// Check rate limit
|
|
379
|
+
const clientIP = '127.0.0.1'
|
|
380
|
+
if (!rateLimiter.check(clientIP)) {
|
|
381
|
+
console.log('Rate limited!')
|
|
382
|
+
continue
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Create mock request
|
|
386
|
+
const request = new Request('https://worker.example.com/api', {
|
|
387
|
+
method: 'POST',
|
|
388
|
+
body: JSON.stringify(body),
|
|
389
|
+
headers: { 'Content-Type': 'application/json' },
|
|
390
|
+
})
|
|
391
|
+
|
|
392
|
+
// Handle request
|
|
393
|
+
const startTime = Date.now()
|
|
394
|
+
const response = await handleRequest(request, {} as Env)
|
|
395
|
+
const result = (await response.json()) as APIResponse
|
|
396
|
+
|
|
397
|
+
console.log(`Response: ${response.status}`)
|
|
398
|
+
console.log(`Latency: ${Date.now() - startTime}ms`)
|
|
399
|
+
console.log(`Result:`, JSON.stringify(result.data, null, 2).substring(0, 200))
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// ============================================================================
|
|
404
|
+
// Export Worker
|
|
405
|
+
// ============================================================================
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* In actual Cloudflare Worker (src/index.ts):
|
|
409
|
+
*
|
|
410
|
+
* ```ts
|
|
411
|
+
* export default {
|
|
412
|
+
* async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
|
|
413
|
+
* // Check for streaming request
|
|
414
|
+
* if (request.url.includes('/stream')) {
|
|
415
|
+
* return handleStreamingRequest(request, env)
|
|
416
|
+
* }
|
|
417
|
+
* return handleRequest(request, env)
|
|
418
|
+
* },
|
|
419
|
+
* }
|
|
420
|
+
* ```
|
|
421
|
+
*/
|
|
422
|
+
|
|
423
|
+
// ============================================================================
|
|
424
|
+
// Main Example
|
|
425
|
+
// ============================================================================
|
|
426
|
+
|
|
427
|
+
async function main() {
|
|
428
|
+
console.log('\n=== Cloudflare Worker Example ===\n')
|
|
429
|
+
|
|
430
|
+
// Configure the AI provider (for local simulation)
|
|
431
|
+
configure({
|
|
432
|
+
model: 'sonnet',
|
|
433
|
+
provider: 'anthropic',
|
|
434
|
+
})
|
|
435
|
+
|
|
436
|
+
// Show worker code structure
|
|
437
|
+
console.log('Worker Code Structure:')
|
|
438
|
+
console.log(`
|
|
439
|
+
// wrangler.toml
|
|
440
|
+
name = "ai-functions-api"
|
|
441
|
+
main = "src/index.ts"
|
|
442
|
+
compatibility_date = "2024-01-01"
|
|
443
|
+
|
|
444
|
+
[vars]
|
|
445
|
+
AI_MODEL = "sonnet"
|
|
446
|
+
|
|
447
|
+
// src/index.ts
|
|
448
|
+
import { ai, write, configure } from 'ai-functions'
|
|
449
|
+
|
|
450
|
+
export default {
|
|
451
|
+
async fetch(request, env, ctx) {
|
|
452
|
+
configure({ model: env.AI_MODEL })
|
|
453
|
+
|
|
454
|
+
const { prompt } = await request.json()
|
|
455
|
+
const response = await write\`\${prompt}\`
|
|
456
|
+
|
|
457
|
+
return new Response(JSON.stringify({ response }), {
|
|
458
|
+
headers: { 'Content-Type': 'application/json' }
|
|
459
|
+
})
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
`)
|
|
463
|
+
|
|
464
|
+
// Run simulation
|
|
465
|
+
await simulateWorkerExecution()
|
|
466
|
+
|
|
467
|
+
console.log('\n--- Deployment Instructions ---')
|
|
468
|
+
console.log('1. npm create cloudflare@latest my-ai-worker')
|
|
469
|
+
console.log('2. cd my-ai-worker && npm install ai-functions')
|
|
470
|
+
console.log('3. Copy the handler code to src/index.ts')
|
|
471
|
+
console.log('4. npx wrangler dev (for local testing)')
|
|
472
|
+
console.log('5. npx wrangler deploy (for production)')
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
main()
|
|
476
|
+
.then(() => {
|
|
477
|
+
console.log('\n=== Example Complete ===\n')
|
|
478
|
+
process.exit(0)
|
|
479
|
+
})
|
|
480
|
+
.catch((error) => {
|
|
481
|
+
console.error('\nError:', error.message)
|
|
482
|
+
process.exit(1)
|
|
483
|
+
})
|