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:
1 parent
8dc52c1337
commit
db1a7c7f64
18 files changed
+1701
-2007
No files matched your search
@@ -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
|
||||
}
|
||||
Reference in new issue
Block a user