Spaces:
Sleeping
Sleeping
File size: 6,442 Bytes
d061432 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 | 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 |