B1 — Die Compaction behielt blind die letzten 6 Nachrichten. Fiel diese Grenze mitten in eine Tool-Sequenz, entstand eine tool-Antwort ohne zugehoerigen assistant-tool_call; die API lehnt das mit HTTP 400 ab. FindSafeTailStart verschiebt die Grenze jetzt rueckwaerts auf eine Blockgrenze. B14 — Bei Konversationen mit hoechstens 6 Nachrichten enthielt der Tail auch die system-Nachricht, die anschliessend ein zweites Mal angehaengt wurde. Ergebnis war ein doppelter System-Prompt und eine duplizierte Konversation — die Compaction vergroesserte den Kontext, statt ihn zu verkleinern. Der Tail beginnt nun grundsaetzlich hinter dem System-Prompt; liegt davor nichts Nennenswertes, wird die Kompaktierung uebersprungen. Gefunden durch den Property-Test. Nebeneffekt: Zusammengefasst wird nur noch der Teil, der tatsaechlich wegfaellt. Der Tail bleibt woertlich erhalten und musste bisher doppelt bezahlt werden. B3 — maxTokens zaehlte kumulativ ueber alle Schritte, wurde aber wie eine Kontextgrenze konfiguriert. Da jeder Schritt den vollen Kontext erneut sendet, brach ein Chat mit 20k Kontext nach vier Schritten ab. Aufgeteilt in maxCumulativeTokens (Kostenbudget, Default 500k) und maxContextTokens (Kontextgroesse). Alte Konfigurationen werden beim Laden migriert, die Fehlermeldungen unterscheiden jetzt Schritt- und Kostenlimit. Alle 31 Tests gruen. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
40 lines
1.4 KiB
C#
40 lines
1.4 KiB
C#
using ClawdDotNet.Core.Config;
|
|
|
|
namespace ClawdDotNet.Core.Engine;
|
|
|
|
public sealed class LoopGuard
|
|
{
|
|
private readonly LoopGuardConfig _cfg;
|
|
private int _steps;
|
|
private int _tokens;
|
|
|
|
public LoopGuard(LoopGuardConfig cfg) => _cfg = cfg;
|
|
|
|
public int Steps => _steps;
|
|
public int Tokens => _tokens;
|
|
|
|
public void RecordStep()
|
|
{
|
|
if (Interlocked.Increment(ref _steps) > _cfg.MaxSteps)
|
|
throw new LoopLimitExceededException(
|
|
$"Schritt-Limit erreicht: {_cfg.MaxSteps} Schritte pro Run.");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bucht die abgerechneten Tokens eines Schritts auf das Kostenbudget des Runs.
|
|
/// Das ist eine reine Kostenbremse — die Kontextgröße wird davon getrennt über
|
|
/// <see cref="LoopGuardConfig.MaxContextTokens"/> und die Compaction gesteuert.
|
|
/// </summary>
|
|
public void RecordTokens(int count)
|
|
{
|
|
var total = Interlocked.Add(ref _tokens, count);
|
|
if (total > _cfg.MaxCumulativeTokens)
|
|
throw new LoopLimitExceededException(
|
|
$"Kostenbudget erschöpft: {total:N0} von maximal {_cfg.MaxCumulativeTokens:N0} " +
|
|
"abgerechneten Tokens in diesem Run. Hinweis: Jeder Schritt sendet den " +
|
|
"kompletten Kontext erneut, die Summe wächst daher überproportional.");
|
|
}
|
|
}
|
|
|
|
public sealed class LoopLimitExceededException(string message) : Exception(message);
|