Add per-agent reasoning effort
Opus 4.5+ and Sonnet 4.6+ support a new "effort" knob that controls how
much budget the model spends on thinking and tool calls. Plumb it through
end to end so each agent can pick its own level.
Storage & shape:
- New TEXT column agents.effort (default '') added via TryAddColumn.
- AgentRecord.Effort + INSERT/UPDATE in DatabaseService.Agents.cs.
- ChatContext.Effort + SubAgentContext.Effort so the value travels with the
request alongside model and tool settings.
Agent editor:
- Effort dropdown above git-commit behavior in the agent form (low/medium/
high/xhigh/max plus empty = "use default"). Always visible; on OpenAI
providers the value is sent but silently ignored.
- agent-page.js load/save and AgentEndpoint create/update wired up.
Anthropic API path:
- StreamChat and RunNonStreamingToolLoop add output_config: { effort } when
effort is set and not "high" – empty/null/"high" means "omit and let the
API pick its default".
- Sub-agents (preparer/review/finalizer) get the parameter through
RunAgentChat so each step uses its own agent's effort.
Anthropic CLI path:
- ClaudeCliService.ChatStreamingAsync takes an effort parameter and exports
CLAUDE_CODE_EFFORT_LEVEL on the spawned process. Env-var beats the
--effort flag because it also covers "max" and "ultracode".
- StreamChatViaCli, ChatNonStreamingViaCli, RunSubAgentViaCli, and the
ChatEndpoint anthropic branches all hand the value through.
ARCHITECTURE.md / ARCHITECTURE_QUICK.md updated with the new field.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
88fb3c9867
commit
3f913f14b5
15 files changed
+70
-17
No files matched your search
@@ -77,7 +77,7 @@ public partial class AnthropicService
|
||||
preparer.Workspace, preparer.UseMemory, preparer.WebSearchVersion,
|
||||
preparer.ToolChoice, preparerMessages, chainId, sessionId, apiToken, usage,
|
||||
0, "preparer", preparer.Agent?.Name ?? "Vorbereiter-Agent",
|
||||
cancellationToken, cancellationToken);
|
||||
cancellationToken, cancellationToken, preparer.Effort);
|
||||
}
|
||||
|
||||
var result = new PreparerResult { IterationCount = iterations, ResponseText = text };
|
||||
@@ -185,7 +185,7 @@ public partial class AnthropicService
|
||||
review.Workspace, review.UseMemory, review.WebSearchVersion,
|
||||
review.ToolChoice, reviewMessages, chainId, sessionId, apiToken, usage,
|
||||
baseIterations, "review", review.Agent?.Name ?? "Review-Agent",
|
||||
workCt, sseCt);
|
||||
workCt, sseCt, review.Effort);
|
||||
}
|
||||
|
||||
var result = new ReviewResult { ReviewIterationCount = iterations };
|
||||
@@ -256,7 +256,7 @@ public partial class AnthropicService
|
||||
finalizer.Workspace, finalizer.UseMemory, finalizer.WebSearchVersion,
|
||||
finalizer.ToolChoice, finalizerMessages, chainId, sessionId, apiToken, usage,
|
||||
baseIterations, "finalizer", finalizer.Agent?.Name ?? "Finalizer-Agent",
|
||||
workCt, sseCt);
|
||||
workCt, sseCt, finalizer.Effort);
|
||||
}
|
||||
|
||||
await SseHelper.TryWriteSse(response, "finalizer_done", new
|
||||
@@ -446,7 +446,8 @@ public partial class AnthropicService
|
||||
int baseIteration,
|
||||
int maxIterations,
|
||||
CancellationToken ct,
|
||||
NonStreamingLoopCallbacks cb = null)
|
||||
NonStreamingLoopCallbacks cb = null,
|
||||
string effort = null)
|
||||
{
|
||||
cb ??= new NonStreamingLoopCallbacks();
|
||||
int iterations = 0;
|
||||
@@ -465,6 +466,10 @@ public partial class AnthropicService
|
||||
["messages"] = messages
|
||||
};
|
||||
|
||||
// Reasoning-Effort (Opus 4.5+, Sonnet 4.6+). "" / "high" / null = Default weglassen.
|
||||
if (!string.IsNullOrEmpty(effort) && effort != "high")
|
||||
payload["output_config"] = new { effort };
|
||||
|
||||
if (systemPrompt is List<object> spBlocks && spBlocks.Count > 0)
|
||||
{
|
||||
// Cache-Breakpoint auf den letzten System-Prompt-Block (1h TTL).
|
||||
@@ -640,7 +645,8 @@ public partial class AnthropicService
|
||||
string agentType,
|
||||
string agentName,
|
||||
CancellationToken workCt,
|
||||
CancellationToken sseCt)
|
||||
CancellationToken sseCt,
|
||||
string effort = null)
|
||||
{
|
||||
return RunNonStreamingToolLoop(
|
||||
modelId, systemPrompt,
|
||||
@@ -657,7 +663,8 @@ public partial class AnthropicService
|
||||
SseResponse = response, SseCt = sseCt,
|
||||
SsePrefix = agentType, AgentName = agentName,
|
||||
Tag = agentType
|
||||
});
|
||||
},
|
||||
effort);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -722,6 +729,7 @@ public partial class AnthropicService
|
||||
systemPrompt: systemText.ToString(),
|
||||
workspace: sub.Workspace,
|
||||
hasWorkspace: !string.IsNullOrEmpty(sub.Workspace),
|
||||
effort: sub.Effort,
|
||||
cancellationToken: workCt
|
||||
);
|
||||
|
||||
@@ -785,7 +793,8 @@ public partial class AnthropicService
|
||||
CancellationToken = ct, VectorSearch = _vectorSearch, EmbeddingBg = _embeddingBg,
|
||||
Db = _db, ChatService = _chatService, AnthropicService = this, OpenAiService = _openAiService
|
||||
})
|
||||
});
|
||||
},
|
||||
ctx.Effort);
|
||||
|
||||
var finalStopReason = text.Length > 0 ? "end_turn" : "error";
|
||||
await FinalizeChain(chain, usage, iterations, finalStopReason, sw);
|
||||
|
||||
@@ -37,6 +37,7 @@ public partial class AnthropicService
|
||||
string unityMcpUrl = null,
|
||||
string unityMcpToken = null,
|
||||
bool useAutoContext = true,
|
||||
string effort = null,
|
||||
CancellationToken workCt = default,
|
||||
CancellationToken sseCt = default)
|
||||
{
|
||||
@@ -250,6 +251,7 @@ public partial class AnthropicService
|
||||
webSearch: webSearch,
|
||||
mcpConfigPath: mcpConfigPath,
|
||||
hasUnityMcp: !string.IsNullOrEmpty(unityMcpUrl),
|
||||
effort: effort,
|
||||
cancellationToken: workCt
|
||||
);
|
||||
|
||||
@@ -491,6 +493,7 @@ public partial class AnthropicService
|
||||
systemPrompt: systemText.ToString(),
|
||||
workspace: ctx.Workspace,
|
||||
hasWorkspace: !string.IsNullOrEmpty(ctx.Workspace),
|
||||
effort: ctx.Effort,
|
||||
cancellationToken: cancellationToken
|
||||
);
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ public partial class AnthropicService
|
||||
bool useAutoContext = true,
|
||||
bool useGitHubSearch = false,
|
||||
bool useSerperSearch = false,
|
||||
string effort = null,
|
||||
CancellationToken workCt = default,
|
||||
CancellationToken hardCt = default,
|
||||
CancellationToken sseCt = default,
|
||||
@@ -97,6 +98,11 @@ public partial class AnthropicService
|
||||
["stream"] = true
|
||||
};
|
||||
|
||||
// Reasoning-Effort (Opus 4.5+, Sonnet 4.6+). "" / "high" / null = Default weglassen.
|
||||
// Werte: low / medium / high / xhigh (Opus 4.7+) / max (Opus 4.6+, Sonnet 4.6).
|
||||
if (!string.IsNullOrEmpty(effort) && effort != "high")
|
||||
payload["output_config"] = new { effort };
|
||||
|
||||
if (systemPrompt != null && systemPrompt.Count > 0)
|
||||
{
|
||||
// Cache-Breakpoint auf den letzten System-Prompt-Block (1h TTL).
|
||||
|
||||
@@ -194,6 +194,7 @@ Für welche Plattform soll die App entwickelt werden? Web, Mobile oder Desktop?
|
||||
ToolChoice = toolChoice,
|
||||
UseMemory = agent.UseMemory,
|
||||
WebSearchVersion = agent.WebSearchVersion,
|
||||
Effort = agent.Effort,
|
||||
MaxIterations = maxIterations,
|
||||
Agent = agent,
|
||||
Session = session,
|
||||
@@ -880,6 +881,7 @@ Für welche Plattform soll die App entwickelt werden? Web, Mobile oder Desktop?
|
||||
UseMemory = agent.UseMemory,
|
||||
WebSearchVersion = agent.WebSearchVersion,
|
||||
ToolChoice = hasTools ? "any" : "auto",
|
||||
Effort = agent.Effort,
|
||||
Agent = agent
|
||||
};
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ public class ClaudeCliService
|
||||
return dir;
|
||||
}
|
||||
|
||||
private ProcessStartInfo CreateCliStartInfo(List<string> args, bool redirectInput = false)
|
||||
private ProcessStartInfo CreateCliStartInfo(List<string> args, bool redirectInput = false, string effort = null)
|
||||
{
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
@@ -75,6 +75,11 @@ public class ClaudeCliService
|
||||
// dieser Flag holt die 1h-TTL zurück (gilt für API-Key, Bedrock, Vertex, Foundry).
|
||||
psi.Environment["ENABLE_PROMPT_CACHING_1H"] = "1";
|
||||
|
||||
// Effort-Level via Env-Var setzen (höchste Priorität, deckt auch "max" und "ultracode" ab).
|
||||
// Das "--effort"-Flag unterstützt nur low/medium/high/xhigh – Env-Var deckt alles.
|
||||
if (!string.IsNullOrEmpty(effort) && effort != "high")
|
||||
psi.Environment["CLAUDE_CODE_EFFORT_LEVEL"] = effort;
|
||||
|
||||
foreach (var arg in args)
|
||||
psi.ArgumentList.Add(arg);
|
||||
|
||||
@@ -140,6 +145,7 @@ public class ClaudeCliService
|
||||
bool webSearch = false,
|
||||
string mcpConfigPath = null,
|
||||
bool hasUnityMcp = false,
|
||||
string effort = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!_isAvailable)
|
||||
@@ -223,14 +229,15 @@ public class ClaudeCliService
|
||||
args.Add(mcpConfigPath);
|
||||
}
|
||||
|
||||
return await RunCliStreamingAsync(args, prompt, onEvent, cancellationToken);
|
||||
return await RunCliStreamingAsync(args, prompt, onEvent, cancellationToken, effort);
|
||||
}
|
||||
|
||||
private async Task<CliChatResult> RunCliStreamingAsync(
|
||||
List<string> args,
|
||||
string stdinPrompt,
|
||||
Func<CliStreamEvent, Task> onEvent,
|
||||
CancellationToken cancellationToken)
|
||||
CancellationToken cancellationToken,
|
||||
string effort = null)
|
||||
{
|
||||
var result = new CliChatResult();
|
||||
var fullOutput = new StringBuilder();
|
||||
@@ -250,7 +257,7 @@ public class ClaudeCliService
|
||||
|
||||
try
|
||||
{
|
||||
var startInfo = CreateCliStartInfo(args, redirectInput: true);
|
||||
var startInfo = CreateCliStartInfo(args, redirectInput: true, effort: effort);
|
||||
|
||||
using var process = new Process { StartInfo = startInfo };
|
||||
|
||||
|
||||
@@ -19,8 +19,8 @@ public partial class DatabaseService
|
||||
agent.CreatedAt = now;
|
||||
agent.UpdatedAt = now;
|
||||
await conn.ExecuteAsync(@"
|
||||
INSERT INTO agents (agent_id, user_id, name, avatar, avatar_small, description, model, tool_choice, custom_commands, auto_commit, use_soul, use_identity, use_user_info, use_memory, use_auto_context, use_github_search, use_serper_search, use_web_search, web_search_version, allow_user_choice, task_instruction, avatar_scale, avatar_offset_x, avatar_offset_y, is_public, is_deleted, created_at, updated_at)
|
||||
VALUES (@AgentId, @UserId, @Name, @Avatar, @AvatarSmall, @Description, @Model, @ToolChoice, @CustomCommands, @AutoCommit, @UseSoul, @UseIdentity, @UseUserInfo, @UseMemory, @UseAutoContext, @UseGitHubSearch, @UseSerperSearch, @UseWebSearch, @WebSearchVersion, @AllowUserChoice, @TaskInstruction, @AvatarScale, @AvatarOffsetX, @AvatarOffsetY, @IsPublic, 0, @CreatedAt, @UpdatedAt)
|
||||
INSERT INTO agents (agent_id, user_id, name, avatar, avatar_small, description, model, tool_choice, custom_commands, auto_commit, use_soul, use_identity, use_user_info, use_memory, use_auto_context, use_github_search, use_serper_search, use_web_search, web_search_version, allow_user_choice, task_instruction, effort, avatar_scale, avatar_offset_x, avatar_offset_y, is_public, is_deleted, created_at, updated_at)
|
||||
VALUES (@AgentId, @UserId, @Name, @Avatar, @AvatarSmall, @Description, @Model, @ToolChoice, @CustomCommands, @AutoCommit, @UseSoul, @UseIdentity, @UseUserInfo, @UseMemory, @UseAutoContext, @UseGitHubSearch, @UseSerperSearch, @UseWebSearch, @WebSearchVersion, @AllowUserChoice, @TaskInstruction, @Effort, @AvatarScale, @AvatarOffsetX, @AvatarOffsetY, @IsPublic, 0, @CreatedAt, @UpdatedAt)
|
||||
", agent);
|
||||
return agent;
|
||||
}
|
||||
@@ -55,7 +55,7 @@ public partial 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_auto_context = @UseAutoContext, use_github_search = @UseGitHubSearch, use_serper_search = @UseSerperSearch, use_web_search = @UseWebSearch, web_search_version = @WebSearchVersion,
|
||||
allow_user_choice = @AllowUserChoice, task_instruction = @TaskInstruction,
|
||||
allow_user_choice = @AllowUserChoice, task_instruction = @TaskInstruction, effort = @Effort,
|
||||
avatar_scale = @AvatarScale, avatar_offset_x = @AvatarOffsetX, avatar_offset_y = @AvatarOffsetY,
|
||||
is_public = @IsPublic, updated_at = @UpdatedAt
|
||||
WHERE agent_id = @AgentId AND user_id = @UserId
|
||||
|
||||
@@ -196,6 +196,7 @@ public partial class DatabaseService
|
||||
TryAddColumn(conn, "sessions", "unity_mcp_url", "TEXT");
|
||||
TryAddColumn(conn, "sessions", "unity_mcp_token", "TEXT");
|
||||
TryAddColumn(conn, "messages", "tag", "TEXT");
|
||||
TryAddColumn(conn, "agents", "effort", "TEXT NOT NULL DEFAULT ''");
|
||||
|
||||
// Migration: 1M-Suffix entfernen (Mai 2026). Seit März 2026 ist 1M-Context Default
|
||||
// bei Opus 4.6/4.7/4.8 + Sonnet 4.6 – kein "[1m]"-Suffix mehr nötig.
|
||||
|
||||
Reference in new issue
Block a user