Skip to content

Commit 0fc448e

Browse files
authored
refactor(js/prompts): changed executable-prompt action return type to GenerateActionOptions (#2845)
1 parent 8fec38a commit 0fc448e

File tree

3 files changed

+140
-53
lines changed

3 files changed

+140
-53
lines changed

js/ai/src/generate.ts

Lines changed: 56 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -332,79 +332,90 @@ export async function generate<
332332
const resolvedOptions: GenerateOptions<O, CustomOptions> = {
333333
...(await Promise.resolve(options)),
334334
};
335-
const resolvedModel = await resolveModel(registry, resolvedOptions.model);
335+
const resolvedFormat = await resolveFormat(registry, resolvedOptions.output);
336+
337+
const params = await toGenerateActionOptions(registry, resolvedOptions);
336338

337339
const tools = await toolsToActionRefs(registry, resolvedOptions.tools);
340+
return await runWithStreamingCallback(
341+
registry,
342+
stripNoop(resolvedOptions.onChunk ?? resolvedOptions.streamingCallback),
343+
async () => {
344+
const response = await runWithContext(
345+
registry,
346+
resolvedOptions.context,
347+
() =>
348+
generateHelper(registry, {
349+
rawRequest: params,
350+
middleware: resolvedOptions.use,
351+
})
352+
);
353+
const request = await toGenerateRequest(registry, {
354+
...resolvedOptions,
355+
tools,
356+
});
357+
return new GenerateResponse<O>(response, {
358+
request: response.request ?? request,
359+
parser: resolvedFormat?.handler(request.output?.schema).parseMessage,
360+
});
361+
}
362+
);
363+
}
338364

339-
const messages: MessageData[] = messagesFromOptions(resolvedOptions);
365+
export async function toGenerateActionOptions<
366+
O extends z.ZodTypeAny = z.ZodTypeAny,
367+
CustomOptions extends z.ZodTypeAny = typeof GenerationCommonConfigSchema,
368+
>(
369+
registry: Registry,
370+
options: GenerateOptions<O, CustomOptions>
371+
): Promise<GenerateActionOptions> {
372+
const resolvedModel = await resolveModel(registry, options.model);
373+
const tools = await toolsToActionRefs(registry, options.tools);
374+
const messages: MessageData[] = messagesFromOptions(options);
340375

341376
const resolvedSchema = toJsonSchema({
342-
schema: resolvedOptions.output?.schema,
343-
jsonSchema: resolvedOptions.output?.jsonSchema,
377+
schema: options.output?.schema,
378+
jsonSchema: options.output?.jsonSchema,
344379
});
345380

346381
// If is schema is set but format is not explicitly set, default to `json` format.
347382
if (
348-
(resolvedOptions.output?.schema || resolvedOptions.output?.jsonSchema) &&
349-
!resolvedOptions.output?.format
383+
(options.output?.schema || options.output?.jsonSchema) &&
384+
!options.output?.format
350385
) {
351-
resolvedOptions.output.format = 'json';
386+
options.output.format = 'json';
352387
}
353-
const resolvedFormat = await resolveFormat(registry, resolvedOptions.output);
354388

355389
const params: GenerateActionOptions = {
356390
model: resolvedModel.modelAction.__action.name,
357-
docs: resolvedOptions.docs,
391+
docs: options.docs,
358392
messages: messages,
359393
tools,
360-
toolChoice: resolvedOptions.toolChoice,
394+
toolChoice: options.toolChoice,
361395
config: {
362396
version: resolvedModel.version,
363397
...stripUndefinedOptions(resolvedModel.config),
364-
...stripUndefinedOptions(resolvedOptions.config),
398+
...stripUndefinedOptions(options.config),
365399
},
366-
output: resolvedOptions.output && {
367-
...resolvedOptions.output,
368-
format: resolvedOptions.output.format,
400+
output: options.output && {
401+
...options.output,
402+
format: options.output.format,
369403
jsonSchema: resolvedSchema,
370404
},
371405
// coerce reply and restart into arrays for the action schema
372-
resume: resolvedOptions.resume && {
373-
respond: [resolvedOptions.resume.respond || []].flat(),
374-
restart: [resolvedOptions.resume.restart || []].flat(),
375-
metadata: resolvedOptions.resume.metadata,
406+
resume: options.resume && {
407+
respond: [options.resume.respond || []].flat(),
408+
restart: [options.resume.restart || []].flat(),
409+
metadata: options.resume.metadata,
376410
},
377-
returnToolRequests: resolvedOptions.returnToolRequests,
378-
maxTurns: resolvedOptions.maxTurns,
411+
returnToolRequests: options.returnToolRequests,
412+
maxTurns: options.maxTurns,
379413
};
380414
// if config is empty and it was not explicitly passed in, we delete it, don't want {}
381-
if (Object.keys(params.config).length === 0 && !resolvedOptions.config) {
415+
if (Object.keys(params.config).length === 0 && !options.config) {
382416
delete params.config;
383417
}
384-
385-
return await runWithStreamingCallback(
386-
registry,
387-
stripNoop(resolvedOptions.onChunk ?? resolvedOptions.streamingCallback),
388-
async () => {
389-
const response = await runWithContext(
390-
registry,
391-
resolvedOptions.context,
392-
() =>
393-
generateHelper(registry, {
394-
rawRequest: params,
395-
middleware: resolvedOptions.use,
396-
})
397-
);
398-
const request = await toGenerateRequest(registry, {
399-
...resolvedOptions,
400-
tools,
401-
});
402-
return new GenerateResponse<O>(response, {
403-
request: response.request ?? request,
404-
parser: resolvedFormat?.handler(request.output?.schema).parseMessage,
405-
});
406-
}
407-
);
418+
return params;
408419
}
409420

410421
/**

js/ai/src/prompt.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,14 @@ import {
4040
generateStream,
4141
GenerateStreamResponse,
4242
OutputOptions,
43+
toGenerateActionOptions,
4344
toGenerateRequest,
4445
ToolChoice,
4546
} from './generate.js';
4647
import { Message } from './message.js';
4748
import {
49+
GenerateActionOptions,
50+
GenerateActionOptionsSchema,
4851
GenerateRequest,
4952
GenerateRequestSchema,
5053
GenerateResponseChunkSchema,
@@ -355,17 +358,15 @@ function definePromptAsync<
355358
name: `${options.name}${options.variant ? `.${options.variant}` : ''}`,
356359
inputJsonSchema: options.input?.jsonSchema,
357360
inputSchema: options.input?.schema,
361+
outputSchema: GenerateActionOptionsSchema,
358362
description: options.description,
359363
actionType: 'executable-prompt',
360364
metadata,
361-
fn: async (
362-
input: z.infer<I>,
363-
{ sendChunk }
364-
): Promise<GenerateResponse> => {
365-
return await generate(registry, {
366-
...(await renderOptionsFn(input, undefined)),
367-
onChunk: sendChunk,
368-
});
365+
fn: async (input: z.infer<I>): Promise<GenerateActionOptions> => {
366+
return await toGenerateActionOptions(
367+
registry,
368+
await renderOptionsFn(input, undefined)
369+
);
369370
},
370371
} as ActionAsyncParams<any, any, any>;
371372
})

js/genkit/tests/prompts_test.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1118,6 +1118,81 @@ describe('prompt', () => {
11181118
});
11191119
});
11201120

1121+
it('renders loaded prompt via executable-prompt', async () => {
1122+
ai.defineModel(
1123+
{ name: 'googleai/gemini-5.0-ultimate-pro-plus' },
1124+
async () => ({})
1125+
);
1126+
1127+
ai.defineTool(
1128+
{
1129+
name: 'toolA',
1130+
description: 'toolA it is',
1131+
},
1132+
async () => {}
1133+
);
1134+
1135+
ai.defineTool(
1136+
{
1137+
name: 'toolB',
1138+
description: 'toolB it is',
1139+
},
1140+
async () => {}
1141+
);
1142+
1143+
const generateActionOptions = await (
1144+
await ai.registry.lookupAction('/executable-prompt/kitchensink')
1145+
)({ subject: 'banana' });
1146+
1147+
assert.deepStrictEqual(stripUndefinedProps(generateActionOptions), {
1148+
config: {
1149+
temperature: 11,
1150+
},
1151+
model: 'googleai/gemini-5.0-ultimate-pro-plus',
1152+
maxTurns: 77,
1153+
messages: [
1154+
{ role: 'system', content: [{ text: ' Hello ' }] },
1155+
{ role: 'model', content: [{ text: ' from the prompt file banana' }] },
1156+
],
1157+
output: {
1158+
format: 'csv',
1159+
jsonSchema: {
1160+
additionalProperties: false,
1161+
properties: {
1162+
arr: {
1163+
description: 'array of objects',
1164+
items: {
1165+
additionalProperties: false,
1166+
properties: {
1167+
nest2: {
1168+
type: ['boolean', 'null'],
1169+
},
1170+
},
1171+
type: 'object',
1172+
},
1173+
type: 'array',
1174+
},
1175+
obj: {
1176+
additionalProperties: false,
1177+
description: 'a nested object',
1178+
properties: {
1179+
nest1: {
1180+
type: ['string', 'null'],
1181+
},
1182+
},
1183+
type: ['object', 'null'],
1184+
},
1185+
},
1186+
required: ['arr'],
1187+
type: 'object',
1188+
},
1189+
},
1190+
returnToolRequests: true,
1191+
toolChoice: 'required',
1192+
tools: ['/tool/toolA', '/tool/toolB'],
1193+
});
1194+
});
1195+
11211196
it('resolved schema refs', async () => {
11221197
const prompt = ai.prompt('schemaRef');
11231198

0 commit comments

Comments
 (0)