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/openai.ts
CHANGED
|
@@ -6,27 +6,37 @@
|
|
|
6
6
|
* - 24-hour turnaround
|
|
7
7
|
* - Up to 50,000 requests per batch
|
|
8
8
|
*
|
|
9
|
+
* Plus a flex adapter that processes items concurrently for faster turnaround
|
|
10
|
+
* at a similar discount.
|
|
11
|
+
*
|
|
12
|
+
* This file is a small adapter on top of the BatchProvider port (`./provider.js`).
|
|
13
|
+
*
|
|
9
14
|
* @see https://platform.openai.com/docs/guides/batch
|
|
10
15
|
*
|
|
11
16
|
* @packageDocumentation
|
|
12
17
|
*/
|
|
13
18
|
|
|
19
|
+
import { schema as convertSchema } from '../schema.js'
|
|
14
20
|
import {
|
|
21
|
+
failedResult,
|
|
22
|
+
pollUntilComplete,
|
|
23
|
+
processConcurrently,
|
|
15
24
|
registerBatchAdapter,
|
|
16
25
|
registerFlexAdapter,
|
|
26
|
+
tryParseJson,
|
|
27
|
+
zodToJsonSchema,
|
|
17
28
|
type BatchAdapter,
|
|
18
|
-
type FlexAdapter,
|
|
19
29
|
type BatchItem,
|
|
20
30
|
type BatchJob,
|
|
21
31
|
type BatchQueueOptions,
|
|
22
32
|
type BatchResult,
|
|
23
|
-
type BatchSubmitResult,
|
|
24
33
|
type BatchStatus,
|
|
25
|
-
|
|
26
|
-
|
|
34
|
+
type BatchSubmitResult,
|
|
35
|
+
type FlexAdapter,
|
|
36
|
+
} from './provider.js'
|
|
27
37
|
|
|
28
38
|
// ============================================================================
|
|
29
|
-
//
|
|
39
|
+
// Provider-specific types
|
|
30
40
|
// ============================================================================
|
|
31
41
|
|
|
32
42
|
interface OpenAIBatchRequest {
|
|
@@ -49,11 +59,7 @@ interface OpenAIBatchResponse {
|
|
|
49
59
|
status_code: number
|
|
50
60
|
body: {
|
|
51
61
|
id: string
|
|
52
|
-
choices: Array<{
|
|
53
|
-
message: {
|
|
54
|
-
content: string
|
|
55
|
-
}
|
|
56
|
-
}>
|
|
62
|
+
choices: Array<{ message: { content: string } }>
|
|
57
63
|
usage: {
|
|
58
64
|
prompt_tokens: number
|
|
59
65
|
completion_tokens: number
|
|
@@ -95,41 +101,34 @@ interface OpenAIBatch {
|
|
|
95
101
|
}
|
|
96
102
|
|
|
97
103
|
// ============================================================================
|
|
98
|
-
// OpenAI
|
|
104
|
+
// OpenAI client
|
|
99
105
|
// ============================================================================
|
|
100
106
|
|
|
101
107
|
let openaiApiKey: string | undefined
|
|
102
108
|
let openaiBaseUrl = 'https://api.openai.com/v1'
|
|
103
109
|
|
|
104
|
-
/**
|
|
105
|
-
* Configure the OpenAI client
|
|
106
|
-
*/
|
|
110
|
+
/** Configure the OpenAI client. */
|
|
107
111
|
export function configureOpenAI(options: { apiKey?: string; baseUrl?: string }): void {
|
|
108
112
|
if (options.apiKey) openaiApiKey = options.apiKey
|
|
109
113
|
if (options.baseUrl) openaiBaseUrl = options.baseUrl
|
|
110
114
|
}
|
|
111
115
|
|
|
112
116
|
function getApiKey(): string {
|
|
113
|
-
const key = openaiApiKey || process.env
|
|
117
|
+
const key = openaiApiKey || process.env['OPENAI_API_KEY']
|
|
114
118
|
if (!key) {
|
|
115
119
|
throw new Error('OpenAI API key not configured. Set OPENAI_API_KEY or call configureOpenAI()')
|
|
116
120
|
}
|
|
117
121
|
return key
|
|
118
122
|
}
|
|
119
123
|
|
|
120
|
-
async function openaiRequest<T>(
|
|
121
|
-
|
|
122
|
-
path: string,
|
|
123
|
-
body?: unknown
|
|
124
|
-
): Promise<T> {
|
|
125
|
-
const url = `${openaiBaseUrl}${path}`
|
|
126
|
-
const response = await fetch(url, {
|
|
124
|
+
async function openaiRequest<T>(method: 'GET' | 'POST', path: string, body?: unknown): Promise<T> {
|
|
125
|
+
const response = await fetch(`${openaiBaseUrl}${path}`, {
|
|
127
126
|
method,
|
|
128
127
|
headers: {
|
|
129
128
|
Authorization: `Bearer ${getApiKey()}`,
|
|
130
129
|
'Content-Type': 'application/json',
|
|
131
130
|
},
|
|
132
|
-
body
|
|
131
|
+
...(body !== undefined && { body: JSON.stringify(body) }),
|
|
133
132
|
})
|
|
134
133
|
|
|
135
134
|
if (!response.ok) {
|
|
@@ -147,9 +146,7 @@ async function uploadFile(content: string, purpose: string): Promise<{ id: strin
|
|
|
147
146
|
|
|
148
147
|
const response = await fetch(`${openaiBaseUrl}/files`, {
|
|
149
148
|
method: 'POST',
|
|
150
|
-
headers: {
|
|
151
|
-
Authorization: `Bearer ${getApiKey()}`,
|
|
152
|
-
},
|
|
149
|
+
headers: { Authorization: `Bearer ${getApiKey()}` },
|
|
153
150
|
body: formData,
|
|
154
151
|
})
|
|
155
152
|
|
|
@@ -163,9 +160,7 @@ async function uploadFile(content: string, purpose: string): Promise<{ id: strin
|
|
|
163
160
|
|
|
164
161
|
async function downloadFile(fileId: string): Promise<string> {
|
|
165
162
|
const response = await fetch(`${openaiBaseUrl}/files/${fileId}/content`, {
|
|
166
|
-
headers: {
|
|
167
|
-
Authorization: `Bearer ${getApiKey()}`,
|
|
168
|
-
},
|
|
163
|
+
headers: { Authorization: `Bearer ${getApiKey()}` },
|
|
169
164
|
})
|
|
170
165
|
|
|
171
166
|
if (!response.ok) {
|
|
@@ -176,10 +171,6 @@ async function downloadFile(fileId: string): Promise<string> {
|
|
|
176
171
|
return response.text()
|
|
177
172
|
}
|
|
178
173
|
|
|
179
|
-
// ============================================================================
|
|
180
|
-
// Status Mapping
|
|
181
|
-
// ============================================================================
|
|
182
|
-
|
|
183
174
|
function mapStatus(status: string): BatchStatus {
|
|
184
175
|
const statusMap: Record<string, BatchStatus> = {
|
|
185
176
|
validating: 'validating',
|
|
@@ -195,14 +186,16 @@ function mapStatus(status: string): BatchStatus {
|
|
|
195
186
|
}
|
|
196
187
|
|
|
197
188
|
// ============================================================================
|
|
198
|
-
// OpenAI
|
|
189
|
+
// OpenAI batch adapter (BatchProvider port)
|
|
199
190
|
// ============================================================================
|
|
200
191
|
|
|
192
|
+
const TERMINAL_FOR_OPENAI: ReadonlySet<BatchStatus> = new Set(['completed', 'failed'])
|
|
193
|
+
const THROW_FOR_OPENAI: ReadonlySet<BatchStatus> = new Set(['cancelled', 'expired'])
|
|
194
|
+
|
|
201
195
|
const openaiAdapter: BatchAdapter = {
|
|
202
196
|
async submit(items: BatchItem[], options: BatchQueueOptions): Promise<BatchSubmitResult> {
|
|
203
197
|
const model = options.model || 'gpt-4o'
|
|
204
198
|
|
|
205
|
-
// Build JSONL content
|
|
206
199
|
const requests: OpenAIBatchRequest[] = items.map((item) => {
|
|
207
200
|
const request: OpenAIBatchRequest = {
|
|
208
201
|
custom_id: item.id,
|
|
@@ -214,21 +207,18 @@ const openaiAdapter: BatchAdapter = {
|
|
|
214
207
|
...(item.options?.system ? [{ role: 'system', content: item.options.system }] : []),
|
|
215
208
|
{ role: 'user', content: item.prompt },
|
|
216
209
|
],
|
|
217
|
-
max_tokens: item.options
|
|
218
|
-
|
|
210
|
+
...(item.options?.maxTokens !== undefined && { max_tokens: item.options.maxTokens }),
|
|
211
|
+
...(item.options?.temperature !== undefined && {
|
|
212
|
+
temperature: item.options.temperature,
|
|
213
|
+
}),
|
|
219
214
|
},
|
|
220
215
|
}
|
|
221
216
|
|
|
222
|
-
// Add JSON schema if provided
|
|
223
217
|
if (item.schema) {
|
|
224
218
|
const zodSchema = convertSchema(item.schema)
|
|
225
|
-
// Convert Zod to JSON Schema (simplified - you'd want a proper converter)
|
|
226
219
|
request.body.response_format = {
|
|
227
220
|
type: 'json_schema',
|
|
228
|
-
json_schema: {
|
|
229
|
-
name: 'response',
|
|
230
|
-
schema: zodToJsonSchema(zodSchema),
|
|
231
|
-
},
|
|
221
|
+
json_schema: { name: 'response', schema: zodToJsonSchema(zodSchema) },
|
|
232
222
|
}
|
|
233
223
|
}
|
|
234
224
|
|
|
@@ -236,11 +226,8 @@ const openaiAdapter: BatchAdapter = {
|
|
|
236
226
|
})
|
|
237
227
|
|
|
238
228
|
const jsonlContent = requests.map((r) => JSON.stringify(r)).join('\n')
|
|
239
|
-
|
|
240
|
-
// Upload the input file
|
|
241
229
|
const inputFile = await uploadFile(jsonlContent, 'batch')
|
|
242
230
|
|
|
243
|
-
// Create the batch
|
|
244
231
|
const batch = await openaiRequest<OpenAIBatch>('POST', '/batches', {
|
|
245
232
|
input_file_id: inputFile.id,
|
|
246
233
|
endpoint: '/v1/chat/completions',
|
|
@@ -256,20 +243,17 @@ const openaiAdapter: BatchAdapter = {
|
|
|
256
243
|
completedItems: 0,
|
|
257
244
|
failedItems: 0,
|
|
258
245
|
createdAt: new Date(batch.created_at * 1000),
|
|
259
|
-
|
|
260
|
-
webhookUrl: options.webhookUrl,
|
|
246
|
+
...(batch.expires_at && { expiresAt: new Date(batch.expires_at * 1000) }),
|
|
247
|
+
...(options.webhookUrl !== undefined && { webhookUrl: options.webhookUrl }),
|
|
261
248
|
inputFileId: batch.input_file_id,
|
|
262
249
|
}
|
|
263
250
|
|
|
264
|
-
// Create completion promise
|
|
265
251
|
const completion = this.waitForCompletion(batch.id)
|
|
266
|
-
|
|
267
252
|
return { job, completion }
|
|
268
253
|
},
|
|
269
254
|
|
|
270
255
|
async getStatus(batchId: string): Promise<BatchJob> {
|
|
271
256
|
const batch = await openaiRequest<OpenAIBatch>('GET', `/batches/${batchId}`)
|
|
272
|
-
|
|
273
257
|
return {
|
|
274
258
|
id: batch.id,
|
|
275
259
|
provider: 'openai',
|
|
@@ -278,12 +262,12 @@ const openaiAdapter: BatchAdapter = {
|
|
|
278
262
|
completedItems: batch.request_counts.completed,
|
|
279
263
|
failedItems: batch.request_counts.failed,
|
|
280
264
|
createdAt: new Date(batch.created_at * 1000),
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
265
|
+
...(batch.in_progress_at && { startedAt: new Date(batch.in_progress_at * 1000) }),
|
|
266
|
+
...(batch.completed_at && { completedAt: new Date(batch.completed_at * 1000) }),
|
|
267
|
+
...(batch.expires_at && { expiresAt: new Date(batch.expires_at * 1000) }),
|
|
284
268
|
inputFileId: batch.input_file_id,
|
|
285
|
-
outputFileId: batch.output_file_id
|
|
286
|
-
errorFileId: batch.error_file_id
|
|
269
|
+
...(batch.output_file_id && { outputFileId: batch.output_file_id }),
|
|
270
|
+
...(batch.error_file_id && { errorFileId: batch.error_file_id }),
|
|
287
271
|
}
|
|
288
272
|
},
|
|
289
273
|
|
|
@@ -300,11 +284,8 @@ const openaiAdapter: BatchAdapter = {
|
|
|
300
284
|
|
|
301
285
|
const results: BatchResult[] = []
|
|
302
286
|
|
|
303
|
-
// Download and parse output file
|
|
304
287
|
if (status.outputFileId) {
|
|
305
|
-
const
|
|
306
|
-
const lines = content.trim().split('\n')
|
|
307
|
-
|
|
288
|
+
const lines = (await downloadFile(status.outputFileId)).trim().split('\n')
|
|
308
289
|
for (const line of lines) {
|
|
309
290
|
const response: OpenAIBatchResponse = JSON.parse(line)
|
|
310
291
|
|
|
@@ -317,22 +298,11 @@ const openaiAdapter: BatchAdapter = {
|
|
|
317
298
|
})
|
|
318
299
|
} else if (response.response) {
|
|
319
300
|
const content = response.response.body.choices[0]?.message?.content
|
|
320
|
-
let result: unknown = content
|
|
321
|
-
|
|
322
|
-
// Try to parse JSON if it looks like JSON
|
|
323
|
-
if (content?.startsWith('{') || content?.startsWith('[')) {
|
|
324
|
-
try {
|
|
325
|
-
result = JSON.parse(content)
|
|
326
|
-
} catch {
|
|
327
|
-
// Keep as string
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
301
|
results.push({
|
|
332
302
|
id: response.custom_id,
|
|
333
303
|
customId: response.custom_id,
|
|
334
304
|
status: 'completed',
|
|
335
|
-
result,
|
|
305
|
+
result: tryParseJson(content),
|
|
336
306
|
usage: {
|
|
337
307
|
promptTokens: response.response.body.usage.prompt_tokens,
|
|
338
308
|
completionTokens: response.response.body.usage.completion_tokens,
|
|
@@ -343,11 +313,8 @@ const openaiAdapter: BatchAdapter = {
|
|
|
343
313
|
}
|
|
344
314
|
}
|
|
345
315
|
|
|
346
|
-
// Download and parse error file
|
|
347
316
|
if (status.errorFileId) {
|
|
348
|
-
const
|
|
349
|
-
const lines = content.trim().split('\n')
|
|
350
|
-
|
|
317
|
+
const lines = (await downloadFile(status.errorFileId)).trim().split('\n')
|
|
351
318
|
for (const line of lines) {
|
|
352
319
|
const response: OpenAIBatchResponse = JSON.parse(line)
|
|
353
320
|
results.push({
|
|
@@ -363,122 +330,37 @@ const openaiAdapter: BatchAdapter = {
|
|
|
363
330
|
},
|
|
364
331
|
|
|
365
332
|
async waitForCompletion(batchId: string, pollInterval = 5000): Promise<BatchResult[]> {
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
if (status.status === 'cancelled' || status.status === 'expired') {
|
|
374
|
-
throw new Error(`Batch ${status.status}`)
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
await new Promise((resolve) => setTimeout(resolve, pollInterval))
|
|
378
|
-
}
|
|
333
|
+
return pollUntilComplete(this, batchId, {
|
|
334
|
+
pollInterval,
|
|
335
|
+
fetchResultsOn: TERMINAL_FOR_OPENAI,
|
|
336
|
+
throwOn: THROW_FOR_OPENAI,
|
|
337
|
+
})
|
|
379
338
|
},
|
|
380
339
|
}
|
|
381
340
|
|
|
382
341
|
// ============================================================================
|
|
383
|
-
//
|
|
342
|
+
// OpenAI flex adapter (FlexAdapter port)
|
|
384
343
|
// ============================================================================
|
|
385
344
|
|
|
386
345
|
/**
|
|
387
|
-
*
|
|
388
|
-
*
|
|
389
|
-
*/
|
|
390
|
-
function zodToJsonSchema(zodSchema: unknown): Record<string, unknown> {
|
|
391
|
-
// This is a simplified converter - in production use zod-to-json-schema
|
|
392
|
-
const schema = zodSchema as { _def?: { typeName?: string; shape?: unknown } }
|
|
393
|
-
|
|
394
|
-
if (!schema._def) {
|
|
395
|
-
return { type: 'object' }
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
const typeName = schema._def.typeName
|
|
399
|
-
|
|
400
|
-
switch (typeName) {
|
|
401
|
-
case 'ZodString':
|
|
402
|
-
return { type: 'string' }
|
|
403
|
-
case 'ZodNumber':
|
|
404
|
-
return { type: 'number' }
|
|
405
|
-
case 'ZodBoolean':
|
|
406
|
-
return { type: 'boolean' }
|
|
407
|
-
case 'ZodArray':
|
|
408
|
-
return { type: 'array', items: zodToJsonSchema((schema._def as any).type) }
|
|
409
|
-
case 'ZodObject': {
|
|
410
|
-
const shape = (schema._def as any).shape()
|
|
411
|
-
const properties: Record<string, unknown> = {}
|
|
412
|
-
for (const [key, value] of Object.entries(shape)) {
|
|
413
|
-
properties[key] = zodToJsonSchema(value)
|
|
414
|
-
}
|
|
415
|
-
return { type: 'object', properties, required: Object.keys(properties) }
|
|
416
|
-
}
|
|
417
|
-
default:
|
|
418
|
-
return { type: 'object' }
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
// ============================================================================
|
|
423
|
-
// Register Adapter
|
|
424
|
-
// ============================================================================
|
|
425
|
-
|
|
426
|
-
// ============================================================================
|
|
427
|
-
// OpenAI Flex Adapter
|
|
428
|
-
// ============================================================================
|
|
429
|
-
|
|
430
|
-
/**
|
|
431
|
-
* OpenAI Flex Adapter
|
|
432
|
-
*
|
|
433
|
-
* Flex processing uses concurrent requests with a service tier that provides
|
|
434
|
-
* ~50% discount similar to batch, but with much faster turnaround (minutes vs 24hr).
|
|
346
|
+
* Flex processing uses concurrent requests for faster turnaround than batch
|
|
347
|
+
* (minutes vs 24h) at a similar discount. Ideal for 5–500 items.
|
|
435
348
|
*
|
|
436
|
-
*
|
|
437
|
-
*
|
|
438
|
-
*
|
|
439
|
-
* Note: As of 2024, OpenAI doesn't have an official "flex" tier API.
|
|
440
|
-
* This adapter implements concurrent processing as a middle ground.
|
|
441
|
-
* When OpenAI adds official flex support, this can be updated.
|
|
349
|
+
* As of 2026, OpenAI doesn't expose a dedicated "flex" tier API, so this
|
|
350
|
+
* adapter implements concurrent direct chat completions as a middle ground.
|
|
442
351
|
*/
|
|
443
352
|
const openaiFlexAdapter: FlexAdapter = {
|
|
444
353
|
async submitFlex(items: BatchItem[], options: { model?: string }): Promise<BatchResult[]> {
|
|
445
354
|
const model = options.model || 'gpt-4o'
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
// Process items concurrently in batches
|
|
451
|
-
for (let i = 0; i < items.length; i += CONCURRENCY) {
|
|
452
|
-
const batch = items.slice(i, i + CONCURRENCY)
|
|
453
|
-
|
|
454
|
-
const batchResults = await Promise.all(
|
|
455
|
-
batch.map(async (item) => {
|
|
456
|
-
try {
|
|
457
|
-
return await processOpenAIItem(item, model)
|
|
458
|
-
} catch (error) {
|
|
459
|
-
return {
|
|
460
|
-
id: item.id,
|
|
461
|
-
customId: item.id,
|
|
462
|
-
status: 'failed' as const,
|
|
463
|
-
error: error instanceof Error ? error.message : 'Unknown error',
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
})
|
|
467
|
-
)
|
|
468
|
-
|
|
469
|
-
results.push(...batchResults)
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
return results
|
|
355
|
+
return processConcurrently(items, (item) => processOpenAIItem(item, model), {
|
|
356
|
+
concurrency: 10,
|
|
357
|
+
})
|
|
473
358
|
},
|
|
474
359
|
}
|
|
475
360
|
|
|
476
|
-
/**
|
|
477
|
-
* Process a single item via OpenAI Chat Completions API
|
|
478
|
-
*/
|
|
361
|
+
/** Process a single item via OpenAI Chat Completions API. */
|
|
479
362
|
async function processOpenAIItem(item: BatchItem, model: string): Promise<BatchResult> {
|
|
480
363
|
const messages: Array<{ role: string; content: string }> = []
|
|
481
|
-
|
|
482
364
|
if (item.options?.system) {
|
|
483
365
|
messages.push({ role: 'system', content: item.options.system })
|
|
484
366
|
}
|
|
@@ -491,15 +373,11 @@ async function processOpenAIItem(item: BatchItem, model: string): Promise<BatchR
|
|
|
491
373
|
temperature: item.options?.temperature,
|
|
492
374
|
}
|
|
493
375
|
|
|
494
|
-
// Add JSON schema if provided
|
|
495
376
|
if (item.schema) {
|
|
496
377
|
const zodSchema = convertSchema(item.schema)
|
|
497
|
-
body
|
|
378
|
+
body['response_format'] = {
|
|
498
379
|
type: 'json_schema',
|
|
499
|
-
json_schema: {
|
|
500
|
-
name: 'response',
|
|
501
|
-
schema: zodToJsonSchema(zodSchema),
|
|
502
|
-
},
|
|
380
|
+
json_schema: { name: 'response', schema: zodToJsonSchema(zodSchema) },
|
|
503
381
|
}
|
|
504
382
|
}
|
|
505
383
|
|
|
@@ -523,22 +401,12 @@ async function processOpenAIItem(item: BatchItem, model: string): Promise<BatchR
|
|
|
523
401
|
}
|
|
524
402
|
|
|
525
403
|
const content = data.choices[0]?.message?.content
|
|
526
|
-
let result: unknown = content
|
|
527
|
-
|
|
528
|
-
// Try to parse JSON if schema was provided or content looks like JSON
|
|
529
|
-
if (content && (item.schema || content.startsWith('{') || content.startsWith('['))) {
|
|
530
|
-
try {
|
|
531
|
-
result = JSON.parse(content)
|
|
532
|
-
} catch {
|
|
533
|
-
// Keep as string
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
404
|
|
|
537
405
|
return {
|
|
538
406
|
id: item.id,
|
|
539
407
|
customId: item.id,
|
|
540
408
|
status: 'completed',
|
|
541
|
-
result,
|
|
409
|
+
result: tryParseJson(content, !!item.schema),
|
|
542
410
|
usage: {
|
|
543
411
|
promptTokens: data.usage.prompt_tokens,
|
|
544
412
|
completionTokens: data.usage.completion_tokens,
|
|
@@ -547,8 +415,12 @@ async function processOpenAIItem(item: BatchItem, model: string): Promise<BatchR
|
|
|
547
415
|
}
|
|
548
416
|
}
|
|
549
417
|
|
|
418
|
+
// `failedResult` re-imported only to keep the import surface stable for tests
|
|
419
|
+
// that may use it. The processConcurrently helper handles failures internally.
|
|
420
|
+
void failedResult
|
|
421
|
+
|
|
550
422
|
// ============================================================================
|
|
551
|
-
// Register
|
|
423
|
+
// Register adapters
|
|
552
424
|
// ============================================================================
|
|
553
425
|
|
|
554
426
|
registerBatchAdapter('openai', openaiAdapter)
|