* @brief Records a network or stream error and ends the turn. Transient failures (429, * 5xx, timeouts) retry with backoff when nothing has streamed yet, instead of * throwing away the whole turn. */
| 976 | * throwing away the whole turn. |
| 977 | */ |
| 978 | void AI::Conversation::onReplyError(const QString& message) |
| 979 | { |
| 980 | qCWarning(serialStudioAI) << "Reply error:" << message; |
| 981 | |
| 982 | if (shouldRetryAfterError()) { |
| 983 | scheduleTransientRetry(message); |
| 984 | return; |
| 985 | } |
| 986 | |
| 987 | setLastError(message); |
| 988 | Q_EMIT errorOccurred(message); |
| 989 | |
| 990 | m_streamFlushTimer->stop(); |
| 991 | if (m_streamDirty) |
| 992 | flushPendingStreamUpdate(); |
| 993 | |
| 994 | if (m_assistantIndex >= 0 && m_assistantIndex < m_uiMessages.size()) { |
| 995 | auto map = m_uiMessages.at(m_assistantIndex).toMap(); |
| 996 | map.insert(QStringLiteral("streaming"), false); |
| 997 | m_uiMessages[m_assistantIndex] = map; |
| 998 | } |
| 999 | |
| 1000 | QVariantMap errorRow; |
| 1001 | errorRow[QStringLiteral("role")] = QStringLiteral("error"); |
| 1002 | errorRow[QStringLiteral("text")] = message; |
| 1003 | errorRow[QStringLiteral("toolCalls")] = QVariantList(); |
| 1004 | m_uiMessages.append(errorRow); |
| 1005 | Q_EMIT messagesChanged(); |
| 1006 | |
| 1007 | if (!m_awaitingConfirm.isEmpty()) { |
| 1008 | for (auto it = m_awaitingConfirm.constBegin(); it != m_awaitingConfirm.constEnd(); ++it) |
| 1009 | updateToolCallCard(it.key(), CallStatus::Error); |
| 1010 | |
| 1011 | m_awaitingConfirm.clear(); |
| 1012 | setAwaitingConfirmation(false); |
| 1013 | } |
| 1014 | |
| 1015 | m_assistantText.clear(); |
| 1016 | m_assistantThinking.clear(); |
| 1017 | m_assistantIndex = -1; |
| 1018 | m_pendingThinkingBlocks = QJsonArray(); |
| 1019 | m_pendingToolUseBlocks = QJsonArray(); |
| 1020 | m_pendingToolResultBlocks = QJsonArray(); |
| 1021 | m_outstandingToolResults = 0; |
| 1022 | teardownReply(); |
| 1023 | setBusy(false); |
| 1024 | } |
| 1025 | |
| 1026 | /** |
| 1027 | * @brief Returns true when the failed request is safe to retry: transient cause, retry |