From f3b649164364f306873279be013364f89700821a Mon Sep 17 00:00:00 2001 From: Meleki Date: Sun, 8 Mar 2026 00:50:29 +0100 Subject: [PATCH] =?UTF-8?q?Review-Agent:=20nur=20Text-Konversation=20?= =?UTF-8?q?=C3=BCbergeben=20+=20Doku=20aktualisiert?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BuildReviewMessages() komplett umgebaut: - Tool-Calls und Tool-Results werden nicht mehr übergeben - Nur noch die reine Text-Konversation (user/assistant) wird extrahiert - Aufeinanderfolgende Messages gleicher Rolle werden zusammengeführt - Korrekte user/assistant-Abwechslung sichergestellt - Fallback wenn nur Tool-Calls ohne Text vorhanden - Unnötigen Serialisierungs-Roundtrip für Rollen-Prüfung entfernt Erwartete Token-Ersparnis: 50-100k+ Input-Tokens pro Review-Runde bei typischen Worker-Sessions mit vielen write_file/edit_file-Calls. ARCHITECTURE.md: - Inhaltsverzeichnis-Nummerierung korrigiert (22-28 statt doppelt 20-21) - ServerSettingsEndpoint.cs in Projektstruktur ergänzt - Tabellenbeschreibungen vervollständigt (users, agents, sessions, chains, messages, cronjobs) mit allen fehlenden Feldern - ChainRecord/MessageRecord/SessionWithStats Felder aktualisiert - AutoCommit mention-Beschreibung korrigiert ARCHITECTURE_QUICK.md: - ToolService 12 → 13 Tools korrigiert - Tabellenbeschreibungen analog zur Hauptdoku aktualisiert --- ARCHITECTURE.md | 47 +++++++++------ ARCHITECTURE_QUICK.md | 10 ++-- Services/AnthropicService.cs | 108 ++++++++++++++++++++++++++--------- 3 files changed, 114 insertions(+), 51 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 27f539b..2927962 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -33,13 +33,13 @@ 19. [Interaktive Rückfragen (ask_user_choice)](#interaktive-rückfragen-ask_user_choice) 20. [Plan-Modus](#plan-modus) 21. [Nachrichten teilen (Shared Messages)](#nachrichten-teilen-shared-messages) -20. [Workspace & File-Explorer](#workspace--file-explorer) -21. [Avatar-System](#avatar-system) -22. [LaTeX/Math-Rendering](#latexmath-rendering) -23. [Windows Desktop App](#windows-desktop-app) -24. [Deployment](#deployment) -25. [Konfiguration](#konfiguration) -26. [Datenbank-Migrationen](#datenbank-migrationen) +22. [Workspace & File-Explorer](#workspace--file-explorer) +23. [Avatar-System](#avatar-system) +24. [LaTeX/Math-Rendering](#latexmath-rendering) +25. [Windows Desktop App](#windows-desktop-app) +26. [Deployment](#deployment) +27. [Konfiguration](#konfiguration) +28. [Datenbank-Migrationen](#datenbank-migrationen) --- @@ -143,6 +143,7 @@ gott/ │ ├── SkillEndpoint.cs # List, Agent-Skill-Mapping, Output-Files │ ├── SharedEndpoint.cs # Nachrichten teilen (öffentliche Links) │ ├── AdminEndpoint.cs # Admin: User-CRUD, Impersonate (Admin-only) +│ ├── ServerSettingsEndpoint.cs # Admin: Server-Settings lesen/schreiben (Admin-only) │ ├── ProfileEndpoint.cs # Profil, Persona, Passwort │ ├── SettingsEndpoint.cs # User-Settings (JSON blob) │ ├── BackupEndpoint.cs # Backup als tar.gz Download @@ -277,16 +278,16 @@ Message (1) ─── (*) SharedMessage | Tabelle | Beschreibung | |-------------------|-----------------------------------------------------| -| `users` | Benutzer (username, password_hash, persona, settings, default_path) | +| `users` | Benutzer (username, password_hash, display_name, is_admin, persona, settings, default_path) | | `tokens` | Auth-Tokens (token → user_id) | -| `agents` | AI-Agenten (model, soul, identity, memory, tools, avatar_scale, avatar_offset_x/y, allow_user_choice, task_instruction, avatar_small) | -| `sessions` | Chat-Sessions (workspace, context, telegram-config, avatar_scale, avatar_offset_x/y, telegram_allowed_chat_ids, preparer_agent_id, review_agent_id, max_review_iterations, finalizer_agent_id, image_small) | -| `chains` | Request-Chains (model, tokens, iterations, timing) | -| `messages` | Einzelne Nachrichten (text, tool_use, tool_result, web_search_result) | +| `agents` | AI-Agenten (model, tool_choice, custom_commands, auto_commit, use_soul/identity/user_info/memory, use_web_search, web_search_version, allow_user_choice, task_instruction, avatar_scale/offset, avatar_small) | +| `sessions` | Chat-Sessions (description, image, image_small, workspace, context, custom_commands, telegram-config, telegram_allowed_chat_ids, avatar_scale/offset, preparer_agent_id, review_agent_id, max_review_iterations, finalizer_agent_id) | +| `chains` | Request-Chains (model, tokens, iterations, timing, tool_choice, tool_names, stream_error_info, plan_mode, archive_id) | +| `messages` | Einzelne Nachrichten (text, tool_use, tool_result, error, web_search_result), Tag für Sub-Agent-Markierung | | `attachments` | Datei-Anhänge (base64, image/document) | | `archives` | Archivierte Chain-Gruppen | | `agent_skills` | Agent ↔ Skill Zuordnung | -| `cronjobs` | Geplante Tasks (cron expression, prompt) | +| `cronjobs` | Geplante Tasks (cron expression, prompt, notify_session_id, is_enabled, last_run, next_run) | | `shared_messages` | Geteilte Nachrichten (Share-Token, Ablaufdatum) | | `telegram_links` | Telegram Chat ↔ Session Verknüpfung | @@ -295,26 +296,36 @@ Message (1) ─── (*) SharedMessage **ChainRecord** – Repräsentiert einen kompletten Request-Response-Zyklus: - `ChainId` – Eindeutige ID - `SessionId` – Zugehörige Session +- `AgentId` – Zugehöriger Agent - `Model` – Verwendetes Claude-Modell (z.B. `claude-sonnet-4-6`) - `SystemPrompt` – Der vollständige System-Prompt (gespeichert für Nachvollziehbarkeit) - `InputTokens`, `OutputTokens`, `CacheCreationTokens`, `CacheReadTokens` – Token-Verbrauch - `Iterations` – Anzahl der Tool-Loop-Durchläufe - `StopReason` – `end_turn`, `tool_use`, `max_iterations`, `error`, `client_disconnected`, `awaiting_user_choice`, `preparer_query` +- `ToolChoice` – Verwendete Tool-Choice-Strategie +- `ToolNames` – Komma-separierte Liste der verwendeten Tools +- `ArchiveId` – Zugehöriges Archiv (wenn archiviert) +- `StreamErrorInfo` – Fehlerdetails bei Stream-Abbrüchen - `PlanMode` – Ob die Chain im Plan-Modus erstellt wurde (informativ) - `DurationMs` – Gesamtdauer der Chain **MessageRecord** – Eine einzelne Nachricht innerhalb einer Chain: -- `Role` – `user`, `assistant`, `system` +- `Role` – `user`, `assistant` - `Type` – `text`, `tool_use`, `tool_result`, `error`, `web_search_result` - `Content` – Textinhalt oder JSON (bei tool_use: Input-JSON) - `ToolName`, `ToolCallId` – Bei Tool-Aufrufen -- `IsMarked` – Als wichtig markierte Nachricht (wird in System-Prompt aufgenommen) +- `IsError` – Ob die Nachricht ein Fehler ist +- `Iteration` – Welche Tool-Loop-Iteration +- `AttachmentCount` – Anzahl angehängter Dateien +- `AttachmentNames` – JSON-Array mit Dateinamen - `Tag` – Sub-Agent-Markierung (`null` = normal, `"preparer"/"review"/"finalizer"` = Sub-Agent-Message, wird aus API-History gefiltert) +- `IsMarked` – Als wichtig markierte Nachricht (wird in System-Prompt aufgenommen) - `DeletedOn` – Soft-Delete Zeitstempel **SessionWithStats** – Erweitertes Session-Objekt für Listen-Endpunkt: -- Enthält alle `SessionRecord`-Felder + `MessageCount`, `ArchiveCount` -- `GitStatus` – Inline Git-Status-Summary (`Add`, `Edit`, `Delete`, `Outgoing`) oder `null` wenn kein Workspace +- Enthält alle `SessionRecord`-Felder + `MessageCount`, `ArchiveMessageCount`, `TotalMessageCount` +- `TelegramConfigured`, `TelegramLinked` – Telegram-Status +- `GitStatus` – Inline Git-Status-Summary (`Add`, `Edit`, `Delete`, `Outgoing`) oder `null` wenn kein Workspace (wird im Endpoint hinzugefügt, nicht Teil des Records) - `Image` wird durch `ImageSmall` ersetzt (Thumbnail für Dashboard-Performance) **GitStatusSummary** – Kompakte Git-Status-Info: @@ -647,7 +658,7 @@ Ein **Agent** definiert die Persönlichkeit und Fähigkeiten der AI: | `Model` | `sonnet`, `sonnet-4-5`, `sonnet-4-6`, `opus`, `opus-4-5`, `opus-4-6`, `haiku`, `haiku-4-5` | | `ToolChoice` | `any` oder `auto` | | `CustomCommands` | JSON-Array mit Slash-Commands (Label + Prompt) | -| `AutoCommit` | `""` (nichts), `"mention"` (verbieten), `"enforce"` (erzwingen), `"forbid"` (strikt verboten) | +| `AutoCommit` | `""` (nichts), `"mention"` (anmerken – nur auf User-Request), `"enforce"` (erzwingen – nach jeder Änderung), `"forbid"` (strikt verboten) | | `UseSoul` | SOUL.md in System-Prompt einbinden | | `UseIdentity` | IDENTITY.md in System-Prompt einbinden | | `UseUserInfo` | User-Persona in System-Prompt einbinden | diff --git a/ARCHITECTURE_QUICK.md b/ARCHITECTURE_QUICK.md index 3b16c0e..d1c076f 100644 --- a/ARCHITECTURE_QUICK.md +++ b/ARCHITECTURE_QUICK.md @@ -63,11 +63,11 @@ User 1──* Agent 1──* Session 1──* Chain 1──* Message 1──* At | Tabelle | Zweck | |---------|-------| -| `users` | Login, Persona, Settings (JSON), DefaultPath | +| `users` | Login, DisplayName, IsAdmin, Persona, Settings (JSON), DefaultPath | | `tokens` | Auth-Tokens → User | -| `agents` | AI-Agenten (Model, Tools, Config, AvatarScale, AvatarOffsetX/Y, AvatarSmall) | -| `sessions` | Chat-Sessions (Workspace, Context, Telegram, AvatarScale, AvatarOffsetX/Y, AllowedChatIds, PreparerAgentId, ReviewAgentId, MaxReviewIterations, FinalizerAgentId, ImageSmall) | -| `chains` | Ein Request-Response-Zyklus (Tokens, Timing, Iterations, PlanMode) | +| `agents` | AI-Agenten (Model, ToolChoice, CustomCommands, AutoCommit, UseSoul/Identity/UserInfo/Memory, WebSearch, AllowUserChoice, TaskInstruction, AvatarScale/Offset, AvatarSmall) | +| `sessions` | Chat-Sessions (Description, Image, ImageSmall, Workspace, Context, CustomCommands, Telegram, AvatarScale/Offset, AllowedChatIds, PreparerAgentId, ReviewAgentId, MaxReviewIterations, FinalizerAgentId) | +| `chains` | Ein Request-Response-Zyklus (Tokens, Timing, Iterations, PlanMode, ToolChoice, ToolNames, StreamErrorInfo, ArchiveId) | | `messages` | Einzelne Nachrichten (text / tool_use / tool_result / error / web_search_result), Tag für Sub-Agent-Markierung | | `attachments` | Base64-Anhänge (Bilder, Dokumente) | | `archives` | Archivierte Chain-Gruppen | @@ -110,7 +110,7 @@ User 1──* Agent 1──* Session 1──* Chain 1──* Message 1──* At | **AuthService** | Token-Auth, Passwort-Hashing (SHA256) | | **ChatService** | Chat-Kontext aufbauen, History aus DB laden | | **AnthropicService** | Claude API: SSE Streaming + Non-Streaming | -| **ToolService** | Tool-Definitionen + Ausführung (12 Tools) | +| **ToolService** | Tool-Definitionen + Ausführung (13 Tools) | | **WorkspaceHelper** | Workspace-Pfad-Auflösung (relativ → absolut), Path-Traversal-Schutz (static) | | **SystemPromptBuilder** | System-Prompt aus modularen Blöcken zusammenbauen (static) | | **MemoryService** | Agent-Gedächtnis auf Dateisystem | diff --git a/Services/AnthropicService.cs b/Services/AnthropicService.cs index cd097fd..26c85e9 100644 --- a/Services/AnthropicService.cs +++ b/Services/AnthropicService.cs @@ -524,10 +524,13 @@ public class AnthropicService /// private List BuildReviewMessages(List workerMessages) { - // Worker-Konversation zusammenfassen - var summary = new StringBuilder(); - summary.AppendLine("Hier ist die bisherige Konversation zwischen User und Worker-Agent. Bitte überprüfe ob die Aufgabe korrekt erledigt wurde:\n"); - summary.AppendLine("---"); + // Nur die Text-Konversation extrahieren – Tool-Calls und Tool-Results weglassen. + // Der Review/Finalizer-Agent hat eigene Tools und kann bei Bedarf selbst Dateien lesen. + // Aufeinanderfolgende Messages gleicher Rolle werden zusammengeführt, + // da die Anthropic API abwechselnde user/assistant-Rollen erfordert. + var messages = new List(); + string lastRole = null; + var currentParts = new List(); foreach (var msg in workerMessages) { @@ -537,11 +540,15 @@ public class AnthropicService if (!el.TryGetProperty("role", out var role)) continue; var roleStr = role.GetString() ?? ""; + var textParts = new List(); + if (el.TryGetProperty("content", out var content)) { if (content.ValueKind == JsonValueKind.String) { - summary.AppendLine($"\n**{roleStr}:** {content.GetString()}"); + var s = content.GetString() ?? ""; + if (!string.IsNullOrWhiteSpace(s)) + textParts.Add(s); } else if (content.ValueKind == JsonValueKind.Array) { @@ -551,37 +558,82 @@ public class AnthropicService if (blockType == "text") { var text = block.TryGetProperty("text", out var t) ? t.GetString() ?? "" : ""; - summary.AppendLine($"\n**{roleStr}:** {text}"); - } - else if (blockType == "tool_use") - { - var toolName = block.TryGetProperty("name", out var tn) ? tn.GetString() ?? "" : ""; - var toolInput = block.TryGetProperty("input", out var ti) ? ti.ToString() : "{}"; - summary.AppendLine($"\n**{roleStr} [Tool: {toolName}]:** {toolInput}"); - } - else if (blockType == "tool_result") - { - var resultContent = block.TryGetProperty("content", out var rc) ? rc.ToString() : ""; - // Tool-Results kürzen für Review-Kontext - if (resultContent.Length > 2000) - resultContent = resultContent[..2000] + "\n[... gekürzt]"; - summary.AppendLine($"\n**{roleStr} [Tool-Result]:** {resultContent}"); + if (!string.IsNullOrWhiteSpace(text)) + textParts.Add(text); } } } } - } - summary.AppendLine("\n---"); - summary.AppendLine("\nBitte überprüfe ob die Aufgabe korrekt erledigt wurde. Du kannst dazu auch Tools verwenden (z.B. Dateien lesen). Antworte mit exakt [APPROVED] wenn alles korrekt ist, oder beschreibe was falsch ist und korrigiert werden muss."); - return new List + if (textParts.Count == 0) continue; + + if (roleStr == lastRole) + { + // Gleiche Rolle wie vorher → zusammenführen + currentParts.AddRange(textParts); + } + else + { + // Neue Rolle → vorherige Message abschließen + if (lastRole != null && currentParts.Count > 0) + { + messages.Add(new Dictionary + { + ["role"] = lastRole, + ["content"] = new List { new { type = "text", text = string.Join("\n\n", currentParts) } } + }); + } + lastRole = roleStr; + currentParts = new List(textParts); + } + } + + // Letzte angesammelte Message abschließen + if (lastRole != null && currentParts.Count > 0) { - new Dictionary + messages.Add(new Dictionary + { + ["role"] = lastRole, + ["content"] = new List { new { type = "text", text = string.Join("\n\n", currentParts) } } + }); + } + + // Anthropic erfordert: erste Message = user, letzte Message = user, + // und abwechselnde Rollen (durch die Zusammenführung oben bereits sichergestellt). + if (messages.Count > 0) + { + var first = (Dictionary)messages[0]; + if ((string)first["role"] == "assistant") + { + messages.Insert(0, new Dictionary + { + ["role"] = "user", + ["content"] = new List { new { type = "text", text = "Hier ist die bisherige Konversation. Bitte überprüfe den aktuellen Zustand." } } + }); + } + + var last = (Dictionary)messages[messages.Count - 1]; + if ((string)last["role"] == "assistant") + { + messages.Add(new Dictionary + { + ["role"] = "user", + ["content"] = new List { new { type = "text", text = "Bitte überprüfe ob die Aufgabe korrekt erledigt wurde." } } + }); + } + } + + // Falls keine Text-Messages vorhanden, Fallback mit Hinweis + if (messages.Count == 0) + { + messages.Add(new Dictionary { ["role"] = "user", - ["content"] = new List { new { type = "text", text = summary.ToString() } } - } - }; + ["content"] = new List { new { type = "text", text = "Die bisherige Konversation enthielt nur Tool-Aufrufe ohne Text. Bitte überprüfe den aktuellen Zustand des Workspaces mit deinen Tools." } } + }); + } + + return messages; } ///