@genkit-ai/dotprompt
Version:
Genkit AI framework `.prompt` file format and management library.
194 lines (172 loc) • 5.56 kB
text/typescript
/**
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//
// IMPORTANT: Please keep type definitions in sync with
// genkit-tools/src/types/prompt.ts
//
import {
GenerationCommonConfigSchema,
ModelArgument,
} from '@genkit-ai/ai/model';
import { ToolArgument } from '@genkit-ai/ai/tool';
import { lookupSchema } from '@genkit-ai/core/registry';
import { JSONSchema, parseSchema, toJsonSchema } from '@genkit-ai/core/schema';
import z from 'zod';
import { picoschema } from './picoschema.js';
/**
* Metadata for a prompt.
*/
export interface PromptMetadata<
Input extends z.ZodTypeAny = z.ZodTypeAny,
Options extends z.ZodTypeAny = z.ZodTypeAny,
> {
/** The name of the prompt. */
name?: string;
/** The variant name for the prompt. */
variant?: string;
/** The name of the model to use for this prompt, e.g. `vertexai/gemini-1.0-pro` */
model?: ModelArgument<Options>;
/** Names of tools (registered separately) to allow use of in this prompt. */
tools?: ToolArgument[];
/** Number of candidates to generate by default. */
candidates?: number;
/** Model configuration. Not all models support all options. */
config?: z.infer<Options>;
input?: {
/** Defines the default input variable values to use if none are provided. */
default?: any;
/** Zod schema defining the input variables. */
schema?: Input;
/**
* Defines the input variables that can be passed into the template in JSON schema form.
* If not supplied, any object will be accepted. `{type: "object"}` is defaulted if not
* supplied.
*/
jsonSchema?: JSONSchema;
};
/** Defines the expected model output format. */
output?: {
/** Desired output format for this prompt. */
format?: 'json' | 'text' | 'media';
/** Zod schema defining the output structure (cannot be specified with non-json format). */
schema?: z.ZodTypeAny;
/** JSON schema of desired output (cannot be specified with non-json format). */
jsonSchema?: JSONSchema;
};
/** Arbitrary metadata to be used by code, tools, and libraries. */
metadata?: Record<string, any>;
}
/**
* Formal schema for prompt YAML frontmatter.
*/
export const PromptFrontmatterSchema = z.object({
name: z.string().optional(),
variant: z.string().optional(),
model: z.string().optional(),
tools: z.array(z.string()).optional(),
candidates: z.number().optional(),
config: GenerationCommonConfigSchema.passthrough().optional(),
input: z
.object({
default: z.any(),
schema: z.unknown(),
})
.optional(),
output: z
.object({
format: z.enum(['json', 'text', 'media']).optional(),
schema: z.unknown().optional(),
})
.optional(),
metadata: z.record(z.unknown()).optional(),
});
export type PromptFrontmatter = z.infer<typeof PromptFrontmatterSchema>;
function stripUndefinedOrNull(obj: any) {
if (typeof obj !== 'object' || obj === null) {
return obj;
}
for (const key in obj) {
if (obj[key] === undefined || obj[key] === null) {
delete obj[key];
} else if (typeof obj[key] === 'object') {
stripUndefinedOrNull(obj[key]); // Recurse into nested objects
}
}
return obj;
}
function fmSchemaToSchema(fmSchema: any) {
if (!fmSchema) return {};
if (typeof fmSchema === 'string') return lookupSchema(fmSchema);
return { jsonSchema: picoschema(fmSchema) };
}
export function toMetadata(attributes: unknown): Partial<PromptMetadata> {
const fm = parseSchema<z.infer<typeof PromptFrontmatterSchema>>(attributes, {
schema: PromptFrontmatterSchema,
});
let input: PromptMetadata['input'] | undefined;
if (fm.input) {
input = { default: fm.input.default, ...fmSchemaToSchema(fm.input.schema) };
}
let output: PromptMetadata['output'] | undefined;
if (fm.output) {
output = {
format: fm.output.format,
...fmSchemaToSchema(fm.output.schema),
};
}
return stripUndefinedOrNull({
name: fm.name,
variant: fm.variant,
model: fm.model,
config: fm.config,
input,
output,
metadata: fm.metadata,
tools: fm.tools,
candidates: fm.candidates,
});
}
export function toFrontmatter(md: PromptMetadata): PromptFrontmatter {
return stripUndefinedOrNull({
name: md.name,
variant: md.variant,
model: typeof md.model === 'string' ? md.model : md.model?.name,
config: md.config,
input: md.input
? {
default: md.input.default,
schema: toJsonSchema({
schema: md.input.schema,
jsonSchema: md.input.jsonSchema,
}),
}
: undefined,
output: md.output
? {
format: md.output.format,
schema: toJsonSchema({
schema: md.output.schema,
jsonSchema: md.output.jsonSchema,
}),
}
: undefined,
metadata: md.metadata,
tools: md.tools?.map((t) =>
typeof t === 'string' ? t : (t as any).__action?.name || (t as any).name
),
candidates: md.candidates,
});
}