diff --git a/py/samples/menu/pyproject.toml b/py/samples/menu/pyproject.toml index 7ba7975d49..d2e4006dd9 100644 --- a/py/samples/menu/pyproject.toml +++ b/py/samples/menu/pyproject.toml @@ -54,4 +54,14 @@ build-backend = "hatchling.build" requires = ["hatchling"] [tool.hatch.build.targets.wheel] -packages = ["src/menu"] +packages = ["src"] + +[tool.uv.sources] +genkit = { workspace = true } +genkit-plugin-dev-local-vectorstore = { workspace = true } +genkit-plugin-firebase = { workspace = true } +genkit-plugin-google-genai = { workspace = true } +genkit-plugin-google-cloud = { workspace = true } +genkit-plugin-ollama = { workspace = true } +genkit-plugin-vertex-ai = { workspace = true } + diff --git a/py/samples/menu/src/case_02/flows.py b/py/samples/menu/src/case_02/flows.py index b0ef54f9d0..8be39a07cb 100644 --- a/py/samples/menu/src/case_02/flows.py +++ b/py/samples/menu/src/case_02/flows.py @@ -21,7 +21,7 @@ from .prompts import s02_dataMenuPrompt -@ai.flow(name='s02_menuQuestion') +@ai.flow(name='s02_menu_question') async def s02_menuQuestionFlow( my_input: MenuQuestionInputSchema, ) -> AnswerOutputSchema: diff --git a/py/samples/menu/src/case_03/flows.py b/py/samples/menu/src/case_03/flows.py index 7b6668104c..f90cce191f 100644 --- a/py/samples/menu/src/case_03/flows.py +++ b/py/samples/menu/src/case_03/flows.py @@ -61,7 +61,7 @@ ) -@ai.flow(name='s03_multiTurnChat') +@ai.flow(name='s03_multi_turn_chat') async def s03_multiTurnChatFlow( my_input: ChatSessionInputSchema, ) -> ChatSessionOutputSchema: diff --git a/py/samples/menu/src/case_04/flows.py b/py/samples/menu/src/case_04/flows.py index 2377e743fd..3541986616 100644 --- a/py/samples/menu/src/case_04/flows.py +++ b/py/samples/menu/src/case_04/flows.py @@ -14,6 +14,9 @@ # # SPDX-License-Identifier: Apache-2.0 +import json +import os + from menu_ai import ai from menu_schemas import AnswerOutputSchema, MenuItemSchema, MenuQuestionInputSchema from pydantic import BaseModel, Field @@ -27,10 +30,17 @@ class IndexMenuItemsOutputSchema(BaseModel): rows: int = Field(...) -@ai.flow(name='s04_indexMenuItems') +@ai.flow(name='s04_index_menu_items') async def s04_indexMenuItemsFlow( menu_items: list[MenuItemSchema], ) -> IndexMenuItemsOutputSchema: + # If empty list provided (e.g., from Dev UI default), load from example file + if not menu_items: + example_file = os.path.join(os.path.dirname(__file__), 'example.indexMenuItems.json') + with open(example_file, 'r') as f: + menu_data = json.load(f) + menu_items = [MenuItemSchema(**item) for item in menu_data] + documents = [ Document.from_text(f'{item.title} {item.price} \n {item.description}', metadata=item.model_dump()) for item in menu_items @@ -43,7 +53,7 @@ async def s04_indexMenuItemsFlow( return IndexMenuItemsOutputSchema(rows=len(menu_items)) -@ai.flow(name='s04_ragMenuQuestion') +@ai.flow(name='s04_rag_menu_question') async def s04_ragMenuQuestionFlow( my_input: MenuQuestionInputSchema, ) -> AnswerOutputSchema: diff --git a/py/samples/menu/src/case_05/flows.py b/py/samples/menu/src/case_05/flows.py index e6fe2dbcee..d254bf7ff4 100644 --- a/py/samples/menu/src/case_05/flows.py +++ b/py/samples/menu/src/case_05/flows.py @@ -19,6 +19,7 @@ import os from case_05.prompts import s05_readMenuPrompt, s05_textMenuPrompt +from constants import DEFAULT_MENU_QUESTION from menu_ai import ai from menu_schemas import ( AnswerOutputSchema, @@ -27,32 +28,35 @@ ) -@ai.flow(name='s05_readMenu') +@ai.flow(name='s05_read_menu') async def s05_readMenuFlow(_: None = None) -> str: image_data_url = inline_data_url('menu.jpeg', 'image/jpeg') response = await s05_readMenuPrompt({'imageUrl': image_data_url}) return response.text -@ai.flow(name='s05_textMenuQuestion') +@ai.flow(name='s05_text_menu_question') async def s05_textMenuQuestionFlow( my_input: TextMenuQuestionInputSchema, ) -> AnswerOutputSchema: - response = await s05_textMenuPrompt({'menuText': my_input.menuText, 'question': my_input.question}) + response = await s05_textMenuPrompt({'menuText': my_input.menu_text, 'question': my_input.question}) return AnswerOutputSchema( answer=response.text, ) -@ai.flow(name='s05_visionMenuQuestion') +@ai.flow(name='s05_vision_menu_question') async def s05_visionMenuQuestionFlow( my_input: MenuQuestionInputSchema, ) -> AnswerOutputSchema: + # If empty question provided (e.g., from Dev UI default), use the default question + question = my_input.question if my_input.question else DEFAULT_MENU_QUESTION + menu_text = await s05_readMenuFlow() return await s05_textMenuQuestionFlow( TextMenuQuestionInputSchema( - question=my_input.question, - menuText=menu_text, + question=question, + menu_text=menu_text, ) ) diff --git a/py/samples/menu/src/constants.py b/py/samples/menu/src/constants.py new file mode 100644 index 0000000000..91c245adf7 --- /dev/null +++ b/py/samples/menu/src/constants.py @@ -0,0 +1,36 @@ +# Copyright 2026 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. +# +# SPDX-License-Identifier: Apache-2.0 + +"""Constants used across the menu sample.""" + +# Default question for menu-related flows +DEFAULT_MENU_QUESTION = 'What kind of burger buns do you have?' + +# Default menu text for text-based menu flows +DEFAULT_MENU_TEXT = """APPETIZERS +- Mozzarella Sticks $8 - Crispy fried mozzarella sticks served with marinara sauce +- Chicken Wings $10 - Crispy fried chicken wings tossed in your choice of sauce +- Nachos $12 - Crispy tortilla chips topped with melted cheese, chili, sour cream, and salsa + +BURGERS & SANDWICHES +- Classic Cheeseburger $12 - A juicy beef patty topped with melted American cheese, lettuce, tomato, and onion on a toasted bun +- Bacon Cheeseburger $14 - A classic cheeseburger with the addition of crispy bacon +- Mushroom Swiss Burger $15 - A beef patty topped with sautéed mushrooms, melted Swiss cheese, and a creamy horseradish sauce +- Chicken Sandwich $13 - A crispy chicken breast on a toasted bun with lettuce, tomato, and your choice of sauce + +SALADS +- House Salad $8 - Mixed greens with your choice of dressing +- Caesar Salad $9 - Romaine lettuce with croutons, Parmesan cheese, and Caesar dressing""" diff --git a/py/samples/menu/src/menu_ai.py b/py/samples/menu/src/menu_ai.py index 5d711a2a70..dfa8c2157d 100644 --- a/py/samples/menu/src/menu_ai.py +++ b/py/samples/menu/src/menu_ai.py @@ -22,7 +22,13 @@ from genkit.plugins.google_genai import GoogleAI if 'GEMINI_API_KEY' not in os.environ: - os.environ['GEMINI_API_KEY'] = input('Please enter your GEMINI_API_KEY: ') + raise ValueError( + 'GEMINI_API_KEY environment variable is required to run this sample.\n' + 'Please set it before starting the application:\n' + ' export GEMINI_API_KEY=your_api_key_here\n' + 'You can get an API key from https://aistudio.google.com/' + ) + ai = Genkit(plugins=[GoogleAI()]) diff --git a/py/samples/menu/src/menu_schemas.py b/py/samples/menu/src/menu_schemas.py index 2bc35cf4fb..1b2a414df4 100644 --- a/py/samples/menu/src/menu_schemas.py +++ b/py/samples/menu/src/menu_schemas.py @@ -15,6 +15,7 @@ # SPDX-License-Identifier: Apache-2.0 +from constants import DEFAULT_MENU_QUESTION, DEFAULT_MENU_TEXT from pydantic import BaseModel, Field @@ -29,7 +30,7 @@ class MenuItemSchema(BaseModel): class MenuQuestionInputSchema(BaseModel): """Input schema for the menu question prompt.""" - question: str = Field(..., description='A question about the menu') + question: str = Field(default=DEFAULT_MENU_QUESTION, description='A question about the menu') class AnswerOutputSchema(BaseModel): @@ -48,8 +49,12 @@ class DataMenuQuestionInputSchema(BaseModel): class TextMenuQuestionInputSchema(BaseModel): """Input schema for the text menu question prompt.""" - menu_text: str = Field(...) - question: str = Field(..., description='A question about the menu') + menu_text: str = Field( + default=DEFAULT_MENU_TEXT, + description='The menu text content', + alias='menuText', + ) + question: str = Field(default=DEFAULT_MENU_QUESTION, description='A question about the menu') class MenuToolOutputSchema(BaseModel):