feat(cache): 1h-Prompt-Cache-TTL für API und Claude CLI
Anthropic hat Anfang März 2026 die Default-Cache-TTL stillschweigend von 1h auf 5min gesenkt. Bei Sessions mit Pausen >5min wird der Cache jedes Mal neu geschrieben (teuer). Dieser Commit aktiviert die extended 1h-TTL über beide Pfade. API-Pfad (AnthropicService): - Beta-Header "extended-cache-ttl-2025-04-11" hinzugefügt - Falsches Top-Level cache_control (Root-Level wurde ignoriert) entfernt - Neue Helper ApplyCacheControlToLast() setzt cache_control idempotent auf das letzte Element via JSON-Roundtrip zu Dictionary - 2 Cache-Breakpoints pro Iteration: * Letztes Tool (cached gesamten Tool-Block) * Letzter System-Prompt-Block (cached System-Prompt) - Sub-Agent-Loop (Preparer/Review/Finalizer): String-System-Prompt wird bei Bedarf in Block-Form mit cache_control konvertiert CLI-Pfad (ClaudeCliService): - Env-Var ENABLE_PROMPT_CACHING_1H=1 für jeden CLI-Child-Prozess gesetzt (offizieller Opt-in seit Claude CLI 2.1.108, gilt für API-Key, Bedrock, Vertex, Foundry)
This commit is contained in:
1 parent
e87d7c4eec
commit
1ece62ce8c
4 files changed
+56
-3
No files matched your search
@@ -466,9 +466,24 @@ public partial class AnthropicService
|
||||
};
|
||||
|
||||
if (systemPrompt is List<object> spBlocks && spBlocks.Count > 0)
|
||||
{
|
||||
// Cache-Breakpoint auf den letzten System-Prompt-Block (1h TTL).
|
||||
ApplyCacheControlToLast(spBlocks);
|
||||
payload["system"] = spBlocks;
|
||||
}
|
||||
else if (systemPrompt is string spText && !string.IsNullOrEmpty(spText))
|
||||
payload["system"] = spText;
|
||||
{
|
||||
// String-System-Prompt → in Block-Form mit cache_control konvertieren.
|
||||
payload["system"] = new List<object>
|
||||
{
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
["type"] = "text",
|
||||
["text"] = spText,
|
||||
["cache_control"] = new { type = "ephemeral", ttl = "1h" }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
AddToolsToPayload(payload, toolOpts with { Iteration = iterations });
|
||||
|
||||
|
||||
@@ -93,13 +93,17 @@ public partial class AnthropicService
|
||||
{
|
||||
["model"] = modelId,
|
||||
["max_tokens"] = 16384,
|
||||
["cache_control"] = new { type = "ephemeral" },
|
||||
["messages"] = messages,
|
||||
["stream"] = true
|
||||
};
|
||||
|
||||
if (systemPrompt != null && systemPrompt.Count > 0)
|
||||
{
|
||||
// Cache-Breakpoint auf den letzten System-Prompt-Block (1h TTL).
|
||||
// Idempotent über Iterationen.
|
||||
ApplyCacheControlToLast(systemPrompt);
|
||||
payload["system"] = systemPrompt;
|
||||
}
|
||||
|
||||
var remoteConn = remoteMgr?.GetBySessionId(sessionId);
|
||||
var hasRemote = remoteConn != null;
|
||||
|
||||
@@ -318,6 +318,31 @@ public partial class AnthropicService
|
||||
private Task FinalizeChain(ChainRecord chain, TokenUsage usage, int iterations, string stopReason, System.Diagnostics.Stopwatch sw, string streamErrorInfo = null)
|
||||
=> SseHelper.FinalizeChain(_db, chain, usage, iterations, stopReason, sw, streamErrorInfo);
|
||||
|
||||
/// <summary>
|
||||
/// Versieht das letzte Element einer Block-Liste (Tools, System-Prompt-Blöcke, etc.) mit
|
||||
/// einer cache_control-Annotation. Konvertiert das Element über JSON-Roundtrip in ein
|
||||
/// Dictionary, damit cache_control auch bei anonymous types gesetzt werden kann.
|
||||
/// Idempotent: Mehrfaches Aufrufen überschreibt nur das cache_control-Feld.
|
||||
/// </summary>
|
||||
private static void ApplyCacheControlToLast(List<object> blocks, string ttl = "1h")
|
||||
{
|
||||
if (blocks == null || blocks.Count == 0) return;
|
||||
var last = blocks[blocks.Count - 1];
|
||||
Dictionary<string, object> dict;
|
||||
if (last is Dictionary<string, object> existing)
|
||||
{
|
||||
dict = existing;
|
||||
}
|
||||
else
|
||||
{
|
||||
var json = JsonSerializer.Serialize(last);
|
||||
var parsed = JsonSerializer.Deserialize<Dictionary<string, object>>(json);
|
||||
dict = parsed ?? new Dictionary<string, object>();
|
||||
}
|
||||
dict["cache_control"] = new { type = "ephemeral", ttl };
|
||||
blocks[blocks.Count - 1] = dict;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fügt Tool-Definitionen und WebSearch zum Payload hinzu.
|
||||
/// </summary>
|
||||
@@ -351,6 +376,10 @@ public partial class AnthropicService
|
||||
tools.Add(new { type = opts.WebSearchVersion, name = "web_search", max_uses = 5 });
|
||||
// web_search_20260209: Code Execution wird von der API automatisch injected – NICHT manuell hinzufügen!
|
||||
}
|
||||
// Cache-Breakpoint auf das letzte Tool → cached den gesamten Tool-Block (1h TTL).
|
||||
// Da Tools pro Iteration neu generiert werden, ist das idempotent.
|
||||
ApplyCacheControlToLast(tools);
|
||||
|
||||
payload["tools"] = tools;
|
||||
var effectiveChoice = (!opts.PlanMode && opts.ToolChoice == "any" && opts.Iteration == 1) ? "any" : "auto";
|
||||
payload["tool_choice"] = new { type = effectiveChoice };
|
||||
@@ -405,7 +434,7 @@ public partial class AnthropicService
|
||||
var client = _httpFactory.CreateClient();
|
||||
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiToken);
|
||||
client.DefaultRequestHeaders.Add("anthropic-version", "2023-06-01");
|
||||
client.DefaultRequestHeaders.Add("anthropic-beta", "claude-code-20250219,oauth-2025-04-20,code-execution-2025-08-25");
|
||||
client.DefaultRequestHeaders.Add("anthropic-beta", "claude-code-20250219,oauth-2025-04-20,code-execution-2025-08-25,extended-cache-ttl-2025-04-11");
|
||||
client.DefaultRequestHeaders.Add("User-Agent", "claude-cli/2.1.2 (external, cli)");
|
||||
client.DefaultRequestHeaders.Add("x-app", "cli");
|
||||
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||
|
||||
@@ -70,6 +70,11 @@ public class ClaudeCliService
|
||||
StandardErrorEncoding = Encoding.UTF8
|
||||
};
|
||||
|
||||
// 1h-Prompt-Cache-TTL für Claude CLI aktivieren (seit CLI 2.1.108).
|
||||
// Anthropic hat Anfang März 2026 den Default von 1h auf 5min gesenkt,
|
||||
// dieser Flag holt die 1h-TTL zurück (gilt für API-Key, Bedrock, Vertex, Foundry).
|
||||
psi.Environment["ENABLE_PROMPT_CACHING_1H"] = "1";
|
||||
|
||||
foreach (var arg in args)
|
||||
psi.ArgumentList.Add(arg);
|
||||
|
||||
|
||||
Reference in new issue
Block a user