@ai-sdk/openai 4.0.65 → 4.0.66
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/CHANGELOG.md +15 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +179 -26
- package/dist/index.js.map +1 -1
- package/dist/internal/index.d.ts +7 -0
- package/dist/internal/index.js +164 -24
- package/dist/internal/index.js.map +1 -1
- package/package.json +3 -3
- package/src/chat/openai-chat-language-model.ts +11 -2
- package/src/chat/openai-chat-prepare-tools.ts +9 -2
- package/src/normalize-openai-json-schema.ts +161 -0
- package/src/openai-batch.ts +16 -0
- package/src/openai-config.ts +7 -0
- package/src/responses/openai-responses-language-model-options.ts +9 -0
- package/src/responses/openai-responses-language-model.ts +16 -3
- package/src/responses/openai-responses-prepare-tools.ts +16 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/openai",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.66",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
}
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@ai-sdk/provider": "4.0.
|
|
39
|
-
"@ai-sdk/provider-utils": "5.0.
|
|
38
|
+
"@ai-sdk/provider": "4.0.14",
|
|
39
|
+
"@ai-sdk/provider-utils": "5.0.40"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@ai-sdk/test-server": "2.0.1",
|
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
type OpenAIChatModelId,
|
|
49
49
|
} from './openai-chat-language-model-options';
|
|
50
50
|
import { prepareChatTools } from './openai-chat-prepare-tools';
|
|
51
|
+
import { normalizeOpenAIJsonSchema } from '../normalize-openai-json-schema';
|
|
51
52
|
|
|
52
53
|
type OpenAIChatConfig = {
|
|
53
54
|
provider: string;
|
|
@@ -159,6 +160,14 @@ export class OpenAIChatLanguageModel implements LanguageModelV4 {
|
|
|
159
160
|
warnings.push(...messageWarnings);
|
|
160
161
|
|
|
161
162
|
const strictJsonSchema = openaiOptions.strictJsonSchema ?? true;
|
|
163
|
+
const normalizedResponseFormatSchema =
|
|
164
|
+
responseFormat?.type === 'json' && responseFormat.schema != null
|
|
165
|
+
? normalizeOpenAIJsonSchema(responseFormat.schema)
|
|
166
|
+
: undefined;
|
|
167
|
+
|
|
168
|
+
if (normalizedResponseFormatSchema != null) {
|
|
169
|
+
warnings.push(...normalizedResponseFormatSchema.warnings);
|
|
170
|
+
}
|
|
162
171
|
|
|
163
172
|
const baseArgs = {
|
|
164
173
|
// model id:
|
|
@@ -190,11 +199,11 @@ export class OpenAIChatLanguageModel implements LanguageModelV4 {
|
|
|
190
199
|
presence_penalty: presencePenalty,
|
|
191
200
|
response_format:
|
|
192
201
|
responseFormat?.type === 'json'
|
|
193
|
-
?
|
|
202
|
+
? normalizedResponseFormatSchema != null
|
|
194
203
|
? {
|
|
195
204
|
type: 'json_schema',
|
|
196
205
|
json_schema: {
|
|
197
|
-
schema:
|
|
206
|
+
schema: normalizedResponseFormatSchema.schema,
|
|
198
207
|
strict: strictJsonSchema,
|
|
199
208
|
name: responseFormat.name ?? 'response',
|
|
200
209
|
description: responseFormat.description,
|
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
OpenAIChatToolChoice,
|
|
8
8
|
OpenAIChatFunctionTool,
|
|
9
9
|
} from './openai-chat-api';
|
|
10
|
+
import { normalizeOpenAIJsonSchema } from '../normalize-openai-json-schema';
|
|
10
11
|
|
|
11
12
|
export function prepareChatTools({
|
|
12
13
|
tools,
|
|
@@ -32,17 +33,23 @@ export function prepareChatTools({
|
|
|
32
33
|
|
|
33
34
|
for (const tool of tools) {
|
|
34
35
|
switch (tool.type) {
|
|
35
|
-
case 'function':
|
|
36
|
+
case 'function': {
|
|
37
|
+
const normalizedInputSchema = normalizeOpenAIJsonSchema(
|
|
38
|
+
tool.inputSchema,
|
|
39
|
+
);
|
|
40
|
+
toolWarnings.push(...normalizedInputSchema.warnings);
|
|
41
|
+
|
|
36
42
|
openaiTools.push({
|
|
37
43
|
type: 'function',
|
|
38
44
|
function: {
|
|
39
45
|
name: tool.name,
|
|
40
46
|
description: tool.description,
|
|
41
|
-
parameters:
|
|
47
|
+
parameters: normalizedInputSchema.schema,
|
|
42
48
|
...(tool.strict != null ? { strict: tool.strict } : {}),
|
|
43
49
|
},
|
|
44
50
|
});
|
|
45
51
|
break;
|
|
52
|
+
}
|
|
46
53
|
default:
|
|
47
54
|
toolWarnings.push({
|
|
48
55
|
type: 'unsupported',
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import {
|
|
2
|
+
UnsupportedFunctionalityError,
|
|
3
|
+
type JSONSchema7,
|
|
4
|
+
type JSONSchema7Definition,
|
|
5
|
+
type SharedV4Warning,
|
|
6
|
+
} from '@ai-sdk/provider';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Normalizes JSON Schema for OpenAI structured outputs.
|
|
10
|
+
*
|
|
11
|
+
* OpenAI does not support the JSON Schema `propertyNames` keyword. Property
|
|
12
|
+
* names in JSON objects are always strings, so string-based constraints can be
|
|
13
|
+
* left to client-side validation after removing the keyword. This
|
|
14
|
+
* compatibility layer does not rewrite non-string property name schemas.
|
|
15
|
+
*/
|
|
16
|
+
export function normalizeOpenAIJsonSchema(schema: JSONSchema7): {
|
|
17
|
+
schema: JSONSchema7;
|
|
18
|
+
warnings: SharedV4Warning[];
|
|
19
|
+
} {
|
|
20
|
+
let removedPropertyNames = false;
|
|
21
|
+
|
|
22
|
+
const normalizedSchema = normalizeSchema(schema);
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
schema: normalizedSchema,
|
|
26
|
+
warnings: removedPropertyNames
|
|
27
|
+
? [
|
|
28
|
+
{
|
|
29
|
+
type: 'compatibility',
|
|
30
|
+
feature: 'JSON Schema propertyNames',
|
|
31
|
+
details:
|
|
32
|
+
'OpenAI does not support JSON Schema propertyNames. It was removed before sending the schema, so OpenAI will not enforce property-name constraints.',
|
|
33
|
+
},
|
|
34
|
+
]
|
|
35
|
+
: [],
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
function normalizeSchema(schema: JSONSchema7): JSONSchema7 {
|
|
39
|
+
const propertyNames = schema.propertyNames;
|
|
40
|
+
|
|
41
|
+
if (propertyNames != null) {
|
|
42
|
+
if (
|
|
43
|
+
typeof propertyNames === 'boolean' ||
|
|
44
|
+
propertyNames.type !== 'string'
|
|
45
|
+
) {
|
|
46
|
+
throw new UnsupportedFunctionalityError({
|
|
47
|
+
functionality:
|
|
48
|
+
'JSON Schema propertyNames that does not use a string schema',
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
removedPropertyNames = true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const normalizedSchema = { ...schema };
|
|
56
|
+
delete normalizedSchema.propertyNames;
|
|
57
|
+
|
|
58
|
+
if (normalizedSchema.properties != null) {
|
|
59
|
+
normalizedSchema.properties = normalizeSchemaRecord(
|
|
60
|
+
normalizedSchema.properties,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (normalizedSchema.patternProperties != null) {
|
|
65
|
+
normalizedSchema.patternProperties = normalizeSchemaRecord(
|
|
66
|
+
normalizedSchema.patternProperties,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (normalizedSchema.additionalProperties != null) {
|
|
71
|
+
normalizedSchema.additionalProperties = normalizeDefinition(
|
|
72
|
+
normalizedSchema.additionalProperties,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (normalizedSchema.additionalItems != null) {
|
|
77
|
+
normalizedSchema.additionalItems = normalizeDefinition(
|
|
78
|
+
normalizedSchema.additionalItems,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (normalizedSchema.items != null) {
|
|
83
|
+
normalizedSchema.items = Array.isArray(normalizedSchema.items)
|
|
84
|
+
? normalizedSchema.items.map(normalizeDefinition)
|
|
85
|
+
: normalizeDefinition(normalizedSchema.items);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (normalizedSchema.contains != null) {
|
|
89
|
+
normalizedSchema.contains = normalizeDefinition(
|
|
90
|
+
normalizedSchema.contains,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (normalizedSchema.not != null) {
|
|
95
|
+
normalizedSchema.not = normalizeDefinition(normalizedSchema.not);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (normalizedSchema.allOf != null) {
|
|
99
|
+
normalizedSchema.allOf = normalizedSchema.allOf.map(normalizeDefinition);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (normalizedSchema.anyOf != null) {
|
|
103
|
+
normalizedSchema.anyOf = normalizedSchema.anyOf.map(normalizeDefinition);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (normalizedSchema.oneOf != null) {
|
|
107
|
+
normalizedSchema.oneOf = normalizedSchema.oneOf.map(normalizeDefinition);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (normalizedSchema.definitions != null) {
|
|
111
|
+
normalizedSchema.definitions = normalizeSchemaRecord(
|
|
112
|
+
normalizedSchema.definitions,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (normalizedSchema.$defs != null) {
|
|
117
|
+
normalizedSchema.$defs = normalizeSchemaRecord(normalizedSchema.$defs);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (normalizedSchema.dependencies != null) {
|
|
121
|
+
normalizedSchema.dependencies = Object.fromEntries(
|
|
122
|
+
Object.entries(normalizedSchema.dependencies).map(
|
|
123
|
+
([key, dependency]) => [
|
|
124
|
+
key,
|
|
125
|
+
Array.isArray(dependency)
|
|
126
|
+
? dependency
|
|
127
|
+
: normalizeDefinition(dependency),
|
|
128
|
+
],
|
|
129
|
+
),
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
for (const keyword of ['if', 'then', 'else'] as const) {
|
|
134
|
+
const conditionalSchema = normalizedSchema[keyword];
|
|
135
|
+
if (conditionalSchema != null) {
|
|
136
|
+
normalizedSchema[keyword] = normalizeDefinition(conditionalSchema);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return normalizedSchema;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function normalizeSchemaRecord(
|
|
144
|
+
schemas: Record<string, JSONSchema7Definition>,
|
|
145
|
+
): Record<string, JSONSchema7Definition> {
|
|
146
|
+
return Object.fromEntries(
|
|
147
|
+
Object.entries(schemas).map(([key, schema]) => [
|
|
148
|
+
key,
|
|
149
|
+
normalizeDefinition(schema),
|
|
150
|
+
]),
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function normalizeDefinition(
|
|
155
|
+
definition: JSONSchema7Definition,
|
|
156
|
+
): JSONSchema7Definition {
|
|
157
|
+
return typeof definition === 'boolean'
|
|
158
|
+
? definition
|
|
159
|
+
: normalizeSchema(definition);
|
|
160
|
+
}
|
|
161
|
+
}
|
package/src/openai-batch.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
InvalidArgumentError,
|
|
3
3
|
InvalidResponseDataError,
|
|
4
|
+
UnsupportedFunctionalityError,
|
|
4
5
|
type Experimental_BatchV4 as BatchV4,
|
|
5
6
|
type Experimental_BatchV4CancelResult as BatchV4CancelResult,
|
|
6
7
|
type Experimental_BatchV4StartResult as BatchV4StartResult,
|
|
@@ -89,6 +90,20 @@ type OpenAIBatchResultConversion =
|
|
|
89
90
|
| { success: true; result: LanguageModelV4GenerateResult }
|
|
90
91
|
| { success: false; error: BatchV4Error };
|
|
91
92
|
|
|
93
|
+
function assertTextBatchRequests(
|
|
94
|
+
requests: BatchV4StartOptions['requests'],
|
|
95
|
+
): asserts requests is ReadonlyArray<OpenAIBatchRequest> {
|
|
96
|
+
for (const request of requests) {
|
|
97
|
+
const requestType = request.type;
|
|
98
|
+
if (requestType !== 'text') {
|
|
99
|
+
throw new UnsupportedFunctionalityError({
|
|
100
|
+
functionality: `batch request type: ${requestType}`,
|
|
101
|
+
message: `The OpenAI Batch API does not support batch requests with type "${requestType}".`,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
92
107
|
const openaiBatchResponseZodSchema = () =>
|
|
93
108
|
z.object({
|
|
94
109
|
id: z.string(),
|
|
@@ -174,6 +189,7 @@ export class OpenAIBatch implements BatchV4<OpenAIBatchModelIds> {
|
|
|
174
189
|
async doStartBatch(
|
|
175
190
|
options: BatchV4StartOptions<OpenAIBatchModelIds>,
|
|
176
191
|
): Promise<BatchV4StartResult> {
|
|
192
|
+
assertTextBatchRequests(options.requests);
|
|
177
193
|
validateSingleModel(options.requests);
|
|
178
194
|
|
|
179
195
|
const fileParts: string[] = [];
|
package/src/openai-config.ts
CHANGED
|
@@ -24,6 +24,13 @@ export type OpenAIConfig = {
|
|
|
24
24
|
* @see https://github.com/vercel/ai/issues/20180
|
|
25
25
|
*/
|
|
26
26
|
explicitMessageItemType?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Whether the provider supports the
|
|
29
|
+
* `web_search_call.action.sources` Responses API include value.
|
|
30
|
+
*
|
|
31
|
+
* Defaults to `true`.
|
|
32
|
+
*/
|
|
33
|
+
supportsWebSearchSourcesInclude?: boolean;
|
|
27
34
|
/**
|
|
28
35
|
* This is soft-deprecated. Use provider references (e.g. `{ openai: 'file-abc123' }`)
|
|
29
36
|
* in file part data instead. File ID prefixes used to identify file IDs
|
|
@@ -179,6 +179,15 @@ export const openaiLanguageModelResponsesOptionsSchema = lazySchema(() =>
|
|
|
179
179
|
)
|
|
180
180
|
.nullish(),
|
|
181
181
|
|
|
182
|
+
/**
|
|
183
|
+
* Whether to automatically include web search action sources in the
|
|
184
|
+
* response. Disable this for OpenAI-compatible providers that do not
|
|
185
|
+
* support the `web_search_call.action.sources` include value.
|
|
186
|
+
*
|
|
187
|
+
* Defaults to `true`.
|
|
188
|
+
*/
|
|
189
|
+
includeWebSearchSources: z.boolean().optional(),
|
|
190
|
+
|
|
182
191
|
/**
|
|
183
192
|
* Instructions for the model.
|
|
184
193
|
* They can be used to change the system or developer message when continuing a conversation using the `previousResponseId` option.
|
|
@@ -70,6 +70,7 @@ import {
|
|
|
70
70
|
isUndeclaredParallelToolCall,
|
|
71
71
|
} from './expand-parallel-tool-call';
|
|
72
72
|
import { mapOpenAIResponseFinishReason } from './map-openai-responses-finish-reason';
|
|
73
|
+
import { normalizeOpenAIJsonSchema } from '../normalize-openai-json-schema';
|
|
73
74
|
import {
|
|
74
75
|
openaiResponsesChunkSchema,
|
|
75
76
|
openaiResponsesResponseSchema,
|
|
@@ -444,6 +445,14 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
|
|
|
444
445
|
}
|
|
445
446
|
|
|
446
447
|
const strictJsonSchema = openaiOptions?.strictJsonSchema ?? true;
|
|
448
|
+
const normalizedResponseFormatSchema =
|
|
449
|
+
responseFormat?.type === 'json' && responseFormat.schema != null
|
|
450
|
+
? normalizeOpenAIJsonSchema(responseFormat.schema)
|
|
451
|
+
: undefined;
|
|
452
|
+
|
|
453
|
+
if (normalizedResponseFormatSchema != null) {
|
|
454
|
+
warnings.push(...normalizedResponseFormatSchema.warnings);
|
|
455
|
+
}
|
|
447
456
|
|
|
448
457
|
let include: OpenAIResponsesIncludeOptions = openaiOptions?.include;
|
|
449
458
|
|
|
@@ -486,7 +495,11 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
|
|
|
486
495
|
) as LanguageModelV4ProviderTool | undefined
|
|
487
496
|
)?.name;
|
|
488
497
|
|
|
489
|
-
if (
|
|
498
|
+
if (
|
|
499
|
+
webSearchToolName &&
|
|
500
|
+
config.supportsWebSearchSourcesInclude !== false &&
|
|
501
|
+
openaiOptions?.includeWebSearchSources !== false
|
|
502
|
+
) {
|
|
490
503
|
addInclude('web_search_call.action.sources');
|
|
491
504
|
}
|
|
492
505
|
|
|
@@ -513,13 +526,13 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
|
|
|
513
526
|
text: {
|
|
514
527
|
...(responseFormat?.type === 'json' && {
|
|
515
528
|
format:
|
|
516
|
-
|
|
529
|
+
normalizedResponseFormatSchema != null
|
|
517
530
|
? {
|
|
518
531
|
type: 'json_schema',
|
|
519
532
|
strict: strictJsonSchema,
|
|
520
533
|
name: responseFormat.name ?? 'response',
|
|
521
534
|
description: responseFormat.description,
|
|
522
|
-
schema:
|
|
535
|
+
schema: normalizedResponseFormatSchema.schema,
|
|
523
536
|
}
|
|
524
537
|
: { type: 'json_object' },
|
|
525
538
|
}),
|
|
@@ -21,6 +21,7 @@ import { shellArgsSchema } from '../tool/shell';
|
|
|
21
21
|
import { toolSearchArgsSchema } from '../tool/tool-search';
|
|
22
22
|
import { webSearchArgsSchema } from '../tool/web-search';
|
|
23
23
|
import { webSearchPreviewArgsSchema } from '../tool/web-search-preview';
|
|
24
|
+
import { normalizeOpenAIJsonSchema } from '../normalize-openai-json-schema';
|
|
24
25
|
import type {
|
|
25
26
|
OpenAIResponsesAllowedTool,
|
|
26
27
|
OpenAIResponsesFunctionTool,
|
|
@@ -147,6 +148,7 @@ export async function prepareResponsesTools({
|
|
|
147
148
|
const openaiFunctionTool = prepareFunctionTool({
|
|
148
149
|
tool,
|
|
149
150
|
options: openaiOptions,
|
|
151
|
+
toolWarnings,
|
|
150
152
|
async: resolveAsyncToolOption({
|
|
151
153
|
value: openaiOptions?.async,
|
|
152
154
|
supportsAsyncToolCalling,
|
|
@@ -627,19 +629,31 @@ function toAllowedToolResolution(
|
|
|
627
629
|
function prepareFunctionTool({
|
|
628
630
|
tool,
|
|
629
631
|
options,
|
|
632
|
+
toolWarnings,
|
|
630
633
|
async,
|
|
631
634
|
}: {
|
|
632
635
|
tool: LanguageModelV4FunctionTool;
|
|
633
636
|
options: OpenAIToolOptions | undefined;
|
|
637
|
+
toolWarnings: SharedV4Warning[];
|
|
634
638
|
async: boolean | undefined;
|
|
635
639
|
}): OpenAIResponsesFunctionTool {
|
|
636
640
|
const deferLoading = options?.deferLoading;
|
|
641
|
+
const normalizedInputSchema = normalizeOpenAIJsonSchema(tool.inputSchema);
|
|
642
|
+
const normalizedOutputSchema =
|
|
643
|
+
options?.outputSchema != null
|
|
644
|
+
? normalizeOpenAIJsonSchema(options.outputSchema as JSONSchema7)
|
|
645
|
+
: undefined;
|
|
646
|
+
|
|
647
|
+
toolWarnings.push(
|
|
648
|
+
...normalizedInputSchema.warnings,
|
|
649
|
+
...(normalizedOutputSchema?.warnings ?? []),
|
|
650
|
+
);
|
|
637
651
|
|
|
638
652
|
return {
|
|
639
653
|
type: 'function',
|
|
640
654
|
name: tool.name,
|
|
641
655
|
description: tool.description,
|
|
642
|
-
parameters:
|
|
656
|
+
parameters: normalizedInputSchema.schema,
|
|
643
657
|
...(async != null ? { async } : {}),
|
|
644
658
|
...(tool.strict != null ? { strict: tool.strict } : {}),
|
|
645
659
|
...(deferLoading != null ? { defer_loading: deferLoading } : {}),
|
|
@@ -647,7 +661,7 @@ function prepareFunctionTool({
|
|
|
647
661
|
? { allowed_callers: options.allowedCallers }
|
|
648
662
|
: {}),
|
|
649
663
|
...(options?.outputSchema != null
|
|
650
|
-
? { output_schema:
|
|
664
|
+
? { output_schema: normalizedOutputSchema?.schema }
|
|
651
665
|
: {}),
|
|
652
666
|
};
|
|
653
667
|
}
|