From 47d6f44e07fa6c9ce8a6e1fa8ee49337aaf04bf0 Mon Sep 17 00:00:00 2001 From: Meleki Date: Mon, 9 Mar 2026 06:35:35 +0100 Subject: [PATCH] Auto-Reminder alle 30 Messages + History-Limit 120 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SystemPromptBuilder.BuildReminder(): generiert automatisch Reminder-Text aus den aktiven Prompt-Blöcken (Identity, Workspace, Memory, etc.) - Alle ~30 Messages wird ein [System Reminder]-Paar in die History injiziert (nur nach assistant-Messages, um user/assistant-Alternierung zu wahren) - History auf maximal 120 Messages begrenzt (älteste werden entfernt) - ChatContext um PromptBlocks-Property erweitert - Alle Aufrufer (ChatEndpoint, TelegramService, CronjobService) übergeben promptBlocks an BuildHistoryFromDb --- Endpoints/ChatEndpoint.cs | 4 +-- Models/ChatContext.cs | 2 ++ Services/ChatService.cs | 53 +++++++++++++++++++++++++++++++-- Services/CronjobService.cs | 2 +- Services/SystemPromptBuilder.cs | 34 +++++++++++++++++++++ Services/TelegramService.cs | 2 +- 6 files changed, 91 insertions(+), 6 deletions(-) diff --git a/Endpoints/ChatEndpoint.cs b/Endpoints/ChatEndpoint.cs index 6d853da..e888435 100644 --- a/Endpoints/ChatEndpoint.cs +++ b/Endpoints/ChatEndpoint.cs @@ -137,7 +137,7 @@ public static class ChatEndpoint { // Preparer bekommt History MIT seinen eigenen vorherigen Messages (Tag "preparer"), // damit er den Kontext seiner Rückfragen kennt - var preparerHistory = await chatService.BuildHistoryFromDb(sessionId, includeTag: "preparer"); + var preparerHistory = await chatService.BuildHistoryFromDb(sessionId, includeTag: "preparer", promptBlocks: ctx.PromptBlocks); var preparerResult = await anthropic.RunPreparer( res, ctx.Preparer, preparerHistory, ctx.ChainId, ctx.Workspace, sessionId, cancellationToken); @@ -170,7 +170,7 @@ public static class ChatEndpoint } // History für den Session-Agent: OHNE Sub-Agent-Messages (saubere History) - var messages = await chatService.BuildHistoryFromDb(sessionId); + var messages = await chatService.BuildHistoryFromDb(sessionId, promptBlocks: ctx.PromptBlocks); // Bei Choice-Response: toolChoice auf "auto" setzen, damit Claude frei antworten kann var effectiveToolChoice = isChoiceResponse ? "auto" : ctx.ToolChoice; diff --git a/Models/ChatContext.cs b/Models/ChatContext.cs index 1becd3c..37f5dd5 100644 --- a/Models/ChatContext.cs +++ b/Models/ChatContext.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Gott.Services; namespace Gott.Models; @@ -11,6 +12,7 @@ public class ChatContext public string AgentId { get; set; } public string SystemPromptText { get; set; } public List SystemPrompt { get; set; } + public List PromptBlocks { get; set; } public string ToolChoice { get; set; } public bool UseMemory { get; set; } public string WebSearchVersion { get; set; } diff --git a/Services/ChatService.cs b/Services/ChatService.cs index 134b9f2..8bb11e2 100644 --- a/Services/ChatService.cs +++ b/Services/ChatService.cs @@ -63,10 +63,11 @@ public class ChatService if (string.IsNullOrEmpty(workspace) && agentSkills.Any()) workspace = "/tmp"; - var systemPromptText = SystemPromptBuilder.Build( + var promptBlocks = SystemPromptBuilder.BuildBlocks( agent, session, markedMessages, _memoryService, agentMemoryDir, agentSkills, _skillService, userPersona: user?.GetEffectivePersona(), resolvedWorkspace: workspace, planMode: planMode); + var systemPromptText = string.Join("\n\n", promptBlocks.Select(b => b.Content)); var hasWorkspace = !string.IsNullOrEmpty(workspace); var includeMemory = agent.UseMemory && !string.IsNullOrEmpty(agent.AgentId); @@ -258,6 +259,7 @@ Für welche Plattform soll die App entwickelt werden? Web, Mobile oder Desktop? AgentId = agent.AgentId, SystemPromptText = systemPromptText, SystemPrompt = systemPrompt, + PromptBlocks = promptBlocks, ToolChoice = toolChoice, UseMemory = agent.UseMemory, WebSearchVersion = agent.WebSearchVersion, @@ -300,7 +302,7 @@ Für welche Plattform soll die App entwickelt werden? Web, Mobile oder Desktop? /// includeTag: Wenn gesetzt, werden Messages mit diesem Tag NICHT ausgefiltert /// (z.B. "preparer" damit der Preparer seine eigenen vorherigen Messages sieht). /// - public async Task> BuildHistoryFromDb(string sessionId, string includeTag = null) + public async Task> BuildHistoryFromDb(string sessionId, string includeTag = null, List promptBlocks = null) { var chainsList = (await _db.GetActiveChains(sessionId)).ToList(); // chronologisch sortieren (älteste zuerst) @@ -476,6 +478,53 @@ Für welche Plattform soll die App entwickelt werden? Web, Mobile oder Desktop? } } + // History auf maximal 120 Messages begrenzen (älteste entfernen) + const int maxHistory = 120; + if (history.Count > maxHistory) + history.RemoveRange(0, history.Count - maxHistory); + + // Reminder einfügen: alle ~30 user/assistant-Paare ein Reminder-Paar einfügen + var reminder = SystemPromptBuilder.BuildReminder(promptBlocks); + if (!string.IsNullOrWhiteSpace(reminder) && history.Count > 30) + { + var reminderUser = new Dictionary + { + ["role"] = "user", + ["content"] = new List { new { type = "text", text = "[System Reminder] " + reminder } } + }; + var reminderAssistant = new Dictionary + { + ["role"] = "assistant", + ["content"] = new List { new { type = "text", text = "Understood." } } + }; + + // Alle 30 Messages ein Reminder-Paar einfügen (von hinten nach vorne, damit Indizes stimmen) + var insertPositions = new List(); + int messageCount = 0; + for (int i = 0; i < history.Count; i++) + { + messageCount++; + if (messageCount > 0 && messageCount % 30 == 0) + { + // 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 d && + d.TryGetValue("role", out var r) && r.ToString() == "assistant") + { + insertPositions.Add(i + 1); + } + } + } + + // Von hinten nach vorne einfügen, damit Indizes nicht verschoben werden + for (int j = insertPositions.Count - 1; j >= 0; j--) + { + var pos = insertPositions[j]; + history.Insert(pos, reminderAssistant); + history.Insert(pos, reminderUser); + } + } + return history; } } diff --git a/Services/CronjobService.cs b/Services/CronjobService.cs index aa03e2d..27e6141 100644 --- a/Services/CronjobService.cs +++ b/Services/CronjobService.cs @@ -87,7 +87,7 @@ public class CronjobService : BackgroundService throw new Exception("Session oder Agent nicht gefunden / nicht konfiguriert"); // History aus DB laden - var history = await _chatService.BuildHistoryFromDb(job.SessionId); + var history = await _chatService.BuildHistoryFromDb(job.SessionId, promptBlocks: ctx.PromptBlocks); // User-Nachricht hinzufügen var userContent = new List { new { type = "text", text = job.Prompt } }; diff --git a/Services/SystemPromptBuilder.cs b/Services/SystemPromptBuilder.cs index 529382b..84e4a0a 100644 --- a/Services/SystemPromptBuilder.cs +++ b/Services/SystemPromptBuilder.cs @@ -232,4 +232,38 @@ Alle Dateipfade in Tool-Aufrufen müssen relativ zu diesem Workspace sein oder a var blocks = BuildBlocks(agent, session, markedMessages, memoryService, agentMemoryDir, skillSlugs, skillService, userPersona, resolvedWorkspace, planMode); return string.Join("\n\n", blocks.Select(b => b.Content)); } + + /// + /// Generiert automatisch einen kompakten Reminder-Text basierend auf den aktiven Prompt-Blöcken. + /// Gibt null zurück wenn weniger als 2 Blöcke aktiv sind (kein Reminder nötig). + /// + public static string BuildReminder(List blocks) + { + if (blocks == null || blocks.Count < 2) + return null; + + var parts = new List(); + + foreach (var block in blocks) + { + var label = block.Label; + if (label.Contains("IDENTITY")) parts.Add("Identity"); + else if (label.Contains("SOUL")) parts.Add("Soul"); + else if (label.Contains("Anweisungen")) parts.Add("Anweisungen"); + else if (label.Contains("Persona")) parts.Add("User-Info"); + else if (label.Contains("Workspace")) parts.Add("Workspace"); + else if (label.Contains("Rückfragen")) parts.Add("Rückfragen"); + else if (label.Contains("Kontext")) parts.Add("Kontext"); + else if (label.Contains("Skills")) parts.Add("Skills"); + else if (label.Contains("Memory")) parts.Add("Memory"); + else if (label.Contains("Git")) parts.Add("Git-Regeln"); + else if (label.Contains("Plan-Modus")) parts.Add("Plan-Modus"); + else if (label.Contains("Markierte")) parts.Add("Markierte Nachrichten"); + } + + if (parts.Count == 0) + return null; + + return "Beachte weiterhin den System-Prompt (" + string.Join(", ", parts) + ")."; + } } diff --git a/Services/TelegramService.cs b/Services/TelegramService.cs index bdf19f6..31f5118 100644 --- a/Services/TelegramService.cs +++ b/Services/TelegramService.cs @@ -172,7 +172,7 @@ public class TelegramService try { - var history = await _chatService.BuildHistoryFromDb(session.SessionId); + var history = await _chatService.BuildHistoryFromDb(session.SessionId, promptBlocks: ctx.PromptBlocks); history.Add(new Dictionary { ["role"] = "user",