diff --git a/README.md b/README.md index e05a5f2..ea22ae6 100644 --- a/README.md +++ b/README.md @@ -121,15 +121,17 @@ export default { * `promptsDir`: (string, default: `'prompts'`) The directory where your `.prompt` files are located. This path is resolved relative to the project root. * `schemaFile`: (string, default: `'schema.ts'`) The path to your schema file, which exports Zod schemas for structured output. This path is resolved relative to the project root. * `plugins`: (DatapromptPlugin[], default: `[]`) An array of dataprompt plugins to register. +* `genkitPlugins`: (Plugin[], default: `[]`) An array of Genkit plugins to use. If provided, they will be used to initialize the default Genkit instance. * `secrets`: (DatapromptSecrets, default: `process.env`) An object containing secret keys, such as API keys. See Secrets. -* `genkit`: (Genkit, default: `getGenkit()`) A pre-configured Genkit instance to use. If not provided, dataprompt will create a default instance with the Google AI provider. +* `genkit`: (Genkit, default: `getGenkit()`) A pre-configured Genkit instance to use. If not provided, dataprompt will create a default instance with the Google AI provider (if a key is present) or use the provided `genkitPlugins`. * `rootDir`: (string, default: project root) This option sets a custom root directory. This is useful when used in a monorepo setup. ### Secrets dataprompt automatically looks for the following environment variables: -* `GOOGLEAI_API_KEY`: API key for the Google AI Gemini model. +* `GEMINI_API_KEY`: (Preferred) API key for the Google AI Gemini model. +* `GOOGLEAI_API_KEY`: (Legacy) API key for the Google AI Gemini model. * `GOOGLE_APPLICATION_CREDENTIALS`: Path to a Firebase service account key file. You can also set these secrets in your `dataprompt.config.{js,ts}` file: @@ -138,7 +140,7 @@ You can also set these secrets in your `dataprompt.config.{js,ts}` file: // dataprompt.config.ts export default { secrets: { - GOOGLEAI_API_KEY: 'YOUR_API_KEY', + GEMINI_API_KEY: 'YOUR_API_KEY', GOOGLE_APPLICATION_CREDENTIALS: '/path/to/serviceAccountKey.json', }, }; @@ -376,12 +378,26 @@ The `TaskManager` API provides methods to manage scheduled tasks: ### Using non Gemini Models -dataprompt is built to work with Google AI (Gemini) models out-of-the-box. However, you can configure a custom Genkit instance with other models and providers as plugins and provide it to a DatapromptConfig. +dataprompt is built to work with Google AI (Gemini) models out-of-the-box, but is flexible enough to support any Genkit plugin. You can configure other models and providers by passing plugins to the `genkitPlugins` array in your configuration. [See Genkit Plugins](https://github.com/TheFireCo/genkit-plugins) Example: +```js +// dataprompt.config.js +import { otherModelPlugin } from ''; + +/** @type {import('dataprompt').DatapromptConfig} */ +export default { + genkitPlugins: [ + otherModelPlugin({ /* ... */ }) + ] +}; +``` + +Alternatively, you can provide a fully configured Genkit instance: + ```js // dataprompt.config.js import { genkit } from 'genkit'; @@ -397,8 +413,6 @@ export default { }; ``` -Out-of-the-box model support for other providers is on the roadmap. - ### JavaScript API - Using dataprompt in your own app You can use dataprompt's JavaScript API to integrate it into your existing applications without needing to run dataprompt as a server. @@ -418,8 +432,9 @@ You can use dataprompt's JavaScript API to integrate it into your existing appli * `promptsDir`: (string, optional) The directory where your `.prompt` files are located. Defaults to `'prompts'`. * `schemaFile`: (string, optional) The path to your Zod schema file. Defaults to `'schema.ts'`. - * `genkit`: (Genkit, optional) A pre-configured Genkit instance. If not provided, dataprompt will create one with the Google AI plugin. + * `genkit`: (Genkit, optional) A pre-configured Genkit instance. If not provided, dataprompt will create one with the Google AI plugin or configured `genkitPlugins`. * `plugins`: (DatapromptPlugin[], optional) An array of custom plugins to register. + * `genkitPlugins`: (Plugin[], optional) An array of Genkit plugins to use. * `secrets`: (DatapromptSecrets, optional) An object containing API keys and other secrets. * `rootDir`: (string, optional) The root directory of your project. @@ -455,6 +470,7 @@ export interface DatapromptPlugin { createDataSource?(): DataSourceProvider; createDataAction?(): DataActionProvider; createTrigger?(): TriggerProvider; + provideGenkitPlugins?(): any[]; } ``` @@ -462,6 +478,7 @@ export interface DatapromptPlugin { * `createDataSource?(): DataSourceProvider`: An optional method that returns a `DataSourceProvider`. * `createDataAction?(): DataActionProvider`: An optional method that returns a `DataActionProvider`. * `createTrigger?(): TriggerProvider`: An optional method that returns a `TriggerProvider`. +* `provideGenkitPlugins?(): any[]`: An optional method that returns an array of Genkit plugins to be used in the default Genkit instance. #### DataSourceProvider diff --git a/src/core/config.manager.ts b/src/core/config.manager.ts index 0ec8a2c..266838d 100644 --- a/src/core/config.manager.ts +++ b/src/core/config.manager.ts @@ -11,8 +11,9 @@ import { pathToFileURL } from 'node:url'; const CoreSecretsSchema = z.object({ // TODO: Make these optional or configurable based on which plugins/providers are actually used. // Currently, this forces every user to have Google credentials even if they use a different provider. - GOOGLEAI_API_KEY: z.string().min(1, { message: 'GOOGLEAI_API_KEY is required.' }), - GOOGLE_APPLICATION_CREDENTIALS: z.string().min(1, { message: 'GOOGLE_APPLICATION_CREDENTIALS is required.' }), + GOOGLEAI_API_KEY: z.string().optional(), + GEMINI_API_KEY: z.string().optional(), + // GOOGLE_APPLICATION_CREDENTIALS: z.string().min(1, { message: 'GOOGLE_APPLICATION_CREDENTIALS is required.' }), }).passthrough(); type PluginSecrets = { @@ -38,11 +39,13 @@ export class ConfigManager { const rootDir = await this.#resolveRootDir(userConfig); const defaultConfig: DatapromptConfig = { plugins: [], + genkitPlugins: [], promptsDir: 'prompts', schemaFile: 'schema.ts', secrets: { GOOGLEAI_API_KEY: process.env.GOOGLEAI_API_KEY, GOOGLE_APPLICATION_CREDENTIALS: process.env.GOOGLE_APPLICATION_CREDENTIALS, + GEMINI_API_KEY: process.env.GEMINI_API_KEY, }, rootDir, }; @@ -51,6 +54,7 @@ export class ConfigManager { const mergedConfig: DatapromptConfig = { ...defaultConfig, ...userConfig, // This will shallow-overwrite promptsDir, etc. which is what we want. + genkitPlugins: userConfig?.genkitPlugins || defaultConfig.genkitPlugins, secrets: { ...defaultConfig.secrets, // Start with default secrets... ...userConfig?.secrets, // ...then merge user's secrets over them. diff --git a/src/core/config.ts b/src/core/config.ts index d390a98..94ed341 100644 --- a/src/core/config.ts +++ b/src/core/config.ts @@ -13,6 +13,7 @@ export type DatapromptUserConfig = { secrets?: Record; rootDir?: string; genkit?: Genkit; + genkitPlugins?: any[]; }; /** @@ -20,6 +21,7 @@ export type DatapromptUserConfig = { * that the application will use. All properties are required. * It does NOT contain live service instances like Genkit. */ -export type DatapromptConfig = Required> & { +export type DatapromptConfig = Required> & { plugins: DatapromptPlugin[]; + genkitPlugins: any[]; }; \ No newline at end of file diff --git a/src/core/dataprompt.ts b/src/core/dataprompt.ts index 6f08b23..6fe8564 100644 --- a/src/core/dataprompt.ts +++ b/src/core/dataprompt.ts @@ -25,15 +25,33 @@ export interface DatapromptStore { } function createDefaultGenkit(config: DatapromptConfig): Genkit { - const apiKey = config.secrets?.GOOGLEAI_API_KEY; - if (!apiKey) { - throw new Error('FATAL: GOOGLEAI_API_KEY not found for default Genkit initialization.'); + const plugins: any[] = []; + const googleApiKey = config.secrets?.GOOGLEAI_API_KEY || config.secrets?.GEMINI_API_KEY; + + if (config.genkitPlugins) { + plugins.push(...config.genkitPlugins); } - // TODO: Allow configuration of other AI providers beyond Google AI. - // TODO: Make the default Genkit initialization more flexible or plugin-based. - const ai = new Genkit({ - plugins: [googleAI({ apiKey })], - }); + + for (const plugin of config.plugins) { + if (plugin.provideGenkitPlugins) { + plugins.push(...plugin.provideGenkitPlugins()); + } + } + + // If no plugins are provided, or if we have a Google API key, + // we try to add Google AI support. + // We prioritize user provided plugins, but also support Google AI if key is present. + if (googleApiKey) { + // We can't easily check if googleAI is already in plugins because they are instances. + // However, Genkit supports multiple plugins. + plugins.push(googleAI({ apiKey: googleApiKey })); + } + + if (plugins.length === 0) { + throw new Error('FATAL: No Genkit plugins configured and GOOGLEAI_API_KEY/GEMINI_API_KEY not found.'); + } + + const ai = new Genkit({ plugins }); ai.defineHelper('dateFormat', dateFormat); return ai; } @@ -56,14 +74,15 @@ async function loadUserGenkitInstance(rootDir: string): Promise>; schema?: Schema; } | undefined; + provideGenkitPlugins?(): any[]; } export type FetchDataParams = { diff --git a/src/plugins/firebase/firestore/index.ts b/src/plugins/firebase/firestore/index.ts index bde465f..e54c666 100644 --- a/src/plugins/firebase/firestore/index.ts +++ b/src/plugins/firebase/firestore/index.ts @@ -7,7 +7,7 @@ import { execute } from './actions.js' import { FirebasePluginConfig } from '../types.js'; const FirestorePluginSecrets = z.object({ - GOOGLE_APPLICATION_CREDENTIALS: z.string().min(1) + GOOGLE_APPLICATION_CREDENTIALS: z.string().optional() }); export function firestorePlugin(