Skip to content

Commit ea88f5d

Browse files
pavelgjmbleigh
andauthored
refactor(js): introduced background actions and reimplemented veo using them (#3070)
Co-authored-by: Michael Bleigh <[email protected]>
1 parent 6bb6deb commit ea88f5d

File tree

21 files changed

+697
-364
lines changed

21 files changed

+697
-364
lines changed

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

Lines changed: 23 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,18 @@ export {
4949
// IMPORTANT: Keep this file in sync with genkit/ai/src/model.ts!
5050
//
5151

52+
/**
53+
* Zod schema of an opration representing a background task.
54+
*/
55+
export const OperationSchema = z.object({
56+
action: z.string().optional(),
57+
id: z.string(),
58+
done: z.boolean().optional(),
59+
output: z.any().optional(),
60+
error: z.object({ message: z.string() }).passthrough().optional(),
61+
metadata: z.record(z.string(), z.any()).optional(),
62+
});
63+
5264
/**
5365
* Zod schema of message part.
5466
*/
@@ -122,8 +134,6 @@ export const ModelInfoSchema = z.object({
122134
constrained: z.enum(['none', 'all', 'no-tools']).optional(),
123135
/** Model supports controlling tool choice, e.g. forced tool calling. */
124136
toolChoice: z.boolean().optional(),
125-
/** Model supports long running operation interface. */
126-
longRunning: z.boolean().optional(),
127137
})
128138
.optional(),
129139
/** At which stage of development this model is.
@@ -196,40 +206,6 @@ export const OutputConfigSchema = z.object({
196206
contentType: z.string().optional(),
197207
});
198208

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-
233209
/**
234210
* Output config.
235211
*/
@@ -243,9 +219,7 @@ export const ModelRequestSchema = z.object({
243219
toolChoice: z.enum(['auto', 'required', 'none']).optional(),
244220
output: OutputConfigSchema.optional(),
245221
docs: z.array(DocumentDataSchema).optional(),
246-
operation: ModelOperationSchema.optional(),
247222
});
248-
249223
/** ModelRequest represents the parameters that are passed to a model when generating content. */
250224
export interface ModelRequest<
251225
CustomOptionsSchema extends z.ZodTypeAny = z.ZodTypeAny,
@@ -299,6 +273,16 @@ export const GenerationUsageSchema = z.object({
299273
*/
300274
export type GenerationUsage = z.infer<typeof GenerationUsageSchema>;
301275

276+
/** Model response finish reason enum. */
277+
export const FinishReasonSchema = z.enum([
278+
'stop',
279+
'length',
280+
'blocked',
281+
'interrupted',
282+
'other',
283+
'unknown',
284+
]);
285+
302286
/** @deprecated All responses now return a single candidate. Only the first candidate will be used if supplied. */
303287
export const CandidateSchema = z.object({
304288
index: z.number(),
@@ -333,7 +317,7 @@ export const ModelResponseSchema = z.object({
333317
custom: z.unknown(),
334318
raw: z.unknown(),
335319
request: GenerateRequestSchema.optional(),
336-
operation: ModelOperationSchema.optional(),
320+
operation: OperationSchema.optional(),
337321
});
338322

339323
/**

genkit-tools/genkit-schema.json

Lines changed: 36 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -602,7 +602,6 @@
602602
"length",
603603
"blocked",
604604
"interrupted",
605-
"pending",
606605
"other",
607606
"unknown"
608607
]
@@ -733,9 +732,6 @@
733732
"$ref": "#/$defs/DocumentData"
734733
}
735734
},
736-
"operation": {
737-
"$ref": "#/$defs/ModelOperation"
738-
},
739735
"candidates": {
740736
"type": "number"
741737
}
@@ -794,7 +790,7 @@
794790
"$ref": "#/$defs/GenerateRequest"
795791
},
796792
"operation": {
797-
"$ref": "#/$defs/ModelOperation"
793+
"$ref": "#/$defs/Operation"
798794
},
799795
"candidates": {
800796
"type": "array",
@@ -962,9 +958,6 @@
962958
},
963959
"toolChoice": {
964960
"type": "boolean"
965-
},
966-
"longRunning": {
967-
"type": "boolean"
968961
}
969962
},
970963
"additionalProperties": false
@@ -982,53 +975,6 @@
982975
},
983976
"additionalProperties": false
984977
},
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-
},
1032978
"ModelRequest": {
1033979
"type": "object",
1034980
"properties": {
@@ -1049,9 +995,6 @@
1049995
},
1050996
"docs": {
1051997
"$ref": "#/$defs/GenerateRequest/properties/docs"
1052-
},
1053-
"operation": {
1054-
"$ref": "#/$defs/GenerateRequest/properties/operation"
1055998
}
1056999
},
10571000
"required": [
@@ -1119,6 +1062,41 @@
11191062
],
11201063
"additionalProperties": false
11211064
},
1065+
"Operation": {
1066+
"type": "object",
1067+
"properties": {
1068+
"action": {
1069+
"type": "string"
1070+
},
1071+
"id": {
1072+
"type": "string"
1073+
},
1074+
"done": {
1075+
"type": "boolean"
1076+
},
1077+
"output": {},
1078+
"error": {
1079+
"type": "object",
1080+
"properties": {
1081+
"message": {
1082+
"type": "string"
1083+
}
1084+
},
1085+
"required": [
1086+
"message"
1087+
],
1088+
"additionalProperties": true
1089+
},
1090+
"metadata": {
1091+
"type": "object",
1092+
"additionalProperties": {}
1093+
}
1094+
},
1095+
"required": [
1096+
"id"
1097+
],
1098+
"additionalProperties": false
1099+
},
11221100
"OutputConfig": {
11231101
"type": "object",
11241102
"properties": {

js/ai/src/check-operation.ts

Lines changed: 11 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -14,42 +14,27 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { GenkitError } from '@genkit-ai/core';
17+
import { GenkitError, Operation } from '@genkit-ai/core';
1818
import { Registry } from '@genkit-ai/core/registry';
19-
import { GenerateRequest, ModelAction, ModelOperation } from './model';
2019

21-
export async function checkOperation(
20+
export async function checkOperation<T = unknown>(
2221
registry: Registry,
23-
operation: ModelOperation
24-
): Promise<ModelOperation> {
25-
if (!operation.request?.model) {
22+
operation: Operation<T>
23+
): Promise<Operation<T>> {
24+
if (!operation.action) {
2625
throw new GenkitError({
2726
status: 'INVALID_ARGUMENT',
2827
message: 'Provided operation is missing original request information',
2928
});
3029
}
31-
const model = (await registry.lookupAction(
32-
`/model/${operation.request?.model}`
33-
)) as ModelAction;
34-
if (!model) {
30+
const backgroundAction = await registry.lookupBackgroundAction(
31+
operation.action
32+
);
33+
if (!backgroundAction) {
3534
throw new GenkitError({
3635
status: 'INVALID_ARGUMENT',
37-
message: `Failed to resolve model from original request: ${operation.request?.model}`,
36+
message: `Failed to resolve background action from original request: ${operation.action}`,
3837
});
3938
}
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-
};
39+
return await backgroundAction.check(operation);
5540
}

js/ai/src/generate.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
GenkitError,
2020
isAction,
2121
isDetachedAction,
22+
Operation,
2223
runWithContext,
2324
runWithStreamingCallback,
2425
sentinelNoopStreamingCallback,
@@ -44,7 +45,7 @@ import { GenerateResponseChunk } from './generate/chunk.js';
4445
import { GenerateResponse } from './generate/response.js';
4546
import { Message } from './message.js';
4647
import {
47-
ModelOperation,
48+
GenerateResponseData,
4849
resolveModel,
4950
type GenerateActionOptions,
5051
type GenerateRequest,
@@ -342,9 +343,6 @@ export async function generate<
342343
registry = maybeRegisterDynamicTools(registry, resolvedOptions);
343344

344345
const params = await toGenerateActionOptions(registry, resolvedOptions);
345-
const model = await resolveModel(registry, resolvedOptions.model, {
346-
warnDeprecated: true,
347-
});
348346

349347
const tools = await toolsToActionRefs(registry, resolvedOptions.tools);
350348
return await runWithStreamingCallback(
@@ -365,7 +363,6 @@ export async function generate<
365363
tools,
366364
});
367365
return new GenerateResponse<O>(response, {
368-
model: model.modelAction.__action.name,
369366
request: response.request ?? request,
370367
parser: resolvedFormat?.handler(request.output?.schema).parseMessage,
371368
});
@@ -381,7 +378,7 @@ export async function generateOperation<
381378
options:
382379
| GenerateOptions<O, CustomOptions>
383380
| PromiseLike<GenerateOptions<O, CustomOptions>>
384-
): Promise<ModelOperation> {
381+
): Promise<Operation<GenerateResponseData>> {
385382
assertUnstable(registry, 'beta', 'generateOperation is a beta feature.');
386383

387384
options = await options;

js/ai/src/generate/action.ts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -322,26 +322,30 @@ async function generate(
322322
);
323323
};
324324

325-
const rawResponse = await dispatch(0, request);
326-
if (!rawResponse.model) {
327-
rawResponse.model = model.__action.name;
325+
const modelResponse = await dispatch(0, request);
326+
327+
if (model.__action.actionType === 'background-model') {
328+
return new GenerateResponse(
329+
{ operation: modelResponse },
330+
{
331+
request,
332+
parser: format?.handler(request.output?.schema).parseMessage,
333+
}
334+
);
328335
}
329336

330-
return new GenerateResponse(rawResponse, {
331-
model: model.__action.name,
337+
return new GenerateResponse(modelResponse, {
332338
request,
333339
parser: format?.handler(request.output?.schema).parseMessage,
334340
});
335341
}
336342
);
337-
338-
// Throw an error if the response is not usable.
339-
response.assertValid();
340-
341-
if (response.operation) {
343+
if (model.__action.actionType === 'background-model') {
342344
return response.toJSON();
343345
}
344346

347+
// Throw an error if the response is not usable.
348+
response.assertValid();
345349
const generatedMessage = response.message!; // would have thrown if no message
346350

347351
const toolRequests = generatedMessage.content.filter(

0 commit comments

Comments
 (0)