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,527 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Budget-Constrained Generation Example
|
|
3
|
+
*
|
|
4
|
+
* This example demonstrates controlling costs and monitoring token usage.
|
|
5
|
+
* It shows how to:
|
|
6
|
+
* - Set and enforce budget limits
|
|
7
|
+
* - Track costs by model and request
|
|
8
|
+
* - Handle budget alerts
|
|
9
|
+
* - Use cost-effective strategies
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```bash
|
|
13
|
+
* ANTHROPIC_API_KEY=sk-... npx tsx examples/09-budget-constrained.ts
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
ai,
|
|
19
|
+
write,
|
|
20
|
+
list,
|
|
21
|
+
configure,
|
|
22
|
+
withBudget,
|
|
23
|
+
BudgetTracker,
|
|
24
|
+
BudgetExceededError,
|
|
25
|
+
TokenCounter,
|
|
26
|
+
createRequestContext,
|
|
27
|
+
FallbackChain,
|
|
28
|
+
} from '../src/index.js'
|
|
29
|
+
|
|
30
|
+
// ============================================================================
|
|
31
|
+
// Types
|
|
32
|
+
// ============================================================================
|
|
33
|
+
|
|
34
|
+
interface GenerationTask {
|
|
35
|
+
id: string
|
|
36
|
+
prompt: string
|
|
37
|
+
priority: 'high' | 'medium' | 'low'
|
|
38
|
+
maxTokens?: number
|
|
39
|
+
requiredModel?: string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface CostReport {
|
|
43
|
+
totalCost: number
|
|
44
|
+
costByModel: Record<string, number>
|
|
45
|
+
tokensByModel: Record<string, { input: number; output: number }>
|
|
46
|
+
requestCount: number
|
|
47
|
+
avgCostPerRequest: number
|
|
48
|
+
budgetUtilization: number
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ============================================================================
|
|
52
|
+
// Budget Management
|
|
53
|
+
// ============================================================================
|
|
54
|
+
|
|
55
|
+
class BudgetAwareGenerator {
|
|
56
|
+
private tracker: BudgetTracker
|
|
57
|
+
private maxBudget: number
|
|
58
|
+
private alertsReceived: string[] = []
|
|
59
|
+
private requestHistory: { id: string; cost: number; tokens: number }[] = []
|
|
60
|
+
|
|
61
|
+
constructor(maxBudget: number) {
|
|
62
|
+
this.maxBudget = maxBudget
|
|
63
|
+
this.tracker = new BudgetTracker({
|
|
64
|
+
maxCost: maxBudget,
|
|
65
|
+
maxTokens: 1000000,
|
|
66
|
+
alertThresholds: [0.25, 0.5, 0.75, 0.9, 1.0],
|
|
67
|
+
onAlert: (alert) => {
|
|
68
|
+
this.alertsReceived.push(
|
|
69
|
+
`${(alert.threshold * 100).toFixed(0)}% budget used ($${alert.currentUsage.toFixed(4)})`
|
|
70
|
+
)
|
|
71
|
+
console.log(` [ALERT] Budget ${(alert.threshold * 100).toFixed(0)}% consumed`)
|
|
72
|
+
},
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Check if we can afford a generation
|
|
78
|
+
*/
|
|
79
|
+
canAfford(estimatedTokens: number, model: string = 'sonnet'): boolean {
|
|
80
|
+
try {
|
|
81
|
+
this.tracker.checkBudget({ estimatedTokens, model })
|
|
82
|
+
return true
|
|
83
|
+
} catch {
|
|
84
|
+
return false
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Get remaining budget
|
|
90
|
+
*/
|
|
91
|
+
getRemainingBudget(): { cost: number; tokens: number } {
|
|
92
|
+
return this.tracker.getRemainingBudget()
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Generate with budget tracking
|
|
97
|
+
*/
|
|
98
|
+
async generate(task: GenerationTask): Promise<string | null> {
|
|
99
|
+
const estimatedInputTokens = Math.ceil(task.prompt.length / 4)
|
|
100
|
+
const estimatedOutputTokens = task.maxTokens || 500
|
|
101
|
+
const estimatedTotal = estimatedInputTokens + estimatedOutputTokens
|
|
102
|
+
|
|
103
|
+
// Check budget before generating
|
|
104
|
+
if (!this.canAfford(estimatedTotal, task.requiredModel || 'sonnet')) {
|
|
105
|
+
console.log(` [SKIP] Task ${task.id}: Insufficient budget`)
|
|
106
|
+
return null
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
console.log(` [GENERATING] Task ${task.id} (est. ${estimatedTotal} tokens)`)
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
const result = await write`${task.prompt}`
|
|
113
|
+
|
|
114
|
+
// Record actual usage (estimated for this demo)
|
|
115
|
+
const actualInputTokens = estimatedInputTokens
|
|
116
|
+
const actualOutputTokens = Math.ceil(result.length / 4)
|
|
117
|
+
|
|
118
|
+
this.tracker.recordUsage({
|
|
119
|
+
inputTokens: actualInputTokens,
|
|
120
|
+
outputTokens: actualOutputTokens,
|
|
121
|
+
model: task.requiredModel || 'claude-sonnet-4-20250514',
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
// Track request
|
|
125
|
+
const cost = this.estimateCost(actualInputTokens, actualOutputTokens, task.requiredModel)
|
|
126
|
+
this.requestHistory.push({
|
|
127
|
+
id: task.id,
|
|
128
|
+
cost,
|
|
129
|
+
tokens: actualInputTokens + actualOutputTokens,
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
return result
|
|
133
|
+
} catch (error) {
|
|
134
|
+
if (error instanceof BudgetExceededError) {
|
|
135
|
+
console.log(` [BUDGET EXCEEDED] Task ${task.id}`)
|
|
136
|
+
return null
|
|
137
|
+
}
|
|
138
|
+
throw error
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Estimate cost for tokens
|
|
144
|
+
*/
|
|
145
|
+
private estimateCost(input: number, output: number, model?: string): number {
|
|
146
|
+
// Approximate pricing per million tokens
|
|
147
|
+
const pricing: Record<string, { input: number; output: number }> = {
|
|
148
|
+
'claude-sonnet-4-20250514': { input: 3, output: 15 },
|
|
149
|
+
'claude-3-5-haiku-latest': { input: 0.25, output: 1.25 },
|
|
150
|
+
'gpt-4o': { input: 2.5, output: 10 },
|
|
151
|
+
'gpt-4o-mini': { input: 0.15, output: 0.6 },
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const p = pricing[model || 'claude-sonnet-4-20250514'] || pricing['claude-sonnet-4-20250514']
|
|
155
|
+
return (input * p.input + output * p.output) / 1000000
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Generate cost report
|
|
160
|
+
*/
|
|
161
|
+
getReport(): CostReport {
|
|
162
|
+
const totalCost = this.tracker.getTotalCost()
|
|
163
|
+
const costByModel = this.tracker.getCostByModel()
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
totalCost,
|
|
167
|
+
costByModel,
|
|
168
|
+
tokensByModel: {}, // Would be tracked in full implementation
|
|
169
|
+
requestCount: this.requestHistory.length,
|
|
170
|
+
avgCostPerRequest:
|
|
171
|
+
this.requestHistory.length > 0 ? totalCost / this.requestHistory.length : 0,
|
|
172
|
+
budgetUtilization: (totalCost / this.maxBudget) * 100,
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Get alerts received
|
|
178
|
+
*/
|
|
179
|
+
getAlerts(): string[] {
|
|
180
|
+
return [...this.alertsReceived]
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ============================================================================
|
|
185
|
+
// Cost Optimization Strategies
|
|
186
|
+
// ============================================================================
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Strategy 1: Use cheaper models for simple tasks
|
|
190
|
+
*/
|
|
191
|
+
async function useModelTiering(
|
|
192
|
+
prompt: string,
|
|
193
|
+
complexity: 'simple' | 'medium' | 'complex'
|
|
194
|
+
): Promise<string> {
|
|
195
|
+
const modelMap = {
|
|
196
|
+
simple: 'claude-3-5-haiku-latest', // Cheapest
|
|
197
|
+
medium: 'gpt-4o-mini', // Mid-tier
|
|
198
|
+
complex: 'claude-sonnet-4-20250514', // Best quality
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
configure({ model: modelMap[complexity] })
|
|
202
|
+
return write`${prompt}`
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Strategy 2: Fallback chain for cost optimization
|
|
207
|
+
*/
|
|
208
|
+
async function useFallbackForCost(prompt: string): Promise<string> {
|
|
209
|
+
const chain = new FallbackChain([
|
|
210
|
+
{
|
|
211
|
+
name: 'haiku',
|
|
212
|
+
execute: async () => {
|
|
213
|
+
configure({ model: 'claude-3-5-haiku-latest' })
|
|
214
|
+
return write`${prompt}`
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
name: 'sonnet',
|
|
219
|
+
execute: async () => {
|
|
220
|
+
configure({ model: 'sonnet' })
|
|
221
|
+
return write`${prompt}`
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
])
|
|
225
|
+
|
|
226
|
+
return chain.execute()
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Strategy 3: Prompt compression
|
|
231
|
+
*/
|
|
232
|
+
function compressPrompt(prompt: string): string {
|
|
233
|
+
// Remove extra whitespace
|
|
234
|
+
let compressed = prompt.replace(/\s+/g, ' ').trim()
|
|
235
|
+
|
|
236
|
+
// Remove filler words (simplified)
|
|
237
|
+
const fillers = ['please', 'kindly', 'just', 'simply', 'basically']
|
|
238
|
+
for (const filler of fillers) {
|
|
239
|
+
compressed = compressed.replace(new RegExp(`\\b${filler}\\b`, 'gi'), '')
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return compressed.replace(/\s+/g, ' ').trim()
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Strategy 4: Cache similar requests
|
|
247
|
+
*/
|
|
248
|
+
class CostAwareCache {
|
|
249
|
+
private cache = new Map<string, { result: string; savedCost: number }>()
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Get cached result or null
|
|
253
|
+
*/
|
|
254
|
+
get(prompt: string): { result: string; savedCost: number } | null {
|
|
255
|
+
const key = this.hashPrompt(prompt)
|
|
256
|
+
return this.cache.get(key) || null
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Cache a result
|
|
261
|
+
*/
|
|
262
|
+
set(prompt: string, result: string, cost: number): void {
|
|
263
|
+
const key = this.hashPrompt(prompt)
|
|
264
|
+
this.cache.set(key, { result, savedCost: cost })
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Get total savings
|
|
269
|
+
*/
|
|
270
|
+
getTotalSavings(): number {
|
|
271
|
+
let total = 0
|
|
272
|
+
for (const entry of this.cache.values()) {
|
|
273
|
+
total += entry.savedCost
|
|
274
|
+
}
|
|
275
|
+
return total
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
private hashPrompt(prompt: string): string {
|
|
279
|
+
// Simple hash for demo
|
|
280
|
+
return prompt.toLowerCase().replace(/\s+/g, ' ').trim().substring(0, 100)
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// ============================================================================
|
|
285
|
+
// withBudget Usage
|
|
286
|
+
// ============================================================================
|
|
287
|
+
|
|
288
|
+
async function demonstrateWithBudget(): Promise<void> {
|
|
289
|
+
console.log('\n--- Using withBudget() wrapper ---\n')
|
|
290
|
+
|
|
291
|
+
try {
|
|
292
|
+
const result = await withBudget(
|
|
293
|
+
{
|
|
294
|
+
maxCost: 0.01, // $0.01 budget
|
|
295
|
+
maxTokens: 1000,
|
|
296
|
+
userId: 'user-123',
|
|
297
|
+
tenantId: 'tenant-456',
|
|
298
|
+
},
|
|
299
|
+
async (tracker, ctx) => {
|
|
300
|
+
console.log(` Request ID: ${ctx?.requestId}`)
|
|
301
|
+
console.log(` User: ${ctx?.userId}`)
|
|
302
|
+
console.log(` Budget: $0.01 / 1000 tokens`)
|
|
303
|
+
|
|
304
|
+
// Track usage
|
|
305
|
+
tracker.recordUsage({ inputTokens: 100, outputTokens: 50 })
|
|
306
|
+
|
|
307
|
+
const remaining = tracker.getRemainingBudget()
|
|
308
|
+
console.log(` Remaining: $${remaining.cost.toFixed(4)} / ${remaining.tokens} tokens`)
|
|
309
|
+
|
|
310
|
+
// Generate something
|
|
311
|
+
const response = await write`Say hello briefly`
|
|
312
|
+
tracker.recordUsage({ inputTokens: 50, outputTokens: 30 })
|
|
313
|
+
|
|
314
|
+
return response
|
|
315
|
+
}
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
console.log(` Result: "${result.substring(0, 50)}..."`)
|
|
319
|
+
} catch (error) {
|
|
320
|
+
if (error instanceof BudgetExceededError) {
|
|
321
|
+
console.log(' Budget exceeded!')
|
|
322
|
+
} else {
|
|
323
|
+
throw error
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// ============================================================================
|
|
329
|
+
// Per-User Budget Management
|
|
330
|
+
// ============================================================================
|
|
331
|
+
|
|
332
|
+
class UserBudgetManager {
|
|
333
|
+
private userBudgets = new Map<string, BudgetTracker>()
|
|
334
|
+
private defaultBudget: number
|
|
335
|
+
|
|
336
|
+
constructor(defaultBudget: number = 1.0) {
|
|
337
|
+
this.defaultBudget = defaultBudget
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Get or create budget tracker for user
|
|
342
|
+
*/
|
|
343
|
+
getTracker(userId: string): BudgetTracker {
|
|
344
|
+
if (!this.userBudgets.has(userId)) {
|
|
345
|
+
this.userBudgets.set(
|
|
346
|
+
userId,
|
|
347
|
+
new BudgetTracker({
|
|
348
|
+
maxCost: this.defaultBudget,
|
|
349
|
+
alertThresholds: [0.8, 1.0],
|
|
350
|
+
onAlert: (alert) => {
|
|
351
|
+
console.log(` [USER ${userId}] Budget alert: ${(alert.threshold * 100).toFixed(0)}%`)
|
|
352
|
+
},
|
|
353
|
+
})
|
|
354
|
+
)
|
|
355
|
+
}
|
|
356
|
+
return this.userBudgets.get(userId)!
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Check if user can make request
|
|
361
|
+
*/
|
|
362
|
+
canRequest(userId: string, estimatedTokens: number): boolean {
|
|
363
|
+
const tracker = this.getTracker(userId)
|
|
364
|
+
try {
|
|
365
|
+
tracker.checkBudget({ estimatedTokens })
|
|
366
|
+
return true
|
|
367
|
+
} catch {
|
|
368
|
+
return false
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Get all user budgets
|
|
374
|
+
*/
|
|
375
|
+
getAllUsage(): Record<string, { cost: number; remaining: number }> {
|
|
376
|
+
const usage: Record<string, { cost: number; remaining: number }> = {}
|
|
377
|
+
for (const [userId, tracker] of this.userBudgets) {
|
|
378
|
+
const remaining = tracker.getRemainingBudget()
|
|
379
|
+
usage[userId] = {
|
|
380
|
+
cost: tracker.getTotalCost(),
|
|
381
|
+
remaining: remaining.cost,
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return usage
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// ============================================================================
|
|
389
|
+
// Main Example
|
|
390
|
+
// ============================================================================
|
|
391
|
+
|
|
392
|
+
async function main() {
|
|
393
|
+
console.log('\n=== Budget-Constrained Generation Example ===\n')
|
|
394
|
+
|
|
395
|
+
// Configure the AI provider
|
|
396
|
+
configure({
|
|
397
|
+
model: 'sonnet',
|
|
398
|
+
provider: 'anthropic',
|
|
399
|
+
})
|
|
400
|
+
|
|
401
|
+
// Example 1: Basic budget tracking
|
|
402
|
+
console.log('=== Example 1: Basic Budget Tracking ===')
|
|
403
|
+
|
|
404
|
+
const generator = new BudgetAwareGenerator(0.1) // $0.10 budget
|
|
405
|
+
|
|
406
|
+
const tasks: GenerationTask[] = [
|
|
407
|
+
{ id: 'task-1', prompt: 'Write a haiku about coding', priority: 'high' },
|
|
408
|
+
{ id: 'task-2', prompt: 'Explain REST APIs in one sentence', priority: 'medium' },
|
|
409
|
+
{ id: 'task-3', prompt: 'List 3 programming languages', priority: 'low' },
|
|
410
|
+
{ id: 'task-4', prompt: 'What is TypeScript?', priority: 'medium' },
|
|
411
|
+
{ id: 'task-5', prompt: 'Define AI briefly', priority: 'low' },
|
|
412
|
+
]
|
|
413
|
+
|
|
414
|
+
console.log(`\nProcessing ${tasks.length} tasks with $0.10 budget:\n`)
|
|
415
|
+
|
|
416
|
+
for (const task of tasks) {
|
|
417
|
+
const result = await generator.generate(task)
|
|
418
|
+
if (result) {
|
|
419
|
+
console.log(` Result: "${result.substring(0, 40)}..."\n`)
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const report = generator.getReport()
|
|
424
|
+
console.log('\nCost Report:')
|
|
425
|
+
console.log(` Total Cost: $${report.totalCost.toFixed(4)}`)
|
|
426
|
+
console.log(` Requests: ${report.requestCount}`)
|
|
427
|
+
console.log(` Avg Cost/Request: $${report.avgCostPerRequest.toFixed(4)}`)
|
|
428
|
+
console.log(` Budget Utilization: ${report.budgetUtilization.toFixed(1)}%`)
|
|
429
|
+
|
|
430
|
+
const alerts = generator.getAlerts()
|
|
431
|
+
if (alerts.length > 0) {
|
|
432
|
+
console.log('\nAlerts:')
|
|
433
|
+
alerts.forEach((a) => console.log(` - ${a}`))
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Example 2: withBudget wrapper
|
|
437
|
+
console.log('\n=== Example 2: withBudget() Wrapper ===')
|
|
438
|
+
await demonstrateWithBudget()
|
|
439
|
+
|
|
440
|
+
// Example 3: Cost optimization strategies
|
|
441
|
+
console.log('\n=== Example 3: Cost Optimization Strategies ===')
|
|
442
|
+
|
|
443
|
+
// Prompt compression
|
|
444
|
+
const longPrompt =
|
|
445
|
+
'Please kindly write me a haiku poem about coding. Just make it simple and basically about programming.'
|
|
446
|
+
const compressed = compressPrompt(longPrompt)
|
|
447
|
+
console.log(`\nPrompt compression:`)
|
|
448
|
+
console.log(` Original: "${longPrompt}" (${longPrompt.length} chars)`)
|
|
449
|
+
console.log(` Compressed: "${compressed}" (${compressed.length} chars)`)
|
|
450
|
+
console.log(
|
|
451
|
+
` Savings: ${(((longPrompt.length - compressed.length) / longPrompt.length) * 100).toFixed(
|
|
452
|
+
0
|
|
453
|
+
)}%`
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
// Example 4: Per-user budgets
|
|
457
|
+
console.log('\n=== Example 4: Per-User Budget Management ===')
|
|
458
|
+
|
|
459
|
+
const userManager = new UserBudgetManager(0.05) // $0.05 per user
|
|
460
|
+
|
|
461
|
+
// Simulate user requests
|
|
462
|
+
const users = ['user-1', 'user-2', 'user-3']
|
|
463
|
+
for (const userId of users) {
|
|
464
|
+
const tracker = userManager.getTracker(userId)
|
|
465
|
+
tracker.recordUsage({
|
|
466
|
+
inputTokens: Math.floor(Math.random() * 500) + 100,
|
|
467
|
+
outputTokens: Math.floor(Math.random() * 200) + 50,
|
|
468
|
+
model: 'claude-sonnet-4-20250514',
|
|
469
|
+
})
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const allUsage = userManager.getAllUsage()
|
|
473
|
+
console.log('\nPer-User Budget Status:')
|
|
474
|
+
for (const [userId, usage] of Object.entries(allUsage)) {
|
|
475
|
+
console.log(
|
|
476
|
+
` ${userId}: Spent $${usage.cost.toFixed(4)}, Remaining $${usage.remaining.toFixed(4)}`
|
|
477
|
+
)
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Example 5: Token estimation
|
|
481
|
+
console.log('\n=== Example 5: Token Estimation ===')
|
|
482
|
+
|
|
483
|
+
const counter = new TokenCounter()
|
|
484
|
+
const sampleText = 'The quick brown fox jumps over the lazy dog.'
|
|
485
|
+
const estimated = counter.estimateTokens(sampleText)
|
|
486
|
+
|
|
487
|
+
console.log(`Text: "${sampleText}"`)
|
|
488
|
+
console.log(`Estimated tokens: ${estimated}`)
|
|
489
|
+
console.log(`Cost estimate (Sonnet): $${((estimated * 3) / 1000000).toFixed(6)} input`)
|
|
490
|
+
|
|
491
|
+
// Summary
|
|
492
|
+
console.log('\n=== Budget Management Summary ===')
|
|
493
|
+
console.log(`
|
|
494
|
+
Key strategies for cost control:
|
|
495
|
+
|
|
496
|
+
1. Set hard limits with BudgetTracker
|
|
497
|
+
- maxCost: Maximum spend in USD
|
|
498
|
+
- maxTokens: Maximum token usage
|
|
499
|
+
- alertThresholds: Early warnings
|
|
500
|
+
|
|
501
|
+
2. Use withBudget() for scoped tracking
|
|
502
|
+
- Automatic enforcement
|
|
503
|
+
- Request context tracking
|
|
504
|
+
- Nested budget support
|
|
505
|
+
|
|
506
|
+
3. Optimize costs with:
|
|
507
|
+
- Model tiering (Haiku < GPT-4o-mini < Sonnet)
|
|
508
|
+
- Prompt compression
|
|
509
|
+
- Caching similar requests
|
|
510
|
+
- Fallback chains
|
|
511
|
+
|
|
512
|
+
4. Monitor per-user/tenant budgets
|
|
513
|
+
- Fair usage enforcement
|
|
514
|
+
- Quota management
|
|
515
|
+
- Usage reporting
|
|
516
|
+
`)
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
main()
|
|
520
|
+
.then(() => {
|
|
521
|
+
console.log('\n=== Example Complete ===\n')
|
|
522
|
+
process.exit(0)
|
|
523
|
+
})
|
|
524
|
+
.catch((error) => {
|
|
525
|
+
console.error('\nError:', error.message)
|
|
526
|
+
process.exit(1)
|
|
527
|
+
})
|