fix: SSE-Stream-Stabilität und History-Trimming tool_use/tool_result-Schutz
- Kestrel MinResponseDataRate per-Request deaktivieren für SSE-Streams (Default 240 bytes/s + 5s Grace → client_disconnected bei langen Tools) - SSE-Heartbeat alle 15s während Stream (schützt gegen Browser/Netzwerk-Idle) - History-Trimming: RemoveRange schneidet nicht mehr in tool_use/tool_result-Paare - Reminder-Insertion: kein Einfügen zwischen tool_use und tool_result
This commit is contained in:
1 parent
e81983cab6
commit
dd99216913
3 files changed
+101
-2
No files matched your search
+74
-2
@@ -445,8 +445,67 @@ Für welche Plattform soll die App entwickelt werden? Web, Mobile oder Desktop?
|
||||
}
|
||||
|
||||
// History auf maximal N Messages begrenzen (älteste entfernen)
|
||||
// WICHTIG: Nicht mitten in einem tool_use/tool_result-Paar abschneiden!
|
||||
if (history.Count > _maxHistory)
|
||||
history.RemoveRange(0, history.Count - _maxHistory);
|
||||
{
|
||||
int removeCount = history.Count - _maxHistory;
|
||||
// Nach vorne korrigieren bis wir an einer sicheren Stelle sind:
|
||||
// Nicht abschneiden wenn die erste verbleibende Message ein tool_result ist
|
||||
// oder die letzte entfernte Message ein assistant mit tool_use ist
|
||||
while (removeCount < history.Count - 1)
|
||||
{
|
||||
var firstRemaining = history[removeCount];
|
||||
bool isToolResult = false;
|
||||
if (firstRemaining is Dictionary<string, object> frDict &&
|
||||
frDict.TryGetValue("role", out var frRole) && frRole.ToString() == "user" &&
|
||||
frDict.TryGetValue("content", out var frContent) && frContent is List<object> frList)
|
||||
{
|
||||
isToolResult = frList.Any(item =>
|
||||
{
|
||||
var json = JsonSerializer.Serialize(item);
|
||||
return json.Contains("\"tool_result\"");
|
||||
});
|
||||
}
|
||||
|
||||
if (isToolResult)
|
||||
{
|
||||
// Die erste verbleibende Message ist ein tool_result → muss den vorherigen
|
||||
// assistant[tool_use] auch behalten → ein Element weniger entfernen
|
||||
removeCount--;
|
||||
// Jetzt prüfen ob der neue Start auch sicher ist (könnte auch tool_result sein)
|
||||
continue;
|
||||
}
|
||||
|
||||
// Prüfen ob die letzte entfernte Message ein assistant mit tool_use war
|
||||
bool lastRemovedHasToolUse = false;
|
||||
if (removeCount > 0)
|
||||
{
|
||||
var lastRemoved = history[removeCount - 1];
|
||||
if (lastRemoved is Dictionary<string, object> lrDict &&
|
||||
lrDict.TryGetValue("role", out var lrRole) && lrRole.ToString() == "assistant" &&
|
||||
lrDict.TryGetValue("content", out var lrContent) && lrContent is List<object> lrList)
|
||||
{
|
||||
lastRemovedHasToolUse = lrList.Any(item =>
|
||||
{
|
||||
var json = JsonSerializer.Serialize(item);
|
||||
return json.Contains("\"tool_use\"");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (lastRemovedHasToolUse)
|
||||
{
|
||||
// Letztes entferntes Element enthält tool_use → auch das tool_result entfernen
|
||||
removeCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (removeCount > 0 && removeCount < history.Count)
|
||||
history.RemoveRange(0, removeCount);
|
||||
}
|
||||
|
||||
// Reminder einfügen: alle ~30 user/assistant-Paare ein Reminder-Paar einfügen
|
||||
var reminder = SystemPromptBuilder.BuildReminder(promptBlocks);
|
||||
@@ -473,7 +532,20 @@ Für welche Plattform soll die App entwickelt werden? Web, Mobile oder Desktop?
|
||||
{
|
||||
// Position nach dem aktuellen Element – aber nur einfügen wenn es eine
|
||||
// assistant-Message ist (damit user/assistant-Alternierung nicht bricht)
|
||||
if (history[i] is Dictionary<string, object> d &&
|
||||
// UND die nächste Message kein tool_result ist (sonst bricht tool_use/tool_result-Paar)
|
||||
bool nextIsToolResult = false;
|
||||
if (i + 1 < history.Count && history[i + 1] is Dictionary<string, object> nextDict &&
|
||||
nextDict.TryGetValue("role", out var nextRole) && nextRole.ToString() == "user" &&
|
||||
nextDict.TryGetValue("content", out var nextContent) && nextContent is List<object> nextList)
|
||||
{
|
||||
nextIsToolResult = nextList.Any(item =>
|
||||
{
|
||||
var json = JsonSerializer.Serialize(item);
|
||||
return json.Contains("\"tool_result\"");
|
||||
});
|
||||
}
|
||||
|
||||
if (!nextIsToolResult && history[i] is Dictionary<string, object> d &&
|
||||
d.TryGetValue("role", out var r) && r.ToString() == "assistant")
|
||||
{
|
||||
insertPositions.Add(i + 1);
|
||||
|
||||
Reference in new issue
Block a user