diff --git a/Services/AnthropicService.cs b/Services/AnthropicService.cs index f2f8308..a847de9 100644 --- a/Services/AnthropicService.cs +++ b/Services/AnthropicService.cs @@ -60,6 +60,7 @@ public class AnthropicService int iterations = 0; int reviewRound = 0; string lastStopReason = ""; + int promptTrimRetries = 0; var chain = new ChainRecord { @@ -106,6 +107,23 @@ public class AnthropicService { var errBody = await httpResp.Content.ReadAsStringAsync(workCt); var errMsg = $"API {(int)httpResp.StatusCode}: {errBody}"; + + // Auto-Retry bei "prompt too long" / "exceed context limit": History kürzen und erneut versuchen + if ((int)httpResp.StatusCode == 400 && + (errBody.Contains("prompt is too long") || errBody.Contains("exceed context limit")) && + messages.Count > 4 && promptTrimRetries < 5) + { + promptTrimRetries++; + int trimCount = Math.Max(2, messages.Count / 5); // 20% der Messages entfernen + trimCount = TrimMessagesCleanly(messages, trimCount); + if (trimCount > 0) + { + await TryWriteSse(response, "info", new { message = $"History zu lang – {trimCount} ältere Messages entfernt, Retry ({promptTrimRetries}/5)..." }, sseCt); + iterations--; // Iteration nicht zählen, war ja kein echter Versuch + continue; // → nächste while-Iteration mit kürzerer History + } + } + await _db.SaveMessage(new MessageRecord { ChainId = chainId, Iteration = iterations, @@ -120,6 +138,7 @@ public class AnthropicService var (stopReason, assistantContent, executedTools, streamErrorInfo) = await ProcessStream(response, httpResp, usage, workspace, agentId, workCt, sseCt); lastStopReason = stopReason; + promptTrimRetries = 0; // Erfolgreicher API-Call → Retry-Counter resetten messages.Add(new { role = "assistant", content = assistantContent }); @@ -726,6 +745,20 @@ public class AnthropicService if (!httpResp.IsSuccessStatusCode) { + // Auto-Retry bei "prompt too long" in Sub-Agents + if ((int)httpResp.StatusCode == 400 && + (respBody.Contains("prompt is too long") || respBody.Contains("exceed context limit")) && + messages.Count > 4) + { + int trimCount = Math.Max(2, messages.Count / 5); + trimCount = TrimMessagesCleanly(messages, trimCount); + if (trimCount > 0) + { + await TryWriteSse(response, "info", new { message = $"{tag}: History zu lang – {trimCount} Messages entfernt, Retry..." }, sseCt); + iterations--; + continue; + } + } return ($"{tag}-Agent API Fehler: {(int)httpResp.StatusCode}: {respBody}", iterations); } @@ -931,6 +964,20 @@ public class AnthropicService if (!httpResp.IsSuccessStatusCode) { + // Auto-Retry bei "prompt too long" in Non-Streaming + if ((int)httpResp.StatusCode == 400 && + (respBody.Contains("prompt is too long") || respBody.Contains("exceed context limit")) && + messages.Count > 4) + { + int trimCount = Math.Max(2, messages.Count / 5); + trimCount = TrimMessagesCleanly(messages, trimCount); + if (trimCount > 0) + { + iterations--; + continue; + } + } + var errMsg = $"API {(int)httpResp.StatusCode}: {respBody}"; await _db.SaveMessage(new MessageRecord { @@ -1485,6 +1532,50 @@ public class AnthropicService payload["tool_choice"] = new { type = effectiveChoice }; } + /// + /// Entfernt Messages vom Anfang der Liste, ohne tool_use/tool_result-Paare zu trennen. + /// Gibt die tatsächliche Anzahl entfernter Messages zurück. + /// + private static int TrimMessagesCleanly(List messages, int targetRemoveCount) + { + if (targetRemoveCount <= 0 || targetRemoveCount >= messages.Count) + return 0; + + int removeCount = targetRemoveCount; + + // Sicherstellen, dass wir nicht mitten in einem tool_use/tool_result-Paar abschneiden + while (removeCount < messages.Count - 1) + { + var firstRemaining = messages[removeCount]; + var firstRemainingJson = JsonSerializer.Serialize(firstRemaining); + + // Wenn die erste verbleibende Message ein tool_result ist → vorherige auch behalten + if (firstRemainingJson.Contains("\"tool_result\"")) + { + removeCount--; + if (removeCount <= 0) return 0; + continue; + } + + // Wenn die letzte entfernte Message ein assistant mit tool_use ist → tool_result auch entfernen + var lastRemovedJson = JsonSerializer.Serialize(messages[removeCount - 1]); + if (lastRemovedJson.Contains("\"tool_use\"") && lastRemovedJson.Contains("\"assistant\"")) + { + removeCount++; + continue; + } + + break; + } + + if (removeCount > 0 && removeCount < messages.Count) + { + messages.RemoveRange(0, removeCount); + return removeCount; + } + return 0; + } + private HttpClient CreateClient() { var client = _httpFactory.CreateClient(); diff --git a/wwwroot/js/chat.js b/wwwroot/js/chat.js index c98eda6..390ce1b 100644 --- a/wwwroot/js/chat.js +++ b/wwwroot/js/chat.js @@ -107,6 +107,10 @@ function handleStreamEvent(eventType, data) { finalizeReviewBubble('done', data, 'finalizer'); break; + case 'info': + addWarningBubble(data.message, true); + break; + case 'error': endStreamBubble(); addErrorBubble(data.error, true);