Files
PolyTraderSharp/src/PolyTrader.Modules.Supervisor/Agent/SupervisorAgent.cs
T
RichardandClaude Opus 4.8 5d732277b2 Supervisor S-2: OpenRouter-Agent, read-only Tool-Registry, Analyse-Chat
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>
2026-07-17 18:06:28 +02:00

97 lines
4.2 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
};
}
}
}