Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ enum class ModelOption(
) {
PUTER_GLM5("GLM-5 (Puter)", "z-ai/glm-5", ApiProvider.PUTER, supportsScreenshot = false),
MISTRAL_LARGE_3("Mistral Large 3", "mistral-large-latest", ApiProvider.MISTRAL),
MISTRAL_MEDIUM_3_1("Mistral Medium 3.1", "mistral-medium-latest", ApiProvider.MISTRAL),
GPT_5_1_CODEX_MAX("GPT-5.1 Codex Max (Vercel)", "openai/gpt-5.1-codex-max", ApiProvider.VERCEL),
GPT_5_1_CODEX_MINI("GPT-5.1 Codex Mini (Vercel)", "openai/gpt-5.1-codex-mini", ApiProvider.VERCEL),
GPT_5_NANO("GPT-5 Nano (Vercel)", "openai/gpt-5-nano", ApiProvider.VERCEL),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,20 @@ internal suspend fun callMistralApi(
.build()

val keysForCoordinator = availableApiKeys.filter { it.isNotBlank() }.distinct().ifEmpty { listOf(apiKey) }
val coordinated = MistralRequestCoordinator.execute(apiKeys = keysForCoordinator, maxAttempts = maxOf(4, keysForCoordinator.size * 3)) { key ->
val minIntervalMs = if (modelName == com.google.ai.sample.ModelOption.MISTRAL_MEDIUM_3_1.modelName) 420L else 1500L
val maxAttempts = if (
modelName == com.google.ai.sample.ModelOption.MISTRAL_LARGE_3.modelName ||
modelName == com.google.ai.sample.ModelOption.MISTRAL_MEDIUM_3_1.modelName
) {
3
} else {
maxOf(4, keysForCoordinator.size * 3)
}
val coordinated = MistralRequestCoordinator.execute(
apiKeys = keysForCoordinator,
maxAttempts = maxAttempts,
minIntervalMs = minIntervalMs
) { key ->
client.newCall(
request.newBuilder()
.header("Authorization", "Bearer $key")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1142,10 +1142,19 @@ class PhotoReasoningViewModel(

// Validate that we have at least one key before proceeding
require(availableKeys.isNotEmpty()) { "No valid Mistral API keys available after filtering" }
val maxAttempts = availableKeys.size * 4 + 8
val mistralMinIntervalMs = when (currentModel) {
ModelOption.MISTRAL_MEDIUM_3_1 -> 420L
else -> 1500L
}
val maxAttempts = when (currentModel) {
ModelOption.MISTRAL_LARGE_3,
ModelOption.MISTRAL_MEDIUM_3_1 -> 3
else -> availableKeys.size * 4 + 8
}
val coordinated = MistralRequestCoordinator.execute(
apiKeys = availableKeys,
maxAttempts = maxAttempts
maxAttempts = maxAttempts,
minIntervalMs = mistralMinIntervalMs
) { selectedKey ->
if (stopExecutionFlag.get()) {
throw IOException("Mistral request aborted.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ internal object MistralRequestCoordinator {
private suspend fun markKeyCooldown(
key: String,
referenceTimeMs: Long,
minIntervalMs: Long,
extraDelayMs: Long = 0L
) {
val nextAllowedAt = referenceTimeMs + max(MIN_INTERVAL_MS, extraDelayMs.coerceAtLeast(0L))
val nextAllowedAt = referenceTimeMs + max(minIntervalMs.coerceAtLeast(0L), extraDelayMs.coerceAtLeast(0L))
cooldownMutex.withLock {
val existing = nextAllowedRequestAtMsByKey[key] ?: 0L
nextAllowedRequestAtMsByKey[key] = max(existing, nextAllowedAt)
Expand Down Expand Up @@ -77,6 +78,7 @@ internal object MistralRequestCoordinator {
suspend fun execute(
apiKeys: List<String>,
maxAttempts: Int = apiKeys.size * 4 + 8,
minIntervalMs: Long = MIN_INTERVAL_MS,
request: suspend (apiKey: String) -> Response
): MistralCoordinatedResponse {
require(apiKeys.isNotEmpty()) { "No Mistral API keys provided." }
Expand Down Expand Up @@ -120,7 +122,7 @@ internal object MistralRequestCoordinator {
TAG,
"[$rid] response code=${response.code}, retryAfterMs=${retryAfterMs ?: -1}, resetDelayMs=${resetDelayMs ?: -1}, appliedDelayMs=$serverRequestedDelayMs"
)
markKeyCooldown(selectedKey, requestEndMs, serverRequestedDelayMs)
markKeyCooldown(selectedKey, requestEndMs, minIntervalMs, serverRequestedDelayMs)

if (response.isSuccessful || !isRetryableFailure(response.code)) {
Log.d(TAG, "[$rid] returning response code=${response.code} with key=${keyFingerprint(selectedKey)}")
Expand All @@ -135,7 +137,7 @@ internal object MistralRequestCoordinator {
TAG,
"[$rid] retryable failure code=${response.code}, consecutiveFailures=$consecutiveFailures, adaptiveDelay=$adaptiveDelay"
)
markKeyCooldown(selectedKey, requestEndMs, max(serverRequestedDelayMs, adaptiveDelay))
markKeyCooldown(selectedKey, requestEndMs, minIntervalMs, max(serverRequestedDelayMs, adaptiveDelay))
} catch (e: Exception) {
val requestEndMs = System.currentTimeMillis()
blockedKeysThisRound.add(selectedKey)
Expand All @@ -145,7 +147,7 @@ internal object MistralRequestCoordinator {
"[$rid] exception on key=${keyFingerprint(selectedKey)}, consecutiveFailures=$consecutiveFailures: ${e.message}",
e
)
markKeyCooldown(selectedKey, requestEndMs, adaptiveRetryDelayMs(consecutiveFailures))
markKeyCooldown(selectedKey, requestEndMs, minIntervalMs, adaptiveRetryDelayMs(consecutiveFailures))
if (consecutiveFailures >= maxAttempts) throw e
}
}
Expand Down
Loading