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();
|
var availableTools = await _mcpService.GetAvailableToolsAsync();
|
||||||
request.Tools = availableTools.ToList();
|
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
|
// Send chat request to LLM
|
||||||
var response = await _llmService.ChatAsync(user, request);
|
var response = await _llmService.ChatAsync(user, request);
|
||||||
|
|
||||||
|
|||||||
@@ -74,21 +74,25 @@ public class IndicatorTools
|
|||||||
{
|
{
|
||||||
try
|
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}");
|
throw new ArgumentException($"Information not available for indicator: {indicatorType}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var resolvedType = type.Value;
|
||||||
|
|
||||||
return new
|
return new
|
||||||
{
|
{
|
||||||
Type = type.ToString(),
|
Type = resolvedType.ToString(),
|
||||||
SignalType = GetSignalType(type).ToString(),
|
SignalType = GetSignalType(resolvedType).ToString(),
|
||||||
Category = GetCategory(type),
|
Category = GetCategory(resolvedType),
|
||||||
Name = info.Name,
|
Name = info.Name,
|
||||||
Description = info.Description,
|
Description = info.Description,
|
||||||
LongDescription = info.LongDescription,
|
LongDescription = info.LongDescription,
|
||||||
@@ -114,21 +118,24 @@ public class IndicatorTools
|
|||||||
{
|
{
|
||||||
try
|
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}");
|
throw new ArgumentException($"Information not available for indicator: {indicatorType}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var resolvedType = type.Value;
|
||||||
var explanation = new
|
var explanation = new
|
||||||
{
|
{
|
||||||
Type = type.ToString(),
|
Type = resolvedType.ToString(),
|
||||||
SignalType = GetSignalType(type).ToString(),
|
SignalType = GetSignalType(resolvedType).ToString(),
|
||||||
Category = GetCategory(type),
|
Category = GetCategory(resolvedType),
|
||||||
Name = info.Name,
|
Name = info.Name,
|
||||||
HowItWorks = info.LongDescription,
|
HowItWorks = info.LongDescription,
|
||||||
WhenToUse = info.UseCases,
|
WhenToUse = info.UseCases,
|
||||||
@@ -143,7 +150,7 @@ public class IndicatorTools
|
|||||||
}),
|
}),
|
||||||
RecommendedParameters = info.RecommendedParameters,
|
RecommendedParameters = info.RecommendedParameters,
|
||||||
TradingStyleRecommendations = info.TradingStyleRecommendations,
|
TradingStyleRecommendations = info.TradingStyleRecommendations,
|
||||||
Examples = includeExamples ? GetIndicatorExamples(type) : null
|
Examples = includeExamples ? GetIndicatorExamples(resolvedType) : null
|
||||||
};
|
};
|
||||||
|
|
||||||
return explanation;
|
return explanation;
|
||||||
@@ -229,16 +236,20 @@ public class IndicatorTools
|
|||||||
{
|
{
|
||||||
try
|
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}");
|
throw new ArgumentException($"Information not available for indicator: {indicatorType}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var resolvedType = type.Value;
|
||||||
|
|
||||||
var refinements = new List<ParameterRefinement>();
|
var refinements = new List<ParameterRefinement>();
|
||||||
|
|
||||||
foreach (var param in info.Parameters)
|
foreach (var param in info.Parameters)
|
||||||
@@ -249,9 +260,9 @@ public class IndicatorTools
|
|||||||
Description = param.Description,
|
Description = param.Description,
|
||||||
CurrentRecommended = param.RecommendedValue,
|
CurrentRecommended = param.RecommendedValue,
|
||||||
RefinedValues = GetRefinedParameterValues(
|
RefinedValues = GetRefinedParameterValues(
|
||||||
type, param.Name, tradingStyle, marketVolatility),
|
resolvedType, param.Name, tradingStyle, marketVolatility),
|
||||||
Reasoning = GetParameterRefinementReasoning(
|
Reasoning = GetParameterRefinementReasoning(
|
||||||
type, param.Name, tradingStyle, marketVolatility)
|
resolvedType, param.Name, tradingStyle, marketVolatility)
|
||||||
};
|
};
|
||||||
|
|
||||||
refinements.Add(refinement);
|
refinements.Add(refinement);
|
||||||
@@ -259,12 +270,12 @@ public class IndicatorTools
|
|||||||
|
|
||||||
return new
|
return new
|
||||||
{
|
{
|
||||||
IndicatorType = type.ToString(),
|
IndicatorType = resolvedType.ToString(),
|
||||||
SignalType = GetSignalType(type).ToString(),
|
SignalType = GetSignalType(resolvedType).ToString(),
|
||||||
TradingStyle = tradingStyle ?? "General",
|
TradingStyle = tradingStyle ?? "General",
|
||||||
MarketVolatility = marketVolatility ?? "Normal",
|
MarketVolatility = marketVolatility ?? "Normal",
|
||||||
ParameterRefinements = refinements,
|
ParameterRefinements = refinements,
|
||||||
GeneralAdvice = GetGeneralParameterAdvice(type, tradingStyle, marketVolatility)
|
GeneralAdvice = GetGeneralParameterAdvice(resolvedType, tradingStyle, marketVolatility)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -281,16 +292,39 @@ public class IndicatorTools
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var types = indicatorTypes.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
var typeStrings = indicatorTypes.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
||||||
.Select(t => t.Trim())
|
.Select(t => t.Trim())
|
||||||
.Where(t => Enum.TryParse<IndicatorType>(t, true, out _))
|
|
||||||
.Select(t => Enum.Parse<IndicatorType>(t, true))
|
|
||||||
.Distinct()
|
|
||||||
.ToList();
|
.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)
|
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 =>
|
var comparisons = types.Select(type =>
|
||||||
@@ -325,6 +359,91 @@ public class IndicatorTools
|
|||||||
|
|
||||||
#region Helper Methods
|
#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)
|
private SignalType GetSignalType(IndicatorType type)
|
||||||
{
|
{
|
||||||
return type switch
|
return type switch
|
||||||
|
|||||||
Reference in New Issue
Block a user