feat: Agent-Einstellung 'Rückfragen erlauben' (allowUserChoice)

- Neues Feld AllowUserChoice (bool, default true) im AgentRecord
- DB-Migration: allow_user_choice Spalte mit DEFAULT 1
- Agent-Form: Checkbox 'Rückfragen erlauben' nach 'Tool-Nutzung erzwingen'
- ToolService.GetToolDefinitions: neuer Parameter includeUserChoice
- ask_user_choice Tool wird nur eingebunden wenn Agent es erlaubt
- Review-Agents und Non-Streaming (Cronjobs) erhalten nie ask_user_choice
- ChatEndpoint reicht Agent.AllowUserChoice an StreamChat durch
This commit is contained in:
Meleki committed 2026-03-07 11:36:02 +01:00
1 parent 5f3e308975
commit 89ceb61948
10 files changed
+26 -36

No files matched your search

+2
View File
@@ -65,6 +65,7 @@ public static class AgentEndpoint
UseMemory = !body.TryGetProperty("useMemory", out var um) || um.GetBoolean(),
UseWebSearch = body.TryGetProperty("webSearchVersion", out var wsv) && !string.IsNullOrEmpty(wsv.GetString()),
WebSearchVersion = body.TryGetProperty("webSearchVersion", out var wsv2) ? wsv2.GetString() ?? "" : "",
AllowUserChoice = !body.TryGetProperty("allowUserChoice", out var auc) || auc.GetBoolean(),
AvatarScale = body.TryGetProperty("avatarScale", out var asc) ? asc.GetDouble() : 1.0,
AvatarOffsetX = body.TryGetProperty("avatarOffsetX", out var aox) ? aox.GetDouble() : 0,
AvatarOffsetY = body.TryGetProperty("avatarOffsetY", out var aoy) ? aoy.GetDouble() : 0
@@ -103,6 +104,7 @@ public static class AgentEndpoint
agent.WebSearchVersion = wsv.GetString() ?? "";
agent.UseWebSearch = !string.IsNullOrEmpty(agent.WebSearchVersion);
}
if (body.TryGetProperty("allowUserChoice", out var auc)) agent.AllowUserChoice = auc.GetBoolean();
if (body.TryGetProperty("avatarScale", out var asc)) agent.AvatarScale = asc.GetDouble();
if (body.TryGetProperty("avatarOffsetX", out var aox)) agent.AvatarOffsetX = aox.GetDouble();
if (body.TryGetProperty("avatarOffsetY", out var aoy)) agent.AvatarOffsetY = aoy.GetDouble();
+1 -1
View File
@@ -132,7 +132,7 @@ public static class ChatEndpoint
try
{
await anthropic.StreamChat(res, ctx.ModelId, ctx.SystemPrompt, messages, ctx.ChainId, ctx.Workspace, sessionId, agentId: ctx.AgentId, useMemory: ctx.UseMemory, webSearchVersion: ctx.WebSearchVersion, maxIterations: ctx.MaxIterations, toolChoice: effectiveToolChoice, review: ctx.Review, finalizer: ctx.Finalizer, cancellationToken: linkedCts.Token);
await anthropic.StreamChat(res, ctx.ModelId, ctx.SystemPrompt, messages, ctx.ChainId, ctx.Workspace, sessionId, agentId: ctx.AgentId, useMemory: ctx.UseMemory, webSearchVersion: ctx.WebSearchVersion, maxIterations: ctx.MaxIterations, toolChoice: effectiveToolChoice, review: ctx.Review, finalizer: ctx.Finalizer, allowUserChoice: ctx.Agent?.AllowUserChoice ?? true, cancellationToken: linkedCts.Token);
}
finally
{
+1
View File
@@ -17,6 +17,7 @@ public class AgentRecord
public bool UseMemory { get; set; } = true;
public bool UseWebSearch { get; set; }
public string WebSearchVersion { get; set; } = "";
public bool AllowUserChoice { get; set; } = true;
public double AvatarScale { get; set; } = 1.0;
public double AvatarOffsetX { get; set; }
public double AvatarOffsetY { get; set; }
+4 -3
View File
@@ -49,6 +49,7 @@ public class AnthropicService
string toolChoice = "any",
ReviewContext review = null,
FinalizerContext finalizer = null,
bool allowUserChoice = true,
CancellationToken cancellationToken = default)
{
var sw = System.Diagnostics.Stopwatch.StartNew();
@@ -92,7 +93,7 @@ public class AnthropicService
{
var tools = new List<object>();
if (hasTools)
tools.AddRange(_toolService.GetToolDefinitions(includeFileTools: hasWorkspace, includeMemory: includeMemory));
tools.AddRange(_toolService.GetToolDefinitions(includeFileTools: hasWorkspace, includeMemory: includeMemory, includeUserChoice: allowUserChoice));
if (hasWebSearch)
tools.Add(new { type = webSearchVersion, name = "web_search", max_uses = 5 });
payload["tools"] = tools;
@@ -543,7 +544,7 @@ public class AnthropicService
{
var tools = new List<object>();
if (hasTools)
tools.AddRange(_toolService.GetToolDefinitions(includeFileTools: hasWorkspace, includeMemory: includeMemory));
tools.AddRange(_toolService.GetToolDefinitions(includeFileTools: hasWorkspace, includeMemory: includeMemory, includeUserChoice: false));
if (hasWebSearch)
tools.Add(new { type = webSearchVersion, name = "web_search", max_uses = 5 });
payload["tools"] = tools;
@@ -768,7 +769,7 @@ public class AnthropicService
{
var tools = new List<object>();
if (hasTools)
tools.AddRange(_toolService.GetToolDefinitions(includeFileTools: hasWorkspace, includeMemory: includeMemory));
tools.AddRange(_toolService.GetToolDefinitions(includeFileTools: hasWorkspace, includeMemory: includeMemory, includeUserChoice: false));
if (hasWebSearch)
tools.Add(new { type = ctx.WebSearchVersion, name = "web_search", max_uses = 5 });
payload["tools"] = tools;
+1 -1
View File
@@ -73,7 +73,7 @@ public class ChatService
var includeMemory = agent.UseMemory && !string.IsNullOrEmpty(agent.AgentId);
var toolDefs = (hasWorkspace || includeMemory)
? _toolService.GetToolDefinitions(includeFileTools: hasWorkspace, includeMemory: includeMemory)
? _toolService.GetToolDefinitions(includeFileTools: hasWorkspace, includeMemory: includeMemory, includeUserChoice: agent.AllowUserChoice)
: Array.Empty<object>();
var toolNames = toolDefs.Length > 0
? JsonSerializer.Serialize(toolDefs.Select(td =>
+4 -2
View File
@@ -178,6 +178,7 @@ public class DatabaseService
TryAddColumn(conn, "agents", "avatar_offset_x", "REAL NOT NULL DEFAULT 0");
TryAddColumn(conn, "agents", "avatar_offset_y", "REAL NOT NULL DEFAULT 0");
TryAddColumn(conn, "sessions", "finalizer_agent_id", "TEXT");
TryAddColumn(conn, "agents", "allow_user_choice", "INTEGER NOT NULL DEFAULT 1");
conn.Execute(@"
CREATE TABLE IF NOT EXISTS agent_skills (
@@ -330,8 +331,8 @@ public class DatabaseService
agent.CreatedAt = now;
agent.UpdatedAt = now;
await conn.ExecuteAsync(@"
INSERT INTO agents (agent_id, user_id, name, avatar, description, model, tool_choice, custom_commands, auto_commit, use_soul, use_identity, use_user_info, use_memory, use_web_search, web_search_version, avatar_scale, avatar_offset_x, avatar_offset_y, is_deleted, created_at, updated_at)
VALUES (@AgentId, @UserId, @Name, @Avatar, @Description, @Model, @ToolChoice, @CustomCommands, @AutoCommit, @UseSoul, @UseIdentity, @UseUserInfo, @UseMemory, @UseWebSearch, @WebSearchVersion, @AvatarScale, @AvatarOffsetX, @AvatarOffsetY, 0, @CreatedAt, @UpdatedAt)
INSERT INTO agents (agent_id, user_id, name, avatar, description, model, tool_choice, custom_commands, auto_commit, use_soul, use_identity, use_user_info, use_memory, use_web_search, web_search_version, allow_user_choice, avatar_scale, avatar_offset_x, avatar_offset_y, is_deleted, created_at, updated_at)
VALUES (@AgentId, @UserId, @Name, @Avatar, @Description, @Model, @ToolChoice, @CustomCommands, @AutoCommit, @UseSoul, @UseIdentity, @UseUserInfo, @UseMemory, @UseWebSearch, @WebSearchVersion, @AllowUserChoice, @AvatarScale, @AvatarOffsetX, @AvatarOffsetY, 0, @CreatedAt, @UpdatedAt)
", agent);
return agent;
}
@@ -359,6 +360,7 @@ public class DatabaseService
model = @Model, tool_choice = @ToolChoice,
custom_commands = @CustomCommands, auto_commit = @AutoCommit,
use_soul = @UseSoul, use_identity = @UseIdentity, use_user_info = @UseUserInfo, use_memory = @UseMemory, use_web_search = @UseWebSearch, web_search_version = @WebSearchVersion,
allow_user_choice = @AllowUserChoice,
avatar_scale = @AvatarScale, avatar_offset_x = @AvatarOffsetX, avatar_offset_y = @AvatarOffsetY, updated_at = @UpdatedAt
WHERE agent_id = @AgentId AND user_id = @UserId
", agent);
+3 -3
View File
@@ -17,11 +17,11 @@ public class ToolService
{
public const string AskUserChoiceToolName = "ask_user_choice";
public object[] GetToolDefinitions(bool includeFileTools = false, bool includeMemory = false)
public object[] GetToolDefinitions(bool includeFileTools = false, bool includeMemory = false, bool includeUserChoice = true)
{
var allTools = new List<object>();
// ask_user_choice ist immer verfügbar wenn Tools aktiv sind
allTools.Add(GetAskUserChoiceTool());
if (includeUserChoice)
allTools.Add(GetAskUserChoiceTool());
if (includeFileTools)
allTools.AddRange(GetFileTools());
if (includeMemory)
+3 -26
View File
@@ -1145,15 +1145,6 @@
max-width: 80%;
display: flex;
flex-direction: column;
gap: var(--box-spacing);
}
.choice-question {
font-size: 0.95em;
}
.choice-question p {
margin: 0;
}
.choice-buttons {
@@ -1163,24 +1154,15 @@
}
.choice-btn {
padding: 0.45em 1em;
border: var(--border-width) solid var(--primary);
border-radius: var(--border-radius);
background: transparent;
color: var(--primary);
font-size: 0.9em;
cursor: pointer;
transition: all 0.15s ease;
font-family: inherit;
}
.choice-btn:hover:not(:disabled) {
background: var(--primary);
color: var(--primary-text);
}
.choice-btn:active:not(:disabled) {
transform: scale(0.97);
color: var(--f1-bg);
}
.choice-btn:disabled {
@@ -1189,17 +1171,12 @@
.choice-btn.choice-selected {
background: var(--primary);
color: var(--primary-text);
color: var(--f1-bg);
border-color: var(--primary);
font-weight: 600;
}
.choice-btn.choice-unselected {
opacity: 0.4;
opacity: var(--semi-transparent);
border-color: var(--f1-border);
color: var(--muted);
}
.choice-answered .choice-question {
opacity: 0.7;
}
+4
View File
@@ -193,6 +193,10 @@
<input type="checkbox" id="agentToolChoice">
Tool-Nutzung erzwingen <span class="checkbox-hint">(tool_choice: any)</span>
</label>
<label class="checkbox-label">
<input type="checkbox" id="agentAllowUserChoice">
Rückfragen erlauben <span class="checkbox-hint">(ask_user_choice Tool)</span>
</label>
<label>Git-Commit-Verhalten</label>
<select id="agentAutoCommit">
<option value="">Nicht erwähnen</option>
+3
View File
@@ -18,6 +18,7 @@ registerPage('agent', {
const descEl = document.getElementById('agentDescription');
const modelEl = document.getElementById('agentModel');
const toolChoiceEl = document.getElementById('agentToolChoice');
const allowUserChoiceEl = document.getElementById('agentAllowUserChoice');
const autoCommitEl = document.getElementById('agentAutoCommit');
const useSoulEl = document.getElementById('agentUseSoul');
const useIdentityEl = document.getElementById('agentUseIdentity');
@@ -49,6 +50,7 @@ registerPage('agent', {
descEl.value = agent.description || '';
modelEl.value = agent.model || 'sonnet';
toolChoiceEl.checked = (agent.toolChoice || 'any') === 'any';
allowUserChoiceEl.checked = agent.allowUserChoice !== false;
autoCommitEl.value = agent.autoCommit || '';
useSoulEl.checked = agent.useSoul !== false;
useIdentityEl.checked = agent.useIdentity !== false;
@@ -94,6 +96,7 @@ registerPage('agent', {
description: descEl.value.trim(),
model: modelEl.value,
toolChoice: toolChoiceEl.checked ? 'any' : 'auto',
allowUserChoice: allowUserChoiceEl.checked,
autoCommit: autoCommitEl.value,
useSoul: useSoulEl.checked,
useIdentity: useIdentityEl.checked,