#608: Ein wegen erschoepften Budgets abgelehnter Lauf hinterlaesst jetzt einen Beleg (Status BudgetExceeded) an Aufgabe und Verlauf; die Log-Warnung kommt einmal je Agent, Grenze und Tag. Die Kostenprognose zaehlt Ablehnungen nicht als Lauf und warnt, wenn die geplanten Aufgaben eines Agenten sein Tagesbudget uebersteigen (API + Verbrauchsansicht). #607: Passt schon der erste Treffer nicht in den Abruf, wird er gekuerzt (Anfang und Ende bleiben) statt ganz zu fehlen. remember weist auf gleichnamige Eintraege ohne Schluessel und auf Eintraege nahe der Abrufgrenze hin. Version 0.1.9. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -165,7 +165,47 @@ public sealed class MemoryTool : IAgentTool
|
||||
|
||||
return ToolResult.Ok(
|
||||
$"Gemerkt (#{saved.Id}, {scopeLabel}){keyHint}\n" +
|
||||
$"Betreff: {saved.Subject} | Art: {saved.Category} | Wichtigkeit: {saved.Importance}");
|
||||
$"Betreff: {saved.Subject} | Art: {saved.Category} | Wichtigkeit: {saved.Importance}" +
|
||||
await HinweiseAsync(saved, scope, repository, context, ct));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// #607: Ein Agent merkte sich bei jedem Lauf dieselbe, wachsende Liste ohne Schlüssel -
|
||||
/// sieben Einträge mit zusammen 23.000 Zeichen, bis der neueste allein nicht mehr in einen
|
||||
/// Abruf passte. Beides soll er erfahren, solange er es noch ändern kann.
|
||||
/// </summary>
|
||||
private static async Task<string> HinweiseAsync(
|
||||
MemoryEntry saved, MemoryScope scope, IMemoryRepository repository, AgentToolContext context, CancellationToken ct)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(saved.Key))
|
||||
{
|
||||
var gleicherBetreff = await repository.RecallAsync(new MemoryQuery
|
||||
{
|
||||
Scope = scope,
|
||||
OwnerId = context.AgentId,
|
||||
Subject = saved.Subject,
|
||||
Limit = 50,
|
||||
MinImportance = 1
|
||||
}, ct);
|
||||
|
||||
var ohneSchluessel = gleicherBetreff.Count(e => string.IsNullOrWhiteSpace(e.Key));
|
||||
if (ohneSchluessel > 1)
|
||||
{
|
||||
sb.Append($"\nHinweis: Unter dem Betreff '{saved.Subject}' liegen jetzt {ohneSchluessel} Einträge " +
|
||||
"ohne Schlüssel. Ersetzt dieser Eintrag einen früheren, merke ihn mit 'key' – dann wird " +
|
||||
"er fortgeschrieben statt verdoppelt. Überholte Einträge entfernst du mit forget (id).");
|
||||
}
|
||||
}
|
||||
|
||||
if (saved.Content.Length > MaxResultChars / 2)
|
||||
{
|
||||
sb.Append($"\nHinweis: Der Eintrag ist {saved.Content.Length} Zeichen lang, ein Abruf zeigt höchstens " +
|
||||
$"{MaxResultChars} Zeichen. Halte ihn kürzer oder teile ihn auf, sonst wird er beim Abruf gekürzt.");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static async Task<ToolResult> RecallAsync(
|
||||
@@ -252,8 +292,21 @@ public sealed class MemoryTool : IAgentTool
|
||||
// Lieber weniger Treffer als ein gesprengter Kontext.
|
||||
if (sb.Length + block.Length > MaxResultChars)
|
||||
{
|
||||
sb.AppendLine($"[… {entries.Count - shown} weitere Treffer ausgelassen. " +
|
||||
"Grenze die Suche ein, um sie zu sehen.]");
|
||||
// #607: Passte schon der erste Treffer nicht, fiel er frueher ganz weg - der
|
||||
// Agent sah keinen einzigen Inhalt und hielt alles fuer neu. Jetzt wird er
|
||||
// gekuerzt; Anfang und Ende bleiben stehen, weil wachsende Listen hinten
|
||||
// anhaengen und Notizen vorne beginnen.
|
||||
if (shown == 0)
|
||||
{
|
||||
sb.Append(FormatEntry(entry, scope, MaxResultChars - sb.Length - FooterReserve));
|
||||
shown++;
|
||||
}
|
||||
|
||||
if (entries.Count > shown)
|
||||
{
|
||||
sb.AppendLine($"[… {entries.Count - shown} weitere Treffer ausgelassen. " +
|
||||
"Grenze die Suche ein, um sie zu sehen.]");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -264,29 +317,56 @@ public sealed class MemoryTool : IAgentTool
|
||||
return sb.ToString().TrimEnd();
|
||||
}
|
||||
|
||||
private static string FormatEntry(MemoryEntry entry, MemoryScope scope)
|
||||
/// <summary>Platz, der nach einem gekuerzten Treffer fuer den Auslassungshinweis bleibt.</summary>
|
||||
private const int FooterReserve = 200;
|
||||
|
||||
private static string FormatEntry(MemoryEntry entry, MemoryScope scope, int? maxChars = null)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.Append($"#{entry.Id} [{entry.Subject}] {entry.Category}, Wichtigkeit {entry.Importance}");
|
||||
var header = new StringBuilder();
|
||||
header.Append($"#{entry.Id} [{entry.Subject}] {entry.Category}, Wichtigkeit {entry.Importance}");
|
||||
if (!string.IsNullOrWhiteSpace(entry.Key))
|
||||
sb.Append($", Schlüssel: {entry.Key}");
|
||||
sb.AppendLine();
|
||||
|
||||
sb.AppendLine($" {entry.Content}");
|
||||
header.Append($", Schlüssel: {entry.Key}");
|
||||
|
||||
var meta = new List<string> { $"aktualisiert {entry.UpdatedAt:yyyy-MM-dd HH:mm} UTC" };
|
||||
if (entry.Tags.Count > 0)
|
||||
meta.Add("Schlagworte: " + string.Join(", ", entry.Tags));
|
||||
if (scope == MemoryScope.Shared && !string.IsNullOrWhiteSpace(entry.CreatedBy))
|
||||
meta.Add($"von {entry.CreatedBy}");
|
||||
var metaLine = $" ({string.Join(" | ", meta)})";
|
||||
|
||||
sb.AppendLine($" ({string.Join(" | ", meta)})");
|
||||
var content = entry.Content;
|
||||
if (maxChars is { } max)
|
||||
content = Kuerze(content, max - header.Length - metaLine.Length - 16);
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.Append(header).AppendLine();
|
||||
sb.AppendLine($" {content}");
|
||||
sb.AppendLine(metaLine);
|
||||
sb.AppendLine();
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>Kuerzt einen Inhalt auf etwa <paramref name="platz"/> Zeichen, Anfang und Ende bleiben.</summary>
|
||||
internal static string Kuerze(string content, int platz)
|
||||
{
|
||||
const int Mindestens = 400;
|
||||
|
||||
var marke = $"\n [… gekürzt: {{0}} von {content.Length} Zeichen ausgelassen – der Eintrag ist zu groß " +
|
||||
"für einen Abruf. Teile ihn auf oder halte ihn kürzer.]\n ";
|
||||
|
||||
if (content.Length <= platz)
|
||||
return content;
|
||||
|
||||
var haelfte = Math.Max(Mindestens, platz - marke.Length) / 2;
|
||||
if (2 * haelfte >= content.Length)
|
||||
return content;
|
||||
|
||||
return content[..haelfte]
|
||||
+ string.Format(marke, content.Length - 2 * haelfte)
|
||||
+ content[^haelfte..];
|
||||
}
|
||||
|
||||
// ─── Eingabe lesen ───
|
||||
|
||||
private static string? GetString(JsonElement input, string name)
|
||||
|
||||
Reference in New Issue
Block a user