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
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
@@ -124,6 +125,13 @@ public static class ChatEndpoint
|
||||
var bufferingFeature = res.HttpContext.Features.Get<Microsoft.AspNetCore.Http.Features.IHttpResponseBodyFeature>();
|
||||
bufferingFeature?.DisableBuffering();
|
||||
|
||||
// Kestrel MinResponseDataRate deaktivieren für diesen SSE-Stream.
|
||||
// Ohne dies killt Kestrel die Verbindung wenn bei langen Tool-Ausführungen
|
||||
// keine Daten fließen (Default: 240 bytes/s, 5s Grace → "client_disconnected").
|
||||
var minRateFeature = res.HttpContext.Features.Get<Microsoft.AspNetCore.Server.Kestrel.Core.Features.IHttpMinResponseDataRateFeature>();
|
||||
if (minRateFeature != null)
|
||||
minRateFeature.MinDataRate = null;
|
||||
|
||||
// ── Preparer-Agent Phase (vor dem Worker) ──
|
||||
if (ctx.Preparer != null && !isChoiceResponse)
|
||||
{
|
||||
@@ -171,6 +179,23 @@ public static class ChatEndpoint
|
||||
var stopCts = streamingTracker.Start(sessionId, ctx.ChainId);
|
||||
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, stopCts.Token);
|
||||
|
||||
// SSE-Heartbeat: Alle 15s ein Kommentar senden, damit die Verbindung
|
||||
// bei langen Tool-Ausführungen nicht von Browser/Netzwerk getrennt wird.
|
||||
var heartbeatTask = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
while (!linkedCts.Token.IsCancellationRequested)
|
||||
{
|
||||
await Task.Delay(15000, linkedCts.Token);
|
||||
await res.WriteAsync(": heartbeat\n\n", linkedCts.Token);
|
||||
await res.Body.FlushAsync(linkedCts.Token);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException) { }
|
||||
catch { }
|
||||
}, linkedCts.Token);
|
||||
|
||||
try
|
||||
{
|
||||
await anthropic.StreamChat(res, ctx.ModelId, ctx.SystemPrompt, messages, ctx.ChainId, ctx.Workspace, sessionId, agentId: ctx.AgentId, useMemory: ctx.UseMemory, webSearchVersion: ctx.WebSearchVersion, maxIterations: ctx.MaxIterations, toolChoice: effectiveToolChoice, review: ctx.Review, finalizer: ctx.Finalizer, allowUserChoice: ctx.Agent?.AllowUserChoice ?? true, planMode: ctx.PlanMode, cancellationToken: linkedCts.Token);
|
||||
@@ -178,6 +203,7 @@ public static class ChatEndpoint
|
||||
finally
|
||||
{
|
||||
streamingTracker.Stop(sessionId);
|
||||
// Heartbeat-Task beenden (linkedCts wird disposed → Token cancelled)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user