Tool-Nutzung: tool_choice pro Agent konfigurierbar (default: any), wiederholte Instruktionen im System-Prompt, MaxIterations in appsettings

This commit is contained in:
Meleki committed 2026-02-28 19:33:41 +01:00
1 parent 8c9f54da79
commit ff67542ba6
10 files changed
+59 -9

No files matched your search

+3 -1
View File
@@ -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);
+4 -1
View File
@@ -151,11 +151,14 @@ public static class ChatEndpoint
if (!string.IsNullOrEmpty(workspace))
Directory.CreateDirectory(workspace);
var maxIterations = config.GetValue<int>("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
{
+1
View File
@@ -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; }
+8 -1
View File
@@ -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);
+4 -3
View File
@@ -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);
}
+8 -2
View File
@@ -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);
}
+2 -1
View File
@@ -5,5 +5,6 @@
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
"AllowedHosts": "*",
"MaxIterations": 100
}
+22
View File
@@ -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);
+4
View File
@@ -126,6 +126,10 @@
<textarea id="agentPersona" rows="6" placeholder="Persönlichkeit / Seele des Agents"></textarea>
<label>Anweisungen</label>
<textarea id="agentInstructions" rows="6" placeholder="Technische Anweisungen"></textarea>
<label class="checkbox-label">
<input type="checkbox" id="agentToolChoice" checked>
Tool-Nutzung erzwingen <span class="checkbox-hint">(tool_choice: any)</span>
</label>
<div class="form-actions">
<button id="agentSaveBtn">Speichern</button>
<button id="agentCancelBtn">Abbrechen</button>
+3
View File
@@ -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
};