Add system message to LLM requests and improve indicator type resolution
- Introduced a system message in LlmController to clarify that tools are optional for LLM responses, enhancing user guidance. - Refactored indicator type resolution in IndicatorTools to support fuzzy matching and provide suggestions for invalid types, improving user experience and error handling. - Updated methods to utilize the new resolution logic, ensuring consistent handling of indicator types across the application.
This commit is contained in:
@@ -57,6 +57,23 @@ public class LlmController : BaseController
|
||||
var availableTools = await _mcpService.GetAvailableToolsAsync();
|
||||
request.Tools = availableTools.ToList();
|
||||
|
||||
// Add system message to clarify that tools are optional and the LLM can respond directly
|
||||
// Check if a system message already exists
|
||||
var hasSystemMessage = request.Messages.Any(m => m.Role == "system");
|
||||
if (!hasSystemMessage)
|
||||
{
|
||||
var systemMessage = new LlmMessage
|
||||
{
|
||||
Role = "system",
|
||||
Content = "You are a helpful AI assistant with expertise in quantitative finance, algorithmic trading, and financial mathematics. " +
|
||||
"You can answer questions directly using your knowledge. " +
|
||||
"Tools are available for specific operations (backtesting, agent management, market data retrieval, etc.) but are optional. " +
|
||||
"Use tools only when they are needed for the specific task. " +
|
||||
"For general questions, explanations, calculations, or discussions, respond directly without using tools."
|
||||
};
|
||||
request.Messages.Insert(0, systemMessage);
|
||||
}
|
||||
|
||||
// Send chat request to LLM
|
||||
var response = await _llmService.ChatAsync(user, request);
|
||||
|
||||
|
||||
@@ -74,21 +74,25 @@ public class IndicatorTools
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Enum.TryParse<IndicatorType>(indicatorType, true, out var type))
|
||||
var type = ResolveIndicatorType(indicatorType);
|
||||
if (type == null)
|
||||
{
|
||||
throw new ArgumentException($"Invalid indicator type: {indicatorType}");
|
||||
var suggestions = GetIndicatorSuggestions(indicatorType);
|
||||
throw new ArgumentException($"Invalid indicator type: '{indicatorType}'. {suggestions}");
|
||||
}
|
||||
|
||||
if (!_indicatorInfoCache.TryGetValue(type, out var info))
|
||||
if (!_indicatorInfoCache.TryGetValue(type.Value, out var info))
|
||||
{
|
||||
throw new ArgumentException($"Information not available for indicator: {indicatorType}");
|
||||
}
|
||||
|
||||
var resolvedType = type.Value;
|
||||
|
||||
return new
|
||||
{
|
||||
Type = type.ToString(),
|
||||
SignalType = GetSignalType(type).ToString(),
|
||||
Category = GetCategory(type),
|
||||
Type = resolvedType.ToString(),
|
||||
SignalType = GetSignalType(resolvedType).ToString(),
|
||||
Category = GetCategory(resolvedType),
|
||||
Name = info.Name,
|
||||
Description = info.Description,
|
||||
LongDescription = info.LongDescription,
|
||||
@@ -114,21 +118,24 @@ public class IndicatorTools
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Enum.TryParse<IndicatorType>(indicatorType, true, out var type))
|
||||
var type = ResolveIndicatorType(indicatorType);
|
||||
if (type == null)
|
||||
{
|
||||
throw new ArgumentException($"Invalid indicator type: {indicatorType}");
|
||||
var suggestions = GetIndicatorSuggestions(indicatorType);
|
||||
throw new ArgumentException($"Invalid indicator type: '{indicatorType}'. {suggestions}");
|
||||
}
|
||||
|
||||
if (!_indicatorInfoCache.TryGetValue(type, out var info))
|
||||
if (!_indicatorInfoCache.TryGetValue(type.Value, out var info))
|
||||
{
|
||||
throw new ArgumentException($"Information not available for indicator: {indicatorType}");
|
||||
}
|
||||
|
||||
var resolvedType = type.Value;
|
||||
var explanation = new
|
||||
{
|
||||
Type = type.ToString(),
|
||||
SignalType = GetSignalType(type).ToString(),
|
||||
Category = GetCategory(type),
|
||||
Type = resolvedType.ToString(),
|
||||
SignalType = GetSignalType(resolvedType).ToString(),
|
||||
Category = GetCategory(resolvedType),
|
||||
Name = info.Name,
|
||||
HowItWorks = info.LongDescription,
|
||||
WhenToUse = info.UseCases,
|
||||
@@ -143,7 +150,7 @@ public class IndicatorTools
|
||||
}),
|
||||
RecommendedParameters = info.RecommendedParameters,
|
||||
TradingStyleRecommendations = info.TradingStyleRecommendations,
|
||||
Examples = includeExamples ? GetIndicatorExamples(type) : null
|
||||
Examples = includeExamples ? GetIndicatorExamples(resolvedType) : null
|
||||
};
|
||||
|
||||
return explanation;
|
||||
@@ -229,16 +236,20 @@ public class IndicatorTools
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Enum.TryParse<IndicatorType>(indicatorType, true, out var type))
|
||||
var type = ResolveIndicatorType(indicatorType);
|
||||
if (type == null)
|
||||
{
|
||||
throw new ArgumentException($"Invalid indicator type: {indicatorType}");
|
||||
var suggestions = GetIndicatorSuggestions(indicatorType);
|
||||
throw new ArgumentException($"Invalid indicator type: '{indicatorType}'. {suggestions}");
|
||||
}
|
||||
|
||||
if (!_indicatorInfoCache.TryGetValue(type, out var info))
|
||||
if (!_indicatorInfoCache.TryGetValue(type.Value, out var info))
|
||||
{
|
||||
throw new ArgumentException($"Information not available for indicator: {indicatorType}");
|
||||
}
|
||||
|
||||
var resolvedType = type.Value;
|
||||
|
||||
var refinements = new List<ParameterRefinement>();
|
||||
|
||||
foreach (var param in info.Parameters)
|
||||
@@ -249,9 +260,9 @@ public class IndicatorTools
|
||||
Description = param.Description,
|
||||
CurrentRecommended = param.RecommendedValue,
|
||||
RefinedValues = GetRefinedParameterValues(
|
||||
type, param.Name, tradingStyle, marketVolatility),
|
||||
resolvedType, param.Name, tradingStyle, marketVolatility),
|
||||
Reasoning = GetParameterRefinementReasoning(
|
||||
type, param.Name, tradingStyle, marketVolatility)
|
||||
resolvedType, param.Name, tradingStyle, marketVolatility)
|
||||
};
|
||||
|
||||
refinements.Add(refinement);
|
||||
@@ -259,12 +270,12 @@ public class IndicatorTools
|
||||
|
||||
return new
|
||||
{
|
||||
IndicatorType = type.ToString(),
|
||||
SignalType = GetSignalType(type).ToString(),
|
||||
IndicatorType = resolvedType.ToString(),
|
||||
SignalType = GetSignalType(resolvedType).ToString(),
|
||||
TradingStyle = tradingStyle ?? "General",
|
||||
MarketVolatility = marketVolatility ?? "Normal",
|
||||
ParameterRefinements = refinements,
|
||||
GeneralAdvice = GetGeneralParameterAdvice(type, tradingStyle, marketVolatility)
|
||||
GeneralAdvice = GetGeneralParameterAdvice(resolvedType, tradingStyle, marketVolatility)
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -281,16 +292,39 @@ public class IndicatorTools
|
||||
{
|
||||
try
|
||||
{
|
||||
var types = indicatorTypes.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
||||
var typeStrings = indicatorTypes.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
||||
.Select(t => t.Trim())
|
||||
.Where(t => Enum.TryParse<IndicatorType>(t, true, out _))
|
||||
.Select(t => Enum.Parse<IndicatorType>(t, true))
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
var types = new List<IndicatorType>();
|
||||
var invalidTypes = new List<string>();
|
||||
|
||||
foreach (var typeString in typeStrings)
|
||||
{
|
||||
var resolvedType = ResolveIndicatorType(typeString);
|
||||
if (resolvedType.HasValue)
|
||||
{
|
||||
types.Add(resolvedType.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
invalidTypes.Add(typeString);
|
||||
}
|
||||
}
|
||||
|
||||
types = types.Distinct().ToList();
|
||||
|
||||
if (types.Count == 0)
|
||||
{
|
||||
throw new ArgumentException("At least one valid indicator type is required");
|
||||
var suggestions = invalidTypes.Count > 0
|
||||
? $"Invalid indicator types: {string.Join(", ", invalidTypes)}. {GetIndicatorSuggestions(invalidTypes.First())}"
|
||||
: "At least one valid indicator type is required";
|
||||
throw new ArgumentException(suggestions);
|
||||
}
|
||||
|
||||
if (invalidTypes.Count > 0)
|
||||
{
|
||||
_logger.LogWarning("Some indicator types could not be resolved: {InvalidTypes}", string.Join(", ", invalidTypes));
|
||||
}
|
||||
|
||||
var comparisons = types.Select(type =>
|
||||
@@ -325,6 +359,91 @@ public class IndicatorTools
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
/// <summary>
|
||||
/// Resolves an indicator type from user input, supporting fuzzy matching
|
||||
/// </summary>
|
||||
private IndicatorType? ResolveIndicatorType(string input)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
return null;
|
||||
|
||||
var normalizedInput = input.Trim().Replace(" ", "").Replace("-", "").Replace("_", "").ToLowerInvariant();
|
||||
|
||||
// First try exact enum match (case-insensitive)
|
||||
if (Enum.TryParse<IndicatorType>(input, true, out var exactMatch))
|
||||
{
|
||||
return exactMatch;
|
||||
}
|
||||
|
||||
// Try fuzzy matching by name
|
||||
foreach (var type in Enum.GetValues<IndicatorType>())
|
||||
{
|
||||
if (type == IndicatorType.Composite) continue;
|
||||
|
||||
var name = GetIndicatorName(type).Replace(" ", "").Replace("-", "").Replace("_", "").ToLowerInvariant();
|
||||
var typeName = type.ToString().ToLowerInvariant();
|
||||
var category = GetCategory(type).ToLowerInvariant();
|
||||
|
||||
// Check if input matches name, type name, or category
|
||||
if (name.Contains(normalizedInput) ||
|
||||
normalizedInput.Contains(name) ||
|
||||
typeName.Contains(normalizedInput) ||
|
||||
normalizedInput.Contains(typeName) ||
|
||||
category.Contains(normalizedInput) ||
|
||||
normalizedInput.Contains(category))
|
||||
{
|
||||
// Special handling for "RSI" - prefer RsiDivergence over RsiDivergenceConfirm
|
||||
if (normalizedInput == "rsi" || normalizedInput == "rsidivergence")
|
||||
{
|
||||
return IndicatorType.RsiDivergence;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets suggestions for invalid indicator types
|
||||
/// </summary>
|
||||
private string GetIndicatorSuggestions(string input)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
return "Please provide an indicator type.";
|
||||
|
||||
var normalizedInput = input.Trim().ToLowerInvariant();
|
||||
var suggestions = new List<string>();
|
||||
|
||||
// Find similar indicators
|
||||
foreach (var type in Enum.GetValues<IndicatorType>())
|
||||
{
|
||||
if (type == IndicatorType.Composite) continue;
|
||||
|
||||
var name = GetIndicatorName(type).ToLowerInvariant();
|
||||
var typeName = type.ToString().ToLowerInvariant();
|
||||
var category = GetCategory(type).ToLowerInvariant();
|
||||
|
||||
if (name.Contains(normalizedInput) ||
|
||||
typeName.Contains(normalizedInput) ||
|
||||
category.Contains(normalizedInput) ||
|
||||
normalizedInput.Contains(name) ||
|
||||
normalizedInput.Contains(typeName) ||
|
||||
normalizedInput.Contains(category))
|
||||
{
|
||||
suggestions.Add($"'{type}' ({GetIndicatorName(type)})");
|
||||
}
|
||||
}
|
||||
|
||||
if (suggestions.Count > 0)
|
||||
{
|
||||
return $"Did you mean: {string.Join(", ", suggestions.Take(5))}? Use 'list_indicators' to see all available indicators.";
|
||||
}
|
||||
|
||||
// If no matches, suggest common indicators
|
||||
return "Available RSI indicators: 'RsiDivergence', 'RsiDivergenceConfirm'. Use 'list_indicators' to see all available indicators.";
|
||||
}
|
||||
|
||||
private SignalType GetSignalType(IndicatorType type)
|
||||
{
|
||||
return type switch
|
||||
|
||||
Reference in New Issue
Block a user