AI Workout Assistant in Mobile Applications
Workout recommendations depend on personalization based on body data, goals, available equipment, and training history. Without this context, AI generates a generic plan—"3x10 squats"—that ignores a knee injury, home-only training, and yesterday's leg day.
Gathering Personalization Data
Recommendation quality depends directly on profile completeness. Minimum set:
- Goal: weight loss, muscle gain, endurance, rehabilitation
- Level: beginner, intermediate, advanced
- Available equipment (multi-select: barbell, dumbbells, pull-up bar, TRX, bodyweight only)
- Limitations/injuries: specific areas (lower back, knees, shoulders)
- Available time per session
- Training frequency per week
- Training history: what was done the past 7 days
From native sources, add HealthKit (iOS) or Health Connect (Android 14+) data:
// iOS: load past week workouts from HealthKit
func fetchRecentWorkouts() async throws -> [HKWorkout] {
let workoutType = HKObjectType.workoutType()
let predicate = HKQuery.predicateForSamples(
withStart: Calendar.current.date(byAdding: .day, value: -7, to: Date())!,
end: Date()
)
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: false)
return try await withCheckedThrowingContinuation { continuation in
let query = HKSampleQuery(
sampleType: workoutType,
predicate: predicate,
limit: 20,
sortDescriptors: [sortDescriptor]
) { _, samples, error in
if let error { continuation.resume(throwing: error); return }
continuation.resume(returning: (samples as? [HKWorkout]) ?? [])
}
healthStore.execute(query)
}
}
Workout Plan Generation
Prompt with full context:
func buildWorkoutPrompt(profile: UserProfile, recentWorkouts: [WorkoutSummary]) -> String {
let workoutHistory = recentWorkouts.map {
"\($0.date.formatted()): \($0.type), \($0.duration) min, \($0.muscleGroups.joined(separator: "+"))"
}.joined(separator: "; ")
return """
Create a workout plan for today.
User profile:
- Goal: \(profile.goal)
- Level: \(profile.level)
- Available time: \(profile.availableMinutes) minutes
- Equipment: \(profile.equipment.joined(separator: ", "))
- Limitations: \(profile.limitations.isEmpty ? "none" : profile.limitations.joined(separator: ", "))
Recent workouts (last 7 days): \(workoutHistory.isEmpty ? "none" : workoutHistory)
Rules:
- Avoid muscle groups trained in last 48 hours
- If limitation mentions specific area (knee, back), exclude exercises for that area
- Balance push/pull if goal is hypertrophy
Return JSON: {
name, totalMinutes, exercises: [{
name, sets, reps, restSeconds, muscleGroups: [], notes, videoSearchQuery
}]
}
"""
}
videoSearchQuery is a key field. Use it to generate search queries for YouTube/Vimeo to show exercise demos directly in the card.
Real-Time Adaptation
The assistant shouldn't stay silent after delivering the plan. Three adaptation triggers:
- User taps "Too hard" → LLM replaces exercise with easier alternative
- More time elapsed than planned → suggest shortening remaining sets
- All exercises completed 15 minutes early → add bonus block
// Android - exercise adaptation
suspend fun substituteExercise(
exercise: Exercise,
reason: SubstitutionReason,
availableEquipment: List<String>
): Exercise {
val prompt = """
The user cannot do "${exercise.name}".
Reason: ${reason.description}
Available equipment: ${availableEquipment.joinToString(", ")}
Target muscles: ${exercise.muscleGroups.joinToString(", ")}
Suggest ONE simpler/alternative exercise that targets the same muscles.
Return JSON: {name, sets, reps, restSeconds, muscleGroups: [], notes}
"""
val response = openAIClient.chat(
model = "gpt-4o-mini",
messages = listOf(Message("user", prompt)),
responseFormat = ResponseFormat.JsonObject
)
return json.decodeFromString(response.content)
}
Rest and Recovery
The AI assistant should recommend rest when needed. If HealthKit shows declining HKQuantityType.heartRateVariability or Sleep Analysis < 6 hours, change the plan to stretching or active recovery.
func shouldRecommendRestDay(healthData: HealthSnapshot) -> Bool {
return healthData.sleepHours < 6.0 ||
healthData.restingHeartRate > healthData.averageRestingHR * 1.1 ||
healthData.hrvTrend == .declining
}
This isn't an AI call—simple heuristics on native data. LLM isn't needed.
Timeline Estimates
Basic plan generator without HealthKit—3–5 days. Full assistant with HealthKit/Health Connect, real-time adaptation, rest timers, and workout history—4–6 weeks.







