feat(Analyzer): refactor analyzer and implement auto mode
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
using System.Text.Json;
|
||||
using FinlyticCore.Models.Analyzer;
|
||||
using FinlyticCore.Util;
|
||||
|
||||
namespace FinlyticAnalyzer.Services;
|
||||
|
||||
public class N8nEvaluationService : IN8nEvaluationService
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly ILogger<N8nEvaluationService> _logger;
|
||||
private readonly string _webhookUrl;
|
||||
|
||||
public N8nEvaluationService(HttpClient httpClient, IConfiguration configuration, ILogger<N8nEvaluationService> logger)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_logger = logger;
|
||||
_webhookUrl = configuration["N8N:WebhookUrl"] ?? configuration["N8N__WebhookUrl"] ?? "https://n8n.kleidukos.me/webhook/gemini/analysis/auto";
|
||||
|
||||
// Timeout auf 45 Sekunden erhöht für komplexere LLM/Gemini Chains in n8n
|
||||
_httpClient.Timeout = TimeSpan.FromSeconds(45);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates an asset asynchronously using N8n / Gemini workflows.
|
||||
/// </summary>
|
||||
public async Task<N8nAnalysisResponseDto?> EvaluateAssetAsync(N8nAnalysisRequestDto request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("[{Channel}] Sending n8n AI Evaluation request {RequestId} for Asset {Symbol} (ISIN: {Isin}) to {Url}...",
|
||||
"AnalyzerChannel", request.RequestId, request.TargetAsset.Symbol, request.TargetAsset.Isin, _webhookUrl);
|
||||
|
||||
// Typsichere AOT-Serialisierung verwenden
|
||||
using var content = JsonContent.Create(
|
||||
request,
|
||||
FinlyticJsonSerializerContext.Default.N8nAnalysisRequestDto);
|
||||
|
||||
using var response = await _httpClient.PostAsync(_webhookUrl, content, cancellationToken);
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
var contentStr = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(contentStr) || contentStr.Trim() == "{}" || contentStr.Trim() == "[]")
|
||||
{
|
||||
_logger.LogWarning("[{Channel}] n8n Webhook returned an EMPTY response for Request {RequestId}. Flagging as AI Rejection (Too Risky).", "AnalyzerChannel", request.RequestId);
|
||||
return CreateRejectionFallback(request, "Die KI (n8n/Gemini) stuft den Trade als zu riskant ein und empfiehlt keine Positionierung.");
|
||||
}
|
||||
|
||||
// N8n schickt Ergebnisse manchmal als JSON-Array [{...}] zurück
|
||||
string jsonToDeserialize = contentStr.Trim();
|
||||
if (jsonToDeserialize.StartsWith('[') && jsonToDeserialize.EndsWith(']'))
|
||||
{
|
||||
using var doc = JsonDocument.Parse(jsonToDeserialize);
|
||||
if (doc.RootElement.ValueKind == JsonValueKind.Array && doc.RootElement.GetArrayLength() > 0)
|
||||
{
|
||||
jsonToDeserialize = doc.RootElement[0].GetRawText();
|
||||
}
|
||||
}
|
||||
|
||||
var responseDto = JsonSerializer.Deserialize(
|
||||
jsonToDeserialize,
|
||||
FinlyticJsonSerializerContext.Default.N8nAnalysisResponseDto);
|
||||
|
||||
if (responseDto != null && !string.IsNullOrWhiteSpace(responseDto.AiDecision))
|
||||
{
|
||||
_logger.LogInformation("[{Channel}] Received n8n AI Response for Request {RequestId}: Decision={Decision}, Score={Score:F2}, Direction={Direction}, Timeframe={Timeframe}",
|
||||
"AnalyzerChannel", request.RequestId, responseDto.AiDecision, responseDto.EvalScore, responseDto.SuggestedDirection, responseDto.SuggestedTimeframe);
|
||||
|
||||
return responseDto;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("[{Channel}] n8n Webhook returned HTTP {StatusCode} for Request {RequestId}",
|
||||
"AnalyzerChannel", response.StatusCode, request.RequestId);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException ex) when (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
_logger.LogError(ex, "[{Channel}] Timeout waiting for n8n AI Evaluation Webhook (Request {RequestId}). Consider increasing timeout.", "AnalyzerChannel", request.RequestId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "[{Channel}] Error calling n8n AI Evaluation Webhook for Request {RequestId}", "AnalyzerChannel", request.RequestId);
|
||||
}
|
||||
|
||||
return null; // Signals RPC/Service failure to caller
|
||||
}
|
||||
|
||||
private static N8nAnalysisResponseDto CreateRejectionFallback(N8nAnalysisRequestDto request, string reasoning)
|
||||
{
|
||||
return new N8nAnalysisResponseDto
|
||||
{
|
||||
RequestId = request.RequestId,
|
||||
AiDecision = "Rejected",
|
||||
EvalScore = 0.0,
|
||||
SuggestedDirection = "NONE",
|
||||
SuggestedRisk = request.UserPreferences?.RiskTolerance ?? "Moderate",
|
||||
SuggestedTimeframe = request.UserPreferences?.TimeframeFormatted ?? "1D",
|
||||
AiReasoning = reasoning
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user