Files
gott/Services/SystemPromptBuilder.cs
T
Meleki b24a000784 Zeilenreferenz-Hinweis im System-Prompt auch ohne ToolChoice:any
- toolAny+workspace: Kombinierter Erinnerungsblock (Tool-Pflicht + Zeilenreferenz)
- !toolAny+workspace: Eigener Erinnerungsblock nur mit Zeilenreferenz-Hinweis
- Kein doppelter ERINNERUNG-Header mehr
2026-03-02 10:30:22 +01:00

242 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections.Generic;
using System.Linq;
using Gott.Models;
namespace Gott.Services;
public class PromptBlock
{
public string Icon { get; set; }
public string Label { get; set; }
public string Content { get; set; }
}
public static class SystemPromptBuilder
{
public static List<PromptBlock> BuildBlocks(AgentRecord agent, SessionRecord session, IEnumerable<MessageRecord> markedMessages = null, MemoryService memoryService = null, string agentMemoryDir = null, string dataDir = null, IEnumerable<string> skillSlugs = null, SkillService skillService = null)
{
var blocks = new List<PromptBlock>();
// IDENTITY.md
var hasIdentity = false;
if (agent.UseIdentity && memoryService != null && !string.IsNullOrWhiteSpace(agent.AgentId))
{
var identity = memoryService.ReadMemoryFile(agent.AgentId, "IDENTITY.md");
if (!string.IsNullOrEmpty(identity))
{
blocks.Add(new PromptBlock { Icon = "fa-id-badge", Label = "Agent · IDENTITY.md (UseIdentity)", Content = identity });
hasIdentity = true;
}
}
if (!hasIdentity && agent.UseIdentity)
blocks.Add(new PromptBlock { Icon = "fa-id-badge", Label = "Agent · IDENTITY.md (UseIdentity, Fallback)", Content = $"Du bist {agent.Name}." });
// SOUL.md
if (agent.UseSoul && memoryService != null && !string.IsNullOrWhiteSpace(agent.AgentId))
{
var soul = memoryService.ReadMemoryFile(agent.AgentId, "SOUL.md");
if (!string.IsNullOrEmpty(soul))
blocks.Add(new PromptBlock { Icon = "fa-heart", Label = "Agent · SOUL.md (UseSoul)", Content = soul });
}
// USER.md
if (agent.UseUserInfo && !string.IsNullOrWhiteSpace(dataDir))
{
var userFile = System.IO.Path.Combine(dataDir, "USER.md");
if (System.IO.File.Exists(userFile))
{
var userContent = System.IO.File.ReadAllText(userFile);
if (!string.IsNullOrWhiteSpace(userContent))
{
var absolutePath = System.IO.Path.GetFullPath(userFile);
blocks.Add(new PromptBlock { Icon = "fa-user", Label = "Agent · USER.md (UseUserInfo)", Content = $"{userContent}\n\n_Pfad: `{absolutePath}`_" });
}
}
}
bool toolAny = string.Equals(agent.ToolChoice, "any", System.StringComparison.OrdinalIgnoreCase);
if (!string.IsNullOrWhiteSpace(session.Workspace))
{
if (toolAny)
{
blocks.Add(new PromptBlock
{
Icon = "fa-exclamation-triangle",
Label = "System · Workspace + ToolChoice: any",
Content = @$"## KRITISCH: Tool-Nutzung
JEDE Datei-Operation und JEDER Shell-Befehl MUSS über einen Tool-Aufruf erfolgen. NIEMALS eine Aktion nur beschreiben oder behaupten - IMMER das Tool tatsächlich aufrufen."
});
}
blocks.Add(new PromptBlock
{
Icon = "fa-wrench",
Label = "Session · Workspace",
Content = @$"## Tools
Du hast Tools zur Verfügung (read_file, write_file, edit_file, list_files, delete_file, execute). Benutze sie um Dateien zu lesen, schreiben und bearbeiten.
Der Workspace-Pfad ist: `{session.Workspace}`
Alle Dateipfade in Tool-Aufrufen müssen relativ zu diesem Workspace sein oder absolute Pfade verwenden, die mit diesem Pfad beginnen.
Wenn der User auf eine bestimmte Zeile in einer Datei verweist (z.B. `[Datei: foo.txt:11]`), zähle Leerzeilen immer mit. Verwende `cat -n` oder ein ähnliches Tool um Zeilennummern exakt zu bestimmen - NIEMALS Leerzeilen beim Zählen überspringen."
});
}
if (!string.IsNullOrWhiteSpace(session.Context))
blocks.Add(new PromptBlock { Icon = "fa-info-circle", Label = "Session · Kontext", Content = $"## Kontext\n{session.Context}" });
// Skills
if (skillSlugs != null && skillService != null)
{
var skillParts = new List<string>();
foreach (var slug in skillSlugs)
{
var skillPrompt = skillService.GetSkillPrompt(slug);
if (string.IsNullOrEmpty(skillPrompt)) continue;
var skill = skillService.GetSkill(slug);
var header = skill != null ? $"### {skill.Name}" : $"### {slug}";
if (skill?.Version != null) header += $" (v{skill.Version})";
header += $"\nPfad: `{skill?.Path ?? slug}`";
skillParts.Add($"{header}\n\n{skillPrompt}");
}
if (skillParts.Count > 0)
{
var skillNames = new List<string>();
foreach (var slug in skillSlugs)
{
var skill = skillService.GetSkill(slug);
if (skill != null && skillService.GetSkillPrompt(slug) != null)
skillNames.Add(skill.Name ?? slug);
}
var skillLabel = "Agent · Skills: " + string.Join(", ", skillNames);
var skillSection = "## Skills\nDu hast folgende Skills zur Verfügung. Nutze sie über das `execute`-Tool wenn der User etwas anfragt, das ein Skill abdeckt.\n\n"
+ string.Join("\n\n---\n\n", skillParts);
blocks.Add(new PromptBlock { Icon = "fa-puzzle-piece", Label = skillLabel, Content = skillSection });
}
}
// Memory
if (agent.UseMemory && memoryService != null && !string.IsNullOrWhiteSpace(agent.AgentId))
{
var memoryLines = new List<string>
{
"## Memory",
"Du hast ein persistentes Gedächtnis über Memory-Tools (`memory_read`, `memory_write`, `memory_list`).",
$"Dein Memory-Verzeichnis: `{agentMemoryDir ?? $"data/agents/{agent.AgentId}/"}`",
"",
"### Regeln",
"- Nutze `memory_read` um gespeicherte Erinnerungen abzurufen, bevor du über vergangene Entscheidungen, Präferenzen oder Fakten sprichst.",
"- Nutze `memory_write` um wichtige Informationen dauerhaft zu speichern – z.B. Entscheidungen, Präferenzen des Users, Projekt-Kontext.",
"- `MEMORY.md` ist dein Haupt-Memory-File für kuratierte, dauerhafte Informationen.",
"- Nutze Unterordner `memory/` für Tages-Logs oder thematische Notizen (z.B. `memory/2025-07-13.md`).",
"- Speichere proaktiv wenn der User etwas Wichtiges sagt oder eine Entscheidung getroffen wird.",
"- Wenn der User sagt \"merke dir das\" oder ähnliches, speichere es sofort."
};
var mainMemory = memoryService.GetMainMemory(agent.AgentId);
if (!string.IsNullOrEmpty(mainMemory))
{
memoryLines.Add("");
memoryLines.Add("### Aktueller Inhalt von MEMORY.md");
memoryLines.Add(mainMemory);
}
blocks.Add(new PromptBlock { Icon = "fa-database", Label = "Agent · Memory (UseMemory)", Content = string.Join("\n", memoryLines) });
}
// Git-Commit-Verhalten: relevant sobald Workspace vorhanden (unabhängig von ToolChoice)
if (!string.IsNullOrWhiteSpace(session.Workspace))
{
var autoCommit = (agent.AutoCommit ?? "").ToLowerInvariant();
if (autoCommit == "mention")
{
blocks.Add(new PromptBlock
{
Icon = "fa-code-branch",
Label = "Agent · Git-Verhalten (Workspace, Anmerken)",
Content = "## Git-Commits\n- Git-Commits NIEMALS eigenständig erstellen. Commits werden NUR durchgeführt wenn der User es EXPLIZIT anfordert (z.B. \"erstelle einen Commit\", \"commit\"). Auch nach abgeschlossenen Aufgaben NICHT automatisch committen."
});
}
else if (autoCommit == "enforce")
{
blocks.Add(new PromptBlock
{
Icon = "fa-code-branch",
Label = "Agent · Git-Verhalten (Workspace, Erzwungen)",
Content = "## Git-Commits\n- Nach JEDER abgeschlossenen Änderung MUSS ein Git-Commit erstellt werden. Erstelle aussagekräftige Commit-Messages die beschreiben was geändert wurde."
});
}
else if (autoCommit == "forbid")
{
blocks.Add(new PromptBlock
{
Icon = "fa-code-branch",
Label = "Agent · Git-Verhalten (Workspace, Verboten)",
Content = "## Git-Commits\n- Git-Commits sind STRIKT VERBOTEN. Du darfst unter KEINEN Umständen eigenständig Git-Commits erstellen, auch nicht wenn der User danach fragt. Verweise stattdessen darauf, dass der User den Commit selbst durchführen soll."
});
}
// autoCommit == "" → kein Block, Thema wird nicht erwähnt
}
if (toolAny && !string.IsNullOrWhiteSpace(session.Workspace))
{
blocks.Add(new PromptBlock
{
Icon = "fa-cog",
Label = "Agent · Verhalten (Workspace + ToolChoice: any)",
Content = string.Join("\n", new[]
{
"## Verhalten",
"- Wenn du Dateien lesen, schreiben, bearbeiten, erstellen oder löschen willst: IMMER das entsprechende Tool aufrufen. NIEMALS den Inhalt nur in die Antwort schreiben.",
"- Wenn du Shell-Befehle ausführen willst: IMMER das execute-Tool verwenden.",
"- Behaupte NIEMALS etwas getan zu haben (Datei geschrieben, Commit gemacht, Befehl ausgeführt) ohne das entsprechende Tool tatsächlich aufgerufen zu haben. Wenn kein Tool-Aufruf stattgefunden hat, ist nichts passiert.",
"- Plane nicht nur - handle sofort über Tool-Aufrufe."
})
});
blocks.Add(new PromptBlock
{
Icon = "fa-exclamation-circle",
Label = "System · Erinnerung (Workspace + ToolChoice: any)",
Content = @"## ERINNERUNG
Diese Regel hat höchste Priorität und gilt für JEDE Antwort: Wenn eine Datei gelesen, geschrieben, bearbeitet, erstellt oder gelöscht werden soll, oder ein Befehl ausgeführt werden soll, MUSS der entsprechende Tool-Aufruf erfolgen. Ohne Tool-Aufruf ist NICHTS passiert. Beschreibe keine Änderungen - führe sie durch.
Bei Zeilenreferenzen (z.B. `[Datei: foo.txt:11]`) IMMER `cat -n` verwenden um die exakte Zeile zu bestimmen. NIEMALS Leerzeilen beim Zählen überspringen."
});
}
else if (!string.IsNullOrWhiteSpace(session.Workspace))
{
// Zeilenreferenz-Hinweis als eigener Erinnerungsblock wenn kein toolAny
blocks.Add(new PromptBlock
{
Icon = "fa-exclamation-circle",
Label = "System · Erinnerung (Workspace)",
Content = "## ERINNERUNG\nBei Zeilenreferenzen (z.B. `[Datei: foo.txt:11]`) IMMER `cat -n` verwenden um die exakte Zeile zu bestimmen. NIEMALS Leerzeilen beim Zählen überspringen."
});
}
var marked = markedMessages?.Where(m => !string.IsNullOrWhiteSpace(m.Content)).ToList();
if (marked != null && marked.Count > 0)
{
var lines = new List<string> { "## Markierte Nachrichten\nDer User hat folgende Nachrichten als wichtig markiert:" };
foreach (var m in marked)
{
var role = m.Role == "user" ? "User" : "Assistant";
lines.Add($"- [{role}]: {m.Content.Trim()}");
}
blocks.Add(new PromptBlock { Icon = "fa-star", Label = "Markierte Nachrichten", Content = string.Join("\n", lines) });
}
return blocks;
}
public static string Build(AgentRecord agent, SessionRecord session, IEnumerable<MessageRecord> markedMessages = null, MemoryService memoryService = null, string agentMemoryDir = null, string dataDir = null, IEnumerable<string> skillSlugs = null, SkillService skillService = null)
{
var blocks = BuildBlocks(agent, session, markedMessages, memoryService, agentMemoryDir, dataDir, skillSlugs, skillService);
return string.Join("\n\n", blocks.Select(b => b.Content));
}
}