Skip to content

Commit 61545c4

Browse files
Backport: feat (provider/openai): include more image generation response metadata (#10783)
This is an automated backport of #10698 to the release-v5.0 branch. FYI @shaper --------- Co-authored-by: Walter Korman <[email protected]>
1 parent 7cd85a8 commit 61545c4

File tree

7 files changed

+239
-21
lines changed

7 files changed

+239
-21
lines changed

.changeset/heavy-suits-do.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@ai-sdk/openai': patch
3+
---
4+
5+
feat (provider/openai): include more image generation response metadata

content/providers/01-ai-sdk-providers/03-openai.mdx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1408,7 +1408,20 @@ const { image, providerMetadata } = await generateImage({
14081408

14091409
For more on `generateImage()` see [Image Generation](/docs/ai-sdk-core/image-generation).
14101410

1411-
OpenAI's image models may return a revised prompt for each image. It can be access at `providerMetadata.openai.images[0]?.revisedPrompt`.
1411+
OpenAI's image models return additional metadata in the response that can be
1412+
accessed via `providerMetadata.openai`. The following OpenAI-specific metadata
1413+
is available:
1414+
1415+
- **images** _Array&lt;object&gt;_
1416+
1417+
Array of image-specific metadata. Each image object may contain:
1418+
1419+
- `revisedPrompt` _string_ - The revised prompt that was actually used to generate the image (OpenAI may modify your prompt for safety or clarity)
1420+
- `created` _number_ - The Unix timestamp (in seconds) of when the image was created
1421+
- `size` _string_ - The size of the generated image. One of `1024x1024`, `1024x1536`, or `1536x1024`
1422+
- `quality` _string_ - The quality of the generated image. One of `low`, `medium`, or `high`
1423+
- `background` _string_ - The background parameter used for the image generation. Either `transparent` or `opaque`
1424+
- `outputFormat` _string_ - The output format of the generated image. One of `png`, `webp`, or `jpeg`
14121425

14131426
For more information on the available OpenAI image model options, see the [OpenAI API reference](https://platform.openai.com/docs/api-reference/images/create).
14141427

examples/ai-core/src/generate-image/google-vertex.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import 'dotenv/config';
77
import { presentImages } from '../lib/present-image';
88

99
async function main() {
10-
const { image } = await generateImage({
10+
const result = await generateImage({
1111
model: vertex.image('imagen-3.0-generate-002'),
1212
prompt: 'A burrito launched through a tunnel',
1313
aspectRatio: '1:1',
@@ -18,7 +18,12 @@ async function main() {
1818
},
1919
});
2020

21-
await presentImages([image]);
21+
await presentImages(result.images);
22+
23+
console.log(
24+
'Provider metadata:',
25+
JSON.stringify(result.providerMetadata, null, 2),
26+
);
2227
}
2328

2429
main().catch(console.error);

examples/ai-core/src/generate-image/openai.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,19 @@ import { presentImages } from '../lib/present-image';
44
import 'dotenv/config';
55

66
async function main() {
7-
const prompt = 'Santa Claus driving a Cadillac';
7+
const prompt = 'A blue cream Persian cat in Kyoto in the style of ukiyo-e';
88
const result = await generateImage({
99
model: openai.image('gpt-image-1-mini'),
1010
prompt,
11+
n: 3,
1112
});
1213

13-
// @ts-expect-error
14-
const revisedPrompt = result.providerMetadata.openai.images[0]?.revisedPrompt;
14+
await presentImages(result.images);
1515

16-
console.log({
17-
prompt,
18-
revisedPrompt,
19-
});
20-
21-
await presentImages([result.image]);
16+
console.log(
17+
'Provider metadata:',
18+
JSON.stringify(result.providerMetadata, null, 2),
19+
);
2220
}
2321

2422
main().catch(console.error);

packages/openai/src/image/openai-image-api.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,30 @@ import { z } from 'zod/v4';
66
export const openaiImageResponseSchema = lazyValidator(() =>
77
zodSchema(
88
z.object({
9+
created: z.number().nullish(),
910
data: z.array(
1011
z.object({
1112
b64_json: z.string(),
1213
revised_prompt: z.string().nullish(),
1314
}),
1415
),
16+
background: z.string().nullish(),
17+
output_format: z.string().nullish(),
18+
size: z.string().nullish(),
19+
quality: z.string().nullish(),
20+
usage: z
21+
.object({
22+
input_tokens: z.number().nullish(),
23+
output_tokens: z.number().nullish(),
24+
total_tokens: z.number().nullish(),
25+
input_tokens_details: z
26+
.object({
27+
image_tokens: z.number().nullish(),
28+
text_tokens: z.number().nullish(),
29+
})
30+
.nullish(),
31+
})
32+
.nullish(),
1533
}),
1634
),
1735
);

packages/openai/src/image/openai-image-model.test.ts

Lines changed: 174 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,11 @@ describe('doGenerate', () => {
265265
expect(result.warnings).toStrictEqual([]);
266266
expect(result.providerMetadata).toStrictEqual({
267267
openai: {
268-
images: [null],
268+
images: [
269+
{
270+
created: 1733837122,
271+
},
272+
],
269273
},
270274
});
271275
});
@@ -305,8 +309,176 @@ describe('doGenerate', () => {
305309
{
306310
revisedPrompt:
307311
'A charming visual illustration of a baby sea otter swimming joyously.',
312+
created: 1733837122,
313+
},
314+
{
315+
created: 1733837122,
316+
},
317+
],
318+
},
319+
});
320+
});
321+
322+
it('should include all metadata fields when present in response', async () => {
323+
server.urls['https://api.openai.com/v1/images/generations'].response = {
324+
type: 'json-value',
325+
body: {
326+
created: 1733837122,
327+
size: '1024x1024',
328+
quality: 'hd',
329+
background: 'transparent',
330+
output_format: 'png',
331+
data: [
332+
{
333+
revised_prompt: 'A detailed illustration of a cat',
334+
b64_json: 'base64-image-1',
335+
},
336+
],
337+
},
338+
};
339+
340+
const result = await model.doGenerate({
341+
prompt,
342+
n: 1,
343+
size: '1024x1024',
344+
aspectRatio: undefined,
345+
seed: undefined,
346+
providerOptions: {},
347+
});
348+
349+
expect(result.providerMetadata).toStrictEqual({
350+
openai: {
351+
images: [
352+
{
353+
revisedPrompt: 'A detailed illustration of a cat',
354+
created: 1733837122,
355+
size: '1024x1024',
356+
quality: 'hd',
357+
background: 'transparent',
358+
outputFormat: 'png',
359+
},
360+
],
361+
},
362+
});
363+
});
364+
365+
it('should handle response with no metadata fields', async () => {
366+
server.urls['https://api.openai.com/v1/images/generations'].response = {
367+
type: 'json-value',
368+
body: {
369+
data: [
370+
{
371+
b64_json: 'base64-image-1',
372+
},
373+
],
374+
},
375+
};
376+
377+
const result = await model.doGenerate({
378+
prompt,
379+
n: 1,
380+
size: undefined,
381+
aspectRatio: undefined,
382+
seed: undefined,
383+
providerOptions: {},
384+
});
385+
386+
expect(result.providerMetadata).toStrictEqual({
387+
openai: {
388+
images: [{}],
389+
},
390+
});
391+
});
392+
393+
it('should handle multiple images with mixed metadata', async () => {
394+
server.urls['https://api.openai.com/v1/images/generations'].response = {
395+
type: 'json-value',
396+
body: {
397+
created: 1733837122,
398+
size: '1024x1024',
399+
quality: 'hd',
400+
data: [
401+
{
402+
revised_prompt: 'First image prompt',
403+
b64_json: 'base64-image-1',
404+
},
405+
{
406+
b64_json: 'base64-image-2',
407+
},
408+
{
409+
revised_prompt: 'Third image prompt',
410+
b64_json: 'base64-image-3',
411+
},
412+
],
413+
},
414+
};
415+
416+
const result = await model.doGenerate({
417+
prompt,
418+
n: 3,
419+
size: '1024x1024',
420+
aspectRatio: undefined,
421+
seed: undefined,
422+
providerOptions: {},
423+
});
424+
425+
expect(result.providerMetadata).toStrictEqual({
426+
openai: {
427+
images: [
428+
{
429+
revisedPrompt: 'First image prompt',
430+
created: 1733837122,
431+
size: '1024x1024',
432+
quality: 'hd',
433+
},
434+
{
435+
created: 1733837122,
436+
size: '1024x1024',
437+
quality: 'hd',
438+
},
439+
{
440+
revisedPrompt: 'Third image prompt',
441+
created: 1733837122,
442+
size: '1024x1024',
443+
quality: 'hd',
444+
},
445+
],
446+
},
447+
});
448+
});
449+
450+
it('should handle jpeg output format', async () => {
451+
server.urls['https://api.openai.com/v1/images/generations'].response = {
452+
type: 'json-value',
453+
body: {
454+
created: 1733837122,
455+
output_format: 'jpeg',
456+
quality: 'standard',
457+
data: [
458+
{
459+
b64_json: 'base64-image-1',
460+
},
461+
],
462+
},
463+
};
464+
465+
const result = await model.doGenerate({
466+
prompt,
467+
n: 1,
468+
size: undefined,
469+
aspectRatio: undefined,
470+
seed: undefined,
471+
providerOptions: {},
472+
});
473+
474+
expect(result.providerMetadata).toStrictEqual({
475+
openai: {
476+
images: [
477+
{
478+
created: 1733837122,
479+
quality: 'standard',
480+
outputFormat: 'jpeg',
308481
},
309-
null,
310482
],
311483
},
312484
});

packages/openai/src/image/openai-image-model.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -97,13 +97,20 @@ export class OpenAIImageModel implements ImageModelV2 {
9797
},
9898
providerMetadata: {
9999
openai: {
100-
images: response.data.map(item =>
101-
item.revised_prompt
102-
? {
103-
revisedPrompt: item.revised_prompt,
104-
}
105-
: null,
106-
),
100+
images: response.data.map(item => ({
101+
...(item.revised_prompt
102+
? { revisedPrompt: item.revised_prompt }
103+
: {}),
104+
...(response.created != null ? { created: response.created } : {}),
105+
...(response.size != null ? { size: response.size } : {}),
106+
...(response.quality != null ? { quality: response.quality } : {}),
107+
...(response.background != null
108+
? { background: response.background }
109+
: {}),
110+
...(response.output_format != null
111+
? { outputFormat: response.output_format }
112+
: {}),
113+
})),
107114
},
108115
},
109116
};

0 commit comments

Comments
 (0)