Der KI-Analyse-Agent (docs/konzepte/KONZEPT-Modul-Supervisor.md, Phase S-2): - SupervisorToolRegistry (transport-agnostisch, spaeter auch MCP-Light): 7 read-only-Tools - query_decisions (inkl. Rejects+ReasonCodes), query_order_events, query_trades, get_dossier (Markdown-Kette), read_logs (JSONL je Tag, CID-Filter), get_kpis (TradeAnalytics), get_architecture_context. Ausfuehrung fehlertolerant (Exception -> Fehlertext, wirft nie). KEIN Tool kann handeln/schreiben. - OpenRouterClient (IChatCompletionClient): OpenAI-kompatibles Chat-Completions-Schema inkl. Function-Calling; Request-Bau + Response-Parsing pur/testbar. API-Key GETRENNT vom Trading: env POLYTRADER_OPENROUTER_KEY oder gitignorierte openrouter.key (in .gitignore aufgenommen). - SupervisorAgent: Function-Calling-Loop (max 8 Iterationen), System-Prompt = Arbeitsanweisung + eingebettetes Architektur-Kontext-Dokument (Context/ArchitectureContext.md, EmbeddedResource, mit dem Code versioniert - beschreibt Entscheidungswege, ReasonCodes, Leiter-Mechanik, Eigenheiten). Tool-Aufrufe werden gesammelt und in der UI transparent angezeigt. - SupervisorMainForm: neuer Tab 'Analyse' (Chat, Modellwahl default openrouter/auto, Tool-Aufrufe live im Verlauf, Token-Zaehler) neben dem Dossiers-Tab. - Sicherheitskonzept: OpenRouter als bewusst freigegebener Egress dokumentiert (nur Tool-Ergebnisse, nie Secrets; Spend-Limit je Key empfohlen). Tests: +7 (Registry-Ausfuehrung/Fehler, Agent-Loop mit Tool-Rueckfluss, Iterationsgrenze, Request-Body/Response-Parsing, eingebetteter Kontext). Build 0 Fehler, 351 Tests gruen, --smoke-ui alle 5 Views gruen. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
97 lines
4.2 KiB
C#
97 lines
4.2 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
|
||
namespace PolyTrader.Modules.Supervisor.Agent
|
||
{
|
||
/// <summary>Ergebnis einer Agenten-Anfrage inkl. transparenter Tool-Aufruf-Historie.</summary>
|
||
public sealed class AgentResult
|
||
{
|
||
public string Answer { get; init; } = string.Empty;
|
||
public List<(string Tool, string Arguments, string Result)> ToolInvocations { get; init; } = new();
|
||
public int PromptTokens { get; init; }
|
||
public int CompletionTokens { get; init; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// Der Analyse-Agent (S-2): Function-Calling-Loop gegen einen <see cref="IChatCompletionClient"/>
|
||
/// mit der read-only <see cref="SupervisorToolRegistry"/>. System-Kontext = Arbeitsanweisung +
|
||
/// Architektur-Dokument. Harte Iterationsgrenze gegen Endlosschleifen; jeder Tool-Aufruf wird
|
||
/// festgehalten (Nachvollziehbarkeit in der UI).
|
||
/// </summary>
|
||
public sealed class SupervisorAgent
|
||
{
|
||
public const int MaxIterations = 8;
|
||
public const string DefaultModel = "openrouter/auto";
|
||
|
||
private readonly IChatCompletionClient _chat;
|
||
private readonly SupervisorToolRegistry _tools;
|
||
|
||
public SupervisorAgent(IChatCompletionClient chat, SupervisorToolRegistry tools)
|
||
{
|
||
_chat = chat;
|
||
_tools = tools;
|
||
}
|
||
|
||
private static string SystemPrompt() =>
|
||
"Du bist der Supervisor von PolyTrader: ein Analyse-Agent für automatisierten Polymarket-Handel. " +
|
||
"Du bist strikt read-only – du kannst und darfst nicht handeln. Nutze die Tools, um Entscheidungsjournal, " +
|
||
"Order-Events, Trades und Logs abzufragen, BEVOR du Schlüsse ziehst. Zitiere konkrete Daten " +
|
||
"(SignalIds, Zeiten, Preise, ReasonCodes). Antworte auf Deutsch, präzise und mit klarer Schlussfolgerung.\n\n" +
|
||
"=== ARCHITEKTUR-KONTEXT ===\n" + ArchitectureContext.Load();
|
||
|
||
/// <summary>
|
||
/// Beantwortet eine Analyse-Frage. <paramref name="progress"/> meldet Tool-Aufrufe live an die UI.
|
||
/// </summary>
|
||
public async Task<AgentResult> AskAsync(string question, string? model = null,
|
||
IProgress<string>? progress = null, CancellationToken ct = default)
|
||
{
|
||
var messages = new List<ChatMessage>
|
||
{
|
||
ChatMessage.System(SystemPrompt()),
|
||
ChatMessage.User(question)
|
||
};
|
||
var invocations = new List<(string, string, string)>();
|
||
int promptTokens = 0, completionTokens = 0;
|
||
string usedModel = string.IsNullOrWhiteSpace(model) ? DefaultModel : model.Trim();
|
||
|
||
for (int i = 0; i < MaxIterations; i++)
|
||
{
|
||
ct.ThrowIfCancellationRequested();
|
||
var response = await _chat.CompleteAsync(usedModel, messages, _tools.Tools, ct);
|
||
promptTokens += response.PromptTokens;
|
||
completionTokens += response.CompletionTokens;
|
||
|
||
if (response.ToolCalls.Count == 0)
|
||
{
|
||
return new AgentResult
|
||
{
|
||
Answer = response.Content ?? "(keine Antwort)",
|
||
ToolInvocations = invocations,
|
||
PromptTokens = promptTokens,
|
||
CompletionTokens = completionTokens
|
||
};
|
||
}
|
||
|
||
messages.Add(ChatMessage.Assistant(response.Content, response.ToolCalls));
|
||
foreach (var call in response.ToolCalls)
|
||
{
|
||
progress?.Report($"🔧 {call.Name}({call.ArgumentsJson})");
|
||
string result = _tools.Execute(call.Name, call.ArgumentsJson);
|
||
invocations.Add((call.Name, call.ArgumentsJson, result));
|
||
messages.Add(ChatMessage.ToolResult(call.Id, result));
|
||
}
|
||
}
|
||
|
||
return new AgentResult
|
||
{
|
||
Answer = "Abbruch: maximale Tool-Iterationen erreicht (Frage ggf. eingrenzen).",
|
||
ToolInvocations = invocations,
|
||
PromptTokens = promptTokens,
|
||
CompletionTokens = completionTokens
|
||
};
|
||
}
|
||
}
|
||
}
|