Add AI overload retry with model fallback and consolidate wizard errors
Anthropic returns overloaded_error (HTTP 529) during high-demand periods. Previously this failed immediately with a generic error. Now the service retries Sonnet once after 5s, then falls back to Haiku (a separate capacity pool) after another 3s before giving up. If all three attempts are overloaded the user sees a clear "high demand" message rather than a generic error. Non-overload errors still log at Error level. Also consolidated AI wizard error display in item-wizard.js: photo upload failures were using browser alert() while analyze failures used the inline red alert bar. All errors now go through aiShowError() so they always appear consistently as the red bar below the Analyze button. Removed the alert() fallback from aiShowError() itself. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -254,8 +254,44 @@ Only ask follow-up questions if truly needed — prefer to make reasonable assum
|
||||
Messages = messages
|
||||
};
|
||||
|
||||
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(60));
|
||||
var response = await client.Messages.GetClaudeMessageAsync(messageRequest, cts.Token);
|
||||
// On overloaded_error (HTTP 529): retry Sonnet once after a short delay, then
|
||||
// fall back to Haiku (separate capacity pool). If Haiku is also overloaded, give up.
|
||||
// Total worst-case added latency before fallback: ~5s.
|
||||
MessageResponse response;
|
||||
var modelsToTry = new[] { "claude-sonnet-4-6", "claude-sonnet-4-6", "claude-haiku-4-5-20251001" };
|
||||
HttpRequestException? lastOverloadEx = null;
|
||||
response = null!;
|
||||
for (int attempt = 0; attempt < modelsToTry.Length; attempt++)
|
||||
{
|
||||
messageRequest.Model = modelsToTry[attempt];
|
||||
if (attempt > 0)
|
||||
{
|
||||
var delay = attempt == 1 ? TimeSpan.FromSeconds(5) : TimeSpan.FromSeconds(3);
|
||||
_logger.LogWarning("Claude API overloaded on {Model} (attempt {Attempt}); retrying with {NextModel} in {Delay}s",
|
||||
modelsToTry[attempt - 1], attempt, modelsToTry[attempt], delay.TotalSeconds);
|
||||
await Task.Delay(delay);
|
||||
}
|
||||
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(60));
|
||||
try
|
||||
{
|
||||
response = await client.Messages.GetClaudeMessageAsync(messageRequest, cts.Token);
|
||||
lastOverloadEx = null;
|
||||
break;
|
||||
}
|
||||
catch (HttpRequestException hex) when (hex.Message.Contains("overloaded_error"))
|
||||
{
|
||||
lastOverloadEx = hex;
|
||||
}
|
||||
}
|
||||
if (lastOverloadEx != null)
|
||||
{
|
||||
_logger.LogWarning(lastOverloadEx, "Claude API overloaded on all models including fallback");
|
||||
return new AiAnalyzeItemResult
|
||||
{
|
||||
Success = false,
|
||||
ErrorMessage = "The AI service is experiencing high demand right now. Please wait a minute and try again."
|
||||
};
|
||||
}
|
||||
var rawText = response.FirstMessage?.Text
|
||||
?? response.Content.OfType<TextContent>().FirstOrDefault()?.Text
|
||||
?? "";
|
||||
@@ -329,6 +365,15 @@ Only ask follow-up questions if truly needed — prefer to make reasonable assum
|
||||
ErrorMessage = "The AI service did not respond in time. Please try again."
|
||||
};
|
||||
}
|
||||
catch (HttpRequestException hex) when (hex.Message.Contains("overloaded_error"))
|
||||
{
|
||||
_logger.LogWarning(hex, "Claude API overloaded (outer catch — unexpected path)");
|
||||
return new AiAnalyzeItemResult
|
||||
{
|
||||
Success = false,
|
||||
ErrorMessage = "The AI service is experiencing high demand right now. Please wait a minute and try again."
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error calling Claude AI for quote analysis");
|
||||
|
||||
Reference in New Issue
Block a user