ai-functions 0.3.0 → 0.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 +5 -0
- package/.turbo/turbo-test.log +105 -0
- package/README.md +190 -86
- package/TODO.md +138 -0
- package/dist/ai-promise.d.ts +219 -0
- package/dist/ai-promise.d.ts.map +1 -0
- package/dist/ai-promise.js +610 -0
- package/dist/ai-promise.js.map +1 -0
- package/dist/ai.d.ts +285 -0
- package/dist/ai.d.ts.map +1 -0
- package/dist/ai.js +842 -0
- package/dist/ai.js.map +1 -0
- package/dist/batch/anthropic.d.ts +23 -0
- package/dist/batch/anthropic.d.ts.map +1 -0
- package/dist/batch/anthropic.js +257 -0
- package/dist/batch/anthropic.js.map +1 -0
- package/dist/batch/bedrock.d.ts +64 -0
- package/dist/batch/bedrock.d.ts.map +1 -0
- package/dist/batch/bedrock.js +586 -0
- package/dist/batch/bedrock.js.map +1 -0
- package/dist/batch/cloudflare.d.ts +37 -0
- package/dist/batch/cloudflare.d.ts.map +1 -0
- package/dist/batch/cloudflare.js +289 -0
- package/dist/batch/cloudflare.js.map +1 -0
- package/dist/batch/google.d.ts +41 -0
- package/dist/batch/google.d.ts.map +1 -0
- package/dist/batch/google.js +360 -0
- package/dist/batch/google.js.map +1 -0
- package/dist/batch/index.d.ts +31 -0
- package/dist/batch/index.d.ts.map +1 -0
- package/dist/batch/index.js +31 -0
- package/dist/batch/index.js.map +1 -0
- package/dist/batch/memory.d.ts +44 -0
- package/dist/batch/memory.d.ts.map +1 -0
- package/dist/batch/memory.js +188 -0
- package/dist/batch/memory.js.map +1 -0
- package/dist/batch/openai.d.ts +37 -0
- package/dist/batch/openai.d.ts.map +1 -0
- package/dist/batch/openai.js +403 -0
- package/dist/batch/openai.js.map +1 -0
- package/dist/batch-map.d.ts +125 -0
- package/dist/batch-map.d.ts.map +1 -0
- package/dist/batch-map.js +406 -0
- package/dist/batch-map.js.map +1 -0
- package/dist/batch-queue.d.ts +273 -0
- package/dist/batch-queue.d.ts.map +1 -0
- package/dist/batch-queue.js +271 -0
- package/dist/batch-queue.js.map +1 -0
- package/dist/context.d.ts +133 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +267 -0
- package/dist/context.js.map +1 -0
- package/dist/embeddings.d.ts +123 -0
- package/dist/embeddings.d.ts.map +1 -0
- package/dist/embeddings.js +170 -0
- package/dist/embeddings.js.map +1 -0
- package/dist/eval/index.d.ts +8 -0
- package/dist/eval/index.d.ts.map +1 -0
- package/dist/eval/index.js +8 -0
- package/dist/eval/index.js.map +1 -0
- package/dist/eval/models.d.ts +66 -0
- package/dist/eval/models.d.ts.map +1 -0
- package/dist/eval/models.js +120 -0
- package/dist/eval/models.js.map +1 -0
- package/dist/eval/runner.d.ts +64 -0
- package/dist/eval/runner.d.ts.map +1 -0
- package/dist/eval/runner.js +148 -0
- package/dist/eval/runner.js.map +1 -0
- package/dist/generate.d.ts +168 -0
- package/dist/generate.d.ts.map +1 -0
- package/dist/generate.js +174 -0
- package/dist/generate.js.map +1 -0
- package/dist/index.d.ts +29 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +53 -52
- package/dist/index.js.map +1 -1
- package/dist/primitives.d.ts +292 -0
- package/dist/primitives.d.ts.map +1 -0
- package/dist/primitives.js +471 -0
- package/dist/primitives.js.map +1 -0
- package/dist/providers/cloudflare.d.ts +9 -0
- package/dist/providers/cloudflare.d.ts.map +1 -0
- package/dist/providers/cloudflare.js +9 -0
- package/dist/providers/cloudflare.js.map +1 -0
- package/dist/providers/index.d.ts +9 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +9 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/schema.d.ts +54 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +109 -0
- package/dist/schema.js.map +1 -0
- package/dist/template.d.ts +73 -0
- package/dist/template.d.ts.map +1 -0
- package/dist/template.js +129 -0
- package/dist/template.js.map +1 -0
- package/dist/types.d.ts +474 -106
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +4 -8
- package/dist/types.js.map +1 -1
- package/evalite.config.ts +19 -0
- package/evals/README.md +212 -0
- package/evals/classification.eval.ts +108 -0
- package/evals/marketing.eval.ts +370 -0
- package/evals/math.eval.ts +94 -0
- package/evals/run-evals.ts +166 -0
- package/evals/structured-output.eval.ts +143 -0
- package/evals/writing.eval.ts +117 -0
- package/examples/batch-blog-posts.ts +160 -0
- package/package.json +57 -57
- package/src/ai-promise.ts +784 -0
- package/src/ai.ts +1183 -0
- package/src/batch/anthropic.ts +375 -0
- package/src/batch/bedrock.ts +801 -0
- package/src/batch/cloudflare.ts +421 -0
- package/src/batch/google.ts +491 -0
- package/src/batch/index.ts +31 -0
- package/src/batch/memory.ts +253 -0
- package/src/batch/openai.ts +557 -0
- package/src/batch-map.ts +534 -0
- package/src/batch-queue.ts +493 -0
- package/src/context.ts +332 -0
- package/src/embeddings.ts +244 -0
- package/src/eval/index.ts +8 -0
- package/src/eval/models.ts +158 -0
- package/src/eval/runner.ts +217 -0
- package/src/generate.ts +245 -0
- package/src/index.ts +154 -0
- package/src/primitives.ts +612 -0
- package/src/providers/cloudflare.ts +15 -0
- package/src/providers/index.ts +14 -0
- package/src/schema.ts +147 -0
- package/src/template.ts +209 -0
- package/src/types.ts +540 -0
- package/test/README.md +105 -0
- package/test/ai-proxy.test.ts +192 -0
- package/test/async-iterators.test.ts +327 -0
- package/test/batch-background.test.ts +482 -0
- package/test/batch-blog-posts.test.ts +387 -0
- package/test/blog-generation.test.ts +510 -0
- package/test/browse-read.test.ts +611 -0
- package/test/core-functions.test.ts +694 -0
- package/test/decide.test.ts +393 -0
- package/test/define.test.ts +274 -0
- package/test/e2e-bedrock-manual.ts +163 -0
- package/test/e2e-bedrock.test.ts +191 -0
- package/test/e2e-flex-gateway.ts +157 -0
- package/test/e2e-flex-manual.ts +183 -0
- package/test/e2e-flex.test.ts +209 -0
- package/test/e2e-google-manual.ts +178 -0
- package/test/e2e-google.test.ts +216 -0
- package/test/embeddings.test.ts +284 -0
- package/test/evals/define-function.eval.test.ts +379 -0
- package/test/evals/primitives.eval.test.ts +384 -0
- package/test/function-types.test.ts +492 -0
- package/test/generate-core.test.ts +319 -0
- package/test/generate.test.ts +163 -0
- package/test/implicit-batch.test.ts +422 -0
- package/test/schema.test.ts +109 -0
- package/test/tagged-templates.test.ts +302 -0
- package/tsconfig.json +10 -0
- package/vitest.config.ts +42 -0
- package/LICENSE +0 -21
- package/bin/cli.js +0 -5
- package/dist/cli/index.d.ts +0 -10
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/index.js +0 -38
- package/dist/cli/index.js.map +0 -1
- package/dist/cli/index.test.d.ts +0 -2
- package/dist/cli/index.test.d.ts.map +0 -1
- package/dist/cli/index.test.js +0 -35
- package/dist/cli/index.test.js.map +0 -1
- package/dist/constants/models.d.ts +0 -10
- package/dist/constants/models.d.ts.map +0 -1
- package/dist/constants/models.js +0 -12
- package/dist/constants/models.js.map +0 -1
- package/dist/converters/index.d.ts +0 -3
- package/dist/converters/index.d.ts.map +0 -1
- package/dist/converters/index.js +0 -3
- package/dist/converters/index.js.map +0 -1
- package/dist/converters/model.d.ts +0 -4
- package/dist/converters/model.d.ts.map +0 -1
- package/dist/converters/model.js +0 -19
- package/dist/converters/model.js.map +0 -1
- package/dist/converters/schema.d.ts +0 -4
- package/dist/converters/schema.d.ts.map +0 -1
- package/dist/converters/schema.js +0 -25
- package/dist/converters/schema.js.map +0 -1
- package/dist/core/responses.d.ts +0 -5
- package/dist/core/responses.d.ts.map +0 -1
- package/dist/core/responses.js +0 -16
- package/dist/core/responses.js.map +0 -1
- package/dist/core/responses.test.d.ts +0 -2
- package/dist/core/responses.test.d.ts.map +0 -1
- package/dist/core/responses.test.js +0 -31
- package/dist/core/responses.test.js.map +0 -1
- package/dist/errors.d.ts +0 -6
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -9
- package/dist/errors.js.map +0 -1
- package/dist/examples/streaming.test.d.ts +0 -2
- package/dist/examples/streaming.test.d.ts.map +0 -1
- package/dist/examples/streaming.test.js +0 -176
- package/dist/examples/streaming.test.js.map +0 -1
- package/dist/factory/__tests__/index.test.d.ts +0 -2
- package/dist/factory/__tests__/index.test.d.ts.map +0 -1
- package/dist/factory/__tests__/index.test.js +0 -430
- package/dist/factory/__tests__/index.test.js.map +0 -1
- package/dist/factory/__tests__/list.test.d.ts +0 -2
- package/dist/factory/__tests__/list.test.d.ts.map +0 -1
- package/dist/factory/__tests__/list.test.js +0 -92
- package/dist/factory/__tests__/list.test.js.map +0 -1
- package/dist/factory/index.d.ts +0 -20
- package/dist/factory/index.d.ts.map +0 -1
- package/dist/factory/index.js +0 -287
- package/dist/factory/index.js.map +0 -1
- package/dist/factory/index.test.d.ts +0 -2
- package/dist/factory/index.test.d.ts.map +0 -1
- package/dist/factory/index.test.js +0 -287
- package/dist/factory/index.test.js.map +0 -1
- package/dist/factory/list.d.ts +0 -3
- package/dist/factory/list.d.ts.map +0 -1
- package/dist/factory/list.js +0 -221
- package/dist/factory/list.js.map +0 -1
- package/dist/factory/list.test.d.ts +0 -2
- package/dist/factory/list.test.d.ts.map +0 -1
- package/dist/factory/list.test.js +0 -84
- package/dist/factory/list.test.js.map +0 -1
- package/dist/generate/index.d.ts +0 -5
- package/dist/generate/index.d.ts.map +0 -1
- package/dist/generate/index.js +0 -17
- package/dist/generate/index.js.map +0 -1
- package/dist/index.test.d.ts +0 -2
- package/dist/index.test.d.ts.map +0 -1
- package/dist/index.test.js +0 -59
- package/dist/index.test.js.map +0 -1
- package/dist/list/await.d.ts +0 -3
- package/dist/list/await.d.ts.map +0 -1
- package/dist/list/await.js +0 -28
- package/dist/list/await.js.map +0 -1
- package/dist/list/constants.d.ts +0 -4
- package/dist/list/constants.d.ts.map +0 -1
- package/dist/list/constants.js +0 -5
- package/dist/list/constants.js.map +0 -1
- package/dist/list/create-function.d.ts +0 -3
- package/dist/list/create-function.d.ts.map +0 -1
- package/dist/list/create-function.js +0 -11
- package/dist/list/create-function.js.map +0 -1
- package/dist/list/index.d.ts +0 -4
- package/dist/list/index.d.ts.map +0 -1
- package/dist/list/index.js +0 -5
- package/dist/list/index.js.map +0 -1
- package/dist/list/prompt.d.ts +0 -3
- package/dist/list/prompt.d.ts.map +0 -1
- package/dist/list/prompt.js +0 -6
- package/dist/list/prompt.js.map +0 -1
- package/dist/list/schemas.d.ts +0 -4
- package/dist/list/schemas.d.ts.map +0 -1
- package/dist/list/schemas.js +0 -8
- package/dist/list/schemas.js.map +0 -1
- package/dist/list/stream.d.ts +0 -3
- package/dist/list/stream.d.ts.map +0 -1
- package/dist/list/stream.js +0 -33
- package/dist/list/stream.js.map +0 -1
- package/dist/list/types.d.ts +0 -11
- package/dist/list/types.d.ts.map +0 -1
- package/dist/list/types.js +0 -2
- package/dist/list/types.js.map +0 -1
- package/dist/list/validation.d.ts +0 -3
- package/dist/list/validation.d.ts.map +0 -1
- package/dist/list/validation.js +0 -12
- package/dist/list/validation.js.map +0 -1
- package/dist/providers/config.d.ts +0 -4
- package/dist/providers/config.d.ts.map +0 -1
- package/dist/providers/config.js +0 -21
- package/dist/providers/config.js.map +0 -1
- package/dist/providers/config.test.d.ts +0 -2
- package/dist/providers/config.test.d.ts.map +0 -1
- package/dist/providers/config.test.js +0 -37
- package/dist/providers/config.test.js.map +0 -1
- package/dist/proxy/constants.d.ts +0 -4
- package/dist/proxy/constants.d.ts.map +0 -1
- package/dist/proxy/constants.js +0 -5
- package/dist/proxy/constants.js.map +0 -1
- package/dist/proxy/create-function.d.ts +0 -4
- package/dist/proxy/create-function.d.ts.map +0 -1
- package/dist/proxy/create-function.js +0 -24
- package/dist/proxy/create-function.js.map +0 -1
- package/dist/proxy/create-proxy.d.ts +0 -2
- package/dist/proxy/create-proxy.d.ts.map +0 -1
- package/dist/proxy/create-proxy.js +0 -11
- package/dist/proxy/create-proxy.js.map +0 -1
- package/dist/proxy/function-generator.d.ts +0 -9
- package/dist/proxy/function-generator.d.ts.map +0 -1
- package/dist/proxy/function-generator.js +0 -29
- package/dist/proxy/function-generator.js.map +0 -1
- package/dist/proxy/index.d.ts +0 -4
- package/dist/proxy/index.d.ts.map +0 -1
- package/dist/proxy/index.js +0 -4
- package/dist/proxy/index.js.map +0 -1
- package/dist/proxy/prompt.d.ts +0 -2
- package/dist/proxy/prompt.d.ts.map +0 -1
- package/dist/proxy/prompt.js +0 -6
- package/dist/proxy/prompt.js.map +0 -1
- package/dist/proxy/types.d.ts +0 -7
- package/dist/proxy/types.d.ts.map +0 -1
- package/dist/proxy/types.js +0 -2
- package/dist/proxy/types.js.map +0 -1
- package/dist/queue/manager.d.ts +0 -5
- package/dist/queue/manager.d.ts.map +0 -1
- package/dist/queue/manager.js +0 -37
- package/dist/queue/manager.js.map +0 -1
- package/dist/queue/manager.test.d.ts +0 -2
- package/dist/queue/manager.test.d.ts.map +0 -1
- package/dist/queue/manager.test.js +0 -52
- package/dist/queue/manager.test.js.map +0 -1
- package/dist/schema-converter.d.ts +0 -4
- package/dist/schema-converter.d.ts.map +0 -1
- package/dist/schema-converter.js +0 -30
- package/dist/schema-converter.js.map +0 -1
- package/dist/stream/index.d.ts +0 -7
- package/dist/stream/index.d.ts.map +0 -1
- package/dist/stream/index.js +0 -23
- package/dist/stream/index.js.map +0 -1
- package/dist/streaming/utils.d.ts +0 -4
- package/dist/streaming/utils.d.ts.map +0 -1
- package/dist/streaming/utils.js +0 -131
- package/dist/streaming/utils.js.map +0 -1
- package/dist/streaming/utils.test.d.ts +0 -2
- package/dist/streaming/utils.test.d.ts.map +0 -1
- package/dist/streaming/utils.test.js +0 -84
- package/dist/streaming/utils.test.js.map +0 -1
- package/dist/templates/result.d.ts +0 -7
- package/dist/templates/result.d.ts.map +0 -1
- package/dist/templates/result.js +0 -40
- package/dist/templates/result.js.map +0 -1
- package/dist/templates/result.test.d.ts +0 -2
- package/dist/templates/result.test.d.ts.map +0 -1
- package/dist/templates/result.test.js +0 -75
- package/dist/templates/result.test.js.map +0 -1
- package/dist/test/setup.d.ts +0 -2
- package/dist/test/setup.d.ts.map +0 -1
- package/dist/test/setup.js +0 -21
- package/dist/test/setup.js.map +0 -1
- package/dist/test-types.d.ts +0 -13
- package/dist/test-types.d.ts.map +0 -1
- package/dist/test-types.js +0 -55
- package/dist/test-types.js.map +0 -1
- package/dist/types/index.d.ts +0 -4
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js +0 -4
- package/dist/types/index.js.map +0 -1
- package/dist/types/list.d.ts +0 -10
- package/dist/types/list.d.ts.map +0 -1
- package/dist/types/list.js +0 -2
- package/dist/types/list.js.map +0 -1
- package/dist/types/model.d.ts +0 -7
- package/dist/types/model.d.ts.map +0 -1
- package/dist/types/model.js +0 -2
- package/dist/types/model.js.map +0 -1
- package/dist/types/options.d.ts +0 -25
- package/dist/types/options.d.ts.map +0 -1
- package/dist/types/options.js +0 -2
- package/dist/types/options.js.map +0 -1
- package/dist/types/schema.d.ts +0 -5
- package/dist/types/schema.d.ts.map +0 -1
- package/dist/types/schema.js +0 -2
- package/dist/types/schema.js.map +0 -1
- package/dist/utils/__tests__/request-handler.test.d.ts +0 -2
- package/dist/utils/__tests__/request-handler.test.d.ts.map +0 -1
- package/dist/utils/__tests__/request-handler.test.js +0 -134
- package/dist/utils/__tests__/request-handler.test.js.map +0 -1
- package/dist/utils/__tests__/schema.test.d.ts +0 -2
- package/dist/utils/__tests__/schema.test.d.ts.map +0 -1
- package/dist/utils/__tests__/schema.test.js +0 -49
- package/dist/utils/__tests__/schema.test.js.map +0 -1
- package/dist/utils/__tests__/stream-progress.test.d.ts +0 -2
- package/dist/utils/__tests__/stream-progress.test.d.ts.map +0 -1
- package/dist/utils/__tests__/stream-progress.test.js +0 -85
- package/dist/utils/__tests__/stream-progress.test.js.map +0 -1
- package/dist/utils/index.d.ts +0 -2
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js +0 -2
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/request-handler.d.ts +0 -17
- package/dist/utils/request-handler.d.ts.map +0 -1
- package/dist/utils/request-handler.js +0 -105
- package/dist/utils/request-handler.js.map +0 -1
- package/dist/utils/schema.d.ts +0 -11
- package/dist/utils/schema.d.ts.map +0 -1
- package/dist/utils/schema.js +0 -51
- package/dist/utils/schema.js.map +0 -1
- package/dist/utils/stream-progress.d.ts +0 -17
- package/dist/utils/stream-progress.d.ts.map +0 -1
- package/dist/utils/stream-progress.js +0 -86
- package/dist/utils/stream-progress.js.map +0 -1
- package/dist/utils/validation.d.ts +0 -3
- package/dist/utils/validation.d.ts.map +0 -1
- package/dist/utils/validation.js +0 -30
- package/dist/utils/validation.js.map +0 -1
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI Batch API Adapter
|
|
3
|
+
*
|
|
4
|
+
* Implements batch processing using OpenAI's Batch API:
|
|
5
|
+
* - 50% cost discount
|
|
6
|
+
* - 24-hour turnaround
|
|
7
|
+
* - Up to 50,000 requests per batch
|
|
8
|
+
*
|
|
9
|
+
* @see https://platform.openai.com/docs/guides/batch
|
|
10
|
+
*
|
|
11
|
+
* @packageDocumentation
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
registerBatchAdapter,
|
|
16
|
+
registerFlexAdapter,
|
|
17
|
+
type BatchAdapter,
|
|
18
|
+
type FlexAdapter,
|
|
19
|
+
type BatchItem,
|
|
20
|
+
type BatchJob,
|
|
21
|
+
type BatchQueueOptions,
|
|
22
|
+
type BatchResult,
|
|
23
|
+
type BatchSubmitResult,
|
|
24
|
+
type BatchStatus,
|
|
25
|
+
} from '../batch-queue.js'
|
|
26
|
+
import { schema as convertSchema, type SimpleSchema } from '../schema.js'
|
|
27
|
+
|
|
28
|
+
// ============================================================================
|
|
29
|
+
// Types
|
|
30
|
+
// ============================================================================
|
|
31
|
+
|
|
32
|
+
interface OpenAIBatchRequest {
|
|
33
|
+
custom_id: string
|
|
34
|
+
method: 'POST'
|
|
35
|
+
url: '/v1/chat/completions'
|
|
36
|
+
body: {
|
|
37
|
+
model: string
|
|
38
|
+
messages: Array<{ role: string; content: string }>
|
|
39
|
+
response_format?: { type: 'json_schema'; json_schema: { name: string; schema: unknown } }
|
|
40
|
+
max_tokens?: number
|
|
41
|
+
temperature?: number
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface OpenAIBatchResponse {
|
|
46
|
+
id: string
|
|
47
|
+
custom_id: string
|
|
48
|
+
response: {
|
|
49
|
+
status_code: number
|
|
50
|
+
body: {
|
|
51
|
+
id: string
|
|
52
|
+
choices: Array<{
|
|
53
|
+
message: {
|
|
54
|
+
content: string
|
|
55
|
+
}
|
|
56
|
+
}>
|
|
57
|
+
usage: {
|
|
58
|
+
prompt_tokens: number
|
|
59
|
+
completion_tokens: number
|
|
60
|
+
total_tokens: number
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
} | null
|
|
64
|
+
error: {
|
|
65
|
+
code: string
|
|
66
|
+
message: string
|
|
67
|
+
} | null
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
interface OpenAIBatch {
|
|
71
|
+
id: string
|
|
72
|
+
object: 'batch'
|
|
73
|
+
endpoint: string
|
|
74
|
+
errors: null | { object: string; data: Array<{ code: string; message: string; line: number }> }
|
|
75
|
+
input_file_id: string
|
|
76
|
+
completion_window: string
|
|
77
|
+
status: string
|
|
78
|
+
output_file_id: string | null
|
|
79
|
+
error_file_id: string | null
|
|
80
|
+
created_at: number
|
|
81
|
+
in_progress_at: number | null
|
|
82
|
+
expires_at: number | null
|
|
83
|
+
finalizing_at: number | null
|
|
84
|
+
completed_at: number | null
|
|
85
|
+
failed_at: number | null
|
|
86
|
+
expired_at: number | null
|
|
87
|
+
cancelling_at: number | null
|
|
88
|
+
cancelled_at: number | null
|
|
89
|
+
request_counts: {
|
|
90
|
+
total: number
|
|
91
|
+
completed: number
|
|
92
|
+
failed: number
|
|
93
|
+
}
|
|
94
|
+
metadata: Record<string, string> | null
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ============================================================================
|
|
98
|
+
// OpenAI Client
|
|
99
|
+
// ============================================================================
|
|
100
|
+
|
|
101
|
+
let openaiApiKey: string | undefined
|
|
102
|
+
let openaiBaseUrl = 'https://api.openai.com/v1'
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Configure the OpenAI client
|
|
106
|
+
*/
|
|
107
|
+
export function configureOpenAI(options: { apiKey?: string; baseUrl?: string }): void {
|
|
108
|
+
if (options.apiKey) openaiApiKey = options.apiKey
|
|
109
|
+
if (options.baseUrl) openaiBaseUrl = options.baseUrl
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function getApiKey(): string {
|
|
113
|
+
const key = openaiApiKey || process.env.OPENAI_API_KEY
|
|
114
|
+
if (!key) {
|
|
115
|
+
throw new Error('OpenAI API key not configured. Set OPENAI_API_KEY or call configureOpenAI()')
|
|
116
|
+
}
|
|
117
|
+
return key
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function openaiRequest<T>(
|
|
121
|
+
method: 'GET' | 'POST',
|
|
122
|
+
path: string,
|
|
123
|
+
body?: unknown
|
|
124
|
+
): Promise<T> {
|
|
125
|
+
const url = `${openaiBaseUrl}${path}`
|
|
126
|
+
const response = await fetch(url, {
|
|
127
|
+
method,
|
|
128
|
+
headers: {
|
|
129
|
+
Authorization: `Bearer ${getApiKey()}`,
|
|
130
|
+
'Content-Type': 'application/json',
|
|
131
|
+
},
|
|
132
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
if (!response.ok) {
|
|
136
|
+
const error = await response.text()
|
|
137
|
+
throw new Error(`OpenAI API error: ${response.status} ${error}`)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return response.json()
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function uploadFile(content: string, purpose: string): Promise<{ id: string }> {
|
|
144
|
+
const formData = new FormData()
|
|
145
|
+
formData.append('purpose', purpose)
|
|
146
|
+
formData.append('file', new Blob([content], { type: 'application/jsonl' }), 'batch.jsonl')
|
|
147
|
+
|
|
148
|
+
const response = await fetch(`${openaiBaseUrl}/files`, {
|
|
149
|
+
method: 'POST',
|
|
150
|
+
headers: {
|
|
151
|
+
Authorization: `Bearer ${getApiKey()}`,
|
|
152
|
+
},
|
|
153
|
+
body: formData,
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
if (!response.ok) {
|
|
157
|
+
const error = await response.text()
|
|
158
|
+
throw new Error(`OpenAI file upload error: ${response.status} ${error}`)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return response.json()
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function downloadFile(fileId: string): Promise<string> {
|
|
165
|
+
const response = await fetch(`${openaiBaseUrl}/files/${fileId}/content`, {
|
|
166
|
+
headers: {
|
|
167
|
+
Authorization: `Bearer ${getApiKey()}`,
|
|
168
|
+
},
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
if (!response.ok) {
|
|
172
|
+
const error = await response.text()
|
|
173
|
+
throw new Error(`OpenAI file download error: ${response.status} ${error}`)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return response.text()
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ============================================================================
|
|
180
|
+
// Status Mapping
|
|
181
|
+
// ============================================================================
|
|
182
|
+
|
|
183
|
+
function mapStatus(status: string): BatchStatus {
|
|
184
|
+
const statusMap: Record<string, BatchStatus> = {
|
|
185
|
+
validating: 'validating',
|
|
186
|
+
in_progress: 'in_progress',
|
|
187
|
+
finalizing: 'finalizing',
|
|
188
|
+
completed: 'completed',
|
|
189
|
+
failed: 'failed',
|
|
190
|
+
expired: 'expired',
|
|
191
|
+
cancelling: 'cancelling',
|
|
192
|
+
cancelled: 'cancelled',
|
|
193
|
+
}
|
|
194
|
+
return statusMap[status] || 'pending'
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ============================================================================
|
|
198
|
+
// OpenAI Batch Adapter
|
|
199
|
+
// ============================================================================
|
|
200
|
+
|
|
201
|
+
const openaiAdapter: BatchAdapter = {
|
|
202
|
+
async submit(items: BatchItem[], options: BatchQueueOptions): Promise<BatchSubmitResult> {
|
|
203
|
+
const model = options.model || 'gpt-4o'
|
|
204
|
+
|
|
205
|
+
// Build JSONL content
|
|
206
|
+
const requests: OpenAIBatchRequest[] = items.map((item) => {
|
|
207
|
+
const request: OpenAIBatchRequest = {
|
|
208
|
+
custom_id: item.id,
|
|
209
|
+
method: 'POST',
|
|
210
|
+
url: '/v1/chat/completions',
|
|
211
|
+
body: {
|
|
212
|
+
model,
|
|
213
|
+
messages: [
|
|
214
|
+
...(item.options?.system ? [{ role: 'system', content: item.options.system }] : []),
|
|
215
|
+
{ role: 'user', content: item.prompt },
|
|
216
|
+
],
|
|
217
|
+
max_tokens: item.options?.maxTokens,
|
|
218
|
+
temperature: item.options?.temperature,
|
|
219
|
+
},
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Add JSON schema if provided
|
|
223
|
+
if (item.schema) {
|
|
224
|
+
const zodSchema = convertSchema(item.schema)
|
|
225
|
+
// Convert Zod to JSON Schema (simplified - you'd want a proper converter)
|
|
226
|
+
request.body.response_format = {
|
|
227
|
+
type: 'json_schema',
|
|
228
|
+
json_schema: {
|
|
229
|
+
name: 'response',
|
|
230
|
+
schema: zodToJsonSchema(zodSchema),
|
|
231
|
+
},
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return request
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
const jsonlContent = requests.map((r) => JSON.stringify(r)).join('\n')
|
|
239
|
+
|
|
240
|
+
// Upload the input file
|
|
241
|
+
const inputFile = await uploadFile(jsonlContent, 'batch')
|
|
242
|
+
|
|
243
|
+
// Create the batch
|
|
244
|
+
const batch = await openaiRequest<OpenAIBatch>('POST', '/batches', {
|
|
245
|
+
input_file_id: inputFile.id,
|
|
246
|
+
endpoint: '/v1/chat/completions',
|
|
247
|
+
completion_window: '24h',
|
|
248
|
+
metadata: options.metadata,
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
const job: BatchJob = {
|
|
252
|
+
id: batch.id,
|
|
253
|
+
provider: 'openai',
|
|
254
|
+
status: mapStatus(batch.status),
|
|
255
|
+
totalItems: items.length,
|
|
256
|
+
completedItems: 0,
|
|
257
|
+
failedItems: 0,
|
|
258
|
+
createdAt: new Date(batch.created_at * 1000),
|
|
259
|
+
expiresAt: batch.expires_at ? new Date(batch.expires_at * 1000) : undefined,
|
|
260
|
+
webhookUrl: options.webhookUrl,
|
|
261
|
+
inputFileId: batch.input_file_id,
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Create completion promise
|
|
265
|
+
const completion = this.waitForCompletion(batch.id)
|
|
266
|
+
|
|
267
|
+
return { job, completion }
|
|
268
|
+
},
|
|
269
|
+
|
|
270
|
+
async getStatus(batchId: string): Promise<BatchJob> {
|
|
271
|
+
const batch = await openaiRequest<OpenAIBatch>('GET', `/batches/${batchId}`)
|
|
272
|
+
|
|
273
|
+
return {
|
|
274
|
+
id: batch.id,
|
|
275
|
+
provider: 'openai',
|
|
276
|
+
status: mapStatus(batch.status),
|
|
277
|
+
totalItems: batch.request_counts.total,
|
|
278
|
+
completedItems: batch.request_counts.completed,
|
|
279
|
+
failedItems: batch.request_counts.failed,
|
|
280
|
+
createdAt: new Date(batch.created_at * 1000),
|
|
281
|
+
startedAt: batch.in_progress_at ? new Date(batch.in_progress_at * 1000) : undefined,
|
|
282
|
+
completedAt: batch.completed_at ? new Date(batch.completed_at * 1000) : undefined,
|
|
283
|
+
expiresAt: batch.expires_at ? new Date(batch.expires_at * 1000) : undefined,
|
|
284
|
+
inputFileId: batch.input_file_id,
|
|
285
|
+
outputFileId: batch.output_file_id || undefined,
|
|
286
|
+
errorFileId: batch.error_file_id || undefined,
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
|
|
290
|
+
async cancel(batchId: string): Promise<void> {
|
|
291
|
+
await openaiRequest('POST', `/batches/${batchId}/cancel`)
|
|
292
|
+
},
|
|
293
|
+
|
|
294
|
+
async getResults(batchId: string): Promise<BatchResult[]> {
|
|
295
|
+
const status = await this.getStatus(batchId)
|
|
296
|
+
|
|
297
|
+
if (status.status !== 'completed' && status.status !== 'failed') {
|
|
298
|
+
throw new Error(`Batch not complete. Status: ${status.status}`)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const results: BatchResult[] = []
|
|
302
|
+
|
|
303
|
+
// Download and parse output file
|
|
304
|
+
if (status.outputFileId) {
|
|
305
|
+
const content = await downloadFile(status.outputFileId)
|
|
306
|
+
const lines = content.trim().split('\n')
|
|
307
|
+
|
|
308
|
+
for (const line of lines) {
|
|
309
|
+
const response: OpenAIBatchResponse = JSON.parse(line)
|
|
310
|
+
|
|
311
|
+
if (response.error) {
|
|
312
|
+
results.push({
|
|
313
|
+
id: response.custom_id,
|
|
314
|
+
customId: response.custom_id,
|
|
315
|
+
status: 'failed',
|
|
316
|
+
error: response.error.message,
|
|
317
|
+
})
|
|
318
|
+
} else if (response.response) {
|
|
319
|
+
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
|
+
results.push({
|
|
332
|
+
id: response.custom_id,
|
|
333
|
+
customId: response.custom_id,
|
|
334
|
+
status: 'completed',
|
|
335
|
+
result,
|
|
336
|
+
usage: {
|
|
337
|
+
promptTokens: response.response.body.usage.prompt_tokens,
|
|
338
|
+
completionTokens: response.response.body.usage.completion_tokens,
|
|
339
|
+
totalTokens: response.response.body.usage.total_tokens,
|
|
340
|
+
},
|
|
341
|
+
})
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Download and parse error file
|
|
347
|
+
if (status.errorFileId) {
|
|
348
|
+
const content = await downloadFile(status.errorFileId)
|
|
349
|
+
const lines = content.trim().split('\n')
|
|
350
|
+
|
|
351
|
+
for (const line of lines) {
|
|
352
|
+
const response: OpenAIBatchResponse = JSON.parse(line)
|
|
353
|
+
results.push({
|
|
354
|
+
id: response.custom_id,
|
|
355
|
+
customId: response.custom_id,
|
|
356
|
+
status: 'failed',
|
|
357
|
+
error: response.error?.message || 'Unknown error',
|
|
358
|
+
})
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return results
|
|
363
|
+
},
|
|
364
|
+
|
|
365
|
+
async waitForCompletion(batchId: string, pollInterval = 5000): Promise<BatchResult[]> {
|
|
366
|
+
while (true) {
|
|
367
|
+
const status = await this.getStatus(batchId)
|
|
368
|
+
|
|
369
|
+
if (status.status === 'completed' || status.status === 'failed') {
|
|
370
|
+
return this.getResults(batchId)
|
|
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
|
+
}
|
|
379
|
+
},
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// ============================================================================
|
|
383
|
+
// Helpers
|
|
384
|
+
// ============================================================================
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Simple Zod to JSON Schema converter
|
|
388
|
+
* In production, use a proper library like zod-to-json-schema
|
|
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).
|
|
435
|
+
*
|
|
436
|
+
* This is ideal for 5-500 items where you need results quickly but still want
|
|
437
|
+
* cost savings.
|
|
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.
|
|
442
|
+
*/
|
|
443
|
+
const openaiFlexAdapter: FlexAdapter = {
|
|
444
|
+
async submitFlex(items: BatchItem[], options: { model?: string }): Promise<BatchResult[]> {
|
|
445
|
+
const model = options.model || 'gpt-4o'
|
|
446
|
+
const CONCURRENCY = 10 // Higher concurrency for flex tier
|
|
447
|
+
|
|
448
|
+
const results: BatchResult[] = []
|
|
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
|
|
473
|
+
},
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Process a single item via OpenAI Chat Completions API
|
|
478
|
+
*/
|
|
479
|
+
async function processOpenAIItem(item: BatchItem, model: string): Promise<BatchResult> {
|
|
480
|
+
const messages: Array<{ role: string; content: string }> = []
|
|
481
|
+
|
|
482
|
+
if (item.options?.system) {
|
|
483
|
+
messages.push({ role: 'system', content: item.options.system })
|
|
484
|
+
}
|
|
485
|
+
messages.push({ role: 'user', content: item.prompt })
|
|
486
|
+
|
|
487
|
+
const body: Record<string, unknown> = {
|
|
488
|
+
model,
|
|
489
|
+
messages,
|
|
490
|
+
max_tokens: item.options?.maxTokens,
|
|
491
|
+
temperature: item.options?.temperature,
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Add JSON schema if provided
|
|
495
|
+
if (item.schema) {
|
|
496
|
+
const zodSchema = convertSchema(item.schema)
|
|
497
|
+
body.response_format = {
|
|
498
|
+
type: 'json_schema',
|
|
499
|
+
json_schema: {
|
|
500
|
+
name: 'response',
|
|
501
|
+
schema: zodToJsonSchema(zodSchema),
|
|
502
|
+
},
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const response = await fetch(`${openaiBaseUrl}/chat/completions`, {
|
|
507
|
+
method: 'POST',
|
|
508
|
+
headers: {
|
|
509
|
+
Authorization: `Bearer ${getApiKey()}`,
|
|
510
|
+
'Content-Type': 'application/json',
|
|
511
|
+
},
|
|
512
|
+
body: JSON.stringify(body),
|
|
513
|
+
})
|
|
514
|
+
|
|
515
|
+
if (!response.ok) {
|
|
516
|
+
const error = await response.text()
|
|
517
|
+
throw new Error(`OpenAI API error: ${response.status} ${error}`)
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const data = (await response.json()) as {
|
|
521
|
+
choices: Array<{ message: { content: string } }>
|
|
522
|
+
usage: { prompt_tokens: number; completion_tokens: number; total_tokens: number }
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
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
|
+
|
|
537
|
+
return {
|
|
538
|
+
id: item.id,
|
|
539
|
+
customId: item.id,
|
|
540
|
+
status: 'completed',
|
|
541
|
+
result,
|
|
542
|
+
usage: {
|
|
543
|
+
promptTokens: data.usage.prompt_tokens,
|
|
544
|
+
completionTokens: data.usage.completion_tokens,
|
|
545
|
+
totalTokens: data.usage.total_tokens,
|
|
546
|
+
},
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// ============================================================================
|
|
551
|
+
// Register Adapters
|
|
552
|
+
// ============================================================================
|
|
553
|
+
|
|
554
|
+
registerBatchAdapter('openai', openaiAdapter)
|
|
555
|
+
registerFlexAdapter('openai', openaiFlexAdapter)
|
|
556
|
+
|
|
557
|
+
export { openaiAdapter, openaiFlexAdapter }
|