Skip to content
6 changes: 6 additions & 0 deletions .changeset/pink-deers-switch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@ai-sdk/google-vertex': patch
'@ai-sdk/google': patch
---

feat: add provider option schemas for vertex imagegen and google genai
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ const model = google('gemini-1.5-pro-latest');
e.g. `tunedModels/my-model`.
</Note>

Google Generative AI models support also some model specific settings that are not part of the [standard call settings](/docs/ai-sdk-core/settings).
Google Generative AI also supports some model specific settings that are not part of the [standard call settings](/docs/ai-sdk-core/settings).
You can pass them as an options argument:

```ts
Expand Down Expand Up @@ -132,6 +132,29 @@ The following optional settings are available for Google Generative AI models:
- `BLOCK_ONLY_HIGH`
- `BLOCK_NONE`

Further configuration can be done using Google Generative AI provider options. You can validate the provider options using the `GoogleGenerativeAIProviderOptions` type.

```ts
import { google } from '@ai-sdk/google';
import { GoogleGenerativeAIProviderOptions } from '@ai-sdk/google';
import { generateText } from 'ai';

const { text } = await generateText({
model: google('gemini-1.5-pro-latest'),
providerOptions: {
google: {
responseModalities: ['TEXT', 'IMAGE'],
} satisfies GoogleGenerativeAIProviderOptions,
},
// ...
});
```

The following provider options are available:

- **responseModalities** _string[]_
The modalities to use for the response. The following modalities are supported: `TEXT`, `IMAGE`. When not defined or empty, the model defaults to returning only text.

You can use Google Generative AI language models to generate text with the `generateText` function:

```ts
Expand Down
35 changes: 35 additions & 0 deletions content/providers/01-ai-sdk-providers/16-google-vertex.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,41 @@ const { image } = await generateImage({
});
```

Further configuration can be done using Google Vertex provider options. You can validate the provider options using the `GoogleVertexImageProviderOptions` type.

```ts
import { vertex } from '@ai-sdk/google-vertex';
import { GoogleVertexImageProviderOptions } from '@ai-sdk/google-vertex';
import { generateImage } from 'ai';

const { image } = await generateImage({
model: vertex.image('imagen-3.0-generate-001'),
providerOptions: {
vertex: {
negativePrompt: 'pixelated, blurry, low-quality',
} satisfies GoogleVertexImageProviderOptions,
},
// ...
});
```

The following provider options are available:

- **negativePrompt** _string_
A description of what to discourage in the generated images.

- **personGeneration** `allow_adult` | `allow_all` | `dont_allow`
Whether to allow person generation. Defaults to `allow_adult`.

- **safetySetting** `block_low_and_above` | `block_medium_and_above` | `block_only_high` | `block_none`
Whether to block unsafe content. Defaults to `block_medium_and_above`.

- **addWatermark** _boolean_
Whether to add an invisible watermark to the generated images. Defaults to `true`.

- **storageUri** _string_
Cloud Storage URI to store the generated images.

<Note>
Imagen models do not support the `size` parameter. Use the `aspectRatio`
parameter instead.
Expand Down
10 changes: 6 additions & 4 deletions examples/ai-core/src/generate-image/google-vertex.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { vertex } from '@ai-sdk/google-vertex';
import {
GoogleVertexImageProviderOptions,
vertex,
} from '@ai-sdk/google-vertex';
import { experimental_generateImage as generateImage } from 'ai';
import { presentImages } from '../lib/present-image';
import 'dotenv/config';
import { presentImages } from '../lib/present-image';

async function main() {
const { image } = await generateImage({
Expand All @@ -10,9 +13,8 @@ async function main() {
aspectRatio: '1:1',
providerOptions: {
vertex: {
// https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/imagen-api#parameter_list
addWatermark: false,
},
} satisfies GoogleVertexImageProviderOptions,
},
});

Expand Down
7 changes: 6 additions & 1 deletion examples/ai-core/src/generate-text/google-image.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { google } from '@ai-sdk/google';
import { google, GoogleGenerativeAIProviderOptions } from '@ai-sdk/google';
import { generateText } from 'ai';
import 'dotenv/config';
import fs from 'node:fs';
Expand All @@ -15,6 +15,11 @@ async function main() {
],
},
],
providerOptions: {
google: {
responseModalities: ['TEXT', 'IMAGE'],
} satisfies GoogleGenerativeAIProviderOptions,
},
});

console.log(result.text);
Expand Down
66 changes: 36 additions & 30 deletions packages/google-vertex/src/google-vertex-image-model.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,27 +38,6 @@ describe('GoogleVertexImageModel', () => {
};
}

it('should pass the correct parameters', async () => {
prepareJsonResponse();

await model.doGenerate({
prompt,
n: 2,
size: undefined,
aspectRatio: undefined,
seed: undefined,
providerOptions: { vertex: { aspectRatio: '1:1' } },
});

expect(await server.calls[0].requestBody).toStrictEqual({
instances: [{ prompt }],
parameters: {
sampleCount: 2,
aspectRatio: '1:1',
},
});
});

it('should pass headers', async () => {
prepareJsonResponse();

Expand Down Expand Up @@ -143,13 +122,9 @@ describe('GoogleVertexImageModel', () => {
prompt: 'test prompt',
n: 1,
size: undefined,
aspectRatio: undefined,
aspectRatio: '16:9',
seed: undefined,
providerOptions: {
vertex: {
aspectRatio: '16:9',
},
},
providerOptions: {},
});

expect(await server.calls[0].requestBody).toStrictEqual({
Expand Down Expand Up @@ -214,7 +189,7 @@ describe('GoogleVertexImageModel', () => {
seed: 42,
providerOptions: {
vertex: {
temperature: 0.8,
addWatermark: false,
},
},
});
Expand All @@ -225,7 +200,7 @@ describe('GoogleVertexImageModel', () => {
sampleCount: 1,
aspectRatio: '1:1',
seed: 42,
temperature: 0.8,
addWatermark: false,
},
});
});
Expand Down Expand Up @@ -302,7 +277,7 @@ describe('GoogleVertexImageModel', () => {

const result = await model.doGenerate({
prompt,
n: 1,
n: 2,
size: undefined,
aspectRatio: undefined,
seed: undefined,
Expand All @@ -319,5 +294,36 @@ describe('GoogleVertexImageModel', () => {
);
expect(result.response.modelId).toBe('imagen-3.0-generate-001');
});

it('should only pass valid provider options', async () => {
prepareJsonResponse();

await model.doGenerate({
prompt,
n: 2,
size: undefined,
aspectRatio: '16:9',
seed: undefined,
providerOptions: {
vertex: {
addWatermark: false,
negativePrompt: 'negative prompt',
personGeneration: 'allow_all',
foo: 'bar',
},
},
});

expect(await server.calls[0].requestBody).toStrictEqual({
instances: [{ prompt }],
parameters: {
sampleCount: 2,
addWatermark: false,
negativePrompt: 'negative prompt',
personGeneration: 'allow_all',
aspectRatio: '16:9',
},
});
});
});
});
29 changes: 28 additions & 1 deletion packages/google-vertex/src/google-vertex-image-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
Resolvable,
combineHeaders,
createJsonResponseHandler,
parseProviderOptions,
postJsonToApi,
resolve,
} from '@ai-sdk/provider-utils';
Expand Down Expand Up @@ -65,13 +66,19 @@ export class GoogleVertexImageModel implements ImageModelV1 {
});
}

const vertexImageOptions = parseProviderOptions({
provider: 'vertex',
providerOptions,
schema: vertexImageProviderOptionsSchema,
});

const body = {
instances: [{ prompt }],
parameters: {
sampleCount: n,
...(aspectRatio != null ? { aspectRatio } : {}),
...(seed != null ? { seed } : {}),
...(providerOptions.vertex ?? {}),
...(vertexImageOptions ?? {}),
},
};

Expand Down Expand Up @@ -108,3 +115,23 @@ export class GoogleVertexImageModel implements ImageModelV1 {
const vertexImageResponseSchema = z.object({
predictions: z.array(z.object({ bytesBase64Encoded: z.string() })).nullish(),
});

const vertexImageProviderOptionsSchema = z.object({
negativePrompt: z.string().nullish(),
personGeneration: z
.enum(['dont_allow', 'allow_adult', 'allow_all'])
.nullish(),
safetySetting: z
.enum([
'block_low_and_above',
'block_medium_and_above',
'block_only_high',
'block_none',
])
.nullish(),
addWatermark: z.boolean().nullish(),
storageUri: z.string().nullish(),
});
export type GoogleVertexImageProviderOptions = z.infer<
typeof vertexImageProviderOptionsSchema
>;
1 change: 1 addition & 0 deletions packages/google-vertex/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export type { GoogleVertexImageProviderOptions } from './google-vertex-image-model';
export { createVertex, vertex } from './google-vertex-provider-node';
export type {
GoogleVertexProvider,
Expand Down
58 changes: 58 additions & 0 deletions packages/google/src/google-generative-ai-language-model.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,39 @@ describe('doGenerate', () => {
});
});

it('should only pass valid provider options', async () => {
prepareJsonResponse({});

await model.doGenerate({
inputFormat: 'prompt',
mode: { type: 'regular' },
prompt: [
{ role: 'system', content: 'test system instruction' },
{ role: 'user', content: [{ type: 'text', text: 'Hello' }] },
],
seed: 123,
temperature: 0.5,
providerMetadata: {
google: { foo: 'bar', responseModalities: ['TEXT', 'IMAGE'] },
},
});

expect(await server.calls[0].requestBody).toStrictEqual({
contents: [
{
role: 'user',
parts: [{ text: 'Hello' }],
},
],
systemInstruction: { parts: [{ text: 'test system instruction' }] },
generationConfig: {
seed: 123,
temperature: 0.5,
responseModalities: ['TEXT', 'IMAGE'],
},
});
});

it('should pass tools and toolChoice', async () => {
prepareJsonResponse({});

Expand Down Expand Up @@ -1869,4 +1902,29 @@ describe('doStream', () => {
'tool-calls',
);
});

it('should only pass valid provider options', async () => {
prepareStreamResponse({ content: [''] });

await model.doStream({
inputFormat: 'prompt',
mode: { type: 'regular' },
prompt: TEST_PROMPT,
providerMetadata: {
google: { foo: 'bar', responseModalities: ['TEXT', 'IMAGE'] },
},
});

expect(await server.calls[0].requestBody).toMatchObject({
contents: [
{
role: 'user',
parts: [{ text: 'Hello' }],
},
],
generationConfig: {
responseModalities: ['TEXT', 'IMAGE'],
},
});
});
});
11 changes: 8 additions & 3 deletions packages/google/src/google-generative-ai-language-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,7 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV1 {
const googleOptions = parseProviderOptions({
provider: 'google',
providerOptions: providerMetadata,
schema: z.object({
responseModalities: z.array(z.enum(['TEXT', 'IMAGE'])).nullish(),
}),
schema: googleGenerativeAIProviderOptionsSchema,
});

const generationConfig = {
Expand Down Expand Up @@ -623,3 +621,10 @@ const chunkSchema = z.object({
})
.nullish(),
});

const googleGenerativeAIProviderOptionsSchema = z.object({
responseModalities: z.array(z.enum(['TEXT', 'IMAGE'])).nullish(),
});
export type GoogleGenerativeAIProviderOptions = z.infer<
typeof googleGenerativeAIProviderOptionsSchema
>;
3 changes: 2 additions & 1 deletion packages/google/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export { createGoogleGenerativeAI, google } from './google-provider';
export type { GoogleErrorData } from './google-error';
export type { GoogleGenerativeAIProviderOptions } from './google-generative-ai-language-model';
export type { GoogleGenerativeAIProviderMetadata } from './google-generative-ai-prompt';
export { createGoogleGenerativeAI, google } from './google-provider';
export type {
GoogleGenerativeAIProvider,
GoogleGenerativeAIProviderSettings,
Expand Down
Loading