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)
|
if (systemPrompt is List<object> spBlocks && spBlocks.Count > 0)
|
||||||
|
{
|
||||||
|
// Cache-Breakpoint auf den letzten System-Prompt-Block (1h TTL).
|
||||||
|
ApplyCacheControlToLast(spBlocks);
|
||||||
payload["system"] = spBlocks;
|
payload["system"] = spBlocks;
|
||||||
|
}
|
||||||
else if (systemPrompt is string spText && !string.IsNullOrEmpty(spText))
|
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 });
|
AddToolsToPayload(payload, toolOpts with { Iteration = iterations });
|
||||||
|
|
||||||
|
|||||||
@@ -93,13 +93,17 @@ public partial class AnthropicService
|
|||||||
{
|
{
|
||||||
["model"] = modelId,
|
["model"] = modelId,
|
||||||
["max_tokens"] = 16384,
|
["max_tokens"] = 16384,
|
||||||
["cache_control"] = new { type = "ephemeral" },
|
|
||||||
["messages"] = messages,
|
["messages"] = messages,
|
||||||
["stream"] = true
|
["stream"] = true
|
||||||
};
|
};
|
||||||
|
|
||||||
if (systemPrompt != null && systemPrompt.Count > 0)
|
if (systemPrompt != null && systemPrompt.Count > 0)
|
||||||
|
{
|
||||||
|
// Cache-Breakpoint auf den letzten System-Prompt-Block (1h TTL).
|
||||||
|
// Idempotent über Iterationen.
|
||||||
|
ApplyCacheControlToLast(systemPrompt);
|
||||||
payload["system"] = systemPrompt;
|
payload["system"] = systemPrompt;
|
||||||
|
}
|
||||||
|
|
||||||
var remoteConn = remoteMgr?.GetBySessionId(sessionId);
|
var remoteConn = remoteMgr?.GetBySessionId(sessionId);
|
||||||
var hasRemote = remoteConn != null;
|
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)
|
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);
|
=> 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>
|
/// <summary>
|
||||||
/// Fügt Tool-Definitionen und WebSearch zum Payload hinzu.
|
/// Fügt Tool-Definitionen und WebSearch zum Payload hinzu.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -351,6 +376,10 @@ public partial class AnthropicService
|
|||||||
tools.Add(new { type = opts.WebSearchVersion, name = "web_search", max_uses = 5 });
|
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!
|
// 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;
|
payload["tools"] = tools;
|
||||||
var effectiveChoice = (!opts.PlanMode && opts.ToolChoice == "any" && opts.Iteration == 1) ? "any" : "auto";
|
var effectiveChoice = (!opts.PlanMode && opts.ToolChoice == "any" && opts.Iteration == 1) ? "any" : "auto";
|
||||||
payload["tool_choice"] = new { type = effectiveChoice };
|
payload["tool_choice"] = new { type = effectiveChoice };
|
||||||
@@ -405,7 +434,7 @@ public partial class AnthropicService
|
|||||||
var client = _httpFactory.CreateClient();
|
var client = _httpFactory.CreateClient();
|
||||||
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiToken);
|
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiToken);
|
||||||
client.DefaultRequestHeaders.Add("anthropic-version", "2023-06-01");
|
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("User-Agent", "claude-cli/2.1.2 (external, cli)");
|
||||||
client.DefaultRequestHeaders.Add("x-app", "cli");
|
client.DefaultRequestHeaders.Add("x-app", "cli");
|
||||||
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||||
|
|||||||
@@ -70,6 +70,11 @@ public class ClaudeCliService
|
|||||||
StandardErrorEncoding = Encoding.UTF8
|
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)
|
foreach (var arg in args)
|
||||||
psi.ArgumentList.Add(arg);
|
psi.ArgumentList.Add(arg);
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user