Refactoring + Dokumentation: api.js komprimiert (745→267 Zeilen), SseHelper extrahiert, DatabaseService in Partial Classes aufgeteilt, unbenutztes CSS entfernt, ARCHITECTURE.md/ARCHITECTURE_QUICK.md/README.md aktualisiert (neue Features: Embedding, Remote, API-Keys, 16 Tools)

This commit is contained in:
Meleki committed 2026-03-12 13:29:19 +01:00
1 parent 8dc52c1337
commit db1a7c7f64
18 files changed
+1701 -2007

No files matched your search

+35 -80
View File
@@ -190,7 +190,7 @@ public class AnthropicService
trimCount = TrimMessagesCleanly(messages, trimCount);
if (trimCount > 0)
{
await TryWriteSse(response, "info", new { message = $"History zu lang – {trimCount} ältere Messages entfernt, Retry ({promptTrimRetries}/5)..." }, sseCt);
await SseHelper.TryWriteSse(response, "info", new { message = $"History zu lang – {trimCount} ältere Messages entfernt, Retry ({promptTrimRetries}/5)..." }, sseCt);
iterations--; // Iteration nicht zählen, war ja kein echter Versuch
continue; // → nächste while-Iteration mit kürzerer History
}
@@ -202,9 +202,9 @@ public class AnthropicService
Role = "system", Type = "error",
Content = errMsg, IsError = true
});
await TryWriteSse(response, "error", new { error = errMsg }, sseCt);
await SseHelper.TryWriteSse(response, "error", new { error = errMsg }, sseCt);
await FinalizeChain(chain, usage, iterations, "error", sw);
await TryWriteSse(response, "done", new { chainId = chain.ChainId }, sseCt);
await SseHelper.TryWriteSse(response, "done", new { chainId = chain.ChainId }, sseCt);
return;
}
@@ -323,7 +323,7 @@ public class AnthropicService
var finalStopReason = string.IsNullOrEmpty(stopReason) ? "unexpected_end" : stopReason;
await FinalizeChain(chain, usage, iterations, finalStopReason, sw, streamErrorInfo);
await TryWriteSse(response, "done", new
await SseHelper.TryWriteSse(response, "done", new
{
model = modelId,
inputTokens = usage.InputTokens,
@@ -359,8 +359,8 @@ public class AnthropicService
}
await FinalizeChain(chain, usage, iterations, "max_iterations", sw);
await TryWriteSse(response, "error", new { error = "Max iterations erreicht" }, sseCt);
await TryWriteSse(response, "done", new { chainId = chain.ChainId }, sseCt);
await SseHelper.TryWriteSse(response, "error", new { error = "Max iterations erreicht" }, sseCt);
await SseHelper.TryWriteSse(response, "done", new { chainId = chain.ChainId }, sseCt);
}
catch (OperationCanceledException) when (workCt.IsCancellationRequested || hardCt.IsCancellationRequested)
{
@@ -368,7 +368,7 @@ public class AnthropicService
await FinalizeChain(chain, usage, iterations, "stopped", sw);
// sseCt ist hier bereits cancelled (gelinkt mit workCt), daher CancellationToken.None
// Der SSE-Stream zum Client ist noch offen – nur der Stop-Button hat ausgelöst
await TryWriteSse(response, "done", new
await SseHelper.TryWriteSse(response, "done", new
{
model = modelId,
inputTokens = usage.InputTokens,
@@ -389,7 +389,7 @@ public class AnthropicService
// Wenn workCt/hardCt gecancelled ist, ist das ein regulärer Stop.
await FinalizeChain(chain, usage, iterations, "stopped", sw);
// sseCt ist hier bereits cancelled (gelinkt mit workCt), daher CancellationToken.None
await TryWriteSse(response, "done", new
await SseHelper.TryWriteSse(response, "done", new
{
model = modelId,
inputTokens = usage.InputTokens,
@@ -421,8 +421,8 @@ public class AnthropicService
Content = errMsg, IsError = true
});
await FinalizeChain(chain, usage, iterations, "error", sw);
await TryWriteSse(response, "error", new { error = errMsg }, sseCt);
await TryWriteSse(response, "done", new { chainId = chain.ChainId }, sseCt);
await SseHelper.TryWriteSse(response, "error", new { error = errMsg }, sseCt);
await SseHelper.TryWriteSse(response, "done", new { chainId = chain.ChainId }, sseCt);
}
}
@@ -455,7 +455,7 @@ public class AnthropicService
// da der Worker noch nicht gestartet hat).
cancellationToken.ThrowIfCancellationRequested();
await TryWriteSse(response, "preparer_start", new
await SseHelper.TryWriteSse(response, "preparer_start", new
{
agentId = preparer.AgentId,
agentName = preparer.Agent?.Name ?? "Vorbereiter-Agent"
@@ -484,7 +484,7 @@ public class AnthropicService
result.ShouldForward = false;
result.ResponseText = questionText;
await TryWriteSse(response, "preparer_done", new
await SseHelper.TryWriteSse(response, "preparer_done", new
{
agentName = preparer.Agent?.Name ?? "Vorbereiter-Agent",
forwarded = false,
@@ -504,7 +504,7 @@ public class AnthropicService
result.ShouldForward = true;
result.ForwardedPrompt = forwardedText;
await TryWriteSse(response, "preparer_done", new
await SseHelper.TryWriteSse(response, "preparer_done", new
{
agentName = preparer.Agent?.Name ?? "Vorbereiter-Agent",
forwarded = true
@@ -545,7 +545,7 @@ public class AnthropicService
workCt.ThrowIfCancellationRequested();
// SSE: Review-Phase Start
await TryWriteSse(response, "review_start", new
await SseHelper.TryWriteSse(response, "review_start", new
{
round = reviewRound + 1,
maxRounds = review.MaxReviewIterations,
@@ -576,7 +576,7 @@ public class AnthropicService
result.HasFeedback = true;
result.Feedback = $"[Review-Feedback von {review.Agent?.Name ?? "Review-Agent"} (Runde {reviewRound + 1}/{review.MaxReviewIterations})]:\n{feedbackText}";
await TryWriteSse(response, "review_feedback", new
await SseHelper.TryWriteSse(response, "review_feedback", new
{
round = reviewRound + 1,
maxRounds = review.MaxReviewIterations,
@@ -587,7 +587,7 @@ public class AnthropicService
else
{
// Default: Approved (kein [FEEDBACK]-Tag → alles OK)
await TryWriteSse(response, "review_approved", new
await SseHelper.TryWriteSse(response, "review_approved", new
{
round = reviewRound + 1,
agentName = review.Agent?.Name ?? "Review-Agent"
@@ -616,7 +616,7 @@ public class AnthropicService
CancellationToken workCt,
CancellationToken sseCt)
{
await TryWriteSse(response, "finalizer_start", new
await SseHelper.TryWriteSse(response, "finalizer_start", new
{
agentId = finalizer.AgentId,
agentName = finalizer.Agent?.Name ?? "Finalizer-Agent"
@@ -632,7 +632,7 @@ public class AnthropicService
baseIterations, "finalizer", finalizer.Agent?.Name ?? "Finalizer-Agent",
workCt, sseCt);
await TryWriteSse(response, "finalizer_done", new
await SseHelper.TryWriteSse(response, "finalizer_done", new
{
agentName = finalizer.Agent?.Name ?? "Finalizer-Agent"
}, sseCt);
@@ -828,7 +828,7 @@ public class AnthropicService
trimCount = TrimMessagesCleanly(messages, trimCount);
if (trimCount > 0)
{
await TryWriteSse(response, "info", new { message = $"{tag}: History zu lang – {trimCount} Messages entfernt, Retry..." }, sseCt);
await SseHelper.TryWriteSse(response, "info", new { message = $"{tag}: History zu lang – {trimCount} Messages entfernt, Retry..." }, sseCt);
iterations--;
continue;
}
@@ -857,7 +857,7 @@ public class AnthropicService
assistantContent.Add(new { type = "text", text });
finalText.Append(text);
await TryWriteSse(response, $"{agentType}_text", new
await SseHelper.TryWriteSse(response, $"{agentType}_text", new
{
text,
agentName,
@@ -882,7 +882,7 @@ public class AnthropicService
assistantContent.Add(new { type = "tool_use", id = toolId, name = toolName, input = toolInput });
await TryWriteSse(response, $"{agentType}_tool_use", new
await SseHelper.TryWriteSse(response, $"{agentType}_tool_use", new
{
toolName,
toolId,
@@ -916,7 +916,7 @@ public class AnthropicService
is_error = isError
});
await TryWriteSse(response, $"{agentType}_tool_end", new
await SseHelper.TryWriteSse(response, $"{agentType}_tool_end", new
{
toolName,
toolId,
@@ -1207,18 +1207,8 @@ public class AnthropicService
catch { /* Logging darf nie die App crashen */ }
}
private async Task FinalizeChain(ChainRecord chain, TokenUsage usage, int iterations, string stopReason, System.Diagnostics.Stopwatch sw, string streamErrorInfo = null)
{
chain.InputTokens = usage.InputTokens;
chain.OutputTokens = usage.OutputTokens;
chain.CacheCreationTokens = usage.CacheCreationTokens;
chain.CacheReadTokens = usage.CacheReadTokens;
chain.Iterations = iterations;
chain.StopReason = stopReason;
chain.StreamErrorInfo = streamErrorInfo;
chain.DurationMs = sw.ElapsedMilliseconds;
await _db.UpdateChain(chain);
}
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);
private async Task<(string StopReason, List<object> Content, List<ToolResult> Tools, string StreamErrorInfo)> ProcessStream(
HttpResponse response,
@@ -1287,7 +1277,7 @@ public class AnthropicService
if (cbType == "text")
{
currentTextBlock.Clear();
await TryWriteSse(response, "text_start", new { index = idx }, sseCt);
await SseHelper.TryWriteSse(response, "text_start", new { index = idx }, sseCt);
}
else if (cbType == "tool_use")
{
@@ -1299,7 +1289,7 @@ public class AnthropicService
currentToolId = cb.GetProperty("id").GetString() ?? "";
currentToolName = cb.GetProperty("name").GetString() ?? "";
currentToolInputJson.Clear();
await TryWriteSse(response, "tool_start", new { name = currentToolName, id = currentToolId }, sseCt);
await SseHelper.TryWriteSse(response, "tool_start", new { name = currentToolName, id = currentToolId }, sseCt);
}
else if (cbType == "server_tool_use")
{
@@ -1311,7 +1301,7 @@ public class AnthropicService
currentToolId = cb.GetProperty("id").GetString() ?? "";
currentToolName = cb.GetProperty("name").GetString() ?? "";
currentToolInputJson.Clear();
await TryWriteSse(response, "web_search_start", new { name = currentToolName, id = currentToolId }, sseCt);
await SseHelper.TryWriteSse(response, "web_search_start", new { name = currentToolName, id = currentToolId }, sseCt);
}
else if (cbType == "web_search_tool_result")
{
@@ -1336,7 +1326,7 @@ public class AnthropicService
{
var text = delta.GetProperty("text").GetString() ?? "";
currentTextBlock.Append(text);
await TryWriteSse(response, "text_delta", new { text }, sseCt);
await SseHelper.TryWriteSse(response, "text_delta", new { text }, sseCt);
}
else if (deltaType == "input_json_delta")
{
@@ -1357,7 +1347,7 @@ public class AnthropicService
if (text.Length > 0)
{
assistantContent.Add(new { type = "text", text });
await TryWriteSse(response, "text_end", new { }, sseCt);
await SseHelper.TryWriteSse(response, "text_end", new { }, sseCt);
}
currentTextBlock.Clear();
}
@@ -1394,7 +1384,7 @@ public class AnthropicService
}
}
await TryWriteSse(response, "user_choice", new
await SseHelper.TryWriteSse(response, "user_choice", new
{
toolCallId = currentToolId,
question,
@@ -1419,7 +1409,7 @@ public class AnthropicService
workCt.ThrowIfCancellationRequested();
executedTools.Add(new ToolResult(currentToolId, toolResult, isError));
await TryWriteSse(response, "tool_end", new
await SseHelper.TryWriteSse(response, "tool_end", new
{
name = currentToolName,
input = currentToolInputJson.ToString(),
@@ -1447,7 +1437,7 @@ public class AnthropicService
// Web-Search-Ergebnis von Anthropic – als Raw-JSON in assistantContent aufnehmen
assistantContent.Add(webSearchResultBlocks[idx]);
var sources = webSearchSources.TryGetValue(idx, out var s) ? s : new List<object>();
await TryWriteSse(response, "web_search_end", new { id = currentToolId, sources }, sseCt);
await SseHelper.TryWriteSse(response, "web_search_end", new { id = currentToolId, sources }, sseCt);
}
else if (bt.EndsWith("_tool_result") && serverToolResultBlocks.TryGetValue(idx, out var srvResultBlock))
{
@@ -1526,14 +1516,8 @@ public class AnthropicService
// ── Shared Helpers ──
internal const int MaxToolResultChars = 20_000;
internal static string TruncateToolResult(string result)
{
return result.Length > MaxToolResultChars
? result[..MaxToolResultChars] + $"\n\n[... gekürzt, {result.Length - MaxToolResultChars} weitere Zeichen ...]"
: result;
}
// TruncateToolResult → SseHelper.cs
internal static string TruncateToolResult(string result) => SseHelper.TruncateToolResult(result);
/// <summary>
/// Extrahiert den Text der letzten User-Message aus der Message-Liste.
@@ -1697,34 +1681,5 @@ public class AnthropicService
return client;
}
public static async Task WriteSseEvent(HttpResponse response, string eventType, object data, CancellationToken ct = default)
{
ct.ThrowIfCancellationRequested();
var json = JsonSerializer.Serialize(data);
await response.WriteAsync($"event: {eventType}\ndata: {json}\n\n", ct);
await response.Body.FlushAsync(ct);
}
private static async Task TryWriteSse(HttpResponse response, string eventType, object data, CancellationToken ct)
{
try
{
if (!ct.IsCancellationRequested)
await WriteSseEvent(response, eventType, data, ct);
}
catch
{
// Client bereits disconnected – ignorieren
}
}
}
public record ToolResult(string Id, string Result, bool IsError);
public class TokenUsage
{
public long InputTokens { get; set; }
public long OutputTokens { get; set; }
public long CacheCreationTokens { get; set; }
public long CacheReadTokens { get; set; }
// WriteSseEvent, TryWriteSse, TokenUsage, ToolResult → SseHelper.cs
}