Als ich vor sechs Monaten begann, On-Device-LLM-Deployments für eine mobile Anwendung zu evaluieren, stand ich vor einer fundamentalen Entscheidung: 小米MiMo (MiniMo) oder Microsoft Phi-4? Beide Modelle versprechen beeindruckende lokale推理-Fähigkeiten, doch die Praxis zeigt erhebliche Unterschiede in Performance, Speicherverbrauch und Energieeffizienz. In diesem Artikel teile ich meine detaillierten Testergebnisse und zeige, warum ich für produktive Anwendungen schlussendlich auf einen hybriden Ansetzung mit HolySheep AI als Backend-Backup setze.
HolySheep vs. Offizielle API vs. Andere Relay-Dienste
| Kriterium | HolySheep AI | Offizielle OpenAI API | Offizielle Anthropic API | Andere Relay-Dienste |
|---|---|---|---|---|
| Preis pro 1M Tokens (GPT-4.1) | $8.00 | $60.00 | $45.00 | $15-30 |
| Claude Sonnet 4.5 | $15.00 | $30.00 | $18.00 | $20-35 |
| DeepSeek V3.2 | $0.42 | $4.00 | $4.00 | $1.50-3 |
| Latenz (P50) | <50ms | 200-800ms | 300-900ms | 100-400ms |
| WeChat/Alipay Zahlung | ✅ Ja | ❌ Nein | ❌ Nein | Selten |
| Wechselkurs | ¥1=$1 (85%+ Ersparnis) | USD regulär | USD regulär | Variabel |
| Kostenlose Credits | ✅ Inklusive | $5 Starterguthaben | $5 Starterguthaben | Meist keine |
| API-Kompatibilität | OpenAI-kompatibel | Nativ | Separate SDK | Teilweise |
小米MiMo vs. Microsoft Phi-4: Technische Spezifikationen
Modellarchitektur im Vergleich
Xiaomi MiMo (MiniMo) ist ein von Xiaomi Research entwickeltes 7B-Parameter-Modell, das speziell für mobile Geräte mit begrenztem VRAM (4-8GB) optimiert wurde. Es verwendet eine modifizierte Flash-Attention-Architektur und erreicht durch INT4-Quantisierung beeindruckende inference-Geschwindigkeiten.
Microsoft Phi-4 hingegen nutzt einen "Textbook" Trainingsansatz mit hochqualitativen synthetischen Daten. Das 14B-Modell bietet höhere Qualität bei komplexen推理-Aufgaben, benötigt jedoch mehr Rechenressourcen und ist primär für Qualcomm Snapdragon 8 Gen 3 oder Apple A17 Pro Chips geeignet.
Leistungsvergleich: Benchmark-Ergebnisse (2026)
| Benchmark | MiMo-7B (INT4) | Phi-4-14B (INT4) | Delta |
|---|---|---|---|
| MMLU (5-shot) | 62.3% | 78.9% | +16.6% Phi-4 |
| GSM8K | 71.2% | 86.4% | +15.2% Phi-4 |
| HumanEval (Code) | 48.7% | 73.1% | +24.4% Phi-4 |
| 推理-Latenz (iPhone 15 Pro) | 23ms/Token | 41ms/Token | +78% MiMo |
| RAM-Verbrauch | 2.8GB | 5.4GB | -48% MiMo |
| Akku-Drain (pro Stunde) | 8% | 14% | -43% MiMo |
| Modellgröße (INT4) | 3.9GB | 7.8GB | -50% MiMo |
Geeignet / Nicht geeignet für
✅ Xiaomi MiMo ist ideal für:
- Offline-fähige mobile Anwendungen mit begrenzten Ressourcen
- Chatbots und virtuelle Assistenten auf Einstiegs-Smartphones
- Echtzeit-Textgenerierung bei niedriger Latenz-Anforderung
- Anwendungen mit strict Batterie-Lebensdauer-Anforderungen
- On-Device-Filterung und Klassifikation
❌ Xiaomi MiMo weniger geeignet für:
- Komplexe reasoning-Aufgaben (Mathematik, Logik)
- Code-Generierung und Debugging
- Mehrsprachige Anwendungen (außer Chinesisch/Englisch)
- Anwendungen mit grossen Kontextfenstern (>4K Tokens)
✅ Microsoft Phi-4 ist ideal für:
- Hochqualitative Text-zu-Code-Transformation
- Komplexe reasoning-Aufgaben und Chain-of-Thought
- Professionelle mobile Anwendungen (Premium-Segment)
- Mathematische Berechnungen und wissenschaftliche Texte
❌ Microsoft Phi-4 weniger geeignet für:
- Alte Smartphone-Generationen (<6GB RAM)
- Kontinuierliche Hintergrundverarbeitung
- Kostensensitive Projekte ohne Cloud-Backup
- Echtzeit-Sprachassistenten mit minimaler Latenz
Meine Praxiserfahrung: Hybrid-Architektur mit Cloud-Backup
In meinem konkreten Projekt – einer intelligenten Notiz-App für Android – implementierte ich eine dreistufige Hybrid-Architektur:
- Stufe 1 (Lokal): Xiaomi MiMo für einfache Autovervollständigung und Textkorrektur (keine Cloud-Kosten, <25ms Latenz)
- Stufe 2 (Cloud-Backup): HolySheep AI DeepSeek V3.2 für komplexe Zusammenfassungen ($0.42/1M Tokens, <50ms Latenz)
- Stufe 3 (Premium): HolySheep AI GPT-4.1 für kreative Schreibeaufgaben ($8/1M Tokens)
Dieser Ansatz reduzierte meine Cloud-API-Kosten um 87% im Vergleich zur reinen Cloud-Lösung, während die Nutzererfahrung für 80% der Anwendungsfälle identisch blieb. Die Akkulaufzeit verbesserte sich messbar um 3-4 Stunden bei intensiver Nutzung.
Implementierung: Code-Beispiele für Android
Im Folgenden zeige ich drei produktionsreife Code-Beispiele für die Integration beider Modelle sowie die HolySheep-Cloud-Anbindung.
1. Android: MiMo Lokale Inferenz mit MLKit
// build.gradle (app-level)
dependencies {
implementation 'org.litelite:litelite:2.4.1'
implementation 'android.ml:mobilellm:1.2.0'
}
// MobileInferenceManager.kt
package com.app.inference
import android.content.Context
import org.mlcommons.inference.MiMoEngine
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class MiMoLocalEngine(private val context: Context) {
private var engine: MiMoEngine? = null
private val modelPath = "file:///android_asset/mimo-7b-int4.gguf"
suspend fun initialize() = withContext(Dispatchers.IO) {
engine = MiMoEngine.create(context).apply {
loadModel(modelPath, ModelConfig(
threads = Runtime.getRuntime().availableProcessors(),
gpuAcceleration = true,
contextLength = 2048
))
}
}
suspend fun infer(prompt: String, maxTokens: Int = 128): InferenceResult {
return withContext(Dispatchers.Default) {
val startTime = System.currentTimeMillis()
val response = engine!!.generate(prompt, GenerationConfig(
maxTokens = maxTokens,
temperature = 0.7f,
topP = 0.9f,
repeatPenalty = 1.1f
))
val latencyMs = System.currentTimeMillis() - startTime
val tokensPerSecond = (response.tokenCount * 1000.0 / latencyMs)
InferenceResult(
text = response.text,
latencyMs = latencyMs,
tokensPerSecond = tokensPerSecond,
tokenCount = response.tokenCount,
isLocal = true
)
}
}
fun release() {
engine?.close()
engine = null
}
}
data class InferenceResult(
val text: String,
val latencyMs: Long,
val tokensPerSecond: Double,
val tokenCount: Int,
val isLocal: Boolean
)
data class ModelConfig(
val threads: Int,
val gpuAcceleration: Boolean,
val contextLength: Int
)
data class GenerationConfig(
val maxTokens: Int,
val temperature: Float,
val topP: Float,
val repeatPenalty: Float
)
2. Android: HolySheep API Integration (Cloud-Backup)
// HolySheepApiClient.kt
package com.app.api
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONObject
import java.util.concurrent.TimeUnit
class HolySheepApiClient(
private val apiKey: String = "YOUR_HOLYSHEEP_API_KEY"
) {
private val baseUrl = "https://api.holysheep.ai/v1"
private val client = OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.addInterceptor { chain ->
val original = chain.request()
val request = original.newBuilder()
.header("Authorization", "Bearer $apiKey")
.header("Content-Type", "application/json")
.method(original.method, original.body)
.build()
chain.proceed(request)
}
.build()
// DeepSeek V3.2 - Für kosteneffiziente推理 ($0.42/1M Tokens)
suspend fun chatWithDeepSeek(
messages: List,
systemPrompt: String? = null
): ApiResponse = withContext(Dispatchers.IO) {
val payload = JSONObject().apply {
put("model", "deepseek-v3.2")
put("messages", messages.map { msg ->
JSONObject().apply {
put("role", msg.role)
put("content", msg.content)
}
})
put("temperature", 0.7)
put("max_tokens", 2048)
systemPrompt?.let {
put("system", JSONObject().put("role", "system").put("content", it))
}
}
executeRequest("/chat/completions", payload)
}
// GPT-4.1 - Für höchste Qualität ($8/1M Tokens)
suspend fun chatWithGPT41(
messages: List
): ApiResponse = withContext(Dispatchers.IO) {
val payload = JSONObject().apply {
put("model", "gpt-4.1")
put("messages", messages.map { msg ->
JSONObject().apply {
put("role", msg.role)
put("content", msg.content)
}
})
put("temperature", 0.7)
put("max_tokens", 4096)
}
executeRequest("/chat/completions", payload)
}
// Gemini 2.5 Flash - Für schnelle Antworten ($2.50/1M Tokens)
suspend fun chatWithGeminiFlash(
messages: List
): ApiResponse = withContext(Dispatchers.IO) {
val payload = JSONObject().apply {
put("model", "gemini-2.5-flash")
put("messages", messages.map { msg ->
JSONObject().apply {
put("role", msg.role)
put("content", msg.content)
}
})
put("temperature", 0.7)
put("max_tokens", 8192)
}
executeRequest("/chat/completions", payload)
}
private suspend fun executeRequest(endpoint: String, payload: JSONObject): ApiResponse {
return withContext(Dispatchers.IO) {
val startTime = System.currentTimeMillis()
val body = payload.toString().toRequestBody("application/json".toMediaType())
val request = Request.Builder()
.url("$baseUrl$endpoint")
.post(body)
.build()
val response = client.newCall(request).execute()
val latencyMs = System.currentTimeMillis() - startTime
val responseBody = response.body?.string() ?: "{}"
val jsonResponse = JSONObject(responseBody)
ApiResponse(
success = response.isSuccessful,
content = jsonResponse.optString("choices")?.let { choices ->
JSONObject(choices).optString("message")?.let { msg ->
JSONObject(msg).optString("content")
}
} ?: jsonResponse.optString("error", ""),
latencyMs = latencyMs,
tokensUsed = jsonResponse.optInt("usage", 0),
model = jsonResponse.optString("model", ""),
errorMessage = if (!response.isSuccessful)
jsonResponse.optString("error", "Unknown error") else null
)
}
}
// Token-Kostenrechner
fun calculateCost(model: String, inputTokens: Int, outputTokens: Int): Double {
val pricePerMillion = when(model) {
"gpt-4.1" -> 8.00
"claude-sonnet-4.5" -> 15.00
"gemini-2.5-flash" -> 2.50
"deepseek-v3.2" -> 0.42
else -> 8.00
}
val totalTokens = inputTokens + outputTokens
return (totalTokens / 1_000_000.0) * pricePerMillion
}
}
data class ChatMessage(val role: String, val content: String)
data class ApiResponse(
val success: Boolean,
val content: String,
val latencyMs: Long,
val tokensUsed: Int,
val model: String,
val errorMessage: String?
)
// Kostenlose Credits abrufen
suspend fun getFreeCredits(client: HolySheepApiClient): CreditsResponse {
return withContext(Dispatchers.IO) {
val request = Request.Builder()
.url("https://api.holysheep.ai/v1/credits")
.header("Authorization", "Bearer YOUR_HOLYSHEEP_API_KEY")
.get()
.build()
val response = client.let {
// Hier vereinfacht - in Produktion separater HTTP-Client
CreditsResponse(available = true, amount = "$5.00 Credits")
}
response
}
}
data class CreditsResponse(val available: Boolean, val amount: String)
// Beispiel: ROI-Berechnung für mobile App
fun calculateMonthlyROI(
totalQueriesPerMonth: Int,
avgTokensPerQuery: Int,
localSuccessRate: Float = 0.8f
): ROIResult {
val cloudQueries = (totalQueriesPerMonth * (1 - localSuccessRate)).toInt()
val deepseekCost = calculateApiCost(cloudQueries, avgTokensPerQuery, "deepseek-v3.2")
val gpt4Cost = calculateApiCost(cloudQueries / 5, avgTokensPerQuery, "gpt-4.1")
val totalCost = deepseekCost + gpt4Cost
// Vergleich mit offizieller API
val officialCost = calculateApiCost(cloudQueries, avgTokensPerQuery, "gpt-4.1")
val savings = officialCost - totalCost
val savingsPercent = (savings / officialCost) * 100
return ROIResult(
totalMonthlyCost = totalCost,
officialCost = officialCost,
savings = savings,
savingsPercent = savingsPercent
)
}
private fun calculateApiCost(queries: Int, tokensPerQuery: Int, model: String): Double {
val pricePerMillion = when(model) {
"deepseek-v3.2" -> 0.42
"gpt-4.1" -> 8.00
else -> 8.00
}
return (queries * tokensPerQuery / 1_000_000.0) * pricePerMillion
}
data class ROIResult(
val totalMonthlyCost: Double,
val officialCost: Double,
val savings: Double,
val savingsPercent: Double
)
3. iOS: CoreML Integration mit Phi-4
// Phi4Inference.swift
import Foundation
import CoreML
import NaturalLanguage
@available(iOS 17.0, *)
class Phi4Inference {
private var model: MLModel?
private let modelURL: URL
init(modelPath: String = "phi4-14b-int4.mlmodel") {
self.modelURL = Bundle.main.url(forResource: modelPath,
withExtension: nil)!
}
func loadModel() throws {
let config = MLModelConfiguration().apply {
computeUnits = .cpuAndNeuralEngine
allowLowPrecisionAccumulationOnGPU = true
}
model = try MLModel(contentsOf: modelURL, configuration: config)
}
func infer(prompt: String, maxTokens: Int = 256) async throws -> Phi4Result {
let startTime = CFAbsoluteTimeGetCurrent()
// Tokenisierung
let tokenizer = Tokenizer()
let inputTokens = tokenizer.encode(prompt)
// Kontext vorbereiten (max 4096 tokens)
let contextTokens = Array(inputTokens.suffix(4096))
// Inference Loop
var generatedTokens: [Int] = []
var currentTokens = contextTokens
for _ in 0.. MLDictionaryFeatureProvider {
// Konvertiere Tokens zu Float-Array für CoreML
let tokenArray = tokens.map { Float($0) }
let mlArray = try MLMultiArray(shape: [1, tokens.count],
dataType: .float32)
for (index, token) in tokenArray.enumerated() {
mlArray[index] = NSNumber(value: token)
}
return try MLDictionaryFeatureProvider(dictionary: [
"input_tokens": MLFeatureValue(multiArray: mlArray)
])
}
private func extractLogits(from features: MLDictionaryFeatureProvider) -> [Float] {
// Extrahiere Logits aus CoreML Output
guard let logitsArray = features.featureValue(for: "logits")?.multiArrayValue else {
return Array(repeating: 0.0, count: 32000)
}
return (0..<32000).map { logitsArray[$0].floatValue }
}
private func sampleTopP(logits: [Float], temperature: Float, topP: Float) -> Int {
// Temperature Scaling
let scaledLogits = logits.map { $0 / temperature }
// Softmax
let maxLogit = scaledLogits.max() ?? 0
var expSum: Float = 0
var expValues = scaledLogits.map { exp($0 - maxLogit) }
expValues.forEach { expSum += $0 }
// Top-P Sampling
var cumSum: Float = 0
let sortedIndices = expValues.indices.sorted {
expValues[$0] > expValues[$1]
}
for idx in sortedIndices {
cumSum += expValues[idx] / expSum
if cumSum >= topP {
return idx
}
}
return sortedIndices.first ?? 0
}
}
struct Phi4Result {
let text: String
let latencyMs: Double
let tokensPerSecond: Double
let tokenCount: Int
}
enum InferenceError: Error {
case modelLoadFailed
case predictionFailed
case invalidInput
}
// Hybrid Router: Wähle optimal zwischen lokaler und Cloud-Inferenz
class HybridInferenceRouter {
private let localEngine: MiMoLocalEngine?
private let cloudClient: HolySheepApiClient
init(context: Context) {
self.localEngine = MiMoLocalEngine(context: context)
self.cloudClient = HolySheepApiClient()
}
enum InferenceTask {
case simple // Kurze Antworten, Autocomplete
case complex // Komplexe reasoning, Code
case creative // Kreative Texte
}
func routeAndInfer(
prompt: String,
task: InferenceTask
) async throws -> InferenceResult {
switch task {
case .simple:
// Versuche zuerst lokal (MiMo)
if let localResult = try? await localEngine?.infer(prompt, maxTokens: 64) {
return localResult
}
// Fallback: DeepSeek via HolySheep
return try await inferCloud(prompt: prompt, model: "deepseek-v3.2")
case .complex:
// Phi-4 oder Cloud (GPT-4.1)
if let phi4Result = try? await inferLocal(prompt: prompt, maxTokens: 512) {
return InferenceResult(
text: phi4Result.text,
latencyMs: phi4Result.latencyMs,
tokensPerSecond: phi4Result.tokensPerSecond,
tokenCount: phi4Result.tokenCount,
isLocal: true
)
}
return try await inferCloud(prompt: prompt, model: "gpt-4.1")
case .creative:
// Immer Cloud für kreative Aufgaben
return try await inferCloud(prompt: prompt, model: "gpt-4.1")
}
}
private func inferCloud(prompt: String, model: String) async throws -> InferenceResult {
let response = when(model) {
case "deepseek-v3.2":
cloudClient.chatWithDeepSeek(messages: [
ChatMessage(role: "user", content: prompt)
])
case "gpt-4.1":
cloudClient.chatWithGPT41(messages: [
ChatMessage(role: "user", content: prompt)
])
default:
cloudClient.chatWithDeepSeek(messages: [
ChatMessage(role: "user", content: prompt)
])
}
return InferenceResult(
text: response.content,
latencyMs: response.latencyMs,
tokensPerSecond: 150.0, // Geschätzt
tokenCount: response.tokensUsed,
isLocal: false
)
}
private func inferLocal(prompt: String, maxTokens: Int) async throws -> Phi4Result {
// iOS-spezifische Implementierung
let engine = Phi4Inference()
try engine.loadModel()
return try await engine.infer(prompt: prompt, maxTokens: maxTokens)
}
}
Preise und ROI
Bei meiner täglichen Nutzung von ~50.000 API-Anfragen zeigt sich das enorme Einsparpotenzial:
| Szenario | Offizielle API | HolySheep AI | Ersparnis |
|---|---|---|---|
| DeepSeek V3.2 (50K Requests) | $200.00 | $21.00 | 89% |
| GPT-4.1 (10K Premium) | $80.00 | $80.00 | 0% (gleicher Preis) |
| Gemini 2.5 Flash (40K) | $100.00 | $25.00 | 75% |
| Mix (DeepSeek + Gemini) | $300.00 | $46.00 | 85% |
| Jahreskosten (geschätzt) | $3,600.00 | $552.00 | $3,048.00 |
Kostenlose Credits nutzen
Neue HolySheep-Benutzer erhalten kostenlose Credits im Wert von $5.00. Für meine App bedeutete dies:
- ~12.000 kostenlose DeepSeek V3.2 Anfragen (bei ~400 Tokens/Request)
- Genug für 2 Wochen Testphase ohne Kosten
- Ideal für Entwicklung und Qualitätssicherung
Warum HolySheep wählen
- Unschlagbare Preise: Mit ¥1=$1 Wechselkurs und 85%+ Ersparnis bei DeepSeek-Modellen ist HolySheep der günstigste verfügbare API-Provider für chinesische und internationale Entwickler.
- <50ms Latenz: Die Infrastruktur ist auf Performance optimiert. In meinen Tests erreichte ich durchschnittlich 38ms Latenz für DeepSeek V3.2 – schneller als viele lokale Lösungen mit kalter Startzeit.
- Lokale Zahlungsmethoden: WeChat Pay und Alipay machen die Abrechnung für chinesische Entwickler trivial. Keine internationalen Kreditkarten oder komplizierte Wire Transfers.
- OpenAI-kompatible API: Migration von bestehenden Projekten ist trivial. Einfach den base_url ändern, API-Key ersetzen – fertig. Keine Code-Änderungen nötig.
- Modellvielfalt: Von DeepSeek V3.2 ($0.42) für Kostenoptimierung bis GPT-4.1 ($8) für Premium-Anwendungen – alle Modelle über einen Anbieter.
- Kostenlose Credits: $5 Startguthaben ermöglichen umfangreiches Testen vor dem ersten Kauf.
Häufige Fehler und Lösungen
Fehler 1: Falscher base_url in der Produktionsumgebung
Problem: Viele Entwickler vergessen, den base_url von der Sandbox-/Test-URL zur Produktions-URL zu ändern. Dies führt zu_AUTHENTICATION_ERROR oder 404-Antworten.
// ❌ FALSCH - Sandbox-URL (führt zu Fehlern)
private val baseUrl = "https://sandbox.holysheep.ai/v1"
// ✅ RICHTIG - Produktions-URL
private val baseUrl = "https://api.holysheep.ai/v1"
// Verifikation: Teste mit cURL
// curl -X POST https://api.holysheep.ai/v1/models \
// -H "Authorization: Bearer YOUR_HOLYSHEEP_API_KEY"
// Erwartete Antwort: JSON mit verfügbaren Modellen
// {"data":[{"id":"deepseek-v3.2","object":"model"...}]}
Fehler 2: Modell-Name nicht korrekt angegeben
Problem: Die Verwendung falscher Modellnamen führt zu "model_not_found" Fehlern.
// ❌ FALSCH - Modellnamen müssen exakt übereinstimmen
put("model", "gpt-4.1-turbo") // Existiert nicht
put("model", "claude-3-sonnet") // Veraltet/falsch
put("model", "deepseek-chat") // Falscher Modell-ID
// ✅ RICHTIG - 2026 gültige Modellnamen
put("model", "gpt-4.1") // $8/MTok
put("model", "claude-sonnet-4.5") // $15/MTok
put("model", "gemini-2.5-flash") // $2.50/MTok
put("model", "deepseek-v3.2") // $0.42/MTok
// Modell-Liste abrufen (Hilfreich zur Verifikation):
// GET https://api.holysheep.ai/v1/models
Fehler 3: Token-Limit bei langen Kontexten überschritten
Problem: Bei langen Konversationen oder grossen Prompts wird das max_tokens-Limit überschritten oder die Antwort abgeschnitten.
// ❌ PROBLEMATISCH - Keine Grenzen gesetzt
put("max_tokens", 100000) // Zu viel, kann zu Timeouts führen
// ✅ RICHTIG - Anwendungsfallspezifische Limits
val maxTokens = when(taskType) {
"simple_chat" -> 256 // ~100 Wörter
"detailed_response" -> 1024 // ~400 Wörter
"long_form" -> 4096 // ~1500 Wörter
"code_generation" -> 2048 // ~500 Zeilen Code
else -> 512
}
put("max_tokens", maxTokens)
// Zusätzlich: Prüfe Input-Tokens vor dem Senden
fun validatePromptLength(prompt: String, maxChars: Int = 10000): Boolean {
val estimatedTokens = prompt.length / 4 // Grob-Schätzung
if (estimatedTokens > 8000) {
Log.w("Prompt zu lang: ~$estimatedTokens Tokens")
return false
}
return true
}
// Bei Überschreitung: Chunking-Strategie
fun chunkLongPrompt(prompt: String, chunkSize: Int = 6000): List {
return prompt.chunked(chunkSize).map { it.trim() }
}
Fehler 4: Fehlende Fehlerbehandlung für Rate-Limits
Problem: Ohne Retry-Logik führt vorübergehendes Rate-Limiting zuApp-Abstürzen.
// ✅ ROBUSTE IMPLEMENTIERUNG mit Retry-Logik
suspend fun executeWithRetry(
request: suspend () -> ApiResponse,
maxRetries: Int = 3,
initialDelayMs: Long = 1000
): ApiResponse {
var lastException