- Add SkillService: scans data/skills/ directory, parses _meta.json and SKILL.md frontmatter - Add SkillEndpoint: REST API for listing skills and managing agent-skill assignments - Add agent_skills DB table for agent-to-skill mapping (only relation in DB, skills live on filesystem) - Extend SystemPromptBuilder: inject assigned skills' SKILL.md content into system prompt - Extend ChatEndpoint + SessionEndpoint: load agent skills and pass to prompt builder - Frontend: skill checkboxes in agent form, API functions for skill management - Add form.css styling for skill list items
166 lines
8.1 KiB
C#
166 lines
8.1 KiB
C#
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using Gott.Models;
|
||
|
||
namespace Gott.Services;
|
||
|
||
public static class SystemPromptBuilder
|
||
{
|
||
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 parts = new List<string>();
|
||
|
||
// IDENTITY.md - Wer bin ich (ersetzt "Du bist {Name}.")
|
||
var hasIdentity = false;
|
||
if (agent.UseIdentity && memoryService != null && !string.IsNullOrWhiteSpace(agent.AgentId))
|
||
{
|
||
var identity = memoryService.ReadMemoryFile(agent.AgentId, "IDENTITY.md");
|
||
if (!string.IsNullOrEmpty(identity))
|
||
{
|
||
parts.Add(identity);
|
||
hasIdentity = true;
|
||
}
|
||
}
|
||
if (!hasIdentity && agent.UseIdentity)
|
||
parts.Add($"Du bist {agent.Name}.");
|
||
|
||
// SOUL.md - Persönlichkeit + Regeln
|
||
if (agent.UseSoul && memoryService != null && !string.IsNullOrWhiteSpace(agent.AgentId))
|
||
{
|
||
var soul = memoryService.ReadMemoryFile(agent.AgentId, "SOUL.md");
|
||
if (!string.IsNullOrEmpty(soul))
|
||
parts.Add(soul);
|
||
}
|
||
|
||
// USER.md - Global (aus data-Ordner)
|
||
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);
|
||
parts.Add($"{userContent}\n\n_Pfad: `{absolutePath}`_");
|
||
}
|
||
}
|
||
}
|
||
|
||
bool toolAny = string.Equals(agent.ToolChoice, "any", System.StringComparison.OrdinalIgnoreCase);
|
||
|
||
if (!string.IsNullOrWhiteSpace(session.Workspace))
|
||
{
|
||
if (toolAny)
|
||
{
|
||
parts.Add(@$"## 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.");
|
||
}
|
||
|
||
parts.Add(@$"## 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))
|
||
parts.Add($"## 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 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);
|
||
parts.Add(skillSection);
|
||
}
|
||
}
|
||
|
||
// Memory-System
|
||
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);
|
||
}
|
||
|
||
parts.Add(string.Join("\n", memoryLines));
|
||
}
|
||
|
||
if (toolAny)
|
||
{
|
||
var verhaltensRegeln = new List<string>
|
||
{
|
||
"## 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."
|
||
};
|
||
|
||
if (agent.AutoCommit)
|
||
{
|
||
verhaltensRegeln.Add(@"- Nach jeder abgeschlossenen Änderung MUSS ein Git-Commit erstellt werden. Prüfe Änderungen mit `git diff`, erstelle einen passenden Commit mit einer aussagekräftigen Message. Mehrere zusammenhängende Änderungen dürfen in einem Commit zusammengefasst werden.");
|
||
}
|
||
else
|
||
{
|
||
verhaltensRegeln.Add(@"- 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.");
|
||
}
|
||
|
||
parts.Add(string.Join("\n", verhaltensRegeln));
|
||
|
||
parts.Add(@"## 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.");
|
||
}
|
||
|
||
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()}");
|
||
}
|
||
parts.Add(string.Join("\n", lines));
|
||
}
|
||
|
||
return string.Join("\n\n", parts);
|
||
}
|
||
}
|