Skip to content

Commit 2f7d612

Browse files
authored
feat(js/plugins/googleai): added support for imagen and veo models to googleai plugin (#3024)
1 parent 14b2c87 commit 2f7d612

File tree

23 files changed

+1195
-78
lines changed

23 files changed

+1195
-78
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ js/testapps/personal-testing
4242
# Test files
4343
last_recording.mp4
4444
js/testapps/flow-simple-ai/*.wav
45+
js/testapps/flow-simple-ai/meme-of-the-day.mp4
4546

4647
# auto-generated
4748
/js/core/src/__codegen

genkit-tools/common/src/types/model.ts

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ export const ModelInfoSchema = z.object({
122122
constrained: z.enum(['none', 'all', 'no-tools']).optional(),
123123
/** Model supports controlling tool choice, e.g. forced tool calling. */
124124
toolChoice: z.boolean().optional(),
125+
/** Model supports long running operation interface. */
126+
longRunning: z.boolean().optional(),
125127
})
126128
.optional(),
127129
/** At which stage of development this model is.
@@ -194,6 +196,40 @@ export const OutputConfigSchema = z.object({
194196
contentType: z.string().optional(),
195197
});
196198

199+
/** Model response finish reason enum. */
200+
export const FinishReasonSchema = z.enum([
201+
'stop',
202+
'length',
203+
'blocked',
204+
'interrupted',
205+
'pending',
206+
'other',
207+
'unknown',
208+
]);
209+
210+
/**
211+
* Zod schema of a long running operation.
212+
*/
213+
export const ModelOperationSchema = z
214+
.object({
215+
name: z.string(),
216+
done: z.boolean().optional(),
217+
request: z
218+
.object({
219+
model: z.string(),
220+
config: z.record(z.string(), z.any()).optional(),
221+
})
222+
.optional(),
223+
response: z
224+
.object({
225+
message: MessageSchema.optional(),
226+
finishReason: FinishReasonSchema,
227+
raw: z.unknown(),
228+
})
229+
.optional(),
230+
})
231+
.passthrough();
232+
197233
/**
198234
* Output config.
199235
*/
@@ -207,7 +243,9 @@ export const ModelRequestSchema = z.object({
207243
toolChoice: z.enum(['auto', 'required', 'none']).optional(),
208244
output: OutputConfigSchema.optional(),
209245
docs: z.array(DocumentDataSchema).optional(),
246+
operation: ModelOperationSchema.optional(),
210247
});
248+
211249
/** ModelRequest represents the parameters that are passed to a model when generating content. */
212250
export interface ModelRequest<
213251
CustomOptionsSchema extends z.ZodTypeAny = z.ZodTypeAny,
@@ -261,16 +299,6 @@ export const GenerationUsageSchema = z.object({
261299
*/
262300
export type GenerationUsage = z.infer<typeof GenerationUsageSchema>;
263301

264-
/** Model response finish reason enum. */
265-
export const FinishReasonSchema = z.enum([
266-
'stop',
267-
'length',
268-
'blocked',
269-
'interrupted',
270-
'other',
271-
'unknown',
272-
]);
273-
274302
/** @deprecated All responses now return a single candidate. Only the first candidate will be used if supplied. */
275303
export const CandidateSchema = z.object({
276304
index: z.number(),
@@ -305,6 +333,7 @@ export const ModelResponseSchema = z.object({
305333
custom: z.unknown(),
306334
raw: z.unknown(),
307335
request: GenerateRequestSchema.optional(),
336+
operation: ModelOperationSchema.optional(),
308337
});
309338

310339
/**

genkit-tools/genkit-schema.json

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,7 @@
602602
"length",
603603
"blocked",
604604
"interrupted",
605+
"pending",
605606
"other",
606607
"unknown"
607608
]
@@ -732,6 +733,9 @@
732733
"$ref": "#/$defs/DocumentData"
733734
}
734735
},
736+
"operation": {
737+
"$ref": "#/$defs/ModelOperation"
738+
},
735739
"candidates": {
736740
"type": "number"
737741
}
@@ -789,6 +793,9 @@
789793
"request": {
790794
"$ref": "#/$defs/GenerateRequest"
791795
},
796+
"operation": {
797+
"$ref": "#/$defs/ModelOperation"
798+
},
792799
"candidates": {
793800
"type": "array",
794801
"items": {
@@ -955,6 +962,9 @@
955962
},
956963
"toolChoice": {
957964
"type": "boolean"
965+
},
966+
"longRunning": {
967+
"type": "boolean"
958968
}
959969
},
960970
"additionalProperties": false
@@ -972,6 +982,53 @@
972982
},
973983
"additionalProperties": false
974984
},
985+
"ModelOperation": {
986+
"type": "object",
987+
"properties": {
988+
"name": {
989+
"type": "string"
990+
},
991+
"done": {
992+
"type": "boolean"
993+
},
994+
"request": {
995+
"type": "object",
996+
"properties": {
997+
"model": {
998+
"type": "string"
999+
},
1000+
"config": {
1001+
"type": "object",
1002+
"additionalProperties": {}
1003+
}
1004+
},
1005+
"required": [
1006+
"model"
1007+
],
1008+
"additionalProperties": false
1009+
},
1010+
"response": {
1011+
"type": "object",
1012+
"properties": {
1013+
"message": {
1014+
"$ref": "#/$defs/Message"
1015+
},
1016+
"finishReason": {
1017+
"$ref": "#/$defs/FinishReason"
1018+
},
1019+
"raw": {}
1020+
},
1021+
"required": [
1022+
"finishReason"
1023+
],
1024+
"additionalProperties": false
1025+
}
1026+
},
1027+
"required": [
1028+
"name"
1029+
],
1030+
"additionalProperties": true
1031+
},
9751032
"ModelRequest": {
9761033
"type": "object",
9771034
"properties": {
@@ -992,6 +1049,9 @@
9921049
},
9931050
"docs": {
9941051
"$ref": "#/$defs/GenerateRequest/properties/docs"
1052+
},
1053+
"operation": {
1054+
"$ref": "#/$defs/GenerateRequest/properties/operation"
9951055
}
9961056
},
9971057
"required": [
@@ -1049,6 +1109,9 @@
10491109
},
10501110
"request": {
10511111
"$ref": "#/$defs/GenerateResponse/properties/request"
1112+
},
1113+
"operation": {
1114+
"$ref": "#/$defs/GenerateResponse/properties/operation"
10521115
}
10531116
},
10541117
"required": [

js/ai/src/check-operation.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/**
2+
* Copyright 2024 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { GenkitError } from '@genkit-ai/core';
18+
import { Registry } from '@genkit-ai/core/registry';
19+
import { GenerateRequest, ModelAction, ModelOperation } from './model';
20+
21+
export async function checkOperation(
22+
registry: Registry,
23+
operation: ModelOperation
24+
): Promise<ModelOperation> {
25+
if (!operation.request?.model) {
26+
throw new GenkitError({
27+
status: 'INVALID_ARGUMENT',
28+
message: 'Provided operation is missing original request information',
29+
});
30+
}
31+
const model = (await registry.lookupAction(
32+
`/model/${operation.request?.model}`
33+
)) as ModelAction;
34+
if (!model) {
35+
throw new GenkitError({
36+
status: 'INVALID_ARGUMENT',
37+
message: `Failed to resolve model from original request: ${operation.request?.model}`,
38+
});
39+
}
40+
const request = {
41+
operation,
42+
messages: [],
43+
} as GenerateRequest;
44+
const rawResponse = await model(request);
45+
if (!rawResponse.operation) {
46+
throw new GenkitError({
47+
status: 'FAILED_PRECONDITION',
48+
message: `The model did not return expected operation information: ${JSON.stringify(rawResponse)}`,
49+
});
50+
}
51+
return {
52+
...rawResponse.operation!,
53+
request: operation.request,
54+
};
55+
}

js/ai/src/generate.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616

1717
import {
18+
assertUnstable,
1819
GenkitError,
1920
isAction,
2021
isDetachedAction,
@@ -43,6 +44,7 @@ import { GenerateResponseChunk } from './generate/chunk.js';
4344
import { GenerateResponse } from './generate/response.js';
4445
import { Message } from './message.js';
4546
import {
47+
ModelOperation,
4648
resolveModel,
4749
type GenerateActionOptions,
4850
type GenerateRequest,
@@ -340,6 +342,9 @@ export async function generate<
340342
registry = maybeRegisterDynamicTools(registry, resolvedOptions);
341343

342344
const params = await toGenerateActionOptions(registry, resolvedOptions);
345+
const model = await resolveModel(registry, resolvedOptions.model, {
346+
warnDeprecated: true,
347+
});
343348

344349
const tools = await toolsToActionRefs(registry, resolvedOptions.tools);
345350
return await runWithStreamingCallback(
@@ -360,13 +365,46 @@ export async function generate<
360365
tools,
361366
});
362367
return new GenerateResponse<O>(response, {
368+
model: model.modelAction.__action.name,
363369
request: response.request ?? request,
364370
parser: resolvedFormat?.handler(request.output?.schema).parseMessage,
365371
});
366372
}
367373
);
368374
}
369375

376+
export async function generateOperation<
377+
O extends z.ZodTypeAny = z.ZodTypeAny,
378+
CustomOptions extends z.ZodTypeAny = typeof GenerationCommonConfigSchema,
379+
>(
380+
registry: Registry,
381+
options:
382+
| GenerateOptions<O, CustomOptions>
383+
| PromiseLike<GenerateOptions<O, CustomOptions>>
384+
): Promise<ModelOperation> {
385+
assertUnstable(registry, 'beta', 'generateOperation is a beta feature.');
386+
387+
options = await options;
388+
const resolvedModel = await resolveModel(registry, options.model);
389+
if (
390+
!resolvedModel.modelAction.__action.metadata?.model.supports?.longRunning
391+
) {
392+
throw new GenkitError({
393+
status: 'INVALID_ARGUMENT',
394+
message: `Model '${resolvedModel.modelAction.__action.name}' does not support long running operations.`,
395+
});
396+
}
397+
398+
const { operation } = await generate(registry, options);
399+
if (!operation) {
400+
throw new GenkitError({
401+
status: 'FAILED_PRECONDITION',
402+
message: `Model '${resolvedModel.modelAction.__action.name}' did not return an operation.`,
403+
});
404+
}
405+
return operation;
406+
}
407+
370408
function maybeRegisterDynamicTools<
371409
O extends z.ZodTypeAny = z.ZodTypeAny,
372410
CustomOptions extends z.ZodTypeAny = typeof GenerationCommonConfigSchema,

js/ai/src/generate/action.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,13 @@ async function generate(
322322
);
323323
};
324324

325-
return new GenerateResponse(await dispatch(0, request), {
325+
const rawResponse = await dispatch(0, request);
326+
if (!rawResponse.model) {
327+
rawResponse.model = model.__action.name;
328+
}
329+
330+
return new GenerateResponse(rawResponse, {
331+
model: model.__action.name,
326332
request,
327333
parser: format?.handler(request.output?.schema).parseMessage,
328334
});
@@ -331,6 +337,11 @@ async function generate(
331337

332338
// Throw an error if the response is not usable.
333339
response.assertValid();
340+
341+
if (response.operation) {
342+
return response.toJSON();
343+
}
344+
334345
const generatedMessage = response.message!; // would have thrown if no message
335346

336347
const toolRequests = generatedMessage.content.filter(

0 commit comments

Comments
 (0)