Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[JS] Genkit flows failing with zod schema #972

Open
sdk1990 opened this issue Sep 26, 2024 · 4 comments
Open

[JS] Genkit flows failing with zod schema #972

sdk1990 opened this issue Sep 26, 2024 · 4 comments
Assignees
Labels
bug Something isn't working js

Comments

@sdk1990
Copy link

sdk1990 commented Sep 26, 2024

Describe the bug
I have configured Genkit to be working with Cloud Functions using the onFlow wrapper. When running a flow from Genkit Tools UI I receive the following error:
Cannot destructure property 'shape' of 'this._getCached(...)' as it is undefined.

To Reproduce
Init Genkit:

/**
 * Configure the Genkit SDK.
 * @see https://firebase.google.com/docs/genkit/get-started
 */
export function initGenkit(apiKey: string) {
	configureGenkit({
		plugins: [
			// Load the Firebase plugin, which provides integrations with several
			// Firebase services.
			firebase({ projectId }),
			// Load the Google AI plugin. You can optionally specify your API key
			// by passing in a config object; if you don't, the Google AI plugin uses
			// the value from the GOOGLE_GENAI_API_KEY environment variable, which is
			// the recommended practice.
			googleAI({ apiKey }),
			dotprompt(),
		],
		// Log debug output to tbe console.
		logLevel: 'debug',
		// Perform OpenTelemetry instrumentation and enable trace collection.
		enableTracingAndMetrics: true,
		flowStateStore: 'firebase',
		traceStore: 'firebase',
		telemetry: {
			instrumentation: 'firebase',
			logger: 'firebase',
		},
	});
}

Zod schema's:

import { defineSchema } from '@genkit-ai/core';
import { z } from '@genkit-ai/core/schema';

import { dietaryRestrictions } from '../../types/nutrition';

export const nutritionGoalInputSchema = defineSchema(
	'NutritionGoalInputSchema',
	z.object({
		age: z
			.number()
			.min(0, 'Age must be a non-negative number.')
			.describe('The age of the individual in years. Must be a non-negative number.'),
		gender: z.enum(['female', 'male']).describe('The biological gender of the individual, either "female" or "male".'),
		weightKg: z
			.number()
			.min(0, 'Weight must be a non-negative number.')
			.describe('The body weight of the individual in kilograms. Must be a non-negative number.'),
		lengthCm: z
			.number()
			.min(0, 'Height must be a non-negative number.')
			.describe('The height of the individual in centimeters. Must be a non-negative number.'),
		activityLevel: z
			.enum(['extra_active', 'very_active', 'moderately_active', 'lightly_active', 'sedentary'])
			.describe(
				'The general activity level (Physical Activity Level) of the individual throughout the day excluding formal exercise."'
			),
		dietaryGoals: z.object({
			goalType: z
				.enum(['fat_loss', 'muscle_gain'])
				.describe(
					'The primary dietary objective of the individual. Options are: "fat_loss" for reducing body fat percentage, and "muscle_gain" for increasing muscle mass.'
				),
		}),
		dietaryRestrictions: z
			.array(z.union([z.enum(dietaryRestrictions), z.string()]))
			.optional()
			.describe(
				'A list of dietary restrictions or preferences for the individual. These can include specific dietary patterns like "vegan", "vegetarian", "kosher" or "halal"'
			),
	})
);

export type NutritionGoalInputSchema = typeof nutritionGoalInputSchema;

export const nutritionGoalOutputSchema = defineSchema(
	'NutritionGoalOutputSchema',
	z.object({
		specs: z.object({
			calories: z
				.number()
				.min(0, 'Calories must be a non-negative number.')
				.describe(
					"The total estimated energy requirement for the individual in kilocalories (kcal) per day. This is calculated based on the individual's activity level, age, weight, height, gender, and dietary goals."
				),
			protein: z
				.number()
				.min(0, 'Protein must be a non-negative number.')
				.describe(
					'The total daily recommended protein intake in grams (g) for the individual, calculated based on body weight, activity level, and dietary objectives.'
				),
			fat: z
				.number()
				.min(0, 'Fat must be a non-negative number.')
				.describe(
					'The total daily recommended fat intake in grams (g) for the individual, derived from the percentage of total calories allocated to dietary fat based on activity level and goals.'
				),
			carbs: z
				.number()
				.min(0, 'Carbohydrates must be a non-negative number.')
				.describe(
					'The total daily recommended carbohydrate intake in grams (g) for the individual, derived from the remaining calories after accounting for protein and fat, adjusted for dietary preferences and activity level.'
				),
		}),
	})
);

export type NutritionGoalOutputSchema = typeof nutritionGoalOutputSchema;

Flow:

const nutritionGoalPrompt = promptRef('nutrition/01-nutrition-goal');

const nutritionGoalFlow = onFlow(
	{
		name: '01-nutritionGoal',
		authPolicy,
		inputSchema: nutritionGoalInputSchema,
		outputSchema: nutritionGoalOutputSchema,
		httpsOptions: httpsOptions(),
	},
	async (input) => {
		info('[01-nutritionGoalFlow] Start');

		try {
			const result = await nutritionGoalPrompt.generate<NutritionGoalOutputSchema>({ input });

			info('[01-nutritionGoalFlow] - ✅ Success');

			return result.output();
		} catch (err) {
			error('[01-nutritionGoalFlow] ❌ Error', err);
			throw err;
		}
	}
);

Expected behavior
I expected the flow to run as desired. When I test the prompt from the Genkit Tools UI, it works as expected.

Screenshots
Failing flow:
image

Runtime (please complete the following information):

  • OS: MacOS
  • Version 15

** Node version

  • v20.17.0

Additional context
Using Cloud Functions 2nd gen with local emulators.

@sdk1990 sdk1990 added bug Something isn't working js labels Sep 26, 2024
@sdk1990 sdk1990 changed the title [JS] [JS] Genkit flows failing with zod schema Sep 26, 2024
@pavelgj pavelgj self-assigned this Oct 3, 2024
@odbol
Copy link

odbol commented Oct 15, 2024

+1 I'm also running into this

@odbol
Copy link

odbol commented Oct 15, 2024

Strangely, I was able to fix it by changing line 1831 in node_modules/zod/lib/types.js:

    _getCached() {
        if (this._cached != null) // note using non-strict equality fixes it somehow
            return this._cached;
        const shape = this._def.shape();
        const keys = util_1.util.objectKeys(shape);
        return (this._cached = { shape, keys });
    }

@pavelgj
Copy link
Collaborator

pavelgj commented Oct 16, 2024

This is an odd issue. Feel like zod bug... I'm trying to come up with a narrower repro.

@pavelgj
Copy link
Collaborator

pavelgj commented Oct 16, 2024

This seems to work fine

import { z } from 'zod';

export const nutritionGoalInputSchema = z.object({
  age: z
    .number()
    .min(0, 'Age must be a non-negative number.')
    .describe(
      'The age of the individual in years. Must be a non-negative number.'
    ),
  gender: z
    .enum(['female', 'male'])
    .describe(
      'The biological gender of the individual, either "female" or "male".'
    ),
  weightKg: z
    .number()
    .min(0, 'Weight must be a non-negative number.')
    .describe(
      'The body weight of the individual in kilograms. Must be a non-negative number.'
    ),
  lengthCm: z
    .number()
    .min(0, 'Height must be a non-negative number.')
    .describe(
      'The height of the individual in centimeters. Must be a non-negative number.'
    ),
  activityLevel: z
    .enum([
      'extra_active',
      'very_active',
      'moderately_active',
      'lightly_active',
      'sedentary',
    ])
    .describe(
      'The general activity level (Physical Activity Level) of the individual throughout the day excluding formal exercise."'
    ),
  dietaryGoals: z.object({
    goalType: z
      .enum(['fat_loss', 'muscle_gain'])
      .describe(
        'The primary dietary objective of the individual. Options are: "fat_loss" for reducing body fat percentage, and "muscle_gain" for increasing muscle mass.'
      ),
  }),
  dietaryRestrictions: z
    .array(z.union([z.enum(['1', '2', '3']), z.string()]))
    .optional()
    .describe(
      'A list of dietary restrictions or preferences for the individual. These can include specific dietary patterns like "vegan", "vegetarian", "kosher" or "halal"'
    ),
});

export const nutritionGoalOutputSchema = z.object({
  specs: z.object({
    calories: z
      .number()
      .min(0, 'Calories must be a non-negative number.')
      .describe(
        "The total estimated energy requirement for the individual in kilocalories (kcal) per day. This is calculated based on the individual's activity level, age, weight, height, gender, and dietary goals."
      ),
    protein: z
      .number()
      .min(0, 'Protein must be a non-negative number.')
      .describe(
        'The total daily recommended protein intake in grams (g) for the individual, calculated based on body weight, activity level, and dietary objectives.'
      ),
    fat: z
      .number()
      .min(0, 'Fat must be a non-negative number.')
      .describe(
        'The total daily recommended fat intake in grams (g) for the individual, derived from the percentage of total calories allocated to dietary fat based on activity level and goals.'
      ),
    carbs: z
      .number()
      .min(0, 'Carbohydrates must be a non-negative number.')
      .describe(
        'The total daily recommended carbohydrate intake in grams (g) for the individual, derived from the remaining calories after accounting for protein and fat, adjusted for dietary preferences and activity level.'
      ),
  }),
});

console.log(
  nutritionGoalInputSchema.parse({
    age: 33,
    gender: 'female',
    weightKg: 88,
    lengthCm: 33,
    activityLevel: 'sedentary',
    dietaryGoals: {
      goalType: 'muscle_gain',
    },
    dietaryRestrictions: ['1'],
  } as z.infer<typeof nutritionGoalInputSchema>)
);

console.log(
  nutritionGoalOutputSchema.parse({
    specs: {
      calories: 123,
      carbs: 2344,
      fat: 343,
      protein: 233
    },
    
  } as z.infer<typeof nutritionGoalOutputSchema>)
);

odbol added a commit to odbol/zod that referenced this issue Nov 7, 2024
There's some kind of weird race condition or something where the strict null check fails but then returns an undefined object. 

See firebase/genkit#972 for details on how to reproduce.

```
  name: 'TypeError',
  message: "Cannot destructure property 'shape' of 'this._getCached(...)' as it is undefined.",
  stack: "TypeError: Cannot destructure property 'shape' of 'this._getCached(...)' as it is undefined.\n" +
    '    at ZodObject._parse (/Users/fuego/Documents/development/gemineye-server/node_modules/zod/lib/types.js:1848:17)\n' +
    '    at ZodObject._parseSync (/Users/fuego/Documents/development/gemineye-server/node_modules/zod/lib/types.js:146:29)\n' +
    '    at /Users/fuego/Documents/development/gemineye-server/node_modules/zod/lib/types.js:1711:29\n' +
    '    at Array.map (<anonymous>)\n' +
    '    at ZodArray._parse (/Users/fuego/Documents/development/gemineye-server/node_modules/zod/lib/types.js:1710:38)\n' +
    '    at ZodObject._parse (/Users/fuego/Documents/development/gemineye-server/node_modules/zod/lib/types.js:1864:37)\n' +
    '    at ZodObject._parseSync (/Users/fuego/Documents/development/gemineye-server/node_modules/zod/lib/types.js:146:29)\n' +
    '    at ZodObject.safeParse (/Users/fuego/Documents/development/gemineye-server/node_modules/zod/lib/types.js:176:29)\n' +
    '    at ZodObject.parse (/Users/fuego/Documents/development/gemineye-server/node_modules/zod/lib/types.js:157:29)\n' +
    '    at /Users/fuego/Documents/development/gemineye-server/node_modules/@genkit-ai/flow/lib/flow.js:522:55',
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working js
Projects
Status: No status
Development

No branches or pull requests

3 participants