ai-functions 2.1.1 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -4
- package/CHANGELOG.md +68 -1
- package/README.md +397 -157
- package/dist/ai-promise.d.ts +50 -3
- package/dist/ai-promise.d.ts.map +1 -1
- package/dist/ai-promise.js +410 -51
- package/dist/ai-promise.js.map +1 -1
- package/dist/ai-schemas.d.ts +56 -0
- package/dist/ai-schemas.d.ts.map +1 -0
- package/dist/ai-schemas.js +53 -0
- package/dist/ai-schemas.js.map +1 -0
- package/dist/ai.d.ts +16 -242
- package/dist/ai.d.ts.map +1 -1
- package/dist/ai.js +54 -837
- package/dist/ai.js.map +1 -1
- package/dist/batch/anthropic.d.ts +6 -4
- package/dist/batch/anthropic.d.ts.map +1 -1
- package/dist/batch/anthropic.js +83 -145
- package/dist/batch/anthropic.js.map +1 -1
- package/dist/batch/bedrock.d.ts +8 -30
- package/dist/batch/bedrock.d.ts.map +1 -1
- package/dist/batch/bedrock.js +155 -338
- package/dist/batch/bedrock.js.map +1 -1
- package/dist/batch/cloudflare.d.ts +8 -20
- package/dist/batch/cloudflare.d.ts.map +1 -1
- package/dist/batch/cloudflare.js +68 -189
- package/dist/batch/cloudflare.js.map +1 -1
- package/dist/batch/google.d.ts +6 -20
- package/dist/batch/google.d.ts.map +1 -1
- package/dist/batch/google.js +70 -238
- package/dist/batch/google.js.map +1 -1
- package/dist/batch/index.d.ts +4 -1
- package/dist/batch/index.d.ts.map +1 -1
- package/dist/batch/index.js +4 -1
- package/dist/batch/index.js.map +1 -1
- package/dist/batch/memory.d.ts +1 -1
- package/dist/batch/memory.d.ts.map +1 -1
- package/dist/batch/memory.js +14 -10
- package/dist/batch/memory.js.map +1 -1
- package/dist/batch/openai.d.ts +11 -14
- package/dist/batch/openai.d.ts.map +1 -1
- package/dist/batch/openai.js +52 -156
- package/dist/batch/openai.js.map +1 -1
- package/dist/batch/provider.d.ts +111 -0
- package/dist/batch/provider.d.ts.map +1 -0
- package/dist/batch/provider.js +233 -0
- package/dist/batch/provider.js.map +1 -0
- package/dist/batch-map.d.ts.map +1 -1
- package/dist/batch-map.js +23 -17
- package/dist/batch-map.js.map +1 -1
- package/dist/batch-queue.d.ts +65 -0
- package/dist/batch-queue.d.ts.map +1 -1
- package/dist/batch-queue.js +169 -14
- package/dist/batch-queue.js.map +1 -1
- package/dist/budget.d.ts +272 -0
- package/dist/budget.d.ts.map +1 -0
- package/dist/budget.js +513 -0
- package/dist/budget.js.map +1 -0
- package/dist/cache.d.ts +295 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +433 -0
- package/dist/cache.js.map +1 -0
- package/dist/context.d.ts +42 -8
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +64 -62
- package/dist/context.js.map +1 -1
- package/dist/digital-objects-registry.d.ts +229 -0
- package/dist/digital-objects-registry.d.ts.map +1 -0
- package/dist/digital-objects-registry.js +617 -0
- package/dist/digital-objects-registry.js.map +1 -0
- package/dist/embeddings.d.ts +2 -2
- package/dist/embeddings.d.ts.map +1 -1
- package/dist/errors.d.ts +22 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +35 -0
- package/dist/errors.js.map +1 -0
- package/dist/eval/runner.d.ts +10 -1
- package/dist/eval/runner.d.ts.map +1 -1
- package/dist/eval/runner.js +41 -35
- package/dist/eval/runner.js.map +1 -1
- package/dist/eval-log/in-memory.d.ts +34 -0
- package/dist/eval-log/in-memory.d.ts.map +1 -0
- package/dist/eval-log/in-memory.js +84 -0
- package/dist/eval-log/in-memory.js.map +1 -0
- package/dist/eval-log/index.d.ts +29 -0
- package/dist/eval-log/index.d.ts.map +1 -0
- package/dist/eval-log/index.js +39 -0
- package/dist/eval-log/index.js.map +1 -0
- package/dist/eval-log/types.d.ts +101 -0
- package/dist/eval-log/types.d.ts.map +1 -0
- package/dist/eval-log/types.js +16 -0
- package/dist/eval-log/types.js.map +1 -0
- package/dist/function-registry.d.ts +116 -0
- package/dist/function-registry.d.ts.map +1 -0
- package/dist/function-registry.js +546 -0
- package/dist/function-registry.js.map +1 -0
- package/dist/generate.d.ts +9 -3
- package/dist/generate.d.ts.map +1 -1
- package/dist/generate.js +18 -22
- package/dist/generate.js.map +1 -1
- package/dist/index.d.ts +35 -20
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +89 -42
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +118 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +187 -0
- package/dist/logger.js.map +1 -0
- package/dist/middleware/budget.d.ts +84 -0
- package/dist/middleware/budget.d.ts.map +1 -0
- package/dist/middleware/budget.js +110 -0
- package/dist/middleware/budget.js.map +1 -0
- package/dist/middleware/cache.d.ts +103 -0
- package/dist/middleware/cache.d.ts.map +1 -0
- package/dist/middleware/cache.js +228 -0
- package/dist/middleware/cache.js.map +1 -0
- package/dist/middleware/embed-cache.d.ts +99 -0
- package/dist/middleware/embed-cache.d.ts.map +1 -0
- package/dist/middleware/embed-cache.js +128 -0
- package/dist/middleware/embed-cache.js.map +1 -0
- package/dist/middleware/index.d.ts +11 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +11 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/trace.d.ts +103 -0
- package/dist/middleware/trace.d.ts.map +1 -0
- package/dist/middleware/trace.js +176 -0
- package/dist/middleware/trace.js.map +1 -0
- package/dist/primitives.d.ts +120 -1
- package/dist/primitives.d.ts.map +1 -1
- package/dist/primitives.js +398 -26
- package/dist/primitives.js.map +1 -1
- package/dist/retry.d.ts +368 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +646 -0
- package/dist/retry.js.map +1 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +2 -10
- package/dist/schema.js.map +1 -1
- package/dist/telemetry.d.ts +128 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +285 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/template.d.ts.map +1 -1
- package/dist/template.js +6 -1
- package/dist/template.js.map +1 -1
- package/dist/tool-orchestration.d.ts +453 -0
- package/dist/tool-orchestration.d.ts.map +1 -0
- package/dist/tool-orchestration.js +763 -0
- package/dist/tool-orchestration.js.map +1 -0
- package/dist/type-guards.d.ts +28 -0
- package/dist/type-guards.d.ts.map +1 -0
- package/dist/type-guards.js +29 -0
- package/dist/type-guards.js.map +1 -0
- package/dist/types.d.ts +135 -17
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +36 -1
- package/dist/types.js.map +1 -1
- package/dist/wrap-for-v3.d.ts +80 -0
- package/dist/wrap-for-v3.d.ts.map +1 -0
- package/dist/wrap-for-v3.js +89 -0
- package/dist/wrap-for-v3.js.map +1 -0
- package/examples/00-quickstart.ts +232 -0
- package/examples/01-rag-chatbot.ts +212 -0
- package/examples/02-multi-agent-research.ts +290 -0
- package/examples/03-email-classification.ts +379 -0
- package/examples/04-content-moderation.ts +400 -0
- package/examples/05-document-extraction.ts +455 -0
- package/examples/06-streaming-chat-nextjs.ts +437 -0
- package/examples/07-cloudflare-worker.ts +483 -0
- package/examples/08-batch-processing.ts +491 -0
- package/examples/09-budget-constrained.ts +527 -0
- package/examples/10-tool-orchestration.ts +565 -0
- package/examples/11-retry-resilience.ts +403 -0
- package/examples/12-caching-strategies.ts +422 -0
- package/examples/README.md +145 -0
- package/package.json +10 -6
- package/src/ai-promise.ts +528 -99
- package/src/ai-schemas.ts +122 -0
- package/src/ai.ts +69 -1153
- package/src/batch/anthropic.ts +96 -161
- package/src/batch/bedrock.ts +203 -454
- package/src/batch/cloudflare.ts +99 -282
- package/src/batch/google.ts +91 -297
- package/src/batch/index.ts +4 -1
- package/src/batch/memory.ts +15 -10
- package/src/batch/openai.ts +65 -193
- package/src/batch/provider.ts +336 -0
- package/src/batch-map.ts +29 -24
- package/src/batch-queue.ts +200 -11
- package/src/budget.ts +740 -0
- package/src/cache.ts +681 -0
- package/src/context.ts +122 -76
- package/src/digital-objects-registry.ts +750 -0
- package/src/errors.ts +37 -0
- package/src/eval/runner.ts +63 -38
- package/src/eval-log/in-memory.ts +90 -0
- package/src/eval-log/index.ts +46 -0
- package/src/eval-log/types.ts +110 -0
- package/src/function-registry.ts +671 -0
- package/src/generate.ts +33 -33
- package/src/index.ts +325 -49
- package/src/logger.ts +232 -0
- package/src/middleware/budget.ts +171 -0
- package/src/middleware/cache.ts +299 -0
- package/src/middleware/embed-cache.ts +195 -0
- package/src/middleware/index.ts +23 -0
- package/src/middleware/trace.ts +248 -0
- package/src/primitives.ts +589 -62
- package/src/retry.ts +902 -0
- package/src/schema.ts +8 -17
- package/src/telemetry.ts +403 -0
- package/src/template.ts +8 -4
- package/src/tool-orchestration.ts +1173 -0
- package/src/type-guards.ts +31 -0
- package/src/types.ts +164 -25
- package/src/wrap-for-v3.ts +105 -0
- package/test/ai-promise.test.ts +1080 -0
- package/test/ai-proxy.test.ts +1 -1
- package/test/backward-compat.test.ts +147 -0
- package/test/batch-autosubmit-errors.test.ts +610 -0
- package/test/batch-blog-posts.test.ts +87 -129
- package/test/budget-tracking.test.ts +800 -0
- package/test/cache.test.ts +712 -0
- package/test/context-isolation.test.ts +687 -0
- package/test/core-functions.test.ts +183 -579
- package/test/decide.test.ts +154 -322
- package/test/define.test.ts +211 -8
- package/test/digital-objects-registry.test.ts +760 -0
- package/test/embedding-cache-middleware.test.ts +140 -0
- package/test/evals/deterministic.eval.test.ts +376 -0
- package/test/generate-core.test.ts +140 -229
- package/test/implicit-batch.test.ts +22 -65
- package/test/json-parse-error-handling.test.ts +463 -0
- package/test/retry-policy-integration.test.ts +117 -0
- package/test/retry.test.ts +1016 -0
- package/test/schema.test.ts +55 -19
- package/test/streaming.test.ts +316 -0
- package/test/template.test.ts +1164 -0
- package/test/tool-orchestration.test.ts +1040 -0
- package/test/wrap-for-v3.test.ts +612 -0
- package/vitest.config.js +6 -0
- package/vitest.config.ts +20 -0
- package/dist/rpc/auth.d.ts +0 -69
- package/dist/rpc/auth.d.ts.map +0 -1
- package/dist/rpc/auth.js +0 -136
- package/dist/rpc/auth.js.map +0 -1
- package/dist/rpc/client.d.ts +0 -62
- package/dist/rpc/client.d.ts.map +0 -1
- package/dist/rpc/client.js +0 -103
- package/dist/rpc/client.js.map +0 -1
- package/dist/rpc/deferred.d.ts +0 -60
- package/dist/rpc/deferred.d.ts.map +0 -1
- package/dist/rpc/deferred.js +0 -96
- package/dist/rpc/deferred.js.map +0 -1
- package/dist/rpc/index.d.ts +0 -22
- package/dist/rpc/index.d.ts.map +0 -1
- package/dist/rpc/index.js +0 -38
- package/dist/rpc/index.js.map +0 -1
- package/dist/rpc/local.d.ts +0 -42
- package/dist/rpc/local.d.ts.map +0 -1
- package/dist/rpc/local.js +0 -50
- package/dist/rpc/local.js.map +0 -1
- package/dist/rpc/server.d.ts +0 -165
- package/dist/rpc/server.d.ts.map +0 -1
- package/dist/rpc/server.js +0 -405
- package/dist/rpc/server.js.map +0 -1
- package/dist/rpc/session.d.ts +0 -32
- package/dist/rpc/session.d.ts.map +0 -1
- package/dist/rpc/session.js +0 -43
- package/dist/rpc/session.js.map +0 -1
- package/dist/rpc/transport.d.ts +0 -306
- package/dist/rpc/transport.d.ts.map +0 -1
- package/dist/rpc/transport.js +0 -731
- package/dist/rpc/transport.js.map +0 -1
- package/src/batch/anthropic.js +0 -256
- package/src/batch/bedrock.js +0 -584
- package/src/batch/cloudflare.js +0 -287
- package/src/batch/google.js +0 -359
- package/src/batch/index.js +0 -30
- package/src/batch/memory.js +0 -187
- package/src/batch/openai.js +0 -402
- package/src/eval/index.js +0 -7
- package/src/eval/models.js +0 -119
- package/src/eval/runner.js +0 -147
- package/test/schema.test.js +0 -96
package/src/batch/google.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Google GenAI (Gemini) Adapter
|
|
3
3
|
*
|
|
4
|
-
* Implements processing using Google's Generative AI API (Gemini models).
|
|
5
4
|
* Google doesn't have a native batch API like OpenAI/Anthropic, so this
|
|
6
|
-
*
|
|
5
|
+
* adapter fakes batch processing via concurrent direct calls and tracks the
|
|
6
|
+
* job state locally (see `LocalJobStore` in `./provider.js`).
|
|
7
|
+
*
|
|
8
|
+
* For true async batch processing, consider Google Cloud Batch with Vertex AI.
|
|
7
9
|
*
|
|
8
10
|
* @see https://ai.google.dev/gemini-api/docs
|
|
9
11
|
*
|
|
@@ -11,20 +13,22 @@
|
|
|
11
13
|
*/
|
|
12
14
|
|
|
13
15
|
import {
|
|
16
|
+
LocalJobStore,
|
|
17
|
+
processConcurrently,
|
|
14
18
|
registerBatchAdapter,
|
|
15
19
|
registerFlexAdapter,
|
|
20
|
+
tryParseJson,
|
|
16
21
|
type BatchAdapter,
|
|
17
|
-
type FlexAdapter,
|
|
18
22
|
type BatchItem,
|
|
19
23
|
type BatchJob,
|
|
20
24
|
type BatchQueueOptions,
|
|
21
25
|
type BatchResult,
|
|
22
26
|
type BatchSubmitResult,
|
|
23
|
-
type
|
|
24
|
-
} from '
|
|
27
|
+
type FlexAdapter,
|
|
28
|
+
} from './provider.js'
|
|
25
29
|
|
|
26
30
|
// ============================================================================
|
|
27
|
-
//
|
|
31
|
+
// Provider-specific types
|
|
28
32
|
// ============================================================================
|
|
29
33
|
|
|
30
34
|
interface GeminiMessage {
|
|
@@ -39,10 +43,7 @@ interface GeminiResponse {
|
|
|
39
43
|
role: string
|
|
40
44
|
}
|
|
41
45
|
finishReason: string
|
|
42
|
-
safetyRatings?: Array<{
|
|
43
|
-
category: string
|
|
44
|
-
probability: string
|
|
45
|
-
}>
|
|
46
|
+
safetyRatings?: Array<{ category: string; probability: string }>
|
|
46
47
|
}>
|
|
47
48
|
usageMetadata?: {
|
|
48
49
|
promptTokenCount: number
|
|
@@ -52,7 +53,7 @@ interface GeminiResponse {
|
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
// ============================================================================
|
|
55
|
-
// Google GenAI
|
|
56
|
+
// Google GenAI client configuration
|
|
56
57
|
// ============================================================================
|
|
57
58
|
|
|
58
59
|
let googleApiKey: string | undefined
|
|
@@ -62,9 +63,7 @@ let googleBaseUrl = 'https://generativelanguage.googleapis.com/v1beta'
|
|
|
62
63
|
let gatewayUrl: string | undefined
|
|
63
64
|
let gatewayToken: string | undefined
|
|
64
65
|
|
|
65
|
-
/**
|
|
66
|
-
* Configure the Google GenAI client
|
|
67
|
-
*/
|
|
66
|
+
/** Configure the Google GenAI client. */
|
|
68
67
|
export function configureGoogleGenAI(options: {
|
|
69
68
|
apiKey?: string
|
|
70
69
|
baseUrl?: string
|
|
@@ -79,12 +78,17 @@ export function configureGoogleGenAI(options: {
|
|
|
79
78
|
if (options.gatewayToken) gatewayToken = options.gatewayToken
|
|
80
79
|
}
|
|
81
80
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
81
|
+
interface GoogleConfig {
|
|
82
|
+
apiKey: string
|
|
83
|
+
baseUrl: string
|
|
84
|
+
gatewayUrl?: string
|
|
85
|
+
gatewayToken?: string
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function getConfig(): GoogleConfig {
|
|
89
|
+
const gwUrl = gatewayUrl || process.env['AI_GATEWAY_URL']
|
|
90
|
+
const gwToken = gatewayToken || process.env['AI_GATEWAY_TOKEN']
|
|
86
91
|
|
|
87
|
-
// If using gateway, we don't need a direct API key
|
|
88
92
|
if (gwUrl && gwToken) {
|
|
89
93
|
return {
|
|
90
94
|
apiKey: '',
|
|
@@ -94,268 +98,109 @@ function getConfig(): { apiKey: string; baseUrl: string; gatewayUrl?: string; ga
|
|
|
94
98
|
}
|
|
95
99
|
}
|
|
96
100
|
|
|
97
|
-
const key = googleApiKey || process.env
|
|
101
|
+
const key = googleApiKey || process.env['GOOGLE_API_KEY'] || process.env['GEMINI_API_KEY']
|
|
98
102
|
if (!key) {
|
|
99
103
|
throw new Error(
|
|
100
104
|
'Google API key not configured. Set GOOGLE_API_KEY or GEMINI_API_KEY, or use AI_GATEWAY_URL and AI_GATEWAY_TOKEN'
|
|
101
105
|
)
|
|
102
106
|
}
|
|
103
|
-
return { apiKey: key, baseUrl: googleBaseUrl
|
|
107
|
+
return { apiKey: key, baseUrl: googleBaseUrl }
|
|
104
108
|
}
|
|
105
109
|
|
|
106
110
|
// ============================================================================
|
|
107
|
-
//
|
|
111
|
+
// Local job tracking
|
|
108
112
|
// ============================================================================
|
|
109
113
|
|
|
110
|
-
const
|
|
111
|
-
string,
|
|
112
|
-
{
|
|
113
|
-
items: BatchItem[]
|
|
114
|
-
options: BatchQueueOptions
|
|
115
|
-
results: BatchResult[]
|
|
116
|
-
status: BatchStatus
|
|
117
|
-
createdAt: Date
|
|
118
|
-
completedAt?: Date
|
|
119
|
-
}
|
|
120
|
-
>()
|
|
121
|
-
|
|
122
|
-
let jobCounter = 0
|
|
114
|
+
const jobs = new LocalJobStore('google_batch')
|
|
123
115
|
|
|
124
116
|
// ============================================================================
|
|
125
|
-
// Google GenAI
|
|
117
|
+
// Google GenAI batch adapter (BatchProvider port)
|
|
126
118
|
// ============================================================================
|
|
127
119
|
|
|
128
|
-
/**
|
|
129
|
-
* Google GenAI batch adapter
|
|
130
|
-
*
|
|
131
|
-
* Note: Google doesn't have a native batch API like OpenAI/Anthropic.
|
|
132
|
-
* This adapter implements batch processing via concurrent requests.
|
|
133
|
-
* For true async batch processing, consider using Google Cloud Batch
|
|
134
|
-
* with Vertex AI.
|
|
135
|
-
*/
|
|
136
120
|
const googleAdapter: BatchAdapter = {
|
|
137
121
|
async submit(items: BatchItem[], options: BatchQueueOptions): Promise<BatchSubmitResult> {
|
|
138
|
-
const jobId = `google_batch_${++jobCounter}_${Date.now()}`
|
|
139
122
|
const model = options.model || 'gemini-2.0-flash'
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
123
|
+
const { id, state } = jobs.create(items, options)
|
|
124
|
+
|
|
125
|
+
// Drive the job state machine in the background; `waitForCompletion`
|
|
126
|
+
// will poll the in-memory state.
|
|
127
|
+
const completion = (async () => {
|
|
128
|
+
state.status = 'in_progress'
|
|
129
|
+
const results = await processConcurrently(items, (item) => processGoogleItem(item, model), {
|
|
130
|
+
concurrency: 10,
|
|
131
|
+
onWaveComplete: (partial) => {
|
|
132
|
+
state.results = partial
|
|
133
|
+
},
|
|
134
|
+
})
|
|
135
|
+
state.results = results
|
|
136
|
+
state.status = results.every((r) => r.status === 'completed') ? 'completed' : 'failed'
|
|
137
|
+
state.completedAt = new Date()
|
|
138
|
+
return results
|
|
139
|
+
})()
|
|
152
140
|
|
|
153
141
|
const job: BatchJob = {
|
|
154
|
-
id
|
|
142
|
+
id,
|
|
155
143
|
provider: 'google',
|
|
156
144
|
status: 'pending',
|
|
157
145
|
totalItems: items.length,
|
|
158
146
|
completedItems: 0,
|
|
159
147
|
failedItems: 0,
|
|
160
|
-
createdAt:
|
|
161
|
-
webhookUrl: options.webhookUrl,
|
|
148
|
+
createdAt: state.createdAt,
|
|
149
|
+
...(options.webhookUrl !== undefined && { webhookUrl: options.webhookUrl }),
|
|
162
150
|
}
|
|
163
151
|
|
|
164
152
|
return { job, completion }
|
|
165
153
|
},
|
|
166
154
|
|
|
167
155
|
async getStatus(batchId: string): Promise<BatchJob> {
|
|
168
|
-
|
|
169
|
-
if (!job) {
|
|
170
|
-
throw new Error(`Batch not found: ${batchId}`)
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const completedItems = job.results.filter((r) => r.status === 'completed').length
|
|
174
|
-
const failedItems = job.results.filter((r) => r.status === 'failed').length
|
|
175
|
-
|
|
176
|
-
return {
|
|
177
|
-
id: batchId,
|
|
178
|
-
provider: 'google',
|
|
179
|
-
status: job.status,
|
|
180
|
-
totalItems: job.items.length,
|
|
181
|
-
completedItems,
|
|
182
|
-
failedItems,
|
|
183
|
-
createdAt: job.createdAt,
|
|
184
|
-
completedAt: job.completedAt,
|
|
185
|
-
}
|
|
156
|
+
return jobs.snapshot(batchId, 'google')
|
|
186
157
|
},
|
|
187
158
|
|
|
188
159
|
async cancel(batchId: string): Promise<void> {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
job.status = 'cancelled'
|
|
160
|
+
if (jobs.has(batchId)) {
|
|
161
|
+
jobs.get(batchId).status = 'cancelled'
|
|
192
162
|
}
|
|
193
163
|
},
|
|
194
164
|
|
|
195
165
|
async getResults(batchId: string): Promise<BatchResult[]> {
|
|
196
|
-
|
|
197
|
-
if (!job) {
|
|
198
|
-
throw new Error(`Batch not found: ${batchId}`)
|
|
199
|
-
}
|
|
200
|
-
return job.results
|
|
166
|
+
return jobs.get(batchId).results
|
|
201
167
|
},
|
|
202
168
|
|
|
203
169
|
async waitForCompletion(batchId: string, pollInterval = 1000): Promise<BatchResult[]> {
|
|
204
|
-
|
|
205
|
-
if (!job) {
|
|
206
|
-
throw new Error(`Batch not found: ${batchId}`)
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
while (job.status !== 'completed' && job.status !== 'failed' && job.status !== 'cancelled') {
|
|
210
|
-
await new Promise((resolve) => setTimeout(resolve, pollInterval))
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
return job.results
|
|
170
|
+
return jobs.waitForCompletion(batchId, pollInterval)
|
|
214
171
|
},
|
|
215
172
|
}
|
|
216
173
|
|
|
217
174
|
// ============================================================================
|
|
218
|
-
// Google GenAI
|
|
175
|
+
// Google GenAI flex adapter (FlexAdapter port)
|
|
219
176
|
// ============================================================================
|
|
220
177
|
|
|
221
|
-
/**
|
|
222
|
-
* Google GenAI Flex Adapter
|
|
223
|
-
*
|
|
224
|
-
* Implements concurrent processing for medium-sized batches.
|
|
225
|
-
* Uses the Gemini API for fast turnaround.
|
|
226
|
-
*/
|
|
227
178
|
const googleFlexAdapter: FlexAdapter = {
|
|
228
179
|
async submitFlex(items: BatchItem[], options: { model?: string }): Promise<BatchResult[]> {
|
|
229
180
|
const model = options.model || 'gemini-2.0-flash'
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
// Process items concurrently
|
|
235
|
-
for (let i = 0; i < items.length; i += CONCURRENCY) {
|
|
236
|
-
const batch = items.slice(i, i + CONCURRENCY)
|
|
237
|
-
|
|
238
|
-
const batchResults = await Promise.all(
|
|
239
|
-
batch.map(async (item) => {
|
|
240
|
-
try {
|
|
241
|
-
return await processGoogleItem(item, model)
|
|
242
|
-
} catch (error) {
|
|
243
|
-
return {
|
|
244
|
-
id: item.id,
|
|
245
|
-
customId: item.id,
|
|
246
|
-
status: 'failed' as const,
|
|
247
|
-
error: error instanceof Error ? error.message : 'Unknown error',
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
})
|
|
251
|
-
)
|
|
252
|
-
|
|
253
|
-
results.push(...batchResults)
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
return results
|
|
181
|
+
return processConcurrently(items, (item) => processGoogleItem(item, model), {
|
|
182
|
+
concurrency: 10,
|
|
183
|
+
})
|
|
257
184
|
},
|
|
258
185
|
}
|
|
259
186
|
|
|
260
187
|
// ============================================================================
|
|
261
|
-
//
|
|
188
|
+
// Per-item processing
|
|
262
189
|
// ============================================================================
|
|
263
190
|
|
|
264
|
-
async function processGoogleRequestsConcurrently(
|
|
265
|
-
jobId: string,
|
|
266
|
-
items: BatchItem[],
|
|
267
|
-
model: string,
|
|
268
|
-
options: BatchQueueOptions
|
|
269
|
-
): Promise<BatchResult[]> {
|
|
270
|
-
const job = pendingJobs.get(jobId)
|
|
271
|
-
if (!job) {
|
|
272
|
-
throw new Error(`Job not found: ${jobId}`)
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
job.status = 'in_progress'
|
|
276
|
-
|
|
277
|
-
const CONCURRENCY = 10
|
|
278
|
-
const results: BatchResult[] = []
|
|
279
|
-
|
|
280
|
-
for (let i = 0; i < items.length; i += CONCURRENCY) {
|
|
281
|
-
const batch = items.slice(i, i + CONCURRENCY)
|
|
282
|
-
|
|
283
|
-
const batchResults = await Promise.all(
|
|
284
|
-
batch.map(async (item) => {
|
|
285
|
-
try {
|
|
286
|
-
return await processGoogleItem(item, model)
|
|
287
|
-
} catch (error) {
|
|
288
|
-
return {
|
|
289
|
-
id: item.id,
|
|
290
|
-
customId: item.id,
|
|
291
|
-
status: 'failed' as const,
|
|
292
|
-
error: error instanceof Error ? error.message : 'Unknown error',
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
})
|
|
296
|
-
)
|
|
297
|
-
|
|
298
|
-
results.push(...batchResults)
|
|
299
|
-
job.results = results
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
job.status = results.every((r) => r.status === 'completed') ? 'completed' : 'failed'
|
|
303
|
-
job.completedAt = new Date()
|
|
304
|
-
|
|
305
|
-
return results
|
|
306
|
-
}
|
|
307
|
-
|
|
308
191
|
async function processGoogleItem(item: BatchItem, model: string): Promise<BatchResult> {
|
|
309
192
|
const config = getConfig()
|
|
310
|
-
|
|
311
|
-
// Check if using AI Gateway
|
|
312
193
|
if (config.gatewayUrl && config.gatewayToken) {
|
|
313
194
|
return processGoogleItemViaGateway(item, config, model)
|
|
314
195
|
}
|
|
315
196
|
|
|
316
|
-
// Build the model name (add models/ prefix if not present)
|
|
317
197
|
const modelName = model.startsWith('models/') ? model : `models/${model}`
|
|
318
|
-
|
|
319
198
|
const url = `${config.baseUrl}/${modelName}:generateContent?key=${config.apiKey}`
|
|
320
199
|
|
|
321
|
-
// Build messages
|
|
322
|
-
const contents: GeminiMessage[] = []
|
|
323
|
-
|
|
324
|
-
// Add system instruction as a user message if provided (Gemini handles this differently)
|
|
325
|
-
if (item.options?.system) {
|
|
326
|
-
contents.push({
|
|
327
|
-
role: 'user',
|
|
328
|
-
parts: [{ text: `System instruction: ${item.options.system}\n\nUser request: ${item.prompt}` }],
|
|
329
|
-
})
|
|
330
|
-
} else {
|
|
331
|
-
contents.push({
|
|
332
|
-
role: 'user',
|
|
333
|
-
parts: [{ text: item.prompt }],
|
|
334
|
-
})
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
const body: Record<string, unknown> = {
|
|
338
|
-
contents,
|
|
339
|
-
generationConfig: {
|
|
340
|
-
maxOutputTokens: item.options?.maxTokens || 8192,
|
|
341
|
-
temperature: item.options?.temperature,
|
|
342
|
-
},
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
// Add JSON mode if schema is provided
|
|
346
|
-
if (item.schema) {
|
|
347
|
-
body.generationConfig = {
|
|
348
|
-
...(body.generationConfig as object),
|
|
349
|
-
responseMimeType: 'application/json',
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
200
|
const response = await fetch(url, {
|
|
354
201
|
method: 'POST',
|
|
355
|
-
headers: {
|
|
356
|
-
|
|
357
|
-
},
|
|
358
|
-
body: JSON.stringify(body),
|
|
202
|
+
headers: { 'Content-Type': 'application/json' },
|
|
203
|
+
body: JSON.stringify(buildGeminiBody(item)),
|
|
359
204
|
})
|
|
360
205
|
|
|
361
206
|
if (!response.ok) {
|
|
@@ -363,88 +208,28 @@ async function processGoogleItem(item: BatchItem, model: string): Promise<BatchR
|
|
|
363
208
|
throw new Error(`Google GenAI API error: ${response.status} ${error}`)
|
|
364
209
|
}
|
|
365
210
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
// Extract content
|
|
369
|
-
const content = data.candidates?.[0]?.content?.parts?.[0]?.text
|
|
370
|
-
|
|
371
|
-
let result: unknown = content
|
|
372
|
-
|
|
373
|
-
// Try to parse JSON if schema was provided or content looks like JSON
|
|
374
|
-
if (content && (item.schema || content.trim().startsWith('{') || content.trim().startsWith('['))) {
|
|
375
|
-
try {
|
|
376
|
-
result = JSON.parse(content)
|
|
377
|
-
} catch {
|
|
378
|
-
// Keep as string
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
return {
|
|
383
|
-
id: item.id,
|
|
384
|
-
customId: item.id,
|
|
385
|
-
status: 'completed',
|
|
386
|
-
result,
|
|
387
|
-
usage: data.usageMetadata
|
|
388
|
-
? {
|
|
389
|
-
promptTokens: data.usageMetadata.promptTokenCount,
|
|
390
|
-
completionTokens: data.usageMetadata.candidatesTokenCount,
|
|
391
|
-
totalTokens: data.usageMetadata.totalTokenCount,
|
|
392
|
-
}
|
|
393
|
-
: undefined,
|
|
394
|
-
}
|
|
211
|
+
return parseGeminiResponse(item, (await response.json()) as GeminiResponse)
|
|
395
212
|
}
|
|
396
213
|
|
|
397
214
|
/**
|
|
398
|
-
* Process a Google GenAI item via Cloudflare AI Gateway
|
|
215
|
+
* Process a Google GenAI item via Cloudflare AI Gateway.
|
|
399
216
|
* Gateway URL format: {gateway_url}/google-ai-studio/v1beta/models/{model}:generateContent
|
|
400
217
|
*/
|
|
401
218
|
async function processGoogleItemViaGateway(
|
|
402
219
|
item: BatchItem,
|
|
403
|
-
config:
|
|
220
|
+
config: GoogleConfig,
|
|
404
221
|
model: string
|
|
405
222
|
): Promise<BatchResult> {
|
|
406
|
-
// AI Gateway URL for Google AI Studio
|
|
407
|
-
// Format: {gateway_url}/google-ai-studio/v1beta/models/{model}:generateContent
|
|
408
223
|
const modelName = model.startsWith('models/') ? model.replace('models/', '') : model
|
|
409
224
|
const url = `${config.gatewayUrl}/google-ai-studio/v1beta/models/${modelName}:generateContent`
|
|
410
225
|
|
|
411
|
-
// Build messages
|
|
412
|
-
const contents: GeminiMessage[] = []
|
|
413
|
-
|
|
414
|
-
if (item.options?.system) {
|
|
415
|
-
contents.push({
|
|
416
|
-
role: 'user',
|
|
417
|
-
parts: [{ text: `System instruction: ${item.options.system}\n\nUser request: ${item.prompt}` }],
|
|
418
|
-
})
|
|
419
|
-
} else {
|
|
420
|
-
contents.push({
|
|
421
|
-
role: 'user',
|
|
422
|
-
parts: [{ text: item.prompt }],
|
|
423
|
-
})
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
const body: Record<string, unknown> = {
|
|
427
|
-
contents,
|
|
428
|
-
generationConfig: {
|
|
429
|
-
maxOutputTokens: item.options?.maxTokens || 8192,
|
|
430
|
-
temperature: item.options?.temperature,
|
|
431
|
-
},
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
if (item.schema) {
|
|
435
|
-
body.generationConfig = {
|
|
436
|
-
...(body.generationConfig as object),
|
|
437
|
-
responseMimeType: 'application/json',
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
|
|
441
226
|
const response = await fetch(url, {
|
|
442
227
|
method: 'POST',
|
|
443
228
|
headers: {
|
|
444
229
|
'cf-aig-authorization': `Bearer ${config.gatewayToken}`,
|
|
445
230
|
'Content-Type': 'application/json',
|
|
446
231
|
},
|
|
447
|
-
body: JSON.stringify(
|
|
232
|
+
body: JSON.stringify(buildGeminiBody(item)),
|
|
448
233
|
})
|
|
449
234
|
|
|
450
235
|
if (!response.ok) {
|
|
@@ -452,37 +237,46 @@ async function processGoogleItemViaGateway(
|
|
|
452
237
|
throw new Error(`Google GenAI via Gateway error: ${response.status} ${error}`)
|
|
453
238
|
}
|
|
454
239
|
|
|
455
|
-
|
|
240
|
+
return parseGeminiResponse(item, (await response.json()) as GeminiResponse)
|
|
241
|
+
}
|
|
456
242
|
|
|
457
|
-
|
|
243
|
+
function buildGeminiBody(item: BatchItem): Record<string, unknown> {
|
|
244
|
+
// Gemini handles system instructions as part of the user message.
|
|
245
|
+
const userText = item.options?.system
|
|
246
|
+
? `System instruction: ${item.options.system}\n\nUser request: ${item.prompt}`
|
|
247
|
+
: item.prompt
|
|
458
248
|
|
|
459
|
-
|
|
249
|
+
const contents: GeminiMessage[] = [{ role: 'user', parts: [{ text: userText }] }]
|
|
460
250
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
// Keep as string
|
|
466
|
-
}
|
|
251
|
+
const generationConfig: Record<string, unknown> = {
|
|
252
|
+
maxOutputTokens: item.options?.maxTokens || 8192,
|
|
253
|
+
...(item.options?.temperature !== undefined && { temperature: item.options.temperature }),
|
|
254
|
+
...(item.schema && { responseMimeType: 'application/json' }),
|
|
467
255
|
}
|
|
468
256
|
|
|
257
|
+
return { contents, generationConfig }
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function parseGeminiResponse(item: BatchItem, data: GeminiResponse): BatchResult {
|
|
261
|
+
const content = data.candidates?.[0]?.content?.parts?.[0]?.text
|
|
262
|
+
|
|
469
263
|
return {
|
|
470
264
|
id: item.id,
|
|
471
265
|
customId: item.id,
|
|
472
266
|
status: 'completed',
|
|
473
|
-
result,
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
267
|
+
result: tryParseJson(content, !!item.schema),
|
|
268
|
+
...(data.usageMetadata && {
|
|
269
|
+
usage: {
|
|
270
|
+
promptTokens: data.usageMetadata.promptTokenCount,
|
|
271
|
+
completionTokens: data.usageMetadata.candidatesTokenCount,
|
|
272
|
+
totalTokens: data.usageMetadata.totalTokenCount,
|
|
273
|
+
},
|
|
274
|
+
}),
|
|
481
275
|
}
|
|
482
276
|
}
|
|
483
277
|
|
|
484
278
|
// ============================================================================
|
|
485
|
-
// Register
|
|
279
|
+
// Register adapters
|
|
486
280
|
// ============================================================================
|
|
487
281
|
|
|
488
282
|
registerBatchAdapter('google', googleAdapter)
|
package/src/batch/index.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Batch Adapters Index
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* The `BatchProvider` port lives in `./provider.js`. Each adapter file is a
|
|
5
|
+
* small implementation of that port for one provider. Import a specific
|
|
6
|
+
* adapter to register it:
|
|
5
7
|
*
|
|
6
8
|
* @example
|
|
7
9
|
* ```ts
|
|
@@ -24,6 +26,7 @@
|
|
|
24
26
|
* @packageDocumentation
|
|
25
27
|
*/
|
|
26
28
|
|
|
29
|
+
export * from './provider.js'
|
|
27
30
|
export * from './openai.js'
|
|
28
31
|
export * from './anthropic.js'
|
|
29
32
|
export * from './cloudflare.js'
|
package/src/batch/memory.ts
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
type BatchQueueOptions,
|
|
16
16
|
type BatchResult,
|
|
17
17
|
type BatchSubmitResult,
|
|
18
|
-
} from '
|
|
18
|
+
} from './provider.js'
|
|
19
19
|
import { generateObject, generateText } from '../generate.js'
|
|
20
20
|
|
|
21
21
|
// ============================================================================
|
|
@@ -105,7 +105,7 @@ const memoryAdapter: BatchAdapter = {
|
|
|
105
105
|
completedItems: 0,
|
|
106
106
|
failedItems: 0,
|
|
107
107
|
createdAt: batch.createdAt,
|
|
108
|
-
webhookUrl: options.webhookUrl,
|
|
108
|
+
...(options.webhookUrl !== undefined && { webhookUrl: options.webhookUrl }),
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
return { job, completion }
|
|
@@ -123,12 +123,17 @@ const memoryAdapter: BatchAdapter = {
|
|
|
123
123
|
return {
|
|
124
124
|
id: batch.id,
|
|
125
125
|
provider: 'openai',
|
|
126
|
-
status:
|
|
126
|
+
status:
|
|
127
|
+
batch.status === 'completed'
|
|
128
|
+
? 'completed'
|
|
129
|
+
: batch.status === 'failed'
|
|
130
|
+
? 'failed'
|
|
131
|
+
: 'in_progress',
|
|
127
132
|
totalItems: batch.items.length,
|
|
128
133
|
completedItems,
|
|
129
134
|
failedItems,
|
|
130
135
|
createdAt: batch.createdAt,
|
|
131
|
-
completedAt: batch.completedAt,
|
|
136
|
+
...(batch.completedAt && { completedAt: batch.completedAt }),
|
|
132
137
|
}
|
|
133
138
|
},
|
|
134
139
|
|
|
@@ -195,9 +200,9 @@ async function processMemoryBatch(batch: MemoryBatch): Promise<BatchResult[]> {
|
|
|
195
200
|
model: batch.options.model || 'sonnet',
|
|
196
201
|
schema: item.schema,
|
|
197
202
|
prompt: item.prompt,
|
|
198
|
-
system: item.options
|
|
199
|
-
temperature: item.options
|
|
200
|
-
maxTokens: item.options
|
|
203
|
+
...(item.options?.system !== undefined && { system: item.options.system }),
|
|
204
|
+
...(item.options?.temperature !== undefined && { temperature: item.options.temperature }),
|
|
205
|
+
...(item.options?.maxTokens !== undefined && { maxTokens: item.options.maxTokens }),
|
|
201
206
|
})
|
|
202
207
|
result = response.object
|
|
203
208
|
} else {
|
|
@@ -205,9 +210,9 @@ async function processMemoryBatch(batch: MemoryBatch): Promise<BatchResult[]> {
|
|
|
205
210
|
const response = await generateText({
|
|
206
211
|
model: batch.options.model || 'sonnet',
|
|
207
212
|
prompt: item.prompt,
|
|
208
|
-
system: item.options
|
|
209
|
-
temperature: item.options
|
|
210
|
-
maxTokens: item.options
|
|
213
|
+
...(item.options?.system !== undefined && { system: item.options.system }),
|
|
214
|
+
...(item.options?.temperature !== undefined && { temperature: item.options.temperature }),
|
|
215
|
+
...(item.options?.maxTokens !== undefined && { maxTokens: item.options.maxTokens }),
|
|
211
216
|
})
|
|
212
217
|
result = response.text
|
|
213
218
|
}
|