feat: Auto-Retry bei prompt-too-long – History dynamisch kürzen
- Bei API 400 'prompt is too long' / 'exceed context limit': - 20% der ältesten Messages aus In-Memory-History entfernen - TrimMessagesCleanly() verhindert Trennung von tool_use/tool_result-Paaren - Retry mit kürzerer History (max 5 Retries im Worker) - Alle 3 API-Call-Stellen abgesichert (StreamChat, RunAgentChat, ChatNonStreaming) - Frontend: Neues 'info'-Event als Warning-Bubble angezeigt - MaxHistory 220 bleibt als Obergrenze, dynamische Kürzung bei Token-Überschreitung
This commit is contained in:
1 parent
8dd962ac49
commit
c9b1523e86
2 files changed
+95
No files matched your search
@@ -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 };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Entfernt Messages vom Anfang der Liste, ohne tool_use/tool_result-Paare zu trennen.
|
||||
/// Gibt die tatsächliche Anzahl entfernter Messages zurück.
|
||||
/// </summary>
|
||||
private static int TrimMessagesCleanly(List<object> 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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user