mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-07 13:00:59 +08:00
- 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
217 lines
5.5 KiB
Go
217 lines
5.5 KiB
Go
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()
|
||
}
|