Skip to content

Commit 29a6212

Browse files
Add Mistral Medium 3.1 and reduce Mistral retries
1 parent 90ee92d commit 29a6212

4 files changed

Lines changed: 32 additions & 7 deletions

File tree

app/src/main/kotlin/com/google/ai/sample/GenerativeAiViewModelFactory.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ enum class ModelOption(
3131
) {
3232
PUTER_GLM5("GLM-5 (Puter)", "z-ai/glm-5", ApiProvider.PUTER, supportsScreenshot = false),
3333
MISTRAL_LARGE_3("Mistral Large 3", "mistral-large-latest", ApiProvider.MISTRAL),
34+
MISTRAL_MEDIUM_3_1("Mistral Medium 3.1", "mistral-medium-latest", ApiProvider.MISTRAL),
3435
GPT_5_1_CODEX_MAX("GPT-5.1 Codex Max (Vercel)", "openai/gpt-5.1-codex-max", ApiProvider.VERCEL),
3536
GPT_5_1_CODEX_MINI("GPT-5.1 Codex Mini (Vercel)", "openai/gpt-5.1-codex-mini", ApiProvider.VERCEL),
3637
GPT_5_NANO("GPT-5 Nano (Vercel)", "openai/gpt-5-nano", ApiProvider.VERCEL),

app/src/main/kotlin/com/google/ai/sample/ScreenCaptureApiClients.kt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,20 @@ internal suspend fun callMistralApi(
134134
.build()
135135

136136
val keysForCoordinator = availableApiKeys.filter { it.isNotBlank() }.distinct().ifEmpty { listOf(apiKey) }
137-
val coordinated = MistralRequestCoordinator.execute(apiKeys = keysForCoordinator, maxAttempts = maxOf(4, keysForCoordinator.size * 3)) { key ->
137+
val minIntervalMs = if (modelName == com.google.ai.sample.ModelOption.MISTRAL_MEDIUM_3_1.modelName) 420L else 1500L
138+
val maxAttempts = if (
139+
modelName == com.google.ai.sample.ModelOption.MISTRAL_LARGE_3.modelName ||
140+
modelName == com.google.ai.sample.ModelOption.MISTRAL_MEDIUM_3_1.modelName
141+
) {
142+
3
143+
} else {
144+
maxOf(4, keysForCoordinator.size * 3)
145+
}
146+
val coordinated = MistralRequestCoordinator.execute(
147+
apiKeys = keysForCoordinator,
148+
maxAttempts = maxAttempts,
149+
minIntervalMs = minIntervalMs
150+
) { key ->
138151
client.newCall(
139152
request.newBuilder()
140153
.header("Authorization", "Bearer $key")

app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningViewModel.kt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1142,10 +1142,19 @@ class PhotoReasoningViewModel(
11421142

11431143
// Validate that we have at least one key before proceeding
11441144
require(availableKeys.isNotEmpty()) { "No valid Mistral API keys available after filtering" }
1145-
val maxAttempts = availableKeys.size * 4 + 8
1145+
val mistralMinIntervalMs = when (currentModel) {
1146+
ModelOption.MISTRAL_MEDIUM_3_1 -> 420L
1147+
else -> 1500L
1148+
}
1149+
val maxAttempts = when (currentModel) {
1150+
ModelOption.MISTRAL_LARGE_3,
1151+
ModelOption.MISTRAL_MEDIUM_3_1 -> 3
1152+
else -> availableKeys.size * 4 + 8
1153+
}
11461154
val coordinated = MistralRequestCoordinator.execute(
11471155
apiKeys = availableKeys,
1148-
maxAttempts = maxAttempts
1156+
maxAttempts = maxAttempts,
1157+
minIntervalMs = mistralMinIntervalMs
11491158
) { selectedKey ->
11501159
if (stopExecutionFlag.get()) {
11511160
throw IOException("Mistral request aborted.")

app/src/main/kotlin/com/google/ai/sample/network/MistralRequestCoordinator.kt

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,10 @@ internal object MistralRequestCoordinator {
3030
private suspend fun markKeyCooldown(
3131
key: String,
3232
referenceTimeMs: Long,
33+
minIntervalMs: Long,
3334
extraDelayMs: Long = 0L
3435
) {
35-
val nextAllowedAt = referenceTimeMs + max(MIN_INTERVAL_MS, extraDelayMs.coerceAtLeast(0L))
36+
val nextAllowedAt = referenceTimeMs + max(minIntervalMs.coerceAtLeast(0L), extraDelayMs.coerceAtLeast(0L))
3637
cooldownMutex.withLock {
3738
val existing = nextAllowedRequestAtMsByKey[key] ?: 0L
3839
nextAllowedRequestAtMsByKey[key] = max(existing, nextAllowedAt)
@@ -77,6 +78,7 @@ internal object MistralRequestCoordinator {
7778
suspend fun execute(
7879
apiKeys: List<String>,
7980
maxAttempts: Int = apiKeys.size * 4 + 8,
81+
minIntervalMs: Long = MIN_INTERVAL_MS,
8082
request: suspend (apiKey: String) -> Response
8183
): MistralCoordinatedResponse {
8284
require(apiKeys.isNotEmpty()) { "No Mistral API keys provided." }
@@ -120,7 +122,7 @@ internal object MistralRequestCoordinator {
120122
TAG,
121123
"[$rid] response code=${response.code}, retryAfterMs=${retryAfterMs ?: -1}, resetDelayMs=${resetDelayMs ?: -1}, appliedDelayMs=$serverRequestedDelayMs"
122124
)
123-
markKeyCooldown(selectedKey, requestEndMs, serverRequestedDelayMs)
125+
markKeyCooldown(selectedKey, requestEndMs, minIntervalMs, serverRequestedDelayMs)
124126

125127
if (response.isSuccessful || !isRetryableFailure(response.code)) {
126128
Log.d(TAG, "[$rid] returning response code=${response.code} with key=${keyFingerprint(selectedKey)}")
@@ -135,7 +137,7 @@ internal object MistralRequestCoordinator {
135137
TAG,
136138
"[$rid] retryable failure code=${response.code}, consecutiveFailures=$consecutiveFailures, adaptiveDelay=$adaptiveDelay"
137139
)
138-
markKeyCooldown(selectedKey, requestEndMs, max(serverRequestedDelayMs, adaptiveDelay))
140+
markKeyCooldown(selectedKey, requestEndMs, minIntervalMs, max(serverRequestedDelayMs, adaptiveDelay))
139141
} catch (e: Exception) {
140142
val requestEndMs = System.currentTimeMillis()
141143
blockedKeysThisRound.add(selectedKey)
@@ -145,7 +147,7 @@ internal object MistralRequestCoordinator {
145147
"[$rid] exception on key=${keyFingerprint(selectedKey)}, consecutiveFailures=$consecutiveFailures: ${e.message}",
146148
e
147149
)
148-
markKeyCooldown(selectedKey, requestEndMs, adaptiveRetryDelayMs(consecutiveFailures))
150+
markKeyCooldown(selectedKey, requestEndMs, minIntervalMs, adaptiveRetryDelayMs(consecutiveFailures))
149151
if (consecutiveFailures >= maxAttempts) throw e
150152
}
151153
}

0 commit comments

Comments
 (0)