feat: CLI-Fallback für Sub-Agents (Preparer/Review/Finalizer) wenn kein API-Key zugewiesen
Statt Hard-Error "Kein API-Key zugewiesen" nutzen Sub-Agents jetzt Claude CLI als Fallback. Preparer wird nur übersprungen wenn weder Key noch CLI verfügbar sind. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
eccecf8f14
commit
1526891cd9
2 files changed
+111
-7
No files matched your search
@@ -63,6 +63,13 @@ public partial class AnthropicService
|
||||
text = await _openAiService.ChatNonStreaming(subCtx, preparerMessages);
|
||||
iterations = 1;
|
||||
}
|
||||
else if (string.IsNullOrEmpty(apiToken) && _cliService != null && _cliService.IsAvailable)
|
||||
{
|
||||
// Kein API-Key → CLI-Fallback
|
||||
(text, iterations) = await RunSubAgentViaCli(
|
||||
response, preparer, preparerMessages, chainId, sessionId,
|
||||
"preparer", cancellationToken, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
(text, iterations) = await RunAgentChat(
|
||||
@@ -164,6 +171,13 @@ public partial class AnthropicService
|
||||
text = await _openAiService.ChatNonStreaming(subCtx, reviewMessages);
|
||||
iterations = 1;
|
||||
}
|
||||
else if (string.IsNullOrEmpty(apiToken) && _cliService != null && _cliService.IsAvailable)
|
||||
{
|
||||
// Kein API-Key → CLI-Fallback
|
||||
(text, iterations) = await RunSubAgentViaCli(
|
||||
response, review, reviewMessages, chainId, sessionId,
|
||||
"review", workCt, sseCt);
|
||||
}
|
||||
else
|
||||
{
|
||||
(text, iterations) = await RunAgentChat(
|
||||
@@ -228,6 +242,13 @@ public partial class AnthropicService
|
||||
text = await _openAiService.ChatNonStreaming(subCtx, finalizerMessages);
|
||||
iterations = 1;
|
||||
}
|
||||
else if (string.IsNullOrEmpty(apiToken) && _cliService != null && _cliService.IsAvailable)
|
||||
{
|
||||
// Kein API-Key → CLI-Fallback
|
||||
(text, iterations) = await RunSubAgentViaCli(
|
||||
response, finalizer, finalizerMessages, chainId, sessionId,
|
||||
"finalizer", workCt, sseCt);
|
||||
}
|
||||
else
|
||||
{
|
||||
(text, iterations) = await RunAgentChat(
|
||||
@@ -624,6 +645,88 @@ public partial class AnthropicService
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Führt einen Sub-Agent (Preparer/Review/Finalizer) über die Claude CLI aus.
|
||||
/// Sammelt den gesamten Text und gibt ihn zurück. Wird als Fallback genutzt,
|
||||
/// wenn kein API-Key vorhanden ist.
|
||||
/// </summary>
|
||||
private async Task<(string text, int iterations)> RunSubAgentViaCli(
|
||||
HttpResponse response,
|
||||
SubAgentContext sub,
|
||||
List<object> historyMessages,
|
||||
string chainId,
|
||||
string sessionId,
|
||||
string agentType,
|
||||
CancellationToken workCt,
|
||||
CancellationToken sseCt)
|
||||
{
|
||||
if (_cliService == null || !_cliService.IsAvailable)
|
||||
return ("", 0);
|
||||
|
||||
// System-Prompt als Text extrahieren
|
||||
var systemText = new StringBuilder();
|
||||
if (sub.SystemPrompt != null)
|
||||
{
|
||||
foreach (var block in sub.SystemPrompt)
|
||||
{
|
||||
var json = JsonSerializer.Serialize(block);
|
||||
var el = JsonSerializer.Deserialize<JsonElement>(json);
|
||||
if (el.TryGetProperty("text", out var t))
|
||||
systemText.AppendLine(t.GetString());
|
||||
}
|
||||
}
|
||||
|
||||
// Letzte User-Message als Prompt extrahieren
|
||||
var prompt = ExtractLastUserText(historyMessages) ?? "";
|
||||
|
||||
// Konversations-History als Kontext
|
||||
var historyContext = BuildHistoryContext(historyMessages);
|
||||
if (!string.IsNullOrEmpty(historyContext))
|
||||
prompt = $"<conversation_history>\n{historyContext}\n</conversation_history>\n\n<current_request>\n{prompt}\n</current_request>";
|
||||
|
||||
var fullText = new StringBuilder();
|
||||
|
||||
var result = await _cliService.ChatStreamingAsync(
|
||||
prompt,
|
||||
async evt =>
|
||||
{
|
||||
switch (evt.Type)
|
||||
{
|
||||
case CliEventType.TextDelta:
|
||||
fullText.Append(evt.Text);
|
||||
await SseHelper.TryWriteSse(response, $"{agentType}_text",
|
||||
new { text = evt.Text, agentName = sub.Agent?.Name ?? agentType }, sseCt);
|
||||
break;
|
||||
case CliEventType.ToolResult:
|
||||
await SseHelper.TryWriteSse(response, $"{agentType}_tool",
|
||||
new { name = evt.ToolName, result = evt.ToolResultContent, isError = evt.ToolResultIsError, agentName = sub.Agent?.Name ?? agentType }, sseCt);
|
||||
break;
|
||||
}
|
||||
},
|
||||
modelId: sub.ModelId,
|
||||
systemPrompt: systemText.ToString(),
|
||||
workspace: sub.Workspace,
|
||||
hasWorkspace: !string.IsNullOrEmpty(sub.Workspace),
|
||||
cancellationToken: workCt
|
||||
);
|
||||
|
||||
// Text in DB speichern
|
||||
if (fullText.Length > 0)
|
||||
{
|
||||
await _db.SaveMessage(new MessageRecord
|
||||
{
|
||||
ChainId = chainId,
|
||||
Iteration = 0,
|
||||
Role = "assistant",
|
||||
Type = "text",
|
||||
Content = fullText.ToString(),
|
||||
Tag = agentType
|
||||
});
|
||||
}
|
||||
|
||||
return (fullText.ToString(), 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Non-Streaming Chat für Telegram. Gibt den finalen Antworttext zurück.
|
||||
/// Wrapper um RunNonStreamingToolLoop mit Chain-Management und Embedding-Enqueue.
|
||||
|
||||
Reference in new issue
Block a user