Token-Optimierung: Prompt Caching + Tool-Result-Truncation in History
This commit is contained in:
1 parent
ec5917ef63
commit
f74409dd89
12 files changed
+220
-59
No files matched your search
@@ -66,7 +66,7 @@ public class AnthropicService
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(workspace))
|
||||
payload["tools"] = _toolService.GetToolDefinitions();
|
||||
payload["tools"] = _toolService.GetToolDefinitions(markLastAsCached: true);
|
||||
|
||||
var client = CreateClient();
|
||||
var json = JsonSerializer.Serialize(payload);
|
||||
@@ -150,6 +150,8 @@ public class AnthropicService
|
||||
model = modelId,
|
||||
inputTokens = usage.InputTokens,
|
||||
outputTokens = usage.OutputTokens,
|
||||
cacheCreationTokens = usage.CacheCreationTokens,
|
||||
cacheReadTokens = usage.CacheReadTokens,
|
||||
iterations,
|
||||
chainId,
|
||||
durationMs = sw.ElapsedMilliseconds,
|
||||
@@ -161,11 +163,20 @@ public class AnthropicService
|
||||
var toolResults = new List<object>();
|
||||
foreach (var tr in executedTools)
|
||||
{
|
||||
// Tool-Results in der History auf 20.000 Zeichen kürzen.
|
||||
// Das vollständige Ergebnis wurde bereits in der DB gespeichert.
|
||||
// 20k Zeichen ≈ ~5k Tokens – verhindert Token-Explosion bei großen
|
||||
// read_file / execute-Outputs die sich über alle weiteren Iterationen schleppen.
|
||||
const int MaxToolResultChars = 20_000;
|
||||
var content = tr.Result.Length > MaxToolResultChars
|
||||
? tr.Result[..MaxToolResultChars] + $"\n\n[... gekürzt, {tr.Result.Length - MaxToolResultChars} weitere Zeichen ...]"
|
||||
: tr.Result;
|
||||
|
||||
toolResults.Add(new
|
||||
{
|
||||
type = "tool_result",
|
||||
tool_use_id = tr.Id,
|
||||
content = tr.Result,
|
||||
content,
|
||||
is_error = tr.IsError
|
||||
});
|
||||
}
|
||||
@@ -202,6 +213,8 @@ public class AnthropicService
|
||||
{
|
||||
chain.InputTokens = usage.InputTokens;
|
||||
chain.OutputTokens = usage.OutputTokens;
|
||||
chain.CacheCreationTokens = usage.CacheCreationTokens;
|
||||
chain.CacheReadTokens = usage.CacheReadTokens;
|
||||
chain.Iterations = iterations;
|
||||
chain.StopReason = stopReason;
|
||||
chain.DurationMs = sw.ElapsedMilliseconds;
|
||||
@@ -246,9 +259,15 @@ public class AnthropicService
|
||||
case "message_start":
|
||||
{
|
||||
if (evt.TryGetProperty("message", out var msg) &&
|
||||
msg.TryGetProperty("usage", out var u) &&
|
||||
u.TryGetProperty("input_tokens", out var it))
|
||||
usage.InputTokens += it.GetInt64();
|
||||
msg.TryGetProperty("usage", out var u))
|
||||
{
|
||||
if (u.TryGetProperty("input_tokens", out var it))
|
||||
usage.InputTokens += it.GetInt64();
|
||||
if (u.TryGetProperty("cache_creation_input_tokens", out var cc))
|
||||
usage.CacheCreationTokens += cc.GetInt64();
|
||||
if (u.TryGetProperty("cache_read_input_tokens", out var cr))
|
||||
usage.CacheReadTokens += cr.GetInt64();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -405,4 +424,6 @@ public class TokenUsage
|
||||
{
|
||||
public long InputTokens { get; set; }
|
||||
public long OutputTokens { get; set; }
|
||||
public long CacheCreationTokens { get; set; }
|
||||
public long CacheReadTokens { get; set; }
|
||||
}
|
||||
@@ -122,6 +122,8 @@ public class DatabaseService
|
||||
TryAddColumn(conn, "sessions", "is_deleted", "INTEGER NOT NULL DEFAULT 0");
|
||||
TryAddColumn(conn, "sessions", "custom_commands", "TEXT");
|
||||
TryAddColumn(conn, "chains", "agent_id", "TEXT");
|
||||
TryAddColumn(conn, "chains", "cache_creation_tokens", "INTEGER NOT NULL DEFAULT 0");
|
||||
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");
|
||||
}
|
||||
@@ -376,6 +378,8 @@ public class DatabaseService
|
||||
UPDATE chains SET
|
||||
input_tokens = @InputTokens,
|
||||
output_tokens = @OutputTokens,
|
||||
cache_creation_tokens = @CacheCreationTokens,
|
||||
cache_read_tokens = @CacheReadTokens,
|
||||
iterations = @Iterations,
|
||||
stop_reason = @StopReason,
|
||||
duration_ms = @DurationMs
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
using System.Collections.Generic;
|
||||
using Gott.Models;
|
||||
|
||||
namespace Gott.Services;
|
||||
|
||||
public static class SystemPromptBuilder
|
||||
{
|
||||
public static string Build(AgentRecord agent, SessionRecord session)
|
||||
{
|
||||
var parts = new List<string>();
|
||||
parts.Add($"Du bist {agent.Name}.");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(agent.Persona))
|
||||
parts.Add($"## Persona\n{agent.Persona}");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(agent.Instructions))
|
||||
parts.Add($"## Instructions\n{agent.Instructions}");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(session.Workspace))
|
||||
{
|
||||
parts.Add(@$"## 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}`
|
||||
Alle Dateipfade in Tool-Aufrufen müssen relativ zu diesem Workspace sein oder absolute Pfade verwenden, die mit diesem Pfad beginnen.
|
||||
|
||||
Wenn der User auf eine bestimmte Zeile in einer Datei verweist (z.B. `[Datei: foo.txt:11]`), zähle Leerzeilen immer mit. Verwende `cat -n` oder ein ähnliches Tool um Zeilennummern exakt zu bestimmen - NIEMALS Leerzeilen beim Zählen überspringen.");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(session.Context))
|
||||
parts.Add($"## Kontext\n{session.Context}");
|
||||
|
||||
return string.Join("\n\n", parts);
|
||||
}
|
||||
}
|
||||
+29
-11
@@ -11,7 +11,7 @@ namespace Gott.Services;
|
||||
|
||||
public class ToolService
|
||||
{
|
||||
public object[] GetToolDefinitions()
|
||||
public object[] GetToolDefinitions(bool markLastAsCached = false)
|
||||
{
|
||||
return new object[]
|
||||
{
|
||||
@@ -88,20 +88,38 @@ public class ToolService
|
||||
required = new[] { "path" }
|
||||
}
|
||||
},
|
||||
new
|
||||
{
|
||||
name = "execute",
|
||||
description = "Führt einen Shell-Befehl im Workspace-Verzeichnis aus. Gibt stdout und stderr zurück. Timeout: 30 Sekunden.",
|
||||
input_schema = new
|
||||
// Letztes Tool bekommt cache_control – Anthropic cached alles bis einschließlich
|
||||
// dieses Breakpoints (System-Prompt + alle Tool-Definitionen) für 5 Minuten.
|
||||
markLastAsCached
|
||||
? (object)new
|
||||
{
|
||||
type = "object",
|
||||
properties = new Dictionary<string, object>
|
||||
name = "execute",
|
||||
description = "Führt einen Shell-Befehl im Workspace-Verzeichnis aus. Gibt stdout und stderr zurück. Timeout: 30 Sekunden.",
|
||||
input_schema = new
|
||||
{
|
||||
["command"] = new { type = "string", description = "Shell-Befehl zum Ausführen" }
|
||||
type = "object",
|
||||
properties = new Dictionary<string, object>
|
||||
{
|
||||
["command"] = new { type = "string", description = "Shell-Befehl zum Ausführen" }
|
||||
},
|
||||
required = new[] { "command" }
|
||||
},
|
||||
required = new[] { "command" }
|
||||
cache_control = new { type = "ephemeral" }
|
||||
}
|
||||
: new
|
||||
{
|
||||
name = "execute",
|
||||
description = "Führt einen Shell-Befehl im Workspace-Verzeichnis aus. Gibt stdout und stderr zurück. Timeout: 30 Sekunden.",
|
||||
input_schema = new
|
||||
{
|
||||
type = "object",
|
||||
properties = new Dictionary<string, object>
|
||||
{
|
||||
["command"] = new { type = "string", description = "Shell-Befehl zum Ausführen" }
|
||||
},
|
||||
required = new[] { "command" }
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user