mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-17 09:24:36 +08:00
feat: Add smart trading assistant with context awareness
- Add SmartAgent with automatic context injection - Real-time portfolio/position data in every prompt - AI knows current state before responding - Add TradingContext builder - Aggregates balance, positions, P&L across all traders - Auto-generates alerts (liquidation risk, large loss, etc.) - Add background Monitor - Proactive position monitoring every 30s - Detects new positions, closed positions - Forwards alerts to Telegram - Enhanced system prompts - Professional trading assistant persona - Risk assessment guidelines - Clear response formatting rules Features: ✅ Context-aware responses ✅ Proactive risk alerts ✅ Background monitoring ✅ Alert broadcasting to Telegram
This commit is contained in:
216
assistant/smart_agent.go
Normal file
216
assistant/smart_agent.go
Normal file
@@ -0,0 +1,216 @@
|
||||
package assistant
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"nofx/logger"
|
||||
"nofx/manager"
|
||||
"nofx/mcp"
|
||||
"nofx/store"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SmartAgent is an enhanced AI agent with trading context awareness
|
||||
type SmartAgent struct {
|
||||
*Agent
|
||||
contextBuilder *ContextBuilder
|
||||
monitor *Monitor
|
||||
|
||||
// Auto-inject context into prompts
|
||||
autoInjectContext bool
|
||||
}
|
||||
|
||||
// NewSmartAgent creates a new smart trading agent
|
||||
func NewSmartAgent(aiClient mcp.AIClient, config AgentConfig, tm *manager.TraderManager, st *store.Store) *SmartAgent {
|
||||
baseAgent := NewAgent(aiClient, config)
|
||||
baseAgent.SetSystemPrompt(SmartTradingPrompt())
|
||||
|
||||
contextBuilder := NewContextBuilder(tm, st)
|
||||
monitor := NewMonitor(tm, st)
|
||||
|
||||
return &SmartAgent{
|
||||
Agent: baseAgent,
|
||||
contextBuilder: contextBuilder,
|
||||
monitor: monitor,
|
||||
autoInjectContext: true,
|
||||
}
|
||||
}
|
||||
|
||||
// SetAutoInjectContext enables/disables automatic context injection
|
||||
func (sa *SmartAgent) SetAutoInjectContext(enabled bool) {
|
||||
sa.autoInjectContext = enabled
|
||||
}
|
||||
|
||||
// StartMonitor starts the background monitor
|
||||
func (sa *SmartAgent) StartMonitor() {
|
||||
sa.monitor.Start()
|
||||
}
|
||||
|
||||
// StopMonitor stops the background monitor
|
||||
func (sa *SmartAgent) StopMonitor() {
|
||||
sa.monitor.Stop()
|
||||
}
|
||||
|
||||
// OnAlert registers an alert callback
|
||||
func (sa *SmartAgent) OnAlert(callback func(Alert)) {
|
||||
sa.monitor.OnAlert(callback)
|
||||
}
|
||||
|
||||
// Chat processes a message with smart context injection
|
||||
func (sa *SmartAgent) Chat(ctx context.Context, sessionID string, userMessage string) (*AgentResponse, error) {
|
||||
session := sa.GetSession(sessionID)
|
||||
|
||||
// Add user message to history
|
||||
session.AddMessage(Message{
|
||||
Role: "user",
|
||||
Content: userMessage,
|
||||
Timestamp: time.Now(),
|
||||
})
|
||||
|
||||
// Build system prompt with tools
|
||||
systemPrompt := sa.buildSmartSystemPrompt()
|
||||
|
||||
// Build conversation prompt with context injection
|
||||
conversationPrompt := sa.buildSmartConversationPrompt(session, userMessage)
|
||||
|
||||
// Agent loop
|
||||
var finalResponse string
|
||||
toolCallCount := 0
|
||||
|
||||
for {
|
||||
if ctx.Err() != nil {
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
|
||||
if toolCallCount >= sa.config.MaxToolCalls {
|
||||
logger.Warnf("⚠️ Max tool calls reached (%d)", sa.config.MaxToolCalls)
|
||||
break
|
||||
}
|
||||
|
||||
response, err := sa.aiClient.CallWithMessages(systemPrompt, conversationPrompt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("AI call failed: %w", err)
|
||||
}
|
||||
|
||||
toolCalls, textResponse, err := sa.parseResponse(response)
|
||||
if err != nil {
|
||||
finalResponse = response
|
||||
break
|
||||
}
|
||||
|
||||
if len(toolCalls) == 0 {
|
||||
finalResponse = textResponse
|
||||
break
|
||||
}
|
||||
|
||||
// Execute tool calls
|
||||
toolResults := sa.executeToolCalls(ctx, toolCalls)
|
||||
toolCallCount += len(toolCalls)
|
||||
|
||||
// Add results to conversation
|
||||
conversationPrompt += fmt.Sprintf("\n\nAssistant called tools:\n%s\n\nTool results:\n%s\n\nBased on the tool results, provide a helpful response:",
|
||||
formatToolCalls(toolCalls),
|
||||
formatToolResults(toolResults))
|
||||
|
||||
if textResponse != "" {
|
||||
finalResponse = textResponse
|
||||
}
|
||||
}
|
||||
|
||||
// Add response to history
|
||||
session.AddMessage(Message{
|
||||
Role: "assistant",
|
||||
Content: finalResponse,
|
||||
Timestamp: time.Now(),
|
||||
})
|
||||
|
||||
return &AgentResponse{
|
||||
Text: finalResponse,
|
||||
SessionID: sessionID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// buildSmartSystemPrompt builds system prompt with tools
|
||||
func (sa *SmartAgent) buildSmartSystemPrompt() string {
|
||||
sa.toolsLock.RLock()
|
||||
defer sa.toolsLock.RUnlock()
|
||||
|
||||
var toolDefs []string
|
||||
for _, tool := range sa.tools {
|
||||
toolDef := fmt.Sprintf(`- **%s**: %s
|
||||
Parameters: %s`, tool.Name(), tool.Description(), tool.ParameterSchema())
|
||||
toolDefs = append(toolDefs, toolDef)
|
||||
}
|
||||
|
||||
toolsSection := ""
|
||||
if len(toolDefs) > 0 {
|
||||
toolsSection = fmt.Sprintf(`
|
||||
|
||||
## 可用工具
|
||||
|
||||
调用工具时,使用以下 JSON 格式:
|
||||
{"tool_calls": [{"name": "工具名", "arguments": {"参数": "值"}}]}
|
||||
|
||||
收到工具结果后,用自然语言回复用户。
|
||||
|
||||
可用工具:
|
||||
%s
|
||||
`, strings.Join(toolDefs, "\n"))
|
||||
}
|
||||
|
||||
return sa.systemPrompt + toolsSection
|
||||
}
|
||||
|
||||
// buildSmartConversationPrompt builds conversation with context injection
|
||||
func (sa *SmartAgent) buildSmartConversationPrompt(session *Session, currentMessage string) string {
|
||||
var sb strings.Builder
|
||||
|
||||
// Inject current trading context if enabled
|
||||
if sa.autoInjectContext {
|
||||
tradingCtx := sa.contextBuilder.BuildContext()
|
||||
sb.WriteString(tradingCtx.FormatContextForPrompt())
|
||||
}
|
||||
|
||||
// Add conversation history
|
||||
messages := session.GetMessages()
|
||||
for _, msg := range messages {
|
||||
sb.WriteString(fmt.Sprintf("\n%s: %s\n", strings.Title(msg.Role), msg.Content))
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// QuickStatus returns a quick status summary
|
||||
func (sa *SmartAgent) QuickStatus() string {
|
||||
ctx := sa.contextBuilder.BuildContext()
|
||||
|
||||
var sb strings.Builder
|
||||
sb.WriteString("📊 **交易状态概览**\n\n")
|
||||
|
||||
sb.WriteString(fmt.Sprintf("💰 总权益: $%.2f\n", ctx.TotalEquity))
|
||||
sb.WriteString(fmt.Sprintf("💵 可用余额: $%.2f\n", ctx.AvailableBalance))
|
||||
|
||||
if ctx.UnrealizedPnL >= 0 {
|
||||
sb.WriteString(fmt.Sprintf("📈 未实现盈亏: 🟢 +$%.2f\n", ctx.UnrealizedPnL))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("📉 未实现盈亏: 🔴 $%.2f\n", ctx.UnrealizedPnL))
|
||||
}
|
||||
|
||||
sb.WriteString(fmt.Sprintf("📍 持仓数: %d\n", len(ctx.Positions)))
|
||||
sb.WriteString(fmt.Sprintf("🤖 运行交易员: %d\n", len(ctx.ActiveTraders)))
|
||||
|
||||
if len(ctx.Alerts) > 0 {
|
||||
sb.WriteString("\n⚠️ **警报**\n")
|
||||
for _, alert := range ctx.Alerts {
|
||||
sb.WriteString(fmt.Sprintf("- %s\n", alert.Message))
|
||||
}
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// GetTradingContext returns current trading context
|
||||
func (sa *SmartAgent) GetTradingContext() *TradingContext {
|
||||
return sa.contextBuilder.BuildContext()
|
||||
}
|
||||
Reference in New Issue
Block a user