diff --git a/Endpoints/AgentEndpoint.cs b/Endpoints/AgentEndpoint.cs index 27ad5d0..674e917 100644 --- a/Endpoints/AgentEndpoint.cs +++ b/Endpoints/AgentEndpoint.cs @@ -49,7 +49,8 @@ public static class AgentEndpoint Description = body.TryGetProperty("description", out var d) ? d.GetString() : null, Model = body.TryGetProperty("model", out var m) ? m.GetString() ?? "sonnet" : "sonnet", Persona = body.TryGetProperty("persona", out var p) ? p.GetString() : null, - Instructions = body.TryGetProperty("instructions", out var i) ? i.GetString() : null + Instructions = body.TryGetProperty("instructions", out var i) ? i.GetString() : null, + ToolChoice = body.TryGetProperty("toolChoice", out var tc) ? tc.GetString() ?? "any" : "any" }; agent = await db.CreateAgent(agent); @@ -72,6 +73,7 @@ public static class AgentEndpoint if (body.TryGetProperty("model", out var m)) agent.Model = m.GetString(); if (body.TryGetProperty("persona", out var p)) agent.Persona = p.GetString(); if (body.TryGetProperty("instructions", out var i)) agent.Instructions = i.GetString(); + if (body.TryGetProperty("toolChoice", out var tc)) agent.ToolChoice = tc.GetString(); await db.UpdateAgent(agent); agent = await db.GetAgent(agentId); diff --git a/Endpoints/ChatEndpoint.cs b/Endpoints/ChatEndpoint.cs index 25eb4c0..1f83a33 100644 --- a/Endpoints/ChatEndpoint.cs +++ b/Endpoints/ChatEndpoint.cs @@ -151,11 +151,14 @@ public static class ChatEndpoint if (!string.IsNullOrEmpty(workspace)) Directory.CreateDirectory(workspace); + var maxIterations = config.GetValue("MaxIterations", 100); + var toolChoice = agent.ToolChoice ?? "any"; + var anthropic = new AnthropicService(httpFactory, token, toolService, db); streamingTracker.Start(sessionId, chainId); try { - await anthropic.StreamChat(res, modelId, systemPrompt, messages, chainId, workspace, sessionId, cancellationToken: cancellationToken); + await anthropic.StreamChat(res, modelId, systemPrompt, messages, chainId, workspace, sessionId, maxIterations: maxIterations, toolChoice: toolChoice, cancellationToken: cancellationToken); } finally { diff --git a/Models/AgentRecord.cs b/Models/AgentRecord.cs index fae3445..783e36d 100644 --- a/Models/AgentRecord.cs +++ b/Models/AgentRecord.cs @@ -10,6 +10,7 @@ public class AgentRecord public string Model { get; set; } // sonnet, opus public string Persona { get; set; } public string Instructions { get; set; } + public string ToolChoice { get; set; } // "any" oder "auto" public bool IsDeleted { get; set; } public string CreatedAt { get; set; } public string UpdatedAt { get; set; } diff --git a/Services/AnthropicService.cs b/Services/AnthropicService.cs index d460956..8a6ce51 100644 --- a/Services/AnthropicService.cs +++ b/Services/AnthropicService.cs @@ -35,7 +35,8 @@ public class AnthropicService string chainId, string workspace, string sessionId = null, - int maxIterations = 25, + int maxIterations = 100, + string toolChoice = "any", CancellationToken cancellationToken = default) { var sw = System.Diagnostics.Stopwatch.StartNew(); @@ -67,7 +68,13 @@ public class AnthropicService }; if (!string.IsNullOrEmpty(workspace)) + { payload["tools"] = _toolService.GetToolDefinitions(); + // "any" erzwingt Tool-Nutzung, aber nur in der ersten Iteration, + // damit Claude nicht endlos Tools aufruft wenn die Aufgabe erledigt ist. + var effectiveChoice = (toolChoice == "any" && iterations == 1) ? "any" : "auto"; + payload["tool_choice"] = new { type = effectiveChoice }; + } var client = CreateClient(); var json = JsonSerializer.Serialize(payload); diff --git a/Services/DatabaseService.cs b/Services/DatabaseService.cs index 3da774a..218b50b 100644 --- a/Services/DatabaseService.cs +++ b/Services/DatabaseService.cs @@ -126,6 +126,7 @@ public class DatabaseService TryAddColumn(conn, "chains", "cache_read_tokens", "INTEGER NOT NULL DEFAULT 0"); TryAddColumn(conn, "messages", "attachment_count", "INTEGER NOT NULL DEFAULT 0"); TryAddColumn(conn, "messages", "attachment_names", "TEXT"); + TryAddColumn(conn, "agents", "tool_choice", "TEXT DEFAULT 'any'"); } private void TryAddColumn(SqliteConnection conn, string table, string column, string type) @@ -144,8 +145,8 @@ public class DatabaseService agent.CreatedAt = now; agent.UpdatedAt = now; await conn.ExecuteAsync(@" - INSERT INTO agents (agent_id, name, avatar, description, model, persona, instructions, is_deleted, created_at, updated_at) - VALUES (@AgentId, @Name, @Avatar, @Description, @Model, @Persona, @Instructions, 0, @CreatedAt, @UpdatedAt) + INSERT INTO agents (agent_id, name, avatar, description, model, persona, instructions, tool_choice, is_deleted, created_at, updated_at) + VALUES (@AgentId, @Name, @Avatar, @Description, @Model, @Persona, @Instructions, @ToolChoice, 0, @CreatedAt, @UpdatedAt) ", agent); return agent; } @@ -170,7 +171,7 @@ public class DatabaseService agent.UpdatedAt = DateTime.UtcNow.ToString("o"); await conn.ExecuteAsync(@" UPDATE agents SET name = @Name, avatar = @Avatar, description = @Description, - model = @Model, persona = @Persona, instructions = @Instructions, updated_at = @UpdatedAt + model = @Model, persona = @Persona, instructions = @Instructions, tool_choice = @ToolChoice, updated_at = @UpdatedAt WHERE agent_id = @AgentId ", agent); } diff --git a/Services/SystemPromptBuilder.cs b/Services/SystemPromptBuilder.cs index 438a36d..5e71378 100644 --- a/Services/SystemPromptBuilder.cs +++ b/Services/SystemPromptBuilder.cs @@ -18,7 +18,10 @@ public static class SystemPromptBuilder if (!string.IsNullOrWhiteSpace(session.Workspace)) { - parts.Add(@$"## Tools + 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. + +## 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}` @@ -34,7 +37,10 @@ Wenn der User auf eine bestimmte Zeile in einer Datei verweist (z.B. `[Datei: fo - 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."); +- Plane nicht nur - handle sofort über Tool-Aufrufe. + +## 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."); return string.Join("\n\n", parts); } diff --git a/appsettings.json b/appsettings.json index 10f68b8..e70debb 100755 --- a/appsettings.json +++ b/appsettings.json @@ -5,5 +5,6 @@ "Microsoft.AspNetCore": "Warning" } }, - "AllowedHosts": "*" + "AllowedHosts": "*", + "MaxIterations": 100 } diff --git a/wwwroot/css/form.css b/wwwroot/css/form.css index 0d4076b..06cc769 100644 --- a/wwwroot/css/form.css +++ b/wwwroot/css/form.css @@ -99,6 +99,28 @@ align-self: flex-start; } +/* ── Checkbox ── */ + +.checkbox-label { + display: flex; + align-items: center; + gap: var(--badge-spacing); + cursor: pointer; + margin-top: var(--box-spacing); +} + +.checkbox-label input[type="checkbox"] { + width: 18px; + height: 18px; + accent-color: var(--accent); + cursor: pointer; +} + +.checkbox-hint { + opacity: 0.5; + font-size: 0.85em; +} + .danger-btn { color: var(--dangerous); border-color: var(--dangerous); diff --git a/wwwroot/index.html b/wwwroot/index.html index 696c98a..0af9554 100644 --- a/wwwroot/index.html +++ b/wwwroot/index.html @@ -126,6 +126,10 @@ +
diff --git a/wwwroot/js/pages/agent-page.js b/wwwroot/js/pages/agent-page.js index 224bc9c..a9117fe 100644 --- a/wwwroot/js/pages/agent-page.js +++ b/wwwroot/js/pages/agent-page.js @@ -20,6 +20,7 @@ registerPage('agent', { const modelEl = document.getElementById('agentModel'); const personaEl = document.getElementById('agentPersona'); const instructionsEl = document.getElementById('agentInstructions'); + const toolChoiceEl = document.getElementById('agentToolChoice'); const avatarPreview = document.getElementById('avatarPreview'); const avatarInput = document.getElementById('avatarInput'); const avatarBtn = document.getElementById('avatarBtn'); @@ -37,6 +38,7 @@ registerPage('agent', { modelEl.value = agent.model || 'sonnet'; personaEl.value = agent.persona || ''; instructionsEl.value = agent.instructions || ''; + toolChoiceEl.checked = (agent.toolChoice || 'any') === 'any'; _agentAvatarData = agent.avatar || null; deleteBtn.hidden = false; @@ -92,6 +94,7 @@ registerPage('agent', { model: modelEl.value, persona: personaEl.value, instructions: instructionsEl.value, + toolChoice: toolChoiceEl.checked ? 'any' : 'auto', avatar: _agentAvatarData };