Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 24 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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',
},
};
Expand Down Expand Up @@ -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 '<other-model>';

/** @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';
Expand All @@ -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.
Expand All @@ -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.

Expand Down Expand Up @@ -455,13 +470,15 @@ export interface DatapromptPlugin {
createDataSource?(): DataSourceProvider;
createDataAction?(): DataActionProvider;
createTrigger?(): TriggerProvider;
provideGenkitPlugins?(): any[];
}
```

* `name`: A unique name for your plugin.
* `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

Expand Down
8 changes: 6 additions & 2 deletions src/core/config.manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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,
};
Expand All @@ -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.
Expand Down
4 changes: 3 additions & 1 deletion src/core/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ export type DatapromptUserConfig = {
secrets?: Record<string, any>;
rootDir?: string;
genkit?: Genkit;
genkitPlugins?: any[];
};

/**
* The final, fully resolved, and validated STATIC configuration object
* that the application will use. All properties are required.
* It does NOT contain live service instances like Genkit.
*/
export type DatapromptConfig = Required<Omit<DatapromptUserConfig, 'plugins' | 'genkit'>> & {
export type DatapromptConfig = Required<Omit<DatapromptUserConfig, 'plugins' | 'genkit' | 'genkitPlugins'>> & {
plugins: DatapromptPlugin[];
genkitPlugins: any[];
};
39 changes: 29 additions & 10 deletions src/core/dataprompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -56,14 +74,15 @@ async function loadUserGenkitInstance(rootDir: string): Promise<Genkit | undefin

function mergeConfigs(base: DatapromptConfig, override?: DatapromptUserConfig): DatapromptConfig {
if (!override) {
return { ...base, secrets: { ...base.secrets }, plugins: [...base.plugins] };
return { ...base, secrets: { ...base.secrets }, plugins: [...base.plugins], genkitPlugins: [...base.genkitPlugins] };
}
const merged = { ...base, secrets: { ...base.secrets }, plugins: [...base.plugins] };
const merged = { ...base, secrets: { ...base.secrets }, plugins: [...base.plugins], genkitPlugins: [...base.genkitPlugins] };
if (override.rootDir) merged.rootDir = override.rootDir;
if (override.promptsDir) merged.promptsDir = override.promptsDir;
if (override.schemaFile) merged.schemaFile = override.schemaFile;
if (override.secrets) Object.assign(merged.secrets, override.secrets);
if (override.plugins) merged.plugins.push(...override.plugins);
if (override.genkitPlugins) merged.genkitPlugins.push(...override.genkitPlugins);
return merged;
}

Expand Down
1 change: 1 addition & 0 deletions src/core/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export interface DatapromptPlugin<Schema extends z.AnyZodObject = z.AnyZodObject
secrets: Partial<z.infer<Schema>>;
schema?: Schema;
} | undefined;
provideGenkitPlugins?(): any[];
}

export type FetchDataParams = {
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/firebase/firestore/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down