Update LLM
This commit is contained in:
@@ -175,3 +175,6 @@ Task Environment (offset ports)
|
|||||||
2. Install dev-manager-mcp: See `docs/INSTALL_VIBE_KANBAN_AND_DEV_MANAGER.md`
|
2. Install dev-manager-mcp: See `docs/INSTALL_VIBE_KANBAN_AND_DEV_MANAGER.md`
|
||||||
3. Configure agent command: See `.cursor/commands/start-dev-env.md`
|
3. Configure agent command: See `.cursor/commands/start-dev-env.md`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
|
using Managing.Domain.Users;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
@@ -83,6 +85,12 @@ public class LlmController : BaseController
|
|||||||
};
|
};
|
||||||
request.Messages.Insert(0, systemMessage);
|
request.Messages.Insert(0, systemMessage);
|
||||||
|
|
||||||
|
// Proactively inject backtest details fetching if user is asking for analysis
|
||||||
|
await InjectBacktestDetailsFetchingIfNeeded(request, user);
|
||||||
|
|
||||||
|
// Add helpful context extraction message if backtest ID was found
|
||||||
|
AddBacktestContextGuidance(request);
|
||||||
|
|
||||||
// Iterative tool calling: keep looping until we get a final answer without tool calls
|
// Iterative tool calling: keep looping until we get a final answer without tool calls
|
||||||
// Use adaptive max iterations based on query complexity
|
// Use adaptive max iterations based on query complexity
|
||||||
int maxIterations = DetermineMaxIterations(request);
|
int maxIterations = DetermineMaxIterations(request);
|
||||||
@@ -271,16 +279,17 @@ public class LlmController : BaseController
|
|||||||
CRITICAL ANALYSIS WORKFLOW (APPLIES TO ALL DATA):
|
CRITICAL ANALYSIS WORKFLOW (APPLIES TO ALL DATA):
|
||||||
1. RETRIEVE COMPLETE DATA:
|
1. RETRIEVE COMPLETE DATA:
|
||||||
- When asked to analyze ANY entity, ALWAYS fetch FULL details first (never rely on summary/paginated data alone)
|
- When asked to analyze ANY entity, ALWAYS fetch FULL details first (never rely on summary/paginated data alone)
|
||||||
- Backtests: get_backtest_by_id() for positions + complete metrics
|
- Backtests: get_backtest_by_id(id='xxx') for positions + complete metrics
|
||||||
- Bundles: analyze_bundle_backtest() for aggregated statistics
|
- Bundles: analyze_bundle_backtest(bundleRequestId='xxx') for aggregated statistics
|
||||||
- Indicators: get_indicator_info() for detailed specs
|
- Indicators: get_indicator_info(indicatorType='RsiDivergence') for detailed specs - ALWAYS provide indicatorType parameter
|
||||||
- Use conversation context: "that X" or "this Y" → extract ID from previous messages
|
- Use conversation context: "that X" or "this Y" → extract ID from previous messages
|
||||||
|
|
||||||
2. CONTEXT EXTRACTION:
|
2. CONTEXT EXTRACTION:
|
||||||
- Pay attention to backtest IDs mentioned in conversation history
|
- Pay attention to IDs and names mentioned in conversation history (backtest IDs, indicator names, etc.)
|
||||||
- When user says "analyze this backtest" or "show me details", extract the backtest ID from previous messages
|
- When user says "analyze this backtest" or "show me details", extract the backtest ID from previous messages
|
||||||
|
- When user asks about an indicator by name (e.g., "What is RsiDivergence?"), extract the indicator type from the question
|
||||||
- If multiple backtests were listed, use the most recently mentioned one or the top-ranked one
|
- If multiple backtests were listed, use the most recently mentioned one or the top-ranked one
|
||||||
- NEVER ask user for IDs that were already provided in conversation
|
- NEVER ask user for IDs/names that were already provided in conversation
|
||||||
|
|
||||||
3. BACKTEST DETAIL WORKFLOW:
|
3. BACKTEST DETAIL WORKFLOW:
|
||||||
When user requests backtest details/analysis:
|
When user requests backtest details/analysis:
|
||||||
@@ -289,7 +298,15 @@ public class LlmController : BaseController
|
|||||||
c) If no ID but refers to "recent/latest" → call get_backtests_paginated(sortOrder='desc', pageSize=1) THEN get_backtest_by_id()
|
c) If no ID but refers to "recent/latest" → call get_backtests_paginated(sortOrder='desc', pageSize=1) THEN get_backtest_by_id()
|
||||||
d) If completely ambiguous → ask ONCE for clarification, then proceed
|
d) If completely ambiguous → ask ONCE for clarification, then proceed
|
||||||
|
|
||||||
4. ANALYZE WITH EXPERTISE:
|
4. INDICATOR WORKFLOW:
|
||||||
|
When user asks about indicators:
|
||||||
|
a) General questions ("What indicators exist?") → call list_indicators()
|
||||||
|
b) Specific indicator ("What is RsiDivergence?") → extract name from question, call get_indicator_info(indicatorType='RsiDivergence')
|
||||||
|
c) Explaining usage ("Explain MACD") → extract name, call explain_indicator(indicatorType='Macd')
|
||||||
|
d) NEVER call get_indicator_info() without indicatorType parameter
|
||||||
|
e) If indicator name is unclear → call list_indicators() first to show available options
|
||||||
|
|
||||||
|
5. ANALYZE WITH EXPERTISE:
|
||||||
After retrieving data, provide comprehensive analysis:
|
After retrieving data, provide comprehensive analysis:
|
||||||
|
|
||||||
Backtests: Performance (PnL, growth, ROI), Risk (Sharpe, drawdown), Win rate, Position patterns, Trade duration, Strengths/weaknesses, Recommendations
|
Backtests: Performance (PnL, growth, ROI), Risk (Sharpe, drawdown), Win rate, Position patterns, Trade duration, Strengths/weaknesses, Recommendations
|
||||||
@@ -297,7 +314,7 @@ public class LlmController : BaseController
|
|||||||
Indicators: Use cases, Parameter sensitivity, Combination suggestions, Pitfalls
|
Indicators: Use cases, Parameter sensitivity, Combination suggestions, Pitfalls
|
||||||
General: Compare to benchmarks, Statistical significance, Actionable insights
|
General: Compare to benchmarks, Statistical significance, Actionable insights
|
||||||
|
|
||||||
5. BE PROACTIVE:
|
6. BE PROACTIVE:
|
||||||
- Execute multiple tool iterations for complete data
|
- Execute multiple tool iterations for complete data
|
||||||
- Interpret data, don't just return it
|
- Interpret data, don't just return it
|
||||||
- Chain tool calls automatically (list → get_by_id → analyze)
|
- Chain tool calls automatically (list → get_by_id → analyze)
|
||||||
@@ -338,4 +355,234 @@ public class LlmController : BaseController
|
|||||||
|
|
||||||
request.Messages = trimmedMessages;
|
request.Messages = trimmedMessages;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Proactively injects backtest details fetching when user asks for backtest analysis.
|
||||||
|
/// Extracts backtest IDs from message content and automatically calls get_backtest_by_id.
|
||||||
|
/// </summary>
|
||||||
|
private async Task InjectBacktestDetailsFetchingIfNeeded(LlmChatRequest request, User user)
|
||||||
|
{
|
||||||
|
var lastUserMessage = request.Messages.LastOrDefault(m => m.Role == "user");
|
||||||
|
if (lastUserMessage == null || string.IsNullOrWhiteSpace(lastUserMessage.Content))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var lastMessageLower = lastUserMessage.Content.ToLowerInvariant();
|
||||||
|
|
||||||
|
// Check if user is asking for backtest analysis/details
|
||||||
|
var isRequestingBacktestDetails =
|
||||||
|
(lastMessageLower.Contains("backtest") || lastMessageLower.Contains("bt")) &&
|
||||||
|
(lastMessageLower.Contains("analyz") || lastMessageLower.Contains("detail") ||
|
||||||
|
lastMessageLower.Contains("show") || lastMessageLower.Contains("look at") ||
|
||||||
|
lastMessageLower.Contains("explain") || lastMessageLower.Contains("performance") ||
|
||||||
|
lastMessageLower.Contains("this") || lastMessageLower.Contains("that"));
|
||||||
|
|
||||||
|
if (!isRequestingBacktestDetails)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Extract backtest ID from conversation context
|
||||||
|
var backtestId = ExtractBacktestIdFromConversation(request.Messages);
|
||||||
|
if (string.IsNullOrEmpty(backtestId))
|
||||||
|
{
|
||||||
|
_logger.LogInformation("User requested backtest analysis but no backtest ID found in conversation context");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("Proactively fetching backtest details for ID: {BacktestId}", backtestId);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Execute get_backtest_by_id tool to fetch complete details
|
||||||
|
var backtestDetails = await _mcpService.ExecuteToolAsync(
|
||||||
|
user,
|
||||||
|
"get_backtest_by_id",
|
||||||
|
new Dictionary<string, object> { ["id"] = backtestId }
|
||||||
|
);
|
||||||
|
|
||||||
|
_logger.LogInformation("Successfully fetched backtest details for ID: {BacktestId}. Result type: {ResultType}",
|
||||||
|
backtestId, backtestDetails?.GetType().Name ?? "null");
|
||||||
|
|
||||||
|
// Inject the backtest details as a tool result in the conversation
|
||||||
|
var toolCallId = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
// Add assistant message indicating tool was called
|
||||||
|
request.Messages.Add(new LlmMessage
|
||||||
|
{
|
||||||
|
Role = "assistant",
|
||||||
|
Content = "I'll analyze the complete backtest details for you.",
|
||||||
|
ToolCalls = new List<LlmToolCall>
|
||||||
|
{
|
||||||
|
new LlmToolCall
|
||||||
|
{
|
||||||
|
Id = toolCallId,
|
||||||
|
Name = "get_backtest_by_id",
|
||||||
|
Arguments = new Dictionary<string, object> { ["id"] = backtestId }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add tool result message
|
||||||
|
var serializedResult = JsonSerializer.Serialize(backtestDetails);
|
||||||
|
_logger.LogInformation("Serialized backtest details length: {Length} characters", serializedResult.Length);
|
||||||
|
|
||||||
|
request.Messages.Add(new LlmMessage
|
||||||
|
{
|
||||||
|
Role = "tool",
|
||||||
|
Content = serializedResult,
|
||||||
|
ToolCallId = toolCallId
|
||||||
|
});
|
||||||
|
|
||||||
|
_logger.LogInformation("Successfully injected backtest details into conversation for ID: {BacktestId}", backtestId);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error fetching backtest details for ID: {BacktestId}", backtestId);
|
||||||
|
|
||||||
|
// Inject an error message so LLM knows what happened
|
||||||
|
var toolCallId = Guid.NewGuid().ToString();
|
||||||
|
request.Messages.Add(new LlmMessage
|
||||||
|
{
|
||||||
|
Role = "assistant",
|
||||||
|
Content = "Let me try to fetch the backtest details.",
|
||||||
|
ToolCalls = new List<LlmToolCall>
|
||||||
|
{
|
||||||
|
new LlmToolCall
|
||||||
|
{
|
||||||
|
Id = toolCallId,
|
||||||
|
Name = "get_backtest_by_id",
|
||||||
|
Arguments = new Dictionary<string, object> { ["id"] = backtestId }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
request.Messages.Add(new LlmMessage
|
||||||
|
{
|
||||||
|
Role = "tool",
|
||||||
|
Content = $"Error: Failed to fetch backtest details - {ex.Message}",
|
||||||
|
ToolCallId = toolCallId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds guidance to the LLM if a backtest ID is present in the message
|
||||||
|
/// </summary>
|
||||||
|
private void AddBacktestContextGuidance(LlmChatRequest request)
|
||||||
|
{
|
||||||
|
var lastUserMessage = request.Messages.LastOrDefault(m => m.Role == "user");
|
||||||
|
if (lastUserMessage == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var backtestId = ExtractBacktestIdFromConversation(request.Messages);
|
||||||
|
if (string.IsNullOrEmpty(backtestId))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Check if we already injected tool results
|
||||||
|
var hasToolResults = request.Messages.Any(m =>
|
||||||
|
m.Role == "tool" &&
|
||||||
|
m.Content?.Contains(backtestId) == true);
|
||||||
|
|
||||||
|
if (hasToolResults)
|
||||||
|
{
|
||||||
|
// Add a system reminder that the data is already available
|
||||||
|
request.Messages.Add(new LlmMessage
|
||||||
|
{
|
||||||
|
Role = "system",
|
||||||
|
Content = $"Note: Complete backtest details for ID '{backtestId}' have been fetched and are available in the conversation context. Use this data for your analysis."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extracts backtest ID from conversation messages by looking for patterns like:
|
||||||
|
/// - "Backtest ID: xxx"
|
||||||
|
/// - JSON objects containing backtest IDs
|
||||||
|
/// - Direct mentions of IDs in recent context
|
||||||
|
/// </summary>
|
||||||
|
private string? ExtractBacktestIdFromConversation(List<LlmMessage> messages)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Extracting backtest ID from {Count} messages", messages.Count);
|
||||||
|
|
||||||
|
// Look through messages in reverse order (most recent first)
|
||||||
|
for (int i = messages.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
var message = messages[i];
|
||||||
|
if (string.IsNullOrWhiteSpace(message.Content))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var content = message.Content;
|
||||||
|
_logger.LogDebug("Checking message {Index} (Role: {Role}): {Preview}",
|
||||||
|
i, message.Role, content.Length > 100 ? content.Substring(0, 100) + "..." : content);
|
||||||
|
|
||||||
|
// Try to extract from JSON in tool results (most reliable)
|
||||||
|
if (message.Role == "tool")
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var doc = JsonDocument.Parse(content);
|
||||||
|
var root = doc.RootElement;
|
||||||
|
|
||||||
|
// Check if it's a single backtest object with an Id property
|
||||||
|
if (root.TryGetProperty("id", out var idElement))
|
||||||
|
{
|
||||||
|
return idElement.GetString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it's an array/list of backtests
|
||||||
|
if (root.ValueKind == JsonValueKind.Array)
|
||||||
|
{
|
||||||
|
var firstItem = root.EnumerateArray().FirstOrDefault();
|
||||||
|
if (firstItem.ValueKind != JsonValueKind.Undefined &&
|
||||||
|
firstItem.TryGetProperty("id", out var firstId))
|
||||||
|
{
|
||||||
|
return firstId.GetString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for nested items (paginated results)
|
||||||
|
if (root.TryGetProperty("items", out var itemsElement) &&
|
||||||
|
itemsElement.ValueKind == JsonValueKind.Array)
|
||||||
|
{
|
||||||
|
var firstItem = itemsElement.EnumerateArray().FirstOrDefault();
|
||||||
|
if (firstItem.ValueKind != JsonValueKind.Undefined &&
|
||||||
|
firstItem.TryGetProperty("id", out var firstId))
|
||||||
|
{
|
||||||
|
return firstId.GetString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Not valid JSON or doesn't contain expected structure, continue searching
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to extract from text patterns
|
||||||
|
// Pattern: "Backtest ID: <guid>" or "ID: <guid>"
|
||||||
|
var idPattern = @"(?:backtest\s+)?id[:\s]+([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})";
|
||||||
|
var match = Regex.Match(content, idPattern,
|
||||||
|
RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
|
if (match.Success)
|
||||||
|
{
|
||||||
|
var extractedId = match.Groups[1].Value;
|
||||||
|
_logger.LogInformation("Extracted backtest ID from text pattern: {BacktestId}", extractedId);
|
||||||
|
return extractedId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pattern: standalone GUID that might be a backtest ID
|
||||||
|
var guidPattern = @"\b([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})\b";
|
||||||
|
var guidMatch = Regex.Match(content, guidPattern,
|
||||||
|
RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
|
if (guidMatch.Success && i >= messages.Count - 5) // Only use standalone GUIDs from recent messages
|
||||||
|
{
|
||||||
|
var extractedId = guidMatch.Groups[1].Value;
|
||||||
|
_logger.LogInformation("Extracted backtest ID from standalone GUID: {BacktestId}", extractedId);
|
||||||
|
return extractedId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogWarning("No backtest ID found in conversation messages");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user