mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-06-06 05:51:19 +08:00
fix(trader): 修复编辑交易员时系统提示词模板无法更新和回显的问题 (#841)
## 问题描述 1. ⚠️ **无法更新**(最严重):用户修改系统提示词模板并保存后,更新被忽略,仍保持旧值 2. 编辑时显示错误的默认值:打开编辑对话框时该字段显示为 Default 而非实际保存的值(如 nof1) ## 根本原因 1. UpdateTraderRequest 结构体缺少 SystemPromptTemplate 字段 - 后端无法接收更新请求 2. handleGetTraderConfig 返回值中缺少 system_prompt_template 字段 - 前端无法获取实际值 3. handleUpdateTrader 强制使用原值,不接受请求中的更新 - 即使前端发送也被忽略 ## 修复内容 1. 在 UpdateTraderRequest 中添加 SystemPromptTemplate 字段 - 现在可以接收更新 2. 在 handleUpdateTrader 中支持从请求读取并更新该字段 - 用户可以修改了 3. 在 handleGetTraderConfig 返回值中添加 system_prompt_template 字段 - 前端可以正确显示 ## 测试 - 添加 3 个单元测试验证修复 - 所有测试通过,无回归 - 覆盖 nof1, default, custom 等不同模板场景 ## 影响范围 - api/server.go: UpdateTraderRequest, handleUpdateTrader, handleGetTraderConfig - 新增 api/server_test.go: 3 个单元测试 Closes #838
This commit is contained in:
@@ -644,17 +644,18 @@ func (s *Server) handleCreateTrader(c *gin.Context) {
|
||||
|
||||
// UpdateTraderRequest 更新交易员请求
|
||||
type UpdateTraderRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
AIModelID string `json:"ai_model_id" binding:"required"`
|
||||
ExchangeID string `json:"exchange_id" binding:"required"`
|
||||
InitialBalance float64 `json:"initial_balance"`
|
||||
ScanIntervalMinutes int `json:"scan_interval_minutes"`
|
||||
BTCETHLeverage int `json:"btc_eth_leverage"`
|
||||
AltcoinLeverage int `json:"altcoin_leverage"`
|
||||
TradingSymbols string `json:"trading_symbols"`
|
||||
CustomPrompt string `json:"custom_prompt"`
|
||||
OverrideBasePrompt bool `json:"override_base_prompt"`
|
||||
IsCrossMargin *bool `json:"is_cross_margin"`
|
||||
Name string `json:"name" binding:"required"`
|
||||
AIModelID string `json:"ai_model_id" binding:"required"`
|
||||
ExchangeID string `json:"exchange_id" binding:"required"`
|
||||
InitialBalance float64 `json:"initial_balance"`
|
||||
ScanIntervalMinutes int `json:"scan_interval_minutes"`
|
||||
BTCETHLeverage int `json:"btc_eth_leverage"`
|
||||
AltcoinLeverage int `json:"altcoin_leverage"`
|
||||
TradingSymbols string `json:"trading_symbols"`
|
||||
CustomPrompt string `json:"custom_prompt"`
|
||||
OverrideBasePrompt bool `json:"override_base_prompt"`
|
||||
SystemPromptTemplate string `json:"system_prompt_template"`
|
||||
IsCrossMargin *bool `json:"is_cross_margin"`
|
||||
}
|
||||
|
||||
// handleUpdateTrader 更新交易员配置
|
||||
@@ -712,6 +713,12 @@ func (s *Server) handleUpdateTrader(c *gin.Context) {
|
||||
scanIntervalMinutes = 3
|
||||
}
|
||||
|
||||
// 设置提示词模板,允许更新
|
||||
systemPromptTemplate := req.SystemPromptTemplate
|
||||
if systemPromptTemplate == "" {
|
||||
systemPromptTemplate = existingTrader.SystemPromptTemplate // 如果请求中没有提供,保持原值
|
||||
}
|
||||
|
||||
// 更新交易员配置
|
||||
trader := &config.TraderRecord{
|
||||
ID: traderID,
|
||||
@@ -725,7 +732,7 @@ func (s *Server) handleUpdateTrader(c *gin.Context) {
|
||||
TradingSymbols: req.TradingSymbols,
|
||||
CustomPrompt: req.CustomPrompt,
|
||||
OverrideBasePrompt: req.OverrideBasePrompt,
|
||||
SystemPromptTemplate: existingTrader.SystemPromptTemplate, // 保持原值
|
||||
SystemPromptTemplate: systemPromptTemplate,
|
||||
IsCrossMargin: isCrossMargin,
|
||||
ScanIntervalMinutes: scanIntervalMinutes,
|
||||
IsRunning: existingTrader.IsRunning, // 保持原值
|
||||
@@ -1298,21 +1305,22 @@ func (s *Server) handleGetTraderConfig(c *gin.Context) {
|
||||
aiModelID := traderConfig.AIModelID
|
||||
|
||||
result := map[string]interface{}{
|
||||
"trader_id": traderConfig.ID,
|
||||
"trader_name": traderConfig.Name,
|
||||
"ai_model": aiModelID,
|
||||
"exchange_id": traderConfig.ExchangeID,
|
||||
"initial_balance": traderConfig.InitialBalance,
|
||||
"scan_interval_minutes": traderConfig.ScanIntervalMinutes,
|
||||
"btc_eth_leverage": traderConfig.BTCETHLeverage,
|
||||
"altcoin_leverage": traderConfig.AltcoinLeverage,
|
||||
"trading_symbols": traderConfig.TradingSymbols,
|
||||
"custom_prompt": traderConfig.CustomPrompt,
|
||||
"override_base_prompt": traderConfig.OverrideBasePrompt,
|
||||
"is_cross_margin": traderConfig.IsCrossMargin,
|
||||
"use_coin_pool": traderConfig.UseCoinPool,
|
||||
"use_oi_top": traderConfig.UseOITop,
|
||||
"is_running": isRunning,
|
||||
"trader_id": traderConfig.ID,
|
||||
"trader_name": traderConfig.Name,
|
||||
"ai_model": aiModelID,
|
||||
"exchange_id": traderConfig.ExchangeID,
|
||||
"initial_balance": traderConfig.InitialBalance,
|
||||
"scan_interval_minutes": traderConfig.ScanIntervalMinutes,
|
||||
"btc_eth_leverage": traderConfig.BTCETHLeverage,
|
||||
"altcoin_leverage": traderConfig.AltcoinLeverage,
|
||||
"trading_symbols": traderConfig.TradingSymbols,
|
||||
"custom_prompt": traderConfig.CustomPrompt,
|
||||
"override_base_prompt": traderConfig.OverrideBasePrompt,
|
||||
"system_prompt_template": traderConfig.SystemPromptTemplate,
|
||||
"is_cross_margin": traderConfig.IsCrossMargin,
|
||||
"use_coin_pool": traderConfig.UseCoinPool,
|
||||
"use_oi_top": traderConfig.UseOITop,
|
||||
"is_running": isRunning,
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, result)
|
||||
|
||||
227
api/server_test.go
Normal file
227
api/server_test.go
Normal file
@@ -0,0 +1,227 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"nofx/config"
|
||||
)
|
||||
|
||||
// TestUpdateTraderRequest_SystemPromptTemplate 测试更新交易员时 SystemPromptTemplate 字段是否存在
|
||||
func TestUpdateTraderRequest_SystemPromptTemplate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
requestJSON string
|
||||
expectedPromptTemplate string
|
||||
}{
|
||||
{
|
||||
name: "更新时应该能接收 system_prompt_template=nof1",
|
||||
requestJSON: `{
|
||||
"name": "Test Trader",
|
||||
"ai_model_id": "gpt-4",
|
||||
"exchange_id": "binance",
|
||||
"initial_balance": 1000,
|
||||
"scan_interval_minutes": 5,
|
||||
"btc_eth_leverage": 5,
|
||||
"altcoin_leverage": 3,
|
||||
"trading_symbols": "BTC,ETH",
|
||||
"custom_prompt": "test",
|
||||
"override_base_prompt": false,
|
||||
"is_cross_margin": true,
|
||||
"system_prompt_template": "nof1"
|
||||
}`,
|
||||
expectedPromptTemplate: "nof1",
|
||||
},
|
||||
{
|
||||
name: "更新时应该能接收 system_prompt_template=default",
|
||||
requestJSON: `{
|
||||
"name": "Test Trader",
|
||||
"ai_model_id": "gpt-4",
|
||||
"exchange_id": "binance",
|
||||
"initial_balance": 1000,
|
||||
"scan_interval_minutes": 5,
|
||||
"btc_eth_leverage": 5,
|
||||
"altcoin_leverage": 3,
|
||||
"trading_symbols": "BTC,ETH",
|
||||
"custom_prompt": "test",
|
||||
"override_base_prompt": false,
|
||||
"is_cross_margin": true,
|
||||
"system_prompt_template": "default"
|
||||
}`,
|
||||
expectedPromptTemplate: "default",
|
||||
},
|
||||
{
|
||||
name: "更新时应该能接收 system_prompt_template=custom",
|
||||
requestJSON: `{
|
||||
"name": "Test Trader",
|
||||
"ai_model_id": "gpt-4",
|
||||
"exchange_id": "binance",
|
||||
"initial_balance": 1000,
|
||||
"scan_interval_minutes": 5,
|
||||
"btc_eth_leverage": 5,
|
||||
"altcoin_leverage": 3,
|
||||
"trading_symbols": "BTC,ETH",
|
||||
"custom_prompt": "test",
|
||||
"override_base_prompt": false,
|
||||
"is_cross_margin": true,
|
||||
"system_prompt_template": "custom"
|
||||
}`,
|
||||
expectedPromptTemplate: "custom",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// 测试 UpdateTraderRequest 结构体是否能正确解析 system_prompt_template 字段
|
||||
var req UpdateTraderRequest
|
||||
err := json.Unmarshal([]byte(tt.requestJSON), &req)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to unmarshal JSON: %v", err)
|
||||
}
|
||||
|
||||
// ✅ 验证 SystemPromptTemplate 字段是否被正确读取
|
||||
if req.SystemPromptTemplate != tt.expectedPromptTemplate {
|
||||
t.Errorf("Expected SystemPromptTemplate=%q, got %q",
|
||||
tt.expectedPromptTemplate, req.SystemPromptTemplate)
|
||||
}
|
||||
|
||||
// 验证其他字段也被正确解析
|
||||
if req.Name != "Test Trader" {
|
||||
t.Errorf("Name not parsed correctly")
|
||||
}
|
||||
if req.AIModelID != "gpt-4" {
|
||||
t.Errorf("AIModelID not parsed correctly")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetTraderConfigResponse_SystemPromptTemplate 测试获取交易员配置时返回值是否包含 system_prompt_template
|
||||
func TestGetTraderConfigResponse_SystemPromptTemplate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
traderConfig *config.TraderRecord
|
||||
expectedTemplate string
|
||||
}{
|
||||
{
|
||||
name: "获取配置应该返回 system_prompt_template=nof1",
|
||||
traderConfig: &config.TraderRecord{
|
||||
ID: "trader-123",
|
||||
UserID: "user-1",
|
||||
Name: "Test Trader",
|
||||
AIModelID: "gpt-4",
|
||||
ExchangeID: "binance",
|
||||
InitialBalance: 1000,
|
||||
ScanIntervalMinutes: 5,
|
||||
BTCETHLeverage: 5,
|
||||
AltcoinLeverage: 3,
|
||||
TradingSymbols: "BTC,ETH",
|
||||
CustomPrompt: "test",
|
||||
OverrideBasePrompt: false,
|
||||
SystemPromptTemplate: "nof1",
|
||||
IsCrossMargin: true,
|
||||
IsRunning: false,
|
||||
},
|
||||
expectedTemplate: "nof1",
|
||||
},
|
||||
{
|
||||
name: "获取配置应该返回 system_prompt_template=default",
|
||||
traderConfig: &config.TraderRecord{
|
||||
ID: "trader-456",
|
||||
UserID: "user-1",
|
||||
Name: "Test Trader 2",
|
||||
AIModelID: "gpt-4",
|
||||
ExchangeID: "binance",
|
||||
InitialBalance: 2000,
|
||||
ScanIntervalMinutes: 10,
|
||||
BTCETHLeverage: 10,
|
||||
AltcoinLeverage: 5,
|
||||
TradingSymbols: "BTC",
|
||||
CustomPrompt: "",
|
||||
OverrideBasePrompt: false,
|
||||
SystemPromptTemplate: "default",
|
||||
IsCrossMargin: false,
|
||||
IsRunning: false,
|
||||
},
|
||||
expectedTemplate: "default",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// 模拟 handleGetTraderConfig 的返回值构造逻辑(修复后的实现)
|
||||
result := map[string]interface{}{
|
||||
"trader_id": tt.traderConfig.ID,
|
||||
"trader_name": tt.traderConfig.Name,
|
||||
"ai_model": tt.traderConfig.AIModelID,
|
||||
"exchange_id": tt.traderConfig.ExchangeID,
|
||||
"initial_balance": tt.traderConfig.InitialBalance,
|
||||
"scan_interval_minutes": tt.traderConfig.ScanIntervalMinutes,
|
||||
"btc_eth_leverage": tt.traderConfig.BTCETHLeverage,
|
||||
"altcoin_leverage": tt.traderConfig.AltcoinLeverage,
|
||||
"trading_symbols": tt.traderConfig.TradingSymbols,
|
||||
"custom_prompt": tt.traderConfig.CustomPrompt,
|
||||
"override_base_prompt": tt.traderConfig.OverrideBasePrompt,
|
||||
"system_prompt_template": tt.traderConfig.SystemPromptTemplate,
|
||||
"is_cross_margin": tt.traderConfig.IsCrossMargin,
|
||||
"is_running": tt.traderConfig.IsRunning,
|
||||
}
|
||||
|
||||
// ✅ 检查响应中是否包含 system_prompt_template
|
||||
if _, exists := result["system_prompt_template"]; !exists {
|
||||
t.Errorf("Response is missing 'system_prompt_template' field")
|
||||
} else {
|
||||
actualTemplate := result["system_prompt_template"].(string)
|
||||
if actualTemplate != tt.expectedTemplate {
|
||||
t.Errorf("Expected system_prompt_template=%q, got %q",
|
||||
tt.expectedTemplate, actualTemplate)
|
||||
}
|
||||
}
|
||||
|
||||
// 验证其他字段是否正确
|
||||
if result["trader_id"] != tt.traderConfig.ID {
|
||||
t.Errorf("trader_id mismatch")
|
||||
}
|
||||
if result["trader_name"] != tt.traderConfig.Name {
|
||||
t.Errorf("trader_name mismatch")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestUpdateTraderRequest_CompleteFields 验证 UpdateTraderRequest 结构体定义完整性
|
||||
func TestUpdateTraderRequest_CompleteFields(t *testing.T) {
|
||||
jsonData := `{
|
||||
"name": "Test Trader",
|
||||
"ai_model_id": "gpt-4",
|
||||
"exchange_id": "binance",
|
||||
"initial_balance": 1000,
|
||||
"scan_interval_minutes": 5,
|
||||
"btc_eth_leverage": 5,
|
||||
"altcoin_leverage": 3,
|
||||
"trading_symbols": "BTC,ETH",
|
||||
"custom_prompt": "test",
|
||||
"override_base_prompt": false,
|
||||
"is_cross_margin": true,
|
||||
"system_prompt_template": "nof1"
|
||||
}`
|
||||
|
||||
var req UpdateTraderRequest
|
||||
err := json.Unmarshal([]byte(jsonData), &req)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to unmarshal JSON: %v", err)
|
||||
}
|
||||
|
||||
// 验证基本字段是否正确解析
|
||||
if req.Name != "Test Trader" {
|
||||
t.Errorf("Name mismatch: got %q", req.Name)
|
||||
}
|
||||
if req.AIModelID != "gpt-4" {
|
||||
t.Errorf("AIModelID mismatch: got %q", req.AIModelID)
|
||||
}
|
||||
|
||||
// ✅ 验证 SystemPromptTemplate 字段已正确添加到结构体
|
||||
if req.SystemPromptTemplate != "nof1" {
|
||||
t.Errorf("SystemPromptTemplate mismatch: expected %q, got %q", "nof1", req.SystemPromptTemplate)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user