Spaces:
Sleeping
Sleeping
| import os, time | |
| def query_model(prompt, max_tokens=1500): | |
| """Single Groq API call.""" | |
| GROQ_API_KEY = os.getenv("GROQ_API_KEY", "").strip() | |
| if not GROQ_API_KEY: | |
| raise ValueError( | |
| "GROQ_API_KEY is not set.\n\n" | |
| "Steps to fix:\n" | |
| "1. Go to https://console.groq.com β sign up free\n" | |
| "2. Click 'API Keys' β 'Create API Key'\n" | |
| "3. Copy the key (starts with gsk_...)\n" | |
| "4. In your HuggingFace Space β Settings β Variables and Secrets\n" | |
| " β Add secret: Name = GROQ_API_KEY, Value = gsk_xxxxxxxxxxxx\n" | |
| "5. Restart the Space" | |
| ) | |
| try: | |
| from groq import Groq | |
| client = Groq(api_key=GROQ_API_KEY) | |
| response = client.chat.completions.create( | |
| model="llama-3.3-70b-versatile", # fastest + best quality on Groq | |
| messages=[ | |
| { | |
| "role": "system", | |
| "content": ( | |
| "You are a certified professional fitness trainer. " | |
| "Provide detailed, structured, day-by-day workout plans exactly as instructed." | |
| ) | |
| }, | |
| {"role": "user", "content": prompt} | |
| ], | |
| max_tokens=max_tokens, | |
| temperature=0.7, | |
| ) | |
| return response.choices[0].message.content | |
| except Exception as e: | |
| err = str(e) | |
| if "401" in err or "invalid_api_key" in err.lower() or "authentication" in err.lower(): | |
| raise ValueError( | |
| "β Groq API key is invalid or expired.\n\n" | |
| "Fix: Go to https://console.groq.com/keys β create a new key β " | |
| "update GROQ_API_KEY in your HuggingFace Space Secrets." | |
| ) from None | |
| if "429" in err or "rate_limit" in err.lower() or "rate limit" in err.lower(): | |
| raise ValueError( | |
| "β³ Groq rate limit hit. Wait 60 seconds and try again.\n\n" | |
| "Free tier: 6,000 tokens/min. Upgrade at console.groq.com for higher limits." | |
| ) from None | |
| if "503" in err or "504" in err or "timeout" in err.lower(): | |
| raise ValueError( | |
| "β‘ Groq server busy. Please try again in a few seconds." | |
| ) from None | |
| raise | |
| def query_model_chunked(name, gender, height, weight, goal, fitness_level, | |
| equipment, days_per_week=5, months=1, | |
| progress_callback=None): | |
| """ | |
| Generate a long plan by splitting into chunks of 3 days each. | |
| Groq is fast enough (~2-3s per chunk) so even 30-day plans finish in ~30s. | |
| progress_callback(chunk_num, total_chunks, days_done, total_days) β optional. | |
| Returns: (full_plan_text, bmi, bmi_cat) | |
| """ | |
| from prompt_builder import calculate_bmi, bmi_category, bmi_advice | |
| bmi = calculate_bmi(weight, height) | |
| bmi_cat = bmi_category(bmi) | |
| advice = bmi_advice(bmi_cat) | |
| total_days = min(days_per_week * 4 * months, 30) | |
| eq_list = ", ".join(equipment) if equipment else "Bodyweight only" | |
| intensity_map = { | |
| "Beginner": "2β3 sets, moderate weight, 90s rest. Prioritise form.", | |
| "Intermediate": "3β4 sets, progressive overload, 60β75s rest.", | |
| "Advanced": "4β5 sets, heavy compounds, 45β60s rest, supersets.", | |
| } | |
| intensity = intensity_map.get(fitness_level, "3 sets, 60s rest.") | |
| CHUNK_SIZE = 3 # 3 days per call β well within Groq token limits | |
| chunks = [] | |
| start_day = 1 | |
| total_chunks = max(1, (total_days + CHUNK_SIZE - 1) // CHUNK_SIZE) | |
| while start_day <= total_days: | |
| end_day = min(start_day + CHUNK_SIZE - 1, total_days) | |
| chunk_num = len(chunks) + 1 | |
| if progress_callback: | |
| progress_callback(chunk_num, total_chunks, start_day - 1, total_days) | |
| context_note = "" | |
| if chunks: | |
| context_note = ( | |
| f"(Continue from Day {start_day}. Do NOT repeat Days 1β{start_day-1}. " | |
| f"Vary muscle groups from previous days.)" | |
| ) | |
| prompt = f"""Fitness trainer. Generate ONLY Days {start_day}-{end_day} of a {total_days}-day plan. | |
| Client: {name}, {gender}, {height}cm/{weight}kg, BMI {bmi:.1f} ({bmi_cat}), Goal: {goal}, Level: {fitness_level}, Equipment: {eq_list}. | |
| {context_note} | |
| For EACH day use EXACTLY this format: | |
| ## Day N - [Muscle Group] | |
| **Warm-Up** - Exercise: 2x10, Exercise: 2x10 | |
| **Main Workout** | |
| - ExerciseName β 3x12 reps (rest 60s) | |
| - ExerciseName β 3x12 reps (rest 60s) | |
| - ExerciseName β 3x12 reps (rest 60s) | |
| - ExerciseName β 3x12 reps (rest 60s) | |
| - ExerciseName β 3x12 reps (rest 60s) | |
| **Cool-Down** - Stretch1, Stretch2 | |
| Rules: vary muscle groups each day, {fitness_level} appropriate, use {eq_list} only, intensity: {intensity}. | |
| {"End with 1 motivational sentence for "+name+"." if end_day == total_days else "No motivational text β more days follow."} | |
| Output ONLY Days {start_day}-{end_day}. No preamble. No extra text. | |
| """ | |
| # Retry up to 3 times on transient errors | |
| for attempt in range(3): | |
| try: | |
| chunk_text = query_model(prompt, max_tokens=1500) | |
| break | |
| except ValueError as e: | |
| # Rate limit β wait and retry | |
| err = str(e) | |
| if "rate limit" in err.lower() and attempt < 2: | |
| wait = 65 # wait just over 1 minute for rate limit window to reset | |
| if progress_callback: | |
| progress_callback(chunk_num, total_chunks, start_day - 1, total_days, | |
| status=f"Rate limit β waiting {wait}sβ¦") | |
| time.sleep(wait) | |
| continue | |
| raise | |
| except Exception as e: | |
| err = str(e) | |
| if attempt < 2: | |
| time.sleep(5 * (attempt + 1)) | |
| continue | |
| raise | |
| chunks.append(chunk_text.strip()) | |
| start_day = end_day + 1 | |
| # Small pause between chunks to respect rate limits on free tier | |
| if start_day <= total_days: | |
| time.sleep(1) | |
| if progress_callback: | |
| progress_callback(total_chunks, total_chunks, total_days, total_days) | |
| full_plan = "\n\n".join(chunks) | |
| return full_plan, bmi, bmi_cat |