Created
December 4, 2025 21:35
-
-
Save kibolho/e31e0dddb90f2608a803666043c7b057 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| This script demonstrates how the client.models.generate_content() call is structured | |
| with mock data, including: | |
| - Text input as user message | |
| - Mock current_program | |
| - Same prompt structure | |
| - Same configuration parameters | |
| Can be run independently: | |
| python simulate_programming_converter.py | |
| """ | |
| import base64 | |
| import json | |
| import mimetypes | |
| import os | |
| import time | |
| from io import BytesIO | |
| from typing import Literal, get_args | |
| from pydantic import BaseModel, Field | |
| from httpx import AsyncHTTPTransport | |
| from google import genai | |
| from google.genai import types as genai_types | |
| from google.genai.types import ( | |
| Content, | |
| GenerateContentConfig, | |
| HarmBlockThreshold, | |
| HarmCategory, | |
| Part, | |
| PartDict, | |
| SafetySetting, | |
| ThinkingConfig, | |
| ) | |
| # Constants (from project.ai.converter.constants) | |
| DEFAULT_PROJECT_ID = "superset-api-472315" | |
| MODEL_NAME = "gemini-2.5-flash" | |
| DEFAULT_LOCATION = "us-central1" | |
| USER_TEXT = f"""Basic structure | |
| Frequency: 2–3 non‑consecutive days per week (for example, Monday, Wednesday, Friday). | |
| Sets and reps: 1–3 sets of 8–15 reps per exercise, resting 1–2 minutes between sets. | |
| Intensity: Choose a weight that makes the last 2–3 reps challenging but still allows perfect technique. | |
| Warm‑up (5–10 minutes) | |
| 3–5 minutes of easy cardio such as brisk walking, cycling, or treadmill at low intensity. | |
| Dynamic movements: arm circles, leg swings, and gentle torso twists to prepare the joints. | |
| Full‑body workout (beginner) | |
| Do this circuit 2–3 times per week with at least one rest day between sessions. | |
| Squat to chair or bodyweight squat: 2–3 sets of 8–12 reps. | |
| Push‑up (wall or knee if needed): 2–3 sets of 6–10 reps. | |
| Hip hinge (dumbbell or bodyweight Romanian deadlift): 2–3 sets of 8–12 reps. | |
| Row (resistance band, machine, or dumbbells): 2–3 sets of 8–12 reps. | |
| Overhead shoulder press (light dumbbells or bands): 2–3 sets of 8–12 reps. | |
| Core: dead bug, bird dog, or basic plank for 15–30 seconds, 2–3 rounds. | |
| Rest 60–90 seconds between sets and exercises. | |
| Optional beginner cardio | |
| On 2–3 other days, add light cardio such as brisk walking, cycling, or elliptical for 20–30 minutes at an easy to moderate pace. | |
| If you share your age, current activity level, any injuries, and whether you train at home or gym (and available equipment), a more tailored plan can be created.""" | |
| DEFAULT_SAFETY_SETTINGS = [ | |
| SafetySetting(**kwargs) # type: ignore | |
| for kwargs in [ | |
| { | |
| "category": HarmCategory.HARM_CATEGORY_HARASSMENT, | |
| "threshold": HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE, | |
| }, | |
| { | |
| "category": HarmCategory.HARM_CATEGORY_HATE_SPEECH, | |
| "threshold": HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE, | |
| }, | |
| { | |
| "category": HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT, | |
| "threshold": HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE, | |
| }, | |
| { | |
| "category": HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, | |
| "threshold": HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE, | |
| }, | |
| ] | |
| ] | |
| def setup_google_credentials(): | |
| """Setup Google Cloud credentials from environment variables. | |
| Same logic as compose/production/django/entrypoint lines 56-63: | |
| - If GOOGLE_APPLICATION_CREDENTIALS_JSON is set, write it to a temp file | |
| - Set GOOGLE_APPLICATION_CREDENTIALS to point to that file | |
| - Clear GOOGLE_APPLICATION_CREDENTIALS_JSON | |
| """ | |
| # Check for JSON credentials in environment, or use hardcoded dict | |
| # Set GOOGLE_APPLICATION_CREDENTIALS to file path (same as entrypoint: export GOOGLE_APPLICATION_CREDENTIALS=/tmp/google/service-key.json) | |
| os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "./service-key.json" | |
| # Set environment variables for Vertex AI (same as programming_converter.py) | |
| os.environ["GOOGLE_CLOUD_PROJECT"] = os.getenv( | |
| "GOOGLE_CLOUD_PROJECT", DEFAULT_PROJECT_ID | |
| ) | |
| os.environ["GOOGLE_CLOUD_LOCATION"] = os.getenv( | |
| "GOOGLE_CLOUD_LOCATION", DEFAULT_LOCATION | |
| ) | |
| os.environ["GOOGLE_GENAI_USE_VERTEXAI"] = "true" | |
| def get_genai_client(): | |
| """Get or create a GenAI client (same as in programming_converter.py).""" | |
| setup_google_credentials() | |
| client = genai.Client( | |
| vertexai=True, | |
| http_options=genai_types.HttpOptions( | |
| async_client_args={"transport": AsyncHTTPTransport()} | |
| ), | |
| ) | |
| return client | |
| # Schema definitions (from project.ai.converter.programming_schema) | |
| # Only define if Pydantic is available | |
| GroupType = Literal["circuit", "warmup", "cooldown", "custom"] | |
| GROUP_TYPES = list(get_args(GroupType)) | |
| class RPE(BaseModel): | |
| """Rate of perceived exertion (RPE) or reps in reserve (RIR)""" | |
| value: str = Field(description="The RPE or RIR value") | |
| variant: Literal["rpe", "rir"] = Field(description="Whether this is RPE or RIR") | |
| class Weight(BaseModel): | |
| """Weight specification with units""" | |
| value: str = Field( | |
| description="The weight value as a number string (e.g., '135', '60')" | |
| ) | |
| units: Literal["lb", "kg"] = Field( | |
| description="The unit (lb/kg) is determined by the program-level weight_unit" | |
| ) | |
| class ProgramWorkoutExerciseSimple(BaseModel): | |
| """Any exercise with sets and reps or time with that info represented in metadata""" | |
| type: Literal["exercise"] = Field(default="exercise") | |
| exercise_name: str = Field(description="The name of the exercise") | |
| sets: str | None = Field( | |
| default=None, | |
| description=( | |
| "Number of sets. Extract from patterns like '3 x 5', '3x5', or '3×5' as sets='3'. " | |
| "If a list is given like '3 x 10, 8, 6', sets='3'. Store as a string." | |
| ), | |
| ) | |
| reps: str | None = Field( | |
| default=None, | |
| description=( | |
| "Repetitions per set, or a duration. Always include when the source specifies reps/time. " | |
| "Map text to reps as follows: " | |
| "'3 x 5': reps='5'; '3 x 10, 8, 6': reps='10, 8, 6'; '3 x 8e': reps='8e' (per-side marked with 'e'); " | |
| "'3 x 8-10': reps='8-10'; '3 x 1 minute': reps='1 minute'; " | |
| "'3 x 8 @ 85%': reps='8' and put '@ 85%' into notes. " | |
| "Accept 'x', 'X', or '×' as the separator. Store as a string." | |
| ), | |
| ) | |
| notes: str | None = Field( | |
| default=None, | |
| max_length=100, | |
| description=( | |
| "Use the notes property to put any information about an exercise that doesn't match " | |
| "any of the other properties, like @ 85%, tempo cues, drop sets, or narrative instructions." | |
| ), | |
| ) | |
| rest: str | None = Field( | |
| default=None, | |
| description="The rest value as a number string (e.g., '1m', '30s')", | |
| ) | |
| rpe: RPE | None = Field( | |
| default=None, | |
| description=( | |
| "Rate of perceived exertion (RPE) or reps in reserve (RIR). Supports multiple formats: " | |
| "1) Fixed RPE: '75%' or '7.5' for consistent effort across all sets; " | |
| "2) RPE range: '70-80%' or '7-8' for flexibility in effort level; " | |
| "3) Comma-separated: '60, 70, 80%' for different effort per set. " | |
| "RPE is typically on a scale of 1-10, while RIR is an integer representing reps in reserve." | |
| ), | |
| ) | |
| weight: Weight | None = Field( | |
| default=None, | |
| description=( | |
| "The weight value with units. Supports multiple formats: " | |
| "1) Fixed weight: '35' or '35+' for consistent weight across all sets; " | |
| "2) Percentage-based: '70%' or '70-80%' based on one-rep max; " | |
| "3) Weight range: '35-55' for flexibility in weight selection; " | |
| "4) Bodyweight: 'BW' to specify bodyweight exercises; " | |
| "5) Comma-separated: '35, 45, 55' for different weights per set. " | |
| "If you can't find a weight value, omit this field." | |
| ), | |
| ) | |
| class ProgramWorkoutExerciseFreeform(BaseModel): | |
| """An exercise that does not map to the formatted metadata, but instead has freeform text""" | |
| type: Literal["freeform"] = Field(default="freeform") | |
| exercise_name: str = Field(description="The name of the exercise") | |
| description: str = Field( | |
| description="This can be multi-line and even bulleted text" | |
| ) | |
| class ProgramWorkoutExerciseGroup(BaseModel): | |
| """A grouping of exercises within a workout that are meant to be completed together | |
| (as a cooldown, warmup, or custom type) or performed in rounds (as a circuit)""" | |
| type: Literal["group"] = Field(default="group") | |
| group_type: GroupType = Field(description="The type of group") | |
| title: str = Field(description="A descriptive title for this group of exercises") | |
| rounds: int | None = Field( | |
| default=None, | |
| description='Non-null only for "circuit" types, specify the number of times to repeat the circuit', | |
| ) | |
| exercises: list[ProgramWorkoutExerciseSimple] = Field( | |
| description="The specific exercises within this group" | |
| ) | |
| class ProgramWorkout(BaseModel): | |
| """A single workout within a program""" | |
| name: str = Field(description="The name of the workout") | |
| day_offset: int = Field( | |
| description=( | |
| "The number of days from the first day of the program (0-indexed) that this workout " | |
| "is meant to be performed. This number must be unique for each workout in a program." | |
| ) | |
| ) | |
| workout_exercises: list[ | |
| ProgramWorkoutExerciseSimple | |
| | ProgramWorkoutExerciseFreeform | |
| | ProgramWorkoutExerciseGroup | |
| ] = Field(description="The exercises in this workout") | |
| class CoachProgram(BaseModel): | |
| """A workout routine with a name and a list of workouts""" | |
| name: str = Field(description="The name of the program") | |
| weight_unit: Literal["lb", "kg"] = Field( | |
| description="The weight unit used throughout the program" | |
| ) | |
| workouts: list[ProgramWorkout] = Field(description="The workouts in this program") | |
| def create_mock_current_program() -> str: | |
| """Create a mock current_program as a JSON string.""" | |
| mock_program = { | |
| "name": "Superset Sheets", | |
| "type": "library", | |
| "weight_unit": "lb", | |
| "effort_variant": "rpe", | |
| "rir_tracking": None, | |
| "assignment_type": None, | |
| "expiration_date": None, | |
| "workouts": [ | |
| { | |
| "name": "Workout 1", | |
| "description": "", | |
| "columns": [ | |
| { | |
| "type": "sets" | |
| }, | |
| { | |
| "type": "reps" | |
| }, | |
| { | |
| "type": "weight", | |
| "units": "lb" | |
| }, | |
| { | |
| "type": "rpe" | |
| }, | |
| { | |
| "type": "rest" | |
| }, | |
| { | |
| "type": "notes" | |
| } | |
| ], | |
| "day_offset": 0, | |
| "workout_exercises": [ | |
| { | |
| "uuid": "5b0eaf98-a5f1-4298-aafc-dfc6932a6538", | |
| "type": "exercise", | |
| "position": 0, | |
| "metadata": [ | |
| { | |
| "type": "sets", | |
| "value": "3" | |
| }, | |
| { | |
| "type": "reps", | |
| "value": "12, 10, 8" | |
| }, | |
| { | |
| "type": "notes", | |
| "value": "1-3-1 tempo" | |
| }, | |
| { | |
| "type": "rpe", | |
| "value": "80%", | |
| "variant": "rpe" | |
| } | |
| ], | |
| "exercise_id": 69, | |
| "exercise_name": "Barbell Bench Press", | |
| "circuit_uuid": None | |
| }, | |
| { | |
| "uuid": "1a2577f1-064c-4543-a96f-af1aa9695918", | |
| "type": "exercise", | |
| "position": 1, | |
| "metadata": [ | |
| { | |
| "type": "sets", | |
| "value": "3" | |
| }, | |
| { | |
| "type": "reps", | |
| "value": "12" | |
| }, | |
| { | |
| "type": "notes", | |
| "value": "Keep your heels down and drive your knees out over your toes" | |
| }, | |
| { | |
| "type": "rest", | |
| "value": "60s" | |
| }, | |
| { | |
| "type": "weight", | |
| "value": "95, 115, 135", | |
| "units": "lb" | |
| } | |
| ], | |
| "exercise_id": 31, | |
| "exercise_name": "Back Squat", | |
| "circuit_uuid": None | |
| }, | |
| { | |
| "uuid": "6ef4c42f-b6e9-4532-b17d-1a2bd277721a", | |
| "exercise_name": "Circuit", | |
| "type": "circuit", | |
| "position": 2, | |
| "metadata": [ | |
| { | |
| "type": "sets", | |
| "value": "3" | |
| } | |
| ] | |
| }, | |
| { | |
| "uuid": "23ac37f9-0e45-47d3-adea-c12d223004f9", | |
| "type": "exercise", | |
| "position": 0, | |
| "metadata": [ | |
| { | |
| "type": "sets", | |
| "value": "1" | |
| }, | |
| { | |
| "type": "reps", | |
| "value": "20s" | |
| }, | |
| { | |
| "type": "weight", | |
| "value": "40", | |
| "units": "lb" | |
| } | |
| ], | |
| "exercise_id": 527, | |
| "exercise_name": "Kettlebell Swing", | |
| "circuit_uuid": "6ef4c42f-b6e9-4532-b17d-1a2bd277721a" | |
| }, | |
| { | |
| "uuid": "6cd2d009-4600-4f4f-afac-b1e515295dca", | |
| "type": "exercise", | |
| "position": 1, | |
| "metadata": [ | |
| { | |
| "type": "sets", | |
| "value": "1" | |
| }, | |
| { | |
| "type": "reps", | |
| "value": "AMRAP" | |
| }, | |
| { | |
| "type": "weight", | |
| "value": "BW", | |
| "units": "lb" | |
| } | |
| ], | |
| "exercise_id": 489, | |
| "exercise_name": "Hanging Leg Raise", | |
| "circuit_uuid": "6ef4c42f-b6e9-4532-b17d-1a2bd277721a" | |
| }, | |
| { | |
| "uuid": "5cbb02d9-54da-4dbd-a51d-c863057f0d7b", | |
| "type": "freeform", | |
| "description": "As many rounds as possible of...21 Toes to bar15 Thrusters9 Burpees10 minute time cap", | |
| "position": 3, | |
| "metadata": [], | |
| "exercise_name": "Finisher" | |
| }, | |
| { | |
| "uuid": "a74e1f6b-255b-4bea-9d22-95b035976696", | |
| "exercise_name": "Cool Down", | |
| "type": "cooldown", | |
| "position": 4, | |
| "metadata": [ | |
| { | |
| "type": "sets", | |
| "value": "1" | |
| } | |
| ] | |
| }, | |
| { | |
| "uuid": "a7c28a37-5c8f-42cb-932d-6d9902f57ce5", | |
| "type": "exercise", | |
| "position": 0, | |
| "metadata": [ | |
| { | |
| "type": "sets", | |
| "value": "3" | |
| }, | |
| { | |
| "type": "reps", | |
| "value": "5" | |
| } | |
| ], | |
| "exercise_id": 279, | |
| "exercise_name": "Cat Cows", | |
| "circuit_uuid": "a74e1f6b-255b-4bea-9d22-95b035976696" | |
| }, | |
| { | |
| "uuid": "17cb3203-5710-4be9-891c-b08c21e2e1b8", | |
| "type": "exercise", | |
| "position": 1, | |
| "metadata": [ | |
| { | |
| "type": "sets", | |
| "value": "3" | |
| }, | |
| { | |
| "type": "reps", | |
| "value": "20s" | |
| } | |
| ], | |
| "exercise_id": 223, | |
| "exercise_name": "Butterfly Stretch", | |
| "circuit_uuid": "a74e1f6b-255b-4bea-9d22-95b035976696" | |
| } | |
| ], | |
| "uuid": "9bddaef0-be60-4225-8e5e-bbbdcdb30915" | |
| }, | |
| { | |
| "name": "Workout 2", | |
| "description": "", | |
| "columns": [ | |
| { | |
| "type": "sets" | |
| }, | |
| { | |
| "type": "reps" | |
| }, | |
| { | |
| "type": "weight", | |
| "units": "lb" | |
| }, | |
| { | |
| "type": "rpe" | |
| }, | |
| { | |
| "type": "rest" | |
| }, | |
| { | |
| "type": "notes" | |
| } | |
| ], | |
| "day_offset": 1, | |
| "workout_exercises": [ | |
| { | |
| "uuid": "9d3bd1c8-e1f5-4eff-967a-34ed6778f27b", | |
| "exercise_name": "Block 1", | |
| "type": "circuit", | |
| "position": 0, | |
| "metadata": [ | |
| { | |
| "type": "sets", | |
| "value": "3" | |
| } | |
| ] | |
| }, | |
| { | |
| "uuid": "4a650d80-a185-4a01-90a8-623d62ff5545", | |
| "type": "exercise", | |
| "position": 0, | |
| "metadata": [ | |
| { | |
| "type": "reps", | |
| "value": "10" | |
| }, | |
| { | |
| "type": "sets", | |
| "value": "1" | |
| } | |
| ], | |
| "exercise_id": 613, | |
| "exercise_name": "Medicine Ball Wall Slam", | |
| "circuit_uuid": "9d3bd1c8-e1f5-4eff-967a-34ed6778f27b" | |
| }, | |
| { | |
| "uuid": "3f36dc96-4281-4f59-9330-ef1b968cfff2", | |
| "type": "exercise", | |
| "position": 1, | |
| "metadata": [ | |
| { | |
| "type": "reps", | |
| "value": "30s" | |
| }, | |
| { | |
| "type": "sets", | |
| "value": "1" | |
| } | |
| ], | |
| "exercise_name": "Hang", | |
| "circuit_uuid": "9d3bd1c8-e1f5-4eff-967a-34ed6778f27b" | |
| }, | |
| { | |
| "uuid": "d4a07268-32c4-4f33-8439-5c2ec508458a", | |
| "type": "exercise", | |
| "position": 2, | |
| "metadata": [ | |
| { | |
| "type": "reps", | |
| "value": "1:00" | |
| }, | |
| { | |
| "type": "sets", | |
| "value": "1" | |
| } | |
| ], | |
| "exercise_name": "Rower", | |
| "circuit_uuid": "9d3bd1c8-e1f5-4eff-967a-34ed6778f27b" | |
| }, | |
| { | |
| "uuid": "0060bf29-00e8-4e00-bef4-51b0179229a2", | |
| "type": "exercise", | |
| "position": 3, | |
| "metadata": [ | |
| { | |
| "type": "reps", | |
| "value": "20s" | |
| }, | |
| { | |
| "type": "notes", | |
| "value": "each side" | |
| }, | |
| { | |
| "type": "sets", | |
| "value": "1" | |
| } | |
| ], | |
| "exercise_id": 919, | |
| "exercise_name": "Single-Arm Walking Dumbbell Farmer’s Carry", | |
| "circuit_uuid": "9d3bd1c8-e1f5-4eff-967a-34ed6778f27b" | |
| }, | |
| { | |
| "uuid": "197d5cac-55ab-4437-ab36-f8a82c842bad", | |
| "exercise_name": "Block 2", | |
| "type": "circuit", | |
| "position": 1, | |
| "metadata": [ | |
| { | |
| "type": "sets", | |
| "value": "4" | |
| } | |
| ] | |
| }, | |
| { | |
| "uuid": "967bd471-c5b7-4c4f-a7f7-7994b742bf0a", | |
| "type": "exercise", | |
| "position": 0, | |
| "metadata": [ | |
| { | |
| "type": "reps", | |
| "value": "30s" | |
| }, | |
| { | |
| "type": "sets", | |
| "value": "1" | |
| } | |
| ], | |
| "exercise_name": "Crossover Footspeed", | |
| "circuit_uuid": "197d5cac-55ab-4437-ab36-f8a82c842bad" | |
| }, | |
| { | |
| "uuid": "37d19b4c-ce69-483d-b0a3-0357807ad065", | |
| "type": "exercise", | |
| "position": 1, | |
| "metadata": [ | |
| { | |
| "type": "reps", | |
| "value": "40 yds" | |
| }, | |
| { | |
| "type": "notes", | |
| "value": "heavy" | |
| }, | |
| { | |
| "type": "sets", | |
| "value": "1" | |
| } | |
| ], | |
| "exercise_id": 957, | |
| "exercise_name": "Sled Push", | |
| "circuit_uuid": "197d5cac-55ab-4437-ab36-f8a82c842bad" | |
| }, | |
| { | |
| "uuid": "75bf9582-e081-4b63-a3df-09c90b8724e3", | |
| "type": "exercise", | |
| "position": 2, | |
| "metadata": [ | |
| { | |
| "type": "reps", | |
| "value": "15" | |
| }, | |
| { | |
| "type": "notes", | |
| "value": "each arm/side" | |
| }, | |
| { | |
| "type": "sets", | |
| "value": "1" | |
| } | |
| ], | |
| "exercise_id": 865, | |
| "exercise_name": "Single-Arm Band Row", | |
| "circuit_uuid": "197d5cac-55ab-4437-ab36-f8a82c842bad" | |
| }, | |
| { | |
| "uuid": "f023387b-0a1b-4983-b8ab-c8c39752c42c", | |
| "type": "exercise", | |
| "position": 3, | |
| "metadata": [ | |
| { | |
| "type": "reps", | |
| "value": "8" | |
| }, | |
| { | |
| "type": "sets", | |
| "value": "1" | |
| } | |
| ], | |
| "exercise_id": 349, | |
| "exercise_name": "Deadlift", | |
| "circuit_uuid": "197d5cac-55ab-4437-ab36-f8a82c842bad" | |
| }, | |
| { | |
| "uuid": "b577b467-61ee-49ea-b63a-4cab41a0506b", | |
| "exercise_name": "Block 3", | |
| "type": "circuit", | |
| "position": 2, | |
| "metadata": [ | |
| { | |
| "type": "sets", | |
| "value": "4" | |
| } | |
| ] | |
| }, | |
| { | |
| "uuid": "b0583cf6-1d34-4616-be4b-a92b3305ea5f", | |
| "type": "exercise", | |
| "position": 0, | |
| "metadata": [ | |
| { | |
| "type": "reps", | |
| "value": "10" | |
| }, | |
| { | |
| "type": "sets", | |
| "value": "1" | |
| } | |
| ], | |
| "exercise_id": 880, | |
| "exercise_name": "Single-Arm Dumbbell Floor Press", | |
| "circuit_uuid": "b577b467-61ee-49ea-b63a-4cab41a0506b" | |
| }, | |
| { | |
| "uuid": "1bcb117a-dd8f-44f7-afef-d7036bf5c090", | |
| "type": "exercise", | |
| "position": 1, | |
| "metadata": [ | |
| { | |
| "type": "sets", | |
| "value": "1" | |
| } | |
| ], | |
| "exercise_name": "Curve Fartlek", | |
| "circuit_uuid": "b577b467-61ee-49ea-b63a-4cab41a0506b" | |
| }, | |
| { | |
| "uuid": "e21f53c0-2b41-4e34-b479-9db0641a1f04", | |
| "type": "exercise", | |
| "position": 2, | |
| "metadata": [ | |
| { | |
| "type": "reps", | |
| "value": "20s" | |
| }, | |
| { | |
| "type": "notes", | |
| "value": "each side" | |
| }, | |
| { | |
| "type": "sets", | |
| "value": "1" | |
| } | |
| ], | |
| "exercise_id": 277, | |
| "exercise_name": "Calf Stretch", | |
| "circuit_uuid": "b577b467-61ee-49ea-b63a-4cab41a0506b" | |
| }, | |
| { | |
| "uuid": "78df5ecf-fdce-49e6-8e4d-7967abcd9aa7", | |
| "type": "exercise", | |
| "position": 3, | |
| "metadata": [ | |
| { | |
| "type": "reps", | |
| "value": "5" | |
| }, | |
| { | |
| "type": "sets", | |
| "value": "1" | |
| } | |
| ], | |
| "exercise_id": 292, | |
| "exercise_name": "Chin Ups", | |
| "circuit_uuid": "b577b467-61ee-49ea-b63a-4cab41a0506b" | |
| }, | |
| { | |
| "uuid": "9a97d4ae-f4a8-446c-96d6-f061499cfdde", | |
| "exercise_name": "Block 4", | |
| "type": "circuit", | |
| "position": 3, | |
| "metadata": [ | |
| { | |
| "type": "sets", | |
| "value": "3" | |
| } | |
| ] | |
| }, | |
| { | |
| "uuid": "c9c1910f-91a0-428c-9fa7-9f235046ece3", | |
| "type": "exercise", | |
| "position": 0, | |
| "metadata": [ | |
| { | |
| "type": "reps", | |
| "value": "8" | |
| }, | |
| { | |
| "type": "sets", | |
| "value": "1" | |
| } | |
| ], | |
| "exercise_id": 372, | |
| "exercise_name": "Dumbbell Alternate Biceps Curl", | |
| "circuit_uuid": "9a97d4ae-f4a8-446c-96d6-f061499cfdde" | |
| }, | |
| { | |
| "uuid": "c78ec043-612d-4e57-8e31-f2b7d6078b77", | |
| "type": "exercise", | |
| "position": 1, | |
| "metadata": [ | |
| { | |
| "type": "reps", | |
| "value": "30s" | |
| }, | |
| { | |
| "type": "notes", | |
| "value": "each side" | |
| }, | |
| { | |
| "type": "sets", | |
| "value": "1" | |
| } | |
| ], | |
| "exercise_name": "Pallof Press Isometric Hold", | |
| "circuit_uuid": "9a97d4ae-f4a8-446c-96d6-f061499cfdde" | |
| }, | |
| { | |
| "uuid": "6e56d3c0-429b-44be-a422-a209b936cb3b", | |
| "type": "exercise", | |
| "position": 2, | |
| "metadata": [ | |
| { | |
| "type": "reps", | |
| "value": "5" | |
| }, | |
| { | |
| "type": "notes", | |
| "value": "each leg" | |
| }, | |
| { | |
| "type": "sets", | |
| "value": "1" | |
| } | |
| ], | |
| "exercise_id": 942, | |
| "exercise_name": "Single-Leg Squat to Box", | |
| "circuit_uuid": "9a97d4ae-f4a8-446c-96d6-f061499cfdde" | |
| }, | |
| { | |
| "uuid": "440ba052-4038-4c4b-a9a5-a959fe688b7d", | |
| "type": "exercise", | |
| "position": 3, | |
| "metadata": [ | |
| { | |
| "type": "reps", | |
| "value": "20" | |
| }, | |
| { | |
| "type": "sets", | |
| "value": "1" | |
| } | |
| ], | |
| "exercise_name": "Flutter Kicks", | |
| "circuit_uuid": "9a97d4ae-f4a8-446c-96d6-f061499cfdde" | |
| }, | |
| { | |
| "uuid": "54f387c4-44c2-4be0-9146-6fa21deb0a01", | |
| "exercise_name": "Block 5", | |
| "type": "circuit", | |
| "position": 4, | |
| "metadata": [ | |
| { | |
| "type": "sets", | |
| "value": "3" | |
| } | |
| ] | |
| }, | |
| { | |
| "uuid": "d71892af-25e6-476f-85f7-68e09362ef79", | |
| "type": "exercise", | |
| "position": 0, | |
| "metadata": [ | |
| { | |
| "type": "reps", | |
| "value": "12" | |
| }, | |
| { | |
| "type": "sets", | |
| "value": "1" | |
| } | |
| ], | |
| "exercise_name": "Quadruped Hip Rockers", | |
| "circuit_uuid": "54f387c4-44c2-4be0-9146-6fa21deb0a01" | |
| }, | |
| { | |
| "uuid": "64dbfc69-ec13-4569-b911-c5a5b34c0a76", | |
| "type": "exercise", | |
| "position": 1, | |
| "metadata": [ | |
| { | |
| "type": "reps", | |
| "value": "5" | |
| }, | |
| { | |
| "type": "notes", | |
| "value": "each side" | |
| }, | |
| { | |
| "type": "sets", | |
| "value": "1" | |
| } | |
| ], | |
| "exercise_name": "Active Isolated Hamstring Stretch", | |
| "circuit_uuid": "54f387c4-44c2-4be0-9146-6fa21deb0a01" | |
| }, | |
| { | |
| "uuid": "29be751a-b556-4097-be49-0b24c5416305", | |
| "type": "exercise", | |
| "position": 2, | |
| "metadata": [ | |
| { | |
| "type": "reps", | |
| "value": "5" | |
| }, | |
| { | |
| "type": "sets", | |
| "value": "1" | |
| } | |
| ], | |
| "exercise_name": "Prev. Ben Johnsons", | |
| "circuit_uuid": "54f387c4-44c2-4be0-9146-6fa21deb0a01" | |
| }, | |
| { | |
| "uuid": "ff01ecd8-d895-4e8a-9cc8-088e89a36d0c", | |
| "type": "exercise", | |
| "position": 3, | |
| "metadata": [ | |
| { | |
| "type": "reps", | |
| "value": "30s" | |
| }, | |
| { | |
| "type": "sets", | |
| "value": "1" | |
| } | |
| ], | |
| "exercise_name": "Lat Stretch", | |
| "circuit_uuid": "54f387c4-44c2-4be0-9146-6fa21deb0a01" | |
| } | |
| ], | |
| "uuid": "5076adb2-8158-45d8-a831-7c579cb2ff40" | |
| } | |
| ] | |
| } | |
| return json.dumps(mock_program, indent=2) | |
| def simulate_api_call(): | |
| """Simulate the client.models.generate_content() call with fake data.""" | |
| # Mock current_program (as JSON string, as expected by the function) | |
| current_program = create_mock_current_program() | |
| # Build the prompt with current program context (same as in programming_converter.py) | |
| prompt_parts = [ | |
| "You are an expert personal trainer and create workout programs from any input." | |
| ] | |
| if current_program: | |
| prompt_parts.append( | |
| "\n\nIMPORTANT: The user has an existing program. Please review the current program structure below " | |
| "and make sure to preserve existing workouts when possible. Only modify or add workouts based on the user's request. " | |
| "Do not override existing workouts unless explicitly requested.\n\n" | |
| f"Current program structure:\n{current_program}" | |
| ) | |
| prompt = "\n".join(prompt_parts).strip() | |
| # Convert text input to file-like object (same as convert_program does) | |
| infile = BytesIO(USER_TEXT.encode("utf8")) | |
| infile.name = "workout_program.txt" | |
| # Build file part (same as _get_file_part does) | |
| file_data = infile.read() | |
| infile.seek(0) | |
| mime_type, _ = mimetypes.guess_type(infile.name) | |
| if not mime_type: | |
| mime_type = "text/plain" | |
| if not PartDict or not Content: | |
| raise ImportError( | |
| "google-genai types are not available. Install with: pip install google-genai" | |
| ) | |
| file_part = PartDict( # type: ignore | |
| inline_data={ | |
| "mime_type": mime_type, | |
| "data": base64.b64encode(file_data).decode("utf-8"), | |
| } | |
| ) | |
| # Build contents array (same structure as in programming_converter.py) | |
| contents = [ | |
| Content( # type: ignore | |
| role="user", | |
| parts=[file_part], | |
| ) | |
| ] | |
| client = get_genai_client() | |
| print("=" * 80) | |
| print(f"\nModel: {MODEL_NAME}") | |
| print(f"\nPrompt:\n{prompt[:200]}...") # Show first 200 chars | |
| print(f"\nUser Input (text): {USER_TEXT}") | |
| print(f"\nCurrent Program (first 200 chars):\n{current_program[:200]}...") | |
| print("\n" + "=" * 80) | |
| print("CALL PARAMETERS:") | |
| print("=" * 80) | |
| print("\n📡 Making API call...") | |
| try: | |
| # Measure time taken for API call | |
| start_time = time.time() | |
| response = client.models.generate_content( # type: ignore | |
| model=MODEL_NAME, | |
| contents=contents, | |
| config=GenerateContentConfig( | |
| response_mime_type="application/json", | |
| thinking_config=ThinkingConfig( # type: ignore | |
| thinking_budget=-1, | |
| ), | |
| system_instruction=[Part.from_text(text=prompt)], # type: ignore | |
| safety_settings=DEFAULT_SAFETY_SETTINGS, | |
| max_output_tokens=16384, | |
| response_schema=CoachProgram, | |
| ), # type: ignore | |
| ) | |
| end_time = time.time() | |
| elapsed_time = end_time - start_time | |
| print(f"\n✅ Real API call completed successfully!") | |
| print( | |
| f"⏱️ Time taken: {elapsed_time:.2f} seconds ({elapsed_time * 1000:.0f} ms)" | |
| ) | |
| response_text = getattr(response, "text", "") or "" | |
| print(f"\nResponse text (first 500 chars):\n{response_text[:500]}...") | |
| # Validate response | |
| try: | |
| program = CoachProgram.model_validate_json(response_text) | |
| print("\n✅ Response validated successfully with Pydantic schema!") | |
| print(f"\nProgram name: {program.name}") | |
| print(f"Number of workouts: {len(program.workouts)}") | |
| print(f"Weight unit: {program.weight_unit}") | |
| except Exception as e: | |
| print(f"\n⚠️ Validation error: {e}") | |
| # Try to parse as JSON for debugging | |
| try: | |
| parsed = json.loads(response_text) | |
| print(f" Response is valid JSON with keys: {list(parsed.keys())}") | |
| except: | |
| print(" Response is not valid JSON") | |
| return response | |
| except Exception as e: | |
| print(f"\n❌ API call failed: {e}") | |
| raise | |
| if __name__ == "__main__": | |
| simulate_api_call() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment