1 line
102 KiB
JSON
1 line
102 KiB
JSON
{"repo": "carayev/nofxaios", "files": {"mcp/client.go": {"324cb9d1f4aad8ec759fd769dea155eb30aee449": {"t": 1785746058.6641197, "n": 7162, "h": "b822836ea86e20c2", "c": "package mcp\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n)\n\n// Provider AI提供商类型\ntype Provider string\n\nconst (\n\tProviderDeepSeek Provider = \"deepseek\"\n\tProviderQwen Provider = \"qwen\"\n\tProviderCustom Provider = \"custom\"\n)\n\n// Client AI API配置\ntype Client struct {\n\tProvider Provider\n\tAPIKey string\n\tBaseURL string\n\tModel string\n\tTimeout time.Duration\n\tUseFullURL bool // 是否使用完整URL(不添加/chat/completions)\n}\n\nfunc New() *Client {\n\t// 默认配置\n\treturn &Client{\n\t\tProvider: ProviderDeepSeek,\n\t\tBaseURL: \"https://api.deepseek.com/v1\",\n\t\tModel: \"deepseek-chat\",\n\t\tTimeout: 120 * time.Second, // 增加到120秒,因为AI需要分析大量数据\n\t}\n}\n\n// SetDeepSeekAPIKey 设置DeepSeek API密钥\n// customURL 为空时使用默认URL,customModel 为空时使用默认模型\nfunc (client *Client) SetDeepSeekAPIKey(apiKey string, customURL string, customModel string) {\n\tclient.Provider = ProviderDeepSeek\n\tclient.APIKey = apiKey\n\tif customURL != \"\" {\n\t\tclient.BaseURL = customURL\n\t\tlog.Printf(\"🔧 [MCP] DeepSeek 使用自定义 BaseURL: %s\", customURL)\n\t} else {\n\t\tclient.BaseURL = \"https://api.deepseek.com/v1\"\n\t\tlog.Printf(\"🔧 [MCP] DeepSeek 使用默认 BaseURL: %s\", client.BaseURL)\n\t}\n\tif customModel != \"\" {\n\t\tclient.Model = customModel\n\t\tlog.Printf(\"🔧 [MCP] DeepSeek 使用自定义 Model: %s\", customModel)\n\t} else {\n\t\tclient.Model = \"deepseek-chat\"\n\t\tlog.Printf(\"🔧 [MCP] DeepSeek 使用默认 Model: %s\", client.Model)\n\t}\n\t// 打印 API Key 的前后各4位用于验证\n\tif len(apiKey) > 8 {\n\t\tlog.Printf(\"🔧 [MCP] DeepSeek API Key: %s...%s\", apiKey[:4], apiKey[len(apiKey)-4:])\n\t}\n}\n\n// SetQwenAPIKey 设置阿里云Qwen API密钥\n// customURL 为空时使用默认URL,customModel 为空时使用默认模型\nfunc (client *Client) SetQwenAPIKey(apiKey string, customURL string, customModel string) {\n\tclient.Provider = ProviderQwen\n\tclient.APIKey = apiKey\n\tif customURL != \"\" {\n\t\tclient.BaseURL = customURL\n\t\tlog.Printf(\"🔧 [MCP] Qwen 使用自定义 BaseURL: %s\", customURL)\n\t} else {\n\t\tclient.BaseURL = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\n\t\tlog.Printf(\"🔧 [MCP] Qwen 使用默认 BaseURL: %s\", client.BaseURL)\n\t}\n\tif customModel != \"\" {\n\t\tclient.Model = customModel\n\t\tlog.Printf(\"🔧 [MCP] Qwen 使用自定义 Model: %s\", customModel)\n\t} else {\n\t\tclient.Model = \"qwen-plus\" // 可选: qwen-turbo, qwen-plus, qwen-max\n\t\tlog.Printf(\"🔧 [MCP] Qwen 使用默认 Model: %s\", client.Model)\n\t}\n\t// 打印 API Key 的前后各4位用于验证\n\tif len(apiKey) > 8 {\n\t\tlog.Printf(\"🔧 [MCP] Qwen API Key: %s...%s\", apiKey[:4], apiKey[len(apiKey)-4:])\n\t}\n}\n\n// SetCustomAPI 设置自定义OpenAI兼容API\nfunc (client *Client) SetCustomAPI(apiURL, apiKey, modelName string) {\n\tclient.Provider = ProviderCustom\n\tclient.APIKey = apiKey\n\n\t// 检查URL是否以#结尾,如果是则使用完整URL(不添加/chat/completions)\n\tif strings.HasSuffix(apiURL, \"#\") {\n\t\tclient.BaseURL = strings.TrimSuffix(apiURL, \"#\")\n\t\tclient.UseFullURL = true\n\t} else {\n\t\tclient.BaseURL = apiURL\n\t\tclient.UseFullURL = false\n\t}\n\n\tclient.Model = modelName\n\tclient.Timeout = 120 * time.Second\n}\n\n// SetClient 设置完整的AI配置(高级用户)\nfunc (client *Client) SetClient(Client Client) {\n\tif Client.Timeout == 0 {\n\t\tClient.Timeout = 30 * time.Second\n\t}\n\tclient = &Client\n}\n\n// CallWithMessages 使用 system + user prompt 调用AI API(推荐)\nfunc (client *Client) CallWithMessages(systemPrompt, userPrompt string) (string, error) {\n\tif client.APIKey == \"\" {\n\t\treturn \"\", fmt.Errorf(\"AI API密钥未设置,请先调用 SetDeepSeekAPIKey() 或 SetQwenAPIKey()\")\n\t}\n\n\t// 重试配置\n\tmaxRetries := 3\n\tvar lastErr error\n\n\tfor attempt := 1; attempt <= maxRetries; attempt++ {\n\t\tif attempt > 1 {\n\t\t\tfmt.Printf(\"⚠️ AI API调用失败,正在重试 (%d/%d)...\\n\", attempt, maxRetries)\n\t\t}\n\n\t\tresult, err := client.callOnce(systemPrompt, userPrompt)\n\t\tif err == nil {\n\t\t\tif attempt > 1 {\n\t\t\t\tfmt.Printf(\"✓ AI API重试成功\\n\")\n\t\t\t}\n\t\t\treturn result, nil\n\t\t}\n\n\t\tlastErr = err\n\t\t// 如果不是网络错误,不重试\n\t\tif !isRetryableError(err) {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\t// 重试前等待\n\t\tif attempt < maxRetries {\n\t\t\twaitTime := time.Duration(attempt) * 2 * time.Second\n\t\t\tfmt.Printf(\"⏳ 等待%v后重试...\\n\", waitTime)\n\t\t\ttime.Sleep(waitTime)\n\t\t}\n\t}\n\n\treturn \"\", fmt.Errorf(\"重试%d次后仍然失败: %w\", maxRetries, lastErr)\n}\n\n// callOnce 单次调用AI API(内部使用)\nfunc (client *Client) callOnce(systemPrompt, userPrompt string) (string, error) {\n\t// 打印当前 AI 配置\n\tlog.Printf(\"📡 [MCP] AI 请求配置:\")\n\tlog.Printf(\" Provider: %s\", client.Provider)\n\tlog.Printf(\" BaseURL: %s\", client.BaseURL)\n\tlog.Printf(\" Model: %s\", client.Model)\n\tlog.Printf(\" UseFullURL: %v\", client.UseFullURL)\n\tif len(client.APIKey) > 8 {\n\t\tlog.Printf(\" API Key: %s...%s\", client.APIKey[:4], client.APIKey[len(client.APIKey)-4:])\n\t}\n\n\t// 构建 messages 数组\n\tmessages := []map[string]string{}\n\n\t// 如果有 system prompt,添加 system message\n\tif systemPrompt != \"\" {\n\t\tmessages = append(messages, map[string]string{\n\t\t\t\"role\": \"system\",\n\t\t\t\"content\": systemPrompt,\n\t\t})\n\t}\n\n\t// 添加 user message\n\tmessages = append(messages, map[string]string{\n\t\t\"role\": \"user\",\n\t\t\"content\": userPrompt,\n\t})\n\n\t// 构建请求体\n\trequestBody := map[string]interface{}{\n\t\t\"model\": client.Model,\n\t\t\"messages\": messages,\n\t\t\"temperature\": 0.5, // 降低temperature以提高JSON格式稳定性\n\t\t\"max_tokens\": 2000,\n\t}\n\n\t// 注意:response_format 参数仅 OpenAI 支持,DeepSeek/Qwen 不支持\n\t// 我们通过强化 prompt 和后处理来确保 JSON 格式正确\n\n\tjsonData, err := json.Marshal(requestBody)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"序列化请求失败: %w\", err)\n\t}\n\n\t// 创建HTTP请求\n\tvar url string\n\tif client.UseFullURL {\n\t\t// 使用完整URL,不添加/chat/completions\n\t\turl = client.BaseURL\n\t} else {\n\t\t// 默认行为:添加/chat/completions\n\t\turl = fmt.Sprintf(\"%s/chat/completions\", client.BaseURL)\n\t}\n\tlog.Printf(\"📡 [MCP] 请求 URL: %s\", url)\n\n\treq, err := http.NewRequest(\"POST\", url, bytes.NewBuffer(jsonData))\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"创建请求失败: %w\", err)\n\t}\n\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\n\t// 根据不同的Provider设置认证方式\n\tswitch client.Provider {\n\tcase ProviderDeepSeek:\n\t\treq.Header.Set(\"Authorization\", fmt.Sprintf(\"Bearer %s\", client.APIKey))\n\tcase ProviderQwen:\n\t\t// 阿里云Qwen使用API-Key认证\n\t\treq.Header.Set(\"Authorization\", fmt.Sprintf(\"Bearer %s\", client.APIKey))\n\t\t// 注意:如果使用的不是兼容模式,可能需要不同的认证方式\n\tdefault:\n\t\treq.Header.Set(\"Authorization\", fmt.Sprintf(\"Bearer %s\", client.APIKey))\n\t}\n\n\t// 发送请求\n\thttpClient := &http.Client{Timeout: client.Timeout}\n\tresp, err := httpClient.Do(req)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"发送请求失败: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\t// 读取响应\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"读取响应失败: %w\", err)\n\t}\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn \"\", fmt.Errorf(\"API返回错误 (status %d): %s\", resp.StatusCode, string(body))\n\t}\n\n\t// 解析响应\n\tvar result struct {\n\t\tChoices []struct {\n\t\t\tMessage struct {\n\t\t\t\tContent string `json:\"content\"`\n\t\t\t} `json:\"message\"`\n\t\t} `json:\"choices\"`\n\t}\n\n\tif err := json.Unmarshal(body, &result); err != nil {\n\t\treturn \"\", fmt.Errorf(\"解析响应失败: %w\", err)\n\t}\n\n\tif len(result.Choices) == 0 {\n\t\treturn \"\", fmt.Errorf(\"API返回空响应\")\n\t}\n\n\treturn result.Choices[0].Message.Content, nil\n}\n\n// isRetryableError 判断错误是否可重试\nfunc isRetryableError(err error) bool {\n\terrStr := err.Error()\n\t// 网络错误、超时、EOF等可以重试\n\tretryableErrors := []string{\n\t\t\"EOF\",\n\t\t\"timeout\",\n\t\t\"connection reset\",\n\t\t\"connection refused\",\n\t\t\"temporary failure\",\n\t\t\"no such host\",\n\t}\n\tfor _, retryable := range retryableErrors {\n\t\tif strings.Contains(errStr, retryable) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"}, "48d13202094e81062970196311a61d2e388a2e94": {"t": 1785759335.2758763, "n": 7162, "h": "b822836ea86e20c2", "c": "package mcp\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n)\n\n// Provider AI提供商类型\ntype Provider string\n\nconst (\n\tProviderDeepSeek Provider = \"deepseek\"\n\tProviderQwen Provider = \"qwen\"\n\tProviderCustom Provider = \"custom\"\n)\n\n// Client AI API配置\ntype Client struct {\n\tProvider Provider\n\tAPIKey string\n\tBaseURL string\n\tModel string\n\tTimeout time.Duration\n\tUseFullURL bool // 是否使用完整URL(不添加/chat/completions)\n}\n\nfunc New() *Client {\n\t// 默认配置\n\treturn &Client{\n\t\tProvider: ProviderDeepSeek,\n\t\tBaseURL: \"https://api.deepseek.com/v1\",\n\t\tModel: \"deepseek-chat\",\n\t\tTimeout: 120 * time.Second, // 增加到120秒,因为AI需要分析大量数据\n\t}\n}\n\n// SetDeepSeekAPIKey 设置DeepSeek API密钥\n// customURL 为空时使用默认URL,customModel 为空时使用默认模型\nfunc (client *Client) SetDeepSeekAPIKey(apiKey string, customURL string, customModel string) {\n\tclient.Provider = ProviderDeepSeek\n\tclient.APIKey = apiKey\n\tif customURL != \"\" {\n\t\tclient.BaseURL = customURL\n\t\tlog.Printf(\"🔧 [MCP] DeepSeek 使用自定义 BaseURL: %s\", customURL)\n\t} else {\n\t\tclient.BaseURL = \"https://api.deepseek.com/v1\"\n\t\tlog.Printf(\"🔧 [MCP] DeepSeek 使用默认 BaseURL: %s\", client.BaseURL)\n\t}\n\tif customModel != \"\" {\n\t\tclient.Model = customModel\n\t\tlog.Printf(\"🔧 [MCP] DeepSeek 使用自定义 Model: %s\", customModel)\n\t} else {\n\t\tclient.Model = \"deepseek-chat\"\n\t\tlog.Printf(\"🔧 [MCP] DeepSeek 使用默认 Model: %s\", client.Model)\n\t}\n\t// 打印 API Key 的前后各4位用于验证\n\tif len(apiKey) > 8 {\n\t\tlog.Printf(\"🔧 [MCP] DeepSeek API Key: %s...%s\", apiKey[:4], apiKey[len(apiKey)-4:])\n\t}\n}\n\n// SetQwenAPIKey 设置阿里云Qwen API密钥\n// customURL 为空时使用默认URL,customModel 为空时使用默认模型\nfunc (client *Client) SetQwenAPIKey(apiKey string, customURL string, customModel string) {\n\tclient.Provider = ProviderQwen\n\tclient.APIKey = apiKey\n\tif customURL != \"\" {\n\t\tclient.BaseURL = customURL\n\t\tlog.Printf(\"🔧 [MCP] Qwen 使用自定义 BaseURL: %s\", customURL)\n\t} else {\n\t\tclient.BaseURL = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\n\t\tlog.Printf(\"🔧 [MCP] Qwen 使用默认 BaseURL: %s\", client.BaseURL)\n\t}\n\tif customModel != \"\" {\n\t\tclient.Model = customModel\n\t\tlog.Printf(\"🔧 [MCP] Qwen 使用自定义 Model: %s\", customModel)\n\t} else {\n\t\tclient.Model = \"qwen-plus\" // 可选: qwen-turbo, qwen-plus, qwen-max\n\t\tlog.Printf(\"🔧 [MCP] Qwen 使用默认 Model: %s\", client.Model)\n\t}\n\t// 打印 API Key 的前后各4位用于验证\n\tif len(apiKey) > 8 {\n\t\tlog.Printf(\"🔧 [MCP] Qwen API Key: %s...%s\", apiKey[:4], apiKey[len(apiKey)-4:])\n\t}\n}\n\n// SetCustomAPI 设置自定义OpenAI兼容API\nfunc (client *Client) SetCustomAPI(apiURL, apiKey, modelName string) {\n\tclient.Provider = ProviderCustom\n\tclient.APIKey = apiKey\n\n\t// 检查URL是否以#结尾,如果是则使用完整URL(不添加/chat/completions)\n\tif strings.HasSuffix(apiURL, \"#\") {\n\t\tclient.BaseURL = strings.TrimSuffix(apiURL, \"#\")\n\t\tclient.UseFullURL = true\n\t} else {\n\t\tclient.BaseURL = apiURL\n\t\tclient.UseFullURL = false\n\t}\n\n\tclient.Model = modelName\n\tclient.Timeout = 120 * time.Second\n}\n\n// SetClient 设置完整的AI配置(高级用户)\nfunc (client *Client) SetClient(Client Client) {\n\tif Client.Timeout == 0 {\n\t\tClient.Timeout = 30 * time.Second\n\t}\n\tclient = &Client\n}\n\n// CallWithMessages 使用 system + user prompt 调用AI API(推荐)\nfunc (client *Client) CallWithMessages(systemPrompt, userPrompt string) (string, error) {\n\tif client.APIKey == \"\" {\n\t\treturn \"\", fmt.Errorf(\"AI API密钥未设置,请先调用 SetDeepSeekAPIKey() 或 SetQwenAPIKey()\")\n\t}\n\n\t// 重试配置\n\tmaxRetries := 3\n\tvar lastErr error\n\n\tfor attempt := 1; attempt <= maxRetries; attempt++ {\n\t\tif attempt > 1 {\n\t\t\tfmt.Printf(\"⚠️ AI API调用失败,正在重试 (%d/%d)...\\n\", attempt, maxRetries)\n\t\t}\n\n\t\tresult, err := client.callOnce(systemPrompt, userPrompt)\n\t\tif err == nil {\n\t\t\tif attempt > 1 {\n\t\t\t\tfmt.Printf(\"✓ AI API重试成功\\n\")\n\t\t\t}\n\t\t\treturn result, nil\n\t\t}\n\n\t\tlastErr = err\n\t\t// 如果不是网络错误,不重试\n\t\tif !isRetryableError(err) {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\t// 重试前等待\n\t\tif attempt < maxRetries {\n\t\t\twaitTime := time.Duration(attempt) * 2 * time.Second\n\t\t\tfmt.Printf(\"⏳ 等待%v后重试...\\n\", waitTime)\n\t\t\ttime.Sleep(waitTime)\n\t\t}\n\t}\n\n\treturn \"\", fmt.Errorf(\"重试%d次后仍然失败: %w\", maxRetries, lastErr)\n}\n\n// callOnce 单次调用AI API(内部使用)\nfunc (client *Client) callOnce(systemPrompt, userPrompt string) (string, error) {\n\t// 打印当前 AI 配置\n\tlog.Printf(\"📡 [MCP] AI 请求配置:\")\n\tlog.Printf(\" Provider: %s\", client.Provider)\n\tlog.Printf(\" BaseURL: %s\", client.BaseURL)\n\tlog.Printf(\" Model: %s\", client.Model)\n\tlog.Printf(\" UseFullURL: %v\", client.UseFullURL)\n\tif len(client.APIKey) > 8 {\n\t\tlog.Printf(\" API Key: %s...%s\", client.APIKey[:4], client.APIKey[len(client.APIKey)-4:])\n\t}\n\n\t// 构建 messages 数组\n\tmessages := []map[string]string{}\n\n\t// 如果有 system prompt,添加 system message\n\tif systemPrompt != \"\" {\n\t\tmessages = append(messages, map[string]string{\n\t\t\t\"role\": \"system\",\n\t\t\t\"content\": systemPrompt,\n\t\t})\n\t}\n\n\t// 添加 user message\n\tmessages = append(messages, map[string]string{\n\t\t\"role\": \"user\",\n\t\t\"content\": userPrompt,\n\t})\n\n\t// 构建请求体\n\trequestBody := map[string]interface{}{\n\t\t\"model\": client.Model,\n\t\t\"messages\": messages,\n\t\t\"temperature\": 0.5, // 降低temperature以提高JSON格式稳定性\n\t\t\"max_tokens\": 2000,\n\t}\n\n\t// 注意:response_format 参数仅 OpenAI 支持,DeepSeek/Qwen 不支持\n\t// 我们通过强化 prompt 和后处理来确保 JSON 格式正确\n\n\tjsonData, err := json.Marshal(requestBody)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"序列化请求失败: %w\", err)\n\t}\n\n\t// 创建HTTP请求\n\tvar url string\n\tif client.UseFullURL {\n\t\t// 使用完整URL,不添加/chat/completions\n\t\turl = client.BaseURL\n\t} else {\n\t\t// 默认行为:添加/chat/completions\n\t\turl = fmt.Sprintf(\"%s/chat/completions\", client.BaseURL)\n\t}\n\tlog.Printf(\"📡 [MCP] 请求 URL: %s\", url)\n\n\treq, err := http.NewRequest(\"POST\", url, bytes.NewBuffer(jsonData))\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"创建请求失败: %w\", err)\n\t}\n\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\n\t// 根据不同的Provider设置认证方式\n\tswitch client.Provider {\n\tcase ProviderDeepSeek:\n\t\treq.Header.Set(\"Authorization\", fmt.Sprintf(\"Bearer %s\", client.APIKey))\n\tcase ProviderQwen:\n\t\t// 阿里云Qwen使用API-Key认证\n\t\treq.Header.Set(\"Authorization\", fmt.Sprintf(\"Bearer %s\", client.APIKey))\n\t\t// 注意:如果使用的不是兼容模式,可能需要不同的认证方式\n\tdefault:\n\t\treq.Header.Set(\"Authorization\", fmt.Sprintf(\"Bearer %s\", client.APIKey))\n\t}\n\n\t// 发送请求\n\thttpClient := &http.Client{Timeout: client.Timeout}\n\tresp, err := httpClient.Do(req)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"发送请求失败: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\t// 读取响应\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"读取响应失败: %w\", err)\n\t}\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn \"\", fmt.Errorf(\"API返回错误 (status %d): %s\", resp.StatusCode, string(body))\n\t}\n\n\t// 解析响应\n\tvar result struct {\n\t\tChoices []struct {\n\t\t\tMessage struct {\n\t\t\t\tContent string `json:\"content\"`\n\t\t\t} `json:\"message\"`\n\t\t} `json:\"choices\"`\n\t}\n\n\tif err := json.Unmarshal(body, &result); err != nil {\n\t\treturn \"\", fmt.Errorf(\"解析响应失败: %w\", err)\n\t}\n\n\tif len(result.Choices) == 0 {\n\t\treturn \"\", fmt.Errorf(\"API返回空响应\")\n\t}\n\n\treturn result.Choices[0].Message.Content, nil\n}\n\n// isRetryableError 判断错误是否可重试\nfunc isRetryableError(err error) bool {\n\terrStr := err.Error()\n\t// 网络错误、超时、EOF等可以重试\n\tretryableErrors := []string{\n\t\t\"EOF\",\n\t\t\"timeout\",\n\t\t\"connection reset\",\n\t\t\"connection refused\",\n\t\t\"temporary failure\",\n\t\t\"no such host\",\n\t}\n\tfor _, retryable := range retryableErrors {\n\t\tif strings.Contains(errStr, retryable) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"}, "69445594e3f4b57b63d264b1cda48dc982feb443": {"t": 1785759337.2208965, "n": 5633, "h": "b1bdcde2213b4949", "c": "package mcp\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n)\n\n// Provider AI提供商类型\ntype Provider string\n\nconst (\n\tProviderDeepSeek Provider = \"deepseek\"\n\tProviderQwen Provider = \"qwen\"\n\tProviderCustom Provider = \"custom\"\n)\n\n// Client AI API配置\ntype Client struct {\n\tProvider Provider\n\tAPIKey string\n\tSecretKey string // 阿里云需要\n\tBaseURL string\n\tModel string\n\tTimeout time.Duration\n\tUseFullURL bool // 是否使用完整URL(不添加/chat/completions)\n}\n\nfunc New() *Client {\n\t// 默认配置\n\treturn &Client{\n\t\tProvider: ProviderDeepSeek,\n\t\tBaseURL: \"https://api.deepseek.com/v1\",\n\t\tModel: \"deepseek-chat\",\n\t\tTimeout: 120 * time.Second, // 增加到120秒,因为AI需要分析大量数据\n\t}\n}\n\n// SetDeepSeekAPIKey 设置DeepSeek API密钥\nfunc (client *Client) SetDeepSeekAPIKey(apiKey string) {\n\tclient.Provider = ProviderDeepSeek\n\tclient.APIKey = apiKey\n\tclient.BaseURL = \"https://api.deepseek.com/v1\"\n\tclient.Model = \"deepseek-chat\"\n}\n\n// SetQwenAPIKey 设置阿里云Qwen API密钥\nfunc (client *Client) SetQwenAPIKey(apiKey, secretKey string) {\n\tclient.Provider = ProviderQwen\n\tclient.APIKey = apiKey\n\tclient.SecretKey = secretKey\n\tclient.BaseURL = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\n\tclient.Model = \"qwen-plus\" // 可选: qwen-turbo, qwen-plus, qwen-max\n}\n\n// SetCustomAPI 设置自定义OpenAI兼容API\nfunc (client *Client) SetCustomAPI(apiURL, apiKey, modelName string) {\n\tclient.Provider = ProviderCustom\n\tclient.APIKey = apiKey\n\n\t// 检查URL是否以#结尾,如果是则使用完整URL(不添加/chat/completions)\n\tif strings.HasSuffix(apiURL, \"#\") {\n\t\tclient.BaseURL = strings.TrimSuffix(apiURL, \"#\")\n\t\tclient.UseFullURL = true\n\t} else {\n\t\tclient.BaseURL = apiURL\n\t\tclient.UseFullURL = false\n\t}\n\n\tclient.Model = modelName\n\tclient.Timeout = 120 * time.Second\n}\n\n// SetClient 设置完整的AI配置(高级用户)\nfunc (client *Client) SetClient(Client Client) {\n\tif Client.Timeout == 0 {\n\t\tClient.Timeout = 30 * time.Second\n\t}\n\tclient = &Client\n}\n\n// CallWithMessages 使用 system + user prompt 调用AI API(推荐)\nfunc (client *Client) CallWithMessages(systemPrompt, userPrompt string) (string, error) {\n\tif client.APIKey == \"\" {\n\t\treturn \"\", fmt.Errorf(\"AI API密钥未设置,请先调用 SetDeepSeekAPIKey() 或 SetQwenAPIKey()\")\n\t}\n\n\t// 重试配置\n\tmaxRetries := 3\n\tvar lastErr error\n\n\tfor attempt := 1; attempt <= maxRetries; attempt++ {\n\t\tif attempt > 1 {\n\t\t\tfmt.Printf(\"⚠️ AI API调用失败,正在重试 (%d/%d)...\\n\", attempt, maxRetries)\n\t\t}\n\n\t\tresult, err := client.callOnce(systemPrompt, userPrompt)\n\t\tif err == nil {\n\t\t\tif attempt > 1 {\n\t\t\t\tfmt.Printf(\"✓ AI API重试成功\\n\")\n\t\t\t}\n\t\t\treturn result, nil\n\t\t}\n\n\t\tlastErr = err\n\t\t// 如果不是网络错误,不重试\n\t\tif !isRetryableError(err) {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\t// 重试前等待\n\t\tif attempt < maxRetries {\n\t\t\twaitTime := time.Duration(attempt) * 2 * time.Second\n\t\t\tfmt.Printf(\"⏳ 等待%v后重试...\\n\", waitTime)\n\t\t\ttime.Sleep(waitTime)\n\t\t}\n\t}\n\n\treturn \"\", fmt.Errorf(\"重试%d次后仍然失败: %w\", maxRetries, lastErr)\n}\n\n// callOnce 单次调用AI API(内部使用)\nfunc (client *Client) callOnce(systemPrompt, userPrompt string) (string, error) {\n\t// 构建 messages 数组\n\tmessages := []map[string]string{}\n\n\t// 如果有 system prompt,添加 system message\n\tif systemPrompt != \"\" {\n\t\tmessages = append(messages, map[string]string{\n\t\t\t\"role\": \"system\",\n\t\t\t\"content\": systemPrompt,\n\t\t})\n\t}\n\n\t// 添加 user message\n\tmessages = append(messages, map[string]string{\n\t\t\"role\": \"user\",\n\t\t\"content\": userPrompt,\n\t})\n\n\t// 构建请求体\n\trequestBody := map[string]interface{}{\n\t\t\"model\": client.Model,\n\t\t\"messages\": messages,\n\t\t\"temperature\": 0.5, // 降低temperature以提高JSON格式稳定性\n\t\t\"max_tokens\": 2000,\n\t}\n\n\t// 注意:response_format 参数仅 OpenAI 支持,DeepSeek/Qwen 不支持\n\t// 我们通过强化 prompt 和后处理来确保 JSON 格式正确\n\n\tjsonData, err := json.Marshal(requestBody)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"序列化请求失败: %w\", err)\n\t}\n\n\t// 创建HTTP请求\n\tvar url string\n\tif client.UseFullURL {\n\t\t// 使用完整URL,不添加/chat/completions\n\t\turl = client.BaseURL\n\t} else {\n\t\t// 默认行为:添加/chat/completions\n\t\turl = fmt.Sprintf(\"%s/chat/completions\", client.BaseURL)\n\t}\n\treq, err := http.NewRequest(\"POST\", url, bytes.NewBuffer(jsonData))\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"创建请求失败: %w\", err)\n\t}\n\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\n\t// 根据不同的Provider设置认证方式\n\tswitch client.Provider {\n\tcase ProviderDeepSeek:\n\t\treq.Header.Set(\"Authorization\", fmt.Sprintf(\"Bearer %s\", client.APIKey))\n\tcase ProviderQwen:\n\t\t// 阿里云Qwen使用API-Key认证\n\t\treq.Header.Set(\"Authorization\", fmt.Sprintf(\"Bearer %s\", client.APIKey))\n\t\t// 注意:如果使用的不是兼容模式,可能需要不同的认证方式\n\tdefault:\n\t\treq.Header.Set(\"Authorization\", fmt.Sprintf(\"Bearer %s\", client.APIKey))\n\t}\n\n\t// 发送请求\n\thttpClient := &http.Client{Timeout: client.Timeout}\n\tresp, err := httpClient.Do(req)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"发送请求失败: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\t// 读取响应\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"读取响应失败: %w\", err)\n\t}\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn \"\", fmt.Errorf(\"API返回错误 (status %d): %s\", resp.StatusCode, string(body))\n\t}\n\n\t// 解析响应\n\tvar result struct {\n\t\tChoices []struct {\n\t\t\tMessage struct {\n\t\t\t\tContent string `json:\"content\"`\n\t\t\t} `json:\"message\"`\n\t\t} `json:\"choices\"`\n\t}\n\n\tif err := json.Unmarshal(body, &result); err != nil {\n\t\treturn \"\", fmt.Errorf(\"解析响应失败: %w\", err)\n\t}\n\n\tif len(result.Choices) == 0 {\n\t\treturn \"\", fmt.Errorf(\"API返回空响应\")\n\t}\n\n\treturn result.Choices[0].Message.Content, nil\n}\n\n// isRetryableError 判断错误是否可重试\nfunc isRetryableError(err error) bool {\n\terrStr := err.Error()\n\t// 网络错误、超时、EOF等可以重试\n\tretryableErrors := []string{\n\t\t\"EOF\",\n\t\t\"timeout\",\n\t\t\"connection reset\",\n\t\t\"connection refused\",\n\t\t\"temporary failure\",\n\t\t\"no such host\",\n\t}\n\tfor _, retryable := range retryableErrors {\n\t\tif strings.Contains(errStr, retryable) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"}, "1083c06d1fb76b0f9a65256ffa7b9fea5bc0f66e": {"t": 1785759338.9016144, "n": 5555, "h": "8ac9d63cd4557d24", "c": "package mcp\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n)\n\n// Provider AI提供商类型\ntype Provider string\n\nconst (\n\tProviderDeepSeek Provider = \"deepseek\"\n\tProviderQwen Provider = \"qwen\"\n\tProviderCustom Provider = \"custom\"\n)\n\n// Client AI API配置\ntype Client struct {\n\tProvider Provider\n\tAPIKey string\n\tSecretKey string // 阿里云需要\n\tBaseURL string\n\tModel string\n\tTimeout time.Duration\n\tUseFullURL bool // 是否使用完整URL(不添加/chat/completions)\n}\n\nfunc New() *Client {\n\t// 默认配置\n\tvar defaultClient = Client{\n\t\tProvider: ProviderDeepSeek,\n\t\tBaseURL: \"https://api.deepseek.com/v1\",\n\t\tModel: \"deepseek-chat\",\n\t\tTimeout: 120 * time.Second, // 增加到120秒,因为AI需要分析大量数据\n\t}\n\treturn &defaultClient\n}\n\n// SetDeepSeekAPIKey 设置DeepSeek API密钥\nfunc (cfg *Client) SetDeepSeekAPIKey(apiKey string) {\n\tcfg.Provider = ProviderDeepSeek\n\tcfg.APIKey = apiKey\n\tcfg.BaseURL = \"https://api.deepseek.com/v1\"\n\tcfg.Model = \"deepseek-chat\"\n}\n\n// SetQwenAPIKey 设置阿里云Qwen API密钥\nfunc (cfg *Client) SetQwenAPIKey(apiKey, secretKey string) {\n\tcfg.Provider = ProviderQwen\n\tcfg.APIKey = apiKey\n\tcfg.SecretKey = secretKey\n\tcfg.BaseURL = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\n\tcfg.Model = \"qwen-plus\" // 可选: qwen-turbo, qwen-plus, qwen-max\n}\n\n// SetCustomAPI 设置自定义OpenAI兼容API\nfunc (cfg *Client) SetCustomAPI(apiURL, apiKey, modelName string) {\n\tcfg.Provider = ProviderCustom\n\tcfg.APIKey = apiKey\n\n\t// 检查URL是否以#结尾,如果是则使用完整URL(不添加/chat/completions)\n\tif strings.HasSuffix(apiURL, \"#\") {\n\t\tcfg.BaseURL = strings.TrimSuffix(apiURL, \"#\")\n\t\tcfg.UseFullURL = true\n\t} else {\n\t\tcfg.BaseURL = apiURL\n\t\tcfg.UseFullURL = false\n\t}\n\n\tcfg.Model = modelName\n\tcfg.Timeout = 120 * time.Second\n}\n\n// SetClient 设置完整的AI配置(高级用户)\nfunc (cfg *Client) SetClient(Client Client) {\n\tif Client.Timeout == 0 {\n\t\tClient.Timeout = 30 * time.Second\n\t}\n\tcfg = &Client\n}\n\n// CallWithMessages 使用 system + user prompt 调用AI API(推荐)\nfunc (cfg *Client) CallWithMessages(systemPrompt, userPrompt string) (string, error) {\n\tif cfg.APIKey == \"\" {\n\t\treturn \"\", fmt.Errorf(\"AI API密钥未设置,请先调用 SetDeepSeekAPIKey() 或 SetQwenAPIKey()\")\n\t}\n\n\t// 重试配置\n\tmaxRetries := 3\n\tvar lastErr error\n\n\tfor attempt := 1; attempt <= maxRetries; attempt++ {\n\t\tif attempt > 1 {\n\t\t\tfmt.Printf(\"⚠️ AI API调用失败,正在重试 (%d/%d)...\\n\", attempt, maxRetries)\n\t\t}\n\n\t\tresult, err := cfg.callOnce(systemPrompt, userPrompt)\n\t\tif err == nil {\n\t\t\tif attempt > 1 {\n\t\t\t\tfmt.Printf(\"✓ AI API重试成功\\n\")\n\t\t\t}\n\t\t\treturn result, nil\n\t\t}\n\n\t\tlastErr = err\n\t\t// 如果不是网络错误,不重试\n\t\tif !isRetryableError(err) {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\t// 重试前等待\n\t\tif attempt < maxRetries {\n\t\t\twaitTime := time.Duration(attempt) * 2 * time.Second\n\t\t\tfmt.Printf(\"⏳ 等待%v后重试...\\n\", waitTime)\n\t\t\ttime.Sleep(waitTime)\n\t\t}\n\t}\n\n\treturn \"\", fmt.Errorf(\"重试%d次后仍然失败: %w\", maxRetries, lastErr)\n}\n\n// callOnce 单次调用AI API(内部使用)\nfunc (cfg *Client) callOnce(systemPrompt, userPrompt string) (string, error) {\n\t// 构建 messages 数组\n\tmessages := []map[string]string{}\n\n\t// 如果有 system prompt,添加 system message\n\tif systemPrompt != \"\" {\n\t\tmessages = append(messages, map[string]string{\n\t\t\t\"role\": \"system\",\n\t\t\t\"content\": systemPrompt,\n\t\t})\n\t}\n\n\t// 添加 user message\n\tmessages = append(messages, map[string]string{\n\t\t\"role\": \"user\",\n\t\t\"content\": userPrompt,\n\t})\n\n\t// 构建请求体\n\trequestBody := map[string]interface{}{\n\t\t\"model\": cfg.Model,\n\t\t\"messages\": messages,\n\t\t\"temperature\": 0.5, // 降低temperature以提高JSON格式稳定性\n\t\t\"max_tokens\": 2000,\n\t}\n\n\t// 注意:response_format 参数仅 OpenAI 支持,DeepSeek/Qwen 不支持\n\t// 我们通过强化 prompt 和后处理来确保 JSON 格式正确\n\n\tjsonData, err := json.Marshal(requestBody)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"序列化请求失败: %w\", err)\n\t}\n\n\t// 创建HTTP请求\n\tvar url string\n\tif cfg.UseFullURL {\n\t\t// 使用完整URL,不添加/chat/completions\n\t\turl = cfg.BaseURL\n\t} else {\n\t\t// 默认行为:添加/chat/completions\n\t\turl = fmt.Sprintf(\"%s/chat/completions\", cfg.BaseURL)\n\t}\n\treq, err := http.NewRequest(\"POST\", url, bytes.NewBuffer(jsonData))\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"创建请求失败: %w\", err)\n\t}\n\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\n\t// 根据不同的Provider设置认证方式\n\tswitch cfg.Provider {\n\tcase ProviderDeepSeek:\n\t\treq.Header.Set(\"Authorization\", fmt.Sprintf(\"Bearer %s\", cfg.APIKey))\n\tcase ProviderQwen:\n\t\t// 阿里云Qwen使用API-Key认证\n\t\treq.Header.Set(\"Authorization\", fmt.Sprintf(\"Bearer %s\", cfg.APIKey))\n\t\t// 注意:如果使用的不是兼容模式,可能需要不同的认证方式\n\tdefault:\n\t\treq.Header.Set(\"Authorization\", fmt.Sprintf(\"Bearer %s\", cfg.APIKey))\n\t}\n\n\t// 发送请求\n\tclient := &http.Client{Timeout: cfg.Timeout}\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"发送请求失败: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\t// 读取响应\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"读取响应失败: %w\", err)\n\t}\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn \"\", fmt.Errorf(\"API返回错误 (status %d): %s\", resp.StatusCode, string(body))\n\t}\n\n\t// 解析响应\n\tvar result struct {\n\t\tChoices []struct {\n\t\t\tMessage struct {\n\t\t\t\tContent string `json:\"content\"`\n\t\t\t} `json:\"message\"`\n\t\t} `json:\"choices\"`\n\t}\n\n\tif err := json.Unmarshal(body, &result); err != nil {\n\t\treturn \"\", fmt.Errorf(\"解析响应失败: %w\", err)\n\t}\n\n\tif len(result.Choices) == 0 {\n\t\treturn \"\", fmt.Errorf(\"API返回空响应\")\n\t}\n\n\treturn result.Choices[0].Message.Content, nil\n}\n\n// isRetryableError 判断错误是否可重试\nfunc isRetryableError(err error) bool {\n\terrStr := err.Error()\n\t// 网络错误、超时、EOF等可以重试\n\tretryableErrors := []string{\n\t\t\"EOF\",\n\t\t\"timeout\",\n\t\t\"connection reset\",\n\t\t\"connection refused\",\n\t\t\"temporary failure\",\n\t\t\"no such host\",\n\t}\n\tfor _, retryable := range retryableErrors {\n\t\tif strings.Contains(errStr, retryable) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"}, "9886c61ea05b84eaf41341921dd730a7357c3785": {"t": 1785759341.261068, "n": 5687, "h": "2b2898a7a8689f6f", "c": "package mcp\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n)\n\n// Provider AI提供商类型\ntype Provider string\n\nconst (\n\tProviderDeepSeek Provider = \"deepseek\"\n\tProviderQwen Provider = \"qwen\"\n\tProviderCustom Provider = \"custom\"\n)\n\n// Config AI API配置\ntype Config struct {\n\tProvider Provider\n\tAPIKey string\n\tSecretKey string // 阿里云需要\n\tBaseURL string\n\tModel string\n\tTimeout time.Duration\n\tUseFullURL bool // 是否使用完整URL(不添加/chat/completions)\n}\n\n// 默认配置\nvar defaultConfig = Config{\n\tProvider: ProviderDeepSeek,\n\tBaseURL: \"https://api.deepseek.com/v1\",\n\tModel: \"deepseek-chat\",\n\tTimeout: 120 * time.Second, // 增加到120秒,因为AI需要分析大量数据\n}\n\n// SetDeepSeekAPIKey 设置DeepSeek API密钥\nfunc SetDeepSeekAPIKey(apiKey string) {\n\tdefaultConfig.Provider = ProviderDeepSeek\n\tdefaultConfig.APIKey = apiKey\n\tdefaultConfig.BaseURL = \"https://api.deepseek.com/v1\"\n\tdefaultConfig.Model = \"deepseek-chat\"\n}\n\n// SetQwenAPIKey 设置阿里云Qwen API密钥\nfunc SetQwenAPIKey(apiKey, secretKey string) {\n\tdefaultConfig.Provider = ProviderQwen\n\tdefaultConfig.APIKey = apiKey\n\tdefaultConfig.SecretKey = secretKey\n\tdefaultConfig.BaseURL = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\n\tdefaultConfig.Model = \"qwen-plus\" // 可选: qwen-turbo, qwen-plus, qwen-max\n}\n\n// SetCustomAPI 设置自定义OpenAI兼容API\nfunc SetCustomAPI(apiURL, apiKey, modelName string) {\n\tdefaultConfig.Provider = ProviderCustom\n\tdefaultConfig.APIKey = apiKey\n\n\t// 检查URL是否以#结尾,如果是则使用完整URL(不添加/chat/completions)\n\tif strings.HasSuffix(apiURL, \"#\") {\n\t\tdefaultConfig.BaseURL = strings.TrimSuffix(apiURL, \"#\")\n\t\tdefaultConfig.UseFullURL = true\n\t} else {\n\t\tdefaultConfig.BaseURL = apiURL\n\t\tdefaultConfig.UseFullURL = false\n\t}\n\n\tdefaultConfig.Model = modelName\n\tdefaultConfig.Timeout = 120 * time.Second\n}\n\n// SetConfig 设置完整的AI配置(高级用户)\nfunc SetConfig(config Config) {\n\tif config.Timeout == 0 {\n\t\tconfig.Timeout = 30 * time.Second\n\t}\n\tdefaultConfig = config\n}\n\n// CallWithMessages 使用 system + user prompt 调用AI API(推荐)\nfunc CallWithMessages(systemPrompt, userPrompt string) (string, error) {\n\tif defaultConfig.APIKey == \"\" {\n\t\treturn \"\", fmt.Errorf(\"AI API密钥未设置,请先调用 SetDeepSeekAPIKey() 或 SetQwenAPIKey()\")\n\t}\n\n\t// 重试配置\n\tmaxRetries := 3\n\tvar lastErr error\n\n\tfor attempt := 1; attempt <= maxRetries; attempt++ {\n\t\tif attempt > 1 {\n\t\t\tfmt.Printf(\"⚠️ AI API调用失败,正在重试 (%d/%d)...\\n\", attempt, maxRetries)\n\t\t}\n\n\t\tresult, err := callOnce(systemPrompt, userPrompt)\n\t\tif err == nil {\n\t\t\tif attempt > 1 {\n\t\t\t\tfmt.Printf(\"✓ AI API重试成功\\n\")\n\t\t\t}\n\t\t\treturn result, nil\n\t\t}\n\n\t\tlastErr = err\n\t\t// 如果不是网络错误,不重试\n\t\tif !isRetryableError(err) {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\t// 重试前等待\n\t\tif attempt < maxRetries {\n\t\t\twaitTime := time.Duration(attempt) * 2 * time.Second\n\t\t\tfmt.Printf(\"⏳ 等待%v后重试...\\n\", waitTime)\n\t\t\ttime.Sleep(waitTime)\n\t\t}\n\t}\n\n\treturn \"\", fmt.Errorf(\"重试%d次后仍然失败: %w\", maxRetries, lastErr)\n}\n\n// callOnce 单次调用AI API(内部使用)\nfunc callOnce(systemPrompt, userPrompt string) (string, error) {\n\t// 构建 messages 数组\n\tmessages := []map[string]string{}\n\n\t// 如果有 system prompt,添加 system message\n\tif systemPrompt != \"\" {\n\t\tmessages = append(messages, map[string]string{\n\t\t\t\"role\": \"system\",\n\t\t\t\"content\": systemPrompt,\n\t\t})\n\t}\n\n\t// 添加 user message\n\tmessages = append(messages, map[string]string{\n\t\t\"role\": \"user\",\n\t\t\"content\": userPrompt,\n\t})\n\n\t// 构建请求体\n\trequestBody := map[string]interface{}{\n\t\t\"model\": defaultConfig.Model,\n\t\t\"messages\": messages,\n\t\t\"temperature\": 0.5, // 降低temperature以提高JSON格式稳定性\n\t\t\"max_tokens\": 2000,\n\t}\n\n\t// 注意:response_format 参数仅 OpenAI 支持,DeepSeek/Qwen 不支持\n\t// 我们通过强化 prompt 和后处理来确保 JSON 格式正确\n\n\tjsonData, err := json.Marshal(requestBody)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"序列化请求失败: %w\", err)\n\t}\n\n\t// 创建HTTP请求\n\tvar url string\n\tif defaultConfig.UseFullURL {\n\t\t// 使用完整URL,不添加/chat/completions\n\t\turl = defaultConfig.BaseURL\n\t} else {\n\t\t// 默认行为:添加/chat/completions\n\t\turl = fmt.Sprintf(\"%s/chat/completions\", defaultConfig.BaseURL)\n\t}\n\treq, err := http.NewRequest(\"POST\", url, bytes.NewBuffer(jsonData))\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"创建请求失败: %w\", err)\n\t}\n\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\n\t// 根据不同的Provider设置认证方式\n\tswitch defaultConfig.Provider {\n\tcase ProviderDeepSeek:\n\t\treq.Header.Set(\"Authorization\", fmt.Sprintf(\"Bearer %s\", defaultConfig.APIKey))\n\tcase ProviderQwen:\n\t\t// 阿里云Qwen使用API-Key认证\n\t\treq.Header.Set(\"Authorization\", fmt.Sprintf(\"Bearer %s\", defaultConfig.APIKey))\n\t\t// 注意:如果使用的不是兼容模式,可能需要不同的认证方式\n\tdefault:\n\t\treq.Header.Set(\"Authorization\", fmt.Sprintf(\"Bearer %s\", defaultConfig.APIKey))\n\t}\n\n\t// 发送请求\n\tclient := &http.Client{Timeout: defaultConfig.Timeout}\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"发送请求失败: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\t// 读取响应\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"读取响应失败: %w\", err)\n\t}\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn \"\", fmt.Errorf(\"API返回错误 (status %d): %s\", resp.StatusCode, string(body))\n\t}\n\n\t// 解析响应\n\tvar result struct {\n\t\tChoices []struct {\n\t\t\tMessage struct {\n\t\t\t\tContent string `json:\"content\"`\n\t\t\t} `json:\"message\"`\n\t\t} `json:\"choices\"`\n\t}\n\n\tif err := json.Unmarshal(body, &result); err != nil {\n\t\treturn \"\", fmt.Errorf(\"解析响应失败: %w\", err)\n\t}\n\n\tif len(result.Choices) == 0 {\n\t\treturn \"\", fmt.Errorf(\"API返回空响应\")\n\t}\n\n\treturn result.Choices[0].Message.Content, nil\n}\n\n// isRetryableError 判断错误是否可重试\nfunc isRetryableError(err error) bool {\n\terrStr := err.Error()\n\t// 网络错误、超时、EOF等可以重试\n\tretryableErrors := []string{\n\t\t\"EOF\",\n\t\t\"timeout\",\n\t\t\"connection reset\",\n\t\t\"connection refused\",\n\t\t\"temporary failure\",\n\t\t\"no such host\",\n\t}\n\tfor _, retryable := range retryableErrors {\n\t\tif strings.Contains(errStr, retryable) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"}, "2cdfb87144f2585915fafaad4636327ca6016393": {"t": 1785759342.8836699, "n": 5251, "h": "8e47e6f30091ca38", "c": "package mcp\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n)\n\n// Provider AI提供商类型\ntype Provider string\n\nconst (\n\tProviderDeepSeek Provider = \"deepseek\"\n\tProviderQwen Provider = \"qwen\"\n\tProviderCustom Provider = \"custom\"\n)\n\n// Config AI API配置\ntype Config struct {\n\tProvider Provider\n\tAPIKey string\n\tSecretKey string // 阿里云需要\n\tBaseURL string\n\tModel string\n\tTimeout time.Duration\n}\n\n// 默认配置\nvar defaultConfig = Config{\n\tProvider: ProviderDeepSeek,\n\tBaseURL: \"https://api.deepseek.com/v1\",\n\tModel: \"deepseek-chat\",\n\tTimeout: 120 * time.Second, // 增加到120秒,因为AI需要分析大量数据\n}\n\n// SetDeepSeekAPIKey 设置DeepSeek API密钥\nfunc SetDeepSeekAPIKey(apiKey string) {\n\tdefaultConfig.Provider = ProviderDeepSeek\n\tdefaultConfig.APIKey = apiKey\n\tdefaultConfig.BaseURL = \"https://api.deepseek.com/v1\"\n\tdefaultConfig.Model = \"deepseek-chat\"\n}\n\n// SetQwenAPIKey 设置阿里云Qwen API密钥\nfunc SetQwenAPIKey(apiKey, secretKey string) {\n\tdefaultConfig.Provider = ProviderQwen\n\tdefaultConfig.APIKey = apiKey\n\tdefaultConfig.SecretKey = secretKey\n\tdefaultConfig.BaseURL = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\n\tdefaultConfig.Model = \"qwen-plus\" // 可选: qwen-turbo, qwen-plus, qwen-max\n}\n\n// SetCustomAPI 设置自定义OpenAI兼容API\nfunc SetCustomAPI(apiURL, apiKey, modelName string) {\n\tdefaultConfig.Provider = ProviderCustom\n\tdefaultConfig.APIKey = apiKey\n\tdefaultConfig.BaseURL = apiURL\n\tdefaultConfig.Model = modelName\n\tdefaultConfig.Timeout = 120 * time.Second\n}\n\n// SetConfig 设置完整的AI配置(高级用户)\nfunc SetConfig(config Config) {\n\tif config.Timeout == 0 {\n\t\tconfig.Timeout = 30 * time.Second\n\t}\n\tdefaultConfig = config\n}\n\n// CallWithMessages 使用 system + user prompt 调用AI API(推荐)\nfunc CallWithMessages(systemPrompt, userPrompt string) (string, error) {\n\tif defaultConfig.APIKey == \"\" {\n\t\treturn \"\", fmt.Errorf(\"AI API密钥未设置,请先调用 SetDeepSeekAPIKey() 或 SetQwenAPIKey()\")\n\t}\n\n\t// 重试配置\n\tmaxRetries := 3\n\tvar lastErr error\n\n\tfor attempt := 1; attempt <= maxRetries; attempt++ {\n\t\tif attempt > 1 {\n\t\t\tfmt.Printf(\"⚠️ AI API调用失败,正在重试 (%d/%d)...\\n\", attempt, maxRetries)\n\t\t}\n\n\t\tresult, err := callOnce(systemPrompt, userPrompt)\n\t\tif err == nil {\n\t\t\tif attempt > 1 {\n\t\t\t\tfmt.Printf(\"✓ AI API重试成功\\n\")\n\t\t\t}\n\t\t\treturn result, nil\n\t\t}\n\n\t\tlastErr = err\n\t\t// 如果不是网络错误,不重试\n\t\tif !isRetryableError(err) {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\t// 重试前等待\n\t\tif attempt < maxRetries {\n\t\t\twaitTime := time.Duration(attempt) * 2 * time.Second\n\t\t\tfmt.Printf(\"⏳ 等待%v后重试...\\n\", waitTime)\n\t\t\ttime.Sleep(waitTime)\n\t\t}\n\t}\n\n\treturn \"\", fmt.Errorf(\"重试%d次后仍然失败: %w\", maxRetries, lastErr)\n}\n\n// callOnce 单次调用AI API(内部使用)\nfunc callOnce(systemPrompt, userPrompt string) (string, error) {\n\t// 构建 messages 数组\n\tmessages := []map[string]string{}\n\n\t// 如果有 system prompt,添加 system message\n\tif systemPrompt != \"\" {\n\t\tmessages = append(messages, map[string]string{\n\t\t\t\"role\": \"system\",\n\t\t\t\"content\": systemPrompt,\n\t\t})\n\t}\n\n\t// 添加 user message\n\tmessages = append(messages, map[string]string{\n\t\t\"role\": \"user\",\n\t\t\"content\": userPrompt,\n\t})\n\n\t// 构建请求体\n\trequestBody := map[string]interface{}{\n\t\t\"model\": defaultConfig.Model,\n\t\t\"messages\": messages,\n\t\t\"temperature\": 0.5, // 降低temperature以提高JSON格式稳定性\n\t\t\"max_tokens\": 2000,\n\t}\n\n\t// 注意:response_format 参数仅 OpenAI 支持,DeepSeek/Qwen 不支持\n\t// 我们通过强化 prompt 和后处理来确保 JSON 格式正确\n\n\tjsonData, err := json.Marshal(requestBody)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"序列化请求失败: %w\", err)\n\t}\n\n\t// 创建HTTP请求\n\turl := fmt.Sprintf(\"%s/chat/completions\", defaultConfig.BaseURL)\n\treq, err := http.NewRequest(\"POST\", url, bytes.NewBuffer(jsonData))\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"创建请求失败: %w\", err)\n\t}\n\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\n\t// 根据不同的Provider设置认证方式\n\tswitch defaultConfig.Provider {\n\tcase ProviderDeepSeek:\n\t\treq.Header.Set(\"Authorization\", fmt.Sprintf(\"Bearer %s\", defaultConfig.APIKey))\n\tcase ProviderQwen:\n\t\t// 阿里云Qwen使用API-Key认证\n\t\treq.Header.Set(\"Authorization\", fmt.Sprintf(\"Bearer %s\", defaultConfig.APIKey))\n\t\t// 注意:如果使用的不是兼容模式,可能需要不同的认证方式\n\tdefault:\n\t\treq.Header.Set(\"Authorization\", fmt.Sprintf(\"Bearer %s\", defaultConfig.APIKey))\n\t}\n\n\t// 发送请求\n\tclient := &http.Client{Timeout: defaultConfig.Timeout}\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"发送请求失败: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\t// 读取响应\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"读取响应失败: %w\", err)\n\t}\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn \"\", fmt.Errorf(\"API返回错误 (status %d): %s\", resp.StatusCode, string(body))\n\t}\n\n\t// 解析响应\n\tvar result struct {\n\t\tChoices []struct {\n\t\t\tMessage struct {\n\t\t\t\tContent string `json:\"content\"`\n\t\t\t} `json:\"message\"`\n\t\t} `json:\"choices\"`\n\t}\n\n\tif err := json.Unmarshal(body, &result); err != nil {\n\t\treturn \"\", fmt.Errorf(\"解析响应失败: %w\", err)\n\t}\n\n\tif len(result.Choices) == 0 {\n\t\treturn \"\", fmt.Errorf(\"API返回空响应\")\n\t}\n\n\treturn result.Choices[0].Message.Content, nil\n}\n\n// isRetryableError 判断错误是否可重试\nfunc isRetryableError(err error) bool {\n\terrStr := err.Error()\n\t// 网络错误、超时、EOF等可以重试\n\tretryableErrors := []string{\n\t\t\"EOF\",\n\t\t\"timeout\",\n\t\t\"connection reset\",\n\t\t\"connection refused\",\n\t\t\"temporary failure\",\n\t\t\"no such host\",\n\t}\n\tfor _, retryable := range retryableErrors {\n\t\tif strings.Contains(errStr, retryable) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"}, "f3a87b5a7a19b5df37ef9501486fc05e0270f99e": {"t": 1785759344.46248, "n": 4943, "h": "2030970e80291d95", "c": "package mcp\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n)\n\n// Provider AI提供商类型\ntype Provider string\n\nconst (\n\tProviderDeepSeek Provider = \"deepseek\"\n\tProviderQwen Provider = \"qwen\"\n)\n\n// Config AI API配置\ntype Config struct {\n\tProvider Provider\n\tAPIKey string\n\tSecretKey string // 阿里云需要\n\tBaseURL string\n\tModel string\n\tTimeout time.Duration\n}\n\n// 默认配置\nvar defaultConfig = Config{\n\tProvider: ProviderDeepSeek,\n\tBaseURL: \"https://api.deepseek.com/v1\",\n\tModel: \"deepseek-chat\",\n\tTimeout: 120 * time.Second, // 增加到120秒,因为AI需要分析大量数据\n}\n\n// SetDeepSeekAPIKey 设置DeepSeek API密钥\nfunc SetDeepSeekAPIKey(apiKey string) {\n\tdefaultConfig.Provider = ProviderDeepSeek\n\tdefaultConfig.APIKey = apiKey\n\tdefaultConfig.BaseURL = \"https://api.deepseek.com/v1\"\n\tdefaultConfig.Model = \"deepseek-chat\"\n}\n\n// SetQwenAPIKey 设置阿里云Qwen API密钥\nfunc SetQwenAPIKey(apiKey, secretKey string) {\n\tdefaultConfig.Provider = ProviderQwen\n\tdefaultConfig.APIKey = apiKey\n\tdefaultConfig.SecretKey = secretKey\n\tdefaultConfig.BaseURL = \"https://dashscope.aliyuncs.com/compatible-mode/v1\"\n\tdefaultConfig.Model = \"qwen-plus\" // 可选: qwen-turbo, qwen-plus, qwen-max\n}\n\n// SetConfig 设置完整的AI配置(高级用户)\nfunc SetConfig(config Config) {\n\tif config.Timeout == 0 {\n\t\tconfig.Timeout = 30 * time.Second\n\t}\n\tdefaultConfig = config\n}\n\n// CallWithMessages 使用 system + user prompt 调用AI API(推荐)\nfunc CallWithMessages(systemPrompt, userPrompt string) (string, error) {\n\tif defaultConfig.APIKey == \"\" {\n\t\treturn \"\", fmt.Errorf(\"AI API密钥未设置,请先调用 SetDeepSeekAPIKey() 或 SetQwenAPIKey()\")\n\t}\n\n\t// 重试配置\n\tmaxRetries := 3\n\tvar lastErr error\n\n\tfor attempt := 1; attempt <= maxRetries; attempt++ {\n\t\tif attempt > 1 {\n\t\t\tfmt.Printf(\"⚠️ AI API调用失败,正在重试 (%d/%d)...\\n\", attempt, maxRetries)\n\t\t}\n\n\t\tresult, err := callOnce(systemPrompt, userPrompt)\n\t\tif err == nil {\n\t\t\tif attempt > 1 {\n\t\t\t\tfmt.Printf(\"✓ AI API重试成功\\n\")\n\t\t\t}\n\t\t\treturn result, nil\n\t\t}\n\n\t\tlastErr = err\n\t\t// 如果不是网络错误,不重试\n\t\tif !isRetryableError(err) {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\t// 重试前等待\n\t\tif attempt < maxRetries {\n\t\t\twaitTime := time.Duration(attempt) * 2 * time.Second\n\t\t\tfmt.Printf(\"⏳ 等待%v后重试...\\n\", waitTime)\n\t\t\ttime.Sleep(waitTime)\n\t\t}\n\t}\n\n\treturn \"\", fmt.Errorf(\"重试%d次后仍然失败: %w\", maxRetries, lastErr)\n}\n\n// callOnce 单次调用AI API(内部使用)\nfunc callOnce(systemPrompt, userPrompt string) (string, error) {\n\t// 构建 messages 数组\n\tmessages := []map[string]string{}\n\n\t// 如果有 system prompt,添加 system message\n\tif systemPrompt != \"\" {\n\t\tmessages = append(messages, map[string]string{\n\t\t\t\"role\": \"system\",\n\t\t\t\"content\": systemPrompt,\n\t\t})\n\t}\n\n\t// 添加 user message\n\tmessages = append(messages, map[string]string{\n\t\t\"role\": \"user\",\n\t\t\"content\": userPrompt,\n\t})\n\n\t// 构建请求体\n\trequestBody := map[string]interface{}{\n\t\t\"model\": defaultConfig.Model,\n\t\t\"messages\": messages,\n\t\t\"temperature\": 0.5, // 降低temperature以提高JSON格式稳定性\n\t\t\"max_tokens\": 2000,\n\t}\n\n\t// 注意:response_format 参数仅 OpenAI 支持,DeepSeek/Qwen 不支持\n\t// 我们通过强化 prompt 和后处理来确保 JSON 格式正确\n\n\tjsonData, err := json.Marshal(requestBody)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"序列化请求失败: %w\", err)\n\t}\n\n\t// 创建HTTP请求\n\turl := fmt.Sprintf(\"%s/chat/completions\", defaultConfig.BaseURL)\n\treq, err := http.NewRequest(\"POST\", url, bytes.NewBuffer(jsonData))\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"创建请求失败: %w\", err)\n\t}\n\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\n\t// 根据不同的Provider设置认证方式\n\tswitch defaultConfig.Provider {\n\tcase ProviderDeepSeek:\n\t\treq.Header.Set(\"Authorization\", fmt.Sprintf(\"Bearer %s\", defaultConfig.APIKey))\n\tcase ProviderQwen:\n\t\t// 阿里云Qwen使用API-Key认证\n\t\treq.Header.Set(\"Authorization\", fmt.Sprintf(\"Bearer %s\", defaultConfig.APIKey))\n\t\t// 注意:如果使用的不是兼容模式,可能需要不同的认证方式\n\tdefault:\n\t\treq.Header.Set(\"Authorization\", fmt.Sprintf(\"Bearer %s\", defaultConfig.APIKey))\n\t}\n\n\t// 发送请求\n\tclient := &http.Client{Timeout: defaultConfig.Timeout}\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"发送请求失败: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\t// 读取响应\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"读取响应失败: %w\", err)\n\t}\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn \"\", fmt.Errorf(\"API返回错误 (status %d): %s\", resp.StatusCode, string(body))\n\t}\n\n\t// 解析响应\n\tvar result struct {\n\t\tChoices []struct {\n\t\t\tMessage struct {\n\t\t\t\tContent string `json:\"content\"`\n\t\t\t} `json:\"message\"`\n\t\t} `json:\"choices\"`\n\t}\n\n\tif err := json.Unmarshal(body, &result); err != nil {\n\t\treturn \"\", fmt.Errorf(\"解析响应失败: %w\", err)\n\t}\n\n\tif len(result.Choices) == 0 {\n\t\treturn \"\", fmt.Errorf(\"API返回空响应\")\n\t}\n\n\treturn result.Choices[0].Message.Content, nil\n}\n\n// isRetryableError 判断错误是否可重试\nfunc isRetryableError(err error) bool {\n\terrStr := err.Error()\n\t// 网络错误、超时、EOF等可以重试\n\tretryableErrors := []string{\n\t\t\"EOF\",\n\t\t\"timeout\",\n\t\t\"connection reset\",\n\t\t\"connection refused\",\n\t\t\"temporary failure\",\n\t\t\"no such host\",\n\t}\n\tfor _, retryable := range retryableErrors {\n\t\tif strings.Contains(errStr, retryable) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"}}, "docs/architecture/README.md": {"324cb9d1f4aad8ec759fd769dea155eb30aee449": {"t": 1785747248.7755442, "n": 14364, "h": "58d70f8ec30eae86", "c": "# 🏗️ NOFX Architecture Documentation\n\n**Language:** [English](README.md) | [中文](README.zh-CN.md)\n\nTechnical documentation for developers who want to understand NOFX internals.\n\n---\n\n## 📋 Overview\n\nNOFX is a full-stack AI trading platform with:\n- **Backend:** Go (Gin framework, SQLite)\n- **Frontend:** React/TypeScript (Vite, TailwindCSS)\n- **Architecture:** Microservice-inspired modular design\n\n---\n\n## 📁 Project Structure\n\n```\nnofx/\n├── main.go # Program entry (multi-trader manager)\n├── config.json # ~~Multi-trader config~~ (Now via web interface)\n├── trading.db # SQLite database (traders, models, exchanges)\n│\n├── api/ # HTTP API service\n│ └── server.go # Gin framework, RESTful API\n│\n├── trader/ # Trading core\n│ ├── auto_trader.go # Auto trading main controller\n│ ├── interface.go # Unified trader interface\n│ ├── binance_futures.go # Binance API wrapper\n│ ├── hyperliquid_trader.go # Hyperliquid DEX wrapper\n│ └── aster_trader.go # Aster DEX wrapper\n│\n├── manager/ # Multi-trader management\n│ └── trader_manager.go # Manages multiple trader instances\n│\n├── config/ # Configuration & database\n│ └── database.go # SQLite operations and schema\n│\n├── auth/ # Authentication\n│ └── jwt.go # JWT token management & 2FA\n│\n├── mcp/ # Model Context Protocol - AI communication\n│ └── client.go # AI API client (DeepSeek/Qwen/Custom)\n│\n├── decision/ # AI decision engine\n│ ├── engine.go # Decision logic with historical feedback\n│ └── prompt_manager.go # Prompt template system\n│\n├── market/ # Market data fetching\n│ └── data.go # Market data & technical indicators (TA-Lib)\n│ └── api_client.go # Market data acquisition API\n│ └── websocket_client.go # Market data acquisition WebSocket interface\n│ └── combined_streams.go # Market data acquisition: Combined streaming (single link to subscribe to multiple cryptocurrencies)\n│ └── monitor.go # Market data cache\n│ └── types.go # market structure\n\n├── pool/ # Coin pool management\n│ └── coin_pool.go # AI500 + OI Top merged pool\n│\n├── logger/ # Logging system\n│ └── decision_logger.go # Decision recording + performance analysis\n│\n├── decision_logs/ # Decision log storage (JSON files)\n│ ├── {trader_id}/ # Per-trader logs\n│ └── {timestamp}.json # Individual decisions\n│\n└── web/ # React frontend\n ├── src/\n │ ├── components/ # React components\n │ │ ├── EquityChart.tsx # Equity curve chart\n │ │ ├── ComparisonChart.tsx # Multi-AI comparison chart\n │ │ └── CompetitionPage.tsx # Competition leaderboard\n │ ├── lib/api.ts # API call wrapper\n │ ├── types/index.ts # TypeScript types\n │ ├── stores/ # Zustand state management\n │ ├── index.css # Binance-style CSS\n │ └── App.tsx # Main app\n ├── package.json # Frontend dependencies\n └── vite.config.ts # Vite configuration\n```\n\n---\n\n## 🔧 Core Dependencies\n\n### Backend (Go)\n\n| Package | Purpose | Version |\n|---------|---------|---------|\n| `github.com/gin-gonic/gin` | HTTP API framework | v1.9+ |\n| `github.com/adshao/go-binance/v2` | Binance API client | v2.4+ |\n| `github.com/markcheno/go-talib` | Technical indicators (TA-Lib) | Latest |\n| `github.com/mattn/go-sqlite3` | SQLite database driver | v1.14+ |\n| `github.com/golang-jwt/jwt/v5` | JWT authentication | v5.0+ |\n| `github.com/pquerna/otp` | 2FA/TOTP support | v1.4+ |\n| `golang.org/x/crypto` | Password hashing (bcrypt) | Latest |\n\n### Frontend (React + TypeScript)\n\n| Package | Purpose | Version |\n|---------|---------|---------|\n| `react` + `react-dom` | UI framework | 18.3+ |\n| `typescript` | Type safety | 5.8+ |\n| `vite` | Build tool | 6.0+ |\n| `recharts` | Charts (equity, comparison) | 2.15+ |\n| `swr` | Data fetching & caching | 2.2+ |\n| `zustand` | State management | 5.0+ |\n| `tailwindcss` | CSS framework | 3.4+ |\n| `lucide-react` | Icon library | Latest |\n\n---\n\n## 🗂️ System Architecture\n\n### High-Level Overview\n\n```\n┌──────────────────────────────────────────────────────────────────┐\n│ PRESENTATION LAYER │\n│ React SPA (Vite + TypeScript + TailwindCSS) │\n│ - Competition dashboard, trader management UI │\n│ - Real-time charts (Recharts), authentication pages │\n└──────────────────────────────────────────────────────────────────┘\n ↓ HTTP/JSON API\n┌──────────────────────────────────────────────────────────────────┐\n│ API LAYER (Gin Router) │\n│ /api/traders, /api/status, /api/positions, /api/decisions │\n│ Authentication middleware (JWT), CORS handling │\n└──────────────────────────────────────────────────────────────────┘\n ↓\n┌──────────────────────────────────────────────────────────────────┐\n│ BUSINESS LOGIC LAYER │\n│ ┌──────────────────┐ ┌──────────────────┐ ┌────────────────┐ │\n│ │ TraderManager │ │ DecisionEngine │ │ MarketData │ │\n│ │ - Multi-trader │ │ - AI reasoning │ │ - K-lines │ │\n│ │ orchestration │ │ - Risk control │ │ - Indicators │ │\n│ └──────────────────┘ └──────────────────┘ └────────────────┘ │\n└──────────────────────────────────────────────────────────────────┘\n ↓\n┌──────────────────────────────────────────────────────────────────┐\n│ DATA ACCESS LAYER │\n│ ┌──────────────┐ ┌──────────────┐ ┌────────────────────┐ │\n│ │ SQLite DB │ │ File Logger │ │ External APIs │ │\n│ │ - Traders │ │ - Decisions │ │ - Binance │ │\n│ │ - Models │ │ - Performance│ │ - Hyperliquid │ │\n│ │ - Exchanges │ │ analysis │ │ - Aster │ │\n│ └──────────────┘ └──────────────┘ └────────────────────┘ │\n└──────────────────────────────────────────────────────────────────┘\n```\n\n### Component Diagram\n\n*(Coming soon: detailed component interaction diagram)*\n\n---\n\n## 📚 Core Modules\n\n### 1. Trader System (`trader/`)\n\n**Purpose:** Trading execution layer with multi-exchange support\n\n**Key Files:**\n- `auto_trader.go` - Main trading orchestrator (100+ lines)\n- `interface.go` - Unified trader interface\n- `binance_futures.go` - Binance API wrapper\n- `hyperliquid_trader.go` - Hyperliquid DEX wrapper\n- `aster_trader.go` - Aster DEX wrapper\n\n**Design Pattern:** Strategy pattern with interface-based abstraction\n\n**Example:**\n```go\ntype ExchangeClient interface {\n GetAccount() (*AccountInfo, error)\n GetPositions() ([]*Position, error)\n CreateOrder(*OrderParams) (*Order, error)\n // ... more methods\n}\n```\n\n---\n\n### 2. Decision Engine (`decision/`)\n\n**Purpose:** AI-powered trading decision making\n\n**Key Files:**\n- `engine.go` - Decision logic with historical feedback\n- `prompt_manager.go` - Template system for AI prompts\n\n**Features:**\n- Chain-of-Thought reasoning\n- Historical performance analysis\n- Risk-aware decision making\n- Multi-model support (DeepSeek, Qwen, custom)\n\n**Flow:**\n```\nHistorical Data → Prompt Generation → AI API Call →\nDecision Parsing → Risk Validation → Execution\n```\n\n---\n\n### 3. Market Data System (`market/`)\n\n**Purpose:** Fetch and analyze market data\n\n**Key Files:**\n- `data.go` - Market data fetching and technical indicators\n\n**Features:**\n- Multi-timeframe K-line data (3min, 4hour)\n- Technical indicators via TA-Lib:\n - EMA (20, 50)\n - MACD\n - RSI (7, 14)\n - ATR (volatility)\n- Open Interest tracking\n\n---\n\n### 4. Manager (`manager/`)\n\n**Purpose:** Multi-trader orchestration\n\n**Key Files:**\n- `trader_manager.go` - Manages multiple trader instances\n\n**Responsibilities:**\n- Trader lifecycle (start, stop, restart)\n- Resource allocation\n- Concurrent execution coordination\n\n---\n\n### 5. API Server (`api/`)\n\n**Purpose:** HTTP API for frontend communication\n\n**Key Files:**\n- `server.go` - Gin framework RESTful API\n\n**Endpoints:**\n```\nGET /api/traders # List all traders\nPOST /api/traders # Create trader\nPOST /api/traders/:id/start # Start trader\nGET /api/status # System status\nGET /api/positions # Current positions\nGET /api/decisions/latest # Recent decisions\n```\n\n---\n\n### 6. Database Layer (`config/`)\n\n**Purpose:** SQLite data persistence\n\n**Key Files:**\n- `database.go` - Database operations and schema\n\n**Tables:**\n- `users` - User accounts (with 2FA support)\n- `ai_models` - AI model configurations\n- `exchanges` - Exchange credentials\n- `traders` - Trader instances\n- `equity_history` - Performance tracking\n- `system_config` - Application settings\n\n---\n\n### 7. Authentication (`auth/`)\n\n**Purpose:** User authentication and authorization\n\n**Features:**\n- JWT token-based auth\n- 2FA with TOTP (Google Authenticator)\n- Bcrypt password hashing\n- Admin mode (simplified single-user)\n\n---\n\n## 🔄 Request Flow Examples\n\n### Example 1: Create New Trader\n\n```\nUser Action (Frontend)\n ↓\nPOST /api/traders\n ↓\nAPI Server (auth middleware)\n ↓\nDatabase.CreateTrader()\n ↓\nTraderManager.StartTrader()\n ↓\nAutoTrader.Run() → goroutine\n ↓\nResponse: {trader_id, status}\n```\n\n### Example 2: Trading Decision Cycle\n\n```\nAutoTrader (every 3-5 min)\n ↓\n1. FetchAccountStatus()\n ↓\n2. GetOpenPositions()\n ↓\n3. FetchMarketData() → TA-Lib indicators\n ↓\n4. AnalyzeHistory() → last 20 trades\n ↓\n5. GeneratePrompt() → full context\n ↓\n6. CallAI() → DeepSeek/Qwen\n ↓\n7. ParseDecision() → structured output\n ↓\n8. ValidateRisk() → position limits, margin\n ↓\n9. ExecuteOrders() → exchange API\n ↓\n10. LogDecision() → JSON file + database\n```\n\n---\n\n## 📊 Data Flow\n\n### Market Data Flow\n\n```\nExchange API\n ↓\nmarket.FetchKlines()\n ↓\nTA-Lib.Calculate(EMA, MACD, RSI)\n ↓\nDecisionEngine (as context)\n ↓\nAI Model (reasoning)\n```\n\n### Decision Logging Flow\n\n```\nAI Response\n ↓\ndecision_logger.go\n ↓\nJSON file: decision_logs/{trader_id}/{timestamp}.json\n ↓\nDatabase: performance tracking\n ↓\nFrontend: /api/decisions/latest\n```\n\n---\n\n## 🗄️ Database Schema\n\n### Core Tables\n\n**users**\n```sql\n- id (INTEGER PRIMARY KEY)\n- username (TEXT UNIQUE)\n- password_hash (TEXT)\n- totp_secret (TEXT)\n- is_admin (BOOLEAN)\n- created_at (DATETIME)\n```\n\n**ai_models**\n```sql\n- id (INTEGER PRIMARY KEY)\n- name (TEXT)\n- model_type (TEXT) -- deepseek, qwen, custom\n- api_key (TEXT)\n- api_url (TEXT)\n- enabled (BOOLEAN)\n```\n\n**traders**\n```sql\n- id (TEXT PRIMARY KEY)\n- name (TEXT)\n- ai_model_id (INTEGER FK)\n- exchange_id (INTEGER FK)\n- initial_balance (REAL)\n- current_equity (REAL)\n- status (TEXT) -- running, stopped\n- created_at (DATETIME)\n```\n\n*(More details: database-schema.md - coming soon)*\n\n---\n\n## 🔌 API Reference\n\n### Authentication\n\n**POST /api/auth/login**\n```json\nRequest: {\n \"username\": \"string\",\n \"password\": \"string\",\n \"totp_code\": \"string\" // optional\n}\n\nResponse: {\n \"token\": \"jwt_token\",\n \"user\": {...}\n}\n```\n\n### Trader Management\n\n**GET /api/traders**\n```json\nResponse: {\n \"traders\": [\n {\n \"id\": \"string\",\n \"name\": \"string\",\n \"status\": \"running|stopped\",\n \"balance\": 1000.0,\n \"roi\": 5.2\n }\n ]\n}\n```\n\n*(Full API reference: api-reference.md - coming soon)*\n\n---\n\n## 🧪 Testing Architecture\n\n### Current State\n- ⚠️ No unit tests yet\n- ⚠️ Manual testing only\n- ⚠️ Testnet verification\n\n### Planned Testing Strategy\n\n**Unit Tests (Priority 1)**\n```\ntrader/binance_futures_test.go\n- Mock API responses\n- Test precision handling\n- Validate order construction\n```\n\n**Integration Tests (Priority 2)**\n```\n- End-to-end trading flow (testnet)\n- Multi-trader scenarios\n- Database operations\n```\n\n**Frontend Tests (Priority 3)**\n```\n- Component tests (Vitest + React Testing Library)\n- API integration tests\n- E2E tests (Playwright)\n```\n\n*(Testing guide: testing-guide.md - coming soon)*\n\n---\n\n## 🔧 Development Tools\n\n### Build & Run\n\n```bash\n# Backend\ngo build -o nofx\n./nofx\n\n# Frontend\ncd web\nnpm run dev\n\n# Docker\ndocker compose up --build\n```\n\n### Code Quality\n\n```bash\n# Format Go code\ngo fmt ./...\n\n# Lint (if configured)\ngolangci-lint run\n\n# Type check TypeScript\ncd web && npm run build\n```\n\n---\n\n## 📈 Performance Considerations\n\n### Backend\n- **Concurrency:** Each trader runs in separate goroutine\n- **Database:** SQLite (good for <100 traders)\n- **API Rate Limits:** Handled per exchange\n- **Memory:** ~50-100MB per trader\n\n### Frontend\n- **Data Fetching:** SWR with 5-10s polling\n- **State:** Zustand (lightweight)\n- **Bundle Size:** ~500KB (gzipped)\n\n---\n\n## 🔮 Future Architecture Plans\n\n### Planned Improvements\n\n1. **Microservices Split** (if scaling needed)\n - Separate decision engine service\n - Market data service\n - Execution service\n\n2. **Database Migration**\n - Mysql for production (>100 traders)\n - Redis for caching\n\n3. **Event-Driven Architecture**\n - WebSocket for real-time updates\n - Message queue (RabbitMQ/NATS)\n\n4. **Kubernetes Deployment**\n - Helm charts\n - Auto-scaling\n - High availability\n\n---\n\n## 🆘 For Developers\n\n**Want to contribute?**\n- Read [Contributing Guide](../../CONTRIBUTING.md)\n- Check [Open Issues](https://github.com/tinkle-community/nofx/issues)\n- Join [Telegram Community](https://t.me/nofx_dev_community)\n\n**Need clarification?**\n- Open a [GitHub Discussion](https://github.com/tinkle-community/nofx/discussions)\n- Ask in Telegram\n\n---\n\n## 📚 Related Documentation\n\n- [Getting Started](../getting-started/README.md) - Setup and deployment\n- [Contributing](../../CONTRIBUTING.md) - How to contribute\n- [Community](../community/README.md) - Bounties and recognition\n\n---\n\n[← Back to Documentation Home](../README.md)\n"}, "95c32fcb2e1a5ef6d08005478d376f66fc6a1591": {"t": 1785759334.7673624, "n": 14364, "h": "58d70f8ec30eae86", "c": "# 🏗️ NOFX Architecture Documentation\n\n**Language:** [English](README.md) | [中文](README.zh-CN.md)\n\nTechnical documentation for developers who want to understand NOFX internals.\n\n---\n\n## 📋 Overview\n\nNOFX is a full-stack AI trading platform with:\n- **Backend:** Go (Gin framework, SQLite)\n- **Frontend:** React/TypeScript (Vite, TailwindCSS)\n- **Architecture:** Microservice-inspired modular design\n\n---\n\n## 📁 Project Structure\n\n```\nnofx/\n├── main.go # Program entry (multi-trader manager)\n├── config.json # ~~Multi-trader config~~ (Now via web interface)\n├── trading.db # SQLite database (traders, models, exchanges)\n│\n├── api/ # HTTP API service\n│ └── server.go # Gin framework, RESTful API\n│\n├── trader/ # Trading core\n│ ├── auto_trader.go # Auto trading main controller\n│ ├── interface.go # Unified trader interface\n│ ├── binance_futures.go # Binance API wrapper\n│ ├── hyperliquid_trader.go # Hyperliquid DEX wrapper\n│ └── aster_trader.go # Aster DEX wrapper\n│\n├── manager/ # Multi-trader management\n│ └── trader_manager.go # Manages multiple trader instances\n│\n├── config/ # Configuration & database\n│ └── database.go # SQLite operations and schema\n│\n├── auth/ # Authentication\n│ └── jwt.go # JWT token management & 2FA\n│\n├── mcp/ # Model Context Protocol - AI communication\n│ └── client.go # AI API client (DeepSeek/Qwen/Custom)\n│\n├── decision/ # AI decision engine\n│ ├── engine.go # Decision logic with historical feedback\n│ └── prompt_manager.go # Prompt template system\n│\n├── market/ # Market data fetching\n│ └── data.go # Market data & technical indicators (TA-Lib)\n│ └── api_client.go # Market data acquisition API\n│ └── websocket_client.go # Market data acquisition WebSocket interface\n│ └── combined_streams.go # Market data acquisition: Combined streaming (single link to subscribe to multiple cryptocurrencies)\n│ └── monitor.go # Market data cache\n│ └── types.go # market structure\n\n├── pool/ # Coin pool management\n│ └── coin_pool.go # AI500 + OI Top merged pool\n│\n├── logger/ # Logging system\n│ └── decision_logger.go # Decision recording + performance analysis\n│\n├── decision_logs/ # Decision log storage (JSON files)\n│ ├── {trader_id}/ # Per-trader logs\n│ └── {timestamp}.json # Individual decisions\n│\n└── web/ # React frontend\n ├── src/\n │ ├── components/ # React components\n │ │ ├── EquityChart.tsx # Equity curve chart\n │ │ ├── ComparisonChart.tsx # Multi-AI comparison chart\n │ │ └── CompetitionPage.tsx # Competition leaderboard\n │ ├── lib/api.ts # API call wrapper\n │ ├── types/index.ts # TypeScript types\n │ ├── stores/ # Zustand state management\n │ ├── index.css # Binance-style CSS\n │ └── App.tsx # Main app\n ├── package.json # Frontend dependencies\n └── vite.config.ts # Vite configuration\n```\n\n---\n\n## 🔧 Core Dependencies\n\n### Backend (Go)\n\n| Package | Purpose | Version |\n|---------|---------|---------|\n| `github.com/gin-gonic/gin` | HTTP API framework | v1.9+ |\n| `github.com/adshao/go-binance/v2` | Binance API client | v2.4+ |\n| `github.com/markcheno/go-talib` | Technical indicators (TA-Lib) | Latest |\n| `github.com/mattn/go-sqlite3` | SQLite database driver | v1.14+ |\n| `github.com/golang-jwt/jwt/v5` | JWT authentication | v5.0+ |\n| `github.com/pquerna/otp` | 2FA/TOTP support | v1.4+ |\n| `golang.org/x/crypto` | Password hashing (bcrypt) | Latest |\n\n### Frontend (React + TypeScript)\n\n| Package | Purpose | Version |\n|---------|---------|---------|\n| `react` + `react-dom` | UI framework | 18.3+ |\n| `typescript` | Type safety | 5.8+ |\n| `vite` | Build tool | 6.0+ |\n| `recharts` | Charts (equity, comparison) | 2.15+ |\n| `swr` | Data fetching & caching | 2.2+ |\n| `zustand` | State management | 5.0+ |\n| `tailwindcss` | CSS framework | 3.4+ |\n| `lucide-react` | Icon library | Latest |\n\n---\n\n## 🗂️ System Architecture\n\n### High-Level Overview\n\n```\n┌──────────────────────────────────────────────────────────────────┐\n│ PRESENTATION LAYER │\n│ React SPA (Vite + TypeScript + TailwindCSS) │\n│ - Competition dashboard, trader management UI │\n│ - Real-time charts (Recharts), authentication pages │\n└──────────────────────────────────────────────────────────────────┘\n ↓ HTTP/JSON API\n┌──────────────────────────────────────────────────────────────────┐\n│ API LAYER (Gin Router) │\n│ /api/traders, /api/status, /api/positions, /api/decisions │\n│ Authentication middleware (JWT), CORS handling │\n└──────────────────────────────────────────────────────────────────┘\n ↓\n┌──────────────────────────────────────────────────────────────────┐\n│ BUSINESS LOGIC LAYER │\n│ ┌──────────────────┐ ┌──────────────────┐ ┌────────────────┐ │\n│ │ TraderManager │ │ DecisionEngine │ │ MarketData │ │\n│ │ - Multi-trader │ │ - AI reasoning │ │ - K-lines │ │\n│ │ orchestration │ │ - Risk control │ │ - Indicators │ │\n│ └──────────────────┘ └──────────────────┘ └────────────────┘ │\n└──────────────────────────────────────────────────────────────────┘\n ↓\n┌──────────────────────────────────────────────────────────────────┐\n│ DATA ACCESS LAYER │\n│ ┌──────────────┐ ┌──────────────┐ ┌────────────────────┐ │\n│ │ SQLite DB │ │ File Logger │ │ External APIs │ │\n│ │ - Traders │ │ - Decisions │ │ - Binance │ │\n│ │ - Models │ │ - Performance│ │ - Hyperliquid │ │\n│ │ - Exchanges │ │ analysis │ │ - Aster │ │\n│ └──────────────┘ └──────────────┘ └────────────────────┘ │\n└──────────────────────────────────────────────────────────────────┘\n```\n\n### Component Diagram\n\n*(Coming soon: detailed component interaction diagram)*\n\n---\n\n## 📚 Core Modules\n\n### 1. Trader System (`trader/`)\n\n**Purpose:** Trading execution layer with multi-exchange support\n\n**Key Files:**\n- `auto_trader.go` - Main trading orchestrator (100+ lines)\n- `interface.go` - Unified trader interface\n- `binance_futures.go` - Binance API wrapper\n- `hyperliquid_trader.go` - Hyperliquid DEX wrapper\n- `aster_trader.go` - Aster DEX wrapper\n\n**Design Pattern:** Strategy pattern with interface-based abstraction\n\n**Example:**\n```go\ntype ExchangeClient interface {\n GetAccount() (*AccountInfo, error)\n GetPositions() ([]*Position, error)\n CreateOrder(*OrderParams) (*Order, error)\n // ... more methods\n}\n```\n\n---\n\n### 2. Decision Engine (`decision/`)\n\n**Purpose:** AI-powered trading decision making\n\n**Key Files:**\n- `engine.go` - Decision logic with historical feedback\n- `prompt_manager.go` - Template system for AI prompts\n\n**Features:**\n- Chain-of-Thought reasoning\n- Historical performance analysis\n- Risk-aware decision making\n- Multi-model support (DeepSeek, Qwen, custom)\n\n**Flow:**\n```\nHistorical Data → Prompt Generation → AI API Call →\nDecision Parsing → Risk Validation → Execution\n```\n\n---\n\n### 3. Market Data System (`market/`)\n\n**Purpose:** Fetch and analyze market data\n\n**Key Files:**\n- `data.go` - Market data fetching and technical indicators\n\n**Features:**\n- Multi-timeframe K-line data (3min, 4hour)\n- Technical indicators via TA-Lib:\n - EMA (20, 50)\n - MACD\n - RSI (7, 14)\n - ATR (volatility)\n- Open Interest tracking\n\n---\n\n### 4. Manager (`manager/`)\n\n**Purpose:** Multi-trader orchestration\n\n**Key Files:**\n- `trader_manager.go` - Manages multiple trader instances\n\n**Responsibilities:**\n- Trader lifecycle (start, stop, restart)\n- Resource allocation\n- Concurrent execution coordination\n\n---\n\n### 5. API Server (`api/`)\n\n**Purpose:** HTTP API for frontend communication\n\n**Key Files:**\n- `server.go` - Gin framework RESTful API\n\n**Endpoints:**\n```\nGET /api/traders # List all traders\nPOST /api/traders # Create trader\nPOST /api/traders/:id/start # Start trader\nGET /api/status # System status\nGET /api/positions # Current positions\nGET /api/decisions/latest # Recent decisions\n```\n\n---\n\n### 6. Database Layer (`config/`)\n\n**Purpose:** SQLite data persistence\n\n**Key Files:**\n- `database.go` - Database operations and schema\n\n**Tables:**\n- `users` - User accounts (with 2FA support)\n- `ai_models` - AI model configurations\n- `exchanges` - Exchange credentials\n- `traders` - Trader instances\n- `equity_history` - Performance tracking\n- `system_config` - Application settings\n\n---\n\n### 7. Authentication (`auth/`)\n\n**Purpose:** User authentication and authorization\n\n**Features:**\n- JWT token-based auth\n- 2FA with TOTP (Google Authenticator)\n- Bcrypt password hashing\n- Admin mode (simplified single-user)\n\n---\n\n## 🔄 Request Flow Examples\n\n### Example 1: Create New Trader\n\n```\nUser Action (Frontend)\n ↓\nPOST /api/traders\n ↓\nAPI Server (auth middleware)\n ↓\nDatabase.CreateTrader()\n ↓\nTraderManager.StartTrader()\n ↓\nAutoTrader.Run() → goroutine\n ↓\nResponse: {trader_id, status}\n```\n\n### Example 2: Trading Decision Cycle\n\n```\nAutoTrader (every 3-5 min)\n ↓\n1. FetchAccountStatus()\n ↓\n2. GetOpenPositions()\n ↓\n3. FetchMarketData() → TA-Lib indicators\n ↓\n4. AnalyzeHistory() → last 20 trades\n ↓\n5. GeneratePrompt() → full context\n ↓\n6. CallAI() → DeepSeek/Qwen\n ↓\n7. ParseDecision() → structured output\n ↓\n8. ValidateRisk() → position limits, margin\n ↓\n9. ExecuteOrders() → exchange API\n ↓\n10. LogDecision() → JSON file + database\n```\n\n---\n\n## 📊 Data Flow\n\n### Market Data Flow\n\n```\nExchange API\n ↓\nmarket.FetchKlines()\n ↓\nTA-Lib.Calculate(EMA, MACD, RSI)\n ↓\nDecisionEngine (as context)\n ↓\nAI Model (reasoning)\n```\n\n### Decision Logging Flow\n\n```\nAI Response\n ↓\ndecision_logger.go\n ↓\nJSON file: decision_logs/{trader_id}/{timestamp}.json\n ↓\nDatabase: performance tracking\n ↓\nFrontend: /api/decisions/latest\n```\n\n---\n\n## 🗄️ Database Schema\n\n### Core Tables\n\n**users**\n```sql\n- id (INTEGER PRIMARY KEY)\n- username (TEXT UNIQUE)\n- password_hash (TEXT)\n- totp_secret (TEXT)\n- is_admin (BOOLEAN)\n- created_at (DATETIME)\n```\n\n**ai_models**\n```sql\n- id (INTEGER PRIMARY KEY)\n- name (TEXT)\n- model_type (TEXT) -- deepseek, qwen, custom\n- api_key (TEXT)\n- api_url (TEXT)\n- enabled (BOOLEAN)\n```\n\n**traders**\n```sql\n- id (TEXT PRIMARY KEY)\n- name (TEXT)\n- ai_model_id (INTEGER FK)\n- exchange_id (INTEGER FK)\n- initial_balance (REAL)\n- current_equity (REAL)\n- status (TEXT) -- running, stopped\n- created_at (DATETIME)\n```\n\n*(More details: database-schema.md - coming soon)*\n\n---\n\n## 🔌 API Reference\n\n### Authentication\n\n**POST /api/auth/login**\n```json\nRequest: {\n \"username\": \"string\",\n \"password\": \"string\",\n \"totp_code\": \"string\" // optional\n}\n\nResponse: {\n \"token\": \"jwt_token\",\n \"user\": {...}\n}\n```\n\n### Trader Management\n\n**GET /api/traders**\n```json\nResponse: {\n \"traders\": [\n {\n \"id\": \"string\",\n \"name\": \"string\",\n \"status\": \"running|stopped\",\n \"balance\": 1000.0,\n \"roi\": 5.2\n }\n ]\n}\n```\n\n*(Full API reference: api-reference.md - coming soon)*\n\n---\n\n## 🧪 Testing Architecture\n\n### Current State\n- ⚠️ No unit tests yet\n- ⚠️ Manual testing only\n- ⚠️ Testnet verification\n\n### Planned Testing Strategy\n\n**Unit Tests (Priority 1)**\n```\ntrader/binance_futures_test.go\n- Mock API responses\n- Test precision handling\n- Validate order construction\n```\n\n**Integration Tests (Priority 2)**\n```\n- End-to-end trading flow (testnet)\n- Multi-trader scenarios\n- Database operations\n```\n\n**Frontend Tests (Priority 3)**\n```\n- Component tests (Vitest + React Testing Library)\n- API integration tests\n- E2E tests (Playwright)\n```\n\n*(Testing guide: testing-guide.md - coming soon)*\n\n---\n\n## 🔧 Development Tools\n\n### Build & Run\n\n```bash\n# Backend\ngo build -o nofx\n./nofx\n\n# Frontend\ncd web\nnpm run dev\n\n# Docker\ndocker compose up --build\n```\n\n### Code Quality\n\n```bash\n# Format Go code\ngo fmt ./...\n\n# Lint (if configured)\ngolangci-lint run\n\n# Type check TypeScript\ncd web && npm run build\n```\n\n---\n\n## 📈 Performance Considerations\n\n### Backend\n- **Concurrency:** Each trader runs in separate goroutine\n- **Database:** SQLite (good for <100 traders)\n- **API Rate Limits:** Handled per exchange\n- **Memory:** ~50-100MB per trader\n\n### Frontend\n- **Data Fetching:** SWR with 5-10s polling\n- **State:** Zustand (lightweight)\n- **Bundle Size:** ~500KB (gzipped)\n\n---\n\n## 🔮 Future Architecture Plans\n\n### Planned Improvements\n\n1. **Microservices Split** (if scaling needed)\n - Separate decision engine service\n - Market data service\n - Execution service\n\n2. **Database Migration**\n - Mysql for production (>100 traders)\n - Redis for caching\n\n3. **Event-Driven Architecture**\n - WebSocket for real-time updates\n - Message queue (RabbitMQ/NATS)\n\n4. **Kubernetes Deployment**\n - Helm charts\n - Auto-scaling\n - High availability\n\n---\n\n## 🆘 For Developers\n\n**Want to contribute?**\n- Read [Contributing Guide](../../CONTRIBUTING.md)\n- Check [Open Issues](https://github.com/tinkle-community/nofx/issues)\n- Join [Telegram Community](https://t.me/nofx_dev_community)\n\n**Need clarification?**\n- Open a [GitHub Discussion](https://github.com/tinkle-community/nofx/discussions)\n- Ask in Telegram\n\n---\n\n## 📚 Related Documentation\n\n- [Getting Started](../getting-started/README.md) - Setup and deployment\n- [Contributing](../../CONTRIBUTING.md) - How to contribute\n- [Community](../community/README.md) - Bounties and recognition\n\n---\n\n[← Back to Documentation Home](../README.md)\n"}, "18820fc319c8e7546dba2bb6ce2d0042c4d59345": {"t": 1785759336.385803, "n": 13968, "h": "6ce74d604dee2322", "c": "# 🏗️ NOFX Architecture Documentation\n\n**Language:** [English](README.md) | [中文](README.zh-CN.md)\n\nTechnical documentation for developers who want to understand NOFX internals.\n\n---\n\n## 📋 Overview\n\nNOFX is a full-stack AI trading platform with:\n- **Backend:** Go (Gin framework, SQLite)\n- **Frontend:** React/TypeScript (Vite, TailwindCSS)\n- **Architecture:** Microservice-inspired modular design\n\n---\n\n## 📁 Project Structure\n\n```\nnofx/\n├── main.go # Program entry (multi-trader manager)\n├── config.json # ~~Multi-trader config~~ (Now via web interface)\n├── trading.db # SQLite database (traders, models, exchanges)\n│\n├── api/ # HTTP API service\n│ └── server.go # Gin framework, RESTful API\n│\n├── trader/ # Trading core\n│ ├── auto_trader.go # Auto trading main controller\n│ ├── interface.go # Unified trader interface\n│ ├── binance_futures.go # Binance API wrapper\n│ ├── hyperliquid_trader.go # Hyperliquid DEX wrapper\n│ └── aster_trader.go # Aster DEX wrapper\n│\n├── manager/ # Multi-trader management\n│ └── trader_manager.go # Manages multiple trader instances\n│\n├── config/ # Configuration & database\n│ └── database.go # SQLite operations and schema\n│\n├── auth/ # Authentication\n│ └── jwt.go # JWT token management & 2FA\n│\n├── mcp/ # Model Context Protocol - AI communication\n│ └── client.go # AI API client (DeepSeek/Qwen/Custom)\n│\n├── decision/ # AI decision engine\n│ ├── engine.go # Decision logic with historical feedback\n│ └── prompt_manager.go # Prompt template system\n│\n├── market/ # Market data fetching\n│ └── data.go # Market data & technical indicators (TA-Lib)\n│\n├── pool/ # Coin pool management\n│ └── coin_pool.go # AI500 + OI Top merged pool\n│\n├── logger/ # Logging system\n│ └── decision_logger.go # Decision recording + performance analysis\n│\n├── decision_logs/ # Decision log storage (JSON files)\n│ ├── {trader_id}/ # Per-trader logs\n│ └── {timestamp}.json # Individual decisions\n│\n└── web/ # React frontend\n ├── src/\n │ ├── components/ # React components\n │ │ ├── EquityChart.tsx # Equity curve chart\n │ │ ├── ComparisonChart.tsx # Multi-AI comparison chart\n │ │ └── CompetitionPage.tsx # Competition leaderboard\n │ ├── lib/api.ts # API call wrapper\n │ ├── types/index.ts # TypeScript types\n │ ├── stores/ # Zustand state management\n │ ├── index.css # Binance-style CSS\n │ └── App.tsx # Main app\n ├── package.json # Frontend dependencies\n └── vite.config.ts # Vite configuration\n```\n\n---\n\n## 🔧 Core Dependencies\n\n### Backend (Go)\n\n| Package | Purpose | Version |\n|---------|---------|---------|\n| `github.com/gin-gonic/gin` | HTTP API framework | v1.9+ |\n| `github.com/adshao/go-binance/v2` | Binance API client | v2.4+ |\n| `github.com/markcheno/go-talib` | Technical indicators (TA-Lib) | Latest |\n| `github.com/mattn/go-sqlite3` | SQLite database driver | v1.14+ |\n| `github.com/golang-jwt/jwt/v5` | JWT authentication | v5.0+ |\n| `github.com/pquerna/otp` | 2FA/TOTP support | v1.4+ |\n| `golang.org/x/crypto` | Password hashing (bcrypt) | Latest |\n\n### Frontend (React + TypeScript)\n\n| Package | Purpose | Version |\n|---------|---------|---------|\n| `react` + `react-dom` | UI framework | 18.3+ |\n| `typescript` | Type safety | 5.8+ |\n| `vite` | Build tool | 6.0+ |\n| `recharts` | Charts (equity, comparison) | 2.15+ |\n| `swr` | Data fetching & caching | 2.2+ |\n| `zustand` | State management | 5.0+ |\n| `tailwindcss` | CSS framework | 3.4+ |\n| `lucide-react` | Icon library | Latest |\n\n---\n\n## 🗂️ System Architecture\n\n### High-Level Overview\n\n```\n┌──────────────────────────────────────────────────────────────────┐\n│ PRESENTATION LAYER │\n│ React SPA (Vite + TypeScript + TailwindCSS) │\n│ - Competition dashboard, trader management UI │\n│ - Real-time charts (Recharts), authentication pages │\n└──────────────────────────────────────────────────────────────────┘\n ↓ HTTP/JSON API\n┌──────────────────────────────────────────────────────────────────┐\n│ API LAYER (Gin Router) │\n│ /api/traders, /api/status, /api/positions, /api/decisions │\n│ Authentication middleware (JWT), CORS handling │\n└──────────────────────────────────────────────────────────────────┘\n ↓\n┌──────────────────────────────────────────────────────────────────┐\n│ BUSINESS LOGIC LAYER │\n│ ┌──────────────────┐ ┌──────────────────┐ ┌────────────────┐ │\n│ │ TraderManager │ │ DecisionEngine │ │ MarketData │ │\n│ │ - Multi-trader │ │ - AI reasoning │ │ - K-lines │ │\n│ │ orchestration │ │ - Risk control │ │ - Indicators │ │\n│ └──────────────────┘ └──────────────────┘ └────────────────┘ │\n└──────────────────────────────────────────────────────────────────┘\n ↓\n┌──────────────────────────────────────────────────────────────────┐\n│ DATA ACCESS LAYER │\n│ ┌──────────────┐ ┌──────────────┐ ┌────────────────────┐ │\n│ │ SQLite DB │ │ File Logger │ │ External APIs │ │\n│ │ - Traders │ │ - Decisions │ │ - Binance │ │\n│ │ - Models │ │ - Performance│ │ - Hyperliquid │ │\n│ │ - Exchanges │ │ analysis │ │ - Aster │ │\n│ └──────────────┘ └──────────────┘ └────────────────────┘ │\n└──────────────────────────────────────────────────────────────────┘\n```\n\n### Component Diagram\n\n*(Coming soon: detailed component interaction diagram)*\n\n---\n\n## 📚 Core Modules\n\n### 1. Trader System (`trader/`)\n\n**Purpose:** Trading execution layer with multi-exchange support\n\n**Key Files:**\n- `auto_trader.go` - Main trading orchestrator (100+ lines)\n- `interface.go` - Unified trader interface\n- `binance_futures.go` - Binance API wrapper\n- `hyperliquid_trader.go` - Hyperliquid DEX wrapper\n- `aster_trader.go` - Aster DEX wrapper\n\n**Design Pattern:** Strategy pattern with interface-based abstraction\n\n**Example:**\n```go\ntype ExchangeClient interface {\n GetAccount() (*AccountInfo, error)\n GetPositions() ([]*Position, error)\n CreateOrder(*OrderParams) (*Order, error)\n // ... more methods\n}\n```\n\n---\n\n### 2. Decision Engine (`decision/`)\n\n**Purpose:** AI-powered trading decision making\n\n**Key Files:**\n- `engine.go` - Decision logic with historical feedback\n- `prompt_manager.go` - Template system for AI prompts\n\n**Features:**\n- Chain-of-Thought reasoning\n- Historical performance analysis\n- Risk-aware decision making\n- Multi-model support (DeepSeek, Qwen, custom)\n\n**Flow:**\n```\nHistorical Data → Prompt Generation → AI API Call →\nDecision Parsing → Risk Validation → Execution\n```\n\n---\n\n### 3. Market Data System (`market/`)\n\n**Purpose:** Fetch and analyze market data\n\n**Key Files:**\n- `data.go` - Market data fetching and technical indicators\n\n**Features:**\n- Multi-timeframe K-line data (3min, 4hour)\n- Technical indicators via TA-Lib:\n - EMA (20, 50)\n - MACD\n - RSI (7, 14)\n - ATR (volatility)\n- Open Interest tracking\n\n---\n\n### 4. Manager (`manager/`)\n\n**Purpose:** Multi-trader orchestration\n\n**Key Files:**\n- `trader_manager.go` - Manages multiple trader instances\n\n**Responsibilities:**\n- Trader lifecycle (start, stop, restart)\n- Resource allocation\n- Concurrent execution coordination\n\n---\n\n### 5. API Server (`api/`)\n\n**Purpose:** HTTP API for frontend communication\n\n**Key Files:**\n- `server.go` - Gin framework RESTful API\n\n**Endpoints:**\n```\nGET /api/traders # List all traders\nPOST /api/traders # Create trader\nPOST /api/traders/:id/start # Start trader\nGET /api/status # System status\nGET /api/positions # Current positions\nGET /api/decisions/latest # Recent decisions\n```\n\n---\n\n### 6. Database Layer (`config/`)\n\n**Purpose:** SQLite data persistence\n\n**Key Files:**\n- `database.go` - Database operations and schema\n\n**Tables:**\n- `users` - User accounts (with 2FA support)\n- `ai_models` - AI model configurations\n- `exchanges` - Exchange credentials\n- `traders` - Trader instances\n- `equity_history` - Performance tracking\n- `system_config` - Application settings\n\n---\n\n### 7. Authentication (`auth/`)\n\n**Purpose:** User authentication and authorization\n\n**Features:**\n- JWT token-based auth\n- 2FA with TOTP (Google Authenticator)\n- Bcrypt password hashing\n- Admin mode (simplified single-user)\n\n---\n\n## 🔄 Request Flow Examples\n\n### Example 1: Create New Trader\n\n```\nUser Action (Frontend)\n ↓\nPOST /api/traders\n ↓\nAPI Server (auth middleware)\n ↓\nDatabase.CreateTrader()\n ↓\nTraderManager.StartTrader()\n ↓\nAutoTrader.Run() → goroutine\n ↓\nResponse: {trader_id, status}\n```\n\n### Example 2: Trading Decision Cycle\n\n```\nAutoTrader (every 3-5 min)\n ↓\n1. FetchAccountStatus()\n ↓\n2. GetOpenPositions()\n ↓\n3. FetchMarketData() → TA-Lib indicators\n ↓\n4. AnalyzeHistory() → last 20 trades\n ↓\n5. GeneratePrompt() → full context\n ↓\n6. CallAI() → DeepSeek/Qwen\n ↓\n7. ParseDecision() → structured output\n ↓\n8. ValidateRisk() → position limits, margin\n ↓\n9. ExecuteOrders() → exchange API\n ↓\n10. LogDecision() → JSON file + database\n```\n\n---\n\n## 📊 Data Flow\n\n### Market Data Flow\n\n```\nExchange API\n ↓\nmarket.FetchKlines()\n ↓\nTA-Lib.Calculate(EMA, MACD, RSI)\n ↓\nDecisionEngine (as context)\n ↓\nAI Model (reasoning)\n```\n\n### Decision Logging Flow\n\n```\nAI Response\n ↓\ndecision_logger.go\n ↓\nJSON file: decision_logs/{trader_id}/{timestamp}.json\n ↓\nDatabase: performance tracking\n ↓\nFrontend: /api/decisions/latest\n```\n\n---\n\n## 🗄️ Database Schema\n\n### Core Tables\n\n**users**\n```sql\n- id (INTEGER PRIMARY KEY)\n- username (TEXT UNIQUE)\n- password_hash (TEXT)\n- totp_secret (TEXT)\n- is_admin (BOOLEAN)\n- created_at (DATETIME)\n```\n\n**ai_models**\n```sql\n- id (INTEGER PRIMARY KEY)\n- name (TEXT)\n- model_type (TEXT) -- deepseek, qwen, custom\n- api_key (TEXT)\n- api_url (TEXT)\n- enabled (BOOLEAN)\n```\n\n**traders**\n```sql\n- id (TEXT PRIMARY KEY)\n- name (TEXT)\n- ai_model_id (INTEGER FK)\n- exchange_id (INTEGER FK)\n- initial_balance (REAL)\n- current_equity (REAL)\n- status (TEXT) -- running, stopped\n- created_at (DATETIME)\n```\n\n*(More details: database-schema.md - coming soon)*\n\n---\n\n## 🔌 API Reference\n\n### Authentication\n\n**POST /api/auth/login**\n```json\nRequest: {\n \"username\": \"string\",\n \"password\": \"string\",\n \"totp_code\": \"string\" // optional\n}\n\nResponse: {\n \"token\": \"jwt_token\",\n \"user\": {...}\n}\n```\n\n### Trader Management\n\n**GET /api/traders**\n```json\nResponse: {\n \"traders\": [\n {\n \"id\": \"string\",\n \"name\": \"string\",\n \"status\": \"running|stopped\",\n \"balance\": 1000.0,\n \"roi\": 5.2\n }\n ]\n}\n```\n\n*(Full API reference: api-reference.md - coming soon)*\n\n---\n\n## 🧪 Testing Architecture\n\n### Current State\n- ⚠️ No unit tests yet\n- ⚠️ Manual testing only\n- ⚠️ Testnet verification\n\n### Planned Testing Strategy\n\n**Unit Tests (Priority 1)**\n```\ntrader/binance_futures_test.go\n- Mock API responses\n- Test precision handling\n- Validate order construction\n```\n\n**Integration Tests (Priority 2)**\n```\n- End-to-end trading flow (testnet)\n- Multi-trader scenarios\n- Database operations\n```\n\n**Frontend Tests (Priority 3)**\n```\n- Component tests (Vitest + React Testing Library)\n- API integration tests\n- E2E tests (Playwright)\n```\n\n*(Testing guide: testing-guide.md - coming soon)*\n\n---\n\n## 🔧 Development Tools\n\n### Build & Run\n\n```bash\n# Backend\ngo build -o nofx\n./nofx\n\n# Frontend\ncd web\nnpm run dev\n\n# Docker\ndocker compose up --build\n```\n\n### Code Quality\n\n```bash\n# Format Go code\ngo fmt ./...\n\n# Lint (if configured)\ngolangci-lint run\n\n# Type check TypeScript\ncd web && npm run build\n```\n\n---\n\n## 📈 Performance Considerations\n\n### Backend\n- **Concurrency:** Each trader runs in separate goroutine\n- **Database:** SQLite (good for <100 traders)\n- **API Rate Limits:** Handled per exchange\n- **Memory:** ~50-100MB per trader\n\n### Frontend\n- **Data Fetching:** SWR with 5-10s polling\n- **State:** Zustand (lightweight)\n- **Bundle Size:** ~500KB (gzipped)\n\n---\n\n## 🔮 Future Architecture Plans\n\n### Planned Improvements\n\n1. **Microservices Split** (if scaling needed)\n - Separate decision engine service\n - Market data service\n - Execution service\n\n2. **Database Migration**\n - Mysql for production (>100 traders)\n - Redis for caching\n\n3. **Event-Driven Architecture**\n - WebSocket for real-time updates\n - Message queue (RabbitMQ/NATS)\n\n4. **Kubernetes Deployment**\n - Helm charts\n - Auto-scaling\n - High availability\n\n---\n\n## 🆘 For Developers\n\n**Want to contribute?**\n- Read [Contributing Guide](../../CONTRIBUTING.md)\n- Check [Open Issues](https://github.com/tinkle-community/nofx/issues)\n- Join [Telegram Community](https://t.me/nofx_dev_community)\n\n**Need clarification?**\n- Open a [GitHub Discussion](https://github.com/tinkle-community/nofx/discussions)\n- Ask in Telegram\n\n---\n\n## 📚 Related Documentation\n\n- [Getting Started](../getting-started/README.md) - Setup and deployment\n- [Contributing](../../CONTRIBUTING.md) - How to contribute\n- [Community](../community/README.md) - Bounties and recognition\n\n---\n\n[← Back to Documentation Home](../README.md)\n"}}}} |